diff --git a/ui/src/lib/i18n.ts b/ui/src/lib/i18n.ts index d590470e..34e42885 100644 --- a/ui/src/lib/i18n.ts +++ b/ui/src/lib/i18n.ts @@ -9,6 +9,7 @@ const resources = { en: 'English', 'zh-CN': 'Simplified Chinese', vi: 'Vietnamese', + ja: 'Japanese', }, layout: { languageSwitcher: 'Language', @@ -1176,6 +1177,7 @@ const resources = { en: 'English', 'zh-CN': '简体中文', vi: '越南语', + ja: '日语', }, layout: { languageSwitcher: '语言', @@ -1962,6 +1964,7 @@ const resources = { createProfile: '创建配置', noProfileMatch: '没有匹配“{{query}}”的配置', profileCount: '{{count}} 个配置', + profileCount_other: '{{count}} 个配置', configuredCount: '{{count}} 个已配置', deleteProfileTitle: '删除配置', deleteProfileDesc: '确定要删除“{{name}}”吗?这会移除设置文件且无法恢复。', @@ -1982,7 +1985,9 @@ const resources = { openCliProxyClaudePool: '打开 CLIProxy Claude Pool', migrationFollowup: '迁移后续处理', legacyContextPending: '{{count}} 个账号仍需首次模式确认。', + legacyContextPending_other: '{{count}} 个账号仍需首次模式确认。', legacyContinuityPending: '{{count}} 个共享账号仍处于标准旧版连续性深度。', + legacyContinuityPending_other: '{{count}} 个共享账号仍处于标准旧版连续性深度。', confirmingLegacy: '正在确认旧版策略...', confirmLegacy: '确认旧版策略({{count}})', noLegacyFollowup: '暂无待处理的迁移后续项。', @@ -2072,6 +2077,7 @@ const resources = { providers: '提供商', variants: '变体', providerCount: '{{count}} 个提供商', + providerCount_other: '{{count}} 个提供商', connectedCount: '{{count}} 个已连接', variantDisplay: '{{name}}({{provider}} 变体)', }, @@ -2298,6 +2304,7 @@ const resources = { en: 'Tiếng Anh', 'zh-CN': 'Tiếng Trung giản thể', vi: 'Tiếng Việt', + ja: 'Tiếng Nhật', }, layout: { languageSwitcher: 'Ngôn ngữ', @@ -3478,6 +3485,1199 @@ const resources = { }, }, }, + ja: { + translation: { + locale: { + en: '英語', + 'zh-CN': '簡体字中国語', + vi: 'ベトナム語', + ja: '日本語', + }, + layout: { + languageSwitcher: '言語', + }, + nav: { + general: '全般', + home: 'ホーム', + analytics: '分析', + identityAccess: '認証とアクセス', + apiProfiles: 'API プロファイル', + cliproxyPlus: 'CLIProxy Plus', + cliproxyOverview: '概要', + controlPanel: 'コントロールパネル', + githubCopilot: 'GitHub Copilot', + cursorIde: 'Cursor IDE', + accounts: 'アカウント', + allAccounts: 'すべてのアカウント', + sharedData: '共有データ', + compatibleClis: '対応 CLI', + factoryDroid: 'Factory Droid', + system: 'システム', + health: 'ヘルス', + settings: '設定', + openrouterTooltip: '注目: OpenRouter + Alibaba Coding Plan + Ollama', + }, + home: { + profiles: 'プロファイル', + cliproxy: 'CLIProxy', + accounts: 'アカウント', + health: 'ヘルス', + configurationRequired: '設定が必要', + }, + auth: { + dashboardTitle: 'CCS Dashboard', + loginDescription: 'ダッシュボードにアクセスするには認証情報を入力してください', + username: 'ユーザー名', + password: 'パスワード', + usernamePlaceholder: 'ユーザー名を入力', + passwordPlaceholder: 'パスワードを入力', + signIn: 'サインイン', + signingIn: 'サインイン中...', + loginFailed: 'ログインに失敗しました', + }, + commonToast: { + apiKeyRequired: 'API キーは必須です', + accountAddedPresetFailed: + 'アカウントは追加されましたが、デフォルトプリセットの適用に失敗しました', + settingsCopied: '設定をクリップボードにコピーしました', + enterPresetName: '保存するプリセット名を入力してください', + codeCopied: 'コードをクリップボードにコピーしました', + copyCodeFailed: 'コードのコピーに失敗しました', + appliedModelAllTiers: 'すべてのティアにモデルを適用しました', + settingsSaved: '設定を保存しました', + defaultTargetUpdated: 'デフォルトターゲットを更新しました', + failedUpdateDefaultTarget: + 'デフォルトターゲットを {{target}}{{suffix}} に更新できませんでした', + }, + profileDialog: { + editTitle: 'プロファイルを編集', + createTitle: 'API プロファイルを作成', + name: '名前', + namePlaceholder: 'my-api', + baseUrl: 'ベース URL', + baseUrlPlaceholder: 'https://api.example.com', + apiKey: 'API キー', + defaultModel: 'デフォルトモデル (ANTHROPIC_MODEL)', + defaultModelHint: '空欄の場合は {{model}} を使用', + modelMappingTitle: 'モデルマッピング (Opus/Sonnet/Haiku)', + modelMappingDesc: + '各ティアに異なるモデル ID を設定します。異なるモデル種別を別バックエンドへ振り分ける API プロキシで便利です。', + opusModel: 'Opus モデル (ANTHROPIC_DEFAULT_OPUS_MODEL)', + sonnetModel: 'Sonnet モデル (ANTHROPIC_DEFAULT_SONNET_MODEL)', + haikuModel: 'Haiku モデル (ANTHROPIC_DEFAULT_HAIKU_MODEL)', + cancel: 'キャンセル', + saving: '保存中...', + update: '更新', + create: '作成', + }, + modelTierMapping: { + title: 'モデルティアマッピング', + advanced: '詳細', + description: 'Claude Code の opus/sonnet/haiku ティアごとに別のモデルを設定します。', + autoSuggest: '{{provider}} に基づいて自動提案', + opusPlaceholder: '例: anthropic/claude-opus-4', + sonnetPlaceholder: '例: anthropic/claude-sonnet-4', + haikuPlaceholder: '例: anthropic/claude-3.5-haiku', + footer: + 'これにより ANTHROPIC_DEFAULT_OPUS_MODEL、ANTHROPIC_DEFAULT_SONNET_MODEL、ANTHROPIC_DEFAULT_HAIKU_MODEL を設定します。', + }, + envEditor: { + sensitive: '機密', + none: '環境変数は設定されていません。', + noneHint: '下の入力から追加するか、JSON を直接編集してください。', + addVariable: '環境変数を追加', + keyPlaceholder: 'VARIABLE_NAME', + valuePlaceholder: '値', + }, + setupVariant: { + using: '使用中:', + variantNameRequired: 'バリアント名 *', + variantNamePlaceholder: '例: my-gemini, g3, flash', + invokeHintPrefix: 'この名前で呼び出せます:', + model: 'モデル', + modelPlaceholder: '例: gemini-2.5-pro, claude-opus-4-6-thinking', + choosePresetInstead: '代わりにプリセットから選択', + selectModel: 'モデルを選択', + customModelName: 'カスタムモデル名...', + enterAnyModel: 'CLIProxy がサポートする任意のモデル名を入力', + defaultModel: 'デフォルト: {{model}}', + providerDefault: 'プロバイダーのデフォルト', + back: '戻る', + skip: 'スキップ', + creating: '作成中...', + createVariant: 'バリアントを作成', + skipHint: 'アカウント追加だけが目的なら、バリアント作成はスキップできます', + }, + customPresetDialog: { + title: 'カスタムプリセット', + presetNameOptional: 'プリセット名 (任意)', + presetNamePlaceholder: '例: My Custom Config', + codexTipPrefix: 'Codex のヒント: サフィックス', + codexTipSuffix: + 'で effort を固定できます。サフィックスなしのモデルは Thinking 設定を使います。', + defaultModel: 'デフォルトモデル', + defaultModelDesc: '特定のティア指定がない場合に使用', + opusModel: 'Opus (最上位)', + opusModelDesc: '複雑な推論タスク向け', + sonnetModel: 'Sonnet (バランス)', + sonnetModelDesc: '速度と性能のバランス', + haikuModel: 'Haiku (高速)', + haikuModelDesc: '単純なタスクにすばやく応答', + cancel: 'キャンセル', + savePreset: 'プリセットを保存', + applyPreset: 'プリセットを適用', + }, + componentModelSelector: { + selectModel: 'モデルを選択', + availableModelsCount: '利用可能なモデル ({{count}})', + preview: 'プレビュー', + }, + copilotSettings: { + enableCopilot: 'Copilot を有効化', + enableCopilotDesc: 'GitHub Copilot サブスクリプションを利用できるようにします', + basicSettings: '基本設定', + accountType: 'アカウント種別', + accountTypeIndividual: '個人', + accountTypeBusiness: 'ビジネス', + accountTypeEnterprise: 'エンタープライズ', + rateLimiting: 'レート制限', + rateLimitSeconds: 'レート制限 (秒)', + noLimit: '制限なし', + waitOnRateLimit: 'レート制限時に待機', + waitOnRateLimitDesc: '制限到達時にエラーではなく待機します', + daemonSettings: 'デーモン設定', + autoStartDaemon: 'デーモンを自動起動', + autoStartDaemonDesc: 'プロファイル使用時に copilot-api を起動します', + }, + providerModelSelector: { + selectModel: 'モデルを選択', + noModelsForProvider: 'このプロバイダーで利用可能なモデルはありません', + broken: '問題あり', + deprecated: '非推奨', + paid: '有料', + freeTier: '無料ティア', + paidTier: '有料ティア', + modelKnownIssues: 'このモデルには既知の問題があります', + viewIssueDetails: '問題の詳細を見る', + modelDeprecated: 'このモデルは非推奨です', + recommended: '推奨', + allModelsCount: 'すべてのモデル ({{count}})', + noModelsAvailable: '利用可能なモデルはありません', + }, + createAuthProfileDialog: { + title: '新しいアカウントを作成', + description: + '認証プロファイルには Claude CLI へのログインが必要です。以下のコマンドをターミナルで実行してください。同期モード、グループ、継続性の深さは後で Accounts テーブルから編集できます。', + profileName: 'プロファイル名', + profileNamePlaceholder: '例: work, personal, client', + invalidProfileName: '名前は英字で始め、英数字、ハイフン、アンダースコアのみ使用できます。', + enableSharedHistory: '他の ccs 認証アカウントとの共有履歴同期を有効にする', + historySyncGroupOptional: '履歴同期グループ (任意)', + historySyncGroupPlaceholder: 'default, sprint-a, client-x', + historySyncGroupHint: + '空欄の場合はデフォルトの共有グループを使用します。スペースはハイフンに正規化されます。', + deeperContinuity: '詳細: より深い継続性モード', + deeperContinuityHint: + 'session-env、file-history、shell-snapshots、todos の同期も追加します。認証情報は分離されたままです。', + invalidContextGroup: + 'グループは英字で始め、英数字、ハイフン、アンダースコアのみ使用できます (最大 {{max}} 文字)。', + command: 'コマンド', + commandFallback: 'ccs auth create ', + afterRunPrefix: 'コマンド実行後:', + afterRunStep1: 'ブラウザで Claude のログインを完了します', + afterRunStep2: 'ここに戻って更新し、新しいアカウントを確認します', + poolingHint: + 'Claude OAuth のプールルーティングを使う場合は、Accounts ページのアクションボタンから CLIProxy Claude pool を利用してください。', + close: '閉じる', + copied: 'コピーしました!', + copyCommand: 'コマンドをコピー', + }, + cliproxyModelCategory: { + google: 'Google (Gemini)', + openai: 'OpenAI (GPT)', + anthropic: 'Anthropic (Claude)', + antigravity: 'Antigravity', + other: 'その他', + }, + cliproxyOverviewComponents: { + availableModels: '利用可能なモデル', + noModelsAvailable: '利用可能なモデルはありません', + startSessionHint: '利用可能なモデルを取得するには CLIProxy セッションを開始してください', + totalCount: '合計 {{count}}', + availableThroughGroupedByProvider: + '{{backendLabel}} 経由で利用可能なモデルをプロバイダー別に表示', + moreCount: '+{{count}} 件', + }, + categorizedModelSelector: { + selectModel: 'モデルを選択', + noModelsAvailable: '利用可能なモデルはありません', + modelPlaceholder: 'モデル...', + }, + openrouterQuickStart: { + recommended: '推奨', + title: 'OpenRouter から始める', + description: + 'OpenAI、Anthropic、Google、Meta などの {{modelCountLabel}} モデルを、1 つの API で利用できます。', + featureOneApi: '1 つの API で全プロバイダー', + featureTierMapping: 'モデルティアマッピング', + createOpenRouterProfile: 'OpenRouter プロファイルを作成', + getApiKeyAt: 'API キー取得:', + or: 'または', + createCustomProfile: 'カスタム API プロファイルを作成', + }, + alibabaCodingPlanQuickStart: { + recommended: '推奨', + title: 'Alibaba Coding Plan を試す', + description: + 'コーディング用途向けに、Anthropic 互換エンドポイント経由で Alibaba Cloud Coding Plan を利用します。', + featureEndpoint: 'Anthropic 互換エンドポイント', + featureKeyFormat: '専用の sk-sp API キー', + createAlibabaProfile: 'Alibaba Coding Plan プロファイルを作成', + readGuideAt: 'セットアップガイド:', + }, + alibabaCodingPlanPromo: { + title: 'Alibaba Coding Plan', + subtitle: 'Anthropic エンドポイント経由の Model Studio Coding Plan', + add: '追加', + }, + credentialHealth: { + title: '認証情報の状態', + ready: '準備完了', + noCredentialsConfigured: '認証情報が未設定です。上のログインボタンから認証してください。', + neverUsed: '未使用', + justNow: 'たった今', + unknown: '不明', + daysAgo: '{{count}}日前', + hoursAgo: '{{count}}時間前', + minutesAgo: '{{count}}分前', + }, + proxyStatusWidget: { + justNow: 'たった今', + minutesAgo: '{{count}}分前', + hoursAgo: '{{count}}時間前', + syncReady: '同期準備完了', + noConfig: '設定なし', + remoteProxy: 'リモートプロキシ', + active: '稼働中', + trafficAutoRouted: 'トラフィックは自動でリモートサーバーにルーティングされます', + tooltipSync: 'CLIProxy にプロファイルを同期', + tooltipRestart: '再起動', + tooltipStop: '停止', + tooltipClose: '閉じる', + tooltipVersionSettings: 'バージョン設定', + versionManagement: 'バージョン管理', + selectVersionPlaceholder: 'インストールするバージョンを選択...', + stable: '(安定版)', + install: 'インストール', + versionsAboveUnstable: '{{version}} より新しいバージョンには既知の問題があります', + versionsKnownIssues: 'バージョン {{version}} には既知の問題があります', + lastChecked: '最終確認: {{time}}', + notRunning: '停止中', + start: '起動', + port: 'ポート {{port}}', + sessionCount: '{{count}} セッション', + sessionCount_other: '{{count}} セッション', + installFaultyTitle: '既知の問題があるバージョンをインストールしますか?', + installFaultyDesc: + '既知の不具合範囲 {{range}} に含まれる v{{version}} をインストールしようとしています。', + installFaultyWarning: + 'このバージョンには既知のバグがあり、失敗したりプロキシが不安定になる可能性があります。', + installUnstableTitle: '不安定なバージョンをインストールしますか?', + installUnstableDesc: + '最大安定版 v{{maxStable}} を上回る v{{version}} をインストールしようとしています。', + installUnstableWarning: + 'このバージョンには安定性の問題があり、予期しない動作を引き起こす可能性があります。', + installUnstableConfirm: 'このまま続行しますか?', + cancel: 'キャンセル', + installAnyway: 'このままインストール', + clickToDowngrade: 'クリックしてダウングレード', + clickToUpdate: 'クリックして更新', + }, + syncStatusCard: { + profileSync: 'プロファイル同期', + ready: '準備完了', + noConfig: '設定なし', + syncsProfilesDesc: 'API プロファイルをローカルの CLIProxy 設定に同期します', + runDoctorHint: '設定を生成するには ccs doctor --fix を実行してください。', + details: '詳細', + syncNow: '今すぐ同期', + }, + syncDialog: { + title: 'プロファイルをローカル CLIProxy に同期', + description: 'CCS の API プロファイルをローカルの CLIProxy config.yaml に同期します。', + noProfiles: '同期するプロファイルがありません。', + createProfilesFirst: '先に Profiles タブで API プロファイルを作成してください。', + modelLabel: 'モデル:', + ready: '準備完了', + profilesToSync_one: '{{count}} 件のプロファイルを同期', + profilesToSync_other: '{{count}} 件のプロファイルを同期', + cancel: 'キャンセル', + syncing: '同期中...', + synced: '同期完了', + syncNow: '今すぐ同期', + }, + errorLogs: { + title: 'エラーログ', + failedRequests_one: '{{count}} 件の失敗したリクエスト', + failedRequests_other: '{{count}} 件の失敗したリクエスト', + cliproxyDiagnostics: 'CLIProxy 診断', + showingCount: '{{total}} 件中 50 件のログを表示', + selectLog: '詳細を表示するログを選択してください', + failedLoadContent: 'ログ内容の読み込みに失敗しました', + copyAbsolutePath: '絶対パスをコピー', + copyRawContent: '生のログ内容をコピー', + tabOverview: '概要', + tabHeaders: 'ヘッダー', + tabRequest: 'リクエスト', + tabResponse: 'レスポンス', + tabRaw: 'Raw', + modelLabel: 'モデル:', + quotaResetsIn: 'クォータのリセットまで ', + method: 'メソッド', + provider: 'プロバイダー', + version: 'バージョン', + endpoint: 'エンドポイント', + url: 'URL', + timestamp: 'タイムスタンプ', + na: 'N/A', + noHeaders: '利用可能なヘッダーはありません', + noRequestBody: 'リクエストボディなし', + noResponseBody: 'レスポンスボディなし', + rateLimitedMsg: + 'レート制限です。別のアカウントに切り替えるか、クォータのリセットを待ってください。モデル {{model}} のクォータは上限に達しています。', + rateLimitedMsgNoModel: + 'レート制限です。別のアカウントに切り替えるか、クォータのリセットを待ってください。', + authFailedMsg: + '認証に失敗しました。CLIProxy Settings で再認証するか、API キーを確認してください。', + notFoundMsg: + 'エンドポイントが見つかりません。このプロバイダーには存在しない可能性があります。', + serverErrorMsg: + '上流サーバーでエラーが発生しました。後でもう一度試すか、プロバイダーのステータスページを確認してください。', + timeoutMsg: + 'リクエストがタイムアウトしました。ネットワーク接続を確認するか、タイムアウト設定を延長してください。', + unknown: '不明', + }, + editAccountContext: { + title: '履歴同期の設定', + description: + '"{{name}}" が他の CCS auth アカウントと履歴や継続性を共有する方法を設定します。', + syncMode: '同期モード', + selectContextMode: 'コンテキストモードを選択', + isolatedOption: 'isolated(同期なし)', + sharedOption: 'shared(同期あり)', + sharedModeHint: + 'Shared モードでは、同じ履歴同期グループ内のアカウントでワークスペースコンテキストを再利用します。', + isolatedModeHint: + 'Isolated モードでは、このアカウントを他の CCS auth アカウントから完全に分離します。', + historySyncGroup: '履歴同期グループ', + groupPlaceholder: 'default', + groupHint: + '小文字に正規化されます(スペースはダッシュに変換)。使用可能: 英字、数字、_、-(最大 {{max}} 文字)。', + invalidGroup: '英字で始まる有効なグループ名を入力してください。', + continuityDepth: '継続性レベル', + selectContinuityDepth: '継続性レベルを選択', + standardOption: 'standard(プロジェクトのみ)', + deeperOption: 'deeper continuity(上級者向け)', + deeperHint: + '上級モードでは、session-env、file-history、shell-snapshots、todos も同期します。', + standardHint: '標準モードでは、プロジェクトのワークスペースコンテキストのみ同期します。', + credentialsIsolated: + 'どのモードでも、認証情報と .anthropic はアカウントごとに分離されます。', + cancel: 'キャンセル', + save: '保存', + saving: '保存中...', + }, + historySyncLearningMap: { + title: '履歴同期の仕組み', + description: 'Isolated -> Shared -> Deeper。変更は各行の Sync から行います。', + learningMap: '概要図', + isolated: 'Isolated', + shared: 'Shared', + deeper: 'Deeper', + cliproxyManaged_one: + '{{count}} 件の CLIProxy Claude プールアカウントは Action Center / CLIProxy ページで管理されます。', + cliproxyManaged_other: + '{{count}} 件の CLIProxy Claude プールアカウントは Action Center / CLIProxy ページで管理されます。', + showDetails: '詳細を表示: グループ、切り替え、レガシーポリシー', + modeSwitch: 'モード切り替え', + modeSwitchDesc: + 'Sync ダイアログから isolated / shared の切り替えと deeper continuity の選択ができます。', + historySyncGroup: '履歴同期グループ', + historySyncGroupDesc: + '同じグループは同じプロジェクトコンテキストレーンを共有します。既定のフォールバックは default です。', + }, + accountsTable: { + name: '名前', + type: '種別', + defaultBadge: '既定', + created: '作成日', + lastUsed: '最終使用', + historySync: '履歴同期', + actions: '操作', + syncTitle: '同期モード、グループ、継続性レベルを編集', + confirmLegacyTitle: 'この legacy アカウントの現在のモードを明示的に確定', + sync: '同期', + confirm: '確定', + active: '有効', + setDefault: '既定に設定', + deleteAccount: 'アカウントを削除', + cannotDeleteDefault: '既定のアカウントは削除できません', + noAccounts: + 'CCS auth アカウントが見つかりません。追加するには ccs auth create を使ってください。', + resetToDefault: 'CCS Default に戻す', + deleteDialogTitle: 'アカウントを削除', + deleteDialogDesc: + 'アカウント "{{name}}" を削除しますか? この操作でプロファイルと関連するすべてのセッションデータが削除されます。この操作は元に戻せません。', + cancel: 'キャンセル', + delete: '削除', + sharedGroupStandard: 'shared({{group}}、標準)', + sharedGroupDeeper: 'shared({{group}}、deeper)', + sharedGroupLegacy: 'shared({{group}}、標準・legacy)', + isolatedLegacy: 'isolated(レガシー既定)', + isolated: 'isolated', + }, + addAccountDialog: { + title: '{{displayName}} アカウントを追加', + descKiro: + 'Kiro の認証方法を選び、ブラウザーで認証するか Kiro IDE からインポートしてください。', + descDeviceCode: + '認証をクリックすると、プロバイダーのサイトで入力する確認コードが表示されます。', + descOauth: + '認証をクリックすると OAuth URL を取得します。任意のブラウザーで開いてサインインしてください。', + powerUserEnabled: '上級者モードが有効です', + powerUserSkipped: + 'Settings > Proxy で AGY responsibility checklist をスキップしています。OAuth / アカウントのリスクはすべて自己責任です。', + authMethod: '認証方法', + selectKiroAuthMethod: 'Kiro の認証方法を選択', + nicknameRequired: 'ニックネーム(必須)', + nicknameOptional: 'ニックネーム(任意)', + nicknamePlaceholder: '例: work, personal', + nicknameRequiredHint: + 'このプロバイダーでは必須です。重複しないわかりやすい名前を付けてください(例: work, personal)。', + nicknameOptionalHint: + 'このアカウントを識別しやすい名前です。空欄ならメールアドレスから自動生成されます。', + waitingForAuth: '認証を待機中...', + deviceCodeHint: + '確認コードのダイアログがまもなく表示されます。プロバイダーのサイトでコードを入力してください。', + browserHint: 'ブラウザーで認証を完了してください。このダイアログは自動で閉じます。', + openUrlLabel: 'サインインするには、この URL を任意のブラウザーで開いてください:', + copied: 'コピー済み', + copy: 'コピー', + open: '開く', + popupBlocked: + 'ポップアップがブロックされました。上の URL をコピーして、ブラウザーで手動で開いてください。', + redirectPasteLabel: + 'リダイレクトが機能しない場合は、コールバック URL を貼り付けてください:', + callbackPlaceholder: 'リダイレクト URL をここに貼り付け...', + submitCallback: 'コールバックを送信', + submitting: '送信中...', + preparingUrl: 'サインイン URL を準備中...', + cancel: 'キャンセル', + importingToken: 'Kiro IDE からトークンをインポート中...', + authenticate: '認証', + importFromIde: 'IDE からインポート', + importing: 'インポート中...', + }, + flowViz: { + backToProviders: 'プロバイダーに戻る', + showDetails: '詳細を表示', + hideDetails: '詳細を隠す', + resetLayout: 'レイアウトをリセット', + provider: 'プロバイダー', + totalRequests: '総リクエスト数', + accounts: 'アカウント', + connectionTimeline: '接続タイムライン', + noRecentConnections: '最近の接続はありません', + }, + quickStats: { + totalRequests: '総リクエスト数', + successRate: '成功率', + totalTokens: '総トークン数', + activeModels: 'アクティブなモデル', + }, + authMonitor: { + accounts: 'アカウント', + accountsCount: '{{count}} アカウント', + success: '成功', + failed: '失敗', + successRate: '成功率', + }, + accountCard: { + resumeAccount: 'アカウントを再開', + pauseAccount: 'アカウントを一時停止', + quotaLoading: 'クォータ...', + quota: 'クォータ', + quotaUnavailable: 'クォータ上限を取得できません', + reauthNeeded: '再認証が必要', + failureLabelReauth: '再認証', + failureLabelWorkspace: 'ワークスペース', + failureLabelNoAccess: 'アクセス不可', + failureLabelRetry: '再試行', + failureLabelReconnect: '再接続', + failureLabelTemporary: '一時的', + failureLabelUnavailable: '利用不可', + failureHintReauth: 'このアカウントの認証フローをもう一度実行して更新してください。', + failureHintWorkspace: + 'このアカウントをアクティブなワークスペースに戻してから、削除して再追加してください。', + failureHintNoAccess: + 'このアカウントでは、プロバイダーのクォータエンドポイントにアクセスできません。', + failureHintRetry: 'しばらく待ってから、クォータリクエストを再試行してください。', + failureHintReconnect: '古いアカウントを削除し、再度認証してください。', + failureHintTemporary: '一時的な問題のようです。後でもう一度試してください。', + removeAndReadd: '削除して再追加', + autoRefreshFailed: '自動更新に失敗しました', + }, + antigravityChecklist: { + title: 'Antigravity OAuth の責任確認', + subtitle: '認証する前に 4 ステップすべて完了してください。', + mandatory: '必須', + completion: '完了状況', + stepsCount: '{{current}}/4 ステップ', + }, + accountSafetyWarning: { + title: 'OAuth アカウント安全性の警告', + subtitle: 'Issue #509 · Gemini + AGY OAuth リスク', + firstLine: + 'Issue #509 では、CCS AGY と、CCS Gemini と CCS AGY 間での共有アカウント利用に関連する停止・BAN 報告がまとめられています。', + secondLine: + 'OAuth とアカウントアクセスのリスクをすべて自己責任で負う場合のみ続行してください。', + issueLabel: 'Issue #509 を読む', + proxySettingsLabel: 'Gemini + AGY の設定: Settings > Proxy', + }, + cliproxyDialog: { + name: '名前', + provider: 'プロバイダー', + account: 'アカウント', + accountOptional: 'アカウント(任意)', + model: 'モデル', + modelOptional: 'モデル(任意)', + defaultTarget: 'デフォルトターゲット', + defaultTier: 'デフォルトティア', + tierConfig: 'ティア設定', + selectProvider: 'プロバイダーを選択...', + useDefaultAccount: 'デフォルトのアカウントを使用', + defaultSuffix: '(デフォルト)', + opus: 'Opus', + sonnet: 'Sonnet', + haiku: 'Haiku', + claudeCode: 'Claude Code', + factoryDroid: 'Factory Droid', + cancel: 'キャンセル', + create: '作成', + creating: '作成中...', + saveChanges: '変更を保存', + saving: '保存中...', + placeholderName: 'my-gemini', + placeholderModel: 'gemini-2.5-pro', + placeholderComposite: 'my-composite', + placeholderModelId: 'model-id', + placeholderAccountId: 'account-id', + editCompositeTitle: '複合バリアントを編集: {{name}}', + editSingleTitle: '単一バリアントを編集: {{name}}', + }, + profileEditor: { + profileInfo: 'プロファイル情報', + profileName: 'プロファイル名', + filePath: 'ファイルパス', + lastModified: '最終更新', + defaultTarget: 'デフォルトターゲット', + quickUsage: 'クイック実行', + runWithProfile: 'このプロファイルで実行', + runOnDroid: 'Droid で実行', + droidAliasExplicit: 'Droid エイリアス(明示)', + overrideToClaude: 'Claude に切り替え', + overrideToClaudeExplicit: 'Claude に切り替え(明示)', + setAsDefault: 'デフォルトに設定', + provider: 'プロバイダー', + custom: 'カスタム', + }, + settings: { + sectionLoadFailed: 'セクションの読み込みに失敗しました', + unknownError: '不明なエラーが発生しました', + reloadPage: 'ページを再読み込み', + refresh: '更新', + copy: 'コピー', + copied: 'コピーしました', + saved: '保存しました', + loading: '読み込み中...', + configFileNotFound: '設定ファイルが見つかりません', + }, + settingsTabs: { + web: 'Web', + env: '環境', + think: 'Thinking', + proxy: 'プロキシ', + auth: '認証', + backup: 'バックアップ', + }, + settingsAuth: { + failedFetchTokens: '認証トークンの取得に失敗しました', + failedSaveTokens: 'トークンの保存に失敗しました', + tokensUpdated: 'トークンを更新しました。反映するには CLIProxy を再起動してください。', + failedRegenerate: 'シークレットの再生成に失敗しました', + secretRegenerated: + '新しい Management Secret を生成しました。反映するには CLIProxy を再起動してください。', + failedReset: 'トークンのリセットに失敗しました', + tokensReset: + 'トークンをデフォルトに戻しました。反映するには CLIProxy を再起動してください。', + description: + 'CLIProxy の認証トークンを設定します。変更を反映するには CLIProxy の再起動が必要です。', + apiKey: 'API Key', + custom: 'カスタム', + apiKeyDesc: 'Claude Code が CLIProxy への認証に使用します', + apiKeyPlaceholder: 'API key', + managementSecret: 'Management Secret', + managementSecretDesc: + 'CCS ダッシュボードが CLIProxy の管理 API にアクセスするために使用します', + managementSecretPlaceholder: 'Management secret', + generateSecret: '新しい安全なシークレットを生成', + resetDefaults: 'デフォルトに戻す', + resetDesc: 'API Key と Management Secret の両方をデフォルト値に戻します。', + saving: '保存中...', + save: '保存', + }, + settingsGlobalEnv: { + description: + 'Claude 以外のサブスクリプションプロファイル(gemini、codex、agy、ghcp など)に注入する環境変数', + enabled: 'Global Env を有効化', + disabled: 'Global Env を無効化', + enabledDesc: 'Env 変数をサードパーティープロファイルへ注入します', + disabledDesc: 'Env 変数は注入されません', + envVars: '環境変数', + noneConfigured: '環境変数は未設定です', + addNew: '変数を追加', + keyName: 'KEY_NAME', + value: '値', + add: '追加', + quickAdd: 'よく使う変数を追加', + allConfigured: '主要な変数はすべて設定済みです', + }, + settingsBackups: { + failedFetch: 'バックアップの取得に失敗しました', + failedRestore: 'バックアップの復元に失敗しました', + restoreSuccess: 'バックアップを復元しました', + title: '設定バックアップ', + description: + 'settings.json の以前のバージョンを復元します。設定を変更するとバックアップが自動作成されます。', + none: '利用できるバックアップはありません', + noneHint: '設定を変更すると、ここにバックアップが表示されます', + latest: '最新', + restoring: '復元中...', + restore: '復元', + restoreConfirmTitle: 'バックアップを復元しますか?', + restoreConfirmPrefix: '現在の設定を次のバックアップで置き換えます:', + restoreConfirmSuffix: 'この操作は元に戻せません。', + cancel: 'キャンセル', + }, + settingsWebsearch: { + description: + 'サードパーティープロファイル(gemini、codex、agy など)向けの CLI ベース Web 検索', + enabled: 'WebSearch を有効化', + disabled: 'WebSearch を無効化', + checking: '状態を確認中...', + providers: 'プロバイダー', + noneConfigured: 'プロバイダーが設定されていません', + noneConfiguredHint: + 'Web 検索プロバイダーを有効にするには CLI ツールをインストールしてください', + checkProviders: 'プロバイダーを確認', + installed: 'インストール済み', + notInstalled: '未インストール', + model: 'モデル:', + howInstall: '{{name}} CLI のインストール方法', + installGlobally: 'グローバルにインストール', + requiresKey: '(xAI API Key が必要)', + freeTier: '(無料枠あり)', + viewDocs: 'ドキュメントを見る', + }, + settingsProxy: { + failedLoadAgyMode: 'AGY パワーユーザーモードの読み込みに失敗しました', + failedUpdateAgyMode: 'AGY パワーユーザーモードの更新に失敗しました', + failedVerifyAgyMode: 'AGY パワーユーザーモードの永続化確認に失敗しました', + notPersistedAgyMode: + 'AGY パワーユーザーモードが保存されませんでした。別のプロセスが設定を変更した可能性があります。', + agyModeEnabled: 'AGY パワーユーザーモードを有効にしました。', + agyModeDisabled: 'AGY パワーユーザーモードを無効にしました。', + typePhraseToContinue: '続行するには「{{value}}」と入力してください。', + description: + 'プロキシベースのプロファイル用に、ローカルまたはリモートの {{backend}} 接続を設定します', + backendPlus: 'CLIProxy Plus', + backend: 'CLIProxy', + instanceStatus: 'インスタンス状態', + connectionMode: '接続モード', + local: 'ローカル', + remote: 'リモート', + localDesc: 'このマシンで {{backend}} バイナリを実行します', + remoteDesc: 'リモートの {{backend}} サーバーに接続します', + backendBinary: 'バックエンドバイナリ', + stopProxyToSwitch: + 'バックエンドを切り替える前に、インスタンス状態から実行中のプロキシを停止してください。', + default: 'デフォルト', + plusDesc: 'Kiro と GitHub Copilot を含むすべてのプロバイダーをサポート', + originalDesc: '元のバイナリ(Gemini、Codex、Antigravity のみ)', + variantsIncompatible: + '既存の Kiro/Copilot バリアントは CLIProxyAPI では動作しません。CLIProxyAPIPlus に切り替えるか、それらのバリアントを削除してください。', + safety: '安全設定', + agyModeTitle: 'Antigravity パワーユーザーモード', + agyModeDesc: + 'Add Account 画面と `ccs agy` フローで AGY の責任チェックリストをスキップします。', + agyWarning: + 'OAuth の停止/BAN リスクパターン (#509) を十分理解している場合にのみ使用してください。CCS はアカウント喪失の責任を負いません。', + finalConfirm: '最終確認', + finalConfirmDesc: + '有効にすると、ダッシュボードと CLI の両方で AGY の安全チェックをスキップします。続行する前に issue #509 を確認し、正確なフレーズを入力してください。', + step1: 'ステップ 1', + readIssue: 'Issue #509 を読む', + step2: 'ステップ 2', + typePrefix: '有効にするには', + typeSuffix: 'と入力してください。', + typePhraseAria: + 'Antigravity パワーユーザーモードを有効にするには I ACCEPT RISK と入力してください', + exactPhrase: '完全一致で入力してください。', + enableAgyMode: 'パワーユーザーモードを有効にする', + toggleAgyMode: 'AGY パワーユーザーモードを切り替え', + fallbackSettings: 'フォールバック設定', + enableFallback: 'ローカルへのフォールバックを有効化', + enableFallbackDesc: 'リモートに接続できない場合はローカルプロキシを使用します', + autoStartLocal: 'ローカルプロキシを自動起動', + autoStartLocalDesc: 'フォールバック時にローカルプロキシを自動で起動します', + advanced: '詳細設定', + debugMode: 'デバッグモード', + debugModeDesc: 'ブラウザコンソールで開発者向け診断を有効にします', + debugModeEnabled: + 'デバッグモードを有効にしました。詳細ログはブラウザコンソールを確認してください。', + localProxy: 'ローカルプロキシ', + port: 'ポート', + autoStart: '自動起動', + localAutoStartDesc: '必要になったらローカルプロキシを自動起動します', + remoteConfigTitle: 'リモートサーバー設定', + host: 'ホスト', + hostPlaceholder: '192.168.1.100 または proxy.example.com', + defaultPort: 'デフォルト: {{value}}', + portPlaceholder: '{{value}} を使う場合は空欄', + protocol: 'プロトコル', + http: 'HTTP', + https: 'HTTPS', + apiKeyOptional: 'API Key(任意)', + apiKeyPlaceholder: '/v1/* API エンドポイント用', + apiKeyDesc: '/v1/chat/completions への API リクエストで使用します', + managementKeyOptional: 'Management Key(任意)', + managementKeyPlaceholder: '/v0/management/* エンドポイント用', + managementKeyDesc: + 'ダッシュボードの管理 API で使用します。未設定の場合は API Key を使用します。', + testing: 'テスト中...', + testConnection: '接続をテスト', + connectedLatency: '接続済み({{value}}ms)', + connectionFailed: '接続に失敗しました', + invalidPortOrEmpty: + 'ポートは 1〜65535 の整数で入力するか、デフォルトを使う場合は空欄にしてください', + invalidLocalPort: 'ローカルポートは 1〜65535 の整数で指定してください', + backendPlusApi: 'CLIProxyAPIPlus', + backendApi: 'CLIProxyAPI', + }, + settingsThinking: { + description: '対応モデルの拡張 Thinking / Reasoning を設定します。', + supportedProviders: '対応プロバイダー', + modeTitle: 'Thinking モード', + modeAutoDesc: 'モデルのティアに応じて Thinking を自動設定します', + modeOffDesc: '拡張 Thinking を無効化します', + modeManualDesc: '永続的な上書きレベルを設定するか、実行ごとに CLI フラグを使います', + tierDefaults: 'ティア別デフォルト', + tierDefaultsDesc: + 'Auto モード時に各モデルティアへ適用するデフォルトの Thinking レベルです。', + persistentOverride: '永続オーバーライド', + persistentOverrideDesc: 'すべてのセッションに適用されます。CLI フラグが優先されます。', + enterCustomBudget: 'カスタム予算を入力', + apply: '適用', + providerOverrides: 'プロバイダー別オーバーライド({{count}})', + providerOverridesDesc: + '特定のプロバイダーに対してティア別デフォルトを上書きします。必要に応じてカスタムのプロバイダーキーを追加してください。', + addProviderPlaceholder: 'プロバイダーキーを追加(例: qwen)', + showWarnings: '警告を表示', + showWarningsDesc: 'Thinking 値が制限または調整された場合に警告を表示します', + cliEnvOverride: 'CLI / Env オーバーライド', + supportLine1Prefix: 'Thinking budget:', + supportLine1Suffix: '(token ベース)', + supportLine2Prefix: 'Reasoning effort:', + supportLine2SuffixPrefix: '(suffix または ', + supportLine2SuffixPostfix: ': medium/high/xhigh)', + supportLine3Prefix: 'Codex の suffix は effort を固定します(例: ', + supportLine3Suffix: ')。suffix なしのモデルは Thinking mode を使用します。', + }, + analytics: { + title: '分析', + subtitle: '利用状況とインサイトを確認', + month: '月', + allTime: '全期間', + updated: '最終更新: {{value}}', + }, + analyticsSummary: { + totalTokens: '総トークン数', + totalTokensSubtitle: '入力 {{input}} / 出力 {{output}}', + totalCost: '総コスト', + totalCostSubtitle: '1日平均 ${{value}}', + cacheTokens: 'キャッシュトークン', + cacheTokensSubtitle: '${{cost}}(コストの{{percent}}%)', + inputCost: '入力コスト', + outputCost: '出力コスト', + tokensSubtitle: '{{value}} トークン', + }, + apiProfiles: { + title: 'APIプロファイル', + new: '新規', + searchPlaceholder: 'プロファイルを検索...', + loadingProfiles: 'プロファイルを読み込み中...', + failedLoadTitle: 'プロファイルを読み込めませんでした', + failedLoadDesc: 'APIプロファイルを取得できませんでした。もう一度お試しください。', + retry: '再試行', + noProfilesYet: 'APIプロファイルがまだありません', + noProfilesDesc: '最初のプロファイルを作成して、カスタムAPIエンドポイントに接続します', + createProfile: 'プロファイルを作成', + noProfileMatch: '"{{query}}" に一致するプロファイルはありません', + profileCount: '{{count}} 件のプロファイル', + profileCount_other: '{{count}} 件のプロファイル', + configuredCount: '{{count}} 件設定済み', + deleteProfileTitle: 'プロファイルを削除', + deleteProfileDesc: + '"{{name}}" を削除しますか?設定ファイルも削除され、この操作は元に戻せません。', + delete: '削除', + unsavedChangesTitle: '未保存の変更', + unsavedChangesDesc: + '"{{current}}" に未保存の変更があります。破棄して "{{next}}" に切り替えますか?', + discardSwitch: '破棄して切り替え', + }, + accountsPage: { + title: 'アカウント', + managePrefix: '', + manageSuffix: 'のアカウント管理とプール導線を一元化。', + mobileManageSuffix: 'の継続性設定を管理。', + primaryActions: '主要アクション', + createAccount: 'アカウントを作成', + authClaudeInPool: 'PoolでClaudeを認証', + openClaudePoolSettings: 'Claude Pool設定を開く', + openCliProxyClaudePool: 'CLIProxy Claude Poolを開く', + migrationFollowup: '移行フォローアップ', + legacyContextPending: '{{count}} 件のアカウントで初回モード確認がまだ必要です。', + legacyContextPending_other: '{{count}} 件のアカウントで初回モード確認がまだ必要です。', + legacyContinuityPending: '{{count}} 件の共有アカウントが標準のレガシー継続深度のままです。', + legacyContinuityPending_other: + '{{count}} 件の共有アカウントが標準のレガシー継続深度のままです。', + confirmingLegacy: 'レガシーポリシーを確認中...', + confirmLegacy: 'レガシーポリシーを確認 ({{count}})', + noLegacyFollowup: '保留中のレガシーフォローアップはありません。', + continuityGuide: '継続性ガイド', + expandWhenNeeded: '必要なときだけ展開。', + sharedStandard: '共有標準', + sharedStandardDesc: + 'プロジェクトワークスペースの同期のみ。ほとんどのチームに最適な既定値です。', + sharedDeeper: '共有拡張', + sharedDeeperPrefix: '追加:', + isolated: '分離', + isolatedDesc: 'リンクなし。厳密な分離に最適です。', + quickCommands: 'クイックコマンド', + quickCommandsDesc: 'コピーしてターミナルで実行します。', + workspaceBadge: 'ccs auth ワークスペース', + historySyncBadge: '履歴同期コントロール', + authAccounts: '認証アカウント', + tableScopePrefix: '', + tableScopeMiddle: 'のアカウントのみを表示します。モード/グループ/深度の変更は', + tableScopeSuffix: 'を使ってください。', + accountMatrix: 'アカウントマトリクス', + sharedTotalDesc: '共有合計: {{count}}。操作にはSync設定とレガシー確認が含まれます。', + loadingAccounts: 'アカウントを読み込み中...', + }, + health: { + justNow: 'たった今', + secondsAgo: '{{count}}秒前', + minutesAgo: '{{count}}分前', + hoursAgo: '{{count}}時間前', + systemHealth: 'システムヘルス', + build: 'ビルド {{version}}', + lastScan: '最終スキャン:', + autoRefresh: '自動更新:', + refresh: '更新', + checks: 'チェック', + version: 'バージョン', + platform: 'プラットフォーム', + liveMonitoring: 'ライブ監視中', + copied: 'クリップボードにコピーしました', + refreshing: 'ヘルスチェックを更新中...', + fix: '修正', + applyFix: '修正を適用', + healthLabel: 'ヘルス', + }, + updates: { + actionRequired: '対応が必要', + done: '完了', + all: 'すべて', + inboxTitle: '更新インボックス', + inboxSubtitle: '対応を優先し、その後で完了または非表示にします。', + needsAction: '要対応', + doneCount: '完了', + searchPlaceholder: '対応内容またはコマンドを検索', + noNotices: 'このビューに一致する通知はありません。', + noUpdates: '利用可能な更新はありません。', + published: '{{date}} 公開', + markDone: '完了にする', + dismiss: '非表示', + reopen: '再表示', + doNext: '次にやる', + impactedIntegrations: '影響を受ける連携', + impactedDesc: '更新範囲とルーティングに基づく関連領域です。', + whyMatters: '重要な理由', + whyMattersDesc: '長文のリリースノートではなく、要点だけを表示します。', + open: '開く', + progressNeedsAction: '要対応', + progressInReview: '確認中', + progressDone: '完了', + progressDismissed: '非表示', + statusNew: '新着', + statusStable: '安定', + statusPlanned: '予定', + dashboard: 'ダッシュボード', + cliUsage: 'CLI利用状況', + }, + cliproxyPage: { + connected: '接続済み', + notConnected: '未接続', + variant: 'バリアント', + parentNotConnected: '親が未接続', + viaProvider: '{{provider}} 経由', + emptyTitle: 'CCS プロファイルマネージャー', + emptyDesc: + 'CLIProxyプロバイダー向けのOAuth認証、アカウント設定、モデル選択を管理します。CCSが各AIバックエンドへのリクエストをどうルーティングするかを設定できます。', + emptyControlPanelPrefix: 'ライブ利用統計とリアルタイム監視は', + controlPanel: 'コントロールパネルで確認できます', + quickSetup: 'クイックセットアップ', + accountManagement: 'CCSレベルのアカウント管理', + providers: 'プロバイダー', + variants: 'バリアント', + providerCount: '{{count}} 件のプロバイダー', + providerCount_other: '{{count}} 件のプロバイダー', + connectedCount: '{{count}} 件接続済み', + variantDisplay: '{{name}}({{provider}} バリアント)', + }, + copilotPage: { + title: 'Copilot', + subtitle: 'GitHub Copilot プロキシ', + setup: 'セットアップ', + auth: '認証', + daemon: 'デーモン', + unofficialTitle: '非公式API: 自己責任で利用してください', + unofficialItem1: + 'リバースエンジニアリングされたAPIのため、いつでも動かなくなる可能性があります', + unofficialItem2: '過度な利用でアカウントに制限がかかる可能性があります', + unofficialItem3: 'CCSは保証も責任も負いません', + yes: 'はい', + no: 'いいえ', + installed: 'インストール済み', + missing: '未インストール', + integration: '連携', + enabled: '有効', + disabled: '無効', + github: 'GitHub', + connected: '接続済み', + notConnected: '未接続', + authenticate: '認証する', + authenticating: '認証中...', + status: 'ステータス', + running: '稼働中', + stopped: '停止', + port: 'ポート', + stopping: '停止中...', + stop: '停止', + starting: '起動中...', + start: '起動', + installing: 'インストール中...', + installCopilotApi: 'copilot-api をインストール', + proxy: 'プロキシ', + active: '有効', + inactive: '無効', + }, + cursorPage: { + title: 'Cursor', + beta: 'ベータ', + subtitle: 'Cursor 専用の連携設定', + unofficialTitle: '非公式API - 自己責任で利用', + unofficialItem1: + 'リバースエンジニアリング連携のため、いつでも利用不能になる可能性があります', + unofficialItem2: '不正利用や過度な利用により、アカウントが制限されるおそれがあります', + unofficialItem3: 'CCSは保証も責任も負いません', + integration: '連携', + authentication: '認証', + daemon: 'デーモン', + enabled: '有効', + disabled: '無効', + expired: '期限切れ', + connected: '接続済み', + notConnected: '未接続', + running: '稼働中', + stopped: '停止中', + actions: '操作', + disableIntegration: '連携を無効化', + enableIntegration: '連携を有効化', + autoDetectAuth: '認証を自動検出', + manualAuthImport: '認証情報を手動インポート', + stopDaemon: 'デーモンを停止', + startDaemon: 'デーモンを起動', + port: 'ポート', + configuration: 'Cursor設定', + lastModified: '最終更新:', + neverSaved: '未保存', + saving: '保存中...', + save: '保存', + modelConfig: 'モデル設定', + settings: '設定', + info: '情報', + presets: 'プリセット', + presetsDesc: '事前設定済みのモデルマッピングを適用します', + modelMapping: 'モデルマッピング', + modelMappingDesc: '各ティアで使うモデルを設定します', + defaultModel: 'デフォルトモデル', + defaultModelDesc: '特定のティア指定がない場合に使われます。', + opusModel: 'Opus(最高性能)', + opusModelDesc: '複雑な推論と最高品質の応答向け。', + sonnetModel: 'Sonnet(バランス)', + sonnetModelDesc: '一般的なコーディングや日常的なチャット向け。', + haikuModel: 'Haiku(高速)', + haikuModelDesc: '低レイテンシで軽量なリクエスト向け。', + runtimeSettings: 'ランタイム設定', + autoStartDaemon: 'デーモンを自動起動', + autoStartDesc: '連携使用時にCursorデーモンを自動で起動します。', + ghostMode: 'ゴーストモード', + ghostModeDesc: 'テレメトリ削減のため x-ghost-mode を要求します。', + provider: 'プロバイダー', + filePath: 'ファイルパス', + availableModels: '利用可能なモデル', + loadingModels: 'モデルを読み込み中...', + noModels: 'モデルメタデータはまだありません。デーモンを起動して更新してください。', + default: 'デフォルト', + rawConfiguration: '生の設定(JSON)', + manualImportTitle: 'Cursor 認証情報の手動インポート', + manualImportDesc: + '自動検出できない場合は、CursorのアクセストークンとマシンIDを入力してください。', + accessToken: 'アクセストークン', + accessTokenPlaceholder: 'Cursorのアクセストークンを貼り付け', + machineId: 'マシンID', + machineIdPlaceholder: '32文字の16進数(ハイフンなしUUID)', + cancel: 'キャンセル', + import: 'インポート', + selectModel: 'モデルを選択', + useDefaultModel: 'デフォルトモデルを使用', + availableModelCount: '利用可能なモデル({{count}})', + invalidPort: 'ポートは1から65535の整数で指定してください', + defaultModelRequired: 'デフォルトモデルは必須です', + savedConfig: 'Cursor設定とモデルマッピングを保存しました', + failedSaveConfig: '設定の保存に失敗しました', + modelsLoadingWait: 'モデルの読み込み中です。プリセットの適用前にしばらくお待ちください。', + noModelsAvailable: + 'まだ利用可能なモデルがありません。先にデーモンを起動して更新してください。', + appliedCodexPreset: 'GPT-5.3 Codexプリセットを適用しました', + appliedClaudePreset: 'Claude 4.6プリセットを適用しました', + appliedGeminiPreset: 'Gemini 3プリセットを適用しました', + integrationEnabled: 'Cursor連携を有効化しました', + integrationDisabled: 'Cursor連携を無効化しました', + failedUpdateIntegration: '連携状態の更新に失敗しました', + credentialsImported: 'Cursor認証情報をインポートしました', + autoDetectFailed: '自動検出に失敗しました', + manualRequired: 'トークンとマシンIDは必須です', + manualImportFailed: '手動インポートに失敗しました', + failedStartDaemon: 'デーモンの起動に失敗しました', + daemonStarted: 'デーモンを起動しました', + daemonStartedWithPid: 'デーモンを起動しました(PID: {{pid}})', + failedStopDaemon: 'デーモンの停止に失敗しました', + daemonStopped: 'デーモンを停止しました', + rawLoading: '生の設定をまだ読み込み中です。しばらく待ってから再試行してください。', + invalidJson: 'JSONが不正です', + rawSaved: '生の設定を保存しました', + rawChanged: '生の設定が外部で変更されました。更新して再試行してください。', + failedSaveRaw: '生の設定の保存に失敗しました', + savedAll: 'Cursor設定を保存しました', + }, + droidPage: { + loadingDiagnostics: 'Droid診断を読み込み中...', + failedDiagnostics: 'Droid診断の読み込みに失敗しました。', + overview: '概要', + byok: 'BYOK', + docs: 'ドキュメント', + runtimeInstall: 'ランタイムとインストール', + status: '状態', + detected: '検出済み', + notFound: '未検出', + detectionSource: '検出元', + binaryPath: 'バイナリパス', + installDirectory: 'インストール先', + version: 'バージョン', + overridePath: 'オーバーライド(CCS_DROID_PATH)', + configFiles: '設定ファイル', + path: 'パス', + resolved: '解決先', + size: 'サイズ', + lastModified: '最終更新', + parseWarning: '解析警告: {{value}}', + readWarning: '読み取り警告: {{value}}', + warnings: '警告', + byokSummary: 'BYOK概要', + activeModelSelector: '現在のモデルセレクター', + customModels: 'カスタムモデル', + ccsManaged: 'CCS管理', + userManaged: 'ユーザー管理', + malformedEntries: '不正なエントリ', + providers: 'プロバイダー', + none: 'なし', + customModelsTitle: 'カスタムモデル', + modelName: '名前 / モデル', + provider: 'プロバイダー', + baseUrl: 'Base URL', + noCustomModels: 'カスタムモデルはありません', + docsAlignedNotes: 'ドキュメント整合メモ', + factoryDocs: 'Factory Docs', + providerFactCheckDocs: 'プロバイダー検証ドキュメント', + providerValues: 'プロバイダー値: {{value}}', + settingsHierarchy: '設定階層: {{value}}', + settingsTitle: 'Droid BYOK設定', + fixJsonBeforeQuickSettings: 'クイック設定を使う前にJSON構文を修正してください。', + fixJsonBeforeReasoning: 'BYOK推論設定を更新する前にJSON構文を修正してください。', + unableUpdateReasoning: '選択したBYOKモデルの推論設定を更新できません。', + fixJsonBeforeBudget: 'thinking budgetを更新する前にJSON構文を修正してください。', + anthropicOnlyBudget: 'thinking budget はAnthropicのBYOKモデルでのみ利用できます。', + invalidJson: 'JSONが不正です: {{value}}', + saved: 'Droid設定を保存しました', + changedExternally: 'Droid設定が外部で変更されました。更新して再試行してください。', + failedSave: 'Droid設定の保存に失敗しました', + }, + sharedPage: { + commands: 'コマンド', + skills: 'スキル', + agents: 'エージェント', + title: '共有データ', + subtitle: 'Claudeインスタンス間で共有されるコマンド、スキル、エージェント', + totalShared: '共有数', + visible: '表示中', + markdownDetail: 'Markdown詳細ビュー', + filterPrefix: 'フィルター:', + configurationRequired: '設定が必要です', + countsUnavailable: '件数を取得できません', + retryCounts: '件数を再取得', + filterPlaceholder: '{{tab}}を名前、説明、パスで絞り込み', + showing: '{{visible}} / {{total}} 件の{{tab}}を表示', + showingQuery: '(「{{query}}」で絞り込み)', + refreshing: '(更新中...)', + loadingShared: '共有{{tab}}を読み込み中...', + failedLoadShared: '共有{{tab}}の読み込みに失敗しました', + retry: '再試行', + noSharedFound: + '共有{{tab}}が見つかりません。`ccs sync` を実行するか、共有ディレクトリに項目を追加してください。', + noMatch: '{{tab}}で「{{query}}」に一致するものはありません。', + selectOne: '内容を表示する{{tab}}を選択してください。', + pathLabel: 'パス', + resolvedSource: '解決後ソース', + loadingMarkdown: 'Markdownコンテンツを読み込み中...', + failedLoadContent: 'コンテンツの読み込みに失敗しました', + retryContent: '再読み込み', + noMarkdown: 'Markdownコンテンツはありません。', + }, + }, + }, } as const; if (!i18n.isInitialized) { diff --git a/ui/src/lib/locales.ts b/ui/src/lib/locales.ts index ca719231..c9f82d5e 100644 --- a/ui/src/lib/locales.ts +++ b/ui/src/lib/locales.ts @@ -1,6 +1,6 @@ export const LOCALE_STORAGE_KEY = 'ccs-ui-locale'; -export const SUPPORTED_LOCALES = ['en', 'zh-CN', 'vi'] as const; +export const SUPPORTED_LOCALES = ['en', 'zh-CN', 'vi', 'ja'] as const; export type AppLocale = (typeof SUPPORTED_LOCALES)[number]; @@ -10,6 +10,7 @@ export function isSupportedLocale(locale: string): locale is AppLocale { export function normalizeLocale(locale: string | null | undefined): AppLocale { if (!locale) return 'en'; + if (locale.toLowerCase().startsWith('ja')) return 'ja'; if (locale.toLowerCase().startsWith('zh')) return 'zh-CN'; if (locale.toLowerCase().startsWith('vi')) return 'vi'; return 'en'; diff --git a/ui/tests/unit/ui/i18n/language-switcher.test.tsx b/ui/tests/unit/ui/i18n/language-switcher.test.tsx index 80f6ced7..8a5a100e 100644 --- a/ui/tests/unit/ui/i18n/language-switcher.test.tsx +++ b/ui/tests/unit/ui/i18n/language-switcher.test.tsx @@ -8,6 +8,7 @@ import { LOCALE_STORAGE_KEY, normalizeLocale, persistLocale, + SUPPORTED_LOCALES, } from '@/lib/locales'; function flattenKeys(node: unknown, prefix = '', out = new Set()): Set { @@ -91,22 +92,28 @@ describe('Dashboard i18n', () => { expect(localStorageMock.setItem).toHaveBeenCalledWith(LOCALE_STORAGE_KEY, 'zh-CN'); }, 10000); - it('supports Vietnamese locale in switcher and persistence', async () => { - render(); + it.each([ + { locale: 'vi', label: 'Vietnamese', browserLocale: 'vi-VN' }, + { locale: 'ja', label: 'Japanese', browserLocale: 'ja-JP' }, + ])( + 'supports $locale locale in switcher and persistence', + async ({ locale, label, browserLocale }) => { + render(); - await userEvent.click(screen.getByRole('combobox')); - await userEvent.click(await screen.findByText('Vietnamese')); + await userEvent.click(screen.getByRole('combobox')); + await userEvent.click(await screen.findByText(label)); - await waitFor(() => { - expect(i18n.language).toBe('vi'); - }); - expect(localStorageMock.setItem).toHaveBeenCalledWith(LOCALE_STORAGE_KEY, 'vi'); - expect(normalizeLocale('vi-VN')).toBe('vi'); - }); + await waitFor(() => { + expect(i18n.language).toBe(locale); + }); + expect(localStorageMock.setItem).toHaveBeenCalledWith(LOCALE_STORAGE_KEY, locale); + expect(normalizeLocale(browserLocale)).toBe(locale); + } + ); it('restores locale from persisted storage', () => { - persistLocale('vi'); - expect(getInitialLocale()).toBe('vi'); + persistLocale('ja'); + expect(getInitialLocale()).toBe('ja'); }); it('shows Chinese labels on translated settings tabs', async () => { @@ -119,32 +126,38 @@ describe('Dashboard i18n', () => { expect(screen.getByText('认证')).toBeInTheDocument(); }); - it('keeps vi translation keys in parity with en and preserves placeholders', () => { - const resources = i18n.options.resources as - | Record }> - | undefined; + it.each(SUPPORTED_LOCALES.filter((locale) => locale !== 'en'))( + 'keeps %s translation keys in parity with en and preserves placeholders', + (locale) => { + const resources = i18n.options.resources as + | Record }> + | undefined; - const enTranslation = resources?.en?.translation; - const viTranslation = resources?.vi?.translation; + const enTranslation = resources?.en?.translation; + const localeTranslation = resources?.[locale]?.translation; - expect(enTranslation).toBeDefined(); - expect(viTranslation).toBeDefined(); + expect(enTranslation).toBeDefined(); + expect(localeTranslation).toBeDefined(); - const enKeys = flattenKeys(enTranslation); - const viKeys = flattenKeys(viTranslation); + const enKeys = flattenKeys(enTranslation); + const localeKeys = flattenKeys(localeTranslation); - expect([...viKeys].sort()).toEqual([...enKeys].sort()); + expect([...localeKeys].sort()).toEqual([...enKeys].sort()); - for (const key of enKeys) { - const enValue = key - .split('.') - .reduce((acc, part) => (acc as Record)?.[part], enTranslation); - const viValue = key - .split('.') - .reduce((acc, part) => (acc as Record)?.[part], viTranslation); + for (const key of enKeys) { + const enValue = key + .split('.') + .reduce((acc, part) => (acc as Record)?.[part], enTranslation); + const localeValue = key + .split('.') + .reduce( + (acc, part) => (acc as Record)?.[part], + localeTranslation + ); - if (typeof enValue !== 'string' || typeof viValue !== 'string') continue; - expect(collectPlaceholders(viValue)).toEqual(collectPlaceholders(enValue)); + if (typeof enValue !== 'string' || typeof localeValue !== 'string') continue; + expect(collectPlaceholders(localeValue)).toEqual(collectPlaceholders(enValue)); + } } - }); + ); });