From fa7a6e18d804dc2d058a799b46b53f44556fa5ce Mon Sep 17 00:00:00 2001 From: lawvs <18554747+lawvs@users.noreply.github.com> Date: Fri, 26 Sep 2025 16:29:02 +0800 Subject: [PATCH] feat(i18n): add translations for task management features - Implemented internationalization for task scheduling, actions, and modal titles. - Updated existing components to utilize translation functions for better localization support. --- .../modules/ai-task/components/task-item.tsx | 80 +++++++++++-------- .../modules/ai-task/components/task-list.tsx | 8 +- .../renderer/src/modules/settings/tabs/ai.tsx | 2 +- .../tabs/ai/tasks/TaskSchedulingSection.tsx | 16 ++-- locales/ai/en.json | 36 +++++++++ locales/ai/ja.json | 36 +++++++++ locales/ai/zh-CN.json | 36 +++++++++ 7 files changed, 167 insertions(+), 47 deletions(-) diff --git a/apps/desktop/layer/renderer/src/modules/ai-task/components/task-item.tsx b/apps/desktop/layer/renderer/src/modules/ai-task/components/task-item.tsx index 0feb0ece6..ac23436ef 100644 --- a/apps/desktop/layer/renderer/src/modules/ai-task/components/task-item.tsx +++ b/apps/desktop/layer/renderer/src/modules/ai-task/components/task-item.tsx @@ -1,6 +1,7 @@ import { cn } from "@follow/utils/utils" import type { AITask, TaskSchedule } from "@follow-app/client-sdk" import dayjs from "dayjs" +import type { TFunction } from "i18next" import { memo, useCallback, useMemo, useState } from "react" import { useTranslation } from "react-i18next" import { toast } from "sonner" @@ -20,29 +21,42 @@ import { } from "../query" import { AITaskModal } from "./ai-task-modal" -const formatScheduleText = (schedule: TaskSchedule) => { - if (!schedule) return "Unknown schedule" - +const formatScheduleText = (schedule: TaskSchedule, t: TFunction<"ai", undefined>) => { + if (!schedule) return t("tasks.schedule.unknown") switch (schedule.type) { case "once": { const date = dayjs(schedule.date) - return `Once on ${date.format("MMM D, YYYY")} at ${date.format("h:mm A")}` + return t("tasks.schedule.once", { + date: date.format("MMM D, YYYY"), + time: date.format("h:mm A"), + }) } case "daily": { const time = dayjs(schedule.timeOfDay) - return `Daily at ${time.format("h:mm A")}` + return t("tasks.schedule.daily", { time: time.format("h:mm A") }) } case "weekly": { - const days = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"] + const days = [ + t("days.sunday", { ns: "common" }), + t("days.monday", { ns: "common" }), + t("days.tuesday", { ns: "common" }), + t("days.wednesday", { ns: "common" }), + t("days.thursday", { ns: "common" }), + t("days.friday", { ns: "common" }), + t("days.saturday", { ns: "common" }), + ] const time = dayjs(schedule.timeOfDay) - return `Weekly on ${days[schedule.dayOfWeek]} at ${time.format("h:mm A")}` + return t("tasks.schedule.weekly", { + day: days[schedule.dayOfWeek], + time: time.format("h:mm A"), + }) } case "monthly": { const time = dayjs(schedule.timeOfDay) - return `Monthly on day ${schedule.dayOfMonth} at ${time.format("h:mm A")}` + return t("tasks.schedule.monthly", { day: schedule.dayOfMonth, time: time.format("h:mm A") }) } default: { - return "Unknown schedule" + return t("tasks.schedule.unknown") } } } @@ -86,7 +100,7 @@ export const TaskItem = memo(({ task }: { task: AITask }) => { const updateTaskMutation = useUpdateAITaskMutation() const testRunMutation = useTestRunAITaskMutation() const { ask } = useDialog() - const { t } = useTranslation() + const { t } = useTranslation("ai") const sessions = useAIChatSessionListQuery() // const chatActions = useChatActions() const [openingReport, setOpeningReport] = useState(false) @@ -100,7 +114,7 @@ export const TaskItem = memo(({ task }: { task: AITask }) => { const handleEditTask = (task: AITask) => { present({ - title: "Edit AI Task", + title: t("tasks.modal.edit_title"), content: () => , }) } @@ -109,7 +123,7 @@ export const TaskItem = memo(({ task }: { task: AITask }) => { const handleOpenReport = useCallback(async () => { if (!taskSession) { - toast.error("No report session found for this task yet.") + toast.error(t("tasks.toast.no_report")) return } setOpeningReport(true) @@ -117,7 +131,7 @@ export const TaskItem = memo(({ task }: { task: AITask }) => { await AIChatSessionService.fetchAndPersistMessages(taskSession) } catch (e) { console.error("Failed to sync chat session messages:", e) - toast.error("Failed to load chat messages") + toast.error(t("tasks.toast.load_failed")) } setAIPanelVisibility(true) const chatActions = ChatSliceActions.getActiveInstance() @@ -126,8 +140,8 @@ export const TaskItem = memo(({ task }: { task: AITask }) => { } chatActions?.switchToChat(taskSession.chatId) setOpeningReport(false) - toast("Switch to the chat panel to view reports.") - }, [taskSession]) + toast(t("tasks.toast.switch_to_chat")) + }, [taskSession, t]) const actions: ActionButton[] = [ // Only show if the task has at least one run @@ -136,7 +150,7 @@ export const TaskItem = memo(({ task }: { task: AITask }) => { { icon: "i-mgc-history-cute-re" as const, onClick: handleOpenReport, - title: "View reports", + title: t("tasks.actions.view_reports"), loading: openingReport, disabled: openingReport, } satisfies ActionButton, @@ -146,34 +160,32 @@ export const TaskItem = memo(({ task }: { task: AITask }) => { icon: "i-mgc-test-tube-cute-re" as const, onClick: async () => { try { - const loadingId = toast.loading( - "Starting test run… This may take about 1 minute. You can view the result in the Chat panel.", - ) + const loadingId = toast.loading(t("tasks.toast.test_start")) await testRunMutation.mutateAsync({ id: task.id }) - toast.success("Test run finished. You can view the report in the Chat panel.", { + toast.success(t("tasks.toast.test_success"), { id: loadingId, }) } catch (error) { console.error("Failed to run test:", error) - toast.error("Failed to run test. Please try again.") + toast.error(t("tasks.toast.test_failed")) } }, - title: "Test run", + title: t("tasks.actions.test_run"), disabled: testRunMutation.isPending, loading: testRunMutation.isPending, }, { icon: "i-mgc-edit-cute-re", onClick: () => handleEditTask(task), - title: "Edit task", + title: t("tasks.actions.edit_task"), }, { icon: "i-mgc-delete-2-cute-re", onClick: async () => { const confirmed = await ask({ - title: "Delete Task", + title: t("tasks.modal.delete_title"), // translation fallback pattern; primary key then default string - message: `Are you sure you want to delete the task "${task.name}"?`, + message: t("tasks.modal.delete_confirm", { name: task.name }), confirmText: t("words.delete", { ns: "common" }), cancelText: t("words.cancel", { ns: "common" }), variant: "danger", @@ -181,13 +193,13 @@ export const TaskItem = memo(({ task }: { task: AITask }) => { if (!confirmed) return try { await deleteTaskMutation.mutateAsync({ id: task.id }) - toast.success("Task deleted successfully") + toast.success(t("tasks.toast.delete_success")) } catch (error) { console.error("Failed to delete task:", error) - toast.error("Failed to delete task. Please try again.") + toast.error(t("tasks.toast.delete_failed")) } }, - title: "Delete task", + title: t("tasks.actions.delete_task"), disabled: isDeleting, loading: isDeleting, }, @@ -210,20 +222,20 @@ export const TaskItem = memo(({ task }: { task: AITask }) => { )} {status === "paused" && } - {status.charAt(0).toUpperCase() + status.slice(1)} + {t(`tasks.status.${status}`)}

- Schedule:{" "} - {formatScheduleText(task.schedule)} + {t("tasks.fields.schedule")}{" "} + {formatScheduleText(task.schedule, t)}

- Prompt: {task.prompt} + {t("tasks.fields.prompt")} {task.prompt}

{task.createdAt && (

- Created:{" "} + {t("tasks.fields.created")}{" "} {dayjs(task.createdAt).format("MMM D, YYYY h:mm A")}

)} @@ -238,7 +250,7 @@ export const TaskItem = memo(({ task }: { task: AITask }) => { await updateTaskMutation.mutateAsync({ id: task.id, isEnabled: !task.isEnabled }) } catch (error) { console.error("Failed to toggle task:", error) - toast.error("Failed to update task. Please try again.") + toast.error(t("tasks.toast.update_failed")) } }} /> diff --git a/apps/desktop/layer/renderer/src/modules/ai-task/components/task-list.tsx b/apps/desktop/layer/renderer/src/modules/ai-task/components/task-list.tsx index d1e6b8537..0cd1c9336 100644 --- a/apps/desktop/layer/renderer/src/modules/ai-task/components/task-list.tsx +++ b/apps/desktop/layer/renderer/src/modules/ai-task/components/task-list.tsx @@ -1,5 +1,6 @@ import { cn } from "@follow/utils/utils" import { memo } from "react" +import { useTranslation } from "react-i18next" import { useAITaskListQuery } from "../query" import { TaskItem } from "./task-item" @@ -10,6 +11,7 @@ interface TaskListProps { export const AITaskList = memo(({ className }) => { const tasks = useAITaskListQuery() + const { t } = useTranslation("ai") if (tasks === undefined) { return null @@ -21,10 +23,8 @@ export const AITaskList = memo(({ className }) => {
-

No scheduled tasks

-

- Create your first AI task to automate your workflows. -

+

{t("tasks.empty.title")}

+

{t("tasks.empty.desc")}

) } diff --git a/apps/desktop/layer/renderer/src/modules/settings/tabs/ai.tsx b/apps/desktop/layer/renderer/src/modules/settings/tabs/ai.tsx index 6e63bdf72..5ce84d086 100644 --- a/apps/desktop/layer/renderer/src/modules/settings/tabs/ai.tsx +++ b/apps/desktop/layer/renderer/src/modules/settings/tabs/ai.tsx @@ -60,7 +60,7 @@ export const SettingAI = () => { , { type: "title", - value: "Task Scheduling", + value: t("tasks.section.title"), }, TaskSchedulingSection, AISecurityDisclosureSection, diff --git a/apps/desktop/layer/renderer/src/modules/settings/tabs/ai/tasks/TaskSchedulingSection.tsx b/apps/desktop/layer/renderer/src/modules/settings/tabs/ai/tasks/TaskSchedulingSection.tsx index aad5c4bf5..a2e778db2 100644 --- a/apps/desktop/layer/renderer/src/modules/settings/tabs/ai/tasks/TaskSchedulingSection.tsx +++ b/apps/desktop/layer/renderer/src/modules/settings/tabs/ai/tasks/TaskSchedulingSection.tsx @@ -1,6 +1,7 @@ import { Button } from "@follow/components/ui/button/index.js" import { Label } from "@follow/components/ui/label/index.js" import { useCallback } from "react" +import { useTranslation } from "react-i18next" import { useModalStack } from "~/components/ui/modal/stacked/hooks" import { AITaskList, AITaskModal, useCanCreateNewAITask } from "~/modules/ai-task" @@ -8,25 +9,24 @@ import { AITaskList, AITaskModal, useCanCreateNewAITask } from "~/modules/ai-tas export const TaskSchedulingSection = () => { const { present } = useModalStack() const canCreateNewTask = useCanCreateNewAITask() + const { t } = useTranslation("ai") const handleCreateTask = useCallback(() => { present({ - title: "New AI Task", + title: t("tasks.modal.new_title"), content: () => , }) - }, [present]) + }, [present, t]) return (
- +
- {"Create and manage automated AI tasks that run on your schedule."} + {t("tasks.manage.desc")} {!canCreateNewTask && ( - - {" (Limit reached: maximum number of tasks reached)"} - + {t("tasks.manage.limit_reached")} )}
@@ -38,7 +38,7 @@ export const TaskSchedulingSection = () => { onClick={handleCreateTask} > - New Task + {t("tasks.actions.new_task")}
diff --git a/locales/ai/en.json b/locales/ai/en.json index 9a6080362..4b82274db 100644 --- a/locales/ai/en.json +++ b/locales/ai/en.json @@ -181,10 +181,27 @@ "shortcuts.updated": "Shortcut updated successfully", "shortcuts.validation.required": "Name and prompt are required", "summary_not_available": "Summary not available", + "tasks.actions.delete_task": "Delete task", + "tasks.actions.edit_task": "Edit task", + "tasks.actions.new_task": "New Task", "tasks.actions.schedule": "Schedule Task", "tasks.actions.scheduling": "Scheduling...", + "tasks.actions.test_run": "Test run", "tasks.actions.update": "Update Task", "tasks.actions.updating": "Updating...", + "tasks.actions.view_reports": "View reports", + "tasks.empty.desc": "Create your first AI task to automate your workflows.", + "tasks.empty.title": "No scheduled tasks", + "tasks.fields.created": "Created:", + "tasks.fields.prompt": "Prompt:", + "tasks.fields.schedule": "Schedule:", + "tasks.manage.desc": "Create and manage automated AI tasks that run on your schedule.", + "tasks.manage.limit_reached": "(Limit reached: maximum number of tasks reached)", + "tasks.manage.title": "Schedule AI Tasks", + "tasks.modal.delete_confirm": "Are you sure you want to delete the task \"{{name}}\"?", + "tasks.modal.delete_title": "Delete Task", + "tasks.modal.edit_title": "Edit AI Task", + "tasks.modal.new_title": "New AI Task", "tasks.name": "Task Name", "tasks.name_placeholder": "Enter a descriptive name for your task...", "tasks.notify.coming_soon": "Coming soon", @@ -193,13 +210,32 @@ "tasks.prompt": "Prompt", "tasks.prompt_helper": "Provide clear, specific instructions for the AI to execute", "tasks.prompt_placeholder": "Describe what you want the AI to do when this task runs...", + "tasks.schedule.daily": "Daily at {{time}}", + "tasks.schedule.monthly": "Monthly on day {{day}} at {{time}}", + "tasks.schedule.once": "Once on {{date}} at {{time}}", + "tasks.schedule.unknown": "Unknown schedule", + "tasks.schedule.weekly": "Weekly on {{day}} at {{time}}", "tasks.section.info": "Task Information", "tasks.section.instructions": "AI Instructions", "tasks.section.notifications": "Notifications", "tasks.section.schedule": "Schedule Configuration", + "tasks.section.title": "AI Task", + "tasks.status.completed": "Completed", + "tasks.status.paused": "Paused", + "tasks.status.scheduled": "Scheduled", + "tasks.status.unknown": "Unknown", "tasks.toast.create_error": "Failed to schedule AI task. Please try again.", "tasks.toast.created": "AI task scheduled successfully", + "tasks.toast.delete_failed": "Failed to delete task. Please try again.", + "tasks.toast.delete_success": "Task deleted successfully", + "tasks.toast.load_failed": "Failed to load chat messages", + "tasks.toast.no_report": "No report session found for this task yet.", + "tasks.toast.switch_to_chat": "Switch to the chat panel to view reports.", + "tasks.toast.test_failed": "Failed to run test. Please try again.", + "tasks.toast.test_start": "Starting test run… This may take about 1 minute. You can view the result in the Chat panel.", + "tasks.toast.test_success": "Test run finished. You can view the report in the Chat panel.", "tasks.toast.update_error": "Failed to update AI task. Please try again.", + "tasks.toast.update_failed": "Failed to update task. Please try again.", "tasks.toast.updated": "AI task updated successfully", "tasks.validation.date_future": "Scheduled date must be in the future", "tasks.validation.prompt_max": "Prompt must be less than 2000 characters", diff --git a/locales/ai/ja.json b/locales/ai/ja.json index 90beb531e..18433d268 100644 --- a/locales/ai/ja.json +++ b/locales/ai/ja.json @@ -147,10 +147,27 @@ "shortcuts.updated": "ショートカットが正常に更新されました", "shortcuts.validation.required": "名前とプロンプトは必須です", "summary_not_available": "要約は利用できません", + "tasks.actions.delete_task": "タスクを削除", + "tasks.actions.edit_task": "タスクを編集", + "tasks.actions.new_task": "新規タスク", "tasks.actions.schedule": "タスクをスケジュール", "tasks.actions.scheduling": "スケジュール中...", + "tasks.actions.test_run": "テスト実行", "tasks.actions.update": "タスクを更新", "tasks.actions.updating": "更新中...", + "tasks.actions.view_reports": "レポートを見る", + "tasks.empty.desc": "最初の AI タスクを作成してワークフローを自動化しましょう。", + "tasks.empty.title": "予定されたタスクはありません", + "tasks.fields.created": "作成日時:", + "tasks.fields.prompt": "プロンプト:", + "tasks.fields.schedule": "スケジュール:", + "tasks.manage.desc": "スケジュールに従って自動実行される AI タスクを作成・管理します。", + "tasks.manage.limit_reached": "(最大タスク数に達しました)", + "tasks.manage.title": "AI タスクをスケジュール", + "tasks.modal.delete_confirm": "タスク \"{{name}}\" を削除してもよろしいですか?", + "tasks.modal.delete_title": "タスクを削除", + "tasks.modal.edit_title": "AI タスクを編集", + "tasks.modal.new_title": "新規 AI タスク", "tasks.name": "タスク名", "tasks.name_placeholder": "タスクのわかりやすい名前を入力...", "tasks.notify.coming_soon": "近日公開", @@ -159,13 +176,32 @@ "tasks.prompt": "プロンプト", "tasks.prompt_helper": "AIが実行するための明確で具体的な指示を提供してください", "tasks.prompt_placeholder": "このタスクが実行されるときにAIに何をさせたいかを記述...", + "tasks.schedule.daily": "毎日 {{time}}", + "tasks.schedule.monthly": "毎月 {{day}} 日 {{time}}", + "tasks.schedule.once": "{{date}} {{time}} 単回", + "tasks.schedule.unknown": "不明なスケジュール", + "tasks.schedule.weekly": "毎週{{day}} {{time}}", "tasks.section.info": "タスク情報", "tasks.section.instructions": "AI 指示", "tasks.section.notifications": "通知", "tasks.section.schedule": "スケジュール設定", + "tasks.section.title": "AI タスク", + "tasks.status.completed": "完了", + "tasks.status.paused": "一時停止", + "tasks.status.scheduled": "予定済み", + "tasks.status.unknown": "不明", "tasks.toast.create_error": "AIタスクのスケジュールに失敗しました。もう一度お試しください。", "tasks.toast.created": "AIタスクが正常にスケジュールされました", + "tasks.toast.delete_failed": "タスクの削除に失敗しました。もう一度お試しください。", + "tasks.toast.delete_success": "タスクが削除されました", + "tasks.toast.load_failed": "チャットメッセージの読み込みに失敗しました", + "tasks.toast.no_report": "このタスクのレポートセッションはまだありません。", + "tasks.toast.switch_to_chat": "レポートを見るにはチャットパネルに切り替えてください。", + "tasks.toast.test_failed": "テスト実行に失敗しました。もう一度お試しください。", + "tasks.toast.test_start": "テスト実行を開始…約1分かかる場合があります。結果はチャットパネルで確認できます。", + "tasks.toast.test_success": "テスト実行が完了しました。レポートはチャットパネルで確認できます。", "tasks.toast.update_error": "AIタスクの更新に失敗しました。もう一度お試しください。", + "tasks.toast.update_failed": "タスクの更新に失敗しました。もう一度お試しください。", "tasks.toast.updated": "AIタスクが正常に更新されました", "tasks.validation.date_future": "スケジュール日時は未来である必要があります", "tasks.validation.prompt_max": "プロンプトは2000文字未満である必要があります", diff --git a/locales/ai/zh-CN.json b/locales/ai/zh-CN.json index 1657eb431..a0ca2187b 100644 --- a/locales/ai/zh-CN.json +++ b/locales/ai/zh-CN.json @@ -175,10 +175,27 @@ "shortcuts.updated": "快捷方式更新成功", "shortcuts.validation.required": "名称和提示为必填项", "summary_not_available": "摘要不可用", + "tasks.actions.delete_task": "删除任务", + "tasks.actions.edit_task": "编辑任务", + "tasks.actions.new_task": "新建任务", "tasks.actions.schedule": "安排任务", "tasks.actions.scheduling": "正在安排...", + "tasks.actions.test_run": "测试运行", "tasks.actions.update": "更新任务", "tasks.actions.updating": "正在更新...", + "tasks.actions.view_reports": "查看报告", + "tasks.empty.desc": "创建您的第一个 AI 任务以自动化您的工作流程。", + "tasks.empty.title": "暂无计划任务", + "tasks.fields.created": "创建时间:", + "tasks.fields.prompt": "指令:", + "tasks.fields.schedule": "计划:", + "tasks.manage.desc": "创建并管理按计划自动运行的 AI 任务。", + "tasks.manage.limit_reached": "(已达到最大任务数量限制)", + "tasks.manage.title": "计划 AI 任务", + "tasks.modal.delete_confirm": "确定要删除任务 \"{{name}}\" 吗?", + "tasks.modal.delete_title": "删除任务", + "tasks.modal.edit_title": "编辑 AI 任务", + "tasks.modal.new_title": "新建 AI 任务", "tasks.name": "任务名称", "tasks.name_placeholder": "输入一个描述性的任务名称...", "tasks.notify.coming_soon": "即将推出", @@ -187,13 +204,32 @@ "tasks.prompt": "指令", "tasks.prompt_helper": "请提供清晰、具体的指令供 AI 执行", "tasks.prompt_placeholder": "描述当此任务运行时您希望 AI 做什么...", + "tasks.schedule.daily": "每天 {{time}}", + "tasks.schedule.monthly": "每月第 {{day}} 天 {{time}}", + "tasks.schedule.once": "{{date}} {{time}} 单次", + "tasks.schedule.unknown": "未知计划", + "tasks.schedule.weekly": "每周{{day}} {{time}}", "tasks.section.info": "任务信息", "tasks.section.instructions": "AI 指令", "tasks.section.notifications": "通知", "tasks.section.schedule": "计划配置", + "tasks.section.title": "AI 任务", + "tasks.status.completed": "已完成", + "tasks.status.paused": "已暂停", + "tasks.status.scheduled": "已计划", + "tasks.status.unknown": "未知", "tasks.toast.create_error": "安排 AI 任务失败,请重试。", "tasks.toast.created": "AI 任务安排成功", + "tasks.toast.delete_failed": "删除任务失败,请重试。", + "tasks.toast.delete_success": "任务删除成功", + "tasks.toast.load_failed": "加载聊天消息失败", + "tasks.toast.no_report": "此任务尚无报告会话。", + "tasks.toast.switch_to_chat": "切换到聊天面板查看报告。", + "tasks.toast.test_failed": "测试运行失败,请重试。", + "tasks.toast.test_start": "开始测试运行…大约需要 1 分钟。您可以在聊天面板查看结果。", + "tasks.toast.test_success": "测试运行完成。您可以在聊天面板查看报告。", "tasks.toast.update_error": "更新 AI 任务失败,请重试。", + "tasks.toast.update_failed": "更新任务失败,请重试。", "tasks.toast.updated": "AI 任务更新成功", "tasks.validation.date_future": "计划时间必须是未来的时间", "tasks.validation.prompt_max": "指令必须少于 2000 个字符",