feat(i18n): Add Internationalization Support for UI and LLM Output (#1058)

This commit is contained in:
pomelo
2025-11-21 15:44:37 +08:00
committed by GitHub
parent 640f30655d
commit 48b77541c3
98 changed files with 4740 additions and 636 deletions

View File

@@ -8,10 +8,13 @@ import type { SlashCommand } from './types.js';
import { CommandKind } from './types.js';
import { MessageType, type HistoryItemAbout } from '../types.js';
import { getExtendedSystemInfo } from '../../utils/systemInfo.js';
import { t } from '../../i18n/index.js';
export const aboutCommand: SlashCommand = {
name: 'about',
description: 'show version info',
get description() {
return t('show version info');
},
kind: CommandKind.BUILT_IN,
action: async (context) => {
const systemInfo = await getExtendedSystemInfo(context);

View File

@@ -9,15 +9,20 @@ import {
type SlashCommand,
type OpenDialogActionReturn,
} from './types.js';
import { t } from '../../i18n/index.js';
export const agentsCommand: SlashCommand = {
name: 'agents',
description: 'Manage subagents for specialized task delegation.',
get description() {
return t('Manage subagents for specialized task delegation.');
},
kind: CommandKind.BUILT_IN,
subCommands: [
{
name: 'manage',
description: 'Manage existing subagents (view, edit, delete).',
get description() {
return t('Manage existing subagents (view, edit, delete).');
},
kind: CommandKind.BUILT_IN,
action: (): OpenDialogActionReturn => ({
type: 'dialog',
@@ -26,7 +31,9 @@ export const agentsCommand: SlashCommand = {
},
{
name: 'create',
description: 'Create a new subagent with guided setup.',
get description() {
return t('Create a new subagent with guided setup.');
},
kind: CommandKind.BUILT_IN,
action: (): OpenDialogActionReturn => ({
type: 'dialog',

View File

@@ -10,10 +10,13 @@ import type {
OpenDialogActionReturn,
} from './types.js';
import { CommandKind } from './types.js';
import { t } from '../../i18n/index.js';
export const approvalModeCommand: SlashCommand = {
name: 'approval-mode',
description: 'View or change the approval mode for tool usage',
get description() {
return t('View or change the approval mode for tool usage');
},
kind: CommandKind.BUILT_IN,
action: async (
_context: CommandContext,

View File

@@ -6,10 +6,13 @@
import type { OpenDialogActionReturn, SlashCommand } from './types.js';
import { CommandKind } from './types.js';
import { t } from '../../i18n/index.js';
export const authCommand: SlashCommand = {
name: 'auth',
description: 'change the auth method',
get description() {
return t('change the auth method');
},
kind: CommandKind.BUILT_IN,
action: (_context, _args): OpenDialogActionReturn => ({
type: 'dialog',

View File

@@ -16,10 +16,13 @@ import {
getSystemInfoFields,
getFieldValue,
} from '../../utils/systemInfoFields.js';
import { t } from '../../i18n/index.js';
export const bugCommand: SlashCommand = {
name: 'bug',
description: 'submit a bug report',
get description() {
return t('submit a bug report');
},
kind: CommandKind.BUILT_IN,
action: async (context: CommandContext, args?: string): Promise<void> => {
const bugDescription = (args || '').trim();

View File

@@ -7,7 +7,6 @@
import * as fsPromises from 'node:fs/promises';
import React from 'react';
import { Text } from 'ink';
import { theme } from '../semantic-colors.js';
import type {
CommandContext,
SlashCommand,
@@ -20,6 +19,7 @@ import path from 'node:path';
import type { HistoryItemWithoutId } from '../types.js';
import { MessageType } from '../types.js';
import type { Content } from '@google/genai';
import { t } from '../../i18n/index.js';
interface ChatDetail {
name: string;
@@ -67,7 +67,9 @@ const getSavedChatTags = async (
const listCommand: SlashCommand = {
name: 'list',
description: 'List saved conversation checkpoints',
get description() {
return t('List saved conversation checkpoints');
},
kind: CommandKind.BUILT_IN,
action: async (context): Promise<MessageActionReturn> => {
const chatDetails = await getSavedChatTags(context, false);
@@ -75,7 +77,7 @@ const listCommand: SlashCommand = {
return {
type: 'message',
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),
);
let message = 'List of saved conversations:\n\n';
let message = t('List of saved conversations:') + '\n\n';
for (const chat of chatDetails) {
const paddedName = chat.name.padEnd(maxNameLength, ' ');
const isoString = chat.mtime.toISOString();
@@ -91,7 +93,7 @@ const listCommand: SlashCommand = {
const formattedDate = match ? `${match[1]} ${match[2]}` : 'Invalid Date';
message += ` - ${paddedName} (saved on ${formattedDate})\n`;
}
message += `\nNote: Newest last, oldest first`;
message += `\n${t('Note: Newest last, oldest first')}`;
return {
type: 'message',
messageType: 'info',
@@ -102,8 +104,11 @@ const listCommand: SlashCommand = {
const saveCommand: SlashCommand = {
name: 'save',
description:
'Save the current conversation as a checkpoint. Usage: /chat save <tag>',
get description() {
return t(
'Save the current conversation as a checkpoint. Usage: /chat save <tag>',
);
},
kind: CommandKind.BUILT_IN,
action: async (context, args): Promise<SlashCommandActionReturn | void> => {
const tag = args.trim();
@@ -111,7 +116,7 @@ const saveCommand: SlashCommand = {
return {
type: 'message',
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(
Text,
null,
'A checkpoint with the tag ',
React.createElement(Text, { color: theme.text.accent }, tag),
' already exists. Do you want to overwrite it?',
t(
'A checkpoint with the tag {{tag}} already exists. Do you want to overwrite it?',
{
tag,
},
),
),
originalInvocation: {
raw: context.invocation?.raw || `/chat save ${tag}`,
@@ -142,7 +150,7 @@ const saveCommand: SlashCommand = {
return {
type: 'message',
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 {
type: 'message',
messageType: 'info',
content: `Conversation checkpoint saved with tag: ${decodeTagName(tag)}.`,
content: t('Conversation checkpoint saved with tag: {{tag}}.', {
tag: decodeTagName(tag),
}),
};
} else {
return {
type: 'message',
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 = {
name: 'resume',
altNames: ['load'],
description:
'Resume a conversation from a checkpoint. Usage: /chat resume <tag>',
get description() {
return t(
'Resume a conversation from a checkpoint. Usage: /chat resume <tag>',
);
},
kind: CommandKind.BUILT_IN,
action: async (context, args) => {
const tag = args.trim();
@@ -176,7 +189,7 @@ const resumeCommand: SlashCommand = {
return {
type: 'message',
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 {
type: 'message',
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 = {
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,
action: async (context, args): Promise<MessageActionReturn> => {
const tag = args.trim();
@@ -245,7 +262,7 @@ const deleteCommand: SlashCommand = {
return {
type: 'message',
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 {
type: 'message',
messageType: 'info',
content: `Conversation checkpoint '${decodeTagName(tag)}' has been deleted.`,
content: t("Conversation checkpoint '{{tag}}' has been deleted.", {
tag: decodeTagName(tag),
}),
};
} else {
return {
type: 'message',
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 = {
name: 'share',
description:
'Share the current conversation to a markdown or json file. Usage: /chat share <file>',
get description() {
return t(
'Share the current conversation to a markdown or json file. Usage: /chat share <file>',
);
},
kind: CommandKind.BUILT_IN,
action: async (context, args): Promise<MessageActionReturn> => {
let filePathArg = args.trim();
@@ -324,7 +348,7 @@ const shareCommand: SlashCommand = {
return {
type: 'message',
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 {
type: 'message',
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 {
type: 'message',
messageType: 'info',
content: 'No conversation found to share.',
content: t('No conversation found to share.'),
};
}
@@ -362,14 +386,18 @@ const shareCommand: SlashCommand = {
return {
type: 'message',
messageType: 'info',
content: `Conversation shared to ${filePath}`,
content: t('Conversation shared to {{filePath}}', {
filePath,
}),
};
} catch (err) {
const errorMessage = err instanceof Error ? err.message : String(err);
return {
type: 'message',
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 = {
name: 'chat',
description: 'Manage conversation history.',
get description() {
return t('Manage conversation history.');
},
kind: CommandKind.BUILT_IN,
subCommands: [
listCommand,

View File

@@ -7,21 +7,24 @@
import { uiTelemetryService } from '@qwen-code/qwen-code-core';
import type { SlashCommand } from './types.js';
import { CommandKind } from './types.js';
import { t } from '../../i18n/index.js';
export const clearCommand: SlashCommand = {
name: 'clear',
description: 'clear the screen and conversation history',
get description() {
return t('clear the screen and conversation history');
},
kind: CommandKind.BUILT_IN,
action: async (context, _args) => {
const geminiClient = context.services.config?.getGeminiClient();
if (geminiClient) {
context.ui.setDebugMessage('Clearing terminal and resetting chat.');
context.ui.setDebugMessage(t('Clearing terminal and resetting chat.'));
// If resetChat fails, the exception will propagate and halt the command,
// which is the correct behavior to signal a failure to the user.
await geminiClient.resetChat();
} else {
context.ui.setDebugMessage('Clearing terminal.');
context.ui.setDebugMessage(t('Clearing terminal.'));
}
uiTelemetryService.setLastPromptTokenCount(0);

View File

@@ -8,11 +8,14 @@ import type { HistoryItemCompression } from '../types.js';
import { MessageType } from '../types.js';
import type { SlashCommand } from './types.js';
import { CommandKind } from './types.js';
import { t } from '../../i18n/index.js';
export const compressCommand: SlashCommand = {
name: 'compress',
altNames: ['summarize'],
description: 'Compresses the context by replacing it with a summary.',
get description() {
return t('Compresses the context by replacing it with a summary.');
},
kind: CommandKind.BUILT_IN,
action: async (context) => {
const { ui } = context;
@@ -20,7 +23,7 @@ export const compressCommand: SlashCommand = {
ui.addItem(
{
type: MessageType.ERROR,
text: 'Already compressing, wait for previous request to complete',
text: t('Already compressing, wait for previous request to complete'),
},
Date.now(),
);
@@ -60,7 +63,7 @@ export const compressCommand: SlashCommand = {
ui.addItem(
{
type: MessageType.ERROR,
text: 'Failed to compress chat history.',
text: t('Failed to compress chat history.'),
},
Date.now(),
);
@@ -69,9 +72,9 @@ export const compressCommand: SlashCommand = {
ui.addItem(
{
type: MessageType.ERROR,
text: `Failed to compress chat history: ${
e instanceof Error ? e.message : String(e)
}`,
text: t('Failed to compress chat history: {{error}}', {
error: e instanceof Error ? e.message : String(e),
}),
},
Date.now(),
);

View File

@@ -7,10 +7,13 @@
import { copyToClipboard } from '../utils/commandUtils.js';
import type { SlashCommand, SlashCommandActionReturn } from './types.js';
import { CommandKind } from './types.js';
import { t } from '../../i18n/index.js';
export const copyCommand: SlashCommand = {
name: 'copy',
description: 'Copy the last result or code snippet to clipboard',
get description() {
return t('Copy the last result or code snippet to clipboard');
},
kind: CommandKind.BUILT_IN,
action: async (context, _args): Promise<SlashCommandActionReturn | void> => {
const chat = await context.services.config?.getGeminiClient()?.getChat();

View File

@@ -10,6 +10,7 @@ import { MessageType } from '../types.js';
import * as os from 'node:os';
import * as path from 'node:path';
import { loadServerHierarchicalMemory } from '@qwen-code/qwen-code-core';
import { t } from '../../i18n/index.js';
export function expandHomeDir(p: string): string {
if (!p) {
@@ -27,13 +28,18 @@ export function expandHomeDir(p: string): string {
export const directoryCommand: SlashCommand = {
name: 'directory',
altNames: ['dir'],
description: 'Manage workspace directories',
get description() {
return t('Manage workspace directories');
},
kind: CommandKind.BUILT_IN,
subCommands: [
{
name: 'add',
description:
'Add directories to the workspace. Use comma to separate multiple paths',
get description() {
return t(
'Add directories to the workspace. Use comma to separate multiple paths',
);
},
kind: CommandKind.BUILT_IN,
action: async (context: CommandContext, args: string) => {
const {
@@ -46,7 +52,7 @@ export const directoryCommand: SlashCommand = {
addItem(
{
type: MessageType.ERROR,
text: 'Configuration is not available.',
text: t('Configuration is not available.'),
},
Date.now(),
);
@@ -63,7 +69,7 @@ export const directoryCommand: SlashCommand = {
addItem(
{
type: MessageType.ERROR,
text: 'Please provide at least one path to add.',
text: t('Please provide at least one path to add.'),
},
Date.now(),
);
@@ -74,8 +80,9 @@ export const directoryCommand: SlashCommand = {
return {
type: 'message' as const,
messageType: 'error' as const,
content:
content: t(
'The /directory add command is not supported in restrictive sandbox profiles. Please use --include-directories when starting the session instead.',
),
};
}
@@ -88,7 +95,12 @@ export const directoryCommand: SlashCommand = {
added.push(pathToAdd.trim());
} catch (e) {
const error = e as Error;
errors.push(`Error adding '${pathToAdd.trim()}': ${error.message}`);
errors.push(
t("Error adding '{{path}}': {{error}}", {
path: pathToAdd.trim(),
error: error.message,
}),
);
}
}
@@ -117,12 +129,21 @@ export const directoryCommand: SlashCommand = {
addItem(
{
type: MessageType.INFO,
text: `Successfully added GEMINI.md files from the following directories if there are:\n- ${added.join('\n- ')}`,
text: t(
'Successfully added GEMINI.md files from the following directories if there are:\n- {{directories}}',
{
directories: added.join('\n- '),
},
),
},
Date.now(),
);
} catch (error) {
errors.push(`Error refreshing memory: ${(error as Error).message}`);
errors.push(
t('Error refreshing memory: {{error}}', {
error: (error as Error).message,
}),
);
}
if (added.length > 0) {
@@ -133,7 +154,9 @@ export const directoryCommand: SlashCommand = {
addItem(
{
type: MessageType.INFO,
text: `Successfully added directories:\n- ${added.join('\n- ')}`,
text: t('Successfully added directories:\n- {{directories}}', {
directories: added.join('\n- '),
}),
},
Date.now(),
);
@@ -150,7 +173,9 @@ export const directoryCommand: SlashCommand = {
},
{
name: 'show',
description: 'Show all directories in the workspace',
get description() {
return t('Show all directories in the workspace');
},
kind: CommandKind.BUILT_IN,
action: async (context: CommandContext) => {
const {
@@ -161,7 +186,7 @@ export const directoryCommand: SlashCommand = {
addItem(
{
type: MessageType.ERROR,
text: 'Configuration is not available.',
text: t('Configuration is not available.'),
},
Date.now(),
);
@@ -173,7 +198,9 @@ export const directoryCommand: SlashCommand = {
addItem(
{
type: MessageType.INFO,
text: `Current workspace directories:\n${directoryList}`,
text: t('Current workspace directories:\n{{directories}}', {
directories: directoryList,
}),
},
Date.now(),
);

View File

@@ -12,19 +12,28 @@ import {
CommandKind,
} from './types.js';
import { MessageType } from '../types.js';
import { t, getCurrentLanguage } from '../../i18n/index.js';
export const docsCommand: SlashCommand = {
name: 'docs',
description: 'open full Qwen Code documentation in your browser',
get description() {
return t('open full Qwen Code documentation in your browser');
},
kind: CommandKind.BUILT_IN,
action: async (context: CommandContext): Promise<void> => {
const docsUrl = 'https://qwenlm.github.io/qwen-code-docs/en';
const langPath = getCurrentLanguage()?.startsWith('zh') ? 'zh' : 'en';
const docsUrl = `https://qwenlm.github.io/qwen-code-docs/${langPath}`;
if (process.env['SANDBOX'] && process.env['SANDBOX'] !== 'sandbox-exec') {
context.ui.addItem(
{
type: MessageType.INFO,
text: `Please open the following URL in your browser to view the documentation:\n${docsUrl}`,
text: t(
'Please open the following URL in your browser to view the documentation:\n{{url}}',
{
url: docsUrl,
},
),
},
Date.now(),
);
@@ -32,7 +41,9 @@ export const docsCommand: SlashCommand = {
context.ui.addItem(
{
type: MessageType.INFO,
text: `Opening documentation in your browser: ${docsUrl}`,
text: t('Opening documentation in your browser: {{url}}', {
url: docsUrl,
}),
},
Date.now(),
);

View File

@@ -9,10 +9,13 @@ import {
type OpenDialogActionReturn,
type SlashCommand,
} from './types.js';
import { t } from '../../i18n/index.js';
export const editorCommand: SlashCommand = {
name: 'editor',
description: 'set external editor preference',
get description() {
return t('set external editor preference');
},
kind: CommandKind.BUILT_IN,
action: (): OpenDialogActionReturn => ({
type: 'dialog',

View File

@@ -19,6 +19,7 @@ import {
type SlashCommand,
CommandKind,
} from './types.js';
import { t } from '../../i18n/index.js';
async function listAction(context: CommandContext) {
context.ui.addItem(
@@ -131,14 +132,18 @@ async function updateAction(context: CommandContext, args: string) {
const listExtensionsCommand: SlashCommand = {
name: 'list',
description: 'List active extensions',
get description() {
return t('List active extensions');
},
kind: CommandKind.BUILT_IN,
action: listAction,
};
const updateExtensionsCommand: SlashCommand = {
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,
action: updateAction,
completion: async (context, partialArg) => {
@@ -158,7 +163,9 @@ const updateExtensionsCommand: SlashCommand = {
export const extensionsCommand: SlashCommand = {
name: 'extensions',
description: 'Manage extensions',
get description() {
return t('Manage extensions');
},
kind: CommandKind.BUILT_IN,
subCommands: [listExtensionsCommand, updateExtensionsCommand],
action: (context, args) =>

View File

@@ -7,12 +7,15 @@
import type { SlashCommand } from './types.js';
import { CommandKind } from './types.js';
import { MessageType, type HistoryItemHelp } from '../types.js';
import { t } from '../../i18n/index.js';
export const helpCommand: SlashCommand = {
name: 'help',
altNames: ['?'],
kind: CommandKind.BUILT_IN,
description: 'for help on Qwen Code',
get description() {
return t('for help on Qwen Code');
},
action: async (context) => {
const helpItem: Omit<HistoryItemHelp, 'id'> = {
type: MessageType.HELP,

View File

@@ -26,6 +26,7 @@ import type {
} from './types.js';
import { CommandKind } from './types.js';
import { SettingScope } from '../../config/settings.js';
import { t } from '../../i18n/index.js';
function getIdeStatusMessage(ideClient: IdeClient): {
messageType: 'info' | 'error';
@@ -138,27 +139,35 @@ export const ideCommand = async (): Promise<SlashCommand> => {
if (!currentIDE) {
return {
name: 'ide',
description: 'manage IDE integration',
get description() {
return t('manage IDE integration');
},
kind: CommandKind.BUILT_IN,
action: (): SlashCommandActionReturn =>
({
type: 'message',
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,
};
}
const ideSlashCommand: SlashCommand = {
name: 'ide',
description: 'manage IDE integration',
get description() {
return t('manage IDE integration');
},
kind: CommandKind.BUILT_IN,
subCommands: [],
};
const statusCommand: SlashCommand = {
name: 'status',
description: 'check status of IDE integration',
get description() {
return t('check status of IDE integration');
},
kind: CommandKind.BUILT_IN,
action: async (): Promise<SlashCommandActionReturn> => {
const { messageType, content } =
@@ -173,7 +182,12 @@ export const ideCommand = async (): Promise<SlashCommand> => {
const installCommand: SlashCommand = {
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,
action: async (context) => {
const installer = getIdeInstaller(currentIDE);
@@ -246,7 +260,9 @@ export const ideCommand = async (): Promise<SlashCommand> => {
const enableCommand: SlashCommand = {
name: 'enable',
description: 'enable IDE integration',
get description() {
return t('enable IDE integration');
},
kind: CommandKind.BUILT_IN,
action: async (context: CommandContext) => {
context.services.settings.setValue(
@@ -268,7 +284,9 @@ export const ideCommand = async (): Promise<SlashCommand> => {
const disableCommand: SlashCommand = {
name: 'disable',
description: 'disable IDE integration',
get description() {
return t('disable IDE integration');
},
kind: CommandKind.BUILT_IN,
action: async (context: CommandContext) => {
context.services.settings.setValue(

View File

@@ -15,10 +15,13 @@ import { getCurrentGeminiMdFilename } from '@qwen-code/qwen-code-core';
import { CommandKind } from './types.js';
import { Text } from 'ink';
import React from 'react';
import { t } from '../../i18n/index.js';
export const initCommand: SlashCommand = {
name: 'init',
description: 'Analyzes the project and creates a tailored QWEN.md file.',
get description() {
return t('Analyzes the project and creates a tailored QWEN.md file.');
},
kind: CommandKind.BUILT_IN,
action: async (
context: CommandContext,
@@ -28,7 +31,7 @@ export const initCommand: SlashCommand = {
return {
type: 'message',
messageType: 'error',
content: 'Configuration not available.',
content: t('Configuration not available.'),
};
}
const targetDir = context.services.config.getTargetDir();

View File

@@ -0,0 +1,458 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import type {
SlashCommand,
CommandContext,
SlashCommandActionReturn,
MessageActionReturn,
} from './types.js';
import { CommandKind } from './types.js';
import { SettingScope } from '../../config/settings.js';
import {
setLanguageAsync,
getCurrentLanguage,
type SupportedLanguage,
t,
} from '../../i18n/index.js';
import * as fs from 'node:fs';
import * as path from 'node:path';
import { Storage } from '@qwen-code/qwen-code-core';
const LLM_OUTPUT_LANGUAGE_RULE_FILENAME = 'output-language.md';
/**
* Generates the LLM output language rule template based on the language name.
*/
function generateLlmOutputLanguageRule(language: string): string {
return `# ⚠️ CRITICAL: ${language} Output Language Rule - HIGHEST PRIORITY ⚠️
## 🚨 MANDATORY RULE - NO EXCEPTIONS 🚨
**YOU MUST RESPOND IN ${language.toUpperCase()} FOR EVERY SINGLE OUTPUT, REGARDLESS OF THE USER'S INPUT LANGUAGE.**
This is a **NON-NEGOTIABLE** requirement. Even if the user writes in English, says "hi", asks a simple question, or explicitly requests another language, **YOU MUST ALWAYS RESPOND IN ${language.toUpperCase()}.**
## What Must Be in ${language}
**EVERYTHING** you output: conversation replies, tool call descriptions, success/error messages, generated file content (comments, documentation), and all explanatory text.
**Tool outputs**: All descriptive text from \`read_file\`, \`write_file\`, \`codebase_search\`, \`run_terminal_cmd\`, \`todo_write\`, \`web_search\`, etc. MUST be in ${language}.
## Examples
### ✅ CORRECT:
- User says "hi" → Respond in ${language} (e.g., "Bonjour" if ${language} is French)
- Tool result → "已成功读取文件 config.json" (if ${language} is Chinese)
- Error → "无法找到指定的文件" (if ${language} is Chinese)
### ❌ WRONG:
- User says "hi" → "Hello" in English
- Tool result → "Successfully read file" in English
- Error → "File not found" in English
## Notes
- Code elements (variable/function names, syntax) can remain in English
- Comments, documentation, and all other text MUST be in ${language}
**THIS RULE IS ACTIVE NOW. ALL OUTPUTS MUST BE IN ${language.toUpperCase()}. NO EXCEPTIONS.**
`;
}
/**
* Gets the path to the LLM output language rule file.
*/
function getLlmOutputLanguageRulePath(): string {
return path.join(
Storage.getGlobalQwenDir(),
LLM_OUTPUT_LANGUAGE_RULE_FILENAME,
);
}
/**
* Gets the current LLM output language from the rule file if it exists.
*/
function getCurrentLlmOutputLanguage(): string | null {
const filePath = getLlmOutputLanguageRulePath();
if (fs.existsSync(filePath)) {
try {
const content = fs.readFileSync(filePath, 'utf-8');
// Extract language name from the first line (e.g., "# Chinese Response Rules" -> "Chinese")
const match = content.match(/^#\s+(.+?)\s+Response Rules/i);
if (match) {
return match[1];
}
} catch {
// Ignore errors
}
}
return null;
}
/**
* Sets the UI language and persists it to settings.
*/
async function setUiLanguage(
context: CommandContext,
lang: SupportedLanguage,
): Promise<MessageActionReturn> {
const { services } = context;
const { settings } = services;
if (!services.config) {
return {
type: 'message',
messageType: 'error',
content: t('Configuration not available.'),
};
}
// Set language in i18n system (async to support JS translation files)
await setLanguageAsync(lang);
// Persist to settings (user scope)
if (settings && typeof settings.setValue === 'function') {
try {
settings.setValue(SettingScope.User, 'general.language', lang);
} catch (error) {
console.warn('Failed to save language setting:', error);
}
}
// Reload commands to update their descriptions with the new language
context.ui.reloadCommands();
// Map language codes to friendly display names
const langDisplayNames: Record<SupportedLanguage, string> = {
zh: '中文zh-CN',
en: 'Englishen-US',
};
return {
type: 'message',
messageType: 'info',
content: t('UI language changed to {{lang}}', {
lang: langDisplayNames[lang],
}),
};
}
/**
* Generates the LLM output language rule file.
*/
function generateLlmOutputLanguageRuleFile(
language: string,
): Promise<MessageActionReturn> {
try {
const filePath = getLlmOutputLanguageRulePath();
const content = generateLlmOutputLanguageRule(language);
// Ensure directory exists
const dir = path.dirname(filePath);
fs.mkdirSync(dir, { recursive: true });
// Write file (overwrite if exists)
fs.writeFileSync(filePath, content, 'utf-8');
return Promise.resolve({
type: 'message',
messageType: 'info',
content: [
t('LLM output language rule file generated at {{path}}', {
path: filePath,
}),
'',
t('Please restart the application for the changes to take effect.'),
].join('\n'),
});
} catch (error) {
return Promise.resolve({
type: 'message',
messageType: 'error',
content: t(
'Failed to generate LLM output language rule file: {{error}}',
{
error: error instanceof Error ? error.message : String(error),
},
),
});
}
}
export const languageCommand: SlashCommand = {
name: 'language',
get description() {
return t('View or change the language setting');
},
kind: CommandKind.BUILT_IN,
action: async (
context: CommandContext,
args: string,
): Promise<SlashCommandActionReturn> => {
const { services } = context;
if (!services.config) {
return {
type: 'message',
messageType: 'error',
content: t('Configuration not available.'),
};
}
const trimmedArgs = args.trim();
// If no arguments, show current language settings and usage
if (!trimmedArgs) {
const currentUiLang = getCurrentLanguage();
const currentLlmLang = getCurrentLlmOutputLanguage();
const message = [
t('Current UI language: {{lang}}', { lang: currentUiLang }),
currentLlmLang
? t('Current LLM output language: {{lang}}', { lang: currentLlmLang })
: t('LLM output language not set'),
'',
t('Available subcommands:'),
` /language ui [zh-CN|en-US] - ${t('Set UI language')}`,
` /language output <language> - ${t('Set LLM output language')}`,
].join('\n');
return {
type: 'message',
messageType: 'info',
content: message,
};
}
// Parse subcommand
const parts = trimmedArgs.split(/\s+/);
const subcommand = parts[0].toLowerCase();
if (subcommand === 'ui') {
// Handle /language ui [zh-CN|en-US]
if (parts.length === 1) {
// Show UI language subcommand help
return {
type: 'message',
messageType: 'info',
content: [
t('Set UI language'),
'',
t('Usage: /language ui [zh-CN|en-US]'),
'',
t('Available options:'),
t(' - zh-CN: Simplified Chinese'),
t(' - en-US: English'),
'',
t(
'To request additional UI language packs, please open an issue on GitHub.',
),
].join('\n'),
};
}
const langArg = parts[1].toLowerCase();
let targetLang: SupportedLanguage | null = null;
if (langArg === 'en' || langArg === 'english' || langArg === 'en-us') {
targetLang = 'en';
} else if (
langArg === 'zh' ||
langArg === 'chinese' ||
langArg === '中文' ||
langArg === 'zh-cn'
) {
targetLang = 'zh';
} else {
return {
type: 'message',
messageType: 'error',
content: t('Invalid language. Available: en-US, zh-CN'),
};
}
return setUiLanguage(context, targetLang);
} else if (subcommand === 'output') {
// Handle /language output <language>
if (parts.length === 1) {
return {
type: 'message',
messageType: 'info',
content: [
t('Set LLM output language'),
'',
t('Usage: /language output <language>'),
` ${t('Example: /language output 中文')}`,
].join('\n'),
};
}
// Join all parts after "output" as the language name
const language = parts.slice(1).join(' ');
return generateLlmOutputLanguageRuleFile(language);
} else {
// Backward compatibility: treat as UI language
const langArg = trimmedArgs.toLowerCase();
let targetLang: SupportedLanguage | null = null;
if (langArg === 'en' || langArg === 'english' || langArg === 'en-us') {
targetLang = 'en';
} else if (
langArg === 'zh' ||
langArg === 'chinese' ||
langArg === '中文' ||
langArg === 'zh-cn'
) {
targetLang = 'zh';
} else {
return {
type: 'message',
messageType: 'error',
content: [
t('Invalid command. Available subcommands:'),
' - /language ui [zh-CN|en-US] - ' + t('Set UI language'),
' - /language output <language> - ' + t('Set LLM output language'),
].join('\n'),
};
}
return setUiLanguage(context, targetLang);
}
},
subCommands: [
{
name: 'ui',
get description() {
return t('Set UI language');
},
kind: CommandKind.BUILT_IN,
action: async (
context: CommandContext,
args: string,
): Promise<MessageActionReturn> => {
const trimmedArgs = args.trim();
if (!trimmedArgs) {
return {
type: 'message',
messageType: 'info',
content: [
t('Set UI language'),
'',
t('Usage: /language ui [zh-CN|en-US]'),
'',
t('Available options:'),
t(' - zh-CN: Simplified Chinese'),
t(' - en-US: English'),
'',
t(
'To request additional UI language packs, please open an issue on GitHub.',
),
].join('\n'),
};
}
const langArg = trimmedArgs.toLowerCase();
let targetLang: SupportedLanguage | null = null;
if (langArg === 'en' || langArg === 'english' || langArg === 'en-us') {
targetLang = 'en';
} else if (
langArg === 'zh' ||
langArg === 'chinese' ||
langArg === '中文' ||
langArg === 'zh-cn'
) {
targetLang = 'zh';
} else {
return {
type: 'message',
messageType: 'error',
content: t('Invalid language. Available: en-US, zh-CN'),
};
}
return setUiLanguage(context, targetLang);
},
subCommands: [
{
name: 'zh-CN',
altNames: ['zh', 'chinese', '中文'],
get description() {
return t('Set UI language to Simplified Chinese (zh-CN)');
},
kind: CommandKind.BUILT_IN,
action: async (
context: CommandContext,
args: string,
): Promise<MessageActionReturn> => {
if (args.trim().length > 0) {
return {
type: 'message',
messageType: 'error',
content: t(
'Language subcommands do not accept additional arguments.',
),
};
}
return setUiLanguage(context, 'zh');
},
},
{
name: 'en-US',
altNames: ['en', 'english'],
get description() {
return t('Set UI language to English (en-US)');
},
kind: CommandKind.BUILT_IN,
action: async (
context: CommandContext,
args: string,
): Promise<MessageActionReturn> => {
if (args.trim().length > 0) {
return {
type: 'message',
messageType: 'error',
content: t(
'Language subcommands do not accept additional arguments.',
),
};
}
return setUiLanguage(context, 'en');
},
},
],
},
{
name: 'output',
get description() {
return t('Set LLM output language');
},
kind: CommandKind.BUILT_IN,
action: async (
context: CommandContext,
args: string,
): Promise<MessageActionReturn> => {
const trimmedArgs = args.trim();
if (!trimmedArgs) {
return {
type: 'message',
messageType: 'info',
content: [
t('Set LLM output language'),
'',
t('Usage: /language output <language>'),
` ${t('Example: /language output 中文')}`,
` ${t('Example: /language output English')}`,
` ${t('Example: /language output 日本語')}`,
].join('\n'),
};
}
return generateLlmOutputLanguageRuleFile(trimmedArgs);
},
},
],
};

View File

@@ -24,10 +24,13 @@ import {
} from '@qwen-code/qwen-code-core';
import { appEvents, AppEvent } from '../../utils/events.js';
import { MessageType, type HistoryItemMcpStatus } from '../types.js';
import { t } from '../../i18n/index.js';
const authCommand: SlashCommand = {
name: 'auth',
description: 'Authenticate with an OAuth-enabled MCP server',
get description() {
return t('Authenticate with an OAuth-enabled MCP server');
},
kind: CommandKind.BUILT_IN,
action: async (
context: CommandContext,
@@ -40,7 +43,7 @@ const authCommand: SlashCommand = {
return {
type: 'message',
messageType: 'error',
content: 'Config not loaded.',
content: t('Config not loaded.'),
};
}
@@ -56,14 +59,14 @@ const authCommand: SlashCommand = {
return {
type: 'message',
messageType: 'info',
content: 'No MCP servers configured with OAuth authentication.',
content: t('No MCP servers configured with OAuth authentication.'),
};
}
return {
type: 'message',
messageType: 'info',
content: `MCP servers with OAuth authentication:\n${oauthServers.map((s) => ` - ${s}`).join('\n')}\n\nUse /mcp auth <server-name> to authenticate.`,
content: `${t('MCP servers with OAuth authentication:')}\n${oauthServers.map((s) => ` - ${s}`).join('\n')}\n\n${t('Use /mcp auth <server-name> to authenticate.')}`,
};
}
@@ -72,7 +75,7 @@ const authCommand: SlashCommand = {
return {
type: 'message',
messageType: 'error',
content: `MCP server '${serverName}' not found.`,
content: t("MCP server '{{name}}' not found.", { name: serverName }),
};
}
@@ -89,7 +92,12 @@ const authCommand: SlashCommand = {
context.ui.addItem(
{
type: 'info',
text: `Starting OAuth authentication for MCP server '${serverName}'...`,
text: t(
"Starting OAuth authentication for MCP server '{{name}}'...",
{
name: serverName,
},
),
},
Date.now(),
);
@@ -111,7 +119,12 @@ const authCommand: SlashCommand = {
context.ui.addItem(
{
type: 'info',
text: `✅ Successfully authenticated with MCP server '${serverName}'!`,
text: t(
"Successfully authenticated and refreshed tools for '{{name}}'.",
{
name: serverName,
},
),
},
Date.now(),
);
@@ -122,7 +135,9 @@ const authCommand: SlashCommand = {
context.ui.addItem(
{
type: 'info',
text: `Re-discovering tools from '${serverName}'...`,
text: t("Re-discovering tools from '{{name}}'...", {
name: serverName,
}),
},
Date.now(),
);
@@ -140,13 +155,24 @@ const authCommand: SlashCommand = {
return {
type: 'message',
messageType: 'info',
content: `Successfully authenticated and refreshed tools for '${serverName}'.`,
content: t(
"Successfully authenticated and refreshed tools for '{{name}}'.",
{
name: serverName,
},
),
};
} catch (error) {
return {
type: 'message',
messageType: 'error',
content: `Failed to authenticate with MCP server '${serverName}': ${getErrorMessage(error)}`,
content: t(
"Failed to authenticate with MCP server '{{name}}': {{error}}",
{
name: serverName,
error: getErrorMessage(error),
},
),
};
} finally {
appEvents.removeListener(AppEvent.OauthDisplayMessage, displayListener);
@@ -165,7 +191,9 @@ const authCommand: SlashCommand = {
const listCommand: SlashCommand = {
name: 'list',
description: 'List configured MCP servers and tools',
get description() {
return t('List configured MCP servers and tools');
},
kind: CommandKind.BUILT_IN,
action: async (
context: CommandContext,
@@ -176,7 +204,7 @@ const listCommand: SlashCommand = {
return {
type: 'message',
messageType: 'error',
content: 'Config not loaded.',
content: t('Config not loaded.'),
};
}
@@ -185,7 +213,7 @@ const listCommand: SlashCommand = {
return {
type: 'message',
messageType: 'error',
content: 'Could not retrieve tool registry.',
content: t('Could not retrieve tool registry.'),
};
}
@@ -276,7 +304,9 @@ const listCommand: SlashCommand = {
const refreshCommand: SlashCommand = {
name: 'refresh',
description: 'Restarts MCP servers.',
get description() {
return t('Restarts MCP servers.');
},
kind: CommandKind.BUILT_IN,
action: async (
context: CommandContext,
@@ -286,7 +316,7 @@ const refreshCommand: SlashCommand = {
return {
type: 'message',
messageType: 'error',
content: 'Config not loaded.',
content: t('Config not loaded.'),
};
}
@@ -295,14 +325,14 @@ const refreshCommand: SlashCommand = {
return {
type: 'message',
messageType: 'error',
content: 'Could not retrieve tool registry.',
content: t('Could not retrieve tool registry.'),
};
}
context.ui.addItem(
{
type: 'info',
text: 'Restarting MCP servers...',
text: t('Restarting MCP servers...'),
},
Date.now(),
);
@@ -324,8 +354,11 @@ const refreshCommand: SlashCommand = {
export const mcpCommand: SlashCommand = {
name: 'mcp',
description:
'list configured MCP servers and tools, or authenticate with OAuth-enabled servers',
get description() {
return t(
'list configured MCP servers and tools, or authenticate with OAuth-enabled servers',
);
},
kind: CommandKind.BUILT_IN,
subCommands: [listCommand, authCommand, refreshCommand],
// Default action when no subcommand is provided

View File

@@ -15,15 +15,20 @@ import fs from 'fs/promises';
import { MessageType } from '../types.js';
import type { SlashCommand, SlashCommandActionReturn } from './types.js';
import { CommandKind } from './types.js';
import { t } from '../../i18n/index.js';
export const memoryCommand: SlashCommand = {
name: 'memory',
description: 'Commands for interacting with memory.',
get description() {
return t('Commands for interacting with memory.');
},
kind: CommandKind.BUILT_IN,
subCommands: [
{
name: 'show',
description: 'Show the current memory contents.',
get description() {
return t('Show the current memory contents.');
},
kind: CommandKind.BUILT_IN,
action: async (context) => {
const memoryContent = context.services.config?.getUserMemory() || '';
@@ -31,8 +36,8 @@ export const memoryCommand: SlashCommand = {
const messageContent =
memoryContent.length > 0
? `Current memory content from ${fileCount} file(s):\n\n---\n${memoryContent}\n---`
: 'Memory is currently empty.';
? `${t('Current memory content from {{count}} file(s):', { count: String(fileCount) })}\n\n---\n${memoryContent}\n---`
: t('Memory is currently empty.');
context.ui.addItem(
{
@@ -45,7 +50,9 @@ export const memoryCommand: SlashCommand = {
subCommands: [
{
name: '--project',
description: 'Show project-level memory contents.',
get description() {
return t('Show project-level memory contents.');
},
kind: CommandKind.BUILT_IN,
action: async (context) => {
try {
@@ -57,8 +64,14 @@ export const memoryCommand: SlashCommand = {
const messageContent =
memoryContent.trim().length > 0
? `Project memory content from ${projectMemoryPath}:\n\n---\n${memoryContent}\n---`
: 'Project memory is currently empty.';
? t(
'Project memory content from {{path}}:\n\n---\n{{content}}\n---',
{
path: projectMemoryPath,
content: memoryContent,
},
)
: t('Project memory is currently empty.');
context.ui.addItem(
{
@@ -71,7 +84,9 @@ export const memoryCommand: SlashCommand = {
context.ui.addItem(
{
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(),
);
@@ -80,7 +95,9 @@ export const memoryCommand: SlashCommand = {
},
{
name: '--global',
description: 'Show global memory contents.',
get description() {
return t('Show global memory contents.');
},
kind: CommandKind.BUILT_IN,
action: async (context) => {
try {
@@ -96,8 +113,10 @@ export const memoryCommand: SlashCommand = {
const messageContent =
globalMemoryContent.trim().length > 0
? `Global memory content:\n\n---\n${globalMemoryContent}\n---`
: 'Global memory is currently empty.';
? t('Global memory content:\n\n---\n{{content}}\n---', {
content: globalMemoryContent,
})
: t('Global memory is currently empty.');
context.ui.addItem(
{
@@ -110,7 +129,9 @@ export const memoryCommand: SlashCommand = {
context.ui.addItem(
{
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(),
);
@@ -121,16 +142,20 @@ export const memoryCommand: SlashCommand = {
},
{
name: 'add',
description:
'Add content to the memory. Use --global for global memory or --project for project memory.',
get description() {
return t(
'Add content to the memory. Use --global for global memory or --project for project memory.',
);
},
kind: CommandKind.BUILT_IN,
action: (context, args): SlashCommandActionReturn | void => {
if (!args || args.trim() === '') {
return {
type: 'message',
messageType: 'error',
content:
content: t(
'Usage: /memory add [--global|--project] <text to remember>',
),
};
}
@@ -150,8 +175,9 @@ export const memoryCommand: SlashCommand = {
return {
type: 'message',
messageType: 'error',
content:
content: t(
'Usage: /memory add [--global|--project] <text to remember>',
),
};
} else {
// No scope specified, will be handled by the tool
@@ -162,8 +188,9 @@ export const memoryCommand: SlashCommand = {
return {
type: 'message',
messageType: 'error',
content:
content: t(
'Usage: /memory add [--global|--project] <text to remember>',
),
};
}
@@ -171,7 +198,10 @@ export const memoryCommand: SlashCommand = {
context.ui.addItem(
{
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(),
);
@@ -185,21 +215,25 @@ export const memoryCommand: SlashCommand = {
subCommands: [
{
name: '--project',
description: 'Add content to project-level memory.',
get description() {
return t('Add content to project-level memory.');
},
kind: CommandKind.BUILT_IN,
action: (context, args): SlashCommandActionReturn | void => {
if (!args || args.trim() === '') {
return {
type: 'message',
messageType: 'error',
content: 'Usage: /memory add --project <text to remember>',
content: t('Usage: /memory add --project <text to remember>'),
};
}
context.ui.addItem(
{
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(),
);
@@ -213,21 +247,25 @@ export const memoryCommand: SlashCommand = {
},
{
name: '--global',
description: 'Add content to global memory.',
get description() {
return t('Add content to global memory.');
},
kind: CommandKind.BUILT_IN,
action: (context, args): SlashCommandActionReturn | void => {
if (!args || args.trim() === '') {
return {
type: 'message',
messageType: 'error',
content: 'Usage: /memory add --global <text to remember>',
content: t('Usage: /memory add --global <text to remember>'),
};
}
context.ui.addItem(
{
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(),
);
@@ -243,13 +281,15 @@ export const memoryCommand: SlashCommand = {
},
{
name: 'refresh',
description: 'Refresh the memory from the source.',
get description() {
return t('Refresh the memory from the source.');
},
kind: CommandKind.BUILT_IN,
action: async (context) => {
context.ui.addItem(
{
type: MessageType.INFO,
text: 'Refreshing memory from source files...',
text: t('Refreshing memory from source files...'),
},
Date.now(),
);

View File

@@ -12,10 +12,13 @@ import type {
} from './types.js';
import { CommandKind } from './types.js';
import { getAvailableModelsForAuthType } from '../models/availableModels.js';
import { t } from '../../i18n/index.js';
export const modelCommand: SlashCommand = {
name: 'model',
description: 'Switch the model for this session',
get description() {
return t('Switch the model for this session');
},
kind: CommandKind.BUILT_IN,
action: async (
context: CommandContext,
@@ -36,7 +39,7 @@ export const modelCommand: SlashCommand = {
return {
type: 'message',
messageType: 'error',
content: 'Content generator configuration not available.',
content: t('Content generator configuration not available.'),
};
}
@@ -45,7 +48,7 @@ export const modelCommand: SlashCommand = {
return {
type: 'message',
messageType: 'error',
content: 'Authentication type not available.',
content: t('Authentication type not available.'),
};
}
@@ -55,7 +58,12 @@ export const modelCommand: SlashCommand = {
return {
type: 'message',
messageType: 'error',
content: `No models available for the current authentication type (${authType}).`,
content: t(
'No models available for the current authentication type ({{authType}}).',
{
authType,
},
),
};
}

View File

@@ -6,10 +6,13 @@
import type { OpenDialogActionReturn, SlashCommand } from './types.js';
import { CommandKind } from './types.js';
import { t } from '../../i18n/index.js';
export const permissionsCommand: SlashCommand = {
name: 'permissions',
description: 'Manage folder trust settings',
get description() {
return t('Manage folder trust settings');
},
kind: CommandKind.BUILT_IN,
action: (): OpenDialogActionReturn => ({
type: 'dialog',

View File

@@ -6,10 +6,13 @@
import { formatDuration } from '../utils/formatters.js';
import { CommandKind, type SlashCommand } from './types.js';
import { t } from '../../i18n/index.js';
export const quitConfirmCommand: SlashCommand = {
name: 'quit-confirm',
description: 'Show quit confirmation dialog',
get description() {
return t('Show quit confirmation dialog');
},
kind: CommandKind.BUILT_IN,
action: (context) => {
const now = Date.now();
@@ -37,7 +40,9 @@ export const quitConfirmCommand: SlashCommand = {
export const quitCommand: SlashCommand = {
name: 'quit',
altNames: ['exit'],
description: 'exit the cli',
get description() {
return t('exit the cli');
},
kind: CommandKind.BUILT_IN,
action: (context) => {
const now = Date.now();

View File

@@ -6,10 +6,13 @@
import type { OpenDialogActionReturn, SlashCommand } from './types.js';
import { CommandKind } from './types.js';
import { t } from '../../i18n/index.js';
export const settingsCommand: SlashCommand = {
name: 'settings',
description: 'View and edit Qwen Code settings',
get description() {
return t('View and edit Qwen Code settings');
},
kind: CommandKind.BUILT_IN,
action: (_context, _args): OpenDialogActionReturn => ({
type: 'dialog',

View File

@@ -20,6 +20,7 @@ import {
import type { SlashCommand, SlashCommandActionReturn } from './types.js';
import { CommandKind } from './types.js';
import { getUrlOpenCommand } from '../../ui/utils/commandUtils.js';
import { t } from '../../i18n/index.js';
export const GITHUB_WORKFLOW_PATHS = [
'gemini-dispatch/gemini-dispatch.yml',
@@ -91,7 +92,9 @@ export async function updateGitignore(gitRepoRoot: string): Promise<void> {
export const setupGithubCommand: SlashCommand = {
name: 'setup-github',
description: 'Set up GitHub Actions',
get description() {
return t('Set up GitHub Actions');
},
kind: CommandKind.BUILT_IN,
action: async (
context: CommandContext,

View File

@@ -12,11 +12,14 @@ import {
type SlashCommand,
CommandKind,
} from './types.js';
import { t } from '../../i18n/index.js';
export const statsCommand: SlashCommand = {
name: 'stats',
altNames: ['usage'],
description: 'check session stats. Usage: /stats [model|tools]',
get description() {
return t('check session stats. Usage: /stats [model|tools]');
},
kind: CommandKind.BUILT_IN,
action: (context: CommandContext) => {
const now = new Date();
@@ -25,7 +28,7 @@ export const statsCommand: SlashCommand = {
context.ui.addItem(
{
type: MessageType.ERROR,
text: 'Session start time is unavailable, cannot calculate stats.',
text: t('Session start time is unavailable, cannot calculate stats.'),
},
Date.now(),
);
@@ -43,7 +46,9 @@ export const statsCommand: SlashCommand = {
subCommands: [
{
name: 'model',
description: 'Show model-specific usage statistics.',
get description() {
return t('Show model-specific usage statistics.');
},
kind: CommandKind.BUILT_IN,
action: (context: CommandContext) => {
context.ui.addItem(
@@ -56,7 +61,9 @@ export const statsCommand: SlashCommand = {
},
{
name: 'tools',
description: 'Show tool-specific usage statistics.',
get description() {
return t('Show tool-specific usage statistics.');
},
kind: CommandKind.BUILT_IN,
action: (context: CommandContext) => {
context.ui.addItem(

View File

@@ -13,11 +13,15 @@ import {
} from './types.js';
import { getProjectSummaryPrompt } from '@qwen-code/qwen-code-core';
import type { HistoryItemSummary } from '../types.js';
import { t } from '../../i18n/index.js';
export const summaryCommand: SlashCommand = {
name: 'summary',
description:
'Generate a project summary and save it to .qwen/PROJECT_SUMMARY.md',
get description() {
return t(
'Generate a project summary and save it to .qwen/PROJECT_SUMMARY.md',
);
},
kind: CommandKind.BUILT_IN,
action: async (context): Promise<SlashCommandActionReturn> => {
const { config } = context.services;
@@ -26,7 +30,7 @@ export const summaryCommand: SlashCommand = {
return {
type: 'message',
messageType: 'error',
content: 'Config not loaded.',
content: t('Config not loaded.'),
};
}
@@ -35,7 +39,7 @@ export const summaryCommand: SlashCommand = {
return {
type: 'message',
messageType: 'error',
content: 'No chat client available to generate summary.',
content: t('No chat client available to generate summary.'),
};
}
@@ -44,15 +48,18 @@ export const summaryCommand: SlashCommand = {
ui.addItem(
{
type: 'error' as const,
text: 'Already generating summary, wait for previous request to complete',
text: t(
'Already generating summary, wait for previous request to complete',
),
},
Date.now(),
);
return {
type: 'message',
messageType: 'error',
content:
content: t(
'Already generating summary, wait for previous request to complete',
),
};
}
@@ -65,7 +72,7 @@ export const summaryCommand: SlashCommand = {
return {
type: 'message',
messageType: 'info',
content: 'No conversation found to summarize.',
content: t('No conversation found to summarize.'),
};
}
@@ -171,9 +178,12 @@ export const summaryCommand: SlashCommand = {
ui.addItem(
{
type: 'error' as const,
text: `Failed to generate project context summary: ${
error instanceof Error ? error.message : String(error)
}`,
text: `${t(
'Failed to generate project context summary: {{error}}',
{
error: error instanceof Error ? error.message : String(error),
},
)}`,
},
Date.now(),
);
@@ -181,9 +191,9 @@ export const summaryCommand: SlashCommand = {
return {
type: 'message',
messageType: 'error',
content: `Failed to generate project context summary: ${
error instanceof Error ? error.message : String(error)
}`,
content: t('Failed to generate project context summary: {{error}}', {
error: error instanceof Error ? error.message : String(error),
}),
};
}
},

View File

@@ -7,6 +7,7 @@
import type { MessageActionReturn, SlashCommand } from './types.js';
import { CommandKind } from './types.js';
import { terminalSetup } from '../utils/terminalSetup.js';
import { t } from '../../i18n/index.js';
/**
* Command to configure terminal keybindings for multiline input support.
@@ -16,8 +17,11 @@ import { terminalSetup } from '../utils/terminalSetup.js';
*/
export const terminalSetupCommand: SlashCommand = {
name: 'terminal-setup',
description:
'Configure terminal keybindings for multiline input (VS Code, Cursor, Windsurf, Trae)',
get description() {
return t(
'Configure terminal keybindings for multiline input (VS Code, Cursor, Windsurf, Trae)',
);
},
kind: CommandKind.BUILT_IN,
action: async (): Promise<MessageActionReturn> => {
@@ -27,7 +31,8 @@ export const terminalSetupCommand: SlashCommand = {
let content = result.message;
if (result.requiresRestart) {
content +=
'\n\nPlease restart your terminal for the changes to take effect.';
'\n\n' +
t('Please restart your terminal for the changes to take effect.');
}
return {
@@ -38,7 +43,9 @@ export const terminalSetupCommand: SlashCommand = {
} catch (error) {
return {
type: 'message',
content: `Failed to configure terminal: ${error}`,
content: t('Failed to configure terminal: {{error}}', {
error: String(error),
}),
messageType: 'error',
};
}

View File

@@ -6,10 +6,13 @@
import type { OpenDialogActionReturn, SlashCommand } from './types.js';
import { CommandKind } from './types.js';
import { t } from '../../i18n/index.js';
export const themeCommand: SlashCommand = {
name: 'theme',
description: 'change the theme',
get description() {
return t('change the theme');
},
kind: CommandKind.BUILT_IN,
action: (_context, _args): OpenDialogActionReturn => ({
type: 'dialog',

View File

@@ -10,10 +10,13 @@ import {
CommandKind,
} from './types.js';
import { MessageType, type HistoryItemToolsList } from '../types.js';
import { t } from '../../i18n/index.js';
export const toolsCommand: SlashCommand = {
name: 'tools',
description: 'list available Qwen Code tools. Usage: /tools [desc]',
get description() {
return t('list available Qwen Code tools. Usage: /tools [desc]');
},
kind: CommandKind.BUILT_IN,
action: async (context: CommandContext, args?: string): Promise<void> => {
const subCommand = args?.trim();
@@ -29,7 +32,7 @@ export const toolsCommand: SlashCommand = {
context.ui.addItem(
{
type: MessageType.ERROR,
text: 'Could not retrieve tool registry.',
text: t('Could not retrieve tool registry.'),
},
Date.now(),
);

View File

@@ -6,10 +6,13 @@
import type { SlashCommand } from './types.js';
import { CommandKind } from './types.js';
import { t } from '../../i18n/index.js';
export const vimCommand: SlashCommand = {
name: 'vim',
description: 'toggle vim mode on/off',
get description() {
return t('toggle vim mode on/off');
},
kind: CommandKind.BUILT_IN,
action: async (context, _args) => {
const newVimState = await context.ui.toggleVimEnabled();