mirror of
https://github.com/QwenLM/qwen-code.git
synced 2025-12-20 08:47:44 +00:00
feat: Add i18n to commands description
This commit is contained in:
@@ -91,6 +91,7 @@ import { useGitBranchName } from './hooks/useGitBranchName.js';
|
||||
import { useExtensionUpdates } from './hooks/useExtensionUpdates.js';
|
||||
import { ShellFocusContext } from './contexts/ShellFocusContext.js';
|
||||
import { useQuitConfirmation } from './hooks/useQuitConfirmation.js';
|
||||
import { t } from '../i18n/index.js';
|
||||
import { useWelcomeBack } from './hooks/useWelcomeBack.js';
|
||||
import { useDialogClose } from './hooks/useDialogClose.js';
|
||||
import { type VisionSwitchOutcome } from './components/ModelSwitchDialog.js';
|
||||
@@ -372,14 +373,14 @@ export const AppContainer = (props: AppContainerProps) => {
|
||||
|
||||
// Handle Qwen OAuth timeout
|
||||
const handleQwenAuthTimeout = useCallback(() => {
|
||||
onAuthError('Qwen OAuth authentication timed out. Please try again.');
|
||||
onAuthError(t('Qwen OAuth authentication timed out. Please try again.'));
|
||||
cancelQwenAuth();
|
||||
setAuthState(AuthState.Updating);
|
||||
}, [onAuthError, cancelQwenAuth, setAuthState]);
|
||||
|
||||
// Handle Qwen OAuth cancel
|
||||
const handleQwenAuthCancel = useCallback(() => {
|
||||
onAuthError('Qwen OAuth authentication cancelled.');
|
||||
onAuthError(t('Qwen OAuth authentication cancelled.'));
|
||||
cancelQwenAuth();
|
||||
setAuthState(AuthState.Updating);
|
||||
}, [onAuthError, cancelQwenAuth, setAuthState]);
|
||||
@@ -401,7 +402,13 @@ export const AppContainer = (props: AppContainerProps) => {
|
||||
settings.merged.security?.auth.selectedType
|
||||
) {
|
||||
onAuthError(
|
||||
`Authentication is enforced to be ${settings.merged.security?.auth.enforcedType}, but you are currently using ${settings.merged.security?.auth.selectedType}.`,
|
||||
t(
|
||||
'Authentication is enforced to be {{enforcedType}}, but you are currently using {{currentType}}.',
|
||||
{
|
||||
enforcedType: settings.merged.security?.auth.enforcedType,
|
||||
currentType: settings.merged.security?.auth.selectedType,
|
||||
},
|
||||
),
|
||||
);
|
||||
} else if (
|
||||
settings.merged.security?.auth?.selectedType &&
|
||||
|
||||
@@ -14,6 +14,7 @@ import { Colors } from '../colors.js';
|
||||
import { useKeypress } from '../hooks/useKeypress.js';
|
||||
import { OpenAIKeyPrompt } from '../components/OpenAIKeyPrompt.js';
|
||||
import { RadioButtonSelect } from '../components/shared/RadioButtonSelect.js';
|
||||
import { t } from '../../i18n/index.js';
|
||||
|
||||
interface AuthDialogProps {
|
||||
onSelect: (
|
||||
@@ -53,10 +54,14 @@ export function AuthDialog({
|
||||
const items = [
|
||||
{
|
||||
key: AuthType.QWEN_OAUTH,
|
||||
label: 'Qwen OAuth',
|
||||
label: t('Qwen OAuth'),
|
||||
value: AuthType.QWEN_OAUTH,
|
||||
},
|
||||
{ key: AuthType.USE_OPENAI, label: 'OpenAI', value: AuthType.USE_OPENAI },
|
||||
{
|
||||
key: AuthType.USE_OPENAI,
|
||||
label: t('OpenAI'),
|
||||
value: AuthType.USE_OPENAI,
|
||||
},
|
||||
];
|
||||
|
||||
const initialAuthIndex = Math.max(
|
||||
@@ -107,7 +112,9 @@ export function AuthDialog({
|
||||
|
||||
const handleOpenAIKeyCancel = () => {
|
||||
setShowOpenAIKeyPrompt(false);
|
||||
setErrorMessage('OpenAI API key is required to use OpenAI authentication.');
|
||||
setErrorMessage(
|
||||
t('OpenAI API key is required to use OpenAI authentication.'),
|
||||
);
|
||||
};
|
||||
|
||||
useKeypress(
|
||||
@@ -125,7 +132,9 @@ export function AuthDialog({
|
||||
if (settings.merged.security?.auth?.selectedType === undefined) {
|
||||
// Prevent exiting if no auth method is set
|
||||
setErrorMessage(
|
||||
'You must select an auth method to proceed. Press Ctrl+C again to exit.',
|
||||
t(
|
||||
'You must select an auth method to proceed. Press Ctrl+C again to exit.',
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -165,9 +174,9 @@ export function AuthDialog({
|
||||
padding={1}
|
||||
width="100%"
|
||||
>
|
||||
<Text bold>Get started</Text>
|
||||
<Text bold>{t('Get started')}</Text>
|
||||
<Box marginTop={1}>
|
||||
<Text>How would you like to authenticate for this project?</Text>
|
||||
<Text>{t('How would you like to authenticate for this project?')}</Text>
|
||||
</Box>
|
||||
<Box marginTop={1}>
|
||||
<RadioButtonSelect
|
||||
@@ -182,10 +191,10 @@ export function AuthDialog({
|
||||
</Box>
|
||||
)}
|
||||
<Box marginTop={1}>
|
||||
<Text color={Colors.AccentPurple}>(Use Enter to Set Auth)</Text>
|
||||
<Text color={Colors.AccentPurple}>{t('(Use Enter to Set Auth)')}</Text>
|
||||
</Box>
|
||||
<Box marginTop={1}>
|
||||
<Text>Terms of Services and Privacy Notice for Qwen Code</Text>
|
||||
<Text>{t('Terms of Services and Privacy Notice for Qwen Code')}</Text>
|
||||
</Box>
|
||||
<Box marginTop={1}>
|
||||
<Text color={Colors.AccentBlue}>
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
} from '@qwen-code/qwen-code-core';
|
||||
import { AuthState } from '../types.js';
|
||||
import { validateAuthMethod } from '../../config/auth.js';
|
||||
import { t } from '../../i18n/index.js';
|
||||
|
||||
export function validateAuthMethodWithSettings(
|
||||
authType: AuthType,
|
||||
@@ -20,7 +21,13 @@ export function validateAuthMethodWithSettings(
|
||||
): string | null {
|
||||
const enforcedType = settings.merged.security?.auth?.enforcedType;
|
||||
if (enforcedType && enforcedType !== authType) {
|
||||
return `Authentication is enforced to be ${enforcedType}, but you are currently using ${authType}.`;
|
||||
return t(
|
||||
'Authentication is enforced to be {{enforcedType}}, but you are currently using {{currentType}}.',
|
||||
{
|
||||
enforcedType,
|
||||
currentType: authType,
|
||||
},
|
||||
);
|
||||
}
|
||||
if (settings.merged.security?.auth?.useExternal) {
|
||||
return null;
|
||||
@@ -76,7 +83,11 @@ export const useAuthCommand = (settings: LoadedSettings, config: Config) => {
|
||||
setAuthError(null);
|
||||
setAuthState(AuthState.Authenticated);
|
||||
} catch (e) {
|
||||
onAuthError(`Failed to login. Message: ${getErrorMessage(e)}`);
|
||||
onAuthError(
|
||||
t('Failed to login. Message: {{message}}', {
|
||||
message: getErrorMessage(e),
|
||||
}),
|
||||
);
|
||||
} finally {
|
||||
setIsAuthenticating(false);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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(),
|
||||
);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 {
|
||||
@@ -150,7 +156,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 {
|
||||
|
||||
@@ -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(),
|
||||
);
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -115,10 +115,18 @@ async function setUiLanguage(
|
||||
// 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: 'English(en-US)',
|
||||
};
|
||||
|
||||
return {
|
||||
type: 'message',
|
||||
messageType: 'info',
|
||||
content: t('UI language changed to {{lang}}', { lang }),
|
||||
content: t('UI language changed to {{lang}}', {
|
||||
lang: langDisplayNames[lang],
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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 }),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -111,7 +114,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 +130,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 +150,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 +186,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 +199,7 @@ const listCommand: SlashCommand = {
|
||||
return {
|
||||
type: 'message',
|
||||
messageType: 'error',
|
||||
content: 'Config not loaded.',
|
||||
content: t('Config not loaded.'),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -276,7 +299,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 +311,7 @@ const refreshCommand: SlashCommand = {
|
||||
return {
|
||||
type: 'message',
|
||||
messageType: 'error',
|
||||
content: 'Config not loaded.',
|
||||
content: t('Config not loaded.'),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -324,8 +349,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
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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();
|
||||
@@ -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(
|
||||
|
||||
@@ -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),
|
||||
}),
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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(),
|
||||
);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -26,6 +26,7 @@ import { useSettings } from '../contexts/SettingsContext.js';
|
||||
import { ApprovalMode } from '@qwen-code/qwen-code-core';
|
||||
import { StreamingState } from '../types.js';
|
||||
import { ConfigInitDisplay } from '../components/ConfigInitDisplay.js';
|
||||
import { t } from '../../i18n/index.js';
|
||||
|
||||
export const Composer = () => {
|
||||
const config = useConfig();
|
||||
@@ -86,14 +87,16 @@ export const Composer = () => {
|
||||
)}
|
||||
{uiState.ctrlCPressedOnce ? (
|
||||
<Text color={theme.status.warning}>
|
||||
Press Ctrl+C again to exit.
|
||||
{t('Press Ctrl+C again to exit.')}
|
||||
</Text>
|
||||
) : uiState.ctrlDPressedOnce ? (
|
||||
<Text color={theme.status.warning}>
|
||||
Press Ctrl+D again to exit.
|
||||
{t('Press Ctrl+D again to exit.')}
|
||||
</Text>
|
||||
) : uiState.showEscapePrompt ? (
|
||||
<Text color={theme.text.secondary}>Press Esc again to clear.</Text>
|
||||
<Text color={theme.text.secondary}>
|
||||
{t('Press Esc again to clear.')}
|
||||
</Text>
|
||||
) : (
|
||||
!settings.merged.ui?.hideContextSummary && (
|
||||
<ContextSummaryDisplay
|
||||
@@ -151,8 +154,8 @@ export const Composer = () => {
|
||||
isEmbeddedShellFocused={uiState.embeddedShellFocused}
|
||||
placeholder={
|
||||
vimEnabled
|
||||
? " Press 'i' for INSERT mode and 'Esc' for NORMAL mode."
|
||||
: ' Type your message or @path/to/file'
|
||||
? ' ' + t("Press 'i' for INSERT mode and 'Esc' for NORMAL mode.")
|
||||
: ' ' + t('Type your message or @path/to/file')
|
||||
}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -89,7 +89,7 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
|
||||
config,
|
||||
slashCommands,
|
||||
commandContext,
|
||||
placeholder = ' Type your message or @path/to/file',
|
||||
placeholder,
|
||||
focus = true,
|
||||
inputWidth,
|
||||
suggestionsWidth,
|
||||
|
||||
@@ -8,6 +8,7 @@ import type React from 'react';
|
||||
import { Box, Text } from 'ink';
|
||||
import { RadioButtonSelect } from './shared/RadioButtonSelect.js';
|
||||
import { theme } from '../semantic-colors.js';
|
||||
import { t } from '../../i18n/index.js';
|
||||
|
||||
interface ProQuotaDialogProps {
|
||||
failedModel: string;
|
||||
@@ -22,12 +23,12 @@ export function ProQuotaDialog({
|
||||
}: ProQuotaDialogProps): React.JSX.Element {
|
||||
const items = [
|
||||
{
|
||||
label: 'Change auth (executes the /auth command)',
|
||||
label: t('Change auth (executes the /auth command)'),
|
||||
value: 'auth' as const,
|
||||
key: 'auth',
|
||||
},
|
||||
{
|
||||
label: `Continue with ${fallbackModel}`,
|
||||
label: t('Continue with {{model}}', { model: fallbackModel }),
|
||||
value: 'continue' as const,
|
||||
key: 'continue',
|
||||
},
|
||||
@@ -40,7 +41,7 @@ export function ProQuotaDialog({
|
||||
return (
|
||||
<Box borderStyle="round" flexDirection="column" paddingX={1}>
|
||||
<Text bold color={theme.status.warning}>
|
||||
Pro quota limit reached for {failedModel}.
|
||||
{t('Pro quota limit reached for {{model}}.', { model: failedModel })}
|
||||
</Text>
|
||||
<Box marginTop={1}>
|
||||
<RadioButtonSelect
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
type RadioSelectItem,
|
||||
} from './shared/RadioButtonSelect.js';
|
||||
import { useKeypress } from '../hooks/useKeypress.js';
|
||||
import { t } from '../../i18n/index.js';
|
||||
|
||||
export enum QuitChoice {
|
||||
CANCEL = 'cancel',
|
||||
@@ -39,22 +40,22 @@ export const QuitConfirmationDialog: React.FC<QuitConfirmationDialogProps> = ({
|
||||
const options: Array<RadioSelectItem<QuitChoice>> = [
|
||||
{
|
||||
key: 'quit',
|
||||
label: 'Quit immediately (/quit)',
|
||||
label: t('Quit immediately (/quit)'),
|
||||
value: QuitChoice.QUIT,
|
||||
},
|
||||
{
|
||||
key: 'summary-and-quit',
|
||||
label: 'Generate summary and quit (/summary)',
|
||||
label: t('Generate summary and quit (/summary)'),
|
||||
value: QuitChoice.SUMMARY_AND_QUIT,
|
||||
},
|
||||
{
|
||||
key: 'save-and-quit',
|
||||
label: 'Save conversation and quit (/chat save)',
|
||||
label: t('Save conversation and quit (/chat save)'),
|
||||
value: QuitChoice.SAVE_AND_QUIT,
|
||||
},
|
||||
{
|
||||
key: 'cancel',
|
||||
label: 'Cancel (stay in application)',
|
||||
label: t('Cancel (stay in application)'),
|
||||
value: QuitChoice.CANCEL,
|
||||
},
|
||||
];
|
||||
@@ -69,7 +70,7 @@ export const QuitConfirmationDialog: React.FC<QuitConfirmationDialogProps> = ({
|
||||
marginLeft={1}
|
||||
>
|
||||
<Box flexDirection="column" marginBottom={1}>
|
||||
<Text>What would you like to do before exiting?</Text>
|
||||
<Text>{t('What would you like to do before exiting?')}</Text>
|
||||
</Box>
|
||||
|
||||
<RadioButtonSelect items={options} onSelect={onSelect} isFocused />
|
||||
|
||||
@@ -13,6 +13,7 @@ import qrcode from 'qrcode-terminal';
|
||||
import { Colors } from '../colors.js';
|
||||
import type { DeviceAuthorizationInfo } from '../hooks/useQwenAuth.js';
|
||||
import { useKeypress } from '../hooks/useKeypress.js';
|
||||
import { t } from '../../i18n/index.js';
|
||||
|
||||
interface QwenOAuthProgressProps {
|
||||
onTimeout: () => void;
|
||||
@@ -52,11 +53,11 @@ function QrCodeDisplay({
|
||||
width="100%"
|
||||
>
|
||||
<Text bold color={Colors.AccentBlue}>
|
||||
Qwen OAuth Authentication
|
||||
{t('Qwen OAuth Authentication')}
|
||||
</Text>
|
||||
|
||||
<Box marginTop={1}>
|
||||
<Text>Please visit this URL to authorize:</Text>
|
||||
<Text>{t('Please visit this URL to authorize:')}</Text>
|
||||
</Box>
|
||||
|
||||
<Link url={verificationUrl} fallback={false}>
|
||||
@@ -66,7 +67,7 @@ function QrCodeDisplay({
|
||||
</Link>
|
||||
|
||||
<Box marginTop={1}>
|
||||
<Text>Or scan the QR code below:</Text>
|
||||
<Text>{t('Or scan the QR code below:')}</Text>
|
||||
</Box>
|
||||
|
||||
<Box marginTop={1}>
|
||||
@@ -103,15 +104,18 @@ function StatusDisplay({
|
||||
>
|
||||
<Box marginTop={1}>
|
||||
<Text>
|
||||
<Spinner type="dots" /> Waiting for authorization{dots}
|
||||
<Spinner type="dots" /> {t('Waiting for authorization')}
|
||||
{dots}
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
<Box marginTop={1} justifyContent="space-between">
|
||||
<Text color={Colors.Gray}>
|
||||
Time remaining: {formatTime(timeRemaining)}
|
||||
{t('Time remaining:')} {formatTime(timeRemaining)}
|
||||
</Text>
|
||||
<Text color={Colors.AccentPurple}>
|
||||
{t('(Press ESC or CTRL+C to cancel)')}
|
||||
</Text>
|
||||
<Text color={Colors.AccentPurple}>(Press ESC or CTRL+C to cancel)</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
@@ -215,19 +219,24 @@ export function QwenOAuthProgress({
|
||||
width="100%"
|
||||
>
|
||||
<Text bold color={Colors.AccentRed}>
|
||||
Qwen OAuth Authentication Timeout
|
||||
{t('Qwen OAuth Authentication Timeout')}
|
||||
</Text>
|
||||
|
||||
<Box marginTop={1}>
|
||||
<Text>
|
||||
{authMessage ||
|
||||
`OAuth token expired (over ${defaultTimeout} seconds). Please select authentication method again.`}
|
||||
t(
|
||||
'OAuth token expired (over {{seconds}} seconds). Please select authentication method again.',
|
||||
{
|
||||
seconds: defaultTimeout.toString(),
|
||||
},
|
||||
)}
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
<Box marginTop={1}>
|
||||
<Text color={Colors.Gray}>
|
||||
Press any key to return to authentication type selection.
|
||||
{t('Press any key to return to authentication type selection.')}
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
@@ -246,16 +255,17 @@ export function QwenOAuthProgress({
|
||||
>
|
||||
<Box>
|
||||
<Text>
|
||||
<Spinner type="dots" /> Waiting for Qwen OAuth authentication...
|
||||
<Spinner type="dots" />
|
||||
{t('Waiting for Qwen OAuth authentication...')}
|
||||
</Text>
|
||||
</Box>
|
||||
<Box marginTop={1} justifyContent="space-between">
|
||||
<Text color={Colors.Gray}>
|
||||
Time remaining: {Math.floor(timeRemaining / 60)}:
|
||||
{t('Time remaining:')} {Math.floor(timeRemaining / 60)}:
|
||||
{(timeRemaining % 60).toString().padStart(2, '0')}
|
||||
</Text>
|
||||
<Text color={Colors.AccentPurple}>
|
||||
(Press ESC or CTRL+C to cancel)
|
||||
{t('(Press ESC or CTRL+C to cancel)')}
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
import type React from 'react';
|
||||
import { StatsDisplay } from './StatsDisplay.js';
|
||||
import { t } from '../../i18n/index.js';
|
||||
|
||||
interface SessionSummaryDisplayProps {
|
||||
duration: string;
|
||||
@@ -14,5 +15,8 @@ interface SessionSummaryDisplayProps {
|
||||
export const SessionSummaryDisplay: React.FC<SessionSummaryDisplayProps> = ({
|
||||
duration,
|
||||
}) => (
|
||||
<StatsDisplay title="Agent powering down. Goodbye!" duration={duration} />
|
||||
<StatsDisplay
|
||||
title={t('Agent powering down. Goodbye!')}
|
||||
duration={duration}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -12,6 +12,7 @@ import { RenderInline } from '../utils/InlineMarkdownRenderer.js';
|
||||
import type { RadioSelectItem } from './shared/RadioButtonSelect.js';
|
||||
import { RadioButtonSelect } from './shared/RadioButtonSelect.js';
|
||||
import { useKeypress } from '../hooks/useKeypress.js';
|
||||
import { t } from '../../i18n/index.js';
|
||||
|
||||
export interface ShellConfirmationRequest {
|
||||
commands: string[];
|
||||
@@ -51,17 +52,17 @@ export const ShellConfirmationDialog: React.FC<
|
||||
|
||||
const options: Array<RadioSelectItem<ToolConfirmationOutcome>> = [
|
||||
{
|
||||
label: 'Yes, allow once',
|
||||
label: t('Yes, allow once'),
|
||||
value: ToolConfirmationOutcome.ProceedOnce,
|
||||
key: 'Yes, allow once',
|
||||
},
|
||||
{
|
||||
label: 'Yes, allow always for this session',
|
||||
label: t('Yes, allow always for this session'),
|
||||
value: ToolConfirmationOutcome.ProceedAlways,
|
||||
key: 'Yes, allow always for this session',
|
||||
},
|
||||
{
|
||||
label: 'No (esc)',
|
||||
label: t('No (esc)'),
|
||||
value: ToolConfirmationOutcome.Cancel,
|
||||
key: 'No (esc)',
|
||||
},
|
||||
@@ -78,10 +79,10 @@ export const ShellConfirmationDialog: React.FC<
|
||||
>
|
||||
<Box flexDirection="column" marginBottom={1}>
|
||||
<Text bold color={theme.text.primary}>
|
||||
Shell Command Execution
|
||||
{t('Shell Command Execution')}
|
||||
</Text>
|
||||
<Text color={theme.text.primary}>
|
||||
A custom command wants to run the following shell commands:
|
||||
{t('A custom command wants to run the following shell commands:')}
|
||||
</Text>
|
||||
<Box
|
||||
flexDirection="column"
|
||||
@@ -99,7 +100,7 @@ export const ShellConfirmationDialog: React.FC<
|
||||
</Box>
|
||||
|
||||
<Box marginBottom={1}>
|
||||
<Text color={theme.text.primary}>Do you want to proceed?</Text>
|
||||
<Text color={theme.text.primary}>{t('Do you want to proceed?')}</Text>
|
||||
</Box>
|
||||
|
||||
<RadioButtonSelect items={options} onSelect={handleSelect} isFocused />
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
USER_AGREEMENT_RATE_MEDIUM,
|
||||
} from '../utils/displayUtils.js';
|
||||
import { computeSessionStats } from '../utils/computeStats.js';
|
||||
import { t } from '../../i18n/index.js';
|
||||
|
||||
// A more flexible and powerful StatRow component
|
||||
interface StatRowProps {
|
||||
@@ -85,22 +86,22 @@ const ModelUsageTable: React.FC<{
|
||||
<Box>
|
||||
<Box width={nameWidth}>
|
||||
<Text bold color={theme.text.primary}>
|
||||
Model Usage
|
||||
{t('Model Usage')}
|
||||
</Text>
|
||||
</Box>
|
||||
<Box width={requestsWidth} justifyContent="flex-end">
|
||||
<Text bold color={theme.text.primary}>
|
||||
Reqs
|
||||
{t('Reqs')}
|
||||
</Text>
|
||||
</Box>
|
||||
<Box width={inputTokensWidth} justifyContent="flex-end">
|
||||
<Text bold color={theme.text.primary}>
|
||||
Input Tokens
|
||||
{t('Input Tokens')}
|
||||
</Text>
|
||||
</Box>
|
||||
<Box width={outputTokensWidth} justifyContent="flex-end">
|
||||
<Text bold color={theme.text.primary}>
|
||||
Output Tokens
|
||||
{t('Output Tokens')}
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
@@ -141,13 +142,14 @@ const ModelUsageTable: React.FC<{
|
||||
{cacheEfficiency > 0 && (
|
||||
<Box flexDirection="column" marginTop={1}>
|
||||
<Text color={theme.text.primary}>
|
||||
<Text color={theme.status.success}>Savings Highlight:</Text>{' '}
|
||||
<Text color={theme.status.success}>{t('Savings Highlight:')}</Text>{' '}
|
||||
{totalCachedTokens.toLocaleString()} ({cacheEfficiency.toFixed(1)}
|
||||
%) of input tokens were served from the cache, reducing costs.
|
||||
%){' '}
|
||||
{t('of input tokens were served from the cache, reducing costs.')}
|
||||
</Text>
|
||||
<Box height={1} />
|
||||
<Text color={theme.text.secondary}>
|
||||
» Tip: For a full token breakdown, run `/stats model`.
|
||||
» {t('Tip: For a full token breakdown, run `/stats model`.')}
|
||||
</Text>
|
||||
</Box>
|
||||
)}
|
||||
@@ -199,7 +201,7 @@ export const StatsDisplay: React.FC<StatsDisplayProps> = ({
|
||||
}
|
||||
return (
|
||||
<Text bold color={theme.text.accent}>
|
||||
Session Stats
|
||||
{t('Session Stats')}
|
||||
</Text>
|
||||
);
|
||||
};
|
||||
@@ -215,33 +217,33 @@ export const StatsDisplay: React.FC<StatsDisplayProps> = ({
|
||||
{renderTitle()}
|
||||
<Box height={1} />
|
||||
|
||||
<Section title="Interaction Summary">
|
||||
<StatRow title="Session ID:">
|
||||
<Section title={t('Interaction Summary')}>
|
||||
<StatRow title={t('Session ID:')}>
|
||||
<Text color={theme.text.primary}>{stats.sessionId}</Text>
|
||||
</StatRow>
|
||||
<StatRow title="Tool Calls:">
|
||||
<StatRow title={t('Tool Calls:')}>
|
||||
<Text color={theme.text.primary}>
|
||||
{tools.totalCalls} ({' '}
|
||||
<Text color={theme.status.success}>✓ {tools.totalSuccess}</Text>{' '}
|
||||
<Text color={theme.status.error}>x {tools.totalFail}</Text> )
|
||||
</Text>
|
||||
</StatRow>
|
||||
<StatRow title="Success Rate:">
|
||||
<StatRow title={t('Success Rate:')}>
|
||||
<Text color={successColor}>{computed.successRate.toFixed(1)}%</Text>
|
||||
</StatRow>
|
||||
{computed.totalDecisions > 0 && (
|
||||
<StatRow title="User Agreement:">
|
||||
<StatRow title={t('User Agreement:')}>
|
||||
<Text color={agreementColor}>
|
||||
{computed.agreementRate.toFixed(1)}%{' '}
|
||||
<Text color={theme.text.secondary}>
|
||||
({computed.totalDecisions} reviewed)
|
||||
({computed.totalDecisions} {t('reviewed')})
|
||||
</Text>
|
||||
</Text>
|
||||
</StatRow>
|
||||
)}
|
||||
{files &&
|
||||
(files.totalLinesAdded > 0 || files.totalLinesRemoved > 0) && (
|
||||
<StatRow title="Code Changes:">
|
||||
<StatRow title={t('Code Changes:')}>
|
||||
<Text color={theme.text.primary}>
|
||||
<Text color={theme.status.success}>
|
||||
+{files.totalLinesAdded}
|
||||
@@ -254,16 +256,16 @@ export const StatsDisplay: React.FC<StatsDisplayProps> = ({
|
||||
)}
|
||||
</Section>
|
||||
|
||||
<Section title="Performance">
|
||||
<StatRow title="Wall Time:">
|
||||
<Section title={t('Performance')}>
|
||||
<StatRow title={t('Wall Time:')}>
|
||||
<Text color={theme.text.primary}>{duration}</Text>
|
||||
</StatRow>
|
||||
<StatRow title="Agent Active:">
|
||||
<StatRow title={t('Agent Active:')}>
|
||||
<Text color={theme.text.primary}>
|
||||
{formatDuration(computed.agentActiveTime)}
|
||||
</Text>
|
||||
</StatRow>
|
||||
<SubStatRow title="API Time:">
|
||||
<SubStatRow title={t('API Time:')}>
|
||||
<Text color={theme.text.primary}>
|
||||
{formatDuration(computed.totalApiTime)}{' '}
|
||||
<Text color={theme.text.secondary}>
|
||||
@@ -271,7 +273,7 @@ export const StatsDisplay: React.FC<StatsDisplayProps> = ({
|
||||
</Text>
|
||||
</Text>
|
||||
</SubStatRow>
|
||||
<SubStatRow title="Tool Time:">
|
||||
<SubStatRow title={t('Tool Time:')}>
|
||||
<Text color={theme.text.primary}>
|
||||
{formatDuration(computed.totalToolTime)}{' '}
|
||||
<Text color={theme.text.secondary}>
|
||||
|
||||
@@ -8,6 +8,7 @@ import type React from 'react';
|
||||
import { Box, Text } from 'ink';
|
||||
import { theme } from '../semantic-colors.js';
|
||||
import { type Config } from '@qwen-code/qwen-code-core';
|
||||
import { t } from '../../i18n/index.js';
|
||||
|
||||
interface TipsProps {
|
||||
config: Config;
|
||||
@@ -17,12 +18,12 @@ export const Tips: React.FC<TipsProps> = ({ config }) => {
|
||||
const geminiMdFileCount = config.getGeminiMdFileCount();
|
||||
return (
|
||||
<Box flexDirection="column">
|
||||
<Text color={theme.text.primary}>Tips for getting started:</Text>
|
||||
<Text color={theme.text.primary}>{t('Tips for getting started:')}</Text>
|
||||
<Text color={theme.text.primary}>
|
||||
1. Ask questions, edit files, or run commands.
|
||||
{t('1. Ask questions, edit files, or run commands.')}
|
||||
</Text>
|
||||
<Text color={theme.text.primary}>
|
||||
2. Be specific for the best results.
|
||||
{t('2. Be specific for the best results.')}
|
||||
</Text>
|
||||
{geminiMdFileCount === 0 && (
|
||||
<Text color={theme.text.primary}>
|
||||
@@ -30,7 +31,7 @@ export const Tips: React.FC<TipsProps> = ({ config }) => {
|
||||
<Text bold color={theme.text.accent}>
|
||||
QWEN.md
|
||||
</Text>{' '}
|
||||
files to customize your interactions with Qwen Code.
|
||||
{t('files to customize your interactions with Qwen Code.')}
|
||||
</Text>
|
||||
)}
|
||||
<Text color={theme.text.primary}>
|
||||
@@ -38,7 +39,7 @@ export const Tips: React.FC<TipsProps> = ({ config }) => {
|
||||
<Text bold color={theme.text.accent}>
|
||||
/help
|
||||
</Text>{' '}
|
||||
for more information.
|
||||
{t('for more information.')}
|
||||
</Text>
|
||||
</Box>
|
||||
);
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
type RadioSelectItem,
|
||||
} from './shared/RadioButtonSelect.js';
|
||||
import { useKeypress } from '../hooks/useKeypress.js';
|
||||
import { t } from '../../i18n/index.js';
|
||||
|
||||
interface WelcomeBackDialogProps {
|
||||
welcomeBackInfo: ProjectSummaryInfo;
|
||||
@@ -36,12 +37,12 @@ export function WelcomeBackDialog({
|
||||
const options: Array<RadioSelectItem<'restart' | 'continue'>> = [
|
||||
{
|
||||
key: 'restart',
|
||||
label: 'Start new chat session',
|
||||
label: t('Start new chat session'),
|
||||
value: 'restart',
|
||||
},
|
||||
{
|
||||
key: 'continue',
|
||||
label: 'Continue previous conversation',
|
||||
label: t('Continue previous conversation'),
|
||||
value: 'continue',
|
||||
},
|
||||
];
|
||||
@@ -67,7 +68,9 @@ export function WelcomeBackDialog({
|
||||
>
|
||||
<Box flexDirection="column" marginBottom={1}>
|
||||
<Text color={Colors.AccentBlue} bold>
|
||||
👋 Welcome back! (Last updated: {timeAgo})
|
||||
{t('👋 Welcome back! (Last updated: {{timeAgo}})', {
|
||||
timeAgo: timeAgo || '',
|
||||
})}
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
@@ -75,7 +78,7 @@ export function WelcomeBackDialog({
|
||||
{goalContent && (
|
||||
<Box flexDirection="column" marginBottom={1}>
|
||||
<Text color={Colors.Foreground} bold>
|
||||
🎯 Overall Goal:
|
||||
{t('🎯 Overall Goal:')}
|
||||
</Text>
|
||||
<Box marginTop={1} paddingLeft={2}>
|
||||
<Text color={Colors.Gray}>{goalContent}</Text>
|
||||
@@ -87,19 +90,25 @@ export function WelcomeBackDialog({
|
||||
{totalTasks > 0 && (
|
||||
<Box flexDirection="column" marginBottom={1}>
|
||||
<Text color={Colors.Foreground} bold>
|
||||
📋 Current Plan:
|
||||
📋 {t('Current Plan:')}
|
||||
</Text>
|
||||
<Box marginTop={1} paddingLeft={2}>
|
||||
<Text color={Colors.Gray}>
|
||||
Progress: {doneCount}/{totalTasks} tasks completed
|
||||
{inProgressCount > 0 && `, ${inProgressCount} in progress`}
|
||||
{t('Progress: {{done}}/{{total}} tasks completed', {
|
||||
done: String(doneCount),
|
||||
total: String(totalTasks),
|
||||
})}
|
||||
{inProgressCount > 0 &&
|
||||
t(', {{inProgress}} in progress', {
|
||||
inProgress: String(inProgressCount),
|
||||
})}
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
{pendingTasks.length > 0 && (
|
||||
<Box flexDirection="column" marginTop={1} paddingLeft={2}>
|
||||
<Text color={Colors.Foreground} bold>
|
||||
Pending Tasks:
|
||||
{t('Pending Tasks:')}
|
||||
</Text>
|
||||
{pendingTasks.map((task: string, index: number) => (
|
||||
<Text key={index} color={Colors.Gray}>
|
||||
@@ -113,8 +122,8 @@ export function WelcomeBackDialog({
|
||||
|
||||
{/* Action Selection */}
|
||||
<Box flexDirection="column" marginTop={1}>
|
||||
<Text bold>What would you like to do?</Text>
|
||||
<Text>Choose how to proceed with your session:</Text>
|
||||
<Text bold>{t('What would you like to do?')}</Text>
|
||||
<Text>{t('Choose how to proceed with your session:')}</Text>
|
||||
</Box>
|
||||
|
||||
<Box marginTop={1}>
|
||||
|
||||
Reference in New Issue
Block a user