mirror of
https://github.com/RSSNext/Folo.git
synced 2026-10-17 05:08:13 +08:00
feat: add localization for scheduling options and labels
This commit is contained in:
@@ -136,6 +136,7 @@ For mobile, see @apps/mobile/CLAUDE.md
|
||||
3. For plural-sensitive languages, use `_one` and `_other` suffixes
|
||||
4. **Avoid conflicting flat keys** - During build, flat dot-separated keys (e.g., 'exif.custom.rendered.custom') are automatically converted to nested objects, which can cause conflicts. For example, 'exif.custom.rendered.custom' conflicts with 'exif.custom.rendered'. Avoid such patterns.
|
||||
5. **Never use `defaultValue` in translations** - Always add proper translations to all three required language files: `en.json`, `zh-CN.json`, and `ja.json` in the appropriate feature directories under `locales/`
|
||||
6. **Top-scope variables with translatable content** - Use `const` assertions (`as const`) instead of functions for type safety
|
||||
|
||||
Example:
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
import { cn } from "@follow/utils/utils"
|
||||
import dayjs from "dayjs"
|
||||
import { memo, useMemo } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
|
||||
import type { ScheduleType } from "~/modules/ai-task/types"
|
||||
|
||||
@@ -20,14 +21,14 @@ interface ScheduleConfigProps {
|
||||
}
|
||||
|
||||
const dayOfWeekOptions = [
|
||||
{ value: "0", label: "Sunday" },
|
||||
{ value: "1", label: "Monday" },
|
||||
{ value: "2", label: "Tuesday" },
|
||||
{ value: "3", label: "Wednesday" },
|
||||
{ value: "4", label: "Thursday" },
|
||||
{ value: "5", label: "Friday" },
|
||||
{ value: "6", label: "Saturday" },
|
||||
]
|
||||
{ value: "0", label: "schedule.days.sunday" },
|
||||
{ value: "1", label: "schedule.days.monday" },
|
||||
{ value: "2", label: "schedule.days.tuesday" },
|
||||
{ value: "3", label: "schedule.days.wednesday" },
|
||||
{ value: "4", label: "schedule.days.thursday" },
|
||||
{ value: "5", label: "schedule.days.friday" },
|
||||
{ value: "6", label: "schedule.days.saturday" },
|
||||
] as const
|
||||
|
||||
const dayOfMonthOptions = Array.from({ length: 31 }, (_, i) => ({
|
||||
value: (i + 1).toString(),
|
||||
@@ -37,50 +38,46 @@ const dayOfMonthOptions = Array.from({ length: 31 }, (_, i) => ({
|
||||
const frequencyOptions = [
|
||||
{
|
||||
value: "once",
|
||||
label: "Once",
|
||||
label: "schedule.frequency.once",
|
||||
icon: "i-mgc-time-cute-re",
|
||||
description: "Run one time only",
|
||||
},
|
||||
{
|
||||
value: "daily",
|
||||
label: "Daily",
|
||||
label: "schedule.frequency.daily",
|
||||
icon: "i-mgc-round-cute-re",
|
||||
description: "Run every day",
|
||||
},
|
||||
{
|
||||
value: "weekly",
|
||||
label: "Weekly",
|
||||
label: "schedule.frequency.weekly",
|
||||
icon: "i-mgc-layout-4-cute-re",
|
||||
description: "Run once per week",
|
||||
},
|
||||
{
|
||||
value: "monthly",
|
||||
label: "Monthly",
|
||||
label: "schedule.frequency.monthly",
|
||||
icon: "i-mgc-grid-cute-re",
|
||||
description: "Run once per month",
|
||||
},
|
||||
]
|
||||
] as const
|
||||
|
||||
// Quick preset options for common schedules
|
||||
const getQuickPresets = () => {
|
||||
const now = dayjs()
|
||||
return [
|
||||
{
|
||||
label: "Tomorrow 9AM",
|
||||
label: "schedule.presets.tomorrow_9am" as const,
|
||||
value: {
|
||||
type: "once" as const,
|
||||
date: now.add(1, "day").hour(9).minute(0).second(0).millisecond(0).toISOString(),
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "Daily 6PM",
|
||||
label: "schedule.presets.daily_6pm" as const,
|
||||
value: {
|
||||
type: "daily" as const,
|
||||
timeOfDay: now.hour(18).minute(0).second(0).millisecond(0).toISOString(),
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "Mon 9AM",
|
||||
label: "schedule.presets.monday_9am" as const,
|
||||
value: {
|
||||
type: "weekly" as const,
|
||||
dayOfWeek: 1,
|
||||
@@ -88,7 +85,7 @@ const getQuickPresets = () => {
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "1st 9AM",
|
||||
label: "schedule.presets.first_9am" as const,
|
||||
value: {
|
||||
type: "monthly" as const,
|
||||
dayOfMonth: 1,
|
||||
@@ -169,6 +166,7 @@ const getNextExecutions = (schedule: ScheduleType, count = 5): dayjs.Dayjs[] =>
|
||||
|
||||
// Compact Timeline Preview Component
|
||||
const TimelinePreview = memo<{ schedule: ScheduleType }>(({ schedule }) => {
|
||||
const { t } = useTranslation("ai")
|
||||
const executions = useMemo(() => getNextExecutions(schedule, 1), [schedule])
|
||||
const nextExecution = executions[0]
|
||||
|
||||
@@ -176,7 +174,7 @@ const TimelinePreview = memo<{ schedule: ScheduleType }>(({ schedule }) => {
|
||||
return (
|
||||
<div className="text-text-tertiary flex items-center gap-2 text-xs">
|
||||
<div className="i-mgc-information-cute-re size-3" />
|
||||
<span>No upcoming executions</span>
|
||||
<span>{t("schedule.no_upcoming")}</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -185,7 +183,10 @@ const TimelinePreview = memo<{ schedule: ScheduleType }>(({ schedule }) => {
|
||||
<div className="text-text-secondary flex items-center gap-2 text-xs">
|
||||
<div className="i-mgc-time-cute-re text-accent size-3" />
|
||||
<span>
|
||||
Next: {nextExecution.format("MMM D, h:mm A")} ({nextExecution.fromNow()})
|
||||
{t("schedule.next_execution", {
|
||||
time: nextExecution.format("MMM D, h:mm A"),
|
||||
relative: nextExecution.fromNow(),
|
||||
})}
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
@@ -195,6 +196,7 @@ TimelinePreview.displayName = "TimelinePreview"
|
||||
|
||||
export const ScheduleConfig = memo<ScheduleConfigProps>(
|
||||
({ value, onChange, errors = defaultErrors }) => {
|
||||
const { t } = useTranslation("ai")
|
||||
const now = useMemo(() => dayjs(), [])
|
||||
const quickPresets = useMemo(() => getQuickPresets(), [])
|
||||
|
||||
@@ -208,7 +210,9 @@ export const ScheduleConfig = memo<ScheduleConfigProps>(
|
||||
<div className="space-y-4">
|
||||
{/* Quick Presets */}
|
||||
<div className="space-y-2">
|
||||
<Label className="text-text pl-2 text-sm font-medium">Quick Presets</Label>
|
||||
<Label className="text-text pl-2 text-sm font-medium">
|
||||
{t("schedule.presets_title")}
|
||||
</Label>
|
||||
<div className="flex gap-2">
|
||||
{quickPresets.map((preset) => (
|
||||
<button
|
||||
@@ -217,7 +221,7 @@ export const ScheduleConfig = memo<ScheduleConfigProps>(
|
||||
onClick={() => onChange(preset.value)}
|
||||
className="bg-material-opaque hover:bg-fill-tertiary border-border/50 hover:border-border text-text-secondary hover:text-text flex-1 rounded-md border px-2 py-1.5 text-xs transition-all duration-200"
|
||||
>
|
||||
{preset.label}
|
||||
{t(preset.label)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
@@ -226,7 +230,7 @@ export const ScheduleConfig = memo<ScheduleConfigProps>(
|
||||
{/* Frequency Selection */}
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<Label className="text-text pl-2 text-sm font-medium">Schedule</Label>
|
||||
<Label className="text-text pl-2 text-sm font-medium">{t("schedule.title")}</Label>
|
||||
<TimelinePreview schedule={value} />
|
||||
</div>
|
||||
<div className="grid grid-cols-4 gap-2">
|
||||
@@ -272,7 +276,7 @@ export const ScheduleConfig = memo<ScheduleConfigProps>(
|
||||
scheduleType === option.value ? "text-accent" : "text-text-tertiary",
|
||||
)}
|
||||
/>
|
||||
<span className="font-medium">{option.label}</span>
|
||||
<span className="font-medium">{t(option.label)}</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
@@ -282,7 +286,9 @@ export const ScheduleConfig = memo<ScheduleConfigProps>(
|
||||
{/* Time Configuration */}
|
||||
{scheduleType === "once" && (
|
||||
<div className="space-y-2">
|
||||
<Label className="text-text pl-2 text-sm font-medium">Date & Time</Label>
|
||||
<Label className="text-text pl-2 text-sm font-medium">
|
||||
{t("schedule.date_time_label")}
|
||||
</Label>
|
||||
<DateTimePicker
|
||||
value={value.date}
|
||||
minDate={now.toISOString()}
|
||||
@@ -292,7 +298,7 @@ export const ScheduleConfig = memo<ScheduleConfigProps>(
|
||||
date,
|
||||
})
|
||||
}}
|
||||
placeholder="Select date & time"
|
||||
placeholder={t("schedule.date_time_placeholder")}
|
||||
/>
|
||||
{errors.date && <p className="text-red mt-2 text-sm">{errors.date}</p>}
|
||||
</div>
|
||||
@@ -300,7 +306,7 @@ export const ScheduleConfig = memo<ScheduleConfigProps>(
|
||||
|
||||
{scheduleType === "daily" && (
|
||||
<div className="space-y-2 pl-2">
|
||||
<Label className="text-text text-sm font-medium">Time</Label>
|
||||
<Label className="text-text text-sm font-medium">{t("schedule.time_label")}</Label>
|
||||
<TimeSelect
|
||||
value={dayjs(value.timeOfDay).format("HH:mm")}
|
||||
onChange={(time) => {
|
||||
@@ -324,10 +330,12 @@ export const ScheduleConfig = memo<ScheduleConfigProps>(
|
||||
|
||||
{scheduleType === "weekly" && (
|
||||
<div className="space-y-2 pl-2">
|
||||
<Label className="text-text text-sm font-medium">Configuration</Label>
|
||||
<Label className="text-text text-sm font-medium">
|
||||
{t("schedule.configuration_label")}
|
||||
</Label>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div className="space-y-1">
|
||||
<Label className="text-text-secondary text-xs">Day</Label>
|
||||
<Label className="text-text-secondary text-xs">{t("schedule.day_label")}</Label>
|
||||
<Select
|
||||
onValueChange={(dayOfWeek) =>
|
||||
updateSchedule({
|
||||
@@ -339,12 +347,12 @@ export const ScheduleConfig = memo<ScheduleConfigProps>(
|
||||
value={value.dayOfWeek.toString()}
|
||||
>
|
||||
<SelectTrigger className="bg-material-opaque hover:bg-mix-accent/background-1/4 h-6 justify-between rounded-[4px] border-0 px-1.5 py-0 text-xs">
|
||||
<SelectValue placeholder="Select day" />
|
||||
<SelectValue placeholder={t("schedule.day_placeholder")} />
|
||||
</SelectTrigger>
|
||||
<SelectContent position="item-aligned">
|
||||
{dayOfWeekOptions.map((option) => (
|
||||
<SelectItem key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
{t(option.label)}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
@@ -352,7 +360,7 @@ export const ScheduleConfig = memo<ScheduleConfigProps>(
|
||||
{errors.dayOfWeek && <p className="text-red text-sm">{errors.dayOfWeek}</p>}
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<Label className="text-text-secondary text-xs">Time</Label>
|
||||
<Label className="text-text-secondary text-xs">{t("schedule.time_label")}</Label>
|
||||
<TimeSelect
|
||||
value={dayjs(value.timeOfDay).format("HH:mm")}
|
||||
onChange={(time) => {
|
||||
@@ -379,10 +387,12 @@ export const ScheduleConfig = memo<ScheduleConfigProps>(
|
||||
|
||||
{scheduleType === "monthly" && (
|
||||
<div className="space-y-2 pl-2">
|
||||
<Label className="text-text text-sm font-medium">Configuration</Label>
|
||||
<Label className="text-text text-sm font-medium">
|
||||
{t("schedule.configuration_label")}
|
||||
</Label>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div className="space-y-1">
|
||||
<Label className="text-text-secondary text-xs">Day</Label>
|
||||
<Label className="text-text-secondary text-xs">{t("schedule.day_label")}</Label>
|
||||
<Select
|
||||
onValueChange={(dayOfMonth) =>
|
||||
updateSchedule({
|
||||
@@ -394,7 +404,7 @@ export const ScheduleConfig = memo<ScheduleConfigProps>(
|
||||
value={value.dayOfMonth.toString()}
|
||||
>
|
||||
<SelectTrigger className="bg-material-opaque hover:bg-mix-accent/background-1/4 h-6 justify-between rounded-[4px] border-0 px-1.5 py-0 text-xs">
|
||||
<SelectValue placeholder="Select day" />
|
||||
<SelectValue placeholder={t("schedule.day_placeholder")} />
|
||||
</SelectTrigger>
|
||||
<SelectContent position="item-aligned">
|
||||
{dayOfMonthOptions.map((option) => (
|
||||
@@ -407,7 +417,7 @@ export const ScheduleConfig = memo<ScheduleConfigProps>(
|
||||
{errors.dayOfMonth && <p className="text-red text-sm">{errors.dayOfMonth}</p>}
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<Label className="text-text-secondary text-xs">Time</Label>
|
||||
<Label className="text-text-secondary text-xs">{t("schedule.time_label")}</Label>
|
||||
<TimeSelect
|
||||
value={dayjs(value.timeOfDay).format("HH:mm")}
|
||||
onChange={(time) => {
|
||||
|
||||
@@ -68,6 +68,32 @@
|
||||
"quick_actions.questions": "Ask questions",
|
||||
"quick_actions.simplify": "Simplify this",
|
||||
"quick_actions.takeaways": "Key takeaways",
|
||||
"schedule.configuration_label": "Configuration",
|
||||
"schedule.date_time_label": "Date & Time",
|
||||
"schedule.date_time_placeholder": "Select date & time",
|
||||
"schedule.day_label": "Day",
|
||||
"schedule.day_placeholder": "Select day",
|
||||
"schedule.days.friday": "Friday",
|
||||
"schedule.days.monday": "Monday",
|
||||
"schedule.days.saturday": "Saturday",
|
||||
"schedule.days.sunday": "Sunday",
|
||||
"schedule.days.thursday": "Thursday",
|
||||
"schedule.days.tuesday": "Tuesday",
|
||||
"schedule.days.wednesday": "Wednesday",
|
||||
"schedule.frequency.daily": "Daily",
|
||||
"schedule.frequency.monthly": "Monthly",
|
||||
"schedule.frequency.once": "Once",
|
||||
"schedule.frequency.weekly": "Weekly",
|
||||
"schedule.next_execution": "Next: {{time}} ({{relative}})",
|
||||
"schedule.no_upcoming": "No upcoming executions",
|
||||
"schedule.presets.daily_6pm": "Daily 6PM",
|
||||
"schedule.presets.first_9am": "1st 9AM",
|
||||
"schedule.presets.monday_9am": "Mon 9AM",
|
||||
"schedule.presets.tomorrow_9am": "Tomorrow 9AM",
|
||||
"schedule.presets_title": "Quick Presets",
|
||||
"schedule.time_label": "Time",
|
||||
"schedule.time_placeholder": "Select time",
|
||||
"schedule.title": "Schedule",
|
||||
"settings.autoScrollWhenStreaming.description": "Automatically scroll to the bottom of the chat when streaming",
|
||||
"settings.autoScrollWhenStreaming.label": "Auto scroll when streaming",
|
||||
"settings.description": "Configure your AI experience and create custom shortcuts",
|
||||
|
||||
@@ -23,6 +23,32 @@
|
||||
"quick_actions.questions": "提出问题",
|
||||
"quick_actions.simplify": "简化内容",
|
||||
"quick_actions.takeaways": "关键要点",
|
||||
"schedule.configuration_label": "配置",
|
||||
"schedule.date_time_label": "日期和时间",
|
||||
"schedule.date_time_placeholder": "选择日期和时间",
|
||||
"schedule.day_label": "日期",
|
||||
"schedule.day_placeholder": "选择日期",
|
||||
"schedule.days.friday": "星期五",
|
||||
"schedule.days.monday": "星期一",
|
||||
"schedule.days.saturday": "星期六",
|
||||
"schedule.days.sunday": "星期日",
|
||||
"schedule.days.thursday": "星期四",
|
||||
"schedule.days.tuesday": "星期二",
|
||||
"schedule.days.wednesday": "星期三",
|
||||
"schedule.frequency.daily": "每日",
|
||||
"schedule.frequency.monthly": "每月",
|
||||
"schedule.frequency.once": "单次",
|
||||
"schedule.frequency.weekly": "每周",
|
||||
"schedule.next_execution": "下次执行:{{time}} ({{relative}})",
|
||||
"schedule.no_upcoming": "没有即将执行的任务",
|
||||
"schedule.presets.daily_6pm": "每天下午6点",
|
||||
"schedule.presets.first_9am": "1号上午9点",
|
||||
"schedule.presets.monday_9am": "周一上午9点",
|
||||
"schedule.presets.tomorrow_9am": "明天上午9点",
|
||||
"schedule.presets_title": "快速预设",
|
||||
"schedule.time_label": "时间",
|
||||
"schedule.time_placeholder": "选择时间",
|
||||
"schedule.title": "计划",
|
||||
"settings.autoScrollWhenStreaming.description": "在流式传输时自动滚动到聊天底部",
|
||||
"settings.autoScrollWhenStreaming.label": "流式传输时自动滚动",
|
||||
"settings.description": "配置您的AI体验并创建自定义快捷方式",
|
||||
|
||||
Reference in New Issue
Block a user