From c6453584db34ecb714cf946f6d9877fb069e1a64 Mon Sep 17 00:00:00 2001 From: Simon <10131203+gaomeng1900@users.noreply.github.com> Date: Wed, 15 Oct 2025 16:19:52 +0800 Subject: [PATCH] feat: improve i18n for panel --- src/i18n/index.ts | 9 +++- src/i18n/locales.ts | 124 +++++++++++++++++++++++++++----------------- src/i18n/types.ts | 57 -------------------- src/ui/Panel.ts | 67 +++++++++++++----------- 4 files changed, 119 insertions(+), 138 deletions(-) delete mode 100644 src/i18n/types.ts diff --git a/src/i18n/index.ts b/src/i18n/index.ts index 211f0eb..7ef12e2 100644 --- a/src/i18n/index.ts +++ b/src/i18n/index.ts @@ -1,5 +1,10 @@ -import { type SupportedLanguage, locales } from './locales' -import type { TranslationKey, TranslationParams, TranslationSchema } from './types' +import { + type SupportedLanguage, + type TranslationKey, + type TranslationParams, + type TranslationSchema, + locales, +} from './locales' export class I18n { private language: SupportedLanguage diff --git a/src/i18n/locales.ts b/src/i18n/locales.ts index 51b1250..b94ae95 100644 --- a/src/i18n/locales.ts +++ b/src/i18n/locales.ts @@ -1,7 +1,55 @@ -import type { TranslationSchema } from './types' +// English translations (base/reference language) +const enUS = { + ui: { + panel: { + ready: 'Ready', + thinking: 'Thinking...', + paused: 'Paused', + taskInput: 'Enter new task, describe steps in detail, press Enter to submit', + userAnswerPrompt: 'Please answer the question above, press Enter to submit', + taskTerminated: 'Task terminated', + taskCompleted: 'Task completed', + continueExecution: 'Continue execution', + userAnswer: 'User answer: {{input}}', + question: 'Question: {{question}}', + waitingPlaceholder: 'Waiting for task to start...', + pause: 'Pause', + continue: 'Continue', + stop: 'Stop', + expand: 'Expand history', + collapse: 'Collapse history', + step: 'Step {{number}} · {{time}}{{duration}}', + }, + tools: { + clicking: 'Clicking element [{{index}}]...', + inputting: 'Inputting text to element [{{index}}]...', + selecting: 'Selecting option "{{text}}"...', + scrolling: 'Scrolling page...', + waiting: 'Waiting {{seconds}} seconds...', + done: 'Task done', + clicked: '🖱️ Clicked element [{{index}}]', + inputted: '⌨️ Inputted text "{{text}}"', + selected: '☑️ Selected option "{{text}}"', + scrolled: '🛞 Page scrolled', + waited: '⌛️ Wait completed', + executing: 'Executing {{toolName}}...', + resultSuccess: 'success', + resultFailure: 'failed', + resultError: 'error', + }, + errors: { + elementNotFound: 'No interactive element found at index {{index}}', + taskRequired: 'Task description is required', + executionFailed: 'Task execution failed', + notInputElement: 'Element is not an input or textarea', + notSelectElement: 'Element is not a select element', + optionNotFound: 'Option "{{text}}" not found', + }, + }, +} as const -// 中文翻译(作为基准) -const zhCN: TranslationSchema = { +// Chinese translations (must match the structure of enUS) +const zhCN = { ui: { panel: { ready: '准备就绪', @@ -13,6 +61,8 @@ const zhCN: TranslationSchema = { taskCompleted: '任务结束', continueExecution: '继续执行', userAnswer: '用户回答: {{input}}', + question: '询问: {{question}}', + waitingPlaceholder: '等待任务开始...', pause: '暂停', continue: '继续', stop: '终止', @@ -33,6 +83,9 @@ const zhCN: TranslationSchema = { scrolled: '🛞 页面滚动完成', waited: '⌛️ 等待完成', executing: '正在执行 {{toolName}}...', + resultSuccess: '成功', + resultFailure: '失败', + resultError: '错误', }, errors: { elementNotFound: '未找到索引为 {{index}} 的交互元素', @@ -45,54 +98,29 @@ const zhCN: TranslationSchema = { }, } as const -// 英文翻译(必须符合相同的结构) -const enUS: TranslationSchema = { - ui: { - panel: { - ready: 'Ready', - thinking: 'Thinking...', - paused: 'Paused', - taskInput: 'Enter new task, describe steps in detail, press Enter to submit', - userAnswerPrompt: 'Please answer the question above, press Enter to submit', - taskTerminated: 'Task terminated', - taskCompleted: 'Task completed', - continueExecution: 'Continue execution', - userAnswer: 'User answer: {{input}}', - pause: 'Pause', - continue: 'Continue', - stop: 'Stop', - expand: 'Expand history', - collapse: 'Collapse history', - step: 'Step {{number}} · {{time}}{{duration}}', - }, - tools: { - clicking: 'Clicking element [{{index}}]...', - inputting: 'Inputting text to element [{{index}}]...', - selecting: 'Selecting option "{{text}}"...', - scrolling: 'Scrolling page...', - waiting: 'Waiting {{seconds}} seconds...', - done: 'Task done', - clicked: '🖱️ Clicked element [{{index}}]', - inputted: '⌨️ Inputted text "{{text}}"', - selected: '☑️ Selected option "{{text}}"', - scrolled: '🛞 Page scrolled', - waited: '⌛️ Wait completed', - executing: '正在执行 {{toolName}}...', - }, - errors: { - elementNotFound: 'No interactive element found at index {{index}}', - taskRequired: 'Task description is required', - executionFailed: 'Task execution failed', - notInputElement: 'Element is not an input or textarea', - notSelectElement: 'Element is not a select element', - optionNotFound: 'Option "{{text}}" not found', - }, - }, -} as const +// Type definitions generated from English base structure (but with string values) +type DeepStringify = { + [K in keyof T]: T[K] extends string ? string : T[K] extends object ? DeepStringify : T[K] +} + +export type TranslationSchema = DeepStringify + +// Utility type: Extract all nested paths from translation object +type NestedKeyOf = { + [Key in keyof ObjectType & (string | number)]: ObjectType[Key] extends object + ? `${Key}` | `${Key}.${NestedKeyOf}` + : `${Key}` +}[keyof ObjectType & (string | number)] + +// Extract all possible key paths from translation structure +export type TranslationKey = NestedKeyOf + +// Parameterized translation types +export type TranslationParams = Record export const locales = { - 'zh-CN': zhCN, 'en-US': enUS, + 'zh-CN': zhCN, } as const export type SupportedLanguage = keyof typeof locales diff --git a/src/i18n/types.ts b/src/i18n/types.ts deleted file mode 100644 index dfbab4f..0000000 --- a/src/i18n/types.ts +++ /dev/null @@ -1,57 +0,0 @@ -// 定义翻译数据的结构类型 -export interface TranslationSchema { - ui: { - panel: { - ready: string - thinking: string - paused: string - taskInput: string - userAnswerPrompt: string - taskTerminated: string - taskCompleted: string - continueExecution: string - userAnswer: string - pause: string - continue: string - stop: string - expand: string - collapse: string - step: string - } - tools: { - clicking: string - inputting: string - selecting: string - scrolling: string - waiting: string - done: string - clicked: string - inputted: string - selected: string - scrolled: string - waited: string - executing: string - } - errors: { - elementNotFound: string - taskRequired: string - executionFailed: string - notInputElement: string - notSelectElement: string - optionNotFound: string - } - } -} - -// 工具类型:提取嵌套对象的所有路径 -type NestedKeyOf = { - [Key in keyof ObjectType & (string | number)]: ObjectType[Key] extends object - ? `${Key}` | `${Key}.${NestedKeyOf}` - : `${Key}` -}[keyof ObjectType & (string | number)] - -// 从翻译结构中提取所有可能的key路径 -export type TranslationKey = NestedKeyOf - -// 参数化翻译的类型 -export type TranslationParams = Record diff --git a/src/ui/Panel.ts b/src/ui/Panel.ts index 85c95d5..c6fe36d 100644 --- a/src/ui/Panel.ts +++ b/src/ui/Panel.ts @@ -70,10 +70,8 @@ export class Panel { // Update state to `running` this.#update({ type: 'output', - displayText: `询问: ${question}`, - }) - - // Expand history panel + displayText: this.#pageAgent.i18n.t('ui.panel.question', { question }), + }) // Expand history panel if (!this.#isExpanded) { this.#expand() } @@ -132,7 +130,7 @@ export class Panel { } /** - * 隐藏面板 + * Hide panel */ #hide(): void { this.wrapper.style.opacity = '0' @@ -141,7 +139,7 @@ export class Panel { } /** - * 重置状态 + * Reset state */ #reset(): void { this.#state.reset() @@ -168,44 +166,44 @@ export class Panel { // Update status display if (this.#pageAgent.paused) { - this.#statusText.textContent = '暂停中,稍后' + this.#statusText.textContent = this.#pageAgent.i18n.t('ui.panel.paused') this.#updateStatusIndicator('thinking') // Use existing thinking state } else { - this.#statusText.textContent = '继续执行' + this.#statusText.textContent = this.#pageAgent.i18n.t('ui.panel.continueExecution') this.#updateStatusIndicator('tool_executing') // Restore to execution state } } /** - * 更新暂停按钮状态 + * Update pause button state */ #updatePauseButton(): void { if (this.#pageAgent.paused) { this.#pauseButton.textContent = '▶' - this.#pauseButton.title = '继续' + this.#pauseButton.title = this.#pageAgent.i18n.t('ui.panel.continue') this.#pauseButton.classList.add(styles.paused) } else { this.#pauseButton.textContent = '⏸︎' - this.#pauseButton.title = '暂停' + this.#pauseButton.title = this.#pageAgent.i18n.t('ui.panel.pause') this.#pauseButton.classList.remove(styles.paused) } } /** - * 终止 Agent + * Stop Agent */ #stopAgent(): void { // Update status display this.#update({ type: 'error', - displayText: '任务已终止', + displayText: this.#pageAgent.i18n.t('ui.panel.taskTerminated'), }) this.#pageAgent.dispose() } /** - * 提交任务 + * Submit task */ #submitTask() { const input = this.#taskInput.value.trim() @@ -223,13 +221,13 @@ export class Panel { } /** - * 处理用户回答 + * Handle user answer */ #handleUserAnswer(input: string): void { // Add user input to history this.#update({ type: 'input', - displayText: `用户回答: ${input}`, + displayText: this.#pageAgent.i18n.t('ui.panel.userAnswer', { input }), }) // Reset state @@ -243,12 +241,12 @@ export class Panel { } /** - * 显示输入区域 + * Show input area */ #showInputArea(placeholder?: string): void { // Clear input field this.#taskInput.value = '' - this.#taskInput.placeholder = placeholder || '输入新任务,详细描述步骤,回车提交' + this.#taskInput.placeholder = placeholder || this.#pageAgent.i18n.t('ui.panel.taskInput') this.#inputSection.classList.remove(styles.hidden) // Focus on input field setTimeout(() => { @@ -257,14 +255,14 @@ export class Panel { } /** - * 隐藏输入区域 + * Hide input area */ #hideInputArea(): void { this.#inputSection.classList.add(styles.hidden) } /** - * 检查是否应该显示输入区域 + * Check if input area should be shown */ #shouldShowInputArea(): boolean { // Always show input area if waiting for user input @@ -294,23 +292,23 @@ export class Panel { stepNumber: 0, timestamp: new Date(), type: 'thinking', - displayText: '等待任务开始...', + displayText: this.#pageAgent.i18n.t('ui.panel.waitingPlaceholder'), })}
-
准备就绪
+
${this.#pageAgent.i18n.t('ui.panel.ready')}
- - -
@@ -459,11 +457,12 @@ export class Panel { if (step.type === 'completed') { // Check if this is a result from done tool if (step.toolName === 'done') { - // @todo not right // Judge success or failure based on result + const failureKeyword = this.#pageAgent.i18n.t('ui.tools.resultFailure') + const errorKeyword = this.#pageAgent.i18n.t('ui.tools.resultError') const isSuccess = !step.toolResult || - (!step.toolResult.includes('失败') && !step.toolResult.includes('错误')) + (!step.toolResult.includes(failureKeyword) && !step.toolResult.includes(errorKeyword)) typeClass = isSuccess ? styles.doneSuccess : styles.doneError statusIcon = isSuccess ? '🎉' : '❌' } else { @@ -488,6 +487,13 @@ export class Panel { statusIcon = '🧠' } + const durationText = step.duration ? ` · ${step.duration}ms` : '' + const stepLabel = this.#pageAgent.i18n.t('ui.panel.step', { + number: step.stepNumber.toString(), + time, + duration: durationText, + }) + return `
@@ -495,8 +501,7 @@ export class Panel { ${step.displayText}
- 步骤 ${step.stepNumber} · ${time} - ${step.duration ? ` · ${step.duration}ms` : ''} + ${stepLabel}
` @@ -504,7 +509,7 @@ export class Panel { } /** - * 获取工具执行时的显示文本 + * Get display text for tool execution */ export function getToolExecutingText(toolName: string, args: any, i18n: I18n): string { switch (toolName) { @@ -526,7 +531,7 @@ export function getToolExecutingText(toolName: string, args: any, i18n: I18n): s } /** - * 获取工具完成时的显示文本 + * Get display text for tool completion */ export function getToolCompletedText(toolName: string, args: any, i18n: I18n): string | null { switch (toolName) {