Skip to content

feat(web-ui): optimize provider CRUD and session hydrate#159

Merged
ymkiux merged 5 commits into
mainfrom
feat/skip-provider-reload
May 12, 2026
Merged

feat(web-ui): optimize provider CRUD and session hydrate#159
ymkiux merged 5 commits into
mainfrom
feat/skip-provider-reload

Conversation

@ymkiux
Copy link
Copy Markdown
Collaborator

@ymkiux ymkiux commented May 12, 2026

Closes #128

Summary

  • i18n: add Japanese (ja) language support with 965 translated keys. Update i18n.mjs to accept ja locale and persist to localStorage. Add JA button to both language switchers in layout-header.html.
  • Provider CRUD: replace full loadAll() reloads with local state updates to avoid unnecessary API calls; add maskKeyLocal matching backend masking.
  • Session browser: skip re-hydration for sessions with exact zero message count (__messageCountExact: true) to prevent unnecessary 5-second polling.
  • OpenAI bridge: improve fallback chat-stream → Responses SSE conversion for Codex by:
    • omitting streamed upstream reasoning_content from user-visible output_text deltas;
    • treating an upstream chat stream that closes after a valid finish_reason as completed even if the upstream omits [DONE];
    • preserving the real failure path when the upstream closes with neither [DONE] nor finish_reason.

Verification

  • node tests/unit/openai-bridge-upstream-responses.test.mjs — 14/14 pass
  • npm run lint && npm test — pass, 531 tests
  • Real local bridge smoke test with isolated Codex home and Telepub DeepSeek-V4-pro through http://127.0.0.1:3737/bridge/openai/telepub/v1 — Codex CLI completed successfully and returned a normal response; no response.failed, Stream disconnected, or Reconnecting marker observed in the JSONL output.

Notes

  • Telepub /v1/models currently returns 404, so provider availability is validated through /v1/responses / chat fallback behavior rather than treating /models as authoritative.
  • Codex CLI 0.106.0 still prints its own fallback metadata warning for custom model slug DeepSeek-V4-pro; this PR keeps explicit context budget config in place and fixes the bridge stream failure that caused reconnects.

Summary by CodeRabbit

  • New Features

    • Added Japanese language support with new language selector buttons in the header and sidebar.
  • Bug Fixes & Improvements

    • Enhanced OpenAI bridge chat message processing and streaming response handling.
    • Provider management operations (add, update, delete) now apply changes instantly without requiring a full page reload.
    • Fixed session message count hydration for sessions with zero message counts.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 12, 2026

📝 Walkthrough

Walkthrough

Consolidates developer/system instructions into a single leading system message, omits internal reasoning from streamed output while honoring upstream finish reasons, updates provider CRUD/model flows to mutate local state with masked keys (no full reload), adds Japanese i18n/ui, and fixes session hydration gating.

Changes

OpenAI Bridge Message Normalization & Streaming

Layer / File(s) Summary
Role normalization and system message consolidation
cli/openai-bridge.js
toRole() now maps developer to system. mergeLeadingSystemMessages() consolidates/deduplicates all leading system-derived segments plus body.instructions into a single leading system message for chat conversion.
Streaming delta filtering & finish tracking
cli/openai-bridge.js
Streamed chunk→SSE conversion no longer forwards delta.reasoning_content; only delta.content is appended to assistant output. Streaming state tracks upstream choice.finish_reason (state.sawFinishReason) and treats it as valid termination to avoid false “ended before [DONE]” failures.
OpenAI bridge tests (fallback & streaming)
tests/unit/openai-bridge-upstream-responses.test.mjs
Updated SSE tests assert omission of reasoning_content and preservation of content deltas; new tests validate completion when upstream sends finish_reason without DONE and merging of developer/AGENTS.md into a single leading system message on fallback and SSE paths.

Provider Management Local State Optimization

Layer / File(s) Summary
Key masking and local state updates for provider operations
web-ui/modules/app.methods.providers.mjs
Adds maskKeyLocal. addProvider, deleteProvider, updateProvider, addModel, and removeModel now update providersList and provider models locally (masked key/hasKey maintained) instead of triggering loadAll() post-success.
Test fixture and assertion updates
tests/unit/provider-switch-regression.test.mjs, tests/unit/providers-validation.test.mjs
Fixtures add a local providersList. Tests now expect loadAllCalls to remain zero and verify local mutations (url update, hasKey, provider append, model add/remove) instead of full reload behavior.

Session Message Count Hydration Fix

Layer / File(s) Summary
Message count hydration condition
web-ui/modules/app.methods.session-browser.mjs
hydrateVisibleSessionListMessageCounts() now allows hydration of entries with messageCountRaw === 0 when session.__messageCountExact is not set.

i18n and Header Language UI

Layer / File(s) Summary
DICT formatting normalization
web-ui/modules/i18n.dict.mjs
Whitespace/comma/newline normalization across zh, ja, and en DICT entries; no string value changes.
Language logic and header buttons
web-ui/modules/i18n.mjs, web-ui/partials/index/layout-header.html
normalizeLang() recognizes ja; initI18n()/setLang() set document.documentElement.lang to en/ja/zh-CN. Adds ja language buttons in header and side-rail wired to setLang('ja').

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • awsl233777
  • SurviveM

Poem

A rabbit trims the chatter down,
Merges systems, keeps no frown.
No hidden reasons spill or show,
Providers update, quick and slow.
Nihongo buttons say kon'nichiwa! 🐰✨

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Title check ⚠️ Warning The title 'feat(web-ui): optimize provider CRUD and session hydrate' primarily describes web-UI optimizations, but the changeset includes substantial bridge improvements (OpenAI chat-to-Responses conversion, streaming fixes) and Japanese language support, which are not reflected in the title. Consider a more comprehensive title such as 'feat: optimize provider CRUD, session hydrate, bridge streaming, and add Japanese support' or split into multiple PRs to align title with scope.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/skip-provider-reload

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@ymkiux ymkiux force-pushed the feat/skip-provider-reload branch from 4d87417 to f04b8c2 Compare May 12, 2026 04:45
@ymkiux ymkiux changed the title feat(web-ui): skip full provider reload on CRUD ops feat(web-ui): optimize provider CRUD and session hydrate May 12, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cli/openai-bridge.js`:
- Around line 458-473: The loop currently hoists every system message by pushing
them into segments and removing them from the rest array, which changes message
order; change the logic to only extract leading system messages (those before
the first non-system message) and stop extracting once the first non-system
message is seen so subsequent system messages remain in-place. Modify the loop
over messages (variables: messages, pushSegment, rest) to track whether a
non-system message has been encountered (e.g., encounteredNonSystem flag), while
encounteredNonSystem is false process system messages exactly as now (string or
array parts) and skip them from rest; once you hit the first non-system message
set the flag and from then on push every message (including later system
messages) into rest preserving original order.

In `@web-ui/modules/app.methods.providers.mjs`:
- Line 181: The local mutation code assumes this.providersList and p.models are
always arrays (e.g., the spread assignment this.providersList =
[...this.providersList, newProvider] and iterations over p.models); add
defensive guards that coerce/initialize these to arrays before mutating: use
Array.isArray(this.providersList) ? this.providersList : [] when spreading or
pushing, ensure p is truthy and use Array.isArray(p.models) ? p.models : []
before mapping/filtering/assigning, and prefer non-mutating updates that create
a safe copy (e.g., const list = Array.isArray(this.providersList) ?
[...this.providersList] : []; list.push(newProvider); this.providersList =
list). Apply the same pattern for all affected symbols (this.providersList,
p.models, newProvider, and any mapping/filtering code around those lines) so
backend updates won't be masked by client-side exceptions.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 99d82b2d-ecbf-4059-aaab-80751600ddb4

📥 Commits

Reviewing files that changed from the base of the PR and between 2f63044 and f663019.

📒 Files selected for processing (6)
  • cli/openai-bridge.js
  • tests/unit/openai-bridge-upstream-responses.test.mjs
  • tests/unit/provider-switch-regression.test.mjs
  • tests/unit/providers-validation.test.mjs
  • web-ui/modules/app.methods.providers.mjs
  • web-ui/modules/app.methods.session-browser.mjs
📜 Review details
🔇 Additional comments (6)
web-ui/modules/app.methods.session-browser.mjs (1)

696-696: LGTM!

tests/unit/providers-validation.test.mjs (1)

82-87: LGTM!

tests/unit/provider-switch-regression.test.mjs (1)

68-74: LGTM!

Also applies to: 261-264, 292-295

web-ui/modules/app.methods.providers.mjs (1)

51-55: LGTM!

cli/openai-bridge.js (1)

284-287: LGTM!

Also applies to: 836-842

tests/unit/openai-bridge-upstream-responses.test.mjs (1)

172-224: LGTM!

Also applies to: 814-961

Comment thread cli/openai-bridge.js
Comment on lines +458 to +473
for (const msg of messages) {
if (msg && msg.role === 'system') {
const content = msg.content;
if (typeof content === 'string') {
pushSegment(content);
} else if (Array.isArray(content)) {
for (const part of content) {
if (part && typeof part === 'object' && typeof part.text === 'string') {
pushSegment(part.text);
}
}
}
continue;
}
rest.push(msg);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Preserve non-leading system message order during merge

Line 458 currently captures every system message in the array, not just leading ones. That hoists later system messages to the front and can change model behavior for mixed-role conversations.

🔧 Proposed fix
 function mergeLeadingSystemMessages(messages, leadingInstructions) {
@@
-    const rest = [];
-    for (const msg of messages) {
-        if (msg && msg.role === 'system') {
+    const rest = [];
+    let stillLeading = true;
+    for (const msg of messages) {
+        if (stillLeading && msg && msg.role === 'system') {
             const content = msg.content;
             if (typeof content === 'string') {
                 pushSegment(content);
@@
             }
             continue;
         }
+        stillLeading = false;
         rest.push(msg);
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
for (const msg of messages) {
if (msg && msg.role === 'system') {
const content = msg.content;
if (typeof content === 'string') {
pushSegment(content);
} else if (Array.isArray(content)) {
for (const part of content) {
if (part && typeof part === 'object' && typeof part.text === 'string') {
pushSegment(part.text);
}
}
}
continue;
}
rest.push(msg);
}
const rest = [];
let stillLeading = true;
for (const msg of messages) {
if (stillLeading && msg && msg.role === 'system') {
const content = msg.content;
if (typeof content === 'string') {
pushSegment(content);
} else if (Array.isArray(content)) {
for (const part of content) {
if (part && typeof part === 'object' && typeof part.text === 'string') {
pushSegment(part.text);
}
}
}
continue;
}
stillLeading = false;
rest.push(msg);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cli/openai-bridge.js` around lines 458 - 473, The loop currently hoists every
system message by pushing them into segments and removing them from the rest
array, which changes message order; change the logic to only extract leading
system messages (those before the first non-system message) and stop extracting
once the first non-system message is seen so subsequent system messages remain
in-place. Modify the loop over messages (variables: messages, pushSegment, rest)
to track whether a non-system message has been encountered (e.g.,
encounteredNonSystem flag), while encounteredNonSystem is false process system
messages exactly as now (string or array parts) and skip them from rest; once
you hit the first non-system message set the flag and from then on push every
message (including later system messages) into rest preserving original order.

nonDeletable: false,
nonEditable: false
};
this.providersList = [...this.providersList, newProvider];
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Defensive guards are missing in local mutation paths.

Line 181, Line 263, Line 370, Line 424, and Line 451 assume this.providersList and p.models are always valid arrays. If state is partially hydrated or malformed, these operations throw after successful API calls, and the catch path shows failure while backend state already changed.

💡 Proposed hardening diff
@@
 function maskKeyLocal(key) {
@@
 }
 
+function asArray(value) {
+    return Array.isArray(value) ? value : [];
+}
+
@@
-                this.providersList = [...this.providersList, newProvider];
+                this.providersList = [...asArray(this.providersList), newProvider];
@@
-                this.providersList = this.providersList.filter(p => p.name !== name);
+                this.providersList = asArray(this.providersList).filter((p) => p && p.name !== name);
@@
-                    this.providersList = this.providersList.map(p => ({
+                    this.providersList = asArray(this.providersList).map((p) => ({
                         ...p,
                         current: p.name === res.provider
                     }));
@@
-                this.providersList = this.providersList.map(p => {
+                this.providersList = asArray(this.providersList).map((p) => {
+                    if (!p || typeof p !== 'object') return p;
                     if (p.name === validation.name) {
@@
-                this.providersList = this.providersList.map(p => {
+                this.providersList = asArray(this.providersList).map((p) => {
+                    if (!p || typeof p !== 'object') return p;
                     if (p.name === this.currentProvider) {
-                            const exists = p.models.some(m => m.id === modelName);
+                            const models = asArray(p.models);
+                            const exists = models.some((m) => m && m.id === modelName);
                             if (!exists) {
                                 return {
                                     ...p,
-                                    models: [...p.models, { id: modelName, name: modelName, cost: null, contextWindow: undefined, maxTokens: undefined }]
+                                    models: [...models, { id: modelName, name: modelName, cost: null, contextWindow: undefined, maxTokens: undefined }]
                                 };
                             }
@@
-                    this.providersList = this.providersList.map(p => {
+                    this.providersList = asArray(this.providersList).map((p) => {
+                        if (!p || typeof p !== 'object') return p;
                         if (p.name === this.currentProvider) {
                             return {
                                 ...p,
-                                models: p.models.filter(m => m.id !== model)
+                                models: asArray(p.models).filter((m) => m && m.id !== model)
                             };
                         }

Also applies to: 263-264, 370-380, 424-435, 451-459

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web-ui/modules/app.methods.providers.mjs` at line 181, The local mutation
code assumes this.providersList and p.models are always arrays (e.g., the spread
assignment this.providersList = [...this.providersList, newProvider] and
iterations over p.models); add defensive guards that coerce/initialize these to
arrays before mutating: use Array.isArray(this.providersList) ?
this.providersList : [] when spreading or pushing, ensure p is truthy and use
Array.isArray(p.models) ? p.models : [] before mapping/filtering/assigning, and
prefer non-mutating updates that create a safe copy (e.g., const list =
Array.isArray(this.providersList) ? [...this.providersList] : [];
list.push(newProvider); this.providersList = list). Apply the same pattern for
all affected symbols (this.providersList, p.models, newProvider, and any
mapping/filtering code around those lines) so backend updates won't be masked by
client-side exceptions.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@web-ui/modules/i18n.dict.mjs`:
- Around line 1040-2080: The ja translation object contains many duplicate keys
(e.g., modal.configTemplate.title, app.loadingConfig, brand.kicker.workspace,
claude.action.* and others) that cause silent overwrites; audit the entire ja
block, remove or consolidate duplicated entries so each translation key appears
only once, decide which value to keep for conflicts (preserve the intended/most
recent or consolidate into a single entry), and run a quick script/linter to
validate uniqueness before committing.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 54bf2e95-d931-471f-8b19-5c5c876337da

📥 Commits

Reviewing files that changed from the base of the PR and between f663019 and 60f089d.

📒 Files selected for processing (5)
  • cli/openai-bridge.js
  • tests/unit/openai-bridge-upstream-responses.test.mjs
  • web-ui/modules/i18n.dict.mjs
  • web-ui/modules/i18n.mjs
  • web-ui/partials/index/layout-header.html
🚧 Files skipped from review as they are similar to previous changes (1)
  • cli/openai-bridge.js
📜 Review details
🧰 Additional context used
🪛 Biome (2.4.15)
web-ui/modules/i18n.dict.mjs

[error] 1090-1090: This property is later overwritten by an object member with the same name.

(lint/suspicious/noDuplicateObjectKeys)


[error] 1433-1433: This property is later overwritten by an object member with the same name.

(lint/suspicious/noDuplicateObjectKeys)


[error] 1435-1435: This property is later overwritten by an object member with the same name.

(lint/suspicious/noDuplicateObjectKeys)


[error] 1508-1508: This property is later overwritten by an object member with the same name.

(lint/suspicious/noDuplicateObjectKeys)


[error] 1511-1511: This property is later overwritten by an object member with the same name.

(lint/suspicious/noDuplicateObjectKeys)


[error] 1624-1624: This property is later overwritten by an object member with the same name.

(lint/suspicious/noDuplicateObjectKeys)

🔇 Additional comments (7)
tests/unit/openai-bridge-upstream-responses.test.mjs (4)

172-229: LGTM!


231-286: LGTM!


871-949: LGTM!


951-1018: LGTM!

web-ui/modules/i18n.mjs (1)

5-10: LGTM!

Also applies to: 23-36, 37-52

web-ui/partials/index/layout-header.html (1)

110-115: LGTM!

Also applies to: 323-328

web-ui/modules/i18n.dict.mjs (1)

1040-2080: ⚡ Quick win

No issues found. The Japanese translation block is complete and consistent.

Verification confirms the ja locale has all 965 keys present, matching zh and en perfectly. No missing keys or fallback behavior concerns.

Comment on lines +1040 to +2080
ja: {

// Global
'lang.zh': '中国語',
'lang.en': 'English',
'lang.label': '言語',
'nav.topTabs.aria': 'ナビゲーション',

// Common
'common.all': 'すべて',
'common.copy': 'コピー',
'common.edit': '編集',
'common.install': 'インストール',
'common.update': 'アップグレード',
'common.uninstall': 'アンインストール',
'common.official': '公式',
'common.custom': 'カスタム',
'common.rules': 'ルール',
'common.troubleshooting': 'トラブルシューティング',
'common.command': 'コマンド',
'common.mirror': 'ミラー',
'common.packageManager': 'パッケージマネージャー',
'common.action': '操作',
'common.targets': 'ターゲット数',
'common.currentPm': '現在のパッケージマネージャー',
'common.currentAction': '現在の操作',
'common.mirrorActive': 'ミラー有効',
'common.defaultOfficial': '公式デフォルト',
'common.cancel': 'キャンセル',
'common.confirm': '確認',
'common.add': '追加',
'common.save': '保存',
'common.saveApply': '保存して適用',
'common.close': '閉じる',
'common.delete': '削除',
'common.clear': 'クリア',
'common.show': '表示',
'common.hide': '非表示',
'common.detail': '詳細',
'common.refresh': '更新',
'common.refreshing': '更新中...',
'common.loading': '読み込み中...',
'common.saving': '保存中...',
'common.sending': '送信中...',
'common.scanning': 'スキャン中...',
'common.export': 'エクスポート',
'common.import': 'インポート',
'common.apply': '適用',
'common.applying': '適用中...',
'common.confirming': '確認中...',
'common.writeToEditor': 'エディタに書き込み',
'common.refreshFromText': 'テキストから更新',
'common.backToEdit': '編集に戻る',
'common.selectAll': 'すべて選択',
'common.unselectAll': '選択解除',
'common.resetFilters': 'フィルターをリセット',
'common.notEditable': '編集不可',
'common.notDeletable': '削除不可',
'common.notLoaded': '未読み込み',
'common.exists': '存在します',
'common.notExistsWillCreateOnApply': '存在しません。適用時に作成されます',
'common.notExistsWillCreateOnSave': '存在しません。保存時に作成されます',
'common.none': 'なし',
'cli.missing.title': '{name} CLI がインストールされていません',
'cli.missing.subtitle': '{name} CLI をインストールしてからこのページをご利用ください。',
'cli.missing.openDocs': 'インストールガイドを開く',
'cli.missing.commandAria': '{name} CLI インストールコマンド',

// Brand
'brand.kicker.workspace': 'ワークスペース',
'brand.subtitle.localConfigSessionsWorkspace': 'ローカル設定とセッションワークスペース',

// Confirm dialog
'confirm.aria': '操作確認',
'confirm.title.default': '操作を確認してください',
'confirm.ok': '確認',
'confirm.cancel': 'キャンセル',

// Shared fields
'field.name': '名前',
'field.configName': '設定名',
'field.apiEndpoint': 'API エンドポイント',
'field.apiKey': '認証キー',
'field.baseUrl': 'Base URL',
'field.provider': 'プロバイダー',
'field.providerName': 'プロバイダー名',
'field.modelName': 'モデル名',
'field.model': 'モデル',
'field.message': 'メッセージ',
'field.varName': '変数名',
'field.targetFile': '対象ファイル',
'field.modelId': 'モデル ID',
'field.displayName': '表示名',
'field.contextAndMaxOutput': 'コンテキストと最大出力',
'field.apiType': 'API タイプ',
'field.env': '環境変数',
'field.allow': '許可',
'field.deny': '拒否',

// Shared placeholders/hints
'placeholder.providerNameExample': '例: myapi',
'placeholder.apiEndpointExample': 'https://api.example.com/v1',
'placeholder.providerName': 'プロバイダー名',
'placeholder.keepUnchanged': '空白のままにすると変更されません',
'hint.keepKeyUnchanged': '空白のままにするとキーは変更されません',
'placeholder.modelExample': '例: gpt-5',
'placeholder.configNameExample': '例: 智譜GLM',
'placeholder.apiKeyExampleClaude': 'sk-ant-...',
'placeholder.baseUrlExampleClaude': 'https://open.bigmodel.cn/api/anthropic',
'placeholder.selectProvider': 'プロバイダーを選択してください',

// Roles / labels
'role.you': 'あなた',
'role.provider': 'プロバイダー',
'label.model': 'モデル:',

// Top tabs
'tab.dashboard': '概要',
'tab.docs': 'ドキュメント',
'tab.config': '設定',
'tab.config.codex': 'Codex',
'tab.config.claude': 'Claude',
'tab.config.openclaw': 'OpenClaw',
'tab.sessions': 'セッション',
'tab.usage': '使用量',
'tab.orchestration': 'タスク',
'tab.market': 'Skills',
'tab.plugins': 'プラグイン',
'tab.settings': '設定',

// Side rail section titles
'side.overview': '概要',
'side.docs': 'ドキュメント',
'side.config': '設定',
'side.sessions': 'セッション',
'side.plugins': 'プラグイン',
'side.system': 'システム',
'side.orchestration': 'タスク',
'side.skills': 'Skills',

// Side rail items
'side.overview.doctor': 'Doctor パネル',
'side.overview.doctor.meta': '概要 / 診断 / ジャンプ',
'side.docs.cliInstall': 'CLI インストール',
'side.docs.cliInstall.meta': 'インストール / 更新 / 削除',
'side.config.codex': 'Codex',
'side.config.codex.meta': 'Provider / Model',
'side.config.claude': 'Claude Code',
'side.config.claude.meta': 'Claude Settings',
'side.config.openclaw': 'OpenClaw',
'side.config.openclaw.meta': 'JSON5 / AGENTS',
'side.sessions.browser': 'セッション閲覧',
'side.sessions.browser.meta': '閲覧 / エクスポート / クリーンアップ',
'side.plugins.tools': 'プロンプトツール',
'side.plugins.tools.meta': 'テンプレート / 変数',
'side.system.settings': '実行設定',
'side.system.settings.meta': 'データ / バックアップ',

// Header titles
'kicker.dashboard': 'Doctor',
'kicker.config': 'Configuration',
'kicker.sessions': 'Sessions',
'kicker.usage': 'Usage',
'kicker.orchestration': 'Tasks',
'kicker.market': 'Skills',
'kicker.plugins': 'Plugins',
'kicker.docs': 'Docs',
'kicker.settings': 'Settings',

'title.dashboard': 'Dashboard / Doctor',
'title.config': 'ローカル設定コンソール',
'title.sessions': 'セッションとエクスポート',
'title.usage': 'ローカル使用量と推移',
'title.orchestration': 'タスクオーケストレーション',
'title.market': 'Skills インストールと同期',
'title.plugins': 'プラグインとテンプレート',
'title.docs': 'CLI インストールとドキュメント',
'title.settings': 'システムとデータ設定',

'subtitle.dashboard': '集約状態と診断エントリ。',
'subtitle.config': 'ローカル設定とモデルの管理。',
'subtitle.sessions': 'セッションの閲覧とエクスポート。',
'subtitle.usage': '過去 7 / 30 日間の使用量を表示。',
'subtitle.orchestration': 'ローカルタスクの計画・キュー・実行・振り返り。',
'subtitle.market': 'ローカル Skills の管理。',
'subtitle.plugins': 'テンプレート化されたプロンプトと再利用可能なプラグインの管理。',
'subtitle.docs': 'CLI インストールコマンドとトラブルシューティング。',
'subtitle.settings': 'ダウンロード・ディレクトリ・ゴミ箱の管理。',

'dashboard.doctor.title': 'Doctor',
'dashboard.doctor.runChecks': 'チェックを実行',
'dashboard.doctor.checking': 'チェック中...',
'dashboard.doctor.export': 'レポートをエクスポート',
'dashboard.doctor.export.json': 'JSON エクスポート',
'dashboard.doctor.export.md': 'Markdown エクスポート',
'dashboard.doctor.open': '開く',
'doctor.action.openConfig': 'Config を開く',
'doctor.action.checkProvider': 'Provider 設定をチェック',
'doctor.action.openUsage': 'Usage を開く',
'doctor.action.openSessions': 'Sessions を開く',
'doctor.action.openTasks': 'Tasks を開く',
'doctor.action.viewTaskLogs': 'Tasks / Logs を表示',
'doctor.action.openSkills': 'Skills を開く',
'doctor.issue.configNotReady.problem': '設定ファイルが準備できていません',
'doctor.issue.configNotReady.impact': 'provider/model が読み取れず、モデル一覧とリクエストが利用できなくなる可能性があります。',
'doctor.issue.providerUnreachable.problem.remote-model-probe-unreachable': 'Provider に到達できません',
'doctor.issue.providerUnreachable.problem.remote-model-probe-auth-failed': 'Provider 認証失敗',
'doctor.issue.providerUnreachable.problem.remote-model-probe-not-found': 'Provider が 404 を返しました',
'doctor.issue.providerUnreachable.problem.remote-model-probe-http-error': 'Provider が HTTP エラーを返しました',
'doctor.issue.providerUnreachable.problem.remote-model-probe-error': 'Provider プローブ失敗',
'doctor.issue.providerUnreachable.problem.unknown': 'Provider が利用できません',
'doctor.issue.providerUnreachable.impactAuth': '認証失敗によりモデル一覧/会話リクエストが 401/403 を返します。',
'doctor.issue.providerUnreachable.impactNetwork': 'リモートに到達できないためモデル一覧/会話リクエストが失敗またはタイムアウトします。',
'doctor.issue.configHealthFailed.problem': '設定ヘルスチェックが失敗しました',
'doctor.issue.configHealthFailed.impact': '一部の機能が利用できなかったり、期待通りの動作をしない可能性があります。',
'doctor.issue.usageError.problem': 'Usage 統計異常',
'doctor.issue.usageError.impact': 'Usage ページでトレンド/サマリーが表示できず、Doctor の使用量診断も欠落します。',
'doctor.issue.usageMissingModel.problem': '一部のセッションにモデル情報がありません',
'doctor.issue.usageMissingModel.impact': '使用量の帰属とコスト見積もりが不正確になります。',
'doctor.issue.tasksError.problem': 'Tasks 状態の読み取りに失敗しました',
'doctor.issue.tasksError.impact': 'オーケストレーションキュー/実行記録が表示できなくなる可能性があります。',
'doctor.issue.tasksFailed.problem': '失敗したタスク実行があります',
'doctor.issue.tasksFailed.impact': '自動化パイプラインが中断される可能性があります。ログを確認して再試行または入力を修正してください。',
'doctor.issue.skillsError.problem': 'Skills 一覧の読み取りに失敗しました',
'doctor.issue.skillsError.impact': 'Skills ページが正常に表示またはインストールできなくなります。',
'doctor.issue.skillsRootMissing.problem': 'Skills ディレクトリが存在しません',
'doctor.issue.skillsRootMissing.impact': 'Skills のインストール/スキャンが空になります。Settings/Docs でガイドに従ってディレクトリを初期化してください。',
'doctor.issue.skillsMissingFiles.problem': 'skill.json が欠落しているスキルがあります',
'doctor.issue.skillsMissingFiles.impact': '一部のスキルが実行または同期できなくなります。',
'dashboard.card.config': '設定',
'dashboard.card.sessions': 'セッション',
'dashboard.card.usage': '使用量',
'dashboard.card.tasks': 'タスク',
'dashboard.card.skills': 'Skills',
'dashboard.kv.model': 'モデル',
'dashboard.kv.issue': '異常',
'dashboard.kv.active': '現在',
'dashboard.kv.sessions': 'セッション数',
'dashboard.kv.missingModel': 'モデル欠落',
'dashboard.kv.blockers': 'ブロッカー',
'dashboard.kv.runs': '実行',
'dashboard.kv.target': 'ターゲット',
'dashboard.kv.root': 'ディレクトリ',
'dashboard.status.health': '健全',
'dashboard.status.busy': 'ビジー',
'dashboard.status.models': 'モデル',
'dashboard.busy.init': '初期化',
'dashboard.busy.sessions': 'セッション読み込み',
'dashboard.busy.models': 'モデル読み込み',
'dashboard.busy.configApply': '設定適用',
'dashboard.busy.agents': 'AGENTS 保存',
'dashboard.busy.skills': 'Skills 管理',
'dashboard.busy.tasks': 'タスクオーケストレーション',
'dashboard.busy.idle': 'アイドル',
'dashboard.message.none': 'メッセージなし',
'dashboard.sessionSource.codex': 'Codex',
'dashboard.sessionSource.claude': 'Claude Code',
'dashboard.sessionSource.gemini': 'Gemini CLI',
'dashboard.sessionSource.codebuddy': 'CodeBuddy Code',
'dashboard.sessionSource.all': 'すべて',
'dashboard.sessionPath.all': 'すべてのパス',
'dashboard.sessionQuery.unsupported': '現在のソースは非対応',
'dashboard.sessionQuery.unset': '未設定',
'dashboard.healthStatus.failRead': '読み取り失敗',
'dashboard.healthStatus.initializing': '初期化中',
'dashboard.healthStatus.ok': '正常',
'dashboard.modelStatus.loading': '読み込み中',
'dashboard.modelStatus.error': '読み込みエラー',
'dashboard.modelStatus.ok': '正常',
'dashboard.health.ok': 'チェック通過',
'dashboard.health.fail': 'チェック失敗',
'dashboard.health.issues': '{count} 件の問題',
'dashboard.issues.title': '{count} 件の対応可能な問題が見つかりました',
'dashboard.state.loading': '読み込み中',
'dashboard.state.ready': '準備完了',
'dashboard.state.idle': '未読み込み',
'dashboard.none': 'なし',
'dashboard.sessions.count': '{count} 件のセッション',
'dashboard.usage.range': '範囲 {value}',
'dashboard.tasks.queue': '実行 {running} / キュー {queued}',
'dashboard.skills.count': 'インストール済 {installed} / インポート可 {importable}',

// Plugins panel
'plugins.sidebar.title': 'Plugins',
'plugins.sidebar.note': '再利用可能な標準化ツール。将来のカスタムプラグインも同じ構造に従います。',
'plugins.sidebar.ariaList': 'プラグイン一覧',
'plugins.main.ariaWorkspace': 'プラグインワークスペース',
'plugins.refresh': '更新',
'plugins.refreshing': '更新中...',
'plugins.promptTemplates.title': 'プロンプトテンプレート',
'plugins.promptTemplates.subtitle': '内蔵:コードコメントのブラッシュアップ({{code}})。コピーして入力欄に貼り付けてください。',
'plugins.promptTemplates.mode.aria': 'プロンプトテンプレートモード',
'plugins.promptTemplates.mode.compose': '生成',
'plugins.promptTemplates.mode.manage': '管理',
'plugins.promptTemplates.compose.selectTemplate': 'テンプレートを選択',
'plugins.promptTemplates.compose.chooseTemplate': 'テンプレートを選択',
'plugins.promptTemplates.compose.chooseTemplateHint': 'テンプレートを選択して生成を開始してください。',
'plugins.promptTemplates.compose.builtinSuffix': '(内蔵)',
'plugins.promptTemplates.compose.empty': '利用可能なテンプレートがありません。',
'plugins.promptTemplates.compose.varsHint': '変数の入力と追加は「管理」で行ってください。',
'plugins.promptTemplates.compose.missingCount': '{count} 項目未入力',
'plugins.promptTemplates.compose.jumpToMissing': '未入力にジャンプ',
'plugins.promptTemplates.compose.goManage': '管理で変数を入力',
'plugins.promptTemplates.compose.outputTitle': '生成結果',
'plugins.promptTemplates.compose.outputHint': '「コピー」をクリックして Codex/Claude の入力欄に貼り付けてください。',
'plugins.promptTemplates.compose.outputAria': '生成結果(プロンプト)',
'plugins.promptTemplates.compose.copy': 'コピー',
'plugins.promptTemplates.manage.searchAria': 'テンプレート検索',
'plugins.promptTemplates.manage.searchPlaceholder': 'テンプレート検索',
'plugins.promptTemplates.manage.create': '新規作成',
'plugins.promptTemplates.manage.export': 'エクスポート',
'plugins.promptTemplates.manage.import': 'インポート',
'plugins.promptTemplates.manage.loading': 'テンプレート読み込み中...',
'plugins.promptTemplates.manage.empty': 'テンプレートがありません。',
'plugins.promptTemplates.manage.vars': '{count} vars',
'plugins.promptTemplates.manage.builtin': 'built-in',
'plugins.promptTemplates.manage.custom': 'custom',
'plugins.promptTemplates.manage.newTemplateName': '新しいテンプレート',
'plugins.promptTemplates.editor.selectHint': '編集するテンプレートを選択してください。',
'plugins.promptTemplates.editor.namePlaceholder': 'テンプレート名',
'plugins.promptTemplates.editor.nameAria': 'テンプレート名',
'plugins.promptTemplates.editor.duplicate': '複製',
'plugins.promptTemplates.editor.delete': '削除',
'plugins.promptTemplates.editor.save': '保存',
'plugins.promptTemplates.editor.builtinReadOnly': '内蔵テンプレートは読み取り専用です。編集/保存/削除/複製はできません。',
'plugins.promptTemplates.editor.descPlaceholder': '説明(任意)',
'plugins.promptTemplates.editor.descAria': 'テンプレート説明',
'plugins.promptTemplates.editor.templateLabel': 'テンプレート',
'plugins.promptTemplates.editor.templateAria': 'テンプレート内容',
'plugins.promptTemplates.editor.templatePlaceholder': 'ここにテンプレートを記述します。{{var}} プレースホルダーを使用してください。',
'plugins.promptTemplates.vars.title': '変数',
'plugins.promptTemplates.vars.hint': 'テンプレートから検出されました。入力後に最終プロンプトをレンダリングできます。',
'plugins.promptTemplates.vars.reset': 'リセット',
'plugins.promptTemplates.vars.empty': '変数が検出されませんでした。',
'plugins.promptTemplates.vars.valuePlaceholder': '変数値:{name}',
'plugins.promptTemplates.preview.title': 'プレビュー',
'plugins.promptTemplates.preview.hint': 'レンダリング結果(欠落変数は空に置換されます)。',
'plugins.promptTemplates.preview.copy': 'コピー',
'plugins.promptTemplates.preview.outputAria': 'レンダリング結果(プロンプト)',
'plugins.promptTemplates.noPluginSelected': '左側からプラグインを選択してください。',

'plugins.meta.attribution': '作成者:{createdBy} · メンテナー:{maintainers}',
'plugins.meta.createdBy': '作成者:{createdBy}',
'plugins.meta.maintainedBy': 'メンテナー:{maintainers}',

// Built-in prompt templates
'plugins.builtin.commentPolish.name': 'コードコメントブラッシュアップ',
'plugins.builtin.commentPolish.desc': '以下のコードコメントを軽く整えてください {{code}}',
'plugins.builtin.commentPolish.line1': '以下のコードコメントを軽く整えてください',
'plugins.builtin.ruleAck.name': 'ルール確認返信',
'plugins.builtin.ruleAck.desc': '【{{rule}}】に従って、受信確認を返してください',
'plugins.builtin.ruleAck.line1': '【{{rule}}】に従って、受信確認を返してください',

// Toasts
'toast.copy.empty': 'コピーする内容がありません',
'toast.copy.ok': 'コピーしました',
'toast.copy.fail': 'コピーに失敗しました',
'toast.save.ok': '保存しました',
'toast.save.fail': '保存に失敗しました',
'toast.delete.ok': '削除しました',
'toast.delete.fail': '削除に失敗しました',
'toast.export.empty': 'エクスポートする内容がありません',
'toast.export.ok': 'エクスポートしました',
'toast.export.fail': 'エクスポートに失敗しました',
'toast.import.ok': 'インポート成功',
'toast.import.fail': 'インポート失敗',
'toast.import.notAvailable': 'インポートは利用できません',
'toast.import.readFileFail': 'ファイル読み取り失敗',
'toast.import.invalidJson': '無効な JSON',
'toast.import.expectedArray': 'JSON は配列である必要があります',
'toast.export.notSupported': '現在エクスポートはサポートされていません',
'toast.plugins.loadFail': 'Plugins 読み込み失敗',
'toast.templates.builtinNotEditable': '内蔵テンプレートは編集できません',
'toast.templates.builtinNotModifiable': '内蔵テンプレートは変更できません。先に複製してから編集してください',
'toast.templates.nameRequired': 'テンプレート名は必須です',
'toast.templates.builtinNotDuplicable': '内蔵テンプレートは複製できません',
'toast.templates.builtinNotDeletable': '内蔵テンプレートは削除できません',
'toast.templates.deleteTitle': 'テンプレート削除',
'toast.templates.deleteMessage': '「{name}」を削除しますか?この操作は取り消せません。',
'toast.templates.deleteConfirm': '削除',
'toast.templates.deleteCancel': 'キャンセル',

// Basic modals
'modal.providerAdd.title': 'プロバイダー追加',
'modal.providerEdit.title': 'プロバイダー編集',
'modal.modelAdd.title': 'モデル追加',
'modal.modelManage.title': 'モデル管理',
'modal.claudeConfigAdd.title': 'Claude Code 設定追加',
'modal.claudeConfigEdit.title': 'Claude Code 設定編集',
'field.useBuiltinTransform': '内蔵変換を使用(OpenAI 形式互換)',
'hint.useBuiltinTransform': '有効時:書き込まれる base_url は codexmate 内蔵変換サービスを指します。Codex のトークンは codexmate に固定されます。',

// Config template / agents modals
'modal.configTemplate.title': 'Config テンプレートエディタ(手動確認適用)',
'modal.configTemplate.label': 'config.toml テンプレート',
'modal.configTemplate.placeholder': 'ここに config.toml テンプレート内容を編集してください',
'modal.configTemplate.mode.twoStep': '二段階確認:先に差分をプレビューし、その後適用します。',
'modal.configTemplate.mode.oneStep': '一段階適用:「適用」をクリックすると直接書き込みます。',
'diff.title.configTemplate': '差分プレビュー(config.toml)',
'diff.generating': '生成中...',
'diff.failed': '生成失敗',
'diff.noChanges': '変更が検出されませんでした',
'diff.hint.busy': '差分生成中または適用中のため、操作は一時的に利用できません。',
'diff.hint.failedBack': '差分プレビューに失敗しました。編集に戻って再試行してください。',
'diff.hint.noChangesBack': '変更が検出されませんでした。編集に戻って修正を続けるか、キャンセルして終了してください。',
'diff.hint.previewMode': '現在プレビューモードです。「適用」をクリックして書き込むか、「編集に戻る」で修正を続けてください。',

'modal.agents.export': 'エクスポート',
'modal.agents.copy': 'コピー',
'modal.agents.title': 'AGENTS.md エディタ',
'modal.agents.hint': '保存すると対象の AGENTS.md(config.toml と同階層)に書き込まれます。',
'modal.agents.targetFile': '対象ファイル',
'modal.agents.contentLabel': 'AGENTS.md 内容',
'modal.agents.placeholder': 'ここに AGENTS.md の内容を編集してください',
'modal.agents.unsaved.previewModeHint': 'プレビューモード:現在の変更はまだ保存されていません。「適用」をクリックするとファイルに書き込まれます。',
'modal.agents.unsaved.detectedHint': '未保存の変更が検出されました:ページを閉じるか適用する前に保存してください。',
'modal.agents.hint.shortcuts': 'ショートカット:Esc(差分プレビュー時は編集に戻る、編集時はウィンドウを閉じる)。',
'modal.agents.hint.twoStepSave': '保存は2段階:「確認」で差分をプレビューし、「適用」で保存します。',
'diff.tooLargeSkip': '内容が大きすぎるため、行単位の差分プレビューをスキップしました',
'diff.viewHint.preview': '現在プレビューモードです。「適用」で保存するか、「編集に戻る」で修正を続けてください。',
'diff.viewHint.truncated': '内容が大きすぎるためプレビューをスキップしました。「適用」で保存するか、「編集に戻る」で修正を続けてください。',

// Skills modal
'modal.skills.title': 'Skills 管理',
'modal.skills.subtitle': '現在のホストのローカル Skills を管理します。',
'modal.skills.target.aria': 'Skills 管理ターゲットを選択',
'modal.skills.rootDir': 'Skills ディレクトリ({label})',
'modal.skills.summary.target': 'インストールターゲット',
'modal.skills.summary.total': 'ローカル総数',
'modal.skills.summary.withSkill': 'SKILL.md あり',
'modal.skills.summary.missingSkill': 'SKILL.md なし',
'modal.skills.summary.importable': 'インポート可',
'modal.skills.panel.aria': 'Skills 管理パネル',
'modal.skills.local.title': 'ローカル Skills',
'modal.skills.local.note': '検索・フィルター・一括削除が可能です。',
'modal.skills.filter.keywordAria': '名前または説明で skill をフィルター',
'modal.skills.filter.keywordPlaceholder': 'ディレクトリ名/表示名/説明で検索',
'modal.skills.filter.statusAria': 'SKILL.md 状態で skill をフィルター',
'modal.skills.filter.status.all': 'すべての状態',
'modal.skills.filter.status.withSkill': 'SKILL.md ありのみ',
'modal.skills.filter.status.missingSkill': 'SKILL.md なしのみ',
'modal.skills.selection.stats': '選択 {selected}(フィルターヒット {filtered} / {total}、フィルター内選択 {visibleSelected})',
'modal.skills.empty.local': '管理可能な skill がありません。',
'modal.skills.empty.filtered': '現在のフィルター条件に一致する skill がありません。',
'modal.skills.pill.hasSkillFile': 'SKILL.md あり',
'modal.skills.pill.missingSkillFile': 'SKILL.md なし',
'modal.skills.pill.symlink': 'シンボリックリンク',
'modal.skills.pill.dir': 'ディレクトリ',
'modal.skills.import.title': 'クロスアプリインポート',
'modal.skills.import.note': 'スキャンして現在の {label} にインポートします。',
'modal.skills.import.scan': 'インポート可能をスキャン',
'modal.skills.import.stats': '選択 {selected} / {total}、SKILL.md あり {configured}、なし {missing}',
'modal.skills.import.emptyHint': 'インポート可能な skill がありません。「インポート可能をスキャン」をクリックしてください。',
'modal.skills.bulk.title': '一括操作',
'modal.skills.bulk.note': '右側はインポート操作、左側はローカル選択です。',
'modal.skills.actions.zipImport': 'ZIP インポート',
'modal.skills.actions.zipImporting': 'ZIP インポート中...',
'modal.skills.actions.exportSelected': '選択をエクスポート',
'modal.skills.actions.exporting': 'エクスポート中...',
'modal.skills.actions.importSelected': '選択をインポート',
'modal.skills.actions.importing': 'インポート中...',
'modal.skills.actions.deleteSelected': '選択を削除',
'modal.skills.actions.deleting': '削除中...',

// OpenClaw config modal (quick)
'placeholder.openclawConfigNameExample': '例: デフォルト設定',
'modal.openclaw.loadCurrent': '現在の設定を読み込む',
'modal.openclaw.quick.title': '初心者向けクイック設定',
'modal.openclaw.quick.subtitle': '3ステップで完了:Provider とモデルを入力、エディタに書き込み、保存して適用。',
'modal.openclaw.quick.readFromEditor': 'エディタから読み取り',
'modal.openclaw.quick.step1': 'Provider とモデルを入力',
'modal.openclaw.quick.step2': 'エディタに書き込みをクリック',
'modal.openclaw.quick.step3': '保存して適用',
'modal.openclaw.quick.providerHint': 'provider/model として主モデル識別子に結合されます。',
'modal.openclaw.quick.baseUrlHintDefault': 'OpenClaw 内蔵 provider のデフォルト値で補完されています。直接変更可能です。',
'modal.openclaw.quick.baseUrlHintReadonly': '外部参照が検出されたため、クイックフォームでは読み取り専用で表示されます。変更するには原文編集を使用してください。',
'modal.openclaw.quick.apiKeyHintFromAuth': '現在の値は OpenClaw 外部認証設定から取得されています。変更後「保存して適用」時に対応する auth profile に書き戻されます。',
'modal.openclaw.quick.apiKeyHintReadonly': '外部認証または参照が検出されたため、クイックフォームでは読み取り専用で表示されます。変更するには原文編集または対応する認証設定を使用してください。',
'modal.openclaw.quick.apiKeyHintKeep': '空白のままにすると既存のキーは上書きされません。',
'placeholder.apiTypeExample': '例: openai',
'modal.openclaw.quick.modelTitle': 'モデル',
'placeholder.modelIdExample': '例: gpt-5',
'placeholder.modelNameOptional': 'モデル表示名(任意)',
'field.contextWindow': 'コンテキスト長',
'field.maxOutput': '最大出力',
'hint.emptyNoChange': '空白のままにすると既存の設定は変更されません。',
'modal.openclaw.quick.optionsTitle': 'オプション',
'modal.openclaw.quick.setPrimary': '主モデルに設定',
'modal.openclaw.quick.overrideProvider': '同名 Provider 基本情報を上書き',
'modal.openclaw.quick.overrideModels': '同名モデルリストを上書き',
'modal.openclaw.quick.optionsHint': '上書きをオフにすると欠落フィールドのみ補完されます。',
'modal.openclaw.quick.writeToEditor': 'エディタに書き込み',

// Docs panel
'docs.title': 'CLI インストールドキュメント',
'docs.subtitle': 'Claude Code / Gemini CLI / CodeBuddy Code / Codex CLI コマンドを表示。',
'docs.section.commands': 'インストールコマンド',
'docs.section.commandsNote': 'コマンドは直接コピーできます。',
'docs.section.faq': 'よくある質問',
'docs.section.faqNote': 'よくある質問は以下をご覧ください。',
'docs.command.aria': '{name} コマンド',
'docs.registryHintPrefix': 'コマンドに付加:',
'docs.registryHintCustom': '完全な URL(http/https 含む)を入力してください。コマンドに自動付加されます。',
'docs.registry.tencent': 'Tencent Cloud',
'docs.meta.bin': 'bin: {bin}',
'docs.termuxLabel': 'Termux',
'docs.termuxAria': 'Termux Codex CLI command',
'docs.rule.1': 'コマンドは現在のパッケージマネージャー、ミラー、操作に基づいて生成されます。',
'docs.rule.2': 'カスタムミラーはインストールとアップグレードにのみ使用されます。',
'docs.tip.win.1': 'PowerShell で権限不足(EACCES/EPERM)エラーが発生した場合は、管理者としてインストールコマンドを実行してください。',
'docs.tip.win.2': 'インストール後にコマンドが見つからない場合は、ターミナルを再起動して実行:where codex / where claude / where gemini / where codebuddy。',
'docs.tip.win.3': '企業ネットワークで制限がある場合は、ミラーソース(npmmirror / Tencent Cloud / カスタム)を切り替えてください。',
'docs.tip.unix.1': 'EACCES 権限エラーが発生した場合は、Node グローバルディレクトリの権限を修正してください。sudo npm は推奨しません。',
'docs.tip.unix.2': 'インストール後にコマンドが認識されない場合は、ターミナルを再起動して実行:which codex / which claude / which gemini / which codebuddy。',
'docs.tip.unix.3': '企業ネットワークで制限がある場合は、ミラーソース(npmmirror / Tencent Cloud / カスタム)を切り替えてください。',

// Sessions panel
'sessions.loading': 'セッション読み込み中...',
'sessions.sourceTitle': 'ソース',
'sessions.refresh': '更新',
'sessions.refreshing': '更新中...',
'sessions.allPaths': 'すべてのパス',
'sessions.source.codex': 'Codex',
'sessions.source.claudeCode': 'Claude Code',
'sessions.source.gemini': 'Gemini',
'sessions.source.codebuddy': 'CodeBuddy',
'sessions.loadingList': 'セッション一覧を読み込み中...',
'sessions.empty': 'セッションがありません',
'sessions.unknownTime': '不明な時間',
'sessions.query.placeholder.enabled': 'セッションを検索...',
'sessions.query.placeholder.disabled': '現在のソースでは検索は利用できません',
'sessions.pin': 'ピン留め',
'sessions.unpin': 'ピン留め解除',
'sessions.copyResume': '再開コマンドをコピー',
'sessions.preview.refresh': '更新',
'sessions.preview.loading': 'プレビュー読み込み中...',
'sessions.preview.deleteHard': '直接削除',
'sessions.preview.moveToTrash': 'ゴミ箱に移動',
'sessions.preview.deleting': '削除中...',
'sessions.preview.moving': '移動中...',
'sessions.preview.export': 'エクスポート',
'sessions.preview.exporting': 'エクスポート中...',
'sessions.preview.convert': '派生セッションを生成',
'sessions.preview.converting': '変換中...',
'sessions.preview.convert.loadedOnly': '読み込み済みのみ変換',
'sessions.preview.openStandalone': 'スタンドアロンで開く',
'sessions.preview.loadingBody': 'メッセージ読み込み中...',
'sessions.preview.emptyMsgs': 'メッセージがありません',
'sessions.preview.rendering': 'レンダリング中...',
'sessions.preview.rerender': '再レンダリング',
'sessions.preview.preparing': '準備中...',
'sessions.preview.clipped': 'プレビューは一部のみ表示されています',
'sessions.preview.shownCount': '{shown} / {total} 件表示',
'sessions.preview.loadMore': 'さらに読み込む',
'sessions.preview.loadingMore': 'さらに読み込み中...',
'sessions.timeline.aria': 'セッションタイムライン',
'sessions.selectHint': '左側からセッションを選択してください',
'sessions.role.all': 'すべてのロール',
'sessions.role.user': 'ユーザー',
'sessions.role.assistant': 'アシスタント',
'sessions.role.system': 'システム',
'sessions.time.all': 'すべての期間',
'sessions.time.7d': '直近 7 日',
'sessions.time.30d': '直近 30 日',
'sessions.time.90d': '直近 90 日',
'sessions.sort.time': '時間順',
'sessions.sort.hot': '話題順',
'sessions.sort.hotBadge': '話題',
'sessions.filters.copyLink': 'リンクをコピー',
'sessions.filters.urlBuildFail': 'リンク生成失敗',
'sessions.filters.source': 'ソース',
'sessions.filters.path': 'パス',
'sessions.filters.keyword': 'キーワード',
'sessions.filters.role': 'ロール',
'sessions.filters.time': '期間',
'sessions.roleLabel.user': 'User',
'sessions.roleLabel.system': 'System',
'sessions.roleLabel.assistant': 'Assistant',

// Usage panel
'usage.overview': 'Usage 概要',
'usage.range.aria': 'Usage 時間範囲',
'usage.range.7d': '直近 7 日',
'usage.range.30d': '直近 30 日',
'usage.range.all': 'すべて',
'usage.compare.toggle': '前期間と比較',
'usage.compare.prev': '前期間',
'usage.compare.delta': '変化',
'usage.refresh': '統計を更新',
'usage.refreshing': '更新中...',
'usage.loading': 'Usage 統計を読み込み中...',
'usage.empty': '統計に使用できるセッションデータがありません',
'usage.refreshOverlay': '更新中…',
'usage.copyTitle': 'クリックでコピー:{value}',
'usage.copySuccess': 'コピー済:{label}',
'usage.copyFail': 'コピー失敗',
'usage.copyNone': 'コピーする内容がありません',
'usage.daily.title': '日別消費',
'usage.daily.subtitle': '日別 token と推定費用のサマリー(費用はそれぞれ最大値で正規化表示)。',
'usage.daily.note': '注:推定費用はデフォルトで Claude を含みません。モデル単価が一致し、セッションが input/output token を記録している場合のみ計算されます。',
'usage.heatmap.title': 'ヒートマップ',
'usage.heatmap.subtitle': '日別セッション数。色が濃いほど多い。',
'usage.heatmap.legend.less': '少',
'usage.heatmap.legend.more': '多',
'usage.heatmap.tooltip': '{date} · {sessions} セッション · {messages} メッセージ · {tokens} token',
'usage.heatmap.aria': '{date}、{sessions} セッション',
'usage.hourlyHeatmap.title': '7×24 アクティブヒートマップ',
'usage.hourlyHeatmap.subtitle': '曜日 × 時間帯でセッション分布を集計、濃色 = 高アクティブ。',
'usage.hourlyHeatmap.tooltip': '{weekday} {hour}:00 · {sessions} セッション · {messages} メッセージ · {tokens} token',
'usage.hourlyHeatmap.legend.less': '少',
'usage.hourlyHeatmap.legend.more': '多',
'usage.legend.tokens': 'Token',
'usage.legend.cost': '推定費用',
'usage.table.date': '日付',
'usage.table.sessions': 'セッション',
'usage.table.messages': 'メッセージ',
'usage.table.tokens': 'Token',
'usage.table.cost': '推定費用',
'usage.trend.sessions': 'セッション推移',
'usage.trend.messages': 'メッセージ推移',
'usage.trend.activeHours': 'アクティブ時間帯',
'usage.trend.sources': 'ソースインサイト',
'usage.legend.codex': 'Codex',
'usage.legend.claude': 'Claude',
'usage.trend.sessions.codexTitle': 'Codex {count}',
'usage.trend.sessions.claudeTitle': 'Claude {count}',
'usage.trend.messages.barTitle': '{count} 件のメッセージ',
'usage.hour.title': '{hour}:00 · {count} 回のセッション',
'usage.source.row': '{sessions} セッション · {messages} メッセージ · 平均 {avg}',
'usage.summary.sessions': '総セッション数',
'usage.summary.messages': '総メッセージ数',
'usage.summary.tokens': '総 Token 数',
'usage.summary.contextWindow': '総コンテキスト数',
'usage.summary.estimatedCost': '推定費用 · {range}',
'usage.estimatedCost.note.excludesClaudePrefix': '現在 Claude を除く、',
'usage.estimatedCost.method.configured': '設定済み単価で推定',
'usage.estimatedCost.method.catalog': '公開モデルカタログで推定',
'usage.estimatedCost.method.configuredAndCatalog': '設定済み単価 + 公開モデルカタログで推定',
'usage.estimatedCost.detail.estimate': '{prefix}{method}、推定 {estimate}、カバー {covered}/{total} セッション、約 {percent}% token',
'usage.estimatedCost.detail.missing': '{prefix}一致するモデル単価または token 分割がありません。先に models.cost を補完するか、セッションが input/output token を記録しているか確認してください。',
'usage.summary.activeDuration': 'アクティブ期間',
'usage.summary.activeDuration.title': '累積セッションスパン {value}',
'usage.summary.totalDuration': '総期間',
'usage.summary.totalDuration.title': '全体の時間スパン {value}',
'usage.summary.activeDays': 'アクティブ日数',
'usage.summary.avgMessagesPerSession': 'セッションあたり平均メッセージ',
'usage.summary.busiestDay': '最も忙しい日',
'usage.summary.busiestHour': 'ピーク時間帯',
'usage.currentSession.title': '現在のセッション',
'usage.currentSession.apiDuration': 'API 時間',
'usage.currentSession.totalDuration': '総時間',
'usage.currentSession.tokens': 'Token',
'usage.range.kicker.all': 'すべて',
'usage.range.kicker.30d': '直近 30 日',
'usage.range.kicker.7d': '直近 7 日',
'usage.copyTokenDay': 'コピー済:Token({day})',
'usage.copyCostDay': 'コピー済:推定費用({day})',
'usage.dayDetail.title': '{day} 詳細',
'usage.dayDetail.subtitle': '日付を選択すると当日の構成をすばやく確認できます。',
'usage.dayDetail.pick': '日付を選択',
'usage.dayDetail.empty': '日付を選択して当日の構成を確認してください。',
'usage.dayDetail.clear': 'クリア',
'usage.dayDetail.topSessions': 'トップセッション',
'usage.dayDetail.topModels': 'トップモデル',
'usage.models.title': '使用モデル',
'usage.models.subtitle': '実際に記録されたモデル名のみを表示します。',
'usage.models.kicker': '識別済 {modeled}/{total}',
'usage.models.count': 'モデル数',
'usage.models.coverage': 'セッションカバレッジ',
'usage.models.missing': 'モデル欠落',
'usage.models.noneTitle': '現在の範囲でモデル名が読み取れません',
'usage.models.noneBody': '{total} セッションをスキャンしましたが、元のレコードに識別可能な model フィールドがありません。',
'usage.models.providerOnly': 'うち {count} 件の古いセッションは provider のみ記録されています。',
'usage.models.missingNote.providerOnly': '別途 {count} 件のセッションにモデル名がなく、一覧に含まれていません。',
'usage.models.missingNote': '別途 {count} 件のセッションに model がなく、一覧に含まれていません。',
'usage.models.missingListTitle': 'まだモデル名が欠落しているセッション',
'usage.models.chipTitle': '{model} · {sessions} セッション · {messages} メッセージ{tokens}',
'usage.models.meta': '{sessions} セッション · {messages} メッセージ{tokens}',
'usage.weekday.title': '曜日分布',
'usage.paths.title': '高頻度パス',
'usage.paths.empty': 'パスデータがありません',
'usage.paths.count': '{count} 回',
'usage.paths.meta': '{messages} メッセージ{recent}',
'usage.paths.recent': ' · 最近 {label}',
'usage.recent.title': '最近アクティブなセッション',
'usage.sessions.empty': 'セッションデータがありません',
'usage.sessions.messages': '{count} メッセージ',
'usage.sessions.topDensity': 'メッセージ密度最高',

// Config panel (Codex)
'config.addProvider': 'プロバイダー追加',
'config.providerTemplate.title': 'プリセットプロバイダー',
'config.models': 'モデル',
'config.modelLoading': '読み込み中...',
'config.models.unlimited': '現在モデル一覧がありません。手動入力可能です。',
'config.models.error': 'モデル一覧の取得に失敗しました。手動入力可能です。',
'config.models.notInList.codex': '現在のモデルは一覧にありません。手動入力またはテンプレート変更が可能です。',
'config.models.notInList.other': '現在のモデルは一覧にありません。手動入力可能です。',
'config.template.editFirst': '先にテンプレートを編集してから適用してください。',
'config.template.bridgeCodexOnly': '{hint} テンプレートは Codex のみ編集可能です。',
'config.template.openEditor': 'テンプレートエディタを開く',
'modal.configTemplate.title': 'Config テンプレートエディタ(手動確認適用)',
'modal.configTemplate.placeholder': 'ここに config.toml テンプレート内容を編集してください',
'config.serviceTier': 'サービスティア',
'config.serviceTier.fast': 'fast(デフォルト)',
'config.serviceTier.standard': 'standard',
'config.serviceTier.hint': 'fast のみ {field} に書き込みます。',
'config.reasoningEffort': '推論強度',
'config.reasoningEffort.medium': 'medium(デフォルト)',
'config.reasoningEffort.hint': '推論の深さを制御します。high はより深く推論します。',
'config.contextBudget': '圧縮閾値',
'config.reset': 'リセット',
'config.example': '例: {value}',
'config.contextWindow.hint': 'コンテキスト上限、デフォルト 190000。',
'config.autoCompact.hint': '自動圧縮閾値、デフォルト 185000。',
'config.agents.open': 'AGENTS.md を開く',
'modal.agents.title.default': 'AGENTS.md エディタ',
'modal.agents.title.claudeMd': 'CLAUDE.md エディタ',
'modal.agents.title.openclaw': 'OpenClaw AGENTS.md エディタ',
'modal.agents.hint.default': '保存すると対象の AGENTS.md(config.toml と同階層)に書き込まれます。',
'modal.agents.hint.claudeMd': '保存すると ~/.claude/CLAUDE.md に書き込まれます。',
'modal.agents.contentLabel.claudeMd': 'CLAUDE.md 内容',
'modal.agents.placeholder.claudeMd': 'ここに CLAUDE.md の内容を編集してください',
'modal.agents.hint.openclaw': '保存すると OpenClaw Workspace の AGENTS.md に書き込まれます。',
'modal.agents.title.openclawWorkspaceFile': 'OpenClaw ワークスペースファイル: {fileName}',
'modal.agents.hint.openclawWorkspaceFile': '保存すると OpenClaw Workspace の {fileName} に書き込まれます。',
'config.url.unset': 'URL 未設定',
'config.model.unset': 'モデル未設定',
'config.badge.system': 'システム',
'config.availabilityTest': '可用性テスト',
'config.availabilityTestAria': '{name} の可用性をテスト',
'config.health.title': '設定ヘルスチェック',
'config.health.run': 'チェック実行',
'config.health.running': 'チェック中...',
'config.health.hint': 'すべてのプロバイダーの可用性を一括プローブし、遅延表示を更新します。',
'config.health.progress': '完了 {done}/{total} · 失敗 {failed}',
'config.health.ok': 'チェック通過',
'config.health.fail': 'チェック失敗',
'config.health.issues': '{count} 件の問題',
'config.shareCommand': 'コマンド共有',
'config.shareDisabled': '共有不可',
'config.shareCommand.aria': 'インポートコマンドを共有',
'config.provider.edit.aria': 'プロバイダーを編集:{name}',
'config.provider.delete.aria': 'プロバイダーを削除:{name}',
'config.provider.clone': 'クローン',
'config.provider.clone.aria': 'プロバイダーをクローン:{name}',
'app.loadingConfig': '設定読み込み中...',
'common.current': '現在 {value}',
'common.notSelected': '未選択',
'common.readFromEditor': 'エディタから読み取り',
'common.writeToEditor': 'エディタに書き込み',
'sessions.sourceLabel': 'ソース:{value}',
'usage.rangeLabel': '範囲:{value}',
'sessions.source.all': 'すべて',
'usage.range.all': 'すべて',
'usage.range.7d.short': '直近 7 日',
'usage.range.30d.short': '直近 30 日',
'orchestration.queueStats': 'キュー {running} 実行中 · {queued} 待機中',
'orchestration.hero.kicker': 'タスクオーケストレーション',
'orchestration.hero.title': '要件を実行可能なステップに分解',
'orchestration.hero.subtitle': '最初に目標を書き、計画をプレビューし、実行します。',
'orchestration.draft.reset': '下書きをリセット',
'orchestration.summary.aria': 'タスクオーケストレーション概要',
'orchestration.summary.running': '実行中',
'orchestration.summary.queued': '待機中',
'orchestration.summary.runs': '実行記録',
'orchestration.step1.title': '最初に結果を明確に書く',
'orchestration.step1.subtitle': '実行に影響する内容だけを書いてください。',
'orchestration.templates.title': 'クイックサンプル',
'orchestration.templates.reviewFix.label': 'レビュー修正 + 回帰',
'orchestration.templates.reviewFix.target': '現在の PR レビューを修正',
'orchestration.templates.reviewFix.notes': '输出统一结论,避免重复描述',
'orchestration.templates.reviewFix.followUps': '继续处理新增 review 评论\n最后更新 PR 摘要',
'orchestration.templates.planOnly.label': '計画のみ',
'orchestration.templates.planOnly.target': '計画を生成するだけ',
'orchestration.templates.planOnly.notes': '出力統一結論,避免重复描述',
'orchestration.templates.workflowBatch.label': 'Workflow 批处理',
'orchestration.templates.workflowBatch.target': 'Workflow 一括実行',
'orchestration.templates.workflowBatch.workflowIds': 'review-fix\nregression-test',
'orchestration.templates.workflowBatch.notes': '用 Workflow 跑一组固定检查并整理结果',
'orchestration.fields.target': 'ターゲット',
'orchestration.fields.target.placeholder': '例: /path/to/project',
'orchestration.fields.target.hint': '作業ディレクトリまたはファイルパス。',
'orchestration.engine.codex': 'Codex',
'orchestration.engine.workflow': 'Workflow',
'orchestration.runMode.write': '書き込み',
'orchestration.runMode.readOnly': '読み取り専用',
'orchestration.runMode.dryRun': 'ドライラン',
'orchestration.pills.hasTitle': 'タイトルあり',
'orchestration.pills.workflowCount': 'Workflow {count}',
'orchestration.pills.planNodes': '計画 {count} ノード',
'orchestration.step2.title': '計画をプレビュー',
'orchestration.step2.subtitle': 'AI が生成した計画を確認・調整します。',
'orchestration.fields.engine': 'エンジン',
'orchestration.fields.runMode': '実行モード',
'orchestration.advanced.title': '詳細設定',
'orchestration.fields.title': 'タイトル',
'orchestration.fields.title.placeholder': '例: PR #42 のレビュー指摘を修正',
'orchestration.fields.notes': '説明',
'orchestration.fields.notes.placeholder': '例:既存アーキテクチャを書き換えず、増分実装のみ行う',
'orchestration.fields.notes.hint': '境界条件、禁止事項、スタイル要件、検証要件を補足してください。',
'orchestration.fields.followUps': '後続アクション(1行1件)',
'orchestration.fields.followUps.placeholder': '例:\nレビューコメントの続きを処理\n最後に回帰テストを実行',
'orchestration.fields.concurrency': '並列度',
'orchestration.fields.concurrency.hint': '複雑なタスクは 1~2 から始めると安定します。',
'orchestration.fields.autoFixRounds': '自動修正',
'orchestration.fields.autoFixRounds.hint': '失敗後に自動で再試行する回数。',
'orchestration.fields.workflowIds': 'Workflow ID(1行1件)',
'orchestration.fields.workflowIds.placeholder': '例:\nreview-fix\nregression-test',
'orchestration.fields.workflowIds.hint': 'Workflow エンジン使用時に必須。',
'orchestration.workflow.stepCount': '{count} ステップ',
'orchestration.step3.title': '実行して確認',
'orchestration.step3.subtitle': '計画を実行し、結果を確認します。',
'orchestration.actions.planning': '計画中...',
'orchestration.actions.previewOnly': 'プレビューのみ',
'orchestration.actions.preparing': '準備中...',
'orchestration.actions.generatePlan': '計画を生成',
'orchestration.actions.planAndRun': '計画して実行',
'orchestration.actions.processing': '処理中...',
'orchestration.actions.queueAndStart': 'キューに入れて開始',
'orchestration.actions.caption': '「計画して実行」は必要に応じて計画を自動更新します。バッチタスクは「キューに入れて開始」を使用してください。',
'orchestration.stage.title': 'タスクステージ',
'orchestration.stage.subtitle': '3ステップでタスクを完了:計画 → プレビュー → 実行。',
'orchestration.stage.pill.target': 'ターゲット',
'orchestration.stage.pill.preview': 'プレビュー',
'orchestration.stage.pill.run': '実行',
'orchestration.plan.title': '計画',
'orchestration.plan.subtitle': 'AI が生成した実行計画。各ノードは1つのステップを表します。',
'orchestration.plan.summary.nodes': 'ノード数',
'orchestration.plan.summary.waves': '波数',
'orchestration.plan.summary.engine': 'エンジン',
'orchestration.plan.node.write': '書き込み',
'orchestration.plan.node.readOnly': '読み取り専用',
'orchestration.labels.dependencies': '依存',
'orchestration.labels.error': 'エラー',
'orchestration.workbench.title': 'タスクワークベンチ',
'orchestration.workbench.subtitle': 'タスクの計画・キュー・実行・振り返り。',
'orchestration.queue.start': '開始',
'orchestration.queue.starting': '開始中...',
'orchestration.workbench.tabs.aria': 'タスクタブ',
'orchestration.workbench.tabs.queue': 'キュー {count}',
'orchestration.workbench.tabs.runs': '実行記録 {count}',
'orchestration.workbench.tabs.detail': '詳細',
'orchestration.queue.empty.title': 'キューは空です',
'orchestration.queue.empty.subtitle': 'キューにタスクがありません。',
'orchestration.runs.empty.title': '実行記録なし',
'orchestration.runs.empty.subtitle': 'まだ実行記録がありません。',
'orchestration.detail.refresh': '詳細を更新',
'orchestration.detail.retry': '再試行',
'orchestration.detail.retrying': '再試行中...',
'orchestration.detail.empty.title': '実行記録を選択して詳細を表示',
'orchestration.detail.empty.subtitle': 'ここにノードの状態、サマリー、ログが表示されます。',
'orchestration.detail.summary.status': '状態',
'orchestration.detail.summary.duration': '所要時間',
'orchestration.detail.summary.nodes': 'ノード数',
'orchestration.detail.summary.summary': 'サマリー',
'orchestration.detail.node.meta': '{id} · 試行 {attempts} · 自動修正 {autoFix}',
'skills.localLabel': '{target} / ローカル Skills',
'skills.counts': 'インストール済 {installed} · インポート可 {importable}',

// Sidebar status labels (compact)
'status.currentSource': '現在のソース',
'status.sessionCount': 'セッション数',
'status.range': '統計範囲',
'status.totalSessions': '総セッション数',
'status.totalMessages': '総メッセージ数',
'status.engine': 'エンジン',
'status.concurrency': '並列度',
'status.running': '実行中',
'status.queued': '待機中',
'status.runs': '履歴実行',
'status.currentTarget': '現在のターゲット',
'status.localSkills': 'ローカル Skills',
'status.importable': 'インポート可',
'status.importableDirect': '直接インポート可',
'status.pm': 'パッケージマネージャー',
'status.action': '現在の操作',
'status.registry': 'ミラー',
'status.claudeConfig': 'Claude 設定',
'status.claudeModel': 'Claude モデル',
'status.openclawConfig': 'OpenClaw 設定',
'status.workspaceFile': 'ワークスペースファイル',
'status.configMode': '設定モード',
'status.currentProvider': '現在の Provider',
'status.currentModel': '現在のモデル',
'status.quickSwitchProvider': 'プロバイダーをクイック切替',
'side.usage.meta': 'ローカル統計 / トレンド',
'side.orchestration.meta': '計画 / キュー / 実行',

// Settings panel
'settings.tab.general': '一般',
'settings.tab.data': 'データ',
'settings.tabs.aria': '設定カテゴリ',
'settings.sharePrefix.title': '共有コマンドプレフィックス',
'settings.sharePrefix.meta': 'Web UI の「共有コマンドをコピー」のプレフィックスに影響',
'settings.sharePrefix.label': 'プレフィックス',
'settings.sharePrefix.hint': 'デフォルトはプロジェクト内の npm start を使用します。グローバル codexmate に切り替えることもできます。この設定はブラウザローカルにキャッシュされます。',
'settings.claude.title': 'Claude 設定',
'settings.claude.meta': '~/.claude のバックアップ / インポート',
'settings.codex.title': 'Codex 設定',
'settings.codex.meta': '~/.codex のバックアップ / インポート',
'settings.backup.progress': 'バックアップ中 {percent}%',
'settings.backup.oneClickClaude': '~/.claude をワンクリックバックアップ',
'settings.backup.importClaude': '~/.claude バックアップをインポート',
'settings.backup.oneClickCodex': '~/.codex をワンクリックバックアップ',
'settings.backup.importCodex': '~/.codex バックアップをインポート',
'settings.importing': 'インポート中...',

'settings.deleteBehavior.title': 'セッション削除動作',
'settings.deleteBehavior.meta': '「削除」を先にゴミ箱に入れるかどうかを決定',
'settings.deleteBehavior.toggle': 'セッション削除時に先にゴミ箱に移動',
'settings.deleteBehavior.hint': 'デフォルトで有効。オフにすると、セッション閲覧での削除は直接完全削除され、ゴミ箱に入りません。',

'settings.trash.title': 'ゴミ箱',
'settings.trash.meta': '削除済みセッション(復元/完全削除可能)',
'settings.trash.refresh': 'リストを更新',
'settings.trash.refreshing': '更新中...',
'settings.trash.clear': 'ゴミ箱を空にする',
'settings.trash.clearing': 'クリア中...',
'settings.trash.loading': 'ゴミ箱を読み込み中...',
'settings.trash.empty': 'ゴミ箱は空です',
'settings.trash.retry': 'ゴミ箱リストの読み込みに失敗しました。更新して再試行してください',
'settings.trash.restore': '復元',
'settings.trash.restoring': '復元中...',
'settings.trash.purge': '完全に削除',
'settings.trash.purging': '削除中...',
'settings.trash.workspace': 'ワークスペース',
'settings.trash.originalFile': '元ファイル',
'settings.trash.loadMore': 'さらに読み込む(残り {count} 件)',
'settings.trash.retention': '自動クリーンアップ',
'settings.trash.retentionMeta': '保持日数を超えたゴミ箱レコードは自動的に削除されます',
'settings.trash.retentionLabel': '保持日数',
'settings.trash.retentionHint': '範囲 1-365 日、デフォルト 30 日。ゴミ箱読み込み時に期限切れレコードを自動クリーンアップします。',

'settings.templateConfirm.title': '設定テンプレートの二次確認',
'settings.templateConfirm.meta': '誤書き込みリスクを低減',
'settings.templateConfirm.toggle': 'テンプレート適用前に差分をプレビュー(2段階:確認 → 適用)',
'settings.templateConfirm.hint': '有効時:先に差分プレビューを表示し、確認後に書き込みます。',

'settings.reset.title': '設定リセット',
'settings.reset.meta': '注意して操作してください',
'settings.reset.hint': '先に config.toml をバックアップし、デフォルト設定を書き込みます。',
'settings.reset.button': '設定を再インストール',
'settings.reset.loading': '再インストール中...',

// Market (Skills)
'market.title': 'Skills 概要',
'market.subtitle': 'ターゲットを切り替えてローカル Skills を表示。',
'market.refresh': '概要を更新',
'market.refreshing': '更新中...',
'market.openManager': 'Skills 管理を開く',
'market.target.aria': 'Skills インストールターゲットを選択',
'market.summary.target': 'インストールターゲット',
'market.summary.total': 'ローカル総数',
'market.summary.configured': 'SKILL.md あり',
'market.summary.missing': 'SKILL.md なし',
'market.summary.importable': 'インポート可',
'market.summary.importableDirect': '直接インポート可',
'market.root.fallback': 'デフォルトディレクトリ',

'market.installed.title': 'インストール済 Skills',
'market.installed.note': '上位 6 項目のみ表示。',
'market.local.refresh': 'ローカルを更新',
'market.local.refreshing': '更新中...',
'market.local.loading': 'ローカル Skills 読み込み中...',
'market.local.empty': '現在インストール済の skill はありません。',
'market.pill.verified': '検証済',
'market.pill.missingSkill': 'SKILL.md 要追加',

'market.import.title': 'インポート可能ソース',
'market.import.note': 'スキャンして現在の {target} にインポートします。',
'market.import.scan': 'ソースをスキャン',
'market.import.scanning': 'スキャン中...',
'market.import.loading': 'インポート可能 skill をスキャン中...',
'market.import.empty': 'インポート可能な skill がスキャンされませんでした。',
'market.pill.importableDirect': '直接インポート可',
'market.pill.importMissing': 'SKILL.md なし',

'market.actions.title': '配布エントリ',
'market.actions.note': '操作はすべて現在のインストールターゲットに適用されます。',
'market.action.manage.title': 'ローカル Skills 管理',
'market.action.manage.copy': '現在の {target} のインストール済 skills を管理',
'market.action.crossImport.title': 'クロスアプリインポート',
'market.action.crossImport.copy': '現在の {target} にインポート',
'market.action.zipImport.title': 'ZIP インポート',
'market.action.zipImport.copy': 'ZIP から現在のターゲットにインポート',

'market.help.title': '操作説明',
'market.help.target.title': 'ターゲット切替',
'market.help.target.copy': '現在の操作は {target} ディレクトリに書き込まれます。',
'market.help.crossImport.title': 'クロスアプリインポート',
'market.help.crossImport.copy': '他のホストから未管理の skill をインポートします。',
'market.help.zipImport.title': 'ZIP インポート',
'market.help.zipImport.copy': 'ZIP からローカル skill をインポートします。',

// Claude config panel
'claude.addProvider': 'プロバイダー追加',
'claude.applyDefault': 'デフォルトで ~/.claude/settings.json に適用されます。',
'claude.presetProviders': 'プリセットプロバイダー',
'claude.customConfig': 'カスタム設定',
'claude.model': 'モデル',
'claude.model.placeholder': '例: claude-3-7-sonnet',
'claude.model.hint': 'モデル変更後は自動保存され、現在の設定に適用されます。',
'claude.health.title': '設定ヘルスチェック',
'claude.health.run': 'チェック実行',
'claude.health.running': 'チェック中...',
'claude.health.hint': 'すべての Claude 設定の可用性を一括プローブし、遅延表示を更新します。',
'claude.health.progress': '完了 {done}/{total} · 失敗 {failed}',
'claude.md.title': 'CLAUDE.md',
'claude.md.open': 'CLAUDE.md を開く',
'claude.md.hint': '~/.claude/CLAUDE.md の読み書き。',
'claude.model.unset': 'モデル未設定',
'claude.configured': '設定済み',
'claude.notConfigured': '未設定',
'claude.action.edit': '編集',
'claude.action.delete': '削除',
'claude.action.shareDisabled': 'インポートコマンド共有(一時無効)',
'claude.action.editAria': 'Claude 設定を編集:{name}',
'claude.action.deleteAria': 'Claude 設定を削除:{name}',
'claude.action.clone': 'クローン',
'claude.action.cloneAria': 'Claude 設定をクローン:{name}',

// OpenClaw config panel
'openclaw.applyHint': '~/.openclaw/openclaw.json に書き込みます。JSON5 対応。',
'openclaw.agents.hint': 'Workspace の AGENTS.md を読み書きします。デフォルトパス ~/.openclaw/workspace/AGENTS.md。',
'openclaw.agents.open': 'AGENTS.md を開く',
'openclaw.workspaceFile': 'ワークスペースファイル',
'openclaw.workspace.placeholder': '例: SOUL.md',
'openclaw.workspace.hint': 'Workspace 内の .md ファイルのみ。',
'openclaw.workspace.open': 'ワークスペースファイルを開く',
'openclaw.configured': '設定済み',
'openclaw.notConfigured': '未設定',
'openclaw.action.edit': '編集',
'openclaw.action.delete': '削除',
'modal.openclaw.quick.subtitle': '3ステップで完了:Provider とモデルを入力、エディタに書き込み、保存して適用。',
'modal.openclaw.quick.step2': 'エディタに書き込みをクリック',
'modal.openclaw.structured.writeHint': 'エディタに書き込むと JSON が再配置され、コメントが失われる可能性があります。',
'openclaw.action.editAria': 'OpenClaw 設定を編集:{name}',
'openclaw.action.deleteAria': 'OpenClaw 設定を削除:{name}',

},
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify duplicate keys in the ja translation block
rg -n "^\s*'[^']+'\s*:" web-ui/modules/i18n.dict.mjs | \
  awk -F: '{print $2}' | \
  sort | uniq -d | head -20

Repository: SakuraByteCore/codexmate

Length of output: 707


Remove all duplicate object keys in the Japanese translation block.

Verification confirms duplicate keys in the ja translation object. The scope is more extensive than initially flagged: beyond the 6 duplicates at lines 1090, 1433, 1435, 1508, 1511, and 1624, there are numerous additional duplicate keys throughout the block (e.g., app.loadingConfig, brand.kicker.workspace, claude.action.*, and many others).

Duplicate keys in JavaScript object literals cause the last value to silently overwrite earlier ones, breaking translation lookups. Audit the entire ja object, identify all duplicates, and consolidate or rename affected keys before merging.

🧰 Tools
🪛 Biome (2.4.15)

[error] 1090-1090: This property is later overwritten by an object member with the same name.

(lint/suspicious/noDuplicateObjectKeys)


[error] 1433-1433: This property is later overwritten by an object member with the same name.

(lint/suspicious/noDuplicateObjectKeys)


[error] 1435-1435: This property is later overwritten by an object member with the same name.

(lint/suspicious/noDuplicateObjectKeys)


[error] 1508-1508: This property is later overwritten by an object member with the same name.

(lint/suspicious/noDuplicateObjectKeys)


[error] 1511-1511: This property is later overwritten by an object member with the same name.

(lint/suspicious/noDuplicateObjectKeys)


[error] 1624-1624: This property is later overwritten by an object member with the same name.

(lint/suspicious/noDuplicateObjectKeys)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web-ui/modules/i18n.dict.mjs` around lines 1040 - 2080, The ja translation
object contains many duplicate keys (e.g., modal.configTemplate.title,
app.loadingConfig, brand.kicker.workspace, claude.action.* and others) that
cause silent overwrites; audit the entire ja block, remove or consolidate
duplicated entries so each translation key appears only once, decide which value
to keep for conflicts (preserve the intended/most recent or consolidate into a
single entry), and run a quick script/linter to validate uniqueness before
committing.

@ymkiux ymkiux merged commit 49c7ac7 into main May 12, 2026
7 checks passed
@ymkiux ymkiux deleted the feat/skip-provider-reload branch May 12, 2026 13:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(i18n): multi-language support

2 participants