mirror of
https://github.com/QwenLM/qwen-code.git
synced 2025-12-19 09:33:53 +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 { useExtensionUpdates } from './hooks/useExtensionUpdates.js';
|
||||||
import { ShellFocusContext } from './contexts/ShellFocusContext.js';
|
import { ShellFocusContext } from './contexts/ShellFocusContext.js';
|
||||||
import { useQuitConfirmation } from './hooks/useQuitConfirmation.js';
|
import { useQuitConfirmation } from './hooks/useQuitConfirmation.js';
|
||||||
|
import { t } from '../i18n/index.js';
|
||||||
import { useWelcomeBack } from './hooks/useWelcomeBack.js';
|
import { useWelcomeBack } from './hooks/useWelcomeBack.js';
|
||||||
import { useDialogClose } from './hooks/useDialogClose.js';
|
import { useDialogClose } from './hooks/useDialogClose.js';
|
||||||
import { type VisionSwitchOutcome } from './components/ModelSwitchDialog.js';
|
import { type VisionSwitchOutcome } from './components/ModelSwitchDialog.js';
|
||||||
@@ -372,14 +373,14 @@ export const AppContainer = (props: AppContainerProps) => {
|
|||||||
|
|
||||||
// Handle Qwen OAuth timeout
|
// Handle Qwen OAuth timeout
|
||||||
const handleQwenAuthTimeout = useCallback(() => {
|
const handleQwenAuthTimeout = useCallback(() => {
|
||||||
onAuthError('Qwen OAuth authentication timed out. Please try again.');
|
onAuthError(t('Qwen OAuth authentication timed out. Please try again.'));
|
||||||
cancelQwenAuth();
|
cancelQwenAuth();
|
||||||
setAuthState(AuthState.Updating);
|
setAuthState(AuthState.Updating);
|
||||||
}, [onAuthError, cancelQwenAuth, setAuthState]);
|
}, [onAuthError, cancelQwenAuth, setAuthState]);
|
||||||
|
|
||||||
// Handle Qwen OAuth cancel
|
// Handle Qwen OAuth cancel
|
||||||
const handleQwenAuthCancel = useCallback(() => {
|
const handleQwenAuthCancel = useCallback(() => {
|
||||||
onAuthError('Qwen OAuth authentication cancelled.');
|
onAuthError(t('Qwen OAuth authentication cancelled.'));
|
||||||
cancelQwenAuth();
|
cancelQwenAuth();
|
||||||
setAuthState(AuthState.Updating);
|
setAuthState(AuthState.Updating);
|
||||||
}, [onAuthError, cancelQwenAuth, setAuthState]);
|
}, [onAuthError, cancelQwenAuth, setAuthState]);
|
||||||
@@ -401,7 +402,13 @@ export const AppContainer = (props: AppContainerProps) => {
|
|||||||
settings.merged.security?.auth.selectedType
|
settings.merged.security?.auth.selectedType
|
||||||
) {
|
) {
|
||||||
onAuthError(
|
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 (
|
} else if (
|
||||||
settings.merged.security?.auth?.selectedType &&
|
settings.merged.security?.auth?.selectedType &&
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import { Colors } from '../colors.js';
|
|||||||
import { useKeypress } from '../hooks/useKeypress.js';
|
import { useKeypress } from '../hooks/useKeypress.js';
|
||||||
import { OpenAIKeyPrompt } from '../components/OpenAIKeyPrompt.js';
|
import { OpenAIKeyPrompt } from '../components/OpenAIKeyPrompt.js';
|
||||||
import { RadioButtonSelect } from '../components/shared/RadioButtonSelect.js';
|
import { RadioButtonSelect } from '../components/shared/RadioButtonSelect.js';
|
||||||
|
import { t } from '../../i18n/index.js';
|
||||||
|
|
||||||
interface AuthDialogProps {
|
interface AuthDialogProps {
|
||||||
onSelect: (
|
onSelect: (
|
||||||
@@ -53,10 +54,14 @@ export function AuthDialog({
|
|||||||
const items = [
|
const items = [
|
||||||
{
|
{
|
||||||
key: AuthType.QWEN_OAUTH,
|
key: AuthType.QWEN_OAUTH,
|
||||||
label: 'Qwen OAuth',
|
label: t('Qwen OAuth'),
|
||||||
value: AuthType.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(
|
const initialAuthIndex = Math.max(
|
||||||
@@ -107,7 +112,9 @@ export function AuthDialog({
|
|||||||
|
|
||||||
const handleOpenAIKeyCancel = () => {
|
const handleOpenAIKeyCancel = () => {
|
||||||
setShowOpenAIKeyPrompt(false);
|
setShowOpenAIKeyPrompt(false);
|
||||||
setErrorMessage('OpenAI API key is required to use OpenAI authentication.');
|
setErrorMessage(
|
||||||
|
t('OpenAI API key is required to use OpenAI authentication.'),
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
useKeypress(
|
useKeypress(
|
||||||
@@ -125,7 +132,9 @@ export function AuthDialog({
|
|||||||
if (settings.merged.security?.auth?.selectedType === undefined) {
|
if (settings.merged.security?.auth?.selectedType === undefined) {
|
||||||
// Prevent exiting if no auth method is set
|
// Prevent exiting if no auth method is set
|
||||||
setErrorMessage(
|
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;
|
return;
|
||||||
}
|
}
|
||||||
@@ -165,9 +174,9 @@ export function AuthDialog({
|
|||||||
padding={1}
|
padding={1}
|
||||||
width="100%"
|
width="100%"
|
||||||
>
|
>
|
||||||
<Text bold>Get started</Text>
|
<Text bold>{t('Get started')}</Text>
|
||||||
<Box marginTop={1}>
|
<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>
|
||||||
<Box marginTop={1}>
|
<Box marginTop={1}>
|
||||||
<RadioButtonSelect
|
<RadioButtonSelect
|
||||||
@@ -182,10 +191,10 @@ export function AuthDialog({
|
|||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
<Box marginTop={1}>
|
<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>
|
||||||
<Box marginTop={1}>
|
<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>
|
||||||
<Box marginTop={1}>
|
<Box marginTop={1}>
|
||||||
<Text color={Colors.AccentBlue}>
|
<Text color={Colors.AccentBlue}>
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
} from '@qwen-code/qwen-code-core';
|
} from '@qwen-code/qwen-code-core';
|
||||||
import { AuthState } from '../types.js';
|
import { AuthState } from '../types.js';
|
||||||
import { validateAuthMethod } from '../../config/auth.js';
|
import { validateAuthMethod } from '../../config/auth.js';
|
||||||
|
import { t } from '../../i18n/index.js';
|
||||||
|
|
||||||
export function validateAuthMethodWithSettings(
|
export function validateAuthMethodWithSettings(
|
||||||
authType: AuthType,
|
authType: AuthType,
|
||||||
@@ -20,7 +21,13 @@ export function validateAuthMethodWithSettings(
|
|||||||
): string | null {
|
): string | null {
|
||||||
const enforcedType = settings.merged.security?.auth?.enforcedType;
|
const enforcedType = settings.merged.security?.auth?.enforcedType;
|
||||||
if (enforcedType && enforcedType !== authType) {
|
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) {
|
if (settings.merged.security?.auth?.useExternal) {
|
||||||
return null;
|
return null;
|
||||||
@@ -76,7 +83,11 @@ export const useAuthCommand = (settings: LoadedSettings, config: Config) => {
|
|||||||
setAuthError(null);
|
setAuthError(null);
|
||||||
setAuthState(AuthState.Authenticated);
|
setAuthState(AuthState.Authenticated);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
onAuthError(`Failed to login. Message: ${getErrorMessage(e)}`);
|
onAuthError(
|
||||||
|
t('Failed to login. Message: {{message}}', {
|
||||||
|
message: getErrorMessage(e),
|
||||||
|
}),
|
||||||
|
);
|
||||||
} finally {
|
} finally {
|
||||||
setIsAuthenticating(false);
|
setIsAuthenticating(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,10 +8,13 @@ import type { SlashCommand } from './types.js';
|
|||||||
import { CommandKind } from './types.js';
|
import { CommandKind } from './types.js';
|
||||||
import { MessageType, type HistoryItemAbout } from '../types.js';
|
import { MessageType, type HistoryItemAbout } from '../types.js';
|
||||||
import { getExtendedSystemInfo } from '../../utils/systemInfo.js';
|
import { getExtendedSystemInfo } from '../../utils/systemInfo.js';
|
||||||
|
import { t } from '../../i18n/index.js';
|
||||||
|
|
||||||
export const aboutCommand: SlashCommand = {
|
export const aboutCommand: SlashCommand = {
|
||||||
name: 'about',
|
name: 'about',
|
||||||
description: 'show version info',
|
get description() {
|
||||||
|
return t('show version info');
|
||||||
|
},
|
||||||
kind: CommandKind.BUILT_IN,
|
kind: CommandKind.BUILT_IN,
|
||||||
action: async (context) => {
|
action: async (context) => {
|
||||||
const systemInfo = await getExtendedSystemInfo(context);
|
const systemInfo = await getExtendedSystemInfo(context);
|
||||||
|
|||||||
@@ -9,15 +9,20 @@ import {
|
|||||||
type SlashCommand,
|
type SlashCommand,
|
||||||
type OpenDialogActionReturn,
|
type OpenDialogActionReturn,
|
||||||
} from './types.js';
|
} from './types.js';
|
||||||
|
import { t } from '../../i18n/index.js';
|
||||||
|
|
||||||
export const agentsCommand: SlashCommand = {
|
export const agentsCommand: SlashCommand = {
|
||||||
name: 'agents',
|
name: 'agents',
|
||||||
description: 'Manage subagents for specialized task delegation.',
|
get description() {
|
||||||
|
return t('Manage subagents for specialized task delegation.');
|
||||||
|
},
|
||||||
kind: CommandKind.BUILT_IN,
|
kind: CommandKind.BUILT_IN,
|
||||||
subCommands: [
|
subCommands: [
|
||||||
{
|
{
|
||||||
name: 'manage',
|
name: 'manage',
|
||||||
description: 'Manage existing subagents (view, edit, delete).',
|
get description() {
|
||||||
|
return t('Manage existing subagents (view, edit, delete).');
|
||||||
|
},
|
||||||
kind: CommandKind.BUILT_IN,
|
kind: CommandKind.BUILT_IN,
|
||||||
action: (): OpenDialogActionReturn => ({
|
action: (): OpenDialogActionReturn => ({
|
||||||
type: 'dialog',
|
type: 'dialog',
|
||||||
@@ -26,7 +31,9 @@ export const agentsCommand: SlashCommand = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'create',
|
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,
|
kind: CommandKind.BUILT_IN,
|
||||||
action: (): OpenDialogActionReturn => ({
|
action: (): OpenDialogActionReturn => ({
|
||||||
type: 'dialog',
|
type: 'dialog',
|
||||||
|
|||||||
@@ -6,10 +6,13 @@
|
|||||||
|
|
||||||
import type { OpenDialogActionReturn, SlashCommand } from './types.js';
|
import type { OpenDialogActionReturn, SlashCommand } from './types.js';
|
||||||
import { CommandKind } from './types.js';
|
import { CommandKind } from './types.js';
|
||||||
|
import { t } from '../../i18n/index.js';
|
||||||
|
|
||||||
export const authCommand: SlashCommand = {
|
export const authCommand: SlashCommand = {
|
||||||
name: 'auth',
|
name: 'auth',
|
||||||
description: 'change the auth method',
|
get description() {
|
||||||
|
return t('change the auth method');
|
||||||
|
},
|
||||||
kind: CommandKind.BUILT_IN,
|
kind: CommandKind.BUILT_IN,
|
||||||
action: (_context, _args): OpenDialogActionReturn => ({
|
action: (_context, _args): OpenDialogActionReturn => ({
|
||||||
type: 'dialog',
|
type: 'dialog',
|
||||||
|
|||||||
@@ -16,10 +16,13 @@ import {
|
|||||||
getSystemInfoFields,
|
getSystemInfoFields,
|
||||||
getFieldValue,
|
getFieldValue,
|
||||||
} from '../../utils/systemInfoFields.js';
|
} from '../../utils/systemInfoFields.js';
|
||||||
|
import { t } from '../../i18n/index.js';
|
||||||
|
|
||||||
export const bugCommand: SlashCommand = {
|
export const bugCommand: SlashCommand = {
|
||||||
name: 'bug',
|
name: 'bug',
|
||||||
description: 'submit a bug report',
|
get description() {
|
||||||
|
return t('submit a bug report');
|
||||||
|
},
|
||||||
kind: CommandKind.BUILT_IN,
|
kind: CommandKind.BUILT_IN,
|
||||||
action: async (context: CommandContext, args?: string): Promise<void> => {
|
action: async (context: CommandContext, args?: string): Promise<void> => {
|
||||||
const bugDescription = (args || '').trim();
|
const bugDescription = (args || '').trim();
|
||||||
|
|||||||
@@ -7,21 +7,24 @@
|
|||||||
import { uiTelemetryService } from '@qwen-code/qwen-code-core';
|
import { uiTelemetryService } from '@qwen-code/qwen-code-core';
|
||||||
import type { SlashCommand } from './types.js';
|
import type { SlashCommand } from './types.js';
|
||||||
import { CommandKind } from './types.js';
|
import { CommandKind } from './types.js';
|
||||||
|
import { t } from '../../i18n/index.js';
|
||||||
|
|
||||||
export const clearCommand: SlashCommand = {
|
export const clearCommand: SlashCommand = {
|
||||||
name: 'clear',
|
name: 'clear',
|
||||||
description: 'clear the screen and conversation history',
|
get description() {
|
||||||
|
return t('clear the screen and conversation history');
|
||||||
|
},
|
||||||
kind: CommandKind.BUILT_IN,
|
kind: CommandKind.BUILT_IN,
|
||||||
action: async (context, _args) => {
|
action: async (context, _args) => {
|
||||||
const geminiClient = context.services.config?.getGeminiClient();
|
const geminiClient = context.services.config?.getGeminiClient();
|
||||||
|
|
||||||
if (geminiClient) {
|
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,
|
// If resetChat fails, the exception will propagate and halt the command,
|
||||||
// which is the correct behavior to signal a failure to the user.
|
// which is the correct behavior to signal a failure to the user.
|
||||||
await geminiClient.resetChat();
|
await geminiClient.resetChat();
|
||||||
} else {
|
} else {
|
||||||
context.ui.setDebugMessage('Clearing terminal.');
|
context.ui.setDebugMessage(t('Clearing terminal.'));
|
||||||
}
|
}
|
||||||
|
|
||||||
uiTelemetryService.setLastPromptTokenCount(0);
|
uiTelemetryService.setLastPromptTokenCount(0);
|
||||||
|
|||||||
@@ -8,11 +8,14 @@ import type { HistoryItemCompression } from '../types.js';
|
|||||||
import { MessageType } from '../types.js';
|
import { MessageType } from '../types.js';
|
||||||
import type { SlashCommand } from './types.js';
|
import type { SlashCommand } from './types.js';
|
||||||
import { CommandKind } from './types.js';
|
import { CommandKind } from './types.js';
|
||||||
|
import { t } from '../../i18n/index.js';
|
||||||
|
|
||||||
export const compressCommand: SlashCommand = {
|
export const compressCommand: SlashCommand = {
|
||||||
name: 'compress',
|
name: 'compress',
|
||||||
altNames: ['summarize'],
|
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,
|
kind: CommandKind.BUILT_IN,
|
||||||
action: async (context) => {
|
action: async (context) => {
|
||||||
const { ui } = context;
|
const { ui } = context;
|
||||||
@@ -20,7 +23,7 @@ export const compressCommand: SlashCommand = {
|
|||||||
ui.addItem(
|
ui.addItem(
|
||||||
{
|
{
|
||||||
type: MessageType.ERROR,
|
type: MessageType.ERROR,
|
||||||
text: 'Already compressing, wait for previous request to complete',
|
text: t('Already compressing, wait for previous request to complete'),
|
||||||
},
|
},
|
||||||
Date.now(),
|
Date.now(),
|
||||||
);
|
);
|
||||||
@@ -60,7 +63,7 @@ export const compressCommand: SlashCommand = {
|
|||||||
ui.addItem(
|
ui.addItem(
|
||||||
{
|
{
|
||||||
type: MessageType.ERROR,
|
type: MessageType.ERROR,
|
||||||
text: 'Failed to compress chat history.',
|
text: t('Failed to compress chat history.'),
|
||||||
},
|
},
|
||||||
Date.now(),
|
Date.now(),
|
||||||
);
|
);
|
||||||
@@ -69,9 +72,9 @@ export const compressCommand: SlashCommand = {
|
|||||||
ui.addItem(
|
ui.addItem(
|
||||||
{
|
{
|
||||||
type: MessageType.ERROR,
|
type: MessageType.ERROR,
|
||||||
text: `Failed to compress chat history: ${
|
text: t('Failed to compress chat history: {{error}}', {
|
||||||
e instanceof Error ? e.message : String(e)
|
error: e instanceof Error ? e.message : String(e),
|
||||||
}`,
|
}),
|
||||||
},
|
},
|
||||||
Date.now(),
|
Date.now(),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -7,10 +7,13 @@
|
|||||||
import { copyToClipboard } from '../utils/commandUtils.js';
|
import { copyToClipboard } from '../utils/commandUtils.js';
|
||||||
import type { SlashCommand, SlashCommandActionReturn } from './types.js';
|
import type { SlashCommand, SlashCommandActionReturn } from './types.js';
|
||||||
import { CommandKind } from './types.js';
|
import { CommandKind } from './types.js';
|
||||||
|
import { t } from '../../i18n/index.js';
|
||||||
|
|
||||||
export const copyCommand: SlashCommand = {
|
export const copyCommand: SlashCommand = {
|
||||||
name: 'copy',
|
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,
|
kind: CommandKind.BUILT_IN,
|
||||||
action: async (context, _args): Promise<SlashCommandActionReturn | void> => {
|
action: async (context, _args): Promise<SlashCommandActionReturn | void> => {
|
||||||
const chat = await context.services.config?.getGeminiClient()?.getChat();
|
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 os from 'node:os';
|
||||||
import * as path from 'node:path';
|
import * as path from 'node:path';
|
||||||
import { loadServerHierarchicalMemory } from '@qwen-code/qwen-code-core';
|
import { loadServerHierarchicalMemory } from '@qwen-code/qwen-code-core';
|
||||||
|
import { t } from '../../i18n/index.js';
|
||||||
|
|
||||||
export function expandHomeDir(p: string): string {
|
export function expandHomeDir(p: string): string {
|
||||||
if (!p) {
|
if (!p) {
|
||||||
@@ -27,13 +28,18 @@ export function expandHomeDir(p: string): string {
|
|||||||
export const directoryCommand: SlashCommand = {
|
export const directoryCommand: SlashCommand = {
|
||||||
name: 'directory',
|
name: 'directory',
|
||||||
altNames: ['dir'],
|
altNames: ['dir'],
|
||||||
description: 'Manage workspace directories',
|
get description() {
|
||||||
|
return t('Manage workspace directories');
|
||||||
|
},
|
||||||
kind: CommandKind.BUILT_IN,
|
kind: CommandKind.BUILT_IN,
|
||||||
subCommands: [
|
subCommands: [
|
||||||
{
|
{
|
||||||
name: 'add',
|
name: 'add',
|
||||||
description:
|
get description() {
|
||||||
'Add directories to the workspace. Use comma to separate multiple paths',
|
return t(
|
||||||
|
'Add directories to the workspace. Use comma to separate multiple paths',
|
||||||
|
);
|
||||||
|
},
|
||||||
kind: CommandKind.BUILT_IN,
|
kind: CommandKind.BUILT_IN,
|
||||||
action: async (context: CommandContext, args: string) => {
|
action: async (context: CommandContext, args: string) => {
|
||||||
const {
|
const {
|
||||||
@@ -150,7 +156,9 @@ export const directoryCommand: SlashCommand = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'show',
|
name: 'show',
|
||||||
description: 'Show all directories in the workspace',
|
get description() {
|
||||||
|
return t('Show all directories in the workspace');
|
||||||
|
},
|
||||||
kind: CommandKind.BUILT_IN,
|
kind: CommandKind.BUILT_IN,
|
||||||
action: async (context: CommandContext) => {
|
action: async (context: CommandContext) => {
|
||||||
const {
|
const {
|
||||||
|
|||||||
@@ -12,19 +12,28 @@ import {
|
|||||||
CommandKind,
|
CommandKind,
|
||||||
} from './types.js';
|
} from './types.js';
|
||||||
import { MessageType } from '../types.js';
|
import { MessageType } from '../types.js';
|
||||||
|
import { t, getCurrentLanguage } from '../../i18n/index.js';
|
||||||
|
|
||||||
export const docsCommand: SlashCommand = {
|
export const docsCommand: SlashCommand = {
|
||||||
name: 'docs',
|
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,
|
kind: CommandKind.BUILT_IN,
|
||||||
action: async (context: CommandContext): Promise<void> => {
|
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') {
|
if (process.env['SANDBOX'] && process.env['SANDBOX'] !== 'sandbox-exec') {
|
||||||
context.ui.addItem(
|
context.ui.addItem(
|
||||||
{
|
{
|
||||||
type: MessageType.INFO,
|
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(),
|
Date.now(),
|
||||||
);
|
);
|
||||||
@@ -32,7 +41,9 @@ export const docsCommand: SlashCommand = {
|
|||||||
context.ui.addItem(
|
context.ui.addItem(
|
||||||
{
|
{
|
||||||
type: MessageType.INFO,
|
type: MessageType.INFO,
|
||||||
text: `Opening documentation in your browser: ${docsUrl}`,
|
text: t('Opening documentation in your browser: {{url}}', {
|
||||||
|
url: docsUrl,
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
Date.now(),
|
Date.now(),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -9,10 +9,13 @@ import {
|
|||||||
type OpenDialogActionReturn,
|
type OpenDialogActionReturn,
|
||||||
type SlashCommand,
|
type SlashCommand,
|
||||||
} from './types.js';
|
} from './types.js';
|
||||||
|
import { t } from '../../i18n/index.js';
|
||||||
|
|
||||||
export const editorCommand: SlashCommand = {
|
export const editorCommand: SlashCommand = {
|
||||||
name: 'editor',
|
name: 'editor',
|
||||||
description: 'set external editor preference',
|
get description() {
|
||||||
|
return t('set external editor preference');
|
||||||
|
},
|
||||||
kind: CommandKind.BUILT_IN,
|
kind: CommandKind.BUILT_IN,
|
||||||
action: (): OpenDialogActionReturn => ({
|
action: (): OpenDialogActionReturn => ({
|
||||||
type: 'dialog',
|
type: 'dialog',
|
||||||
|
|||||||
@@ -7,12 +7,15 @@
|
|||||||
import type { SlashCommand } from './types.js';
|
import type { SlashCommand } from './types.js';
|
||||||
import { CommandKind } from './types.js';
|
import { CommandKind } from './types.js';
|
||||||
import { MessageType, type HistoryItemHelp } from '../types.js';
|
import { MessageType, type HistoryItemHelp } from '../types.js';
|
||||||
|
import { t } from '../../i18n/index.js';
|
||||||
|
|
||||||
export const helpCommand: SlashCommand = {
|
export const helpCommand: SlashCommand = {
|
||||||
name: 'help',
|
name: 'help',
|
||||||
altNames: ['?'],
|
altNames: ['?'],
|
||||||
kind: CommandKind.BUILT_IN,
|
kind: CommandKind.BUILT_IN,
|
||||||
description: 'for help on Qwen Code',
|
get description() {
|
||||||
|
return t('for help on Qwen Code');
|
||||||
|
},
|
||||||
action: async (context) => {
|
action: async (context) => {
|
||||||
const helpItem: Omit<HistoryItemHelp, 'id'> = {
|
const helpItem: Omit<HistoryItemHelp, 'id'> = {
|
||||||
type: MessageType.HELP,
|
type: MessageType.HELP,
|
||||||
|
|||||||
@@ -15,10 +15,13 @@ import { getCurrentGeminiMdFilename } from '@qwen-code/qwen-code-core';
|
|||||||
import { CommandKind } from './types.js';
|
import { CommandKind } from './types.js';
|
||||||
import { Text } from 'ink';
|
import { Text } from 'ink';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { t } from '../../i18n/index.js';
|
||||||
|
|
||||||
export const initCommand: SlashCommand = {
|
export const initCommand: SlashCommand = {
|
||||||
name: 'init',
|
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,
|
kind: CommandKind.BUILT_IN,
|
||||||
action: async (
|
action: async (
|
||||||
context: CommandContext,
|
context: CommandContext,
|
||||||
@@ -28,7 +31,7 @@ export const initCommand: SlashCommand = {
|
|||||||
return {
|
return {
|
||||||
type: 'message',
|
type: 'message',
|
||||||
messageType: 'error',
|
messageType: 'error',
|
||||||
content: 'Configuration not available.',
|
content: t('Configuration not available.'),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const targetDir = context.services.config.getTargetDir();
|
const targetDir = context.services.config.getTargetDir();
|
||||||
|
|||||||
@@ -115,10 +115,18 @@ async function setUiLanguage(
|
|||||||
// Reload commands to update their descriptions with the new language
|
// Reload commands to update their descriptions with the new language
|
||||||
context.ui.reloadCommands();
|
context.ui.reloadCommands();
|
||||||
|
|
||||||
|
// Map language codes to friendly display names
|
||||||
|
const langDisplayNames: Record<SupportedLanguage, string> = {
|
||||||
|
zh: '中文(zh-CN)',
|
||||||
|
en: 'English(en-US)',
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: 'message',
|
type: 'message',
|
||||||
messageType: 'info',
|
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';
|
} from '@qwen-code/qwen-code-core';
|
||||||
import { appEvents, AppEvent } from '../../utils/events.js';
|
import { appEvents, AppEvent } from '../../utils/events.js';
|
||||||
import { MessageType, type HistoryItemMcpStatus } from '../types.js';
|
import { MessageType, type HistoryItemMcpStatus } from '../types.js';
|
||||||
|
import { t } from '../../i18n/index.js';
|
||||||
|
|
||||||
const authCommand: SlashCommand = {
|
const authCommand: SlashCommand = {
|
||||||
name: 'auth',
|
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,
|
kind: CommandKind.BUILT_IN,
|
||||||
action: async (
|
action: async (
|
||||||
context: CommandContext,
|
context: CommandContext,
|
||||||
@@ -40,7 +43,7 @@ const authCommand: SlashCommand = {
|
|||||||
return {
|
return {
|
||||||
type: 'message',
|
type: 'message',
|
||||||
messageType: 'error',
|
messageType: 'error',
|
||||||
content: 'Config not loaded.',
|
content: t('Config not loaded.'),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,14 +59,14 @@ const authCommand: SlashCommand = {
|
|||||||
return {
|
return {
|
||||||
type: 'message',
|
type: 'message',
|
||||||
messageType: 'info',
|
messageType: 'info',
|
||||||
content: 'No MCP servers configured with OAuth authentication.',
|
content: t('No MCP servers configured with OAuth authentication.'),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: 'message',
|
type: 'message',
|
||||||
messageType: 'info',
|
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 {
|
return {
|
||||||
type: 'message',
|
type: 'message',
|
||||||
messageType: 'error',
|
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(
|
context.ui.addItem(
|
||||||
{
|
{
|
||||||
type: 'info',
|
type: 'info',
|
||||||
text: `✅ Successfully authenticated with MCP server '${serverName}'!`,
|
text: t(
|
||||||
|
"Successfully authenticated and refreshed tools for '{{name}}'.",
|
||||||
|
{
|
||||||
|
name: serverName,
|
||||||
|
},
|
||||||
|
),
|
||||||
},
|
},
|
||||||
Date.now(),
|
Date.now(),
|
||||||
);
|
);
|
||||||
@@ -122,7 +130,9 @@ const authCommand: SlashCommand = {
|
|||||||
context.ui.addItem(
|
context.ui.addItem(
|
||||||
{
|
{
|
||||||
type: 'info',
|
type: 'info',
|
||||||
text: `Re-discovering tools from '${serverName}'...`,
|
text: t("Re-discovering tools from '{{name}}'...", {
|
||||||
|
name: serverName,
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
Date.now(),
|
Date.now(),
|
||||||
);
|
);
|
||||||
@@ -140,13 +150,24 @@ const authCommand: SlashCommand = {
|
|||||||
return {
|
return {
|
||||||
type: 'message',
|
type: 'message',
|
||||||
messageType: 'info',
|
messageType: 'info',
|
||||||
content: `Successfully authenticated and refreshed tools for '${serverName}'.`,
|
content: t(
|
||||||
|
"Successfully authenticated and refreshed tools for '{{name}}'.",
|
||||||
|
{
|
||||||
|
name: serverName,
|
||||||
|
},
|
||||||
|
),
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return {
|
return {
|
||||||
type: 'message',
|
type: 'message',
|
||||||
messageType: 'error',
|
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 {
|
} finally {
|
||||||
appEvents.removeListener(AppEvent.OauthDisplayMessage, displayListener);
|
appEvents.removeListener(AppEvent.OauthDisplayMessage, displayListener);
|
||||||
@@ -165,7 +186,9 @@ const authCommand: SlashCommand = {
|
|||||||
|
|
||||||
const listCommand: SlashCommand = {
|
const listCommand: SlashCommand = {
|
||||||
name: 'list',
|
name: 'list',
|
||||||
description: 'List configured MCP servers and tools',
|
get description() {
|
||||||
|
return t('List configured MCP servers and tools');
|
||||||
|
},
|
||||||
kind: CommandKind.BUILT_IN,
|
kind: CommandKind.BUILT_IN,
|
||||||
action: async (
|
action: async (
|
||||||
context: CommandContext,
|
context: CommandContext,
|
||||||
@@ -176,7 +199,7 @@ const listCommand: SlashCommand = {
|
|||||||
return {
|
return {
|
||||||
type: 'message',
|
type: 'message',
|
||||||
messageType: 'error',
|
messageType: 'error',
|
||||||
content: 'Config not loaded.',
|
content: t('Config not loaded.'),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -276,7 +299,9 @@ const listCommand: SlashCommand = {
|
|||||||
|
|
||||||
const refreshCommand: SlashCommand = {
|
const refreshCommand: SlashCommand = {
|
||||||
name: 'refresh',
|
name: 'refresh',
|
||||||
description: 'Restarts MCP servers.',
|
get description() {
|
||||||
|
return t('Restarts MCP servers.');
|
||||||
|
},
|
||||||
kind: CommandKind.BUILT_IN,
|
kind: CommandKind.BUILT_IN,
|
||||||
action: async (
|
action: async (
|
||||||
context: CommandContext,
|
context: CommandContext,
|
||||||
@@ -286,7 +311,7 @@ const refreshCommand: SlashCommand = {
|
|||||||
return {
|
return {
|
||||||
type: 'message',
|
type: 'message',
|
||||||
messageType: 'error',
|
messageType: 'error',
|
||||||
content: 'Config not loaded.',
|
content: t('Config not loaded.'),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -324,8 +349,11 @@ const refreshCommand: SlashCommand = {
|
|||||||
|
|
||||||
export const mcpCommand: SlashCommand = {
|
export const mcpCommand: SlashCommand = {
|
||||||
name: 'mcp',
|
name: 'mcp',
|
||||||
description:
|
get description() {
|
||||||
'list configured MCP servers and tools, or authenticate with OAuth-enabled servers',
|
return t(
|
||||||
|
'list configured MCP servers and tools, or authenticate with OAuth-enabled servers',
|
||||||
|
);
|
||||||
|
},
|
||||||
kind: CommandKind.BUILT_IN,
|
kind: CommandKind.BUILT_IN,
|
||||||
subCommands: [listCommand, authCommand, refreshCommand],
|
subCommands: [listCommand, authCommand, refreshCommand],
|
||||||
// Default action when no subcommand is provided
|
// Default action when no subcommand is provided
|
||||||
|
|||||||
@@ -6,10 +6,13 @@
|
|||||||
|
|
||||||
import type { OpenDialogActionReturn, SlashCommand } from './types.js';
|
import type { OpenDialogActionReturn, SlashCommand } from './types.js';
|
||||||
import { CommandKind } from './types.js';
|
import { CommandKind } from './types.js';
|
||||||
|
import { t } from '../../i18n/index.js';
|
||||||
|
|
||||||
export const permissionsCommand: SlashCommand = {
|
export const permissionsCommand: SlashCommand = {
|
||||||
name: 'permissions',
|
name: 'permissions',
|
||||||
description: 'Manage folder trust settings',
|
get description() {
|
||||||
|
return t('Manage folder trust settings');
|
||||||
|
},
|
||||||
kind: CommandKind.BUILT_IN,
|
kind: CommandKind.BUILT_IN,
|
||||||
action: (): OpenDialogActionReturn => ({
|
action: (): OpenDialogActionReturn => ({
|
||||||
type: 'dialog',
|
type: 'dialog',
|
||||||
|
|||||||
@@ -6,10 +6,13 @@
|
|||||||
|
|
||||||
import { formatDuration } from '../utils/formatters.js';
|
import { formatDuration } from '../utils/formatters.js';
|
||||||
import { CommandKind, type SlashCommand } from './types.js';
|
import { CommandKind, type SlashCommand } from './types.js';
|
||||||
|
import { t } from '../../i18n/index.js';
|
||||||
|
|
||||||
export const quitConfirmCommand: SlashCommand = {
|
export const quitConfirmCommand: SlashCommand = {
|
||||||
name: 'quit-confirm',
|
name: 'quit-confirm',
|
||||||
description: 'Show quit confirmation dialog',
|
get description() {
|
||||||
|
return t('Show quit confirmation dialog');
|
||||||
|
},
|
||||||
kind: CommandKind.BUILT_IN,
|
kind: CommandKind.BUILT_IN,
|
||||||
action: (context) => {
|
action: (context) => {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
@@ -37,7 +40,9 @@ export const quitConfirmCommand: SlashCommand = {
|
|||||||
export const quitCommand: SlashCommand = {
|
export const quitCommand: SlashCommand = {
|
||||||
name: 'quit',
|
name: 'quit',
|
||||||
altNames: ['exit'],
|
altNames: ['exit'],
|
||||||
description: 'exit the cli',
|
get description() {
|
||||||
|
return t('exit the cli');
|
||||||
|
},
|
||||||
kind: CommandKind.BUILT_IN,
|
kind: CommandKind.BUILT_IN,
|
||||||
action: (context) => {
|
action: (context) => {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
|
|||||||
@@ -6,10 +6,13 @@
|
|||||||
|
|
||||||
import type { OpenDialogActionReturn, SlashCommand } from './types.js';
|
import type { OpenDialogActionReturn, SlashCommand } from './types.js';
|
||||||
import { CommandKind } from './types.js';
|
import { CommandKind } from './types.js';
|
||||||
|
import { t } from '../../i18n/index.js';
|
||||||
|
|
||||||
export const settingsCommand: SlashCommand = {
|
export const settingsCommand: SlashCommand = {
|
||||||
name: 'settings',
|
name: 'settings',
|
||||||
description: 'View and edit Qwen Code settings',
|
get description() {
|
||||||
|
return t('View and edit Qwen Code settings');
|
||||||
|
},
|
||||||
kind: CommandKind.BUILT_IN,
|
kind: CommandKind.BUILT_IN,
|
||||||
action: (_context, _args): OpenDialogActionReturn => ({
|
action: (_context, _args): OpenDialogActionReturn => ({
|
||||||
type: 'dialog',
|
type: 'dialog',
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import {
|
|||||||
import type { SlashCommand, SlashCommandActionReturn } from './types.js';
|
import type { SlashCommand, SlashCommandActionReturn } from './types.js';
|
||||||
import { CommandKind } from './types.js';
|
import { CommandKind } from './types.js';
|
||||||
import { getUrlOpenCommand } from '../../ui/utils/commandUtils.js';
|
import { getUrlOpenCommand } from '../../ui/utils/commandUtils.js';
|
||||||
|
import { t } from '../../i18n/index.js';
|
||||||
|
|
||||||
export const GITHUB_WORKFLOW_PATHS = [
|
export const GITHUB_WORKFLOW_PATHS = [
|
||||||
'gemini-dispatch/gemini-dispatch.yml',
|
'gemini-dispatch/gemini-dispatch.yml',
|
||||||
@@ -91,7 +92,9 @@ export async function updateGitignore(gitRepoRoot: string): Promise<void> {
|
|||||||
|
|
||||||
export const setupGithubCommand: SlashCommand = {
|
export const setupGithubCommand: SlashCommand = {
|
||||||
name: 'setup-github',
|
name: 'setup-github',
|
||||||
description: 'Set up GitHub Actions',
|
get description() {
|
||||||
|
return t('Set up GitHub Actions');
|
||||||
|
},
|
||||||
kind: CommandKind.BUILT_IN,
|
kind: CommandKind.BUILT_IN,
|
||||||
action: async (
|
action: async (
|
||||||
context: CommandContext,
|
context: CommandContext,
|
||||||
|
|||||||
@@ -12,11 +12,14 @@ import {
|
|||||||
type SlashCommand,
|
type SlashCommand,
|
||||||
CommandKind,
|
CommandKind,
|
||||||
} from './types.js';
|
} from './types.js';
|
||||||
|
import { t } from '../../i18n/index.js';
|
||||||
|
|
||||||
export const statsCommand: SlashCommand = {
|
export const statsCommand: SlashCommand = {
|
||||||
name: 'stats',
|
name: 'stats',
|
||||||
altNames: ['usage'],
|
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,
|
kind: CommandKind.BUILT_IN,
|
||||||
action: (context: CommandContext) => {
|
action: (context: CommandContext) => {
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
@@ -43,7 +46,9 @@ export const statsCommand: SlashCommand = {
|
|||||||
subCommands: [
|
subCommands: [
|
||||||
{
|
{
|
||||||
name: 'model',
|
name: 'model',
|
||||||
description: 'Show model-specific usage statistics.',
|
get description() {
|
||||||
|
return t('Show model-specific usage statistics.');
|
||||||
|
},
|
||||||
kind: CommandKind.BUILT_IN,
|
kind: CommandKind.BUILT_IN,
|
||||||
action: (context: CommandContext) => {
|
action: (context: CommandContext) => {
|
||||||
context.ui.addItem(
|
context.ui.addItem(
|
||||||
@@ -56,7 +61,9 @@ export const statsCommand: SlashCommand = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'tools',
|
name: 'tools',
|
||||||
description: 'Show tool-specific usage statistics.',
|
get description() {
|
||||||
|
return t('Show tool-specific usage statistics.');
|
||||||
|
},
|
||||||
kind: CommandKind.BUILT_IN,
|
kind: CommandKind.BUILT_IN,
|
||||||
action: (context: CommandContext) => {
|
action: (context: CommandContext) => {
|
||||||
context.ui.addItem(
|
context.ui.addItem(
|
||||||
|
|||||||
@@ -13,11 +13,15 @@ import {
|
|||||||
} from './types.js';
|
} from './types.js';
|
||||||
import { getProjectSummaryPrompt } from '@qwen-code/qwen-code-core';
|
import { getProjectSummaryPrompt } from '@qwen-code/qwen-code-core';
|
||||||
import type { HistoryItemSummary } from '../types.js';
|
import type { HistoryItemSummary } from '../types.js';
|
||||||
|
import { t } from '../../i18n/index.js';
|
||||||
|
|
||||||
export const summaryCommand: SlashCommand = {
|
export const summaryCommand: SlashCommand = {
|
||||||
name: 'summary',
|
name: 'summary',
|
||||||
description:
|
get description() {
|
||||||
'Generate a project summary and save it to .qwen/PROJECT_SUMMARY.md',
|
return t(
|
||||||
|
'Generate a project summary and save it to .qwen/PROJECT_SUMMARY.md',
|
||||||
|
);
|
||||||
|
},
|
||||||
kind: CommandKind.BUILT_IN,
|
kind: CommandKind.BUILT_IN,
|
||||||
action: async (context): Promise<SlashCommandActionReturn> => {
|
action: async (context): Promise<SlashCommandActionReturn> => {
|
||||||
const { config } = context.services;
|
const { config } = context.services;
|
||||||
@@ -26,7 +30,7 @@ export const summaryCommand: SlashCommand = {
|
|||||||
return {
|
return {
|
||||||
type: 'message',
|
type: 'message',
|
||||||
messageType: 'error',
|
messageType: 'error',
|
||||||
content: 'Config not loaded.',
|
content: t('Config not loaded.'),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,7 +39,7 @@ export const summaryCommand: SlashCommand = {
|
|||||||
return {
|
return {
|
||||||
type: 'message',
|
type: 'message',
|
||||||
messageType: 'error',
|
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(
|
ui.addItem(
|
||||||
{
|
{
|
||||||
type: 'error' as const,
|
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(),
|
Date.now(),
|
||||||
);
|
);
|
||||||
return {
|
return {
|
||||||
type: 'message',
|
type: 'message',
|
||||||
messageType: 'error',
|
messageType: 'error',
|
||||||
content:
|
content: t(
|
||||||
'Already generating summary, wait for previous request to complete',
|
'Already generating summary, wait for previous request to complete',
|
||||||
|
),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,7 +72,7 @@ export const summaryCommand: SlashCommand = {
|
|||||||
return {
|
return {
|
||||||
type: 'message',
|
type: 'message',
|
||||||
messageType: 'info',
|
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(
|
ui.addItem(
|
||||||
{
|
{
|
||||||
type: 'error' as const,
|
type: 'error' as const,
|
||||||
text: `❌ Failed to generate project context summary: ${
|
text: `❌ ${t(
|
||||||
error instanceof Error ? error.message : String(error)
|
'Failed to generate project context summary: {{error}}',
|
||||||
}`,
|
{
|
||||||
|
error: error instanceof Error ? error.message : String(error),
|
||||||
|
},
|
||||||
|
)}`,
|
||||||
},
|
},
|
||||||
Date.now(),
|
Date.now(),
|
||||||
);
|
);
|
||||||
@@ -181,9 +191,9 @@ export const summaryCommand: SlashCommand = {
|
|||||||
return {
|
return {
|
||||||
type: 'message',
|
type: 'message',
|
||||||
messageType: 'error',
|
messageType: 'error',
|
||||||
content: `Failed to generate project context summary: ${
|
content: t('Failed to generate project context summary: {{error}}', {
|
||||||
error instanceof Error ? error.message : String(error)
|
error: error instanceof Error ? error.message : String(error),
|
||||||
}`,
|
}),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -6,10 +6,13 @@
|
|||||||
|
|
||||||
import type { OpenDialogActionReturn, SlashCommand } from './types.js';
|
import type { OpenDialogActionReturn, SlashCommand } from './types.js';
|
||||||
import { CommandKind } from './types.js';
|
import { CommandKind } from './types.js';
|
||||||
|
import { t } from '../../i18n/index.js';
|
||||||
|
|
||||||
export const themeCommand: SlashCommand = {
|
export const themeCommand: SlashCommand = {
|
||||||
name: 'theme',
|
name: 'theme',
|
||||||
description: 'change the theme',
|
get description() {
|
||||||
|
return t('change the theme');
|
||||||
|
},
|
||||||
kind: CommandKind.BUILT_IN,
|
kind: CommandKind.BUILT_IN,
|
||||||
action: (_context, _args): OpenDialogActionReturn => ({
|
action: (_context, _args): OpenDialogActionReturn => ({
|
||||||
type: 'dialog',
|
type: 'dialog',
|
||||||
|
|||||||
@@ -10,10 +10,13 @@ import {
|
|||||||
CommandKind,
|
CommandKind,
|
||||||
} from './types.js';
|
} from './types.js';
|
||||||
import { MessageType, type HistoryItemToolsList } from '../types.js';
|
import { MessageType, type HistoryItemToolsList } from '../types.js';
|
||||||
|
import { t } from '../../i18n/index.js';
|
||||||
|
|
||||||
export const toolsCommand: SlashCommand = {
|
export const toolsCommand: SlashCommand = {
|
||||||
name: 'tools',
|
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,
|
kind: CommandKind.BUILT_IN,
|
||||||
action: async (context: CommandContext, args?: string): Promise<void> => {
|
action: async (context: CommandContext, args?: string): Promise<void> => {
|
||||||
const subCommand = args?.trim();
|
const subCommand = args?.trim();
|
||||||
@@ -29,7 +32,7 @@ export const toolsCommand: SlashCommand = {
|
|||||||
context.ui.addItem(
|
context.ui.addItem(
|
||||||
{
|
{
|
||||||
type: MessageType.ERROR,
|
type: MessageType.ERROR,
|
||||||
text: 'Could not retrieve tool registry.',
|
text: t('Could not retrieve tool registry.'),
|
||||||
},
|
},
|
||||||
Date.now(),
|
Date.now(),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -6,10 +6,13 @@
|
|||||||
|
|
||||||
import type { SlashCommand } from './types.js';
|
import type { SlashCommand } from './types.js';
|
||||||
import { CommandKind } from './types.js';
|
import { CommandKind } from './types.js';
|
||||||
|
import { t } from '../../i18n/index.js';
|
||||||
|
|
||||||
export const vimCommand: SlashCommand = {
|
export const vimCommand: SlashCommand = {
|
||||||
name: 'vim',
|
name: 'vim',
|
||||||
description: 'toggle vim mode on/off',
|
get description() {
|
||||||
|
return t('toggle vim mode on/off');
|
||||||
|
},
|
||||||
kind: CommandKind.BUILT_IN,
|
kind: CommandKind.BUILT_IN,
|
||||||
action: async (context, _args) => {
|
action: async (context, _args) => {
|
||||||
const newVimState = await context.ui.toggleVimEnabled();
|
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 { ApprovalMode } from '@qwen-code/qwen-code-core';
|
||||||
import { StreamingState } from '../types.js';
|
import { StreamingState } from '../types.js';
|
||||||
import { ConfigInitDisplay } from '../components/ConfigInitDisplay.js';
|
import { ConfigInitDisplay } from '../components/ConfigInitDisplay.js';
|
||||||
|
import { t } from '../../i18n/index.js';
|
||||||
|
|
||||||
export const Composer = () => {
|
export const Composer = () => {
|
||||||
const config = useConfig();
|
const config = useConfig();
|
||||||
@@ -86,14 +87,16 @@ export const Composer = () => {
|
|||||||
)}
|
)}
|
||||||
{uiState.ctrlCPressedOnce ? (
|
{uiState.ctrlCPressedOnce ? (
|
||||||
<Text color={theme.status.warning}>
|
<Text color={theme.status.warning}>
|
||||||
Press Ctrl+C again to exit.
|
{t('Press Ctrl+C again to exit.')}
|
||||||
</Text>
|
</Text>
|
||||||
) : uiState.ctrlDPressedOnce ? (
|
) : uiState.ctrlDPressedOnce ? (
|
||||||
<Text color={theme.status.warning}>
|
<Text color={theme.status.warning}>
|
||||||
Press Ctrl+D again to exit.
|
{t('Press Ctrl+D again to exit.')}
|
||||||
</Text>
|
</Text>
|
||||||
) : uiState.showEscapePrompt ? (
|
) : 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 && (
|
!settings.merged.ui?.hideContextSummary && (
|
||||||
<ContextSummaryDisplay
|
<ContextSummaryDisplay
|
||||||
@@ -151,8 +154,8 @@ export const Composer = () => {
|
|||||||
isEmbeddedShellFocused={uiState.embeddedShellFocused}
|
isEmbeddedShellFocused={uiState.embeddedShellFocused}
|
||||||
placeholder={
|
placeholder={
|
||||||
vimEnabled
|
vimEnabled
|
||||||
? " Press 'i' for INSERT mode and 'Esc' for NORMAL mode."
|
? ' ' + t("Press 'i' for INSERT mode and 'Esc' for NORMAL mode.")
|
||||||
: ' Type your message or @path/to/file'
|
: ' ' + t('Type your message or @path/to/file')
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
|
|||||||
config,
|
config,
|
||||||
slashCommands,
|
slashCommands,
|
||||||
commandContext,
|
commandContext,
|
||||||
placeholder = ' Type your message or @path/to/file',
|
placeholder,
|
||||||
focus = true,
|
focus = true,
|
||||||
inputWidth,
|
inputWidth,
|
||||||
suggestionsWidth,
|
suggestionsWidth,
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import type React from 'react';
|
|||||||
import { Box, Text } from 'ink';
|
import { Box, Text } from 'ink';
|
||||||
import { RadioButtonSelect } from './shared/RadioButtonSelect.js';
|
import { RadioButtonSelect } from './shared/RadioButtonSelect.js';
|
||||||
import { theme } from '../semantic-colors.js';
|
import { theme } from '../semantic-colors.js';
|
||||||
|
import { t } from '../../i18n/index.js';
|
||||||
|
|
||||||
interface ProQuotaDialogProps {
|
interface ProQuotaDialogProps {
|
||||||
failedModel: string;
|
failedModel: string;
|
||||||
@@ -22,12 +23,12 @@ export function ProQuotaDialog({
|
|||||||
}: ProQuotaDialogProps): React.JSX.Element {
|
}: ProQuotaDialogProps): React.JSX.Element {
|
||||||
const items = [
|
const items = [
|
||||||
{
|
{
|
||||||
label: 'Change auth (executes the /auth command)',
|
label: t('Change auth (executes the /auth command)'),
|
||||||
value: 'auth' as const,
|
value: 'auth' as const,
|
||||||
key: 'auth',
|
key: 'auth',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: `Continue with ${fallbackModel}`,
|
label: t('Continue with {{model}}', { model: fallbackModel }),
|
||||||
value: 'continue' as const,
|
value: 'continue' as const,
|
||||||
key: 'continue',
|
key: 'continue',
|
||||||
},
|
},
|
||||||
@@ -40,7 +41,7 @@ export function ProQuotaDialog({
|
|||||||
return (
|
return (
|
||||||
<Box borderStyle="round" flexDirection="column" paddingX={1}>
|
<Box borderStyle="round" flexDirection="column" paddingX={1}>
|
||||||
<Text bold color={theme.status.warning}>
|
<Text bold color={theme.status.warning}>
|
||||||
Pro quota limit reached for {failedModel}.
|
{t('Pro quota limit reached for {{model}}.', { model: failedModel })}
|
||||||
</Text>
|
</Text>
|
||||||
<Box marginTop={1}>
|
<Box marginTop={1}>
|
||||||
<RadioButtonSelect
|
<RadioButtonSelect
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
type RadioSelectItem,
|
type RadioSelectItem,
|
||||||
} from './shared/RadioButtonSelect.js';
|
} from './shared/RadioButtonSelect.js';
|
||||||
import { useKeypress } from '../hooks/useKeypress.js';
|
import { useKeypress } from '../hooks/useKeypress.js';
|
||||||
|
import { t } from '../../i18n/index.js';
|
||||||
|
|
||||||
export enum QuitChoice {
|
export enum QuitChoice {
|
||||||
CANCEL = 'cancel',
|
CANCEL = 'cancel',
|
||||||
@@ -39,22 +40,22 @@ export const QuitConfirmationDialog: React.FC<QuitConfirmationDialogProps> = ({
|
|||||||
const options: Array<RadioSelectItem<QuitChoice>> = [
|
const options: Array<RadioSelectItem<QuitChoice>> = [
|
||||||
{
|
{
|
||||||
key: 'quit',
|
key: 'quit',
|
||||||
label: 'Quit immediately (/quit)',
|
label: t('Quit immediately (/quit)'),
|
||||||
value: QuitChoice.QUIT,
|
value: QuitChoice.QUIT,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'summary-and-quit',
|
key: 'summary-and-quit',
|
||||||
label: 'Generate summary and quit (/summary)',
|
label: t('Generate summary and quit (/summary)'),
|
||||||
value: QuitChoice.SUMMARY_AND_QUIT,
|
value: QuitChoice.SUMMARY_AND_QUIT,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'save-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,
|
value: QuitChoice.SAVE_AND_QUIT,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'cancel',
|
key: 'cancel',
|
||||||
label: 'Cancel (stay in application)',
|
label: t('Cancel (stay in application)'),
|
||||||
value: QuitChoice.CANCEL,
|
value: QuitChoice.CANCEL,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@@ -69,7 +70,7 @@ export const QuitConfirmationDialog: React.FC<QuitConfirmationDialogProps> = ({
|
|||||||
marginLeft={1}
|
marginLeft={1}
|
||||||
>
|
>
|
||||||
<Box flexDirection="column" marginBottom={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>
|
</Box>
|
||||||
|
|
||||||
<RadioButtonSelect items={options} onSelect={onSelect} isFocused />
|
<RadioButtonSelect items={options} onSelect={onSelect} isFocused />
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import qrcode from 'qrcode-terminal';
|
|||||||
import { Colors } from '../colors.js';
|
import { Colors } from '../colors.js';
|
||||||
import type { DeviceAuthorizationInfo } from '../hooks/useQwenAuth.js';
|
import type { DeviceAuthorizationInfo } from '../hooks/useQwenAuth.js';
|
||||||
import { useKeypress } from '../hooks/useKeypress.js';
|
import { useKeypress } from '../hooks/useKeypress.js';
|
||||||
|
import { t } from '../../i18n/index.js';
|
||||||
|
|
||||||
interface QwenOAuthProgressProps {
|
interface QwenOAuthProgressProps {
|
||||||
onTimeout: () => void;
|
onTimeout: () => void;
|
||||||
@@ -52,11 +53,11 @@ function QrCodeDisplay({
|
|||||||
width="100%"
|
width="100%"
|
||||||
>
|
>
|
||||||
<Text bold color={Colors.AccentBlue}>
|
<Text bold color={Colors.AccentBlue}>
|
||||||
Qwen OAuth Authentication
|
{t('Qwen OAuth Authentication')}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<Box marginTop={1}>
|
<Box marginTop={1}>
|
||||||
<Text>Please visit this URL to authorize:</Text>
|
<Text>{t('Please visit this URL to authorize:')}</Text>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Link url={verificationUrl} fallback={false}>
|
<Link url={verificationUrl} fallback={false}>
|
||||||
@@ -66,7 +67,7 @@ function QrCodeDisplay({
|
|||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
<Box marginTop={1}>
|
<Box marginTop={1}>
|
||||||
<Text>Or scan the QR code below:</Text>
|
<Text>{t('Or scan the QR code below:')}</Text>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box marginTop={1}>
|
<Box marginTop={1}>
|
||||||
@@ -103,15 +104,18 @@ function StatusDisplay({
|
|||||||
>
|
>
|
||||||
<Box marginTop={1}>
|
<Box marginTop={1}>
|
||||||
<Text>
|
<Text>
|
||||||
<Spinner type="dots" /> Waiting for authorization{dots}
|
<Spinner type="dots" /> {t('Waiting for authorization')}
|
||||||
|
{dots}
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box marginTop={1} justifyContent="space-between">
|
<Box marginTop={1} justifyContent="space-between">
|
||||||
<Text color={Colors.Gray}>
|
<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>
|
||||||
<Text color={Colors.AccentPurple}>(Press ESC or CTRL+C to cancel)</Text>
|
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
@@ -215,19 +219,24 @@ export function QwenOAuthProgress({
|
|||||||
width="100%"
|
width="100%"
|
||||||
>
|
>
|
||||||
<Text bold color={Colors.AccentRed}>
|
<Text bold color={Colors.AccentRed}>
|
||||||
Qwen OAuth Authentication Timeout
|
{t('Qwen OAuth Authentication Timeout')}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<Box marginTop={1}>
|
<Box marginTop={1}>
|
||||||
<Text>
|
<Text>
|
||||||
{authMessage ||
|
{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>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box marginTop={1}>
|
<Box marginTop={1}>
|
||||||
<Text color={Colors.Gray}>
|
<Text color={Colors.Gray}>
|
||||||
Press any key to return to authentication type selection.
|
{t('Press any key to return to authentication type selection.')}
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
@@ -246,16 +255,17 @@ export function QwenOAuthProgress({
|
|||||||
>
|
>
|
||||||
<Box>
|
<Box>
|
||||||
<Text>
|
<Text>
|
||||||
<Spinner type="dots" /> Waiting for Qwen OAuth authentication...
|
<Spinner type="dots" />
|
||||||
|
{t('Waiting for Qwen OAuth authentication...')}
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
<Box marginTop={1} justifyContent="space-between">
|
<Box marginTop={1} justifyContent="space-between">
|
||||||
<Text color={Colors.Gray}>
|
<Text color={Colors.Gray}>
|
||||||
Time remaining: {Math.floor(timeRemaining / 60)}:
|
{t('Time remaining:')} {Math.floor(timeRemaining / 60)}:
|
||||||
{(timeRemaining % 60).toString().padStart(2, '0')}
|
{(timeRemaining % 60).toString().padStart(2, '0')}
|
||||||
</Text>
|
</Text>
|
||||||
<Text color={Colors.AccentPurple}>
|
<Text color={Colors.AccentPurple}>
|
||||||
(Press ESC or CTRL+C to cancel)
|
{t('(Press ESC or CTRL+C to cancel)')}
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
import type React from 'react';
|
import type React from 'react';
|
||||||
import { StatsDisplay } from './StatsDisplay.js';
|
import { StatsDisplay } from './StatsDisplay.js';
|
||||||
|
import { t } from '../../i18n/index.js';
|
||||||
|
|
||||||
interface SessionSummaryDisplayProps {
|
interface SessionSummaryDisplayProps {
|
||||||
duration: string;
|
duration: string;
|
||||||
@@ -14,5 +15,8 @@ interface SessionSummaryDisplayProps {
|
|||||||
export const SessionSummaryDisplay: React.FC<SessionSummaryDisplayProps> = ({
|
export const SessionSummaryDisplay: React.FC<SessionSummaryDisplayProps> = ({
|
||||||
duration,
|
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 type { RadioSelectItem } from './shared/RadioButtonSelect.js';
|
||||||
import { RadioButtonSelect } from './shared/RadioButtonSelect.js';
|
import { RadioButtonSelect } from './shared/RadioButtonSelect.js';
|
||||||
import { useKeypress } from '../hooks/useKeypress.js';
|
import { useKeypress } from '../hooks/useKeypress.js';
|
||||||
|
import { t } from '../../i18n/index.js';
|
||||||
|
|
||||||
export interface ShellConfirmationRequest {
|
export interface ShellConfirmationRequest {
|
||||||
commands: string[];
|
commands: string[];
|
||||||
@@ -51,17 +52,17 @@ export const ShellConfirmationDialog: React.FC<
|
|||||||
|
|
||||||
const options: Array<RadioSelectItem<ToolConfirmationOutcome>> = [
|
const options: Array<RadioSelectItem<ToolConfirmationOutcome>> = [
|
||||||
{
|
{
|
||||||
label: 'Yes, allow once',
|
label: t('Yes, allow once'),
|
||||||
value: ToolConfirmationOutcome.ProceedOnce,
|
value: ToolConfirmationOutcome.ProceedOnce,
|
||||||
key: 'Yes, allow once',
|
key: 'Yes, allow once',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Yes, allow always for this session',
|
label: t('Yes, allow always for this session'),
|
||||||
value: ToolConfirmationOutcome.ProceedAlways,
|
value: ToolConfirmationOutcome.ProceedAlways,
|
||||||
key: 'Yes, allow always for this session',
|
key: 'Yes, allow always for this session',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'No (esc)',
|
label: t('No (esc)'),
|
||||||
value: ToolConfirmationOutcome.Cancel,
|
value: ToolConfirmationOutcome.Cancel,
|
||||||
key: 'No (esc)',
|
key: 'No (esc)',
|
||||||
},
|
},
|
||||||
@@ -78,10 +79,10 @@ export const ShellConfirmationDialog: React.FC<
|
|||||||
>
|
>
|
||||||
<Box flexDirection="column" marginBottom={1}>
|
<Box flexDirection="column" marginBottom={1}>
|
||||||
<Text bold color={theme.text.primary}>
|
<Text bold color={theme.text.primary}>
|
||||||
Shell Command Execution
|
{t('Shell Command Execution')}
|
||||||
</Text>
|
</Text>
|
||||||
<Text color={theme.text.primary}>
|
<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>
|
</Text>
|
||||||
<Box
|
<Box
|
||||||
flexDirection="column"
|
flexDirection="column"
|
||||||
@@ -99,7 +100,7 @@ export const ShellConfirmationDialog: React.FC<
|
|||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box marginBottom={1}>
|
<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>
|
</Box>
|
||||||
|
|
||||||
<RadioButtonSelect items={options} onSelect={handleSelect} isFocused />
|
<RadioButtonSelect items={options} onSelect={handleSelect} isFocused />
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import {
|
|||||||
USER_AGREEMENT_RATE_MEDIUM,
|
USER_AGREEMENT_RATE_MEDIUM,
|
||||||
} from '../utils/displayUtils.js';
|
} from '../utils/displayUtils.js';
|
||||||
import { computeSessionStats } from '../utils/computeStats.js';
|
import { computeSessionStats } from '../utils/computeStats.js';
|
||||||
|
import { t } from '../../i18n/index.js';
|
||||||
|
|
||||||
// A more flexible and powerful StatRow component
|
// A more flexible and powerful StatRow component
|
||||||
interface StatRowProps {
|
interface StatRowProps {
|
||||||
@@ -85,22 +86,22 @@ const ModelUsageTable: React.FC<{
|
|||||||
<Box>
|
<Box>
|
||||||
<Box width={nameWidth}>
|
<Box width={nameWidth}>
|
||||||
<Text bold color={theme.text.primary}>
|
<Text bold color={theme.text.primary}>
|
||||||
Model Usage
|
{t('Model Usage')}
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
<Box width={requestsWidth} justifyContent="flex-end">
|
<Box width={requestsWidth} justifyContent="flex-end">
|
||||||
<Text bold color={theme.text.primary}>
|
<Text bold color={theme.text.primary}>
|
||||||
Reqs
|
{t('Reqs')}
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
<Box width={inputTokensWidth} justifyContent="flex-end">
|
<Box width={inputTokensWidth} justifyContent="flex-end">
|
||||||
<Text bold color={theme.text.primary}>
|
<Text bold color={theme.text.primary}>
|
||||||
Input Tokens
|
{t('Input Tokens')}
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
<Box width={outputTokensWidth} justifyContent="flex-end">
|
<Box width={outputTokensWidth} justifyContent="flex-end">
|
||||||
<Text bold color={theme.text.primary}>
|
<Text bold color={theme.text.primary}>
|
||||||
Output Tokens
|
{t('Output Tokens')}
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
@@ -141,13 +142,14 @@ const ModelUsageTable: React.FC<{
|
|||||||
{cacheEfficiency > 0 && (
|
{cacheEfficiency > 0 && (
|
||||||
<Box flexDirection="column" marginTop={1}>
|
<Box flexDirection="column" marginTop={1}>
|
||||||
<Text color={theme.text.primary}>
|
<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)}
|
{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>
|
</Text>
|
||||||
<Box height={1} />
|
<Box height={1} />
|
||||||
<Text color={theme.text.secondary}>
|
<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>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
@@ -199,7 +201,7 @@ export const StatsDisplay: React.FC<StatsDisplayProps> = ({
|
|||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Text bold color={theme.text.accent}>
|
<Text bold color={theme.text.accent}>
|
||||||
Session Stats
|
{t('Session Stats')}
|
||||||
</Text>
|
</Text>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -215,33 +217,33 @@ export const StatsDisplay: React.FC<StatsDisplayProps> = ({
|
|||||||
{renderTitle()}
|
{renderTitle()}
|
||||||
<Box height={1} />
|
<Box height={1} />
|
||||||
|
|
||||||
<Section title="Interaction Summary">
|
<Section title={t('Interaction Summary')}>
|
||||||
<StatRow title="Session ID:">
|
<StatRow title={t('Session ID:')}>
|
||||||
<Text color={theme.text.primary}>{stats.sessionId}</Text>
|
<Text color={theme.text.primary}>{stats.sessionId}</Text>
|
||||||
</StatRow>
|
</StatRow>
|
||||||
<StatRow title="Tool Calls:">
|
<StatRow title={t('Tool Calls:')}>
|
||||||
<Text color={theme.text.primary}>
|
<Text color={theme.text.primary}>
|
||||||
{tools.totalCalls} ({' '}
|
{tools.totalCalls} ({' '}
|
||||||
<Text color={theme.status.success}>✓ {tools.totalSuccess}</Text>{' '}
|
<Text color={theme.status.success}>✓ {tools.totalSuccess}</Text>{' '}
|
||||||
<Text color={theme.status.error}>x {tools.totalFail}</Text> )
|
<Text color={theme.status.error}>x {tools.totalFail}</Text> )
|
||||||
</Text>
|
</Text>
|
||||||
</StatRow>
|
</StatRow>
|
||||||
<StatRow title="Success Rate:">
|
<StatRow title={t('Success Rate:')}>
|
||||||
<Text color={successColor}>{computed.successRate.toFixed(1)}%</Text>
|
<Text color={successColor}>{computed.successRate.toFixed(1)}%</Text>
|
||||||
</StatRow>
|
</StatRow>
|
||||||
{computed.totalDecisions > 0 && (
|
{computed.totalDecisions > 0 && (
|
||||||
<StatRow title="User Agreement:">
|
<StatRow title={t('User Agreement:')}>
|
||||||
<Text color={agreementColor}>
|
<Text color={agreementColor}>
|
||||||
{computed.agreementRate.toFixed(1)}%{' '}
|
{computed.agreementRate.toFixed(1)}%{' '}
|
||||||
<Text color={theme.text.secondary}>
|
<Text color={theme.text.secondary}>
|
||||||
({computed.totalDecisions} reviewed)
|
({computed.totalDecisions} {t('reviewed')})
|
||||||
</Text>
|
</Text>
|
||||||
</Text>
|
</Text>
|
||||||
</StatRow>
|
</StatRow>
|
||||||
)}
|
)}
|
||||||
{files &&
|
{files &&
|
||||||
(files.totalLinesAdded > 0 || files.totalLinesRemoved > 0) && (
|
(files.totalLinesAdded > 0 || files.totalLinesRemoved > 0) && (
|
||||||
<StatRow title="Code Changes:">
|
<StatRow title={t('Code Changes:')}>
|
||||||
<Text color={theme.text.primary}>
|
<Text color={theme.text.primary}>
|
||||||
<Text color={theme.status.success}>
|
<Text color={theme.status.success}>
|
||||||
+{files.totalLinesAdded}
|
+{files.totalLinesAdded}
|
||||||
@@ -254,16 +256,16 @@ export const StatsDisplay: React.FC<StatsDisplayProps> = ({
|
|||||||
)}
|
)}
|
||||||
</Section>
|
</Section>
|
||||||
|
|
||||||
<Section title="Performance">
|
<Section title={t('Performance')}>
|
||||||
<StatRow title="Wall Time:">
|
<StatRow title={t('Wall Time:')}>
|
||||||
<Text color={theme.text.primary}>{duration}</Text>
|
<Text color={theme.text.primary}>{duration}</Text>
|
||||||
</StatRow>
|
</StatRow>
|
||||||
<StatRow title="Agent Active:">
|
<StatRow title={t('Agent Active:')}>
|
||||||
<Text color={theme.text.primary}>
|
<Text color={theme.text.primary}>
|
||||||
{formatDuration(computed.agentActiveTime)}
|
{formatDuration(computed.agentActiveTime)}
|
||||||
</Text>
|
</Text>
|
||||||
</StatRow>
|
</StatRow>
|
||||||
<SubStatRow title="API Time:">
|
<SubStatRow title={t('API Time:')}>
|
||||||
<Text color={theme.text.primary}>
|
<Text color={theme.text.primary}>
|
||||||
{formatDuration(computed.totalApiTime)}{' '}
|
{formatDuration(computed.totalApiTime)}{' '}
|
||||||
<Text color={theme.text.secondary}>
|
<Text color={theme.text.secondary}>
|
||||||
@@ -271,7 +273,7 @@ export const StatsDisplay: React.FC<StatsDisplayProps> = ({
|
|||||||
</Text>
|
</Text>
|
||||||
</Text>
|
</Text>
|
||||||
</SubStatRow>
|
</SubStatRow>
|
||||||
<SubStatRow title="Tool Time:">
|
<SubStatRow title={t('Tool Time:')}>
|
||||||
<Text color={theme.text.primary}>
|
<Text color={theme.text.primary}>
|
||||||
{formatDuration(computed.totalToolTime)}{' '}
|
{formatDuration(computed.totalToolTime)}{' '}
|
||||||
<Text color={theme.text.secondary}>
|
<Text color={theme.text.secondary}>
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import type React from 'react';
|
|||||||
import { Box, Text } from 'ink';
|
import { Box, Text } from 'ink';
|
||||||
import { theme } from '../semantic-colors.js';
|
import { theme } from '../semantic-colors.js';
|
||||||
import { type Config } from '@qwen-code/qwen-code-core';
|
import { type Config } from '@qwen-code/qwen-code-core';
|
||||||
|
import { t } from '../../i18n/index.js';
|
||||||
|
|
||||||
interface TipsProps {
|
interface TipsProps {
|
||||||
config: Config;
|
config: Config;
|
||||||
@@ -17,12 +18,12 @@ export const Tips: React.FC<TipsProps> = ({ config }) => {
|
|||||||
const geminiMdFileCount = config.getGeminiMdFileCount();
|
const geminiMdFileCount = config.getGeminiMdFileCount();
|
||||||
return (
|
return (
|
||||||
<Box flexDirection="column">
|
<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}>
|
<Text color={theme.text.primary}>
|
||||||
1. Ask questions, edit files, or run commands.
|
{t('1. Ask questions, edit files, or run commands.')}
|
||||||
</Text>
|
</Text>
|
||||||
<Text color={theme.text.primary}>
|
<Text color={theme.text.primary}>
|
||||||
2. Be specific for the best results.
|
{t('2. Be specific for the best results.')}
|
||||||
</Text>
|
</Text>
|
||||||
{geminiMdFileCount === 0 && (
|
{geminiMdFileCount === 0 && (
|
||||||
<Text color={theme.text.primary}>
|
<Text color={theme.text.primary}>
|
||||||
@@ -30,7 +31,7 @@ export const Tips: React.FC<TipsProps> = ({ config }) => {
|
|||||||
<Text bold color={theme.text.accent}>
|
<Text bold color={theme.text.accent}>
|
||||||
QWEN.md
|
QWEN.md
|
||||||
</Text>{' '}
|
</Text>{' '}
|
||||||
files to customize your interactions with Qwen Code.
|
{t('files to customize your interactions with Qwen Code.')}
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
<Text color={theme.text.primary}>
|
<Text color={theme.text.primary}>
|
||||||
@@ -38,7 +39,7 @@ export const Tips: React.FC<TipsProps> = ({ config }) => {
|
|||||||
<Text bold color={theme.text.accent}>
|
<Text bold color={theme.text.accent}>
|
||||||
/help
|
/help
|
||||||
</Text>{' '}
|
</Text>{' '}
|
||||||
for more information.
|
{t('for more information.')}
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
type RadioSelectItem,
|
type RadioSelectItem,
|
||||||
} from './shared/RadioButtonSelect.js';
|
} from './shared/RadioButtonSelect.js';
|
||||||
import { useKeypress } from '../hooks/useKeypress.js';
|
import { useKeypress } from '../hooks/useKeypress.js';
|
||||||
|
import { t } from '../../i18n/index.js';
|
||||||
|
|
||||||
interface WelcomeBackDialogProps {
|
interface WelcomeBackDialogProps {
|
||||||
welcomeBackInfo: ProjectSummaryInfo;
|
welcomeBackInfo: ProjectSummaryInfo;
|
||||||
@@ -36,12 +37,12 @@ export function WelcomeBackDialog({
|
|||||||
const options: Array<RadioSelectItem<'restart' | 'continue'>> = [
|
const options: Array<RadioSelectItem<'restart' | 'continue'>> = [
|
||||||
{
|
{
|
||||||
key: 'restart',
|
key: 'restart',
|
||||||
label: 'Start new chat session',
|
label: t('Start new chat session'),
|
||||||
value: 'restart',
|
value: 'restart',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'continue',
|
key: 'continue',
|
||||||
label: 'Continue previous conversation',
|
label: t('Continue previous conversation'),
|
||||||
value: 'continue',
|
value: 'continue',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@@ -67,7 +68,9 @@ export function WelcomeBackDialog({
|
|||||||
>
|
>
|
||||||
<Box flexDirection="column" marginBottom={1}>
|
<Box flexDirection="column" marginBottom={1}>
|
||||||
<Text color={Colors.AccentBlue} bold>
|
<Text color={Colors.AccentBlue} bold>
|
||||||
👋 Welcome back! (Last updated: {timeAgo})
|
{t('👋 Welcome back! (Last updated: {{timeAgo}})', {
|
||||||
|
timeAgo: timeAgo || '',
|
||||||
|
})}
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
@@ -75,7 +78,7 @@ export function WelcomeBackDialog({
|
|||||||
{goalContent && (
|
{goalContent && (
|
||||||
<Box flexDirection="column" marginBottom={1}>
|
<Box flexDirection="column" marginBottom={1}>
|
||||||
<Text color={Colors.Foreground} bold>
|
<Text color={Colors.Foreground} bold>
|
||||||
🎯 Overall Goal:
|
{t('🎯 Overall Goal:')}
|
||||||
</Text>
|
</Text>
|
||||||
<Box marginTop={1} paddingLeft={2}>
|
<Box marginTop={1} paddingLeft={2}>
|
||||||
<Text color={Colors.Gray}>{goalContent}</Text>
|
<Text color={Colors.Gray}>{goalContent}</Text>
|
||||||
@@ -87,19 +90,25 @@ export function WelcomeBackDialog({
|
|||||||
{totalTasks > 0 && (
|
{totalTasks > 0 && (
|
||||||
<Box flexDirection="column" marginBottom={1}>
|
<Box flexDirection="column" marginBottom={1}>
|
||||||
<Text color={Colors.Foreground} bold>
|
<Text color={Colors.Foreground} bold>
|
||||||
📋 Current Plan:
|
📋 {t('Current Plan:')}
|
||||||
</Text>
|
</Text>
|
||||||
<Box marginTop={1} paddingLeft={2}>
|
<Box marginTop={1} paddingLeft={2}>
|
||||||
<Text color={Colors.Gray}>
|
<Text color={Colors.Gray}>
|
||||||
Progress: {doneCount}/{totalTasks} tasks completed
|
{t('Progress: {{done}}/{{total}} tasks completed', {
|
||||||
{inProgressCount > 0 && `, ${inProgressCount} in progress`}
|
done: String(doneCount),
|
||||||
|
total: String(totalTasks),
|
||||||
|
})}
|
||||||
|
{inProgressCount > 0 &&
|
||||||
|
t(', {{inProgress}} in progress', {
|
||||||
|
inProgress: String(inProgressCount),
|
||||||
|
})}
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{pendingTasks.length > 0 && (
|
{pendingTasks.length > 0 && (
|
||||||
<Box flexDirection="column" marginTop={1} paddingLeft={2}>
|
<Box flexDirection="column" marginTop={1} paddingLeft={2}>
|
||||||
<Text color={Colors.Foreground} bold>
|
<Text color={Colors.Foreground} bold>
|
||||||
Pending Tasks:
|
{t('Pending Tasks:')}
|
||||||
</Text>
|
</Text>
|
||||||
{pendingTasks.map((task: string, index: number) => (
|
{pendingTasks.map((task: string, index: number) => (
|
||||||
<Text key={index} color={Colors.Gray}>
|
<Text key={index} color={Colors.Gray}>
|
||||||
@@ -113,8 +122,8 @@ export function WelcomeBackDialog({
|
|||||||
|
|
||||||
{/* Action Selection */}
|
{/* Action Selection */}
|
||||||
<Box flexDirection="column" marginTop={1}>
|
<Box flexDirection="column" marginTop={1}>
|
||||||
<Text bold>What would you like to do?</Text>
|
<Text bold>{t('What would you like to do?')}</Text>
|
||||||
<Text>Choose how to proceed with your session:</Text>
|
<Text>{t('Choose how to proceed with your session:')}</Text>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box marginTop={1}>
|
<Box marginTop={1}>
|
||||||
|
|||||||
Reference in New Issue
Block a user