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.
This commit is contained in:
lawvs
2025-09-26 16:29:15 +08:00
parent 1a91d57cf0
commit fa7a6e18d8
7 changed files with 167 additions and 47 deletions
@@ -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: () => <AITaskModal task={task} />,
})
}
@@ -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 }) => {
<i className="i-mgc-calendar-time-add-cute-re mr-1 size-3" />
)}
{status === "paused" && <i className="i-mgc-pause-cute-re mr-1 size-3" />}
{status.charAt(0).toUpperCase() + status.slice(1)}
<span>{t(`tasks.status.${status}`)}</span>
</span>
</div>
<div className="space-y-1">
<p className="text-text-secondary text-xs">
<span className="text-text-tertiary">Schedule:</span>{" "}
{formatScheduleText(task.schedule)}
<span className="text-text-tertiary">{t("tasks.fields.schedule")}</span>{" "}
{formatScheduleText(task.schedule, t)}
</p>
<p className="text-text-secondary text-xs">
<span className="text-text-tertiary">Prompt:</span> {task.prompt}
<span className="text-text-tertiary">{t("tasks.fields.prompt")}</span> {task.prompt}
</p>
{task.createdAt && (
<p className="text-text-secondary text-xs">
<span className="text-text-tertiary">Created:</span>{" "}
<span className="text-text-tertiary">{t("tasks.fields.created")}</span>{" "}
{dayjs(task.createdAt).format("MMM D, YYYY h:mm A")}
</p>
)}
@@ -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"))
}
}}
/>
@@ -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<TaskListProps>(({ className }) => {
const tasks = useAITaskListQuery()
const { t } = useTranslation("ai")
if (tasks === undefined) {
return null
@@ -21,10 +23,8 @@ export const AITaskList = memo<TaskListProps>(({ className }) => {
<div className="bg-fill-secondary mx-auto mb-3 flex size-12 items-center justify-center rounded-full">
<i className="i-mgc-calendar-time-add-cute-re text-text size-6" />
</div>
<h4 className="text-text mb-1 text-sm font-medium">No scheduled tasks</h4>
<p className="text-text-secondary text-xs">
Create your first AI task to automate your workflows.
</p>
<h4 className="text-text mb-1 text-sm font-medium">{t("tasks.empty.title")}</h4>
<p className="text-text-secondary text-xs">{t("tasks.empty.desc")}</p>
</div>
)
}
@@ -60,7 +60,7 @@ export const SettingAI = () => {
<Divider key="task-scheduling-divider" />,
{
type: "title",
value: "Task Scheduling",
value: t("tasks.section.title"),
},
TaskSchedulingSection,
AISecurityDisclosureSection,
@@ -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: () => <AITaskModal />,
})
}, [present])
}, [present, t])
return (
<div className="space-y-4">
<div className="flex items-center justify-between">
<div className="space-y-1">
<Label className="text-text text-sm font-medium">{"Schedule AI Tasks"}</Label>
<Label className="text-text text-sm font-medium">{t("tasks.manage.title")}</Label>
<div className="text-text-secondary text-xs">
{"Create and manage automated AI tasks that run on your schedule."}
{t("tasks.manage.desc")}
{!canCreateNewTask && (
<span className="text-red">
{" (Limit reached: maximum number of tasks reached)"}
</span>
<span className="text-red"> {t("tasks.manage.limit_reached")}</span>
)}
</div>
</div>
@@ -38,7 +38,7 @@ export const TaskSchedulingSection = () => {
onClick={handleCreateTask}
>
<i className="i-mgc-add-cute-re mr-2 size-4" />
New Task
{t("tasks.actions.new_task")}
</Button>
</div>
+36
View File
@@ -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",
+36
View File
@@ -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文字未満である必要があります",
+36
View File
@@ -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 个字符",