- {/* TODO i18n: missing keys codex.providersCount / codex.profilesCount / codex.enabledFeaturesCount / codex.mcpServersCount */}
- providers: {diagnostics.config.modelProviderCount}
+ {t('codex.providersCount', { count: diagnostics.config.modelProviderCount })}
- profiles: {diagnostics.config.profileCount}
+ {t('codex.profilesCount', { count: diagnostics.config.profileCount })}
- enabled features: {diagnostics.config.enabledFeatures.length}
+ {t('codex.enabledFeaturesCount', {
+ count: diagnostics.config.enabledFeatures.length,
+ })}
- MCP servers: {diagnostics.config.mcpServerCount}
+ {t('codex.mcpServersCount', { count: diagnostics.config.mcpServerCount })}
{diagnostics.config.topLevelKeys.length > 0 && (
@@ -109,7 +110,7 @@ export function UpdatesDetailsPanel({
{entry.name}
- {SUPPORT_SCOPE_LABELS[entry.scope]}
+ {scopeLabels[entry.scope]}
diff --git a/ui/src/hooks/use-accounts.ts b/ui/src/hooks/use-accounts.ts
index d1461b67..890ff1ac 100644
--- a/ui/src/hooks/use-accounts.ts
+++ b/ui/src/hooks/use-accounts.ts
@@ -135,12 +135,16 @@ export function useUpdateAccountContext() {
}) => api.accounts.updateContext(name, { context_mode, context_group, continuity_mode }),
onSuccess: (_data, vars) => {
queryClient.invalidateQueries({ queryKey: ['accounts'] });
+ const normalizedGroup = (vars.context_group || 'default')
+ .trim()
+ .toLowerCase()
+ .replace(/\s+/g, '-');
const contextSummary =
vars.context_mode === 'shared'
? vars.continuity_mode === 'deeper'
- ? `shared (${(vars.context_group || 'default').trim().toLowerCase().replace(/\s+/g, '-')}, deeper continuity)`
- : `shared (${(vars.context_group || 'default').trim().toLowerCase().replace(/\s+/g, '-')}, standard)`
- : 'isolated';
+ ? `${t('accountsPage.sharedDeeper')} (${normalizedGroup})`
+ : `${t('accountsPage.sharedStandard')} (${normalizedGroup})`
+ : t('accountsPage.isolated');
toast.success(t('toasts.contextUpdated', { name: vars.name, summary: contextSummary }));
},
onError: (error: Error) => {
@@ -180,18 +184,14 @@ export function useConfirmLegacyAccountPolicies() {
onSuccess: ({ updatedCount, failedCount }) => {
queryClient.invalidateQueries({ queryKey: ['accounts'] });
if (failedCount > 0 && updatedCount > 0) {
- // TODO i18n: missing key for partial legacy confirmation with failures
toast.error(
- `Confirmed ${updatedCount} legacy account${updatedCount > 1 ? 's' : ''}, but ${failedCount} update${failedCount > 1 ? 's' : ''} failed. Refreshed account state.`
+ t('toasts.legacyConfirmPartial', { updated: updatedCount, failed: failedCount })
);
return;
}
if (failedCount > 0) {
- // TODO i18n: missing key for all legacy confirmations failed
- toast.error(
- `Failed to confirm ${failedCount} legacy account${failedCount > 1 ? 's' : ''}. Refreshed account state.`
- );
+ toast.error(t('toasts.legacyConfirmAllFailed', { failed: failedCount }));
return;
}
diff --git a/ui/src/hooks/use-cliproxy-auth-flow.ts b/ui/src/hooks/use-cliproxy-auth-flow.ts
index 3f41a1e3..ce847823 100644
--- a/ui/src/hooks/use-cliproxy-auth-flow.ts
+++ b/ui/src/hooks/use-cliproxy-auth-flow.ts
@@ -131,8 +131,7 @@ export function useCliproxyAuthFlow() {
setState((prev) => ({
...prev,
isAuthenticating: false,
- // TODO i18n: missing key for 'Authentication timed out. Please try again.'
- error: 'Authentication timed out. Please try again.',
+ error: t('toasts.providerAuthTimeout'),
}));
}
return;
@@ -161,8 +160,7 @@ export function useCliproxyAuthFlow() {
const hasAccount = typeof data.account === 'object' && data.account !== null;
if (!hasAccount) {
stopPolling();
- // TODO i18n: missing key for 'Authenticated account could not be registered'
- const errorMsg = 'Authenticated account could not be registered';
+ const errorMsg = t('toasts.providerAccountRegistrationFailed');
toast.error(errorMsg);
setState((prev) => ({
...prev,
@@ -226,11 +224,10 @@ export function useCliproxyAuthFlow() {
}
stopPolling();
- // TODO i18n: missing key for 'Lost contact with the auth status endpoint'
const message =
error instanceof Error && error.message.trim().length > 0
? error.message
- : 'Lost contact with the auth status endpoint';
+ : t('toasts.providerLostStatusEndpoint');
toast.error(message);
setState((prev) => ({
...prev,
@@ -245,10 +242,9 @@ export function useCliproxyAuthFlow() {
const startAuth = useCallback(
async (provider: string, options?: StartAuthOptions) => {
if (!isValidProvider(provider)) {
- // TODO i18n: missing key for 'Unknown provider: {{provider}}'
setState({
...INITIAL_STATE,
- error: `Unknown provider: ${provider}`,
+ error: t('toasts.providerUnknown', { provider }),
});
return;
}
@@ -319,13 +315,12 @@ export function useCliproxyAuthFlow() {
openedAuthUrlRef.current = false;
setState(INITIAL_STATE);
} else {
- // TODO i18n: missing key for 'Authenticated account could not be registered' (start endpoint)
const errorMsg =
typeof data.error === 'string'
? data.error
: success
- ? 'Authenticated account could not be registered'
- : 'Authentication failed';
+ ? t('toasts.providerAccountRegistrationFailed')
+ : t('auth.loginFailed');
toast.error(errorMsg);
setState((prev) => ({
...prev,
@@ -370,8 +365,8 @@ export function useCliproxyAuthFlow() {
const success = data.success === true;
if (!response.ok || !success) {
- // TODO i18n: missing key for 'Failed to start OAuth'
- const errorMsg = typeof data.error === 'string' ? data.error : 'Failed to start OAuth';
+ const errorMsg =
+ typeof data.error === 'string' ? data.error : t('toasts.providerStartOAuthFailed');
throw new Error(errorMsg);
}
@@ -418,7 +413,7 @@ export function useCliproxyAuthFlow() {
setState(INITIAL_STATE);
return;
}
- const message = error instanceof Error ? error.message : 'Authentication failed';
+ const message = error instanceof Error ? error.message : t('auth.loginFailed');
toast.error(message);
setState((prev) => ({
...prev,
@@ -427,7 +422,7 @@ export function useCliproxyAuthFlow() {
}));
}
},
- [isActiveAttempt, pollStatus, stopPolling, queryClient]
+ [isActiveAttempt, pollStatus, stopPolling, queryClient, t]
);
const cancelAuth = useCallback(() => {
@@ -486,21 +481,20 @@ export function useCliproxyAuthFlow() {
toast.success(t('toasts.providerAuthSuccess', { provider: currentProvider }));
setState(INITIAL_STATE);
} else {
- // TODO i18n: missing key for 'Callback submission failed'
const errorMsg =
typeof data.error === 'string'
? data.error
: success
- ? 'Authenticated account could not be registered'
- : 'Callback submission failed';
+ ? t('toasts.providerAccountRegistrationFailed')
+ : t('toasts.providerCallbackFailed');
throw new Error(errorMsg);
}
} catch (error) {
if (!isActiveAttempt(attemptId)) {
return;
}
- // TODO i18n: missing key for 'Failed to submit callback'
- const message = error instanceof Error ? error.message : 'Failed to submit callback';
+ const message =
+ error instanceof Error ? error.message : t('toasts.providerSubmitCallbackFailed');
toast.error(message);
setState((prev) => ({ ...prev, isSubmittingCallback: false, error: message }));
}
diff --git a/ui/src/lib/codex-config.ts b/ui/src/lib/codex-config.ts
index 35f57b14..11c4d602 100644
--- a/ui/src/lib/codex-config.ts
+++ b/ui/src/lib/codex-config.ts
@@ -1,3 +1,5 @@
+import i18n from '@/lib/i18n';
+
export interface CodexTopLevelSettingsView {
model: string | null;
modelReasoningEffort: string | null;
@@ -58,46 +60,73 @@ base_url = "http://127.0.0.1:8317/api/provider/codex"
env_key = "CLIPROXY_API_KEY"
wire_api = "responses"`;
-export const KNOWN_CODEX_FEATURES: CodexFeatureCatalogEntry[] = [
- {
- name: 'multi_agent',
- label: 'Multi-agent', // TODO i18n: missing key for codex feature
- description: 'Enable subagent collaboration tools.', // TODO i18n: missing key
- },
- {
- name: 'unified_exec',
- label: 'Unified exec', // TODO i18n: missing key for codex feature
- description: 'Use the PTY-backed unified exec tool.', // TODO i18n: missing key
- },
- {
- name: 'shell_snapshot',
- label: 'Shell snapshot', // TODO i18n: missing key for codex feature
- description: 'Reuse shell environment snapshots.', // TODO i18n: missing key
- },
- {
- name: 'apply_patch_freeform',
- label: 'Apply patch', // TODO i18n: missing key for codex feature
- description: 'Enable freeform apply_patch edits.', // TODO i18n: missing key
- },
- { name: 'js_repl', label: 'JS REPL', description: 'Enable the Node-backed JavaScript REPL.' }, // TODO i18n: missing keys
- {
- name: 'runtime_metrics',
- label: 'Runtime metrics', // TODO i18n: missing key for codex feature
- description: 'Collect Codex runtime metrics.', // TODO i18n: missing key
- },
- {
- name: 'prevent_idle_sleep',
- label: 'Prevent idle sleep', // TODO i18n: missing key for codex feature
- description: 'Keep the machine awake while active.', // TODO i18n: missing key
- },
- { name: 'fast_mode', label: 'Fast mode', description: 'Allow the fast service tier path.' }, // TODO i18n: missing keys
- { name: 'apps', label: 'Apps', description: 'Enable ChatGPT Apps and connectors support.' }, // TODO i18n: missing keys
- {
- name: 'smart_approvals',
- label: 'Smart approvals', // TODO i18n: missing key for codex feature
- description: 'Route eligible approvals through the guardian flow.', // TODO i18n: missing key
- },
-];
+export const KNOWN_CODEX_FEATURE_NAMES = [
+ 'multi_agent',
+ 'unified_exec',
+ 'shell_snapshot',
+ 'apply_patch_freeform',
+ 'js_repl',
+ 'runtime_metrics',
+ 'prevent_idle_sleep',
+ 'fast_mode',
+ 'apps',
+ 'smart_approvals',
+] as const;
+
+export function getKnownCodexFeatures(): CodexFeatureCatalogEntry[] {
+ return [
+ {
+ name: 'multi_agent',
+ label: i18n.t('codex.featureMultiAgentLabel'),
+ description: i18n.t('codex.featureMultiAgentDesc'),
+ },
+ {
+ name: 'unified_exec',
+ label: i18n.t('codex.featureUnifiedExecLabel'),
+ description: i18n.t('codex.featureUnifiedExecDesc'),
+ },
+ {
+ name: 'shell_snapshot',
+ label: i18n.t('codex.featureShellSnapshotLabel'),
+ description: i18n.t('codex.featureShellSnapshotDesc'),
+ },
+ {
+ name: 'apply_patch_freeform',
+ label: i18n.t('codex.featureApplyPatchLabel'),
+ description: i18n.t('codex.featureApplyPatchDesc'),
+ },
+ {
+ name: 'js_repl',
+ label: i18n.t('codex.featureJsReplLabel'),
+ description: i18n.t('codex.featureJsReplDesc'),
+ },
+ {
+ name: 'runtime_metrics',
+ label: i18n.t('codex.featureRuntimeMetricsLabel'),
+ description: i18n.t('codex.featureRuntimeMetricsDesc'),
+ },
+ {
+ name: 'prevent_idle_sleep',
+ label: i18n.t('codex.featurePreventIdleSleepLabel'),
+ description: i18n.t('codex.featurePreventIdleSleepDesc'),
+ },
+ {
+ name: 'fast_mode',
+ label: i18n.t('codex.featureFastModeLabel'),
+ description: i18n.t('codex.featureFastModeDesc'),
+ },
+ {
+ name: 'apps',
+ label: i18n.t('codex.featureAppsLabel'),
+ description: i18n.t('codex.featureAppsDesc'),
+ },
+ {
+ name: 'smart_approvals',
+ label: i18n.t('codex.featureSmartApprovalsLabel'),
+ description: i18n.t('codex.featureSmartApprovalsDesc'),
+ },
+ ];
+}
function asObject(value: unknown): Record | null {
return typeof value === 'object' && value !== null && !Array.isArray(value)
@@ -222,9 +251,9 @@ export function readCodexFeatureState(
const features = asObject(config?.features);
const state: Record = {};
- for (const feature of KNOWN_CODEX_FEATURES) {
- const value = features?.[feature.name];
- state[feature.name] = typeof value === 'boolean' ? value : null;
+ for (const featureName of KNOWN_CODEX_FEATURE_NAMES) {
+ const value = features?.[featureName];
+ state[featureName] = typeof value === 'boolean' ? value : null;
}
if (features) {
diff --git a/ui/src/lib/i18n.ts b/ui/src/lib/i18n.ts
index 99c7133d..ce95fc3e 100644
--- a/ui/src/lib/i18n.ts
+++ b/ui/src/lib/i18n.ts
@@ -1066,6 +1066,8 @@ const resources = {
},
apiProfiles: {
title: 'Profiles',
+ sidebarTitle: 'API Profiles',
+ sidebarSubtitle: 'Premium APIs, local runtimes, custom endpoints',
new: 'New',
searchPlaceholder: 'Search profiles...',
loadingProfiles: 'Loading profiles...',
@@ -1087,6 +1089,19 @@ const resources = {
unsavedChangesDesc:
'You have unsaved changes in "{{current}}". Discard and switch to "{{next}}"?',
discardSwitch: 'Discard and Switch',
+ noOrphansFound: 'No orphan profile settings found',
+ confirmRegisterOrphans:
+ 'Found {{total}} orphan profile setting file(s), with {{valid}} ready to register. Register them now?',
+ registeredWithSkipped: ' ({{count}} skipped)',
+ registeredProfiles: 'Registered {{count}} profile(s)',
+ copyPrompt: 'Copy "{{name}}" to which new profile name?',
+ destinationEmpty: 'Destination profile name cannot be empty',
+ exportRedacted:
+ 'Export created with redacted token. Use include-secrets flow in CLI if needed.',
+ exportDownloaded: 'Profile export downloaded',
+ importFailed: 'Failed to import profile bundle',
+ discoverOrphans: 'Discover orphan profiles',
+ importProfileBundle: 'Import profile bundle',
},
accountsPage: {
title: 'Accounts',
@@ -1578,6 +1593,11 @@ const resources = {
fiveHourResets: '5h resets {{time}}',
weeklyResets: 'Weekly resets {{time}}',
},
+ utils: {
+ codeReview5h: 'Code review (5h)',
+ codeReviewWeekly: 'Code review (weekly)',
+ codeReview: 'Code review',
+ },
sponsorButton: {
title: 'Sponsor this project on GitHub',
sponsor: 'Sponsor',
@@ -1635,6 +1655,8 @@ const resources = {
conflictDesc:
'This settings file was modified by another process. Overwrite with your changes or discard?',
overwrite: 'Overwrite',
+ invalidJson: 'Invalid JSON',
+ failedSave: 'Failed to save',
},
// ========================================
@@ -1852,12 +1874,60 @@ const resources = {
nativeCodexRuntime: 'Native Codex Runtime',
ccsCodexProvider: 'CCS Codex provider / bridge',
codexDocs: 'Codex docs',
+ howCodexWorks: 'How Codex works in CCS',
supportedFlows: 'Supported flows',
twoSupportedPaths: 'Two supported paths:',
nativeLabel: 'Native:',
nativeDesc: 'Codex is a first-class, runtime-only target in CCS v1.',
+ nativeConfigLabel: 'Native config:',
+ nativeConfigDesc: 'ccs-codex and ccsx launch native Codex using your saved defaults.',
+ transientOverridesLabel: 'Transient overrides:',
+ transientOverridesDesc:
+ 'ccsxp (or ccs codex --target codex) uses the CCS provider shortcut.',
+ cliproxyDefaultLabel: 'CLIProxy default:',
+ cliproxyDefaultDesc:
+ 'To make plain codex use CLIProxy, set model_provider = "cliproxy" and add the recipe below.',
ccsBridge: 'CCS Bridge',
apiProfilesDefault: 'API profiles continue to default to Claude or Droid.',
+ runtimeInstall: 'Runtime install',
+ detected: 'Detected',
+ notFound: 'Not found',
+ detectionSource: 'Detection source',
+ binaryPath: 'Binary path',
+ installDirectory: 'Install directory',
+ versionLabel: 'Version',
+ nativeAliases: 'Native aliases',
+ ccsProviderShortcut: 'CCS provider shortcut',
+ configOverrideSupport: '--config override support',
+ available: 'Available',
+ missing: 'Missing',
+ cliproxyNativeCodex: 'CLIProxy-backed native Codex',
+ builtInLabel: 'Built-in:',
+ builtInCcsxpDesc: 'Use ccsxp for the CCS provider shortcut.',
+ nativeRecipeLabel: 'Native:',
+ nativeRecipeDesc: 'Configure the recipe below to use CLIProxy directly with codex.',
+ saveProviderNamedCliproxy:
+ 'Save a provider named cliproxy with the base URL and env key above.',
+ inTopLevelSetDefault: 'In Top-level settings, set Default provider to cliproxy.',
+ exportCliproxyApiKey:
+ 'Export CLIPROXY_API_KEY in your shell before launching native Codex.',
+ noConfigOverrides:
+ 'This Codex build can still use the native path, but CCS-backed Codex routing via ccsxp or ccs codex --target codex stays unavailable until the detected Codex binary exposes --config overrides.',
+ configFile: 'Config file',
+ path: 'Path',
+ resolved: 'Resolved',
+ size: 'Size',
+ lastModified: 'Last modified',
+ tomlWarning: 'TOML warning',
+ readWarning: 'Read warning',
+ currentUserLayerSummary: 'Current user-layer summary',
+ activeProfile: 'Active profile',
+ notSet: 'Not set',
+ providersCount: 'providers: {{count}}',
+ profilesCount: 'profiles: {{count}}',
+ enabledFeaturesCount: 'enabled features: {{count}}',
+ mcpServersCount: 'mcp servers: {{count}}',
+ userLayerKeysPresent: 'User-layer keys present',
recommendedSetupFlow: 'Recommended setup flow',
fastestPath: 'Fastest path',
officialChannels: 'Official Channels',
@@ -1866,6 +1936,9 @@ const resources = {
runBuiltInCodex: 'Run built-in Codex on Codex',
runBuiltInCodexExplicit: 'Run built-in Codex on Codex (explicit)',
openCodexDashboard: 'Open Codex dashboard',
+ nativeShortAlias: 'Native short alias',
+ ccsCodexShortcut: 'CCS Codex shortcut',
+ explicitProviderRoute: 'Explicit provider route',
status: 'Status',
profiles: 'Profiles',
createNewProfile: 'Create new profile',
@@ -1895,6 +1968,10 @@ const resources = {
untrusted: 'untrusted',
noProjectTrustEntries: 'No explicit project trust entries saved.',
codexNativeRecipe: 'Saved native Codex recipe',
+ runtimeVsProvider: 'Runtime vs provider',
+ honorsSavedNativeConfig: 'Honors saved native user config',
+ usesTransientOverrides: 'Uses transient overrides',
+ unavailableNoConfig: 'Unavailable (Codex build lacks --config support).',
gptContextCap: 'GPT-5.4 context cap',
usageLimitCost: 'Usage-limit cost above 272K',
longContextOverride: 'Long context override',
@@ -1911,6 +1988,37 @@ const resources = {
configYaml: 'config.yaml',
flow: 'Flow',
docsTab: 'Docs',
+ yes: 'Yes',
+ no: 'No',
+ warningsTitle: 'Warnings',
+ features: 'Features',
+ featuresDesc: 'Toggle the supported Codex feature flags CCS can safely manage.',
+ configOnlyFlags: 'Existing config-only flags',
+ configOnlyFlagsDesc:
+ 'These feature keys already exist in your `config.toml`, so CCS can surface them without claiming full catalog coverage.',
+ existing: 'existing',
+ nonBooleanForm: 'Stored in a non-boolean form. Use raw TOML if you need to edit it.',
+ discoveredFromFile: "Discovered from the current file instead of CCS's built-in catalog.",
+ featureMultiAgentLabel: 'Multi-agent',
+ featureMultiAgentDesc: 'Enable subagent collaboration tools.',
+ featureUnifiedExecLabel: 'Unified exec',
+ featureUnifiedExecDesc: 'Use the PTY-backed unified exec tool.',
+ featureShellSnapshotLabel: 'Shell snapshot',
+ featureShellSnapshotDesc: 'Reuse shell environment snapshots.',
+ featureApplyPatchLabel: 'Apply patch',
+ featureApplyPatchDesc: 'Enable freeform apply_patch edits.',
+ featureJsReplLabel: 'JS REPL',
+ featureJsReplDesc: 'Enable the Node-backed JavaScript REPL.',
+ featureRuntimeMetricsLabel: 'Runtime metrics',
+ featureRuntimeMetricsDesc: 'Collect Codex runtime metrics.',
+ featurePreventIdleSleepLabel: 'Prevent idle sleep',
+ featurePreventIdleSleepDesc: 'Keep the machine awake while active.',
+ featureFastModeLabel: 'Fast mode',
+ featureFastModeDesc: 'Allow the fast service tier path.',
+ featureAppsLabel: 'Apps',
+ featureAppsDesc: 'Enable ChatGPT Apps and connectors support.',
+ featureSmartApprovalsLabel: 'Smart approvals',
+ featureSmartApprovalsDesc: 'Route eligible approvals through the guardian flow.',
},
droidSettings: {
quickControls: 'Quick Controls',
@@ -2022,6 +2130,13 @@ const resources = {
syncFailed: 'Sync failed: {{error}}',
providerAuthSuccess: '{{provider}} authentication successful',
providerDeviceCodeInCallback: 'Provider returned Device Code flow in callback mode',
+ providerAuthTimeout: 'Authentication timed out. Please try again.',
+ providerAccountRegistrationFailed: 'Authenticated account could not be registered',
+ providerLostStatusEndpoint: 'Lost contact with the auth status endpoint',
+ providerUnknown: 'Unknown provider: {{provider}}',
+ providerStartOAuthFailed: 'Failed to start OAuth',
+ providerCallbackFailed: 'Callback submission failed',
+ providerSubmitCallbackFailed: 'Failed to submit callback',
loggingConfigSaved: 'Logging configuration saved.',
loggingConfigSaveFailed: 'Failed to save logging configuration.',
unifiedConfigUpdated: 'Configuration updated successfully',
@@ -2039,6 +2154,10 @@ const resources = {
legacyConfirmFailed: 'Legacy account "{{name}}" failed confirmation: {{error}}',
legacyConfirmSuccess_one: '{{count}} legacy account confirmed',
legacyConfirmSuccess_other: '{{count}} legacy accounts confirmed',
+ legacyConfirmPartial:
+ 'Confirmed {{updated}} legacy account(s), but {{failed}} update(s) failed. Refreshed account state.',
+ legacyConfirmAllFailed:
+ 'Failed to confirm {{failed}} legacy account(s). Refreshed account state.',
noLegacyAccounts: 'No legacy accounts need confirmation',
routingStrategySet: 'Routing strategy set to {{strategy}}',
variantCreated: 'Variant created successfully',
@@ -3315,6 +3434,8 @@ const resources = {
},
apiProfiles: {
title: 'API 配置',
+ sidebarTitle: 'API 配置',
+ sidebarSubtitle: '高级 API、本地运行时、自定义端点',
new: '新建',
searchPlaceholder: '搜索配置...',
loadingProfiles: '正在加载配置...',
@@ -3334,6 +3455,19 @@ const resources = {
unsavedChangesTitle: '未保存的更改',
unsavedChangesDesc: '你在“{{current}}”中有未保存内容,是否丢弃并切换到“{{next}}”?',
discardSwitch: '丢弃并切换',
+ noOrphansFound: '未发现孤立配置',
+ confirmRegisterOrphans:
+ '发现 {{total}} 个孤立配置文件,其中 {{valid}} 个可直接注册。现在注册吗?',
+ registeredWithSkipped: '(跳过 {{count}} 个)',
+ registeredProfiles: '已注册 {{count}} 个配置',
+ copyPrompt: '将“{{name}}”复制为哪个新配置名称?',
+ destinationEmpty: '目标配置名称不能为空',
+ exportRedacted:
+ '导出内容中的令牌已脱敏。如需包含密钥,请改用 CLI 的 include-secrets 流程。',
+ exportDownloaded: '配置导出已下载',
+ importFailed: '导入配置包失败',
+ discoverOrphans: '发现孤立配置',
+ importProfileBundle: '导入配置包',
},
accountsPage: {
title: '账号',
@@ -3812,6 +3946,11 @@ const resources = {
fiveHourResets: '5 小时重置于 {{time}}',
weeklyResets: '每周重置于 {{time}}',
},
+ utils: {
+ codeReview5h: '代码审查(5 小时)',
+ codeReviewWeekly: '代码审查(每周)',
+ codeReview: '代码审查',
+ },
sponsorButton: {
title: '在 GitHub 上赞助此项目',
sponsor: '赞助',
@@ -3868,6 +4007,8 @@ const resources = {
conflictTitle: '文件被外部修改',
conflictDesc: '此设置文件已被其他进程修改。用你的更改覆盖还是丢弃?',
overwrite: '覆盖',
+ invalidJson: 'JSON 无效',
+ failedSave: '保存失败',
},
setupWizard: {
title: '快速设置向导',
@@ -4131,6 +4272,90 @@ const resources = {
configYaml: 'config.yaml',
flow: '工作流',
docsTab: '文档',
+ features: '功能',
+ featuresDesc: '切换 CCS 可安全管理的 Codex 功能开关。',
+ configOnlyFlags: '仅配置文件中的现有开关',
+ configOnlyFlagsDesc:
+ '这些功能键已存在于你的 `config.toml` 中,因此 CCS 可以显示它们,而不必宣称自己覆盖了完整目录。',
+ existing: '已存在',
+ nonBooleanForm: '该值以非布尔形式保存。如果需要编辑,请使用原始 TOML。',
+ discoveredFromFile: '这是从当前文件中发现的,而不是来自 CCS 内置目录。',
+ featureMultiAgentLabel: 'Multi-agent',
+ featureMultiAgentDesc: '启用子代理协作工具。',
+ featureUnifiedExecLabel: 'Unified exec',
+ featureUnifiedExecDesc: '使用基于 PTY 的统一 exec 工具。',
+ featureShellSnapshotLabel: 'Shell snapshot',
+ featureShellSnapshotDesc: '复用 shell 环境快照。',
+ featureApplyPatchLabel: 'Apply patch',
+ featureApplyPatchDesc: '启用自由格式 apply_patch 编辑。',
+ featureJsReplLabel: 'JS REPL',
+ featureJsReplDesc: '启用基于 Node 的 JavaScript REPL。',
+ featureRuntimeMetricsLabel: 'Runtime metrics',
+ featureRuntimeMetricsDesc: '收集 Codex 运行时指标。',
+ featurePreventIdleSleepLabel: 'Prevent idle sleep',
+ featurePreventIdleSleepDesc: '保持机器在运行期间不休眠。',
+ featureFastModeLabel: 'Fast mode',
+ featureFastModeDesc: '允许使用快速服务层路径。',
+ featureAppsLabel: 'Apps',
+ featureAppsDesc: '启用 ChatGPT Apps 和连接器支持。',
+ featureSmartApprovalsLabel: 'Smart approvals',
+ featureSmartApprovalsDesc: '将符合条件的审批交给 guardian 流程处理。',
+ howCodexWorks: 'Codex 在 CCS 中的工作方式',
+ nativeConfigLabel: '原生配置:',
+ nativeConfigDesc: 'ccs-codex 和 ccsx 会使用你保存的默认值启动原生 Codex。',
+ transientOverridesLabel: '临时覆盖:',
+ transientOverridesDesc: 'ccsxp(或 ccs codex --target codex)会使用 CCS 提供商快捷方式。',
+ cliproxyDefaultLabel: 'CLIProxy 默认:',
+ cliproxyDefaultDesc:
+ '如果你想让普通的 codex 走 CLIProxy,请设置 model_provider = "cliproxy",并加入下面的配方。',
+ runtimeInstall: '运行时安装',
+ detected: '已检测到',
+ notFound: '未找到',
+ detectionSource: '检测来源',
+ binaryPath: '二进制路径',
+ installDirectory: '安装目录',
+ versionLabel: '版本',
+ nativeAliases: '原生别名',
+ ccsProviderShortcut: 'CCS 提供商快捷方式',
+ configOverrideSupport: '--config 覆盖支持',
+ available: '可用',
+ missing: '缺失',
+ cliproxyNativeCodex: '由 CLIProxy 支持的原生 Codex',
+ builtInLabel: '内置:',
+ builtInCcsxpDesc: '使用 ccsxp 作为 CCS 提供商快捷方式。',
+ nativeRecipeLabel: '原生:',
+ nativeRecipeDesc: '按下面的配方配置,即可让 codex 直接走 CLIProxy。',
+ saveProviderNamedCliproxy:
+ '保存一个名为 cliproxy 的 provider,并填入上面的 base URL 与 env key。',
+ inTopLevelSetDefault: '在顶层设置中,将 Default provider 设为 cliproxy。',
+ exportCliproxyApiKey: '在启动原生 Codex 之前,在 shell 中导出 CLIPROXY_API_KEY。',
+ noConfigOverrides:
+ '这个 Codex 版本仍然可以走原生路径,但在检测到的二进制支持 --config 覆盖之前,ccsxp 或 ccs codex --target codex 这样的 CCS 路由不可用。',
+ configFile: '配置文件',
+ path: '路径',
+ resolved: '已解析',
+ size: '大小',
+ lastModified: '最后修改',
+ tomlWarning: 'TOML 警告',
+ readWarning: '读取警告',
+ currentUserLayerSummary: '当前用户层摘要',
+ activeProfile: '当前激活的配置',
+ notSet: '未设置',
+ providersCount: 'providers: {{count}}',
+ profilesCount: 'profiles: {{count}}',
+ enabledFeaturesCount: 'enabled features: {{count}}',
+ mcpServersCount: 'mcp servers: {{count}}',
+ userLayerKeysPresent: '已存在的用户层键',
+ nativeShortAlias: '原生短别名',
+ ccsCodexShortcut: 'CCS Codex 快捷方式',
+ explicitProviderRoute: '显式 provider 路径',
+ runtimeVsProvider: '运行时与 provider 的区别',
+ honorsSavedNativeConfig: '遵循已保存的原生用户配置',
+ usesTransientOverrides: '使用临时覆盖',
+ unavailableNoConfig: '不可用(当前 Codex 版本缺少 --config 支持)。',
+ yes: '是',
+ no: '否',
+ warningsTitle: '警告',
},
droidSettings: {
quickControls: '快捷控制',
@@ -4234,6 +4459,13 @@ const resources = {
syncFailed: '同步失败:{{error}}',
providerAuthSuccess: '{{provider}} 认证成功',
providerDeviceCodeInCallback: '提供商在回调模式中返回了设备码流程',
+ providerAuthTimeout: '认证超时,请重试。',
+ providerAccountRegistrationFailed: '已认证账号无法注册',
+ providerLostStatusEndpoint: '与认证状态端点失去连接',
+ providerUnknown: '未知提供商:{{provider}}',
+ providerStartOAuthFailed: '启动 OAuth 失败',
+ providerCallbackFailed: '回调提交失败',
+ providerSubmitCallbackFailed: '提交回调失败',
loggingConfigSaved: '日志配置已保存。',
loggingConfigSaveFailed: '保存日志配置失败。',
unifiedConfigUpdated: '配置更新成功',
@@ -4250,6 +4482,9 @@ const resources = {
legacyConfirmFailed: '旧版账号「{{name}}」确认失败:{{error}}',
legacyConfirmSuccess_one: '{{count}} 个旧版账号已确认',
legacyConfirmSuccess_other: '{{count}} 个旧版账号已确认',
+ legacyConfirmPartial:
+ '已确认 {{updated}} 个旧版账号,但有 {{failed}} 个更新失败。账号状态已刷新。',
+ legacyConfirmAllFailed: '确认 {{failed}} 个旧版账号失败。账号状态已刷新。',
noLegacyAccounts: '没有需要确认的旧版账号',
routingStrategySet: '路由策略已设为 {{strategy}}',
variantCreated: '变体创建成功',
@@ -5604,6 +5839,8 @@ const resources = {
},
apiProfiles: {
title: 'Hồ sơ API',
+ sidebarTitle: 'Hồ sơ API',
+ sidebarSubtitle: 'API cao cấp, runtime cục bộ, endpoint tùy chỉnh',
new: 'Mới',
searchPlaceholder: 'Tìm kiếm hồ sơ...',
loadingProfiles: 'Đang tải hồ sơ...',
@@ -5625,6 +5862,18 @@ const resources = {
unsavedChangesDesc:
'Bạn có những thay đổi chưa được lưu trong "{{current}}". Hủy và chuyển sang "{{next}}"?',
discardSwitch: 'Loại bỏ và chuyển đổi',
+ noOrphansFound: 'Không tìm thấy cấu hình hồ sơ mồ côi',
+ confirmRegisterOrphans:
+ 'Đã tìm thấy {{total}} tệp cấu hình hồ sơ mồ côi, trong đó {{valid}} tệp sẵn sàng đăng ký. Đăng ký ngay?',
+ registeredWithSkipped: ' (bỏ qua {{count}})',
+ registeredProfiles: 'Đã đăng ký {{count}} hồ sơ',
+ copyPrompt: 'Sao chép "{{name}}" sang tên hồ sơ mới nào?',
+ destinationEmpty: 'Tên hồ sơ đích không được để trống',
+ exportRedacted: 'Bản xuất đã được che token. Dùng luồng include-secrets trong CLI nếu cần.',
+ exportDownloaded: 'Đã tải xuống bản xuất hồ sơ',
+ importFailed: 'Không nhập được gói hồ sơ',
+ discoverOrphans: 'Phát hiện hồ sơ mồ côi',
+ importProfileBundle: 'Nhập gói hồ sơ',
},
accountsPage: {
title: 'Tài khoản',
@@ -6116,6 +6365,11 @@ const resources = {
fiveHourResets: 'Reset 5h lúc {{time}}',
weeklyResets: 'Reset hàng tuần lúc {{time}}',
},
+ utils: {
+ codeReview5h: 'Đánh giá mã (5h)',
+ codeReviewWeekly: 'Đánh giá mã (hàng tuần)',
+ codeReview: 'Đánh giá mã',
+ },
sponsorButton: {
title: 'Tài trợ dự án này trên GitHub',
sponsor: 'Tài trợ',
@@ -6173,6 +6427,8 @@ const resources = {
conflictDesc:
'Tệp cài đặt này đã bị thay đổi bởi một tiến trình khác. Ghi đè thay đổi của bạn hay hủy?',
overwrite: 'Ghi đè',
+ invalidJson: 'JSON không hợp lệ',
+ failedSave: 'Không lưu được',
},
setupWizard: {
title: 'Trình thiết lập nhanh',
@@ -6438,6 +6694,91 @@ const resources = {
configYaml: 'config.yaml',
flow: 'Luồng',
docsTab: 'Tài liệu',
+ features: 'Tính năng',
+ featuresDesc: 'Bật/tắt các cờ tính năng Codex mà CCS có thể quản lý an toàn.',
+ configOnlyFlags: 'Cờ chỉ có trong cấu hình',
+ configOnlyFlagsDesc:
+ 'Các khóa này đã tồn tại trong `config.toml`, nên CCS có thể hiển thị chúng mà không cần tuyên bố bao phủ toàn bộ danh mục.',
+ existing: 'đã có',
+ nonBooleanForm:
+ 'Giá trị đang được lưu ở dạng không phải boolean. Hãy dùng TOML thô nếu bạn muốn chỉnh sửa.',
+ discoveredFromFile: 'Được phát hiện từ tệp hiện tại thay vì từ danh mục tích hợp của CCS.',
+ featureMultiAgentLabel: 'Multi-agent',
+ featureMultiAgentDesc: 'Bật các công cụ cộng tác giữa subagent.',
+ featureUnifiedExecLabel: 'Unified exec',
+ featureUnifiedExecDesc: 'Dùng công cụ exec hợp nhất dựa trên PTY.',
+ featureShellSnapshotLabel: 'Shell snapshot',
+ featureShellSnapshotDesc: 'Tái sử dụng snapshot môi trường shell.',
+ featureApplyPatchLabel: 'Apply patch',
+ featureApplyPatchDesc: 'Bật chỉnh sửa apply_patch dạng tự do.',
+ featureJsReplLabel: 'JS REPL',
+ featureJsReplDesc: 'Bật JavaScript REPL dựa trên Node.',
+ featureRuntimeMetricsLabel: 'Runtime metrics',
+ featureRuntimeMetricsDesc: 'Thu thập chỉ số runtime của Codex.',
+ featurePreventIdleSleepLabel: 'Prevent idle sleep',
+ featurePreventIdleSleepDesc: 'Giữ máy không ngủ khi đang hoạt động.',
+ featureFastModeLabel: 'Fast mode',
+ featureFastModeDesc: 'Cho phép dùng đường dẫn fast service tier.',
+ featureAppsLabel: 'Apps',
+ featureAppsDesc: 'Bật hỗ trợ ChatGPT Apps và connectors.',
+ featureSmartApprovalsLabel: 'Smart approvals',
+ featureSmartApprovalsDesc: 'Đưa các phê duyệt phù hợp qua luồng guardian.',
+ howCodexWorks: 'Codex hoạt động trong CCS như thế nào',
+ nativeConfigLabel: 'Cấu hình gốc:',
+ nativeConfigDesc: 'ccs-codex và ccsx khởi chạy Codex gốc bằng các mặc định bạn đã lưu.',
+ transientOverridesLabel: 'Ghi đè tạm thời:',
+ transientOverridesDesc:
+ 'ccsxp (hoặc ccs codex --target codex) dùng đường tắt nhà cung cấp CCS.',
+ cliproxyDefaultLabel: 'Mặc định CLIProxy:',
+ cliproxyDefaultDesc:
+ 'Để lệnh codex thông thường đi qua CLIProxy, hãy đặt model_provider = "cliproxy" và dùng công thức bên dưới.',
+ runtimeInstall: 'Cài đặt runtime',
+ detected: 'Đã phát hiện',
+ notFound: 'Không tìm thấy',
+ detectionSource: 'Nguồn phát hiện',
+ binaryPath: 'Đường dẫn binary',
+ installDirectory: 'Thư mục cài đặt',
+ versionLabel: 'Phiên bản',
+ nativeAliases: 'Bí danh gốc',
+ ccsProviderShortcut: 'Lối tắt nhà cung cấp CCS',
+ configOverrideSupport: 'Hỗ trợ ghi đè --config',
+ available: 'Có sẵn',
+ missing: 'Thiếu',
+ cliproxyNativeCodex: 'Codex gốc dùng CLIProxy',
+ builtInLabel: 'Tích hợp:',
+ builtInCcsxpDesc: 'Dùng ccsxp cho lối tắt nhà cung cấp CCS.',
+ nativeRecipeLabel: 'Gốc:',
+ nativeRecipeDesc: 'Cấu hình công thức bên dưới để dùng CLIProxy trực tiếp với codex.',
+ saveProviderNamedCliproxy: 'Lưu một provider tên cliproxy với base URL và env key ở trên.',
+ inTopLevelSetDefault: 'Trong Top-level settings, đặt Default provider là cliproxy.',
+ exportCliproxyApiKey: 'Xuất CLIPROXY_API_KEY trong shell trước khi chạy Codex gốc.',
+ noConfigOverrides:
+ 'Bản Codex này vẫn có thể dùng đường gốc, nhưng định tuyến Codex qua CCS như ccsxp hoặc ccs codex --target codex sẽ không hoạt động cho đến khi binary hỗ trợ ghi đè --config.',
+ configFile: 'Tệp cấu hình',
+ path: 'Đường dẫn',
+ resolved: 'Đã phân giải',
+ size: 'Kích thước',
+ lastModified: 'Sửa đổi lần cuối',
+ tomlWarning: 'Cảnh báo TOML',
+ readWarning: 'Cảnh báo đọc',
+ currentUserLayerSummary: 'Tóm tắt lớp người dùng hiện tại',
+ activeProfile: 'Hồ sơ đang dùng',
+ notSet: 'Chưa đặt',
+ providersCount: 'providers: {{count}}',
+ profilesCount: 'profiles: {{count}}',
+ enabledFeaturesCount: 'enabled features: {{count}}',
+ mcpServersCount: 'mcp servers: {{count}}',
+ userLayerKeysPresent: 'Các khóa lớp người dùng hiện có',
+ nativeShortAlias: 'Bí danh gốc ngắn',
+ ccsCodexShortcut: 'Lối tắt CCS Codex',
+ explicitProviderRoute: 'Tuyến provider tường minh',
+ runtimeVsProvider: 'Runtime so với provider',
+ honorsSavedNativeConfig: 'Tuân theo cấu hình gốc đã lưu',
+ usesTransientOverrides: 'Dùng ghi đè tạm thời',
+ unavailableNoConfig: 'Không khả dụng (bản Codex này thiếu hỗ trợ --config).',
+ yes: 'Có',
+ no: 'Không',
+ warningsTitle: 'Cảnh báo',
},
droidSettings: {
quickControls: 'Điều khiển nhanh',
@@ -6541,6 +6882,13 @@ const resources = {
syncFailed: 'Đồng bộ thất bại: {{error}}',
providerAuthSuccess: 'Xác thực {{provider}} thành công',
providerDeviceCodeInCallback: 'Nhà cung cấp trả về Device Code flow trong chế độ callback',
+ providerAuthTimeout: 'Đã hết thời gian xác thực. Vui lòng thử lại.',
+ providerAccountRegistrationFailed: 'Không thể đăng ký tài khoản đã xác thực',
+ providerLostStatusEndpoint: 'Mất kết nối với điểm cuối trạng thái xác thực',
+ providerUnknown: 'Nhà cung cấp không xác định: {{provider}}',
+ providerStartOAuthFailed: 'Không thể bắt đầu OAuth',
+ providerCallbackFailed: 'Gửi callback thất bại',
+ providerSubmitCallbackFailed: 'Không thể gửi callback',
loggingConfigSaved: 'Đã lưu cấu hình ghi nhật ký.',
loggingConfigSaveFailed: 'Không lưu được cấu hình ghi nhật ký.',
unifiedConfigUpdated: 'Đã cập nhật cấu hình thành công',
@@ -6558,6 +6906,10 @@ const resources = {
legacyConfirmFailed: 'Tài khoản cũ "{{name}}" xác nhận thất bại: {{error}}',
legacyConfirmSuccess_one: 'Đã xác nhận {{count}} tài khoản cũ',
legacyConfirmSuccess_other: 'Đã xác nhận {{count}} tài khoản cũ',
+ legacyConfirmPartial:
+ 'Đã xác nhận {{updated}} tài khoản cũ, nhưng {{failed}} lần cập nhật thất bại. Trạng thái tài khoản đã được làm mới.',
+ legacyConfirmAllFailed:
+ 'Không thể xác nhận {{failed}} tài khoản cũ. Trạng thái tài khoản đã được làm mới.',
noLegacyAccounts: 'Không có tài khoản cũ nào cần xác nhận',
routingStrategySet: 'Chiến lược định tuyến đã đặt thành {{strategy}}',
variantCreated: 'Đã tạo biến thể thành công',
@@ -7921,6 +8273,8 @@ const resources = {
},
apiProfiles: {
title: 'API プロファイル',
+ sidebarTitle: 'API プロファイル',
+ sidebarSubtitle: 'プレミアム API、ローカルランタイム、カスタムエンドポイント',
new: '新規',
searchPlaceholder: 'プロファイルを検索...',
loadingProfiles: 'プロファイルを読み込み中...',
@@ -7942,6 +8296,19 @@ const resources = {
unsavedChangesDesc:
'"{{current}}" に未保存の変更があります。破棄して "{{next}}" に切り替えますか?',
discardSwitch: '破棄して切り替え',
+ noOrphansFound: '孤立したプロファイル設定は見つかりませんでした',
+ confirmRegisterOrphans:
+ '{{total}} 件の孤立したプロファイル設定ファイルが見つかりました。うち {{valid}} 件は登録可能です。今すぐ登録しますか?',
+ registeredWithSkipped: '({{count}} 件スキップ)',
+ registeredProfiles: '{{count}} 件のプロファイルを登録しました',
+ copyPrompt: '「{{name}}」をどの新しいプロファイル名にコピーしますか?',
+ destinationEmpty: 'コピー先のプロファイル名は空にできません',
+ exportRedacted:
+ 'トークンをマスクした状態でエクスポートしました。必要なら CLI の include-secrets フローを使ってください。',
+ exportDownloaded: 'プロファイルのエクスポートをダウンロードしました',
+ importFailed: 'プロファイルバンドルをインポートできませんでした',
+ discoverOrphans: '孤立プロファイルを検出',
+ importProfileBundle: 'プロファイルバンドルをインポート',
},
accountsPage: {
title: 'アカウント',
@@ -8504,6 +8871,93 @@ const resources = {
configYaml: 'config.yaml',
flow: 'フロー',
docsTab: 'ドキュメント',
+ features: '機能',
+ featuresDesc: 'CCS が安全に管理できる Codex の機能フラグを切り替えます。',
+ configOnlyFlags: '設定ファイル内のみの既存フラグ',
+ configOnlyFlagsDesc:
+ 'これらのキーはすでに `config.toml` に存在するため、CCS は完全なカタログ対応を主張せずに表示できます。',
+ existing: '既存',
+ nonBooleanForm:
+ '非 boolean 形式で保存されています。編集する場合は Raw TOML を使用してください。',
+ discoveredFromFile: 'CCS の内蔵カタログではなく現在のファイルから検出されました。',
+ featureMultiAgentLabel: 'Multi-agent',
+ featureMultiAgentDesc: 'subagent 連携ツールを有効にします。',
+ featureUnifiedExecLabel: 'Unified exec',
+ featureUnifiedExecDesc: 'PTY ベースの unified exec ツールを使用します。',
+ featureShellSnapshotLabel: 'Shell snapshot',
+ featureShellSnapshotDesc: 'shell 環境スナップショットを再利用します。',
+ featureApplyPatchLabel: 'Apply patch',
+ featureApplyPatchDesc: '自由形式の apply_patch 編集を有効にします。',
+ featureJsReplLabel: 'JS REPL',
+ featureJsReplDesc: 'Node ベースの JavaScript REPL を有効にします。',
+ featureRuntimeMetricsLabel: 'Runtime metrics',
+ featureRuntimeMetricsDesc: 'Codex のランタイム指標を収集します。',
+ featurePreventIdleSleepLabel: 'Prevent idle sleep',
+ featurePreventIdleSleepDesc: '動作中にマシンがスリープしないようにします。',
+ featureFastModeLabel: 'Fast mode',
+ featureFastModeDesc: '高速サービス階層パスを許可します。',
+ featureAppsLabel: 'Apps',
+ featureAppsDesc: 'ChatGPT Apps と connector のサポートを有効にします。',
+ featureSmartApprovalsLabel: 'Smart approvals',
+ featureSmartApprovalsDesc: '対象となる承認を guardian フローに回します。',
+ howCodexWorks: 'CCS での Codex の動作',
+ nativeConfigLabel: 'ネイティブ設定:',
+ nativeConfigDesc: 'ccs-codex と ccsx は保存済みデフォルトでネイティブ Codex を起動します。',
+ transientOverridesLabel: '一時オーバーライド:',
+ transientOverridesDesc:
+ 'ccsxp(または ccs codex --target codex)は CCS プロバイダーのショートカットを使います。',
+ cliproxyDefaultLabel: 'CLIProxy デフォルト:',
+ cliproxyDefaultDesc:
+ '通常の codex を CLIProxy 経由にするには、model_provider = "cliproxy" を設定し、下のレシピを追加します。',
+ runtimeInstall: 'ランタイムのインストール',
+ detected: '検出済み',
+ notFound: '見つかりません',
+ detectionSource: '検出元',
+ binaryPath: 'バイナリパス',
+ installDirectory: 'インストール先',
+ versionLabel: 'バージョン',
+ nativeAliases: 'ネイティブ別名',
+ ccsProviderShortcut: 'CCS プロバイダーショートカット',
+ configOverrideSupport: '--config オーバーライド対応',
+ available: '利用可能',
+ missing: '不足',
+ cliproxyNativeCodex: 'CLIProxy 経由のネイティブ Codex',
+ builtInLabel: '内蔵:',
+ builtInCcsxpDesc: 'CCS プロバイダーショートカットとして ccsxp を使用します。',
+ nativeRecipeLabel: 'ネイティブ:',
+ nativeRecipeDesc: '下のレシピを設定すると codex を直接 CLIProxy 経由で利用できます。',
+ saveProviderNamedCliproxy:
+ '上の base URL と env key を使って cliproxy という名前の provider を保存します。',
+ inTopLevelSetDefault: 'Top-level settings で Default provider を cliproxy に設定します。',
+ exportCliproxyApiKey:
+ 'ネイティブ Codex を起動する前に shell で CLIPROXY_API_KEY を export してください。',
+ noConfigOverrides:
+ 'この Codex ビルドでもネイティブパスは使えますが、ccsxp や ccs codex --target codex などの CCS 側ルーティングは、検出された Codex バイナリが --config オーバーライドに対応するまで利用できません。',
+ configFile: '設定ファイル',
+ path: 'パス',
+ resolved: '解決先',
+ size: 'サイズ',
+ lastModified: '最終更新',
+ tomlWarning: 'TOML 警告',
+ readWarning: '読み込み警告',
+ currentUserLayerSummary: '現在のユーザーレイヤー概要',
+ activeProfile: 'アクティブプロファイル',
+ notSet: '未設定',
+ providersCount: 'providers: {{count}}',
+ profilesCount: 'profiles: {{count}}',
+ enabledFeaturesCount: 'enabled features: {{count}}',
+ mcpServersCount: 'mcp servers: {{count}}',
+ userLayerKeysPresent: '現在存在するユーザーレイヤーキー',
+ nativeShortAlias: 'ネイティブ短縮エイリアス',
+ ccsCodexShortcut: 'CCS Codex ショートカット',
+ explicitProviderRoute: '明示的な provider ルート',
+ runtimeVsProvider: 'ランタイムと provider の違い',
+ honorsSavedNativeConfig: '保存済みのネイティブ設定を尊重',
+ usesTransientOverrides: '一時オーバーライドを使用',
+ unavailableNoConfig: '利用不可(この Codex ビルドには --config サポートがありません)。',
+ yes: 'はい',
+ no: 'いいえ',
+ warningsTitle: '警告',
},
codexPage: {
title: 'Codex',
@@ -8913,6 +9367,11 @@ const resources = {
fiveHourResets: '5時間リセット {{time}}',
weeklyResets: '週間リセット {{time}}',
},
+ utils: {
+ codeReview5h: 'コードレビュー(5時間)',
+ codeReviewWeekly: 'コードレビュー(週次)',
+ codeReview: 'コードレビュー',
+ },
rawEditorSection: {
rawConfig: 'Raw 設定',
},
@@ -8946,6 +9405,8 @@ const resources = {
conflictDesc:
'この設定ファイルは別のプロセスで変更されました。変更で上書きしますか?それとも破棄しますか?',
overwrite: '上書き',
+ invalidJson: '無効な JSON です',
+ failedSave: '保存に失敗しました',
},
settingsPage: {
title: '設定',
@@ -9080,6 +9541,13 @@ const resources = {
providerAuthSuccess: '{{provider}} の認証に成功しました',
providerDeviceCodeInCallback:
'コールバックモードでプロバイダーがデバイスコードフローを返しました',
+ providerAuthTimeout: '認証がタイムアウトしました。もう一度お試しください。',
+ providerAccountRegistrationFailed: '認証済みアカウントを登録できませんでした',
+ providerLostStatusEndpoint: '認証ステータスエンドポイントとの接続が失われました',
+ providerUnknown: '不明なプロバイダーです: {{provider}}',
+ providerStartOAuthFailed: 'OAuth を開始できませんでした',
+ providerCallbackFailed: 'コールバック送信に失敗しました',
+ providerSubmitCallbackFailed: 'コールバックを送信できませんでした',
loggingConfigSaved: 'ログ設定を保存しました。',
loggingConfigSaveFailed: 'ログ設定の保存に失敗しました。',
unifiedConfigUpdated: '設定を更新しました',
@@ -9097,6 +9565,10 @@ const resources = {
legacyConfirmFailed: 'レガシーアカウント「{{name}}」の確認に失敗しました: {{error}}',
legacyConfirmSuccess_one: '{{count}} 件のレガシーアカウントを確認しました',
legacyConfirmSuccess_other: '{{count}} 件のレガシーアカウントを確認しました',
+ legacyConfirmPartial:
+ '{{updated}} 件のレガシーアカウントを確認しましたが、{{failed}} 件の更新に失敗しました。アカウント状態を再取得しました。',
+ legacyConfirmAllFailed:
+ '{{failed}} 件のレガシーアカウントの確認に失敗しました。アカウント状態を再取得しました。',
noLegacyAccounts: '確認が必要なレガシーアカウントはありません',
routingStrategySet: 'ルーティング戦略を {{strategy}} に設定しました',
variantCreated: 'バリアントを作成しました',
diff --git a/ui/src/lib/support-updates-catalog.ts b/ui/src/lib/support-updates-catalog.ts
index ac248b36..e52be324 100644
--- a/ui/src/lib/support-updates-catalog.ts
+++ b/ui/src/lib/support-updates-catalog.ts
@@ -1,3 +1,5 @@
+import i18n from '@/lib/i18n';
+
export type SupportStatus = 'new' | 'stable' | 'planned';
export type SupportScope = 'target' | 'cliproxy' | 'api-profiles' | 'websearch';
@@ -47,14 +49,14 @@ export interface CliSupportEntry {
notes?: string;
}
-export const SUPPORT_SCOPE_LABELS: Record = {
- target: 'Target CLI', // TODO i18n: missing key for support scope target
- cliproxy: 'CLIProxy Provider', // TODO i18n: missing key for support scope cliproxy
- 'api-profiles': 'API Profile', // TODO i18n: missing key for support scope api-profiles
- websearch: 'WebSearch', // TODO i18n: missing key for support scope websearch
+const BASE_SUPPORT_SCOPE_LABELS: Record = {
+ target: 'Target CLI',
+ cliproxy: 'CLIProxy Provider',
+ 'api-profiles': 'API Profile',
+ websearch: 'WebSearch',
};
-export const SUPPORT_NOTICES: SupportNotice[] = [
+const BASE_SUPPORT_NOTICES: SupportNotice[] = [
{
id: 'codex-target-runtime-support',
title: 'Native Codex runtime support is live',
@@ -204,7 +206,7 @@ export const SUPPORT_NOTICES: SupportNotice[] = [
},
];
-export const CLI_SUPPORT_ENTRIES: CliSupportEntry[] = [
+const BASE_CLI_SUPPORT_ENTRIES: CliSupportEntry[] = [
{
id: 'claude-target',
name: 'Claude Code',
@@ -348,20 +350,86 @@ export const CLI_SUPPORT_ENTRIES: CliSupportEntry[] = [
},
];
-const SUPPORT_ENTRY_LOOKUP = new Map(CLI_SUPPORT_ENTRIES.map((entry) => [entry.id, entry]));
+function tx(key: string, defaultValue: string, options?: Record): string {
+ return i18n.t(key, { defaultValue, ...options });
+}
+
+export function getSupportScopeLabels(): Record {
+ return {
+ target: tx('supportCatalog.scope.target', BASE_SUPPORT_SCOPE_LABELS.target),
+ cliproxy: tx('supportCatalog.scope.cliproxy', BASE_SUPPORT_SCOPE_LABELS.cliproxy),
+ 'api-profiles': tx(
+ 'supportCatalog.scope.apiProfiles',
+ BASE_SUPPORT_SCOPE_LABELS['api-profiles']
+ ),
+ websearch: tx('supportCatalog.scope.websearch', BASE_SUPPORT_SCOPE_LABELS.websearch),
+ };
+}
+
+function localizeSupportNotice(notice: SupportNotice): SupportNotice {
+ return {
+ ...notice,
+ title: tx(`supportCatalog.notice.${notice.id}.title`, notice.title),
+ summary: tx(`supportCatalog.notice.${notice.id}.summary`, notice.summary),
+ primaryAction: tx(`supportCatalog.notice.${notice.id}.primaryAction`, notice.primaryAction),
+ highlights: notice.highlights.map((highlight, index) =>
+ tx(`supportCatalog.notice.${notice.id}.highlight.${index}`, highlight)
+ ),
+ actions: notice.actions.map((action) => ({
+ ...action,
+ label: tx(`supportCatalog.notice.${notice.id}.action.${action.id}.label`, action.label),
+ description: tx(
+ `supportCatalog.notice.${notice.id}.action.${action.id}.description`,
+ action.description
+ ),
+ })),
+ routes: notice.routes.map((route, index) => ({
+ ...route,
+ label: tx(`supportCatalog.notice.${notice.id}.route.${index}.label`, route.label),
+ })),
+ };
+}
+
+function localizeCliSupportEntry(entry: CliSupportEntry): CliSupportEntry {
+ return {
+ ...entry,
+ name: tx(`supportCatalog.entry.${entry.id}.name`, entry.name),
+ summary: tx(`supportCatalog.entry.${entry.id}.summary`, entry.summary),
+ pillars: {
+ baseUrl: tx(`supportCatalog.entry.${entry.id}.pillar.baseUrl`, entry.pillars.baseUrl),
+ auth: tx(`supportCatalog.entry.${entry.id}.pillar.auth`, entry.pillars.auth),
+ model: tx(`supportCatalog.entry.${entry.id}.pillar.model`, entry.pillars.model),
+ },
+ routes: entry.routes.map((route, index) => ({
+ ...route,
+ label: tx(`supportCatalog.entry.${entry.id}.route.${index}.label`, route.label),
+ })),
+ notes: entry.notes ? tx(`supportCatalog.entry.${entry.id}.notes`, entry.notes) : entry.notes,
+ };
+}
+
+export function getSupportNotices(): SupportNotice[] {
+ return BASE_SUPPORT_NOTICES.map(localizeSupportNotice);
+}
+
+export function getCliSupportEntries(): CliSupportEntry[] {
+ return BASE_CLI_SUPPORT_ENTRIES.map(localizeCliSupportEntry);
+}
export function getSupportEntriesForNotice(notice: SupportNotice): CliSupportEntry[] {
+ const supportEntryLookup = new Map(getCliSupportEntries().map((entry) => [entry.id, entry]));
return notice.entryIds
- .map((entryId) => SUPPORT_ENTRY_LOOKUP.get(entryId))
+ .map((entryId) => supportEntryLookup.get(entryId))
.filter((entry): entry is CliSupportEntry => Boolean(entry));
}
export function getLatestSupportNotice(): SupportNotice | null {
- if (SUPPORT_NOTICES.length === 0) {
+ const notices = getSupportNotices();
+ if (notices.length === 0) {
return null;
}
- return [...SUPPORT_NOTICES].sort((a, b) => b.publishedAt.localeCompare(a.publishedAt))[0];
+ return [...notices].sort((a, b) => b.publishedAt.localeCompare(a.publishedAt))[0];
}
export function formatCatalogDate(value: string): string {
diff --git a/ui/src/pages/api.tsx b/ui/src/pages/api.tsx
index 74cb842b..532126ad 100644
--- a/ui/src/pages/api.tsx
+++ b/ui/src/pages/api.tsx
@@ -38,10 +38,6 @@ import type { ProviderPreset } from '@/lib/provider-presets';
import { cn } from '@/lib/utils';
import { CopyButton } from '@/components/ui/copy-button';
import { useTranslation } from 'react-i18next';
-// TODO i18n: missing keys for apiProfiles: noOrphansFound, confirmRegisterOrphans,
-// registeredWithSkipped, registeredProfiles, copyPrompt, destinationEmpty,
-// exportRedacted, exportDownloaded, importFailed, sidebarTitle, sidebarSubtitle,
-// discoverOrphans, importProfileBundle
import { toast } from 'sonner';
import { useNavigate } from 'react-router-dom';
diff --git a/ui/src/pages/claude-extension.tsx b/ui/src/pages/claude-extension.tsx
index 9e20d896..251292b1 100644
--- a/ui/src/pages/claude-extension.tsx
+++ b/ui/src/pages/claude-extension.tsx
@@ -227,7 +227,10 @@ function TargetStatusCard({
- {status?.message || 'Verify the binding after saving to inspect the current file state.'}
+ {status?.message ||
+ t('claudeExtensionPage.verifyAfterSaving', {
+ defaultValue: 'Verify the binding after saving to inspect the current file state.',
+ })}
@@ -374,7 +377,15 @@ export function ClaudeExtensionPage() {
async function handleDelete(): Promise
{
if (!effectiveSelectedBindingId || !selectedBinding) return;
- if (!window.confirm(`Delete binding "${selectedBinding.name}"?`)) return;
+ if (
+ !window.confirm(
+ t('claudeExtensionPage.deleteBindingConfirm', {
+ name: selectedBinding.name,
+ defaultValue: 'Delete binding "{{name}}"?',
+ })
+ )
+ )
+ return;
await deleteBinding.mutateAsync(effectiveSelectedBindingId);
const remaining = bindings.filter((binding) => binding.id !== effectiveSelectedBindingId);
@@ -419,8 +430,9 @@ export function ClaudeExtensionPage() {
{t('claudeExtensionPage.title')}
- {/* TODO i18n: missing key for subtitle */}
- Saved IDE bindings for CCS profiles
+ {t('claudeExtensionPage.savedBindingsSubtitle', {
+ defaultValue: 'Saved IDE bindings for CCS profiles',
+ })}
@@ -431,8 +443,7 @@ export function ClaudeExtensionPage() {