i18n add extension commands

This commit is contained in:
LaZzyMan 2026-01-20 17:20:20 +08:00
parent ba14e9e531
commit e87376e06c
12 changed files with 440 additions and 67 deletions

View file

@ -8,6 +8,7 @@ import { type CommandModule } from 'yargs';
import { SettingScope } from '../../config/settings.js';
import { getErrorMessage } from '../../utils/errors.js';
import { getExtensionManager } from './utils.js';
import { t } from '../../i18n/index.js';
interface DisableArgs {
name: string;
@ -23,7 +24,10 @@ export async function handleDisable(args: DisableArgs) {
extensionManager.disableExtension(args.name, SettingScope.User);
}
console.log(
`Extension "${args.name}" successfully disabled for scope "${args.scope}".`,
t('Extension "{{name}}" successfully disabled for scope "{{scope}}".', {
name: args.name,
scope: args.scope || SettingScope.User,
}),
);
} catch (error) {
console.error(getErrorMessage(error));
@ -33,15 +37,15 @@ export async function handleDisable(args: DisableArgs) {
export const disableCommand: CommandModule = {
command: 'disable [--scope] <name>',
describe: 'Disables an extension.',
describe: t('Disables an extension.'),
builder: (yargs) =>
yargs
.positional('name', {
describe: 'The name of the extension to disable.',
describe: t('The name of the extension to disable.'),
type: 'string',
})
.option('scope', {
describe: 'The scope to disable the extenison in.',
describe: t('The scope to disable the extenison in.'),
type: 'string',
default: SettingScope.User,
})
@ -53,11 +57,12 @@ export const disableCommand: CommandModule = {
.includes((argv.scope as string).toLowerCase())
) {
throw new Error(
`Invalid scope: ${argv.scope}. Please use one of ${Object.values(
SettingScope,
)
.map((s) => s.toLowerCase())
.join(', ')}.`,
t('Invalid scope: {{scope}}. Please use one of {{scopes}}.', {
scope: argv.scope as string,
scopes: Object.values(SettingScope)
.map((s) => s.toLowerCase())
.join(', '),
}),
);
}
return true;

View file

@ -8,6 +8,7 @@ import { type CommandModule } from 'yargs';
import { FatalConfigError, getErrorMessage } from '@qwen-code/qwen-code-core';
import { SettingScope } from '../../config/settings.js';
import { getExtensionManager } from './utils.js';
import { t } from '../../i18n/index.js';
interface EnableArgs {
name: string;
@ -25,11 +26,16 @@ export async function handleEnable(args: EnableArgs) {
}
if (args.scope) {
console.log(
`Extension "${args.name}" successfully enabled for scope "${args.scope}".`,
t('Extension "{{name}}" successfully enabled for scope "{{scope}}".', {
name: args.name,
scope: args.scope,
}),
);
} else {
console.log(
`Extension "${args.name}" successfully enabled in all scopes.`,
t('Extension "{{name}}" successfully enabled in all scopes.', {
name: args.name,
}),
);
}
} catch (error) {
@ -39,16 +45,17 @@ export async function handleEnable(args: EnableArgs) {
export const enableCommand: CommandModule = {
command: 'enable [--scope] <name>',
describe: 'Enables an extension.',
describe: t('Enables an extension.'),
builder: (yargs) =>
yargs
.positional('name', {
describe: 'The name of the extension to enable.',
describe: t('The name of the extension to enable.'),
type: 'string',
})
.option('scope', {
describe:
describe: t(
'The scope to enable the extenison in. If not set, will be enabled in all scopes.',
),
type: 'string',
})
.check((argv) => {
@ -59,11 +66,12 @@ export const enableCommand: CommandModule = {
.includes((argv.scope as string).toLowerCase())
) {
throw new Error(
`Invalid scope: ${argv.scope}. Please use one of ${Object.values(
SettingScope,
)
.map((s) => s.toLowerCase())
.join(', ')}.`,
t('Invalid scope: {{scope}}. Please use one of {{scopes}}.', {
scope: argv.scope as string,
scopes: Object.values(SettingScope)
.map((s) => s.toLowerCase())
.join(', '),
}),
);
}
return true;

View file

@ -17,6 +17,7 @@ import {
requestConsentOrFail,
requestConsentNonInteractive,
} from './consent.js';
import { t } from '../../i18n/index.js';
interface InstallArgs {
source: string;
@ -36,7 +37,9 @@ export async function handleInstall(args: InstallArgs) {
) {
if (args.ref || args.autoUpdate) {
throw new Error(
'--ref and --auto-update are not applicable for marketplace extensions.',
t(
'--ref and --auto-update are not applicable for marketplace extensions.',
),
);
}
}
@ -64,7 +67,9 @@ export async function handleInstall(args: InstallArgs) {
requestConsent,
);
console.log(
`Extension "${extension.name}" installed successfully and enabled.`,
t('Extension "{{name}}" installed successfully and enabled.', {
name: extension.name,
}),
);
} catch (error) {
console.error(getErrorMessage(error));
@ -74,37 +79,40 @@ export async function handleInstall(args: InstallArgs) {
export const installCommand: CommandModule = {
command: 'install <source>',
describe:
describe: t(
'Installs an extension from a git repository URL, local path, or claude marketplace (marketplace-url:plugin-name).',
),
builder: (yargs) =>
yargs
.positional('source', {
describe:
describe: t(
'The github URL, local path, or marketplace source (marketplace-url:plugin-name) of the extension to install.',
),
type: 'string',
demandOption: true,
})
.option('ref', {
describe: 'The git ref to install from.',
describe: t('The git ref to install from.'),
type: 'string',
})
.option('auto-update', {
describe: 'Enable auto-update for this extension.',
describe: t('Enable auto-update for this extension.'),
type: 'boolean',
})
.option('pre-release', {
describe: 'Enable pre-release versions for this extension.',
describe: t('Enable pre-release versions for this extension.'),
type: 'boolean',
})
.option('consent', {
describe:
describe: t(
'Acknowledge the security risks of installing an extension and skip the confirmation prompt.',
),
type: 'boolean',
default: false,
})
.check((argv) => {
if (!argv.source) {
throw new Error('The source argument must be provided.');
throw new Error(t('The source argument must be provided.'));
}
return true;
}),

View file

@ -12,6 +12,7 @@ import {
requestConsentOrFail,
} from './consent.js';
import { getExtensionManager } from './utils.js';
import { t } from '../../i18n/index.js';
interface InstallArgs {
path: string;
@ -30,11 +31,13 @@ export async function handleLink(args: InstallArgs) {
requestConsentOrFail.bind(null, requestConsentNonInteractive),
);
if (!extension) {
console.log('Link extension failed to install.');
console.log(t('Link extension failed to install.'));
return;
}
console.log(
`Extension "${extension.name}" linked successfully and enabled.`,
t('Extension "{{name}}" linked successfully and enabled.', {
name: extension.name,
}),
);
} catch (error) {
console.error(getErrorMessage(error));
@ -44,12 +47,13 @@ export async function handleLink(args: InstallArgs) {
export const linkCommand: CommandModule = {
command: 'link <path>',
describe:
describe: t(
'Links an extension from a local path. Updates made to the local path will always be reflected.',
),
builder: (yargs) =>
yargs
.positional('path', {
describe: 'The name of the extension to link.',
describe: t('The name of the extension to link.'),
type: 'string',
})
.check((_) => true),

View file

@ -7,6 +7,7 @@
import type { CommandModule } from 'yargs';
import { getErrorMessage } from '../../utils/errors.js';
import { getExtensionManager } from './utils.js';
import { t } from '../../i18n/index.js';
export async function handleList() {
try {
@ -14,7 +15,7 @@ export async function handleList() {
const extensions = extensionManager.getLoadedExtensions();
if (!extensions || extensions.length === 0) {
console.log('No extensions installed.');
console.log(t('No extensions installed.'));
return;
}
console.log(
@ -32,7 +33,7 @@ export async function handleList() {
export const listCommand: CommandModule = {
command: 'list',
describe: 'Lists installed extensions.',
describe: t('Lists installed extensions.'),
builder: (yargs) => yargs,
handler: async () => {
await handleList();

View file

@ -12,6 +12,7 @@ import {
promptForSetting,
updateSetting,
} from '@qwen-code/qwen-code-core';
import { t } from '../../i18n/index.js';
// --- SET COMMAND ---
interface SetArgs {
@ -22,21 +23,21 @@ interface SetArgs {
const setCommand: CommandModule<object, SetArgs> = {
command: 'set [--scope] <name> <setting>',
describe: 'Set a specific setting for an extension.',
describe: t('Set a specific setting for an extension.'),
builder: (yargs) =>
yargs
.positional('name', {
describe: 'Name of the extension to configure.',
describe: t('Name of the extension to configure.'),
type: 'string',
demandOption: true,
})
.positional('setting', {
describe: 'The setting to configure (name or env var).',
describe: t('The setting to configure (name or env var).'),
type: 'string',
demandOption: true,
})
.option('scope', {
describe: 'The scope to set the setting in.',
describe: t('The scope to set the setting in.'),
type: 'string',
choices: ['user', 'workspace'],
default: 'user',
@ -49,7 +50,7 @@ const setCommand: CommandModule<object, SetArgs> = {
if (!extensions || extensions.length === 0) return;
const extension = extensions.find((e) => e.name === name);
if (!extension) {
console.log(`Extension "${name}" not found.`);
console.log(t('Extension "{{name}}" not found.', { name }));
return;
}
await updateSetting(
@ -69,10 +70,10 @@ interface ListArgs {
const listCommand: CommandModule<object, ListArgs> = {
command: 'list <name>',
describe: 'List all settings for an extension.',
describe: t('List all settings for an extension.'),
builder: (yargs) =>
yargs.positional('name', {
describe: 'Name of the extension.',
describe: t('Name of the extension.'),
type: 'string',
demandOption: true,
}),
@ -84,11 +85,13 @@ const listCommand: CommandModule<object, ListArgs> = {
if (!extensions || extensions.length === 0) return;
const extension = extensions.find((e) => e.name === name);
if (!extension) {
console.log(`Extension "${name}" not found.`);
console.log(t('Extension "{{name}}" not found.', { name }));
return;
}
if (!extension || !extension.settings || extension.settings.length === 0) {
console.log(`Extension "${name}" has no settings to configure.`);
console.log(
t('Extension "{{name}}" has no settings to configure.', { name }),
);
return;
}
@ -104,29 +107,29 @@ const listCommand: CommandModule<object, ListArgs> = {
);
const mergedSettings = { ...userSettings, ...workspaceSettings };
console.log(`Settings for "${name}":`);
console.log(t('Settings for "{{name}}":', { name }));
for (const setting of extension.settings) {
const value = mergedSettings[setting.envVar];
let displayValue: string;
let scopeInfo = '';
if (workspaceSettings[setting.envVar] !== undefined) {
scopeInfo = ' (workspace)';
scopeInfo = ' ' + t('(workspace)');
} else if (userSettings[setting.envVar] !== undefined) {
scopeInfo = ' (user)';
scopeInfo = ' ' + t('(user)');
}
if (value === undefined) {
displayValue = '[not set]';
displayValue = t('[not set]');
} else if (setting.sensitive) {
displayValue = '[value stored in keychain]';
displayValue = t('[value stored in keychain]');
} else {
displayValue = value;
}
console.log(`
- ${setting.name} (${setting.envVar})`);
console.log(` Description: ${setting.description}`);
console.log(` Value: ${displayValue}${scopeInfo}`);
console.log(` ${t('Description:')} ${setting.description}`);
console.log(` ${t('Value:')} ${displayValue}${scopeInfo}`);
}
},
};
@ -134,12 +137,12 @@ const listCommand: CommandModule<object, ListArgs> = {
// --- SETTINGS COMMAND ---
export const settingsCommand: CommandModule = {
command: 'settings <command>',
describe: 'Manage extension settings.',
describe: t('Manage extension settings.'),
builder: (yargs) =>
yargs
.command(setCommand)
.command(listCommand)
.demandCommand(1, 'You need to specify a command (set or list).')
.demandCommand(1, t('You need to specify a command (set or list).'))
.version(false),
handler: () => {
// This handler is not called when a subcommand is provided.

View file

@ -13,6 +13,7 @@ import {
} from './consent.js';
import { isWorkspaceTrusted } from '../../config/trustedFolders.js';
import { loadSettings } from '../../config/settings.js';
import { t } from '../../i18n/index.js';
interface UninstallArgs {
name: string; // can be extension name or source URL.
@ -33,7 +34,9 @@ export async function handleUninstall(args: UninstallArgs) {
});
await extensionManager.refreshCache();
await extensionManager.uninstallExtension(args.name, false);
console.log(`Extension "${args.name}" successfully uninstalled.`);
console.log(
t('Extension "{{name}}" successfully uninstalled.', { name: args.name }),
);
} catch (error) {
console.error(getErrorMessage(error));
process.exit(1);
@ -42,17 +45,19 @@ export async function handleUninstall(args: UninstallArgs) {
export const uninstallCommand: CommandModule = {
command: 'uninstall <name>',
describe: 'Uninstalls an extension.',
describe: t('Uninstalls an extension.'),
builder: (yargs) =>
yargs
.positional('name', {
describe: 'The name or source path of the extension to uninstall.',
describe: t('The name or source path of the extension to uninstall.'),
type: 'string',
})
.check((argv) => {
if (!argv.name) {
throw new Error(
'Please include the name of the extension to uninstall as a positional argument.',
t(
'Please include the name of the extension to uninstall as a positional argument.',
),
);
}
return true;

View file

@ -12,6 +12,7 @@ import {
type ExtensionUpdateInfo,
} from '@qwen-code/qwen-code-core';
import { getExtensionManager } from './utils.js';
import { t } from '../../i18n/index.js';
interface UpdateArgs {
name?: string;
@ -19,7 +20,14 @@ interface UpdateArgs {
}
const updateOutput = (info: ExtensionUpdateInfo) =>
`Extension "${info.name}" successfully updated: ${info.originalVersion}${info.updatedVersion}.`;
t(
'Extension "{{name}}" successfully updated: {{oldVersion}} → {{newVersion}}.',
{
name: info.name,
oldVersion: info.originalVersion,
newVersion: info.updatedVersion,
},
);
export async function handleUpdate(args: UpdateArgs) {
const extensionManager = await getExtensionManager();
@ -31,12 +39,15 @@ export async function handleUpdate(args: UpdateArgs) {
(extension) => extension.name === args.name,
);
if (!extension) {
console.log(`Extension "${args.name}" not found.`);
console.log(t('Extension "{{name}}" not found.', { name: args.name }));
return;
}
if (!extension.installMetadata) {
console.log(
`Unable to install extension "${args.name}" due to missing install metadata`,
t(
'Unable to install extension "{{name}}" due to missing install metadata',
{ name: args.name },
),
);
return;
}
@ -45,7 +56,9 @@ export async function handleUpdate(args: UpdateArgs) {
extensionManager,
);
if (updateState !== ExtensionUpdateState.UPDATE_AVAILABLE) {
console.log(`Extension "${args.name}" is already up to date.`);
console.log(
t('Extension "{{name}}" is already up to date.', { name: args.name }),
);
return;
}
// TODO(chrstnb): we should list extensions if the requested extension is not installed.
@ -59,10 +72,19 @@ export async function handleUpdate(args: UpdateArgs) {
updatedExtensionInfo.updatedVersion
) {
console.log(
`Extension "${args.name}" successfully updated: ${updatedExtensionInfo.originalVersion}${updatedExtensionInfo.updatedVersion}.`,
t(
'Extension "{{name}}" successfully updated: {{oldVersion}} → {{newVersion}}.',
{
name: args.name,
oldVersion: updatedExtensionInfo.originalVersion,
newVersion: updatedExtensionInfo.updatedVersion,
},
),
);
} else {
console.log(`Extension "${args.name}" is already up to date.`);
console.log(
t('Extension "{{name}}" is already up to date.', { name: args.name }),
);
}
} catch (error) {
console.error(getErrorMessage(error));
@ -87,7 +109,7 @@ export async function handleUpdate(args: UpdateArgs) {
(info) => info.originalVersion !== info.updatedVersion,
);
if (updateInfos.length === 0) {
console.log('No extensions to update.');
console.log(t('No extensions to update.'));
return;
}
console.log(updateInfos.map((info) => updateOutput(info)).join('\n'));
@ -99,22 +121,25 @@ export async function handleUpdate(args: UpdateArgs) {
export const updateCommand: CommandModule = {
command: 'update [<name>] [--all]',
describe:
describe: t(
'Updates all extensions or a named extension to the latest version.',
),
builder: (yargs) =>
yargs
.positional('name', {
describe: 'The name of the extension to update.',
describe: t('The name of the extension to update.'),
type: 'string',
})
.option('all', {
describe: 'Update all extensions.',
describe: t('Update all extensions.'),
type: 'boolean',
})
.conflicts('name', 'all')
.check((argv) => {
if (!argv.all && !argv.name) {
throw new Error('Either an extension name or --all must be provided');
throw new Error(
t('Either an extension name or --all must be provided'),
);
}
return true;
}),