feat: Add i18n support for /settings, /theme

This commit is contained in:
pomelo-nwu
2025-11-18 14:08:41 +08:00
parent 12bebe1b5f
commit 32982a16e6
12 changed files with 323 additions and 44 deletions

View File

@@ -6,6 +6,7 @@
import { themeManager } from '../ui/themes/theme-manager.js'; import { themeManager } from '../ui/themes/theme-manager.js';
import { type LoadedSettings } from '../config/settings.js'; import { type LoadedSettings } from '../config/settings.js';
import { t } from '../i18n/index.js';
/** /**
* Validates the configured theme. * Validates the configured theme.
@@ -15,7 +16,9 @@ import { type LoadedSettings } from '../config/settings.js';
export function validateTheme(settings: LoadedSettings): string | null { export function validateTheme(settings: LoadedSettings): string | null {
const effectiveTheme = settings.merged.ui?.theme; const effectiveTheme = settings.merged.ui?.theme;
if (effectiveTheme && !themeManager.findThemeByName(effectiveTheme)) { if (effectiveTheme && !themeManager.findThemeByName(effectiveTheme)) {
return `Theme "${effectiveTheme}" not found.`; return t('Theme "{{themeName}}" not found.', {
themeName: effectiveTheme,
});
} }
return null; return null;
} }

View File

@@ -18,6 +18,11 @@ export default {
'@': '@', '@': '@',
'@src/myFile.ts': '@src/myFile.ts', '@src/myFile.ts': '@src/myFile.ts',
'Shell mode': 'Shell mode', 'Shell mode': 'Shell mode',
'YOLO mode': 'YOLO mode',
'plan mode': 'plan mode',
'auto-accept edits': 'auto-accept edits',
'Accepting edits': 'Accepting edits',
'(shift + tab to cycle)': '(shift + tab to cycle)',
'Execute shell commands via {{symbol}} (e.g., {{example1}}) or use natural language (e.g., {{example2}}).': 'Execute shell commands via {{symbol}} (e.g., {{example1}}) or use natural language (e.g., {{example2}}).':
'Execute shell commands via {{symbol}} (e.g., {{example1}}) or use natural language (e.g., {{example2}}).', 'Execute shell commands via {{symbol}} (e.g., {{example1}}) or use natural language (e.g., {{example2}}).',
'!': '!', '!': '!',
@@ -80,10 +85,23 @@ export default {
'Analyzes the project and creates a tailored QWEN.md file.', 'Analyzes the project and creates a tailored QWEN.md file.',
'list available Qwen Code tools. Usage: /tools [desc]': 'list available Qwen Code tools. Usage: /tools [desc]':
'list available Qwen Code tools. Usage: /tools [desc]', 'list available Qwen Code tools. Usage: /tools [desc]',
'Available Qwen Code CLI tools:': 'Available Qwen Code CLI tools:',
'No tools available': 'No tools available',
'View or change the approval mode for tool usage': 'View or change the approval mode for tool usage':
'View or change the approval mode for tool usage', 'View or change the approval mode for tool usage',
'View or change the language setting': 'View or change the language setting', 'View or change the language setting': 'View or change the language setting',
'change the theme': 'change the theme', 'change the theme': 'change the theme',
'Select Theme': 'Select Theme',
Preview: 'Preview',
'(Use Enter to select, Tab to configure scope)':
'(Use Enter to select, Tab to configure scope)',
'(Use Enter to apply scope, Tab to select theme)':
'(Use Enter to apply scope, Tab to select theme)',
'Theme configuration unavailable due to NO_COLOR env variable.':
'Theme configuration unavailable due to NO_COLOR env variable.',
'Theme "{{themeName}}" not found.': 'Theme "{{themeName}}" not found.',
'Theme "{{themeName}}" not found in selected scope.':
'Theme "{{themeName}}" not found in selected scope.',
'clear the screen and conversation history': 'clear the screen and conversation history':
'clear the screen and conversation history', 'clear the screen and conversation history',
'Compresses the context by replacing it with a summary.': 'Compresses the context by replacing it with a summary.':
@@ -236,6 +254,71 @@ export default {
// Commands - General (continued) // Commands - General (continued)
// ============================================================================ // ============================================================================
'View and edit Qwen Code settings': 'View and edit Qwen Code settings', 'View and edit Qwen Code settings': 'View and edit Qwen Code settings',
Settings: 'Settings',
'(Use Enter to select{{tabText}})': '(Use Enter to select{{tabText}})',
', Tab to change focus': ', Tab to change focus',
'To see changes, Qwen Code must be restarted. Press r to exit and apply changes now.':
'To see changes, Qwen Code must be restarted. Press r to exit and apply changes now.',
// ============================================================================
// Settings Labels
// ============================================================================
'Vim Mode': 'Vim Mode',
'Disable Auto Update': 'Disable Auto Update',
'Enable Prompt Completion': 'Enable Prompt Completion',
'Debug Keystroke Logging': 'Debug Keystroke Logging',
Language: 'Language',
'Output Format': 'Output Format',
'Hide Window Title': 'Hide Window Title',
'Show Status in Title': 'Show Status in Title',
'Hide Tips': 'Hide Tips',
'Hide Banner': 'Hide Banner',
'Hide Context Summary': 'Hide Context Summary',
'Hide CWD': 'Hide CWD',
'Hide Sandbox Status': 'Hide Sandbox Status',
'Hide Model Info': 'Hide Model Info',
'Hide Footer': 'Hide Footer',
'Show Memory Usage': 'Show Memory Usage',
'Show Line Numbers': 'Show Line Numbers',
'Show Citations': 'Show Citations',
'Custom Witty Phrases': 'Custom Witty Phrases',
'Enable Welcome Back': 'Enable Welcome Back',
'Disable Loading Phrases': 'Disable Loading Phrases',
'Screen Reader Mode': 'Screen Reader Mode',
'IDE Mode': 'IDE Mode',
'Max Session Turns': 'Max Session Turns',
'Skip Next Speaker Check': 'Skip Next Speaker Check',
'Skip Loop Detection': 'Skip Loop Detection',
'Skip Startup Context': 'Skip Startup Context',
'Enable OpenAI Logging': 'Enable OpenAI Logging',
'OpenAI Logging Directory': 'OpenAI Logging Directory',
Timeout: 'Timeout',
'Max Retries': 'Max Retries',
'Disable Cache Control': 'Disable Cache Control',
'Memory Discovery Max Dirs': 'Memory Discovery Max Dirs',
'Load Memory From Include Directories':
'Load Memory From Include Directories',
'Respect .gitignore': 'Respect .gitignore',
'Respect .qwenignore': 'Respect .qwenignore',
'Enable Recursive File Search': 'Enable Recursive File Search',
'Disable Fuzzy Search': 'Disable Fuzzy Search',
'Enable Interactive Shell': 'Enable Interactive Shell',
'Show Color': 'Show Color',
'Auto Accept': 'Auto Accept',
'Use Ripgrep': 'Use Ripgrep',
'Use Builtin Ripgrep': 'Use Builtin Ripgrep',
'Enable Tool Output Truncation': 'Enable Tool Output Truncation',
'Tool Output Truncation Threshold': 'Tool Output Truncation Threshold',
'Tool Output Truncation Lines': 'Tool Output Truncation Lines',
'Folder Trust': 'Folder Trust',
'Vision Model Preview': 'Vision Model Preview',
// Settings enum options
'Auto (detect from system)': 'Auto (detect from system)',
Text: 'Text',
JSON: 'JSON',
Plan: 'Plan',
Default: 'Default',
'Auto Edit': 'Auto Edit',
YOLO: 'YOLO',
'toggle vim mode on/off': 'toggle vim mode on/off', 'toggle vim mode on/off': 'toggle vim mode on/off',
'check session stats. Usage: /stats [model|tools]': 'check session stats. Usage: /stats [model|tools]':
'check session stats. Usage: /stats [model|tools]', 'check session stats. Usage: /stats [model|tools]',
@@ -534,6 +617,33 @@ export default {
'Failed to compress chat history.': 'Failed to compress chat history.', 'Failed to compress chat history.': 'Failed to compress chat history.',
'Failed to compress chat history: {{error}}': 'Failed to compress chat history: {{error}}':
'Failed to compress chat history: {{error}}', 'Failed to compress chat history: {{error}}',
'Compressing chat history': 'Compressing chat history',
'Chat history compressed from {{originalTokens}} to {{newTokens}} tokens.':
'Chat history compressed from {{originalTokens}} to {{newTokens}} tokens.',
'Compression was not beneficial for this history size.':
'Compression was not beneficial for this history size.',
'Chat history compression did not reduce size. This may indicate issues with the compression prompt.':
'Chat history compression did not reduce size. This may indicate issues with the compression prompt.',
'Could not compress chat history due to a token counting error.':
'Could not compress chat history due to a token counting error.',
'Chat history is already compressed.': 'Chat history is already compressed.',
// ============================================================================
// Commands - Directory
// ============================================================================
'Configuration is not available.': 'Configuration is not available.',
'Please provide at least one path to add.':
'Please provide at least one path to add.',
'The /directory add command is not supported in restrictive sandbox profiles. Please use --include-directories when starting the session instead.':
'The /directory add command is not supported in restrictive sandbox profiles. Please use --include-directories when starting the session instead.',
"Error adding '{{path}}': {{error}}": "Error adding '{{path}}': {{error}}",
'Successfully added GEMINI.md files from the following directories if there are:\n- {{directories}}':
'Successfully added GEMINI.md files from the following directories if there are:\n- {{directories}}',
'Error refreshing memory: {{error}}': 'Error refreshing memory: {{error}}',
'Successfully added directories:\n- {{directories}}':
'Successfully added directories:\n- {{directories}}',
'Current workspace directories:\n{{directories}}':
'Current workspace directories:\n{{directories}}',
// ============================================================================ // ============================================================================
// Commands - Docs // Commands - Docs

View File

@@ -17,6 +17,11 @@ export default {
'@': '@', '@': '@',
'@src/myFile.ts': '@src/myFile.ts', '@src/myFile.ts': '@src/myFile.ts',
'Shell mode': 'Shell 模式', 'Shell mode': 'Shell 模式',
'YOLO mode': 'YOLO 模式',
'plan mode': '规划模式',
'auto-accept edits': '自动接受编辑',
'Accepting edits': '接受编辑',
'(shift + tab to cycle)': '(shift + tab 切换)',
'Execute shell commands via {{symbol}} (e.g., {{example1}}) or use natural language (e.g., {{example2}}).': 'Execute shell commands via {{symbol}} (e.g., {{example1}}) or use natural language (e.g., {{example2}}).':
'通过 {{symbol}} 执行 shell 命令(例如,{{example1}})或使用自然语言(例如,{{example2}}', '通过 {{symbol}} 执行 shell 命令(例如,{{example1}})或使用自然语言(例如,{{example2}}',
'!': '!', '!': '!',
@@ -79,10 +84,23 @@ export default {
'分析项目并创建定制的 QWEN.md 文件', '分析项目并创建定制的 QWEN.md 文件',
'list available Qwen Code tools. Usage: /tools [desc]': 'list available Qwen Code tools. Usage: /tools [desc]':
'列出可用的 Qwen Code 工具。用法:/tools [desc]', '列出可用的 Qwen Code 工具。用法:/tools [desc]',
'Available Qwen Code CLI tools:': '可用的 Qwen Code CLI 工具:',
'No tools available': '没有可用工具',
'View or change the approval mode for tool usage': 'View or change the approval mode for tool usage':
'查看或更改工具使用的审批模式', '查看或更改工具使用的审批模式',
'View or change the language setting': '查看或更改语言设置', 'View or change the language setting': '查看或更改语言设置',
'change the theme': '更改主题', 'change the theme': '更改主题',
'Select Theme': '选择主题',
Preview: '预览',
'(Use Enter to select, Tab to configure scope)':
'(使用 Enter 选择Tab 配置作用域)',
'(Use Enter to apply scope, Tab to select theme)':
'(使用 Enter 应用作用域Tab 选择主题)',
'Theme configuration unavailable due to NO_COLOR env variable.':
'由于 NO_COLOR 环境变量,主题配置不可用。',
'Theme "{{themeName}}" not found.': '未找到主题 "{{themeName}}"。',
'Theme "{{themeName}}" not found in selected scope.':
'在所选作用域中未找到主题 "{{themeName}}"。',
'clear the screen and conversation history': '清屏并清除对话历史', 'clear the screen and conversation history': '清屏并清除对话历史',
'Compresses the context by replacing it with a summary.': 'Compresses the context by replacing it with a summary.':
'通过用摘要替换来压缩上下文', '通过用摘要替换来压缩上下文',
@@ -227,6 +245,70 @@ export default {
// Commands - General (continued) // Commands - General (continued)
// ============================================================================ // ============================================================================
'View and edit Qwen Code settings': '查看和编辑 Qwen Code 设置', 'View and edit Qwen Code settings': '查看和编辑 Qwen Code 设置',
Settings: '设置',
'(Use Enter to select{{tabText}})': '(使用 Enter 选择{{tabText}}',
', Tab to change focus': 'Tab 切换焦点',
'To see changes, Qwen Code must be restarted. Press r to exit and apply changes now.':
'要查看更改,必须重启 Qwen Code。按 r 退出并立即应用更改。',
// ============================================================================
// Settings Labels
// ============================================================================
'Vim Mode': 'Vim 模式',
'Disable Auto Update': '禁用自动更新',
'Enable Prompt Completion': '启用提示补全',
'Debug Keystroke Logging': '调试按键记录',
Language: '语言',
'Output Format': '输出格式',
'Hide Window Title': '隐藏窗口标题',
'Show Status in Title': '在标题中显示状态',
'Hide Tips': '隐藏提示',
'Hide Banner': '隐藏横幅',
'Hide Context Summary': '隐藏上下文摘要',
'Hide CWD': '隐藏当前工作目录',
'Hide Sandbox Status': '隐藏沙箱状态',
'Hide Model Info': '隐藏模型信息',
'Hide Footer': '隐藏页脚',
'Show Memory Usage': '显示内存使用',
'Show Line Numbers': '显示行号',
'Show Citations': '显示引用',
'Custom Witty Phrases': '自定义诙谐短语',
'Enable Welcome Back': '启用欢迎回来',
'Disable Loading Phrases': '禁用加载短语',
'Screen Reader Mode': '屏幕阅读器模式',
'IDE Mode': 'IDE 模式',
'Max Session Turns': '最大会话轮次',
'Skip Next Speaker Check': '跳过下一个说话者检查',
'Skip Loop Detection': '跳过循环检测',
'Skip Startup Context': '跳过启动上下文',
'Enable OpenAI Logging': '启用 OpenAI 日志',
'OpenAI Logging Directory': 'OpenAI 日志目录',
Timeout: '超时',
'Max Retries': '最大重试次数',
'Disable Cache Control': '禁用缓存控制',
'Memory Discovery Max Dirs': '内存发现最大目录数',
'Load Memory From Include Directories': '从包含目录加载内存',
'Respect .gitignore': '遵守 .gitignore',
'Respect .qwenignore': '遵守 .qwenignore',
'Enable Recursive File Search': '启用递归文件搜索',
'Disable Fuzzy Search': '禁用模糊搜索',
'Enable Interactive Shell': '启用交互式 Shell',
'Show Color': '显示颜色',
'Auto Accept': '自动接受',
'Use Ripgrep': '使用 Ripgrep',
'Use Builtin Ripgrep': '使用内置 Ripgrep',
'Enable Tool Output Truncation': '启用工具输出截断',
'Tool Output Truncation Threshold': '工具输出截断阈值',
'Tool Output Truncation Lines': '工具输出截断行数',
'Folder Trust': '文件夹信任',
'Vision Model Preview': '视觉模型预览',
// Settings enum options
'Auto (detect from system)': '自动(从系统检测)',
Text: '文本',
JSON: 'JSON',
Plan: '规划',
Default: '默认',
'Auto Edit': '自动编辑',
YOLO: 'YOLO',
'toggle vim mode on/off': '切换 vim 模式开关', 'toggle vim mode on/off': '切换 vim 模式开关',
'check session stats. Usage: /stats [model|tools]': 'check session stats. Usage: /stats [model|tools]':
'检查会话统计信息。用法:/stats [model|tools]', '检查会话统计信息。用法:/stats [model|tools]',
@@ -336,7 +418,7 @@ export default {
'Scope subcommands do not accept additional arguments.': 'Scope subcommands do not accept additional arguments.':
'作用域子命令不接受额外参数', '作用域子命令不接受额外参数',
'Plan mode - Analyze only, do not modify files or execute commands': 'Plan mode - Analyze only, do not modify files or execute commands':
'划模式 - 仅分析,不修改文件或执行命令', '划模式 - 仅分析,不修改文件或执行命令',
'Default mode - Require approval for file edits or shell commands': 'Default mode - Require approval for file edits or shell commands':
'默认模式 - 需要批准文件编辑或 shell 命令', '默认模式 - 需要批准文件编辑或 shell 命令',
'Auto-edit mode - Automatically approve file edits': 'Auto-edit mode - Automatically approve file edits':
@@ -502,6 +584,32 @@ export default {
'正在压缩中,请等待上一个请求完成', '正在压缩中,请等待上一个请求完成',
'Failed to compress chat history.': '压缩聊天历史失败', 'Failed to compress chat history.': '压缩聊天历史失败',
'Failed to compress chat history: {{error}}': '压缩聊天历史失败:{{error}}', 'Failed to compress chat history: {{error}}': '压缩聊天历史失败:{{error}}',
'Compressing chat history': '正在压缩聊天历史',
'Chat history compressed from {{originalTokens}} to {{newTokens}} tokens.':
'聊天历史已从 {{originalTokens}} 个 token 压缩到 {{newTokens}} 个 token。',
'Compression was not beneficial for this history size.':
'对于此历史记录大小,压缩没有益处。',
'Chat history compression did not reduce size. This may indicate issues with the compression prompt.':
'聊天历史压缩未能减小大小。这可能表明压缩提示存在问题。',
'Could not compress chat history due to a token counting error.':
'由于 token 计数错误,无法压缩聊天历史。',
'Chat history is already compressed.': '聊天历史已经压缩。',
// ============================================================================
// Commands - Directory
// ============================================================================
'Configuration is not available.': '配置不可用。',
'Please provide at least one path to add.': '请提供至少一个要添加的路径。',
'The /directory add command is not supported in restrictive sandbox profiles. Please use --include-directories when starting the session instead.':
'/directory add 命令在限制性沙箱配置文件中不受支持。请改为在启动会话时使用 --include-directories。',
"Error adding '{{path}}': {{error}}": "添加 '{{path}}' 时出错:{{error}}",
'Successfully added GEMINI.md files from the following directories if there are:\n- {{directories}}':
'如果存在,已成功从以下目录添加 GEMINI.md 文件:\n- {{directories}}',
'Error refreshing memory: {{error}}': '刷新内存时出错:{{error}}',
'Successfully added directories:\n- {{directories}}':
'成功添加目录:\n- {{directories}}',
'Current workspace directories:\n{{directories}}':
'当前工作区目录:\n{{directories}}',
// ============================================================================ // ============================================================================
// Commands - Docs // Commands - Docs
@@ -593,8 +701,8 @@ export default {
'(Use Enter to Set Auth)': '(使用 Enter 设置认证)', '(Use Enter to Set Auth)': '(使用 Enter 设置认证)',
'Terms of Services and Privacy Notice for Qwen Code': 'Terms of Services and Privacy Notice for Qwen Code':
'Qwen Code 的服务条款和隐私声明', 'Qwen Code 的服务条款和隐私声明',
'Qwen OAuth': 'Qwen OAuth (推荐)', 'Qwen OAuth': 'Qwen OAuth (免费)',
OpenAI: 'OpenAI (兼容 API)', OpenAI: 'OpenAI',
'Failed to login. Message: {{message}}': '登录失败。消息:{{message}}', 'Failed to login. Message: {{message}}': '登录失败。消息:{{message}}',
'Authentication is enforced to be {{enforcedType}}, but you are currently using {{currentType}}.': 'Authentication is enforced to be {{enforcedType}}, but you are currently using {{currentType}}.':
'认证方式被强制设置为 {{enforcedType}},但您当前使用的是 {{currentType}}', '认证方式被强制设置为 {{enforcedType}},但您当前使用的是 {{currentType}}',

View File

@@ -52,7 +52,7 @@ export const directoryCommand: SlashCommand = {
addItem( addItem(
{ {
type: MessageType.ERROR, type: MessageType.ERROR,
text: 'Configuration is not available.', text: t('Configuration is not available.'),
}, },
Date.now(), Date.now(),
); );
@@ -69,7 +69,7 @@ export const directoryCommand: SlashCommand = {
addItem( addItem(
{ {
type: MessageType.ERROR, type: MessageType.ERROR,
text: 'Please provide at least one path to add.', text: t('Please provide at least one path to add.'),
}, },
Date.now(), Date.now(),
); );
@@ -80,8 +80,9 @@ export const directoryCommand: SlashCommand = {
return { return {
type: 'message' as const, type: 'message' as const,
messageType: 'error' as const, messageType: 'error' as const,
content: content: t(
'The /directory add command is not supported in restrictive sandbox profiles. Please use --include-directories when starting the session instead.', 'The /directory add command is not supported in restrictive sandbox profiles. Please use --include-directories when starting the session instead.',
),
}; };
} }
@@ -94,7 +95,12 @@ export const directoryCommand: SlashCommand = {
added.push(pathToAdd.trim()); added.push(pathToAdd.trim());
} catch (e) { } catch (e) {
const error = e as Error; const error = e as Error;
errors.push(`Error adding '${pathToAdd.trim()}': ${error.message}`); errors.push(
t("Error adding '{{path}}': {{error}}", {
path: pathToAdd.trim(),
error: error.message,
}),
);
} }
} }
@@ -123,12 +129,21 @@ export const directoryCommand: SlashCommand = {
addItem( addItem(
{ {
type: MessageType.INFO, type: MessageType.INFO,
text: `Successfully added GEMINI.md files from the following directories if there are:\n- ${added.join('\n- ')}`, text: t(
'Successfully added GEMINI.md files from the following directories if there are:\n- {{directories}}',
{
directories: added.join('\n- '),
},
),
}, },
Date.now(), Date.now(),
); );
} catch (error) { } catch (error) {
errors.push(`Error refreshing memory: ${(error as Error).message}`); errors.push(
t('Error refreshing memory: {{error}}', {
error: (error as Error).message,
}),
);
} }
if (added.length > 0) { if (added.length > 0) {
@@ -139,7 +154,9 @@ export const directoryCommand: SlashCommand = {
addItem( addItem(
{ {
type: MessageType.INFO, type: MessageType.INFO,
text: `Successfully added directories:\n- ${added.join('\n- ')}`, text: t('Successfully added directories:\n- {{directories}}', {
directories: added.join('\n- '),
}),
}, },
Date.now(), Date.now(),
); );
@@ -169,7 +186,7 @@ export const directoryCommand: SlashCommand = {
addItem( addItem(
{ {
type: MessageType.ERROR, type: MessageType.ERROR,
text: 'Configuration is not available.', text: t('Configuration is not available.'),
}, },
Date.now(), Date.now(),
); );
@@ -181,7 +198,9 @@ export const directoryCommand: SlashCommand = {
addItem( addItem(
{ {
type: MessageType.INFO, type: MessageType.INFO,
text: `Current workspace directories:\n${directoryList}`, text: t('Current workspace directories:\n{{directories}}', {
directories: directoryList,
}),
}, },
Date.now(), Date.now(),
); );

View File

@@ -8,6 +8,7 @@ import type React from 'react';
import { Box, Text } from 'ink'; import { Box, Text } from 'ink';
import { theme } from '../semantic-colors.js'; import { theme } from '../semantic-colors.js';
import { ApprovalMode } from '@qwen-code/qwen-code-core'; import { ApprovalMode } from '@qwen-code/qwen-code-core';
import { t } from '../../i18n/index.js';
interface AutoAcceptIndicatorProps { interface AutoAcceptIndicatorProps {
approvalMode: ApprovalMode; approvalMode: ApprovalMode;
@@ -23,18 +24,18 @@ export const AutoAcceptIndicator: React.FC<AutoAcceptIndicatorProps> = ({
switch (approvalMode) { switch (approvalMode) {
case ApprovalMode.PLAN: case ApprovalMode.PLAN:
textColor = theme.status.success; textColor = theme.status.success;
textContent = 'plan mode'; textContent = t('plan mode');
subText = ' (shift + tab to cycle)'; subText = ` ${t('(shift + tab to cycle)')}`;
break; break;
case ApprovalMode.AUTO_EDIT: case ApprovalMode.AUTO_EDIT:
textColor = theme.status.warning; textColor = theme.status.warning;
textContent = 'auto-accept edits'; textContent = t('auto-accept edits');
subText = ' (shift + tab to cycle)'; subText = ` ${t('(shift + tab to cycle)')}`;
break; break;
case ApprovalMode.YOLO: case ApprovalMode.YOLO:
textColor = theme.status.error; textColor = theme.status.error;
textContent = 'YOLO mode'; textContent = t('YOLO mode');
subText = ' (shift + tab to cycle)'; subText = ` ${t('(shift + tab to cycle)')}`;
break; break;
case ApprovalMode.DEFAULT: case ApprovalMode.DEFAULT:
default: default:

View File

@@ -28,6 +28,7 @@ import {
parseInputForHighlighting, parseInputForHighlighting,
buildSegmentsForVisualSlice, buildSegmentsForVisualSlice,
} from '../utils/highlight.js'; } from '../utils/highlight.js';
import { t } from '../../i18n/index.js';
import { import {
clipboardHasImage, clipboardHasImage,
saveClipboardImage, saveClipboardImage,
@@ -833,13 +834,13 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
let statusText = ''; let statusText = '';
if (shellModeActive) { if (shellModeActive) {
statusColor = theme.ui.symbol; statusColor = theme.ui.symbol;
statusText = 'Shell mode'; statusText = t('Shell mode');
} else if (showYoloStyling) { } else if (showYoloStyling) {
statusColor = theme.status.error; statusColor = theme.status.error;
statusText = 'YOLO mode'; statusText = t('YOLO mode');
} else if (showAutoAcceptStyling) { } else if (showAutoAcceptStyling) {
statusColor = theme.status.warning; statusColor = theme.status.warning;
statusText = 'Accepting edits'; statusText = t('Accepting edits');
} }
return ( return (

View File

@@ -11,6 +11,7 @@ import type { LoadedSettings, Settings } from '../../config/settings.js';
import { SettingScope } from '../../config/settings.js'; import { SettingScope } from '../../config/settings.js';
import { getScopeMessageForSetting } from '../../utils/dialogScopeUtils.js'; import { getScopeMessageForSetting } from '../../utils/dialogScopeUtils.js';
import { ScopeSelector } from './shared/ScopeSelector.js'; import { ScopeSelector } from './shared/ScopeSelector.js';
import { t } from '../../i18n/index.js';
import { import {
getDialogSettingKeys, getDialogSettingKeys,
setPendingSettingValue, setPendingSettingValue,
@@ -124,7 +125,9 @@ export function SettingsDialog({
const definition = getSettingDefinition(key); const definition = getSettingDefinition(key);
return { return {
label: definition?.label || key, label: definition?.label
? t(definition.label) || definition.label
: key,
value: key, value: key,
type: definition?.type, type: definition?.type,
toggle: () => { toggle: () => {
@@ -779,7 +782,8 @@ export function SettingsDialog({
> >
<Box flexDirection="column" flexGrow={1}> <Box flexDirection="column" flexGrow={1}>
<Text bold={focusSection === 'settings'} wrap="truncate"> <Text bold={focusSection === 'settings'} wrap="truncate">
{focusSection === 'settings' ? '> ' : ' '}Settings {focusSection === 'settings' ? '> ' : ' '}
{t('Settings')}
</Text> </Text>
<Box height={1} /> <Box height={1} />
{showScrollUp && <Text color={theme.text.secondary}></Text>} {showScrollUp && <Text color={theme.text.secondary}></Text>}
@@ -916,13 +920,15 @@ export function SettingsDialog({
<Box height={1} /> <Box height={1} />
<Text color={theme.text.secondary}> <Text color={theme.text.secondary}>
(Use Enter to select {t('(Use Enter to select{{tabText}})', {
{showScopeSelection ? ', Tab to change focus' : ''}) tabText: showScopeSelection ? t(', Tab to change focus') : '',
})}
</Text> </Text>
{showRestartPrompt && ( {showRestartPrompt && (
<Text color={theme.status.warning}> <Text color={theme.status.warning}>
To see changes, Qwen Code must be restarted. Press r to exit and {t(
apply changes now. 'To see changes, Qwen Code must be restarted. Press r to exit and apply changes now.',
)}
</Text> </Text>
)} )}
</Box> </Box>

View File

@@ -17,6 +17,7 @@ import { SettingScope } from '../../config/settings.js';
import { getScopeMessageForSetting } from '../../utils/dialogScopeUtils.js'; import { getScopeMessageForSetting } from '../../utils/dialogScopeUtils.js';
import { useKeypress } from '../hooks/useKeypress.js'; import { useKeypress } from '../hooks/useKeypress.js';
import { ScopeSelector } from './shared/ScopeSelector.js'; import { ScopeSelector } from './shared/ScopeSelector.js';
import { t } from '../../i18n/index.js';
interface ThemeDialogProps { interface ThemeDialogProps {
/** Callback function when a theme is selected */ /** Callback function when a theme is selected */
@@ -198,7 +199,8 @@ export function ThemeDialog({
{/* Left Column: Selection */} {/* Left Column: Selection */}
<Box flexDirection="column" width="45%" paddingRight={2}> <Box flexDirection="column" width="45%" paddingRight={2}>
<Text bold={mode === 'theme'} wrap="truncate"> <Text bold={mode === 'theme'} wrap="truncate">
{mode === 'theme' ? '> ' : ' '}Select Theme{' '} {mode === 'theme' ? '> ' : ' '}
{t('Select Theme')}{' '}
<Text color={theme.text.secondary}> <Text color={theme.text.secondary}>
{otherScopeModifiedMessage} {otherScopeModifiedMessage}
</Text> </Text>
@@ -218,7 +220,7 @@ export function ThemeDialog({
{/* Right Column: Preview */} {/* Right Column: Preview */}
<Box flexDirection="column" width="55%" paddingLeft={2}> <Box flexDirection="column" width="55%" paddingLeft={2}>
<Text bold color={theme.text.primary}> <Text bold color={theme.text.primary}>
Preview {t('Preview')}
</Text> </Text>
{/* Get the Theme object for the highlighted theme, fall back to default if not found */} {/* Get the Theme object for the highlighted theme, fall back to default if not found */}
{(() => { {(() => {
@@ -274,8 +276,9 @@ def fibonacci(n):
)} )}
<Box marginTop={1}> <Box marginTop={1}>
<Text color={theme.text.secondary} wrap="truncate"> <Text color={theme.text.secondary} wrap="truncate">
(Use Enter to {mode === 'theme' ? 'select' : 'apply scope'}, Tab to{' '} {mode === 'theme'
{mode === 'theme' ? 'configure scope' : 'select theme'}) ? t('(Use Enter to select, Tab to configure scope)')
: t('(Use Enter to apply scope, Tab to select theme)')}
</Text> </Text>
</Box> </Box>
</Box> </Box>

View File

@@ -10,6 +10,7 @@ import Spinner from 'ink-spinner';
import { theme } from '../../semantic-colors.js'; import { theme } from '../../semantic-colors.js';
import { SCREEN_READER_MODEL_PREFIX } from '../../textConstants.js'; import { SCREEN_READER_MODEL_PREFIX } from '../../textConstants.js';
import { CompressionStatus } from '@qwen-code/qwen-code-core'; import { CompressionStatus } from '@qwen-code/qwen-code-core';
import { t } from '../../../i18n/index.js';
export interface CompressionDisplayProps { export interface CompressionDisplayProps {
compression: CompressionProps; compression: CompressionProps;
@@ -30,24 +31,34 @@ export function CompressionMessage({
const getCompressionText = () => { const getCompressionText = () => {
if (isPending) { if (isPending) {
return 'Compressing chat history'; return t('Compressing chat history');
} }
switch (compressionStatus) { switch (compressionStatus) {
case CompressionStatus.COMPRESSED: case CompressionStatus.COMPRESSED:
return `Chat history compressed from ${originalTokens} to ${newTokens} tokens.`; return t(
'Chat history compressed from {{originalTokens}} to {{newTokens}} tokens.',
{
originalTokens: String(originalTokens),
newTokens: String(newTokens),
},
);
case CompressionStatus.COMPRESSION_FAILED_INFLATED_TOKEN_COUNT: case CompressionStatus.COMPRESSION_FAILED_INFLATED_TOKEN_COUNT:
// For smaller histories (< 50k tokens), compression overhead likely exceeds benefits // For smaller histories (< 50k tokens), compression overhead likely exceeds benefits
if (originalTokens < 50000) { if (originalTokens < 50000) {
return 'Compression was not beneficial for this history size.'; return t('Compression was not beneficial for this history size.');
} }
// For larger histories where compression should work but didn't, // For larger histories where compression should work but didn't,
// this suggests an issue with the compression process itself // this suggests an issue with the compression process itself
return 'Chat history compression did not reduce size. This may indicate issues with the compression prompt.'; return t(
'Chat history compression did not reduce size. This may indicate issues with the compression prompt.',
);
case CompressionStatus.COMPRESSION_FAILED_TOKEN_COUNT_ERROR: case CompressionStatus.COMPRESSION_FAILED_TOKEN_COUNT_ERROR:
return 'Could not compress chat history due to a token counting error.'; return t(
'Could not compress chat history due to a token counting error.',
);
case CompressionStatus.NOOP: case CompressionStatus.NOOP:
return 'Chat history is already compressed.'; return t('Chat history is already compressed.');
default: default:
return ''; return '';
} }

View File

@@ -9,6 +9,7 @@ import { Box, Text } from 'ink';
import { theme } from '../../semantic-colors.js'; import { theme } from '../../semantic-colors.js';
import { type ToolDefinition } from '../../types.js'; import { type ToolDefinition } from '../../types.js';
import { MarkdownDisplay } from '../../utils/MarkdownDisplay.js'; import { MarkdownDisplay } from '../../utils/MarkdownDisplay.js';
import { t } from '../../../i18n/index.js';
interface ToolsListProps { interface ToolsListProps {
tools: readonly ToolDefinition[]; tools: readonly ToolDefinition[];
@@ -23,7 +24,7 @@ export const ToolsList: React.FC<ToolsListProps> = ({
}) => ( }) => (
<Box flexDirection="column" marginBottom={1}> <Box flexDirection="column" marginBottom={1}>
<Text bold color={theme.text.primary}> <Text bold color={theme.text.primary}>
Available Qwen Code CLI tools: {t('Available Qwen Code CLI tools:')}
</Text> </Text>
<Box height={1} /> <Box height={1} />
{tools.length > 0 ? ( {tools.length > 0 ? (
@@ -46,7 +47,7 @@ export const ToolsList: React.FC<ToolsListProps> = ({
</Box> </Box>
)) ))
) : ( ) : (
<Text color={theme.text.primary}> No tools available</Text> <Text color={theme.text.primary}> {t('No tools available')}</Text>
)} )}
</Box> </Box>
); );

View File

@@ -9,6 +9,7 @@ import { themeManager } from '../themes/theme-manager.js';
import type { LoadedSettings, SettingScope } from '../../config/settings.js'; // Import LoadedSettings, AppSettings, MergedSetting import type { LoadedSettings, SettingScope } from '../../config/settings.js'; // Import LoadedSettings, AppSettings, MergedSetting
import { type HistoryItem, MessageType } from '../types.js'; import { type HistoryItem, MessageType } from '../types.js';
import process from 'node:process'; import process from 'node:process';
import { t } from '../../i18n/index.js';
interface UseThemeCommandReturn { interface UseThemeCommandReturn {
isThemeDialogOpen: boolean; isThemeDialogOpen: boolean;
@@ -34,7 +35,9 @@ export const useThemeCommand = (
addItem( addItem(
{ {
type: MessageType.INFO, type: MessageType.INFO,
text: 'Theme configuration unavailable due to NO_COLOR env variable.', text: t(
'Theme configuration unavailable due to NO_COLOR env variable.',
),
}, },
Date.now(), Date.now(),
); );
@@ -48,7 +51,11 @@ export const useThemeCommand = (
if (!themeManager.setActiveTheme(themeName)) { if (!themeManager.setActiveTheme(themeName)) {
// If theme is not found, open the theme selection dialog and set error message // If theme is not found, open the theme selection dialog and set error message
setIsThemeDialogOpen(true); setIsThemeDialogOpen(true);
setThemeError(`Theme "${themeName}" not found.`); setThemeError(
t('Theme "{{themeName}}" not found.', {
themeName: themeName ?? '',
}),
);
} else { } else {
setThemeError(null); // Clear any previous theme error on success setThemeError(null); // Clear any previous theme error on success
} }
@@ -75,7 +82,11 @@ export const useThemeCommand = (
const isBuiltIn = themeManager.findThemeByName(themeName); const isBuiltIn = themeManager.findThemeByName(themeName);
const isCustom = themeName && mergedCustomThemes[themeName]; const isCustom = themeName && mergedCustomThemes[themeName];
if (!isBuiltIn && !isCustom) { if (!isBuiltIn && !isCustom) {
setThemeError(`Theme "${themeName}" not found in selected scope.`); setThemeError(
t('Theme "{{themeName}}" not found in selected scope.', {
themeName: themeName ?? '',
}),
);
setIsThemeDialogOpen(true); setIsThemeDialogOpen(true);
return; return;
} }

View File

@@ -16,6 +16,7 @@ import type {
SettingsValue, SettingsValue,
} from '../config/settingsSchema.js'; } from '../config/settingsSchema.js';
import { getSettingsSchema } from '../config/settingsSchema.js'; import { getSettingsSchema } from '../config/settingsSchema.js';
import { t } from '../i18n/index.js';
// The schema is now nested, but many parts of the UI and logic work better // The schema is now nested, but many parts of the UI and logic work better
// with a flattened structure and dot-notation keys. This section flattens the // with a flattened structure and dot-notation keys. This section flattens the
@@ -446,7 +447,11 @@ export function getDisplayValue(
if (definition?.type === 'enum' && definition.options) { if (definition?.type === 'enum' && definition.options) {
const option = definition.options?.find((option) => option.value === value); const option = definition.options?.find((option) => option.value === value);
valueString = option?.label ?? `${value}`; if (option?.label) {
valueString = t(option.label) || option.label;
} else {
valueString = `${value}`;
}
} }
// Check if value is different from default OR if it's in modified settings OR if there are pending changes // Check if value is different from default OR if it's in modified settings OR if there are pending changes