mirror of
https://github.com/QwenLM/qwen-code.git
synced 2025-12-19 09:33:53 +00:00
feat: update description
This commit is contained in:
@@ -19,7 +19,8 @@
|
|||||||
"format": "prettier --write .",
|
"format": "prettier --write .",
|
||||||
"test": "vitest run",
|
"test": "vitest run",
|
||||||
"test:ci": "vitest run",
|
"test:ci": "vitest run",
|
||||||
"typecheck": "tsc --noEmit"
|
"typecheck": "tsc --noEmit",
|
||||||
|
"check-i18n": "tsx ../../scripts/check-i18n.ts"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"dist"
|
"dist"
|
||||||
|
|||||||
@@ -109,6 +109,8 @@ export default {
|
|||||||
'install required IDE companion for {{ideName}}',
|
'install required IDE companion for {{ideName}}',
|
||||||
'enable IDE integration': 'enable IDE integration',
|
'enable IDE integration': 'enable IDE integration',
|
||||||
'disable IDE integration': 'disable IDE integration',
|
'disable IDE integration': 'disable IDE integration',
|
||||||
|
'IDE integration is not supported in your current environment. To use this feature, run Qwen Code in one of these supported IDEs: VS Code or VS Code forks.':
|
||||||
|
'IDE integration is not supported in your current environment. To use this feature, run Qwen Code in one of these supported IDEs: VS Code or VS Code forks.',
|
||||||
'Set up GitHub Actions': 'Set up GitHub Actions',
|
'Set up GitHub Actions': 'Set up GitHub Actions',
|
||||||
'Configure terminal keybindings for multiline input (VS Code, Cursor, Windsurf)':
|
'Configure terminal keybindings for multiline input (VS Code, Cursor, Windsurf)':
|
||||||
'Configure terminal keybindings for multiline input (VS Code, Cursor, Windsurf)',
|
'Configure terminal keybindings for multiline input (VS Code, Cursor, Windsurf)',
|
||||||
@@ -220,6 +222,12 @@ export default {
|
|||||||
'Project memory is currently empty.': 'Project memory is currently empty.',
|
'Project memory is currently empty.': 'Project memory is currently empty.',
|
||||||
'Refreshing memory from source files...':
|
'Refreshing memory from source files...':
|
||||||
'Refreshing memory from source files...',
|
'Refreshing memory from source files...',
|
||||||
|
'Add content to the memory. Use --global for global memory or --project for project memory.':
|
||||||
|
'Add content to the memory. Use --global for global memory or --project for project memory.',
|
||||||
|
'Usage: /memory add [--global|--project] <text to remember>':
|
||||||
|
'Usage: /memory add [--global|--project] <text to remember>',
|
||||||
|
'Attempting to save to memory {{scope}}: "{{fact}}"':
|
||||||
|
'Attempting to save to memory {{scope}}: "{{fact}}"',
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Commands - MCP
|
// Commands - MCP
|
||||||
@@ -287,6 +295,8 @@ export default {
|
|||||||
'Error sharing conversation: {{error}}',
|
'Error sharing conversation: {{error}}',
|
||||||
'Conversation shared to {{filePath}}': 'Conversation shared to {{filePath}}',
|
'Conversation shared to {{filePath}}': 'Conversation shared to {{filePath}}',
|
||||||
'No conversation found to share.': 'No conversation found to share.',
|
'No conversation found to share.': 'No conversation found to share.',
|
||||||
|
'Share the current conversation to a markdown or json file. Usage: /chat share <file>':
|
||||||
|
'Share the current conversation to a markdown or json file. Usage: /chat share <file>',
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Commands - Summary
|
// Commands - Summary
|
||||||
@@ -344,6 +354,27 @@ export default {
|
|||||||
No: 'No',
|
No: 'No',
|
||||||
'No (esc)': 'No (esc)',
|
'No (esc)': 'No (esc)',
|
||||||
'Yes, allow always for this session': 'Yes, allow always for this session',
|
'Yes, allow always for this session': 'Yes, allow always for this session',
|
||||||
|
'Modify in progress:': 'Modify in progress:',
|
||||||
|
'Save and close external editor to continue':
|
||||||
|
'Save and close external editor to continue',
|
||||||
|
'Apply this change?': 'Apply this change?',
|
||||||
|
'Yes, allow always': 'Yes, allow always',
|
||||||
|
'Modify with external editor': 'Modify with external editor',
|
||||||
|
'No, suggest changes (esc)': 'No, suggest changes (esc)',
|
||||||
|
"Allow execution of: '{{command}}'?": "Allow execution of: '{{command}}'?",
|
||||||
|
'Yes, allow always ...': 'Yes, allow always ...',
|
||||||
|
'Yes, and auto-accept edits': 'Yes, and auto-accept edits',
|
||||||
|
'Yes, and manually approve edits': 'Yes, and manually approve edits',
|
||||||
|
'No, keep planning (esc)': 'No, keep planning (esc)',
|
||||||
|
'URLs to fetch:': 'URLs to fetch:',
|
||||||
|
'MCP Server: {{server}}': 'MCP Server: {{server}}',
|
||||||
|
'Tool: {{tool}}': 'Tool: {{tool}}',
|
||||||
|
'Allow execution of MCP tool "{{tool}}" from server "{{server}}"?':
|
||||||
|
'Allow execution of MCP tool "{{tool}}" from server "{{server}}"?',
|
||||||
|
'Yes, always allow tool "{{tool}}" from server "{{server}}"':
|
||||||
|
'Yes, always allow tool "{{tool}}" from server "{{server}}"',
|
||||||
|
'Yes, always allow all tools from server "{{server}}"':
|
||||||
|
'Yes, always allow all tools from server "{{server}}"',
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Dialogs - Shell Confirmation
|
// Dialogs - Shell Confirmation
|
||||||
@@ -427,6 +458,16 @@ export default {
|
|||||||
'Waiting for Qwen OAuth authentication...':
|
'Waiting for Qwen OAuth authentication...':
|
||||||
'Waiting for Qwen OAuth authentication...',
|
'Waiting for Qwen OAuth authentication...',
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Dialogs - Model
|
||||||
|
// ============================================================================
|
||||||
|
'Select Model': 'Select Model',
|
||||||
|
'(Press Esc to close)': '(Press Esc to close)',
|
||||||
|
'The latest Qwen Coder model from Alibaba Cloud ModelStudio (version: qwen3-coder-plus-2025-09-23)':
|
||||||
|
'The latest Qwen Coder model from Alibaba Cloud ModelStudio (version: qwen3-coder-plus-2025-09-23)',
|
||||||
|
'The latest Qwen Vision model from Alibaba Cloud ModelStudio (version: qwen3-vl-plus-2025-09-23)':
|
||||||
|
'The latest Qwen Vision model from Alibaba Cloud ModelStudio (version: qwen3-vl-plus-2025-09-23)',
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Dialogs - Permissions
|
// Dialogs - Permissions
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
@@ -471,6 +512,29 @@ export default {
|
|||||||
'{{count}} prompt': '{{count}} prompt',
|
'{{count}} prompt': '{{count}} prompt',
|
||||||
'{{count}} prompts': '{{count}} prompts',
|
'{{count}} prompts': '{{count}} prompts',
|
||||||
'(from {{extensionName}})': '(from {{extensionName}})',
|
'(from {{extensionName}})': '(from {{extensionName}})',
|
||||||
|
OAuth: 'OAuth',
|
||||||
|
'OAuth expired': 'OAuth expired',
|
||||||
|
'OAuth not authenticated': 'OAuth not authenticated',
|
||||||
|
'tools and prompts will appear when ready':
|
||||||
|
'tools and prompts will appear when ready',
|
||||||
|
'{{count}} tools cached': '{{count}} tools cached',
|
||||||
|
'Tools:': 'Tools:',
|
||||||
|
'Parameters:': 'Parameters:',
|
||||||
|
'Prompts:': 'Prompts:',
|
||||||
|
Blocked: 'Blocked',
|
||||||
|
'💡 Tips:': '💡 Tips:',
|
||||||
|
Use: 'Use',
|
||||||
|
'to show server and tool descriptions':
|
||||||
|
'to show server and tool descriptions',
|
||||||
|
'to show tool parameter schemas': 'to show tool parameter schemas',
|
||||||
|
'to hide descriptions': 'to hide descriptions',
|
||||||
|
'to authenticate with OAuth-enabled servers':
|
||||||
|
'to authenticate with OAuth-enabled servers',
|
||||||
|
Press: 'Press',
|
||||||
|
'to toggle tool descriptions on/off': 'to toggle tool descriptions on/off',
|
||||||
|
"Starting OAuth authentication for MCP server '{{name}}'...":
|
||||||
|
"Starting OAuth authentication for MCP server '{{name}}'...",
|
||||||
|
'Restarting MCP servers...': 'Restarting MCP servers...',
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Startup Tips
|
// Startup Tips
|
||||||
|
|||||||
@@ -103,6 +103,8 @@ export default {
|
|||||||
'安装 {{ideName}} 所需的 IDE 配套工具',
|
'安装 {{ideName}} 所需的 IDE 配套工具',
|
||||||
'enable IDE integration': '启用 IDE 集成',
|
'enable IDE integration': '启用 IDE 集成',
|
||||||
'disable IDE integration': '禁用 IDE 集成',
|
'disable IDE integration': '禁用 IDE 集成',
|
||||||
|
'IDE integration is not supported in your current environment. To use this feature, run Qwen Code in one of these supported IDEs: VS Code or VS Code forks.':
|
||||||
|
'您当前环境不支持 IDE 集成。要使用此功能,请在以下支持的 IDE 之一中运行 Qwen Code:VS Code 或 VS Code 分支版本。',
|
||||||
'Set up GitHub Actions': '设置 GitHub Actions',
|
'Set up GitHub Actions': '设置 GitHub Actions',
|
||||||
'Configure terminal keybindings for multiline input (VS Code, Cursor, Windsurf)':
|
'Configure terminal keybindings for multiline input (VS Code, Cursor, Windsurf)':
|
||||||
'配置终端按键绑定以支持多行输入(VS Code、Cursor、Windsurf)',
|
'配置终端按键绑定以支持多行输入(VS Code、Cursor、Windsurf)',
|
||||||
@@ -204,6 +206,12 @@ export default {
|
|||||||
'项目记忆内容来自 {{path}}:\n\n---\n{{content}}\n---',
|
'项目记忆内容来自 {{path}}:\n\n---\n{{content}}\n---',
|
||||||
'Project memory is currently empty.': '项目记忆当前为空',
|
'Project memory is currently empty.': '项目记忆当前为空',
|
||||||
'Refreshing memory from source files...': '正在从源文件刷新记忆...',
|
'Refreshing memory from source files...': '正在从源文件刷新记忆...',
|
||||||
|
'Add content to the memory. Use --global for global memory or --project for project memory.':
|
||||||
|
'添加内容到记忆。使用 --global 表示全局记忆,使用 --project 表示项目记忆',
|
||||||
|
'Usage: /memory add [--global|--project] <text to remember>':
|
||||||
|
'用法:/memory add [--global|--project] <要记住的文本>',
|
||||||
|
'Attempting to save to memory {{scope}}: "{{fact}}"':
|
||||||
|
'正在尝试保存到记忆 {{scope}}:"{{fact}}"',
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Commands - MCP
|
// Commands - MCP
|
||||||
@@ -266,6 +274,8 @@ export default {
|
|||||||
'Error sharing conversation: {{error}}': '分享对话时出错:{{error}}',
|
'Error sharing conversation: {{error}}': '分享对话时出错:{{error}}',
|
||||||
'Conversation shared to {{filePath}}': '对话已分享到 {{filePath}}',
|
'Conversation shared to {{filePath}}': '对话已分享到 {{filePath}}',
|
||||||
'No conversation found to share.': '未找到要分享的对话',
|
'No conversation found to share.': '未找到要分享的对话',
|
||||||
|
'Share the current conversation to a markdown or json file. Usage: /chat share <file>':
|
||||||
|
'将当前对话分享到 markdown 或 json 文件。用法:/chat share <file>',
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Commands - Summary
|
// Commands - Summary
|
||||||
@@ -320,6 +330,26 @@ export default {
|
|||||||
No: '否',
|
No: '否',
|
||||||
'No (esc)': '否 (esc)',
|
'No (esc)': '否 (esc)',
|
||||||
'Yes, allow always for this session': '是,本次会话总是允许',
|
'Yes, allow always for this session': '是,本次会话总是允许',
|
||||||
|
'Modify in progress:': '正在修改:',
|
||||||
|
'Save and close external editor to continue': '保存并关闭外部编辑器以继续',
|
||||||
|
'Apply this change?': '是否应用此更改?',
|
||||||
|
'Yes, allow always': '是,总是允许',
|
||||||
|
'Modify with external editor': '使用外部编辑器修改',
|
||||||
|
'No, suggest changes (esc)': '否,建议更改 (esc)',
|
||||||
|
"Allow execution of: '{{command}}'?": "允许执行:'{{command}}'?",
|
||||||
|
'Yes, allow always ...': '是,总是允许 ...',
|
||||||
|
'Yes, and auto-accept edits': '是,并自动接受编辑',
|
||||||
|
'Yes, and manually approve edits': '是,并手动批准编辑',
|
||||||
|
'No, keep planning (esc)': '否,继续规划 (esc)',
|
||||||
|
'URLs to fetch:': '要获取的 URL:',
|
||||||
|
'MCP Server: {{server}}': 'MCP 服务器:{{server}}',
|
||||||
|
'Tool: {{tool}}': '工具:{{tool}}',
|
||||||
|
'Allow execution of MCP tool "{{tool}}" from server "{{server}}"?':
|
||||||
|
'允许执行来自服务器 "{{server}}" 的 MCP 工具 "{{tool}}"?',
|
||||||
|
'Yes, always allow tool "{{tool}}" from server "{{server}}"':
|
||||||
|
'是,总是允许来自服务器 "{{server}}" 的工具 "{{tool}}"',
|
||||||
|
'Yes, always allow all tools from server "{{server}}"':
|
||||||
|
'是,总是允许来自服务器 "{{server}}" 的所有工具',
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Dialogs - Shell Confirmation
|
// Dialogs - Shell Confirmation
|
||||||
@@ -394,6 +424,16 @@ export default {
|
|||||||
'按任意键返回认证类型选择',
|
'按任意键返回认证类型选择',
|
||||||
'Waiting for Qwen OAuth authentication...': '正在等待 Qwen OAuth 认证...',
|
'Waiting for Qwen OAuth authentication...': '正在等待 Qwen OAuth 认证...',
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Dialogs - Model
|
||||||
|
// ============================================================================
|
||||||
|
'Select Model': '选择模型',
|
||||||
|
'(Press Esc to close)': '(按 Esc 关闭)',
|
||||||
|
'The latest Qwen Coder model from Alibaba Cloud ModelStudio (version: qwen3-coder-plus-2025-09-23)':
|
||||||
|
'来自阿里云 ModelStudio 的最新 Qwen Coder 模型(版本:qwen3-coder-plus-2025-09-23)',
|
||||||
|
'The latest Qwen Vision model from Alibaba Cloud ModelStudio (version: qwen3-vl-plus-2025-09-23)':
|
||||||
|
'来自阿里云 ModelStudio 的最新 Qwen Vision 模型(版本:qwen3-vl-plus-2025-09-23)',
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Dialogs - Permissions
|
// Dialogs - Permissions
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
@@ -438,6 +478,27 @@ export default {
|
|||||||
'{{count}} prompt': '{{count}} 个提示',
|
'{{count}} prompt': '{{count}} 个提示',
|
||||||
'{{count}} prompts': '{{count}} 个提示',
|
'{{count}} prompts': '{{count}} 个提示',
|
||||||
'(from {{extensionName}})': '(来自 {{extensionName}})',
|
'(from {{extensionName}})': '(来自 {{extensionName}})',
|
||||||
|
OAuth: 'OAuth',
|
||||||
|
'OAuth expired': 'OAuth 已过期',
|
||||||
|
'OAuth not authenticated': 'OAuth 未认证',
|
||||||
|
'tools and prompts will appear when ready': '工具和提示将在就绪时显示',
|
||||||
|
'{{count}} tools cached': '{{count}} 个工具已缓存',
|
||||||
|
'Tools:': '工具:',
|
||||||
|
'Parameters:': '参数:',
|
||||||
|
'Prompts:': '提示:',
|
||||||
|
Blocked: '已阻止',
|
||||||
|
'💡 Tips:': '💡 提示:',
|
||||||
|
Use: '使用',
|
||||||
|
'to show server and tool descriptions': '显示服务器和工具描述',
|
||||||
|
'to show tool parameter schemas': '显示工具参数架构',
|
||||||
|
'to hide descriptions': '隐藏描述',
|
||||||
|
'to authenticate with OAuth-enabled servers':
|
||||||
|
'使用支持 OAuth 的服务器进行认证',
|
||||||
|
Press: '按',
|
||||||
|
'to toggle tool descriptions on/off': '切换工具描述开关',
|
||||||
|
"Starting OAuth authentication for MCP server '{{name}}'...":
|
||||||
|
"正在为 MCP 服务器 '{{name}}' 启动 OAuth 认证...",
|
||||||
|
'Restarting MCP servers...': '正在重启 MCP 服务器...',
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Startup Tips
|
// Startup Tips
|
||||||
|
|||||||
@@ -7,7 +7,6 @@
|
|||||||
import * as fsPromises from 'node:fs/promises';
|
import * as fsPromises from 'node:fs/promises';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Text } from 'ink';
|
import { Text } from 'ink';
|
||||||
import { theme } from '../semantic-colors.js';
|
|
||||||
import type {
|
import type {
|
||||||
CommandContext,
|
CommandContext,
|
||||||
SlashCommand,
|
SlashCommand,
|
||||||
@@ -20,6 +19,7 @@ import path from 'node:path';
|
|||||||
import type { HistoryItemWithoutId } from '../types.js';
|
import type { HistoryItemWithoutId } from '../types.js';
|
||||||
import { MessageType } from '../types.js';
|
import { MessageType } from '../types.js';
|
||||||
import type { Content } from '@google/genai';
|
import type { Content } from '@google/genai';
|
||||||
|
import { t } from '../../i18n/index.js';
|
||||||
|
|
||||||
interface ChatDetail {
|
interface ChatDetail {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -67,7 +67,9 @@ const getSavedChatTags = async (
|
|||||||
|
|
||||||
const listCommand: SlashCommand = {
|
const listCommand: SlashCommand = {
|
||||||
name: 'list',
|
name: 'list',
|
||||||
description: 'List saved conversation checkpoints',
|
get description() {
|
||||||
|
return t('List saved conversation checkpoints');
|
||||||
|
},
|
||||||
kind: CommandKind.BUILT_IN,
|
kind: CommandKind.BUILT_IN,
|
||||||
action: async (context): Promise<MessageActionReturn> => {
|
action: async (context): Promise<MessageActionReturn> => {
|
||||||
const chatDetails = await getSavedChatTags(context, false);
|
const chatDetails = await getSavedChatTags(context, false);
|
||||||
@@ -75,7 +77,7 @@ const listCommand: SlashCommand = {
|
|||||||
return {
|
return {
|
||||||
type: 'message',
|
type: 'message',
|
||||||
messageType: 'info',
|
messageType: 'info',
|
||||||
content: 'No saved conversation checkpoints found.',
|
content: t('No saved conversation checkpoints found.'),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,7 +85,7 @@ const listCommand: SlashCommand = {
|
|||||||
...chatDetails.map((chat) => chat.name.length),
|
...chatDetails.map((chat) => chat.name.length),
|
||||||
);
|
);
|
||||||
|
|
||||||
let message = 'List of saved conversations:\n\n';
|
let message = t('List of saved conversations:') + '\n\n';
|
||||||
for (const chat of chatDetails) {
|
for (const chat of chatDetails) {
|
||||||
const paddedName = chat.name.padEnd(maxNameLength, ' ');
|
const paddedName = chat.name.padEnd(maxNameLength, ' ');
|
||||||
const isoString = chat.mtime.toISOString();
|
const isoString = chat.mtime.toISOString();
|
||||||
@@ -91,7 +93,7 @@ const listCommand: SlashCommand = {
|
|||||||
const formattedDate = match ? `${match[1]} ${match[2]}` : 'Invalid Date';
|
const formattedDate = match ? `${match[1]} ${match[2]}` : 'Invalid Date';
|
||||||
message += ` - ${paddedName} (saved on ${formattedDate})\n`;
|
message += ` - ${paddedName} (saved on ${formattedDate})\n`;
|
||||||
}
|
}
|
||||||
message += `\nNote: Newest last, oldest first`;
|
message += `\n${t('Note: Newest last, oldest first')}`;
|
||||||
return {
|
return {
|
||||||
type: 'message',
|
type: 'message',
|
||||||
messageType: 'info',
|
messageType: 'info',
|
||||||
@@ -102,8 +104,11 @@ const listCommand: SlashCommand = {
|
|||||||
|
|
||||||
const saveCommand: SlashCommand = {
|
const saveCommand: SlashCommand = {
|
||||||
name: 'save',
|
name: 'save',
|
||||||
description:
|
get description() {
|
||||||
|
return t(
|
||||||
'Save the current conversation as a checkpoint. Usage: /chat save <tag>',
|
'Save the current conversation as a checkpoint. Usage: /chat save <tag>',
|
||||||
|
);
|
||||||
|
},
|
||||||
kind: CommandKind.BUILT_IN,
|
kind: CommandKind.BUILT_IN,
|
||||||
action: async (context, args): Promise<SlashCommandActionReturn | void> => {
|
action: async (context, args): Promise<SlashCommandActionReturn | void> => {
|
||||||
const tag = args.trim();
|
const tag = args.trim();
|
||||||
@@ -111,7 +116,7 @@ const saveCommand: SlashCommand = {
|
|||||||
return {
|
return {
|
||||||
type: 'message',
|
type: 'message',
|
||||||
messageType: 'error',
|
messageType: 'error',
|
||||||
content: 'Missing tag. Usage: /chat save <tag>',
|
content: t('Missing tag. Usage: /chat save <tag>'),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,9 +131,12 @@ const saveCommand: SlashCommand = {
|
|||||||
prompt: React.createElement(
|
prompt: React.createElement(
|
||||||
Text,
|
Text,
|
||||||
null,
|
null,
|
||||||
'A checkpoint with the tag ',
|
t(
|
||||||
React.createElement(Text, { color: theme.text.accent }, tag),
|
'A checkpoint with the tag {{tag}} already exists. Do you want to overwrite it?',
|
||||||
' already exists. Do you want to overwrite it?',
|
{
|
||||||
|
tag,
|
||||||
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
originalInvocation: {
|
originalInvocation: {
|
||||||
raw: context.invocation?.raw || `/chat save ${tag}`,
|
raw: context.invocation?.raw || `/chat save ${tag}`,
|
||||||
@@ -142,7 +150,7 @@ const saveCommand: SlashCommand = {
|
|||||||
return {
|
return {
|
||||||
type: 'message',
|
type: 'message',
|
||||||
messageType: 'error',
|
messageType: 'error',
|
||||||
content: 'No chat client available to save conversation.',
|
content: t('No chat client available to save conversation.'),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -152,13 +160,15 @@ const saveCommand: SlashCommand = {
|
|||||||
return {
|
return {
|
||||||
type: 'message',
|
type: 'message',
|
||||||
messageType: 'info',
|
messageType: 'info',
|
||||||
content: `Conversation checkpoint saved with tag: ${decodeTagName(tag)}.`,
|
content: t('Conversation checkpoint saved with tag: {{tag}}.', {
|
||||||
|
tag: decodeTagName(tag),
|
||||||
|
}),
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
type: 'message',
|
type: 'message',
|
||||||
messageType: 'info',
|
messageType: 'info',
|
||||||
content: 'No conversation found to save.',
|
content: t('No conversation found to save.'),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -167,8 +177,11 @@ const saveCommand: SlashCommand = {
|
|||||||
const resumeCommand: SlashCommand = {
|
const resumeCommand: SlashCommand = {
|
||||||
name: 'resume',
|
name: 'resume',
|
||||||
altNames: ['load'],
|
altNames: ['load'],
|
||||||
description:
|
get description() {
|
||||||
|
return t(
|
||||||
'Resume a conversation from a checkpoint. Usage: /chat resume <tag>',
|
'Resume a conversation from a checkpoint. Usage: /chat resume <tag>',
|
||||||
|
);
|
||||||
|
},
|
||||||
kind: CommandKind.BUILT_IN,
|
kind: CommandKind.BUILT_IN,
|
||||||
action: async (context, args) => {
|
action: async (context, args) => {
|
||||||
const tag = args.trim();
|
const tag = args.trim();
|
||||||
@@ -176,7 +189,7 @@ const resumeCommand: SlashCommand = {
|
|||||||
return {
|
return {
|
||||||
type: 'message',
|
type: 'message',
|
||||||
messageType: 'error',
|
messageType: 'error',
|
||||||
content: 'Missing tag. Usage: /chat resume <tag>',
|
content: t('Missing tag. Usage: /chat resume <tag>'),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,7 +201,9 @@ const resumeCommand: SlashCommand = {
|
|||||||
return {
|
return {
|
||||||
type: 'message',
|
type: 'message',
|
||||||
messageType: 'info',
|
messageType: 'info',
|
||||||
content: `No saved checkpoint found with tag: ${decodeTagName(tag)}.`,
|
content: t('No saved checkpoint found with tag: {{tag}}.', {
|
||||||
|
tag: decodeTagName(tag),
|
||||||
|
}),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -237,7 +252,9 @@ const resumeCommand: SlashCommand = {
|
|||||||
|
|
||||||
const deleteCommand: SlashCommand = {
|
const deleteCommand: SlashCommand = {
|
||||||
name: 'delete',
|
name: 'delete',
|
||||||
description: 'Delete a conversation checkpoint. Usage: /chat delete <tag>',
|
get description() {
|
||||||
|
return t('Delete a conversation checkpoint. Usage: /chat delete <tag>');
|
||||||
|
},
|
||||||
kind: CommandKind.BUILT_IN,
|
kind: CommandKind.BUILT_IN,
|
||||||
action: async (context, args): Promise<MessageActionReturn> => {
|
action: async (context, args): Promise<MessageActionReturn> => {
|
||||||
const tag = args.trim();
|
const tag = args.trim();
|
||||||
@@ -245,7 +262,7 @@ const deleteCommand: SlashCommand = {
|
|||||||
return {
|
return {
|
||||||
type: 'message',
|
type: 'message',
|
||||||
messageType: 'error',
|
messageType: 'error',
|
||||||
content: 'Missing tag. Usage: /chat delete <tag>',
|
content: t('Missing tag. Usage: /chat delete <tag>'),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -257,13 +274,17 @@ const deleteCommand: SlashCommand = {
|
|||||||
return {
|
return {
|
||||||
type: 'message',
|
type: 'message',
|
||||||
messageType: 'info',
|
messageType: 'info',
|
||||||
content: `Conversation checkpoint '${decodeTagName(tag)}' has been deleted.`,
|
content: t("Conversation checkpoint '{{tag}}' has been deleted.", {
|
||||||
|
tag: decodeTagName(tag),
|
||||||
|
}),
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
type: 'message',
|
type: 'message',
|
||||||
messageType: 'error',
|
messageType: 'error',
|
||||||
content: `Error: No checkpoint found with tag '${decodeTagName(tag)}'.`,
|
content: t("Error: No checkpoint found with tag '{{tag}}'.", {
|
||||||
|
tag: decodeTagName(tag),
|
||||||
|
}),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -309,8 +330,11 @@ export function serializeHistoryToMarkdown(history: Content[]): string {
|
|||||||
|
|
||||||
const shareCommand: SlashCommand = {
|
const shareCommand: SlashCommand = {
|
||||||
name: 'share',
|
name: 'share',
|
||||||
description:
|
get description() {
|
||||||
|
return t(
|
||||||
'Share the current conversation to a markdown or json file. Usage: /chat share <file>',
|
'Share the current conversation to a markdown or json file. Usage: /chat share <file>',
|
||||||
|
);
|
||||||
|
},
|
||||||
kind: CommandKind.BUILT_IN,
|
kind: CommandKind.BUILT_IN,
|
||||||
action: async (context, args): Promise<MessageActionReturn> => {
|
action: async (context, args): Promise<MessageActionReturn> => {
|
||||||
let filePathArg = args.trim();
|
let filePathArg = args.trim();
|
||||||
@@ -324,7 +348,7 @@ const shareCommand: SlashCommand = {
|
|||||||
return {
|
return {
|
||||||
type: 'message',
|
type: 'message',
|
||||||
messageType: 'error',
|
messageType: 'error',
|
||||||
content: 'Invalid file format. Only .md and .json are supported.',
|
content: t('Invalid file format. Only .md and .json are supported.'),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -333,7 +357,7 @@ const shareCommand: SlashCommand = {
|
|||||||
return {
|
return {
|
||||||
type: 'message',
|
type: 'message',
|
||||||
messageType: 'error',
|
messageType: 'error',
|
||||||
content: 'No chat client available to share conversation.',
|
content: t('No chat client available to share conversation.'),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -346,7 +370,7 @@ const shareCommand: SlashCommand = {
|
|||||||
return {
|
return {
|
||||||
type: 'message',
|
type: 'message',
|
||||||
messageType: 'info',
|
messageType: 'info',
|
||||||
content: 'No conversation found to share.',
|
content: t('No conversation found to share.'),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -362,14 +386,18 @@ const shareCommand: SlashCommand = {
|
|||||||
return {
|
return {
|
||||||
type: 'message',
|
type: 'message',
|
||||||
messageType: 'info',
|
messageType: 'info',
|
||||||
content: `Conversation shared to ${filePath}`,
|
content: t('Conversation shared to {{filePath}}', {
|
||||||
|
filePath,
|
||||||
|
}),
|
||||||
};
|
};
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const errorMessage = err instanceof Error ? err.message : String(err);
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
||||||
return {
|
return {
|
||||||
type: 'message',
|
type: 'message',
|
||||||
messageType: 'error',
|
messageType: 'error',
|
||||||
content: `Error sharing conversation: ${errorMessage}`,
|
content: t('Error sharing conversation: {{error}}', {
|
||||||
|
error: errorMessage,
|
||||||
|
}),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -377,7 +405,9 @@ const shareCommand: SlashCommand = {
|
|||||||
|
|
||||||
export const chatCommand: SlashCommand = {
|
export const chatCommand: SlashCommand = {
|
||||||
name: 'chat',
|
name: 'chat',
|
||||||
description: 'Manage conversation history.',
|
get description() {
|
||||||
|
return t('Manage conversation history.');
|
||||||
|
},
|
||||||
kind: CommandKind.BUILT_IN,
|
kind: CommandKind.BUILT_IN,
|
||||||
subCommands: [
|
subCommands: [
|
||||||
listCommand,
|
listCommand,
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import {
|
|||||||
type SlashCommand,
|
type SlashCommand,
|
||||||
CommandKind,
|
CommandKind,
|
||||||
} from './types.js';
|
} from './types.js';
|
||||||
|
import { t } from '../../i18n/index.js';
|
||||||
|
|
||||||
async function listAction(context: CommandContext) {
|
async function listAction(context: CommandContext) {
|
||||||
context.ui.addItem(
|
context.ui.addItem(
|
||||||
@@ -131,14 +132,18 @@ async function updateAction(context: CommandContext, args: string) {
|
|||||||
|
|
||||||
const listExtensionsCommand: SlashCommand = {
|
const listExtensionsCommand: SlashCommand = {
|
||||||
name: 'list',
|
name: 'list',
|
||||||
description: 'List active extensions',
|
get description() {
|
||||||
|
return t('List active extensions');
|
||||||
|
},
|
||||||
kind: CommandKind.BUILT_IN,
|
kind: CommandKind.BUILT_IN,
|
||||||
action: listAction,
|
action: listAction,
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateExtensionsCommand: SlashCommand = {
|
const updateExtensionsCommand: SlashCommand = {
|
||||||
name: 'update',
|
name: 'update',
|
||||||
description: 'Update extensions. Usage: update <extension-names>|--all',
|
get description() {
|
||||||
|
return t('Update extensions. Usage: update <extension-names>|--all');
|
||||||
|
},
|
||||||
kind: CommandKind.BUILT_IN,
|
kind: CommandKind.BUILT_IN,
|
||||||
action: updateAction,
|
action: updateAction,
|
||||||
completion: async (context, partialArg) => {
|
completion: async (context, partialArg) => {
|
||||||
@@ -158,7 +163,9 @@ const updateExtensionsCommand: SlashCommand = {
|
|||||||
|
|
||||||
export const extensionsCommand: SlashCommand = {
|
export const extensionsCommand: SlashCommand = {
|
||||||
name: 'extensions',
|
name: 'extensions',
|
||||||
description: 'Manage extensions',
|
get description() {
|
||||||
|
return t('Manage extensions');
|
||||||
|
},
|
||||||
kind: CommandKind.BUILT_IN,
|
kind: CommandKind.BUILT_IN,
|
||||||
subCommands: [listExtensionsCommand, updateExtensionsCommand],
|
subCommands: [listExtensionsCommand, updateExtensionsCommand],
|
||||||
action: (context, args) =>
|
action: (context, args) =>
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import type {
|
|||||||
} from './types.js';
|
} from './types.js';
|
||||||
import { CommandKind } from './types.js';
|
import { CommandKind } from './types.js';
|
||||||
import { SettingScope } from '../../config/settings.js';
|
import { SettingScope } from '../../config/settings.js';
|
||||||
|
import { t } from '../../i18n/index.js';
|
||||||
|
|
||||||
function getIdeStatusMessage(ideClient: IdeClient): {
|
function getIdeStatusMessage(ideClient: IdeClient): {
|
||||||
messageType: 'info' | 'error';
|
messageType: 'info' | 'error';
|
||||||
@@ -138,27 +139,35 @@ export const ideCommand = async (): Promise<SlashCommand> => {
|
|||||||
if (!currentIDE) {
|
if (!currentIDE) {
|
||||||
return {
|
return {
|
||||||
name: 'ide',
|
name: 'ide',
|
||||||
description: 'manage IDE integration',
|
get description() {
|
||||||
|
return t('manage IDE integration');
|
||||||
|
},
|
||||||
kind: CommandKind.BUILT_IN,
|
kind: CommandKind.BUILT_IN,
|
||||||
action: (): SlashCommandActionReturn =>
|
action: (): SlashCommandActionReturn =>
|
||||||
({
|
({
|
||||||
type: 'message',
|
type: 'message',
|
||||||
messageType: 'error',
|
messageType: 'error',
|
||||||
content: `IDE integration is not supported in your current environment. To use this feature, run Qwen Code in one of these supported IDEs: VS Code or VS Code forks.`,
|
content: t(
|
||||||
|
'IDE integration is not supported in your current environment. To use this feature, run Qwen Code in one of these supported IDEs: VS Code or VS Code forks.',
|
||||||
|
),
|
||||||
}) as const,
|
}) as const,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const ideSlashCommand: SlashCommand = {
|
const ideSlashCommand: SlashCommand = {
|
||||||
name: 'ide',
|
name: 'ide',
|
||||||
description: 'manage IDE integration',
|
get description() {
|
||||||
|
return t('manage IDE integration');
|
||||||
|
},
|
||||||
kind: CommandKind.BUILT_IN,
|
kind: CommandKind.BUILT_IN,
|
||||||
subCommands: [],
|
subCommands: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
const statusCommand: SlashCommand = {
|
const statusCommand: SlashCommand = {
|
||||||
name: 'status',
|
name: 'status',
|
||||||
description: 'check status of IDE integration',
|
get description() {
|
||||||
|
return t('check status of IDE integration');
|
||||||
|
},
|
||||||
kind: CommandKind.BUILT_IN,
|
kind: CommandKind.BUILT_IN,
|
||||||
action: async (): Promise<SlashCommandActionReturn> => {
|
action: async (): Promise<SlashCommandActionReturn> => {
|
||||||
const { messageType, content } =
|
const { messageType, content } =
|
||||||
@@ -173,7 +182,12 @@ export const ideCommand = async (): Promise<SlashCommand> => {
|
|||||||
|
|
||||||
const installCommand: SlashCommand = {
|
const installCommand: SlashCommand = {
|
||||||
name: 'install',
|
name: 'install',
|
||||||
description: `install required IDE companion for ${ideClient.getDetectedIdeDisplayName()}`,
|
get description() {
|
||||||
|
const ideName = ideClient.getDetectedIdeDisplayName() ?? 'IDE';
|
||||||
|
return t('install required IDE companion for {{ideName}}', {
|
||||||
|
ideName,
|
||||||
|
});
|
||||||
|
},
|
||||||
kind: CommandKind.BUILT_IN,
|
kind: CommandKind.BUILT_IN,
|
||||||
action: async (context) => {
|
action: async (context) => {
|
||||||
const installer = getIdeInstaller(currentIDE);
|
const installer = getIdeInstaller(currentIDE);
|
||||||
@@ -246,7 +260,9 @@ export const ideCommand = async (): Promise<SlashCommand> => {
|
|||||||
|
|
||||||
const enableCommand: SlashCommand = {
|
const enableCommand: SlashCommand = {
|
||||||
name: 'enable',
|
name: 'enable',
|
||||||
description: 'enable IDE integration',
|
get description() {
|
||||||
|
return t('enable IDE integration');
|
||||||
|
},
|
||||||
kind: CommandKind.BUILT_IN,
|
kind: CommandKind.BUILT_IN,
|
||||||
action: async (context: CommandContext) => {
|
action: async (context: CommandContext) => {
|
||||||
context.services.settings.setValue(
|
context.services.settings.setValue(
|
||||||
@@ -268,7 +284,9 @@ export const ideCommand = async (): Promise<SlashCommand> => {
|
|||||||
|
|
||||||
const disableCommand: SlashCommand = {
|
const disableCommand: SlashCommand = {
|
||||||
name: 'disable',
|
name: 'disable',
|
||||||
description: 'disable IDE integration',
|
get description() {
|
||||||
|
return t('disable IDE integration');
|
||||||
|
},
|
||||||
kind: CommandKind.BUILT_IN,
|
kind: CommandKind.BUILT_IN,
|
||||||
action: async (context: CommandContext) => {
|
action: async (context: CommandContext) => {
|
||||||
context.services.settings.setValue(
|
context.services.settings.setValue(
|
||||||
|
|||||||
@@ -92,7 +92,12 @@ const authCommand: SlashCommand = {
|
|||||||
context.ui.addItem(
|
context.ui.addItem(
|
||||||
{
|
{
|
||||||
type: 'info',
|
type: 'info',
|
||||||
text: `Starting OAuth authentication for MCP server '${serverName}'...`,
|
text: t(
|
||||||
|
"Starting OAuth authentication for MCP server '{{name}}'...",
|
||||||
|
{
|
||||||
|
name: serverName,
|
||||||
|
},
|
||||||
|
),
|
||||||
},
|
},
|
||||||
Date.now(),
|
Date.now(),
|
||||||
);
|
);
|
||||||
@@ -208,7 +213,7 @@ const listCommand: SlashCommand = {
|
|||||||
return {
|
return {
|
||||||
type: 'message',
|
type: 'message',
|
||||||
messageType: 'error',
|
messageType: 'error',
|
||||||
content: 'Could not retrieve tool registry.',
|
content: t('Could not retrieve tool registry.'),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -320,14 +325,14 @@ const refreshCommand: SlashCommand = {
|
|||||||
return {
|
return {
|
||||||
type: 'message',
|
type: 'message',
|
||||||
messageType: 'error',
|
messageType: 'error',
|
||||||
content: 'Could not retrieve tool registry.',
|
content: t('Could not retrieve tool registry.'),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
context.ui.addItem(
|
context.ui.addItem(
|
||||||
{
|
{
|
||||||
type: 'info',
|
type: 'info',
|
||||||
text: 'Restarting MCP servers...',
|
text: t('Restarting MCP servers...'),
|
||||||
},
|
},
|
||||||
Date.now(),
|
Date.now(),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -15,15 +15,20 @@ import fs from 'fs/promises';
|
|||||||
import { MessageType } from '../types.js';
|
import { MessageType } from '../types.js';
|
||||||
import type { SlashCommand, SlashCommandActionReturn } from './types.js';
|
import type { SlashCommand, SlashCommandActionReturn } from './types.js';
|
||||||
import { CommandKind } from './types.js';
|
import { CommandKind } from './types.js';
|
||||||
|
import { t } from '../../i18n/index.js';
|
||||||
|
|
||||||
export const memoryCommand: SlashCommand = {
|
export const memoryCommand: SlashCommand = {
|
||||||
name: 'memory',
|
name: 'memory',
|
||||||
description: 'Commands for interacting with memory.',
|
get description() {
|
||||||
|
return t('Commands for interacting with memory.');
|
||||||
|
},
|
||||||
kind: CommandKind.BUILT_IN,
|
kind: CommandKind.BUILT_IN,
|
||||||
subCommands: [
|
subCommands: [
|
||||||
{
|
{
|
||||||
name: 'show',
|
name: 'show',
|
||||||
description: 'Show the current memory contents.',
|
get description() {
|
||||||
|
return t('Show the current memory contents.');
|
||||||
|
},
|
||||||
kind: CommandKind.BUILT_IN,
|
kind: CommandKind.BUILT_IN,
|
||||||
action: async (context) => {
|
action: async (context) => {
|
||||||
const memoryContent = context.services.config?.getUserMemory() || '';
|
const memoryContent = context.services.config?.getUserMemory() || '';
|
||||||
@@ -31,8 +36,8 @@ export const memoryCommand: SlashCommand = {
|
|||||||
|
|
||||||
const messageContent =
|
const messageContent =
|
||||||
memoryContent.length > 0
|
memoryContent.length > 0
|
||||||
? `Current memory content from ${fileCount} file(s):\n\n---\n${memoryContent}\n---`
|
? `${t('Current memory content from {{count}} file(s):', { count: String(fileCount) })}\n\n---\n${memoryContent}\n---`
|
||||||
: 'Memory is currently empty.';
|
: t('Memory is currently empty.');
|
||||||
|
|
||||||
context.ui.addItem(
|
context.ui.addItem(
|
||||||
{
|
{
|
||||||
@@ -45,7 +50,9 @@ export const memoryCommand: SlashCommand = {
|
|||||||
subCommands: [
|
subCommands: [
|
||||||
{
|
{
|
||||||
name: '--project',
|
name: '--project',
|
||||||
description: 'Show project-level memory contents.',
|
get description() {
|
||||||
|
return t('Show project-level memory contents.');
|
||||||
|
},
|
||||||
kind: CommandKind.BUILT_IN,
|
kind: CommandKind.BUILT_IN,
|
||||||
action: async (context) => {
|
action: async (context) => {
|
||||||
try {
|
try {
|
||||||
@@ -57,8 +64,14 @@ export const memoryCommand: SlashCommand = {
|
|||||||
|
|
||||||
const messageContent =
|
const messageContent =
|
||||||
memoryContent.trim().length > 0
|
memoryContent.trim().length > 0
|
||||||
? `Project memory content from ${projectMemoryPath}:\n\n---\n${memoryContent}\n---`
|
? t(
|
||||||
: 'Project memory is currently empty.';
|
'Project memory content from {{path}}:\n\n---\n{{content}}\n---',
|
||||||
|
{
|
||||||
|
path: projectMemoryPath,
|
||||||
|
content: memoryContent,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
: t('Project memory is currently empty.');
|
||||||
|
|
||||||
context.ui.addItem(
|
context.ui.addItem(
|
||||||
{
|
{
|
||||||
@@ -71,7 +84,9 @@ export const memoryCommand: SlashCommand = {
|
|||||||
context.ui.addItem(
|
context.ui.addItem(
|
||||||
{
|
{
|
||||||
type: MessageType.INFO,
|
type: MessageType.INFO,
|
||||||
text: 'Project memory file not found or is currently empty.',
|
text: t(
|
||||||
|
'Project memory file not found or is currently empty.',
|
||||||
|
),
|
||||||
},
|
},
|
||||||
Date.now(),
|
Date.now(),
|
||||||
);
|
);
|
||||||
@@ -80,7 +95,9 @@ export const memoryCommand: SlashCommand = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '--global',
|
name: '--global',
|
||||||
description: 'Show global memory contents.',
|
get description() {
|
||||||
|
return t('Show global memory contents.');
|
||||||
|
},
|
||||||
kind: CommandKind.BUILT_IN,
|
kind: CommandKind.BUILT_IN,
|
||||||
action: async (context) => {
|
action: async (context) => {
|
||||||
try {
|
try {
|
||||||
@@ -96,8 +113,10 @@ export const memoryCommand: SlashCommand = {
|
|||||||
|
|
||||||
const messageContent =
|
const messageContent =
|
||||||
globalMemoryContent.trim().length > 0
|
globalMemoryContent.trim().length > 0
|
||||||
? `Global memory content:\n\n---\n${globalMemoryContent}\n---`
|
? t('Global memory content:\n\n---\n{{content}}\n---', {
|
||||||
: 'Global memory is currently empty.';
|
content: globalMemoryContent,
|
||||||
|
})
|
||||||
|
: t('Global memory is currently empty.');
|
||||||
|
|
||||||
context.ui.addItem(
|
context.ui.addItem(
|
||||||
{
|
{
|
||||||
@@ -110,7 +129,9 @@ export const memoryCommand: SlashCommand = {
|
|||||||
context.ui.addItem(
|
context.ui.addItem(
|
||||||
{
|
{
|
||||||
type: MessageType.INFO,
|
type: MessageType.INFO,
|
||||||
text: 'Global memory file not found or is currently empty.',
|
text: t(
|
||||||
|
'Global memory file not found or is currently empty.',
|
||||||
|
),
|
||||||
},
|
},
|
||||||
Date.now(),
|
Date.now(),
|
||||||
);
|
);
|
||||||
@@ -121,16 +142,20 @@ export const memoryCommand: SlashCommand = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'add',
|
name: 'add',
|
||||||
description:
|
get description() {
|
||||||
|
return t(
|
||||||
'Add content to the memory. Use --global for global memory or --project for project memory.',
|
'Add content to the memory. Use --global for global memory or --project for project memory.',
|
||||||
|
);
|
||||||
|
},
|
||||||
kind: CommandKind.BUILT_IN,
|
kind: CommandKind.BUILT_IN,
|
||||||
action: (context, args): SlashCommandActionReturn | void => {
|
action: (context, args): SlashCommandActionReturn | void => {
|
||||||
if (!args || args.trim() === '') {
|
if (!args || args.trim() === '') {
|
||||||
return {
|
return {
|
||||||
type: 'message',
|
type: 'message',
|
||||||
messageType: 'error',
|
messageType: 'error',
|
||||||
content:
|
content: t(
|
||||||
'Usage: /memory add [--global|--project] <text to remember>',
|
'Usage: /memory add [--global|--project] <text to remember>',
|
||||||
|
),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,8 +175,9 @@ export const memoryCommand: SlashCommand = {
|
|||||||
return {
|
return {
|
||||||
type: 'message',
|
type: 'message',
|
||||||
messageType: 'error',
|
messageType: 'error',
|
||||||
content:
|
content: t(
|
||||||
'Usage: /memory add [--global|--project] <text to remember>',
|
'Usage: /memory add [--global|--project] <text to remember>',
|
||||||
|
),
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
// No scope specified, will be handled by the tool
|
// No scope specified, will be handled by the tool
|
||||||
@@ -162,8 +188,9 @@ export const memoryCommand: SlashCommand = {
|
|||||||
return {
|
return {
|
||||||
type: 'message',
|
type: 'message',
|
||||||
messageType: 'error',
|
messageType: 'error',
|
||||||
content:
|
content: t(
|
||||||
'Usage: /memory add [--global|--project] <text to remember>',
|
'Usage: /memory add [--global|--project] <text to remember>',
|
||||||
|
),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -171,7 +198,10 @@ export const memoryCommand: SlashCommand = {
|
|||||||
context.ui.addItem(
|
context.ui.addItem(
|
||||||
{
|
{
|
||||||
type: MessageType.INFO,
|
type: MessageType.INFO,
|
||||||
text: `Attempting to save to memory ${scopeText}: "${fact}"`,
|
text: t('Attempting to save to memory {{scope}}: "{{fact}}"', {
|
||||||
|
scope: scopeText,
|
||||||
|
fact,
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
Date.now(),
|
Date.now(),
|
||||||
);
|
);
|
||||||
@@ -185,21 +215,25 @@ export const memoryCommand: SlashCommand = {
|
|||||||
subCommands: [
|
subCommands: [
|
||||||
{
|
{
|
||||||
name: '--project',
|
name: '--project',
|
||||||
description: 'Add content to project-level memory.',
|
get description() {
|
||||||
|
return t('Add content to project-level memory.');
|
||||||
|
},
|
||||||
kind: CommandKind.BUILT_IN,
|
kind: CommandKind.BUILT_IN,
|
||||||
action: (context, args): SlashCommandActionReturn | void => {
|
action: (context, args): SlashCommandActionReturn | void => {
|
||||||
if (!args || args.trim() === '') {
|
if (!args || args.trim() === '') {
|
||||||
return {
|
return {
|
||||||
type: 'message',
|
type: 'message',
|
||||||
messageType: 'error',
|
messageType: 'error',
|
||||||
content: 'Usage: /memory add --project <text to remember>',
|
content: t('Usage: /memory add --project <text to remember>'),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
context.ui.addItem(
|
context.ui.addItem(
|
||||||
{
|
{
|
||||||
type: MessageType.INFO,
|
type: MessageType.INFO,
|
||||||
text: `Attempting to save to project memory: "${args.trim()}"`,
|
text: t('Attempting to save to project memory: "{{text}}"', {
|
||||||
|
text: args.trim(),
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
Date.now(),
|
Date.now(),
|
||||||
);
|
);
|
||||||
@@ -213,21 +247,25 @@ export const memoryCommand: SlashCommand = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '--global',
|
name: '--global',
|
||||||
description: 'Add content to global memory.',
|
get description() {
|
||||||
|
return t('Add content to global memory.');
|
||||||
|
},
|
||||||
kind: CommandKind.BUILT_IN,
|
kind: CommandKind.BUILT_IN,
|
||||||
action: (context, args): SlashCommandActionReturn | void => {
|
action: (context, args): SlashCommandActionReturn | void => {
|
||||||
if (!args || args.trim() === '') {
|
if (!args || args.trim() === '') {
|
||||||
return {
|
return {
|
||||||
type: 'message',
|
type: 'message',
|
||||||
messageType: 'error',
|
messageType: 'error',
|
||||||
content: 'Usage: /memory add --global <text to remember>',
|
content: t('Usage: /memory add --global <text to remember>'),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
context.ui.addItem(
|
context.ui.addItem(
|
||||||
{
|
{
|
||||||
type: MessageType.INFO,
|
type: MessageType.INFO,
|
||||||
text: `Attempting to save to global memory: "${args.trim()}"`,
|
text: t('Attempting to save to global memory: "{{text}}"', {
|
||||||
|
text: args.trim(),
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
Date.now(),
|
Date.now(),
|
||||||
);
|
);
|
||||||
@@ -243,13 +281,15 @@ export const memoryCommand: SlashCommand = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'refresh',
|
name: 'refresh',
|
||||||
description: 'Refresh the memory from the source.',
|
get description() {
|
||||||
|
return t('Refresh the memory from the source.');
|
||||||
|
},
|
||||||
kind: CommandKind.BUILT_IN,
|
kind: CommandKind.BUILT_IN,
|
||||||
action: async (context) => {
|
action: async (context) => {
|
||||||
context.ui.addItem(
|
context.ui.addItem(
|
||||||
{
|
{
|
||||||
type: MessageType.INFO,
|
type: MessageType.INFO,
|
||||||
text: 'Refreshing memory from source files...',
|
text: t('Refreshing memory from source files...'),
|
||||||
},
|
},
|
||||||
Date.now(),
|
Date.now(),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -12,10 +12,13 @@ import type {
|
|||||||
} from './types.js';
|
} from './types.js';
|
||||||
import { CommandKind } from './types.js';
|
import { CommandKind } from './types.js';
|
||||||
import { getAvailableModelsForAuthType } from '../models/availableModels.js';
|
import { getAvailableModelsForAuthType } from '../models/availableModels.js';
|
||||||
|
import { t } from '../../i18n/index.js';
|
||||||
|
|
||||||
export const modelCommand: SlashCommand = {
|
export const modelCommand: SlashCommand = {
|
||||||
name: 'model',
|
name: 'model',
|
||||||
description: 'Switch the model for this session',
|
get description() {
|
||||||
|
return t('Switch the model for this session');
|
||||||
|
},
|
||||||
kind: CommandKind.BUILT_IN,
|
kind: CommandKind.BUILT_IN,
|
||||||
action: async (
|
action: async (
|
||||||
context: CommandContext,
|
context: CommandContext,
|
||||||
@@ -36,7 +39,7 @@ export const modelCommand: SlashCommand = {
|
|||||||
return {
|
return {
|
||||||
type: 'message',
|
type: 'message',
|
||||||
messageType: 'error',
|
messageType: 'error',
|
||||||
content: 'Content generator configuration not available.',
|
content: t('Content generator configuration not available.'),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,7 +48,7 @@ export const modelCommand: SlashCommand = {
|
|||||||
return {
|
return {
|
||||||
type: 'message',
|
type: 'message',
|
||||||
messageType: 'error',
|
messageType: 'error',
|
||||||
content: 'Authentication type not available.',
|
content: t('Authentication type not available.'),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,7 +58,12 @@ export const modelCommand: SlashCommand = {
|
|||||||
return {
|
return {
|
||||||
type: 'message',
|
type: 'message',
|
||||||
messageType: 'error',
|
messageType: 'error',
|
||||||
content: `No models available for the current authentication type (${authType}).`,
|
content: t(
|
||||||
|
'No models available for the current authentication type ({{authType}}).',
|
||||||
|
{
|
||||||
|
authType,
|
||||||
|
},
|
||||||
|
),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,15 +11,16 @@ import { useConfig } from '../contexts/ConfigContext.js';
|
|||||||
import { type McpClient, MCPServerStatus } from '@qwen-code/qwen-code-core';
|
import { type McpClient, MCPServerStatus } from '@qwen-code/qwen-code-core';
|
||||||
import { GeminiSpinner } from './GeminiRespondingSpinner.js';
|
import { GeminiSpinner } from './GeminiRespondingSpinner.js';
|
||||||
import { theme } from '../semantic-colors.js';
|
import { theme } from '../semantic-colors.js';
|
||||||
|
import { t } from '../../i18n/index.js';
|
||||||
|
|
||||||
export const ConfigInitDisplay = () => {
|
export const ConfigInitDisplay = () => {
|
||||||
const config = useConfig();
|
const config = useConfig();
|
||||||
const [message, setMessage] = useState('Initializing...');
|
const [message, setMessage] = useState(t('Initializing...'));
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const onChange = (clients?: Map<string, McpClient>) => {
|
const onChange = (clients?: Map<string, McpClient>) => {
|
||||||
if (!clients || clients.size === 0) {
|
if (!clients || clients.size === 0) {
|
||||||
setMessage(`Initializing...`);
|
setMessage(t('Initializing...'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let connected = 0;
|
let connected = 0;
|
||||||
@@ -28,7 +29,12 @@ export const ConfigInitDisplay = () => {
|
|||||||
connected++;
|
connected++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setMessage(`Connecting to MCP servers... (${connected}/${clients.size})`);
|
setMessage(
|
||||||
|
t('Connecting to MCP servers... ({{connected}}/{{total}})', {
|
||||||
|
connected: String(connected),
|
||||||
|
total: String(clients.size),
|
||||||
|
}),
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
appEvents.on('mcp-client-update', onChange);
|
appEvents.on('mcp-client-update', onChange);
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
} from '@qwen-code/qwen-code-core';
|
} from '@qwen-code/qwen-code-core';
|
||||||
import { useTerminalSize } from '../hooks/useTerminalSize.js';
|
import { useTerminalSize } from '../hooks/useTerminalSize.js';
|
||||||
import { isNarrowWidth } from '../utils/isNarrowWidth.js';
|
import { isNarrowWidth } from '../utils/isNarrowWidth.js';
|
||||||
|
import { t } from '../../i18n/index.js';
|
||||||
|
|
||||||
interface ContextSummaryDisplayProps {
|
interface ContextSummaryDisplayProps {
|
||||||
geminiMdFileCount: number;
|
geminiMdFileCount: number;
|
||||||
@@ -50,9 +51,11 @@ export const ContextSummaryDisplay: React.FC<ContextSummaryDisplayProps> = ({
|
|||||||
if (openFileCount === 0) {
|
if (openFileCount === 0) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
return `${openFileCount} open file${
|
const fileText =
|
||||||
openFileCount > 1 ? 's' : ''
|
openFileCount === 1
|
||||||
} (ctrl+g to view)`;
|
? t('{{count}} open file', { count: String(openFileCount) })
|
||||||
|
: t('{{count}} open files', { count: String(openFileCount) });
|
||||||
|
return `${fileText} ${t('(ctrl+g to view)')}`;
|
||||||
})();
|
})();
|
||||||
|
|
||||||
const geminiMdText = (() => {
|
const geminiMdText = (() => {
|
||||||
@@ -61,9 +64,15 @@ export const ContextSummaryDisplay: React.FC<ContextSummaryDisplayProps> = ({
|
|||||||
}
|
}
|
||||||
const allNamesTheSame = new Set(contextFileNames).size < 2;
|
const allNamesTheSame = new Set(contextFileNames).size < 2;
|
||||||
const name = allNamesTheSame ? contextFileNames[0] : 'context';
|
const name = allNamesTheSame ? contextFileNames[0] : 'context';
|
||||||
return `${geminiMdFileCount} ${name} file${
|
return geminiMdFileCount === 1
|
||||||
geminiMdFileCount > 1 ? 's' : ''
|
? t('{{count}} {{name}} file', {
|
||||||
}`;
|
count: String(geminiMdFileCount),
|
||||||
|
name,
|
||||||
|
})
|
||||||
|
: t('{{count}} {{name}} files', {
|
||||||
|
count: String(geminiMdFileCount),
|
||||||
|
name,
|
||||||
|
});
|
||||||
})();
|
})();
|
||||||
|
|
||||||
const mcpText = (() => {
|
const mcpText = (() => {
|
||||||
@@ -73,15 +82,27 @@ export const ContextSummaryDisplay: React.FC<ContextSummaryDisplayProps> = ({
|
|||||||
|
|
||||||
const parts = [];
|
const parts = [];
|
||||||
if (mcpServerCount > 0) {
|
if (mcpServerCount > 0) {
|
||||||
parts.push(
|
const serverText =
|
||||||
`${mcpServerCount} MCP server${mcpServerCount > 1 ? 's' : ''}`,
|
mcpServerCount === 1
|
||||||
);
|
? t('{{count}} MCP server', { count: String(mcpServerCount) })
|
||||||
|
: t('{{count}} MCP servers', { count: String(mcpServerCount) });
|
||||||
|
parts.push(serverText);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (blockedMcpServerCount > 0) {
|
if (blockedMcpServerCount > 0) {
|
||||||
let blockedText = `${blockedMcpServerCount} Blocked`;
|
let blockedText = t('{{count}} Blocked', {
|
||||||
|
count: String(blockedMcpServerCount),
|
||||||
|
});
|
||||||
if (mcpServerCount === 0) {
|
if (mcpServerCount === 0) {
|
||||||
blockedText += ` MCP server${blockedMcpServerCount > 1 ? 's' : ''}`;
|
const serverText =
|
||||||
|
blockedMcpServerCount === 1
|
||||||
|
? t('{{count}} MCP server', {
|
||||||
|
count: String(blockedMcpServerCount),
|
||||||
|
})
|
||||||
|
: t('{{count}} MCP servers', {
|
||||||
|
count: String(blockedMcpServerCount),
|
||||||
|
});
|
||||||
|
blockedText += ` ${serverText}`;
|
||||||
}
|
}
|
||||||
parts.push(blockedText);
|
parts.push(blockedText);
|
||||||
}
|
}
|
||||||
@@ -89,9 +110,9 @@ export const ContextSummaryDisplay: React.FC<ContextSummaryDisplayProps> = ({
|
|||||||
// Add ctrl+t hint when MCP servers are available
|
// Add ctrl+t hint when MCP servers are available
|
||||||
if (mcpServers && Object.keys(mcpServers).length > 0) {
|
if (mcpServers && Object.keys(mcpServers).length > 0) {
|
||||||
if (showToolDescriptions) {
|
if (showToolDescriptions) {
|
||||||
text += ' (ctrl+t to toggle)';
|
text += ` ${t('(ctrl+t to toggle)')}`;
|
||||||
} else {
|
} else {
|
||||||
text += ' (ctrl+t to view)';
|
text += ` ${t('(ctrl+t to view)')}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return text;
|
return text;
|
||||||
@@ -102,7 +123,7 @@ export const ContextSummaryDisplay: React.FC<ContextSummaryDisplayProps> = ({
|
|||||||
if (isNarrow) {
|
if (isNarrow) {
|
||||||
return (
|
return (
|
||||||
<Box flexDirection="column">
|
<Box flexDirection="column">
|
||||||
<Text color={theme.text.secondary}>Using:</Text>
|
<Text color={theme.text.secondary}>{t('Using:')}</Text>
|
||||||
{summaryParts.map((part, index) => (
|
{summaryParts.map((part, index) => (
|
||||||
<Text key={index} color={theme.text.secondary}>
|
<Text key={index} color={theme.text.secondary}>
|
||||||
{' '}- {part}
|
{' '}- {part}
|
||||||
@@ -115,7 +136,7 @@ export const ContextSummaryDisplay: React.FC<ContextSummaryDisplayProps> = ({
|
|||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<Text color={theme.text.secondary}>
|
<Text color={theme.text.secondary}>
|
||||||
Using: {summaryParts.join(' | ')}
|
{t('Using:')} {summaryParts.join(' | ')}
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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 { type SlashCommand, CommandKind } from '../commands/types.js';
|
import { type SlashCommand, CommandKind } from '../commands/types.js';
|
||||||
|
import { t } from '../../i18n/index.js';
|
||||||
|
|
||||||
interface Help {
|
interface Help {
|
||||||
commands: readonly SlashCommand[];
|
commands: readonly SlashCommand[];
|
||||||
@@ -23,46 +24,41 @@ export const Help: React.FC<Help> = ({ commands }) => (
|
|||||||
>
|
>
|
||||||
{/* Basics */}
|
{/* Basics */}
|
||||||
<Text bold color={theme.text.primary}>
|
<Text bold color={theme.text.primary}>
|
||||||
Basics:
|
{t('Basics:')}
|
||||||
</Text>
|
</Text>
|
||||||
<Text color={theme.text.primary}>
|
<Text color={theme.text.primary}>
|
||||||
<Text bold color={theme.text.accent}>
|
<Text bold color={theme.text.accent}>
|
||||||
Add context
|
{t('Add context')}
|
||||||
</Text>
|
</Text>
|
||||||
: Use{' '}
|
:{' '}
|
||||||
<Text bold color={theme.text.accent}>
|
{t(
|
||||||
@
|
'Use {{symbol}} to specify files for context (e.g., {{example}}) to target specific files or folders.',
|
||||||
</Text>{' '}
|
{
|
||||||
to specify files for context (e.g.,{' '}
|
symbol: t('@'),
|
||||||
<Text bold color={theme.text.accent}>
|
example: t('@src/myFile.ts'),
|
||||||
@src/myFile.ts
|
},
|
||||||
</Text>
|
)}
|
||||||
) to target specific files or folders.
|
|
||||||
</Text>
|
</Text>
|
||||||
<Text color={theme.text.primary}>
|
<Text color={theme.text.primary}>
|
||||||
<Text bold color={theme.text.accent}>
|
<Text bold color={theme.text.accent}>
|
||||||
Shell mode
|
{t('Shell mode')}
|
||||||
</Text>
|
</Text>
|
||||||
: Execute shell commands via{' '}
|
:{' '}
|
||||||
<Text bold color={theme.text.accent}>
|
{t(
|
||||||
!
|
'Execute shell commands via {{symbol}} (e.g., {{example1}}) or use natural language (e.g., {{example2}}).',
|
||||||
</Text>{' '}
|
{
|
||||||
(e.g.,{' '}
|
symbol: t('!'),
|
||||||
<Text bold color={theme.text.accent}>
|
example1: t('!npm run start'),
|
||||||
!npm run start
|
example2: t('start server'),
|
||||||
</Text>
|
},
|
||||||
) or use natural language (e.g.{' '}
|
)}
|
||||||
<Text bold color={theme.text.accent}>
|
|
||||||
start server
|
|
||||||
</Text>
|
|
||||||
).
|
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<Box height={1} />
|
<Box height={1} />
|
||||||
|
|
||||||
{/* Commands */}
|
{/* Commands */}
|
||||||
<Text bold color={theme.text.primary}>
|
<Text bold color={theme.text.primary}>
|
||||||
Commands:
|
{t('Commands:')}
|
||||||
</Text>
|
</Text>
|
||||||
{commands
|
{commands
|
||||||
.filter((command) => command.description && !command.hidden)
|
.filter((command) => command.description && !command.hidden)
|
||||||
@@ -97,81 +93,81 @@ export const Help: React.FC<Help> = ({ commands }) => (
|
|||||||
{' '}
|
{' '}
|
||||||
!{' '}
|
!{' '}
|
||||||
</Text>
|
</Text>
|
||||||
- shell command
|
- {t('shell command')}
|
||||||
</Text>
|
</Text>
|
||||||
<Text color={theme.text.primary}>
|
<Text color={theme.text.primary}>
|
||||||
<Text color={theme.text.secondary}>[MCP]</Text> - Model Context Protocol
|
<Text color={theme.text.secondary}>[MCP]</Text> -{' '}
|
||||||
command (from external servers)
|
{t('Model Context Protocol command (from external servers)')}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<Box height={1} />
|
<Box height={1} />
|
||||||
|
|
||||||
{/* Shortcuts */}
|
{/* Shortcuts */}
|
||||||
<Text bold color={theme.text.primary}>
|
<Text bold color={theme.text.primary}>
|
||||||
Keyboard Shortcuts:
|
{t('Keyboard Shortcuts:')}
|
||||||
</Text>
|
</Text>
|
||||||
<Text color={theme.text.primary}>
|
<Text color={theme.text.primary}>
|
||||||
<Text bold color={theme.text.accent}>
|
<Text bold color={theme.text.accent}>
|
||||||
Alt+Left/Right
|
Alt+Left/Right
|
||||||
</Text>{' '}
|
</Text>{' '}
|
||||||
- Jump through words in the input
|
- {t('Jump through words in the input')}
|
||||||
</Text>
|
</Text>
|
||||||
<Text color={theme.text.primary}>
|
<Text color={theme.text.primary}>
|
||||||
<Text bold color={theme.text.accent}>
|
<Text bold color={theme.text.accent}>
|
||||||
Ctrl+C
|
Ctrl+C
|
||||||
</Text>{' '}
|
</Text>{' '}
|
||||||
- Close dialogs, cancel requests, or quit application
|
- {t('Close dialogs, cancel requests, or quit application')}
|
||||||
</Text>
|
</Text>
|
||||||
<Text color={theme.text.primary}>
|
<Text color={theme.text.primary}>
|
||||||
<Text bold color={theme.text.accent}>
|
<Text bold color={theme.text.accent}>
|
||||||
{process.platform === 'win32' ? 'Ctrl+Enter' : 'Ctrl+J'}
|
{process.platform === 'win32' ? 'Ctrl+Enter' : 'Ctrl+J'}
|
||||||
</Text>{' '}
|
</Text>{' '}
|
||||||
|
-{' '}
|
||||||
{process.platform === 'linux'
|
{process.platform === 'linux'
|
||||||
? '- New line (Alt+Enter works for certain linux distros)'
|
? t('New line (Alt+Enter works for certain linux distros)')
|
||||||
: '- New line'}
|
: t('New line')}
|
||||||
</Text>
|
</Text>
|
||||||
<Text color={theme.text.primary}>
|
<Text color={theme.text.primary}>
|
||||||
<Text bold color={theme.text.accent}>
|
<Text bold color={theme.text.accent}>
|
||||||
Ctrl+L
|
Ctrl+L
|
||||||
</Text>{' '}
|
</Text>{' '}
|
||||||
- Clear the screen
|
- {t('Clear the screen')}
|
||||||
</Text>
|
</Text>
|
||||||
<Text color={theme.text.primary}>
|
<Text color={theme.text.primary}>
|
||||||
<Text bold color={theme.text.accent}>
|
<Text bold color={theme.text.accent}>
|
||||||
{process.platform === 'darwin' ? 'Ctrl+X / Meta+Enter' : 'Ctrl+X'}
|
{process.platform === 'darwin' ? 'Ctrl+X / Meta+Enter' : 'Ctrl+X'}
|
||||||
</Text>{' '}
|
</Text>{' '}
|
||||||
- Open input in external editor
|
- {t('Open input in external editor')}
|
||||||
</Text>
|
</Text>
|
||||||
<Text color={theme.text.primary}>
|
<Text color={theme.text.primary}>
|
||||||
<Text bold color={theme.text.accent}>
|
<Text bold color={theme.text.accent}>
|
||||||
Enter
|
Enter
|
||||||
</Text>{' '}
|
</Text>{' '}
|
||||||
- Send message
|
- {t('Send message')}
|
||||||
</Text>
|
</Text>
|
||||||
<Text color={theme.text.primary}>
|
<Text color={theme.text.primary}>
|
||||||
<Text bold color={theme.text.accent}>
|
<Text bold color={theme.text.accent}>
|
||||||
Esc
|
Esc
|
||||||
</Text>{' '}
|
</Text>{' '}
|
||||||
- Cancel operation / Clear input (double press)
|
- {t('Cancel operation / Clear input (double press)')}
|
||||||
</Text>
|
</Text>
|
||||||
<Text color={theme.text.primary}>
|
<Text color={theme.text.primary}>
|
||||||
<Text bold color={theme.text.accent}>
|
<Text bold color={theme.text.accent}>
|
||||||
Shift+Tab
|
Shift+Tab
|
||||||
</Text>{' '}
|
</Text>{' '}
|
||||||
- Cycle approval modes
|
- {t('Cycle approval modes')}
|
||||||
</Text>
|
</Text>
|
||||||
<Text color={theme.text.primary}>
|
<Text color={theme.text.primary}>
|
||||||
<Text bold color={theme.text.accent}>
|
<Text bold color={theme.text.accent}>
|
||||||
Up/Down
|
Up/Down
|
||||||
</Text>{' '}
|
</Text>{' '}
|
||||||
- Cycle through your prompt history
|
- {t('Cycle through your prompt history')}
|
||||||
</Text>
|
</Text>
|
||||||
<Box height={1} />
|
<Box height={1} />
|
||||||
<Text color={theme.text.primary}>
|
<Text color={theme.text.primary}>
|
||||||
For a full list of shortcuts, see{' '}
|
{t('For a full list of shortcuts, see {{docPath}}', {
|
||||||
<Text bold color={theme.text.accent}>
|
docPath: t('docs/keyboard-shortcuts.md'),
|
||||||
docs/keyboard-shortcuts.md
|
})}
|
||||||
</Text>
|
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import {
|
|||||||
getAvailableModelsForAuthType,
|
getAvailableModelsForAuthType,
|
||||||
MAINLINE_CODER,
|
MAINLINE_CODER,
|
||||||
} from '../models/availableModels.js';
|
} from '../models/availableModels.js';
|
||||||
|
import { t } from '../../i18n/index.js';
|
||||||
|
|
||||||
interface ModelDialogProps {
|
interface ModelDialogProps {
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
@@ -87,7 +88,7 @@ export function ModelDialog({ onClose }: ModelDialogProps): React.JSX.Element {
|
|||||||
padding={1}
|
padding={1}
|
||||||
width="100%"
|
width="100%"
|
||||||
>
|
>
|
||||||
<Text bold>Select Model</Text>
|
<Text bold>{t('Select Model')}</Text>
|
||||||
<Box marginTop={1}>
|
<Box marginTop={1}>
|
||||||
<DescriptiveRadioButtonSelect
|
<DescriptiveRadioButtonSelect
|
||||||
items={MODEL_OPTIONS}
|
items={MODEL_OPTIONS}
|
||||||
@@ -97,7 +98,7 @@ export function ModelDialog({ onClose }: ModelDialogProps): React.JSX.Element {
|
|||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
<Box marginTop={1} flexDirection="column">
|
<Box marginTop={1} flexDirection="column">
|
||||||
<Text color={theme.text.secondary}>(Press Esc to close)</Text>
|
<Text color={theme.text.secondary}>{t('(Press Esc to close)')}</Text>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import { MaxSizedBox } from '../shared/MaxSizedBox.js';
|
|||||||
import { useKeypress } from '../../hooks/useKeypress.js';
|
import { useKeypress } from '../../hooks/useKeypress.js';
|
||||||
import { useSettings } from '../../contexts/SettingsContext.js';
|
import { useSettings } from '../../contexts/SettingsContext.js';
|
||||||
import { theme } from '../../semantic-colors.js';
|
import { theme } from '../../semantic-colors.js';
|
||||||
|
import { t } from '../../../i18n/index.js';
|
||||||
|
|
||||||
export interface ToolConfirmationMessageProps {
|
export interface ToolConfirmationMessageProps {
|
||||||
confirmationDetails: ToolCallConfirmationDetails;
|
confirmationDetails: ToolCallConfirmationDetails;
|
||||||
@@ -105,17 +106,17 @@ export const ToolConfirmationMessage: React.FC<
|
|||||||
const compactOptions: Array<RadioSelectItem<ToolConfirmationOutcome>> = [
|
const compactOptions: Array<RadioSelectItem<ToolConfirmationOutcome>> = [
|
||||||
{
|
{
|
||||||
key: 'proceed-once',
|
key: 'proceed-once',
|
||||||
label: 'Yes, allow once',
|
label: t('Yes, allow once'),
|
||||||
value: ToolConfirmationOutcome.ProceedOnce,
|
value: ToolConfirmationOutcome.ProceedOnce,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'proceed-always',
|
key: 'proceed-always',
|
||||||
label: 'Allow always',
|
label: t('Allow always'),
|
||||||
value: ToolConfirmationOutcome.ProceedAlways,
|
value: ToolConfirmationOutcome.ProceedAlways,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'cancel',
|
key: 'cancel',
|
||||||
label: 'No',
|
label: t('No'),
|
||||||
value: ToolConfirmationOutcome.Cancel,
|
value: ToolConfirmationOutcome.Cancel,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@@ -123,7 +124,7 @@ export const ToolConfirmationMessage: React.FC<
|
|||||||
return (
|
return (
|
||||||
<Box flexDirection="column">
|
<Box flexDirection="column">
|
||||||
<Box>
|
<Box>
|
||||||
<Text wrap="truncate">Do you want to proceed?</Text>
|
<Text wrap="truncate">{t('Do you want to proceed?')}</Text>
|
||||||
</Box>
|
</Box>
|
||||||
<Box>
|
<Box>
|
||||||
<RadioButtonSelect
|
<RadioButtonSelect
|
||||||
@@ -185,37 +186,37 @@ export const ToolConfirmationMessage: React.FC<
|
|||||||
padding={1}
|
padding={1}
|
||||||
overflow="hidden"
|
overflow="hidden"
|
||||||
>
|
>
|
||||||
<Text color={theme.text.primary}>Modify in progress: </Text>
|
<Text color={theme.text.primary}>{t('Modify in progress:')} </Text>
|
||||||
<Text color={theme.status.success}>
|
<Text color={theme.status.success}>
|
||||||
Save and close external editor to continue
|
{t('Save and close external editor to continue')}
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
question = `Apply this change?`;
|
question = t('Apply this change?');
|
||||||
options.push({
|
options.push({
|
||||||
label: 'Yes, allow once',
|
label: t('Yes, allow once'),
|
||||||
value: ToolConfirmationOutcome.ProceedOnce,
|
value: ToolConfirmationOutcome.ProceedOnce,
|
||||||
key: 'Yes, allow once',
|
key: 'Yes, allow once',
|
||||||
});
|
});
|
||||||
if (isTrustedFolder) {
|
if (isTrustedFolder) {
|
||||||
options.push({
|
options.push({
|
||||||
label: 'Yes, allow always',
|
label: t('Yes, allow always'),
|
||||||
value: ToolConfirmationOutcome.ProceedAlways,
|
value: ToolConfirmationOutcome.ProceedAlways,
|
||||||
key: 'Yes, allow always',
|
key: 'Yes, allow always',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if ((!config.getIdeMode() || !isDiffingEnabled) && preferredEditor) {
|
if ((!config.getIdeMode() || !isDiffingEnabled) && preferredEditor) {
|
||||||
options.push({
|
options.push({
|
||||||
label: 'Modify with external editor',
|
label: t('Modify with external editor'),
|
||||||
value: ToolConfirmationOutcome.ModifyWithEditor,
|
value: ToolConfirmationOutcome.ModifyWithEditor,
|
||||||
key: 'Modify with external editor',
|
key: 'Modify with external editor',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
options.push({
|
options.push({
|
||||||
label: 'No, suggest changes (esc)',
|
label: t('No, suggest changes (esc)'),
|
||||||
value: ToolConfirmationOutcome.Cancel,
|
value: ToolConfirmationOutcome.Cancel,
|
||||||
key: 'No, suggest changes (esc)',
|
key: 'No, suggest changes (esc)',
|
||||||
});
|
});
|
||||||
@@ -232,21 +233,23 @@ export const ToolConfirmationMessage: React.FC<
|
|||||||
const executionProps =
|
const executionProps =
|
||||||
confirmationDetails as ToolExecuteConfirmationDetails;
|
confirmationDetails as ToolExecuteConfirmationDetails;
|
||||||
|
|
||||||
question = `Allow execution of: '${executionProps.rootCommand}'?`;
|
question = t("Allow execution of: '{{command}}'?", {
|
||||||
|
command: executionProps.rootCommand,
|
||||||
|
});
|
||||||
options.push({
|
options.push({
|
||||||
label: 'Yes, allow once',
|
label: t('Yes, allow once'),
|
||||||
value: ToolConfirmationOutcome.ProceedOnce,
|
value: ToolConfirmationOutcome.ProceedOnce,
|
||||||
key: 'Yes, allow once',
|
key: 'Yes, allow once',
|
||||||
});
|
});
|
||||||
if (isTrustedFolder) {
|
if (isTrustedFolder) {
|
||||||
options.push({
|
options.push({
|
||||||
label: `Yes, allow always ...`,
|
label: t('Yes, allow always ...'),
|
||||||
value: ToolConfirmationOutcome.ProceedAlways,
|
value: ToolConfirmationOutcome.ProceedAlways,
|
||||||
key: `Yes, allow always ...`,
|
key: 'Yes, allow always ...',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
options.push({
|
options.push({
|
||||||
label: 'No, suggest changes (esc)',
|
label: t('No, suggest changes (esc)'),
|
||||||
value: ToolConfirmationOutcome.Cancel,
|
value: ToolConfirmationOutcome.Cancel,
|
||||||
key: 'No, suggest changes (esc)',
|
key: 'No, suggest changes (esc)',
|
||||||
});
|
});
|
||||||
@@ -275,17 +278,17 @@ export const ToolConfirmationMessage: React.FC<
|
|||||||
question = planProps.title;
|
question = planProps.title;
|
||||||
options.push({
|
options.push({
|
||||||
key: 'proceed-always',
|
key: 'proceed-always',
|
||||||
label: 'Yes, and auto-accept edits',
|
label: t('Yes, and auto-accept edits'),
|
||||||
value: ToolConfirmationOutcome.ProceedAlways,
|
value: ToolConfirmationOutcome.ProceedAlways,
|
||||||
});
|
});
|
||||||
options.push({
|
options.push({
|
||||||
key: 'proceed-once',
|
key: 'proceed-once',
|
||||||
label: 'Yes, and manually approve edits',
|
label: t('Yes, and manually approve edits'),
|
||||||
value: ToolConfirmationOutcome.ProceedOnce,
|
value: ToolConfirmationOutcome.ProceedOnce,
|
||||||
});
|
});
|
||||||
options.push({
|
options.push({
|
||||||
key: 'cancel',
|
key: 'cancel',
|
||||||
label: 'No, keep planning (esc)',
|
label: t('No, keep planning (esc)'),
|
||||||
value: ToolConfirmationOutcome.Cancel,
|
value: ToolConfirmationOutcome.Cancel,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -305,21 +308,21 @@ export const ToolConfirmationMessage: React.FC<
|
|||||||
infoProps.urls &&
|
infoProps.urls &&
|
||||||
!(infoProps.urls.length === 1 && infoProps.urls[0] === infoProps.prompt);
|
!(infoProps.urls.length === 1 && infoProps.urls[0] === infoProps.prompt);
|
||||||
|
|
||||||
question = `Do you want to proceed?`;
|
question = t('Do you want to proceed?');
|
||||||
options.push({
|
options.push({
|
||||||
label: 'Yes, allow once',
|
label: t('Yes, allow once'),
|
||||||
value: ToolConfirmationOutcome.ProceedOnce,
|
value: ToolConfirmationOutcome.ProceedOnce,
|
||||||
key: 'Yes, allow once',
|
key: 'Yes, allow once',
|
||||||
});
|
});
|
||||||
if (isTrustedFolder) {
|
if (isTrustedFolder) {
|
||||||
options.push({
|
options.push({
|
||||||
label: 'Yes, allow always',
|
label: t('Yes, allow always'),
|
||||||
value: ToolConfirmationOutcome.ProceedAlways,
|
value: ToolConfirmationOutcome.ProceedAlways,
|
||||||
key: 'Yes, allow always',
|
key: 'Yes, allow always',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
options.push({
|
options.push({
|
||||||
label: 'No, suggest changes (esc)',
|
label: t('No, suggest changes (esc)'),
|
||||||
value: ToolConfirmationOutcome.Cancel,
|
value: ToolConfirmationOutcome.Cancel,
|
||||||
key: 'No, suggest changes (esc)',
|
key: 'No, suggest changes (esc)',
|
||||||
});
|
});
|
||||||
@@ -331,7 +334,7 @@ export const ToolConfirmationMessage: React.FC<
|
|||||||
</Text>
|
</Text>
|
||||||
{displayUrls && infoProps.urls && infoProps.urls.length > 0 && (
|
{displayUrls && infoProps.urls && infoProps.urls.length > 0 && (
|
||||||
<Box flexDirection="column" marginTop={1}>
|
<Box flexDirection="column" marginTop={1}>
|
||||||
<Text color={theme.text.primary}>URLs to fetch:</Text>
|
<Text color={theme.text.primary}>{t('URLs to fetch:')}</Text>
|
||||||
{infoProps.urls.map((url) => (
|
{infoProps.urls.map((url) => (
|
||||||
<Text key={url}>
|
<Text key={url}>
|
||||||
{' '}
|
{' '}
|
||||||
@@ -348,31 +351,46 @@ export const ToolConfirmationMessage: React.FC<
|
|||||||
|
|
||||||
bodyContent = (
|
bodyContent = (
|
||||||
<Box flexDirection="column" paddingX={1} marginLeft={1}>
|
<Box flexDirection="column" paddingX={1} marginLeft={1}>
|
||||||
<Text color={theme.text.link}>MCP Server: {mcpProps.serverName}</Text>
|
<Text color={theme.text.link}>
|
||||||
<Text color={theme.text.link}>Tool: {mcpProps.toolName}</Text>
|
{t('MCP Server: {{server}}', { server: mcpProps.serverName })}
|
||||||
|
</Text>
|
||||||
|
<Text color={theme.text.link}>
|
||||||
|
{t('Tool: {{tool}}', { tool: mcpProps.toolName })}
|
||||||
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|
||||||
question = `Allow execution of MCP tool "${mcpProps.toolName}" from server "${mcpProps.serverName}"?`;
|
question = t(
|
||||||
|
'Allow execution of MCP tool "{{tool}}" from server "{{server}}"?',
|
||||||
|
{
|
||||||
|
tool: mcpProps.toolName,
|
||||||
|
server: mcpProps.serverName,
|
||||||
|
},
|
||||||
|
);
|
||||||
options.push({
|
options.push({
|
||||||
label: 'Yes, allow once',
|
label: t('Yes, allow once'),
|
||||||
value: ToolConfirmationOutcome.ProceedOnce,
|
value: ToolConfirmationOutcome.ProceedOnce,
|
||||||
key: 'Yes, allow once',
|
key: 'Yes, allow once',
|
||||||
});
|
});
|
||||||
if (isTrustedFolder) {
|
if (isTrustedFolder) {
|
||||||
options.push({
|
options.push({
|
||||||
label: `Yes, always allow tool "${mcpProps.toolName}" from server "${mcpProps.serverName}"`,
|
label: t('Yes, always allow tool "{{tool}}" from server "{{server}}"', {
|
||||||
|
tool: mcpProps.toolName,
|
||||||
|
server: mcpProps.serverName,
|
||||||
|
}),
|
||||||
value: ToolConfirmationOutcome.ProceedAlwaysTool, // Cast until types are updated
|
value: ToolConfirmationOutcome.ProceedAlwaysTool, // Cast until types are updated
|
||||||
key: `Yes, always allow tool "${mcpProps.toolName}" from server "${mcpProps.serverName}"`,
|
key: `Yes, always allow tool "${mcpProps.toolName}" from server "${mcpProps.serverName}"`,
|
||||||
});
|
});
|
||||||
options.push({
|
options.push({
|
||||||
label: `Yes, always allow all tools from server "${mcpProps.serverName}"`,
|
label: t('Yes, always allow all tools from server "{{server}}"', {
|
||||||
|
server: mcpProps.serverName,
|
||||||
|
}),
|
||||||
value: ToolConfirmationOutcome.ProceedAlwaysServer,
|
value: ToolConfirmationOutcome.ProceedAlwaysServer,
|
||||||
key: `Yes, always allow all tools from server "${mcpProps.serverName}"`,
|
key: `Yes, always allow all tools from server "${mcpProps.serverName}"`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
options.push({
|
options.push({
|
||||||
label: 'No, suggest changes (esc)',
|
label: t('No, suggest changes (esc)'),
|
||||||
value: ToolConfirmationOutcome.Cancel,
|
value: ToolConfirmationOutcome.Cancel,
|
||||||
key: 'No, suggest changes (esc)',
|
key: 'No, suggest changes (esc)',
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import type {
|
|||||||
JsonMcpPrompt,
|
JsonMcpPrompt,
|
||||||
JsonMcpTool,
|
JsonMcpTool,
|
||||||
} from '../../types.js';
|
} from '../../types.js';
|
||||||
|
import { t } from '../../../i18n/index.js';
|
||||||
|
|
||||||
interface McpStatusProps {
|
interface McpStatusProps {
|
||||||
servers: Record<string, MCPServerConfig>;
|
servers: Record<string, MCPServerConfig>;
|
||||||
@@ -47,13 +48,13 @@ export const McpStatus: React.FC<McpStatusProps> = ({
|
|||||||
if (serverNames.length === 0 && blockedServers.length === 0) {
|
if (serverNames.length === 0 && blockedServers.length === 0) {
|
||||||
return (
|
return (
|
||||||
<Box flexDirection="column">
|
<Box flexDirection="column">
|
||||||
<Text>No MCP servers configured.</Text>
|
<Text>{t('No MCP servers configured.')}</Text>
|
||||||
<Text>
|
<Text>
|
||||||
Please view MCP documentation in your browser:{' '}
|
{t('Please view MCP documentation in your browser:')}{' '}
|
||||||
<Text color={theme.text.link}>
|
<Text color={theme.text.link}>
|
||||||
https://goo.gle/gemini-cli-docs-mcp
|
https://goo.gle/gemini-cli-docs-mcp
|
||||||
</Text>{' '}
|
</Text>{' '}
|
||||||
or use the cli /docs command
|
{t('or use the cli /docs command')}
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
@@ -64,17 +65,19 @@ export const McpStatus: React.FC<McpStatusProps> = ({
|
|||||||
{discoveryInProgress && (
|
{discoveryInProgress && (
|
||||||
<Box flexDirection="column" marginBottom={1}>
|
<Box flexDirection="column" marginBottom={1}>
|
||||||
<Text color={theme.status.warning}>
|
<Text color={theme.status.warning}>
|
||||||
⏳ MCP servers are starting up ({connectingServers.length}{' '}
|
{t('⏳ MCP servers are starting up ({{count}} initializing)...', {
|
||||||
initializing)...
|
count: String(connectingServers.length),
|
||||||
|
})}
|
||||||
</Text>
|
</Text>
|
||||||
<Text color={theme.text.primary}>
|
<Text color={theme.text.primary}>
|
||||||
Note: First startup may take longer. Tool availability will update
|
{t(
|
||||||
automatically.
|
'Note: First startup may take longer. Tool availability will update automatically.',
|
||||||
|
)}
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Text bold>Configured MCP servers:</Text>
|
<Text bold>{t('Configured MCP servers:')}</Text>
|
||||||
<Box height={1} />
|
<Box height={1} />
|
||||||
|
|
||||||
{serverNames.map((serverName) => {
|
{serverNames.map((serverName) => {
|
||||||
@@ -100,50 +103,61 @@ export const McpStatus: React.FC<McpStatusProps> = ({
|
|||||||
switch (status) {
|
switch (status) {
|
||||||
case MCPServerStatus.CONNECTED:
|
case MCPServerStatus.CONNECTED:
|
||||||
statusIndicator = '🟢';
|
statusIndicator = '🟢';
|
||||||
statusText = 'Ready';
|
statusText = t('Ready');
|
||||||
statusColor = theme.status.success;
|
statusColor = theme.status.success;
|
||||||
break;
|
break;
|
||||||
case MCPServerStatus.CONNECTING:
|
case MCPServerStatus.CONNECTING:
|
||||||
statusIndicator = '🔄';
|
statusIndicator = '🔄';
|
||||||
statusText = 'Starting... (first startup may take longer)';
|
statusText = t('Starting... (first startup may take longer)');
|
||||||
statusColor = theme.status.warning;
|
statusColor = theme.status.warning;
|
||||||
break;
|
break;
|
||||||
case MCPServerStatus.DISCONNECTED:
|
case MCPServerStatus.DISCONNECTED:
|
||||||
default:
|
default:
|
||||||
statusIndicator = '🔴';
|
statusIndicator = '🔴';
|
||||||
statusText = 'Disconnected';
|
statusText = t('Disconnected');
|
||||||
statusColor = theme.status.error;
|
statusColor = theme.status.error;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
let serverDisplayName = serverName;
|
let serverDisplayName = serverName;
|
||||||
if (server.extensionName) {
|
if (server.extensionName) {
|
||||||
serverDisplayName += ` (from ${server.extensionName})`;
|
serverDisplayName += ` ${t('(from {{extensionName}})', {
|
||||||
|
extensionName: server.extensionName,
|
||||||
|
})}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const toolCount = serverTools.length;
|
const toolCount = serverTools.length;
|
||||||
const promptCount = serverPrompts.length;
|
const promptCount = serverPrompts.length;
|
||||||
const parts = [];
|
const parts = [];
|
||||||
if (toolCount > 0) {
|
if (toolCount > 0) {
|
||||||
parts.push(`${toolCount} ${toolCount === 1 ? 'tool' : 'tools'}`);
|
parts.push(
|
||||||
|
toolCount === 1
|
||||||
|
? t('{{count}} tool', { count: String(toolCount) })
|
||||||
|
: t('{{count}} tools', { count: String(toolCount) }),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (promptCount > 0) {
|
if (promptCount > 0) {
|
||||||
parts.push(
|
parts.push(
|
||||||
`${promptCount} ${promptCount === 1 ? 'prompt' : 'prompts'}`,
|
promptCount === 1
|
||||||
|
? t('{{count}} prompt', { count: String(promptCount) })
|
||||||
|
: t('{{count}} prompts', { count: String(promptCount) }),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const serverAuthStatus = authStatus[serverName];
|
const serverAuthStatus = authStatus[serverName];
|
||||||
let authStatusNode: React.ReactNode = null;
|
let authStatusNode: React.ReactNode = null;
|
||||||
if (serverAuthStatus === 'authenticated') {
|
if (serverAuthStatus === 'authenticated') {
|
||||||
authStatusNode = <Text> (OAuth)</Text>;
|
authStatusNode = <Text> ({t('OAuth')})</Text>;
|
||||||
} else if (serverAuthStatus === 'expired') {
|
} else if (serverAuthStatus === 'expired') {
|
||||||
authStatusNode = (
|
authStatusNode = (
|
||||||
<Text color={theme.status.error}> (OAuth expired)</Text>
|
<Text color={theme.status.error}> ({t('OAuth expired')})</Text>
|
||||||
);
|
);
|
||||||
} else if (serverAuthStatus === 'unauthenticated') {
|
} else if (serverAuthStatus === 'unauthenticated') {
|
||||||
authStatusNode = (
|
authStatusNode = (
|
||||||
<Text color={theme.status.warning}> (OAuth not authenticated)</Text>
|
<Text color={theme.status.warning}>
|
||||||
|
{' '}
|
||||||
|
({t('OAuth not authenticated')})
|
||||||
|
</Text>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -162,10 +176,12 @@ export const McpStatus: React.FC<McpStatusProps> = ({
|
|||||||
{authStatusNode}
|
{authStatusNode}
|
||||||
</Box>
|
</Box>
|
||||||
{status === MCPServerStatus.CONNECTING && (
|
{status === MCPServerStatus.CONNECTING && (
|
||||||
<Text> (tools and prompts will appear when ready)</Text>
|
<Text> ({t('tools and prompts will appear when ready')})</Text>
|
||||||
)}
|
)}
|
||||||
{status === MCPServerStatus.DISCONNECTED && toolCount > 0 && (
|
{status === MCPServerStatus.DISCONNECTED && toolCount > 0 && (
|
||||||
<Text> ({toolCount} tools cached)</Text>
|
<Text>
|
||||||
|
({t('{{count}} tools cached', { count: String(toolCount) })})
|
||||||
|
</Text>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{showDescriptions && server?.description && (
|
{showDescriptions && server?.description && (
|
||||||
@@ -176,7 +192,7 @@ export const McpStatus: React.FC<McpStatusProps> = ({
|
|||||||
|
|
||||||
{serverTools.length > 0 && (
|
{serverTools.length > 0 && (
|
||||||
<Box flexDirection="column" marginLeft={2}>
|
<Box flexDirection="column" marginLeft={2}>
|
||||||
<Text color={theme.text.primary}>Tools:</Text>
|
<Text color={theme.text.primary}>{t('Tools:')}</Text>
|
||||||
{serverTools.map((tool) => {
|
{serverTools.map((tool) => {
|
||||||
const schemaContent =
|
const schemaContent =
|
||||||
showSchema &&
|
showSchema &&
|
||||||
@@ -204,7 +220,9 @@ export const McpStatus: React.FC<McpStatusProps> = ({
|
|||||||
)}
|
)}
|
||||||
{schemaContent && (
|
{schemaContent && (
|
||||||
<Box flexDirection="column" marginLeft={4}>
|
<Box flexDirection="column" marginLeft={4}>
|
||||||
<Text color={theme.text.secondary}>Parameters:</Text>
|
<Text color={theme.text.secondary}>
|
||||||
|
{t('Parameters:')}
|
||||||
|
</Text>
|
||||||
<Text color={theme.text.secondary}>
|
<Text color={theme.text.secondary}>
|
||||||
{schemaContent}
|
{schemaContent}
|
||||||
</Text>
|
</Text>
|
||||||
@@ -218,7 +236,7 @@ export const McpStatus: React.FC<McpStatusProps> = ({
|
|||||||
|
|
||||||
{serverPrompts.length > 0 && (
|
{serverPrompts.length > 0 && (
|
||||||
<Box flexDirection="column" marginLeft={2}>
|
<Box flexDirection="column" marginLeft={2}>
|
||||||
<Text color={theme.text.primary}>Prompts:</Text>
|
<Text color={theme.text.primary}>{t('Prompts:')}</Text>
|
||||||
{serverPrompts.map((prompt) => (
|
{serverPrompts.map((prompt) => (
|
||||||
<Box key={prompt.name} flexDirection="column">
|
<Box key={prompt.name} flexDirection="column">
|
||||||
<Text>
|
<Text>
|
||||||
@@ -244,35 +262,41 @@ export const McpStatus: React.FC<McpStatusProps> = ({
|
|||||||
<Text color={theme.status.error}>🔴 </Text>
|
<Text color={theme.status.error}>🔴 </Text>
|
||||||
<Text bold>
|
<Text bold>
|
||||||
{server.name}
|
{server.name}
|
||||||
{server.extensionName ? ` (from ${server.extensionName})` : ''}
|
{server.extensionName
|
||||||
|
? ` ${t('(from {{extensionName}})', {
|
||||||
|
extensionName: server.extensionName,
|
||||||
|
})}`
|
||||||
|
: ''}
|
||||||
</Text>
|
</Text>
|
||||||
<Text> - Blocked</Text>
|
<Text> - {t('Blocked')}</Text>
|
||||||
</Box>
|
</Box>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
{showTips && (
|
{showTips && (
|
||||||
<Box flexDirection="column" marginTop={1}>
|
<Box flexDirection="column" marginTop={1}>
|
||||||
<Text color={theme.text.accent}>💡 Tips:</Text>
|
<Text color={theme.text.accent}>{t('💡 Tips:')}</Text>
|
||||||
<Text>
|
<Text>
|
||||||
{' '}- Use <Text color={theme.text.accent}>/mcp desc</Text> to show
|
{' '}- {t('Use')} <Text color={theme.text.accent}>/mcp desc</Text>{' '}
|
||||||
server and tool descriptions
|
{t('to show server and tool descriptions')}
|
||||||
</Text>
|
</Text>
|
||||||
<Text>
|
<Text>
|
||||||
{' '}- Use <Text color={theme.text.accent}>/mcp schema</Text> to
|
{' '}- {t('Use')}{' '}
|
||||||
show tool parameter schemas
|
<Text color={theme.text.accent}>/mcp schema</Text>{' '}
|
||||||
|
{t('to show tool parameter schemas')}
|
||||||
</Text>
|
</Text>
|
||||||
<Text>
|
<Text>
|
||||||
{' '}- Use <Text color={theme.text.accent}>/mcp nodesc</Text> to
|
{' '}- {t('Use')}{' '}
|
||||||
hide descriptions
|
<Text color={theme.text.accent}>/mcp nodesc</Text>{' '}
|
||||||
|
{t('to hide descriptions')}
|
||||||
</Text>
|
</Text>
|
||||||
<Text>
|
<Text>
|
||||||
{' '}- Use{' '}
|
{' '}- {t('Use')}{' '}
|
||||||
<Text color={theme.text.accent}>/mcp auth <server-name></Text>{' '}
|
<Text color={theme.text.accent}>/mcp auth <server-name></Text>{' '}
|
||||||
to authenticate with OAuth-enabled servers
|
{t('to authenticate with OAuth-enabled servers')}
|
||||||
</Text>
|
</Text>
|
||||||
<Text>
|
<Text>
|
||||||
{' '}- Press <Text color={theme.text.accent}>Ctrl+T</Text> to
|
{' '}- {t('Press')} <Text color={theme.text.accent}>Ctrl+T</Text>{' '}
|
||||||
toggle tool descriptions on/off
|
{t('to toggle tool descriptions on/off')}
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { AuthType, DEFAULT_QWEN_MODEL } from '@qwen-code/qwen-code-core';
|
import { AuthType, DEFAULT_QWEN_MODEL } from '@qwen-code/qwen-code-core';
|
||||||
|
import { t } from '../../i18n/index.js';
|
||||||
|
|
||||||
export type AvailableModel = {
|
export type AvailableModel = {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -20,14 +21,20 @@ export const AVAILABLE_MODELS_QWEN: AvailableModel[] = [
|
|||||||
{
|
{
|
||||||
id: MAINLINE_CODER,
|
id: MAINLINE_CODER,
|
||||||
label: MAINLINE_CODER,
|
label: MAINLINE_CODER,
|
||||||
description:
|
get description() {
|
||||||
|
return t(
|
||||||
'The latest Qwen Coder model from Alibaba Cloud ModelStudio (version: qwen3-coder-plus-2025-09-23)',
|
'The latest Qwen Coder model from Alibaba Cloud ModelStudio (version: qwen3-coder-plus-2025-09-23)',
|
||||||
|
);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: MAINLINE_VLM,
|
id: MAINLINE_VLM,
|
||||||
label: MAINLINE_VLM,
|
label: MAINLINE_VLM,
|
||||||
description:
|
get description() {
|
||||||
|
return t(
|
||||||
'The latest Qwen Vision model from Alibaba Cloud ModelStudio (version: qwen3-vl-plus-2025-09-23)',
|
'The latest Qwen Vision model from Alibaba Cloud ModelStudio (version: qwen3-vl-plus-2025-09-23)',
|
||||||
|
);
|
||||||
|
},
|
||||||
isVision: true,
|
isVision: true,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
329
scripts/check-i18n.ts
Normal file
329
scripts/check-i18n.ts
Normal file
@@ -0,0 +1,329 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright 2025 Qwen
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as fs from 'node:fs';
|
||||||
|
import * as path from 'node:path';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
import { glob } from 'glob';
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = path.dirname(__filename);
|
||||||
|
|
||||||
|
interface CheckResult {
|
||||||
|
success: boolean;
|
||||||
|
errors: string[];
|
||||||
|
warnings: string[];
|
||||||
|
stats: {
|
||||||
|
totalKeys: number;
|
||||||
|
translatedKeys: number;
|
||||||
|
unusedKeys: string[];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load translations from JS file
|
||||||
|
*/
|
||||||
|
async function loadTranslationsFile(
|
||||||
|
filePath: string,
|
||||||
|
): Promise<Record<string, string>> {
|
||||||
|
try {
|
||||||
|
// Dynamic import for ES modules
|
||||||
|
const module = await import(filePath);
|
||||||
|
return module.default || module;
|
||||||
|
} catch (error) {
|
||||||
|
// Fallback: try reading as JSON if JS import fails
|
||||||
|
try {
|
||||||
|
const content = fs.readFileSync(
|
||||||
|
filePath.replace('.js', '.json'),
|
||||||
|
'utf-8',
|
||||||
|
);
|
||||||
|
return JSON.parse(content);
|
||||||
|
} catch {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract string literal from code, handling escaped quotes
|
||||||
|
*/
|
||||||
|
function extractStringLiteral(
|
||||||
|
content: string,
|
||||||
|
startPos: number,
|
||||||
|
quote: string,
|
||||||
|
): { value: string; endPos: number } | null {
|
||||||
|
let pos = startPos + 1; // Skip opening quote
|
||||||
|
let value = '';
|
||||||
|
let escaped = false;
|
||||||
|
|
||||||
|
while (pos < content.length) {
|
||||||
|
const char = content[pos];
|
||||||
|
|
||||||
|
if (escaped) {
|
||||||
|
if (char === '\\') {
|
||||||
|
value += '\\';
|
||||||
|
} else if (char === quote) {
|
||||||
|
value += quote;
|
||||||
|
} else if (char === 'n') {
|
||||||
|
value += '\n';
|
||||||
|
} else if (char === 't') {
|
||||||
|
value += '\t';
|
||||||
|
} else if (char === 'r') {
|
||||||
|
value += '\r';
|
||||||
|
} else {
|
||||||
|
value += char;
|
||||||
|
}
|
||||||
|
escaped = false;
|
||||||
|
} else if (char === '\\') {
|
||||||
|
escaped = true;
|
||||||
|
} else if (char === quote) {
|
||||||
|
return { value, endPos: pos };
|
||||||
|
} else {
|
||||||
|
value += char;
|
||||||
|
}
|
||||||
|
|
||||||
|
pos++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null; // String not closed
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract all t() calls from source files
|
||||||
|
*/
|
||||||
|
async function extractUsedKeys(sourceDir: string): Promise<Set<string>> {
|
||||||
|
const usedKeys = new Set<string>();
|
||||||
|
|
||||||
|
// Find all TypeScript/TSX files
|
||||||
|
const files = await glob('**/*.{ts,tsx}', {
|
||||||
|
cwd: sourceDir,
|
||||||
|
ignore: [
|
||||||
|
'**/node_modules/**',
|
||||||
|
'**/dist/**',
|
||||||
|
'**/*.test.ts',
|
||||||
|
'**/*.test.tsx',
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const file of files) {
|
||||||
|
const filePath = path.join(sourceDir, file);
|
||||||
|
const content = fs.readFileSync(filePath, 'utf-8');
|
||||||
|
|
||||||
|
// Find all t( calls
|
||||||
|
const tCallRegex = /t\s*\(/g;
|
||||||
|
let match;
|
||||||
|
while ((match = tCallRegex.exec(content)) !== null) {
|
||||||
|
const startPos = match.index + match[0].length;
|
||||||
|
let pos = startPos;
|
||||||
|
|
||||||
|
// Skip whitespace
|
||||||
|
while (pos < content.length && /\s/.test(content[pos])) {
|
||||||
|
pos++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pos >= content.length) continue;
|
||||||
|
|
||||||
|
const char = content[pos];
|
||||||
|
if (char === "'" || char === '"') {
|
||||||
|
const result = extractStringLiteral(content, pos, char);
|
||||||
|
if (result) {
|
||||||
|
usedKeys.add(result.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return usedKeys;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check key-value consistency in en.js
|
||||||
|
*/
|
||||||
|
function checkKeyValueConsistency(
|
||||||
|
enTranslations: Record<string, string>,
|
||||||
|
): string[] {
|
||||||
|
const errors: string[] = [];
|
||||||
|
|
||||||
|
for (const [key, value] of Object.entries(enTranslations)) {
|
||||||
|
if (key !== value) {
|
||||||
|
errors.push(`Key-value mismatch: "${key}" !== "${value}"`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if en.js and zh.js have matching keys
|
||||||
|
*/
|
||||||
|
function checkKeyMatching(
|
||||||
|
enTranslations: Record<string, string>,
|
||||||
|
zhTranslations: Record<string, string>,
|
||||||
|
): string[] {
|
||||||
|
const errors: string[] = [];
|
||||||
|
const enKeys = new Set(Object.keys(enTranslations));
|
||||||
|
const zhKeys = new Set(Object.keys(zhTranslations));
|
||||||
|
|
||||||
|
// Check for keys in en but not in zh
|
||||||
|
for (const key of enKeys) {
|
||||||
|
if (!zhKeys.has(key)) {
|
||||||
|
errors.push(`Missing translation in zh.js: "${key}"`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for keys in zh but not in en
|
||||||
|
for (const key of zhKeys) {
|
||||||
|
if (!enKeys.has(key)) {
|
||||||
|
errors.push(`Extra key in zh.js (not in en.js): "${key}"`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find unused translation keys
|
||||||
|
*/
|
||||||
|
function findUnusedKeys(allKeys: Set<string>, usedKeys: Set<string>): string[] {
|
||||||
|
const unused: string[] = [];
|
||||||
|
|
||||||
|
for (const key of allKeys) {
|
||||||
|
if (!usedKeys.has(key)) {
|
||||||
|
unused.push(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return unused.sort();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main check function
|
||||||
|
*/
|
||||||
|
async function checkI18n(): Promise<CheckResult> {
|
||||||
|
const errors: string[] = [];
|
||||||
|
const warnings: string[] = [];
|
||||||
|
|
||||||
|
const localesDir = path.join(__dirname, '../packages/cli/src/i18n/locales');
|
||||||
|
const sourceDir = path.join(__dirname, '../packages/cli/src');
|
||||||
|
|
||||||
|
const enPath = path.join(localesDir, 'en.js');
|
||||||
|
const zhPath = path.join(localesDir, 'zh.js');
|
||||||
|
|
||||||
|
// Load translation files
|
||||||
|
let enTranslations: Record<string, string>;
|
||||||
|
let zhTranslations: Record<string, string>;
|
||||||
|
|
||||||
|
try {
|
||||||
|
enTranslations = await loadTranslationsFile(enPath);
|
||||||
|
} catch (error) {
|
||||||
|
errors.push(
|
||||||
|
`Failed to load en.js: ${error instanceof Error ? error.message : String(error)}`,
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
errors,
|
||||||
|
warnings,
|
||||||
|
stats: { totalKeys: 0, translatedKeys: 0, unusedKeys: [] },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
zhTranslations = await loadTranslationsFile(zhPath);
|
||||||
|
} catch (error) {
|
||||||
|
errors.push(
|
||||||
|
`Failed to load zh.js: ${error instanceof Error ? error.message : String(error)}`,
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
errors,
|
||||||
|
warnings,
|
||||||
|
stats: { totalKeys: 0, translatedKeys: 0, unusedKeys: [] },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check key-value consistency in en.js
|
||||||
|
const consistencyErrors = checkKeyValueConsistency(enTranslations);
|
||||||
|
errors.push(...consistencyErrors);
|
||||||
|
|
||||||
|
// Check key matching between en and zh
|
||||||
|
const matchingErrors = checkKeyMatching(enTranslations, zhTranslations);
|
||||||
|
errors.push(...matchingErrors);
|
||||||
|
|
||||||
|
// Extract used keys from source code
|
||||||
|
const usedKeys = await extractUsedKeys(sourceDir);
|
||||||
|
|
||||||
|
// Find unused keys
|
||||||
|
const enKeys = new Set(Object.keys(enTranslations));
|
||||||
|
const unusedKeys = findUnusedKeys(enKeys, usedKeys);
|
||||||
|
|
||||||
|
if (unusedKeys.length > 0) {
|
||||||
|
warnings.push(`Found ${unusedKeys.length} unused translation keys`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const totalKeys = Object.keys(enTranslations).length;
|
||||||
|
const translatedKeys = Object.keys(zhTranslations).length;
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: errors.length === 0,
|
||||||
|
errors,
|
||||||
|
warnings,
|
||||||
|
stats: {
|
||||||
|
totalKeys,
|
||||||
|
translatedKeys,
|
||||||
|
unusedKeys,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run checks
|
||||||
|
checkI18n()
|
||||||
|
.then((result) => {
|
||||||
|
console.log('\n=== i18n Check Results ===\n');
|
||||||
|
|
||||||
|
console.log(`Total keys: ${result.stats.totalKeys}`);
|
||||||
|
console.log(`Translated keys: ${result.stats.translatedKeys}`);
|
||||||
|
console.log(
|
||||||
|
`Translation coverage: ${((result.stats.translatedKeys / result.stats.totalKeys) * 100).toFixed(1)}%\n`,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (result.warnings.length > 0) {
|
||||||
|
console.log('⚠️ Warnings:');
|
||||||
|
result.warnings.forEach((warning) => console.log(` - ${warning}`));
|
||||||
|
if (
|
||||||
|
result.stats.unusedKeys.length > 0 &&
|
||||||
|
result.stats.unusedKeys.length <= 10
|
||||||
|
) {
|
||||||
|
console.log('\nUnused keys:');
|
||||||
|
result.stats.unusedKeys.forEach((key) => console.log(` - "${key}"`));
|
||||||
|
} else if (result.stats.unusedKeys.length > 10) {
|
||||||
|
console.log(
|
||||||
|
`\nUnused keys (showing first 10 of ${result.stats.unusedKeys.length}):`,
|
||||||
|
);
|
||||||
|
result.stats.unusedKeys
|
||||||
|
.slice(0, 10)
|
||||||
|
.forEach((key) => console.log(` - "${key}"`));
|
||||||
|
}
|
||||||
|
console.log();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.errors.length > 0) {
|
||||||
|
console.log('❌ Errors:');
|
||||||
|
result.errors.forEach((error) => console.log(` - ${error}`));
|
||||||
|
console.log();
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
console.log('✅ All checks passed!\n');
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('❌ Fatal error:', error);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
@@ -28,7 +28,7 @@ const targetDir = path.join('dist', 'src');
|
|||||||
|
|
||||||
const extensionsToCopy = ['.md', '.json', '.sb'];
|
const extensionsToCopy = ['.md', '.json', '.sb'];
|
||||||
|
|
||||||
function copyFilesRecursive(source, target) {
|
function copyFilesRecursive(source, target, rootSourceDir) {
|
||||||
if (!fs.existsSync(target)) {
|
if (!fs.existsSync(target)) {
|
||||||
fs.mkdirSync(target, { recursive: true });
|
fs.mkdirSync(target, { recursive: true });
|
||||||
}
|
}
|
||||||
@@ -40,14 +40,15 @@ function copyFilesRecursive(source, target) {
|
|||||||
const targetPath = path.join(target, item.name);
|
const targetPath = path.join(target, item.name);
|
||||||
|
|
||||||
if (item.isDirectory()) {
|
if (item.isDirectory()) {
|
||||||
copyFilesRecursive(sourcePath, targetPath);
|
copyFilesRecursive(sourcePath, targetPath, rootSourceDir);
|
||||||
} else {
|
} else {
|
||||||
const ext = path.extname(item.name);
|
const ext = path.extname(item.name);
|
||||||
// Copy standard extensions, or .js files in i18n/locales directory
|
// Copy standard extensions, or .js files in i18n/locales directory
|
||||||
|
// Use path.relative for precise matching to avoid false positives
|
||||||
|
const relativePath = path.relative(rootSourceDir, sourcePath);
|
||||||
|
const normalizedPath = relativePath.replace(/\\/g, '/');
|
||||||
const isLocaleJs =
|
const isLocaleJs =
|
||||||
ext === '.js' &&
|
ext === '.js' && normalizedPath.startsWith('i18n/locales/');
|
||||||
(sourcePath.includes('i18n/locales') ||
|
|
||||||
sourcePath.includes(path.join('i18n', 'locales')));
|
|
||||||
if (extensionsToCopy.includes(ext) || isLocaleJs) {
|
if (extensionsToCopy.includes(ext) || isLocaleJs) {
|
||||||
fs.copyFileSync(sourcePath, targetPath);
|
fs.copyFileSync(sourcePath, targetPath);
|
||||||
}
|
}
|
||||||
@@ -60,7 +61,7 @@ if (!fs.existsSync(sourceDir)) {
|
|||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
copyFilesRecursive(sourceDir, targetDir);
|
copyFilesRecursive(sourceDir, targetDir, sourceDir);
|
||||||
|
|
||||||
// Copy example extensions into the bundle.
|
// Copy example extensions into the bundle.
|
||||||
const packageName = path.basename(process.cwd());
|
const packageName = path.basename(process.cwd());
|
||||||
|
|||||||
Reference in New Issue
Block a user