Skip to content

feat: 日本語/英語のi18n対応と設定による言語切替 - #1

Merged
takecchi merged 1 commit into
mainfrom
feat/i18n-language-support
Jul 18, 2026
Merged

feat: 日本語/英語のi18n対応と設定による言語切替#1
takecchi merged 1 commit into
mainfrom
feat/i18n-language-support

Conversation

@takecchi

Copy link
Copy Markdown
Owner

概要

UIの表示言語を 日本語 / 英語 で切り替えられるようにしました。日本語で見たい人・英語で見たい人が混在するケースに対応するため、UI文字列をすべてカタログに集約し、設定で切り替え可能にしています。

言語の切り替え方(優先順)

  1. 環境変数: CODIVA_LANG=en(一時上書き)
  2. 設定ファイル ~/.codiva/config.json~/.claude/ と同じユーザーグローバルの流儀):
    { "language": "en" }
    "ja" / "en" / "auto" を指定可能。
  3. 未設定 / auto: OSロケール自動判定(LC_ALL/LC_MESSAGES/LANGja* なら日本語、他は英語)

配線は合成ルート src/index.tsx で行い、解決済みカタログを Appmessages prop に注入します。

変更点

新規(純粋ロジック / I-O / UI)

  • src/core/i18n.tsLang / Messages 型 + ja/en カタログ + resolveLang / detectLocaleLang / normalizeLang(Ink/React/I-O 非依存)
  • src/core/config.tsCodivaConfig + toConfig(外部JSONの検証変換を閉じ込め)
  • src/utils/config.ts~/.codiva/config.jsonloadConfig / saveConfig(薄いI-Oラッパ、パス注入可能)
  • src/ui/i18n-context.tsxMessagesProvider / useMessages()(Provider外は既定 ja にフォールバック)

変更

  • session-list / session-detail / permission-dialog / progress-badge のハードコード文字列を useMessages() 経由に置換。badgeForMessages を引数で受ける純関数に変更
  • src/index.tsx — 起動時に言語を解決し App へ注入、終了時メッセージも翻訳
  • 各バレル(core/utils/ui)に新モジュールを追加

ドキュメント / 規約

  • .claude/rules/i18n.mdi18n規約(ハードコード禁止・カタログ集約・ja/en対で追加・レイヤ配置)。CLAUDE.md の自動読込リストに追加
  • docs/ARCHITECTURE.md — i18nレイヤ節を追記

翻訳漏れの防止(二重の番人)

  • Messages 型が ja/en 両カタログのキー欠落を型エラーで検知
  • src/core/i18n.spec.ts が ja/en のキー集合一致を実行時にも検証

Test plan

  • npm test — 127 passed(coverage: core/utils 80%閾値クリア)
  • npm run typecheck — pass
  • npm run lint(Biome)— pass
  • npm run build(tsup)— success
  • resolveLang の優先順位をテーブルドリブンで検証(env > config > locale)
  • loadConfig の寛容フォールバック(ファイル無し・不正JSON・不正language → 空設定)
  • Appmessages={en} 注入で描画し英語表示を確認(index.tsx と同じDI経路)
  • 実機での手動確認(各言語での起動): サンプルリポジトリ + Claude認証が必要

補足

アプリ内キーによるライブ切り替えは今回は未対応(一覧画面は任意キーが指示入力バッファに入る設計のため、専用キー割当が競合する)。現状は設定ファイル/環境変数で切り替え → 再起動で反映。必要ならライブトグルを別PRで追加可能です。

UI文字列を core/i18n.ts のカタログ(ja/en)に集約し、useMessages() 経由で
参照するように変更。表示言語は CODIVA_LANG 環境変数 > ~/.codiva/config.json
> OSロケール自動判定 の優先順で解決する。

- core/i18n.ts: Messages型 + ja/en カタログ + resolveLang(純粋)
- core/config.ts: CodivaConfig + toConfig(外部JSONの検証変換)
- utils/config.ts: ~/.codiva/config.json の読み書き(I/Oラッパ)
- ui/i18n-context.tsx: MessagesProvider / useMessages
- UIコンポーネントのハードコード文字列を全てカタログ化(badgeForはMessagesを引数で受ける純関数へ)
- index.tsx: 起動時に言語解決しAppへ注入、終了メッセージも翻訳
- .claude/rules/i18n.md: i18n規約を追加(CLAUDE.mdから自動読込)

Messages型と i18n.spec.ts の ja/en キー集合一致テストで翻訳漏れを型・実行時の両方で検知。
@takecchi
takecchi merged commit 411ed9d into main Jul 18, 2026
1 check passed
@takecchi
takecchi deleted the feat/i18n-language-support branch July 18, 2026 15:34
takecchi added a commit that referenced this pull request Jul 18, 2026
## 概要

Phase 6(MVP後バックログ)のうち **4項目** を実装しました(`includePartialMessages`
によるストリーミング表示は今回対象外)。純粋ロジックは `core/`、副作用は `utils/`/合成ルートという既存の分離を踏襲しています。

着手項目はユーザーと相談のうえ決定(TASKS.md の「着手前にユーザーと相談」に従う)。

## 実装内容

### 1. 設定ファイル拡張 (#2)
- `CodivaConfig` に `model` / `effort` / `permissionMode` /
`maxBudgetUsd` / `notifications` を追加
- 検証は `toConfig()` に集約(不正値は静かに既定へフォールバック、TUI をクラッシュさせない)
- `SessionOptions` に束ね、`SessionManager → Session → SDK Options` へ注入
- `permissionMode` 未指定時は従来どおり `acceptEdits`

### 2. コスト表示 (#3)
- 純粋な `core/cost.ts`(`totalCostUsd()` / `formatUsd()`)を追加
- 一覧のバナーに合計コスト、詳細に各セッションのコスト行
- reducer は既に `result.total_cost_usd` を `state.totalCostUsd`
に保持していたため、導出のみ追加

### 3. デスクトップ通知 (#5)
- 発火判定は純粋な `core/notify.ts` の `notificationFor(prev, next, messages)` —
**状態遷移時のみ**発火(連続更新で鳴り続けない)
- 実 I/O は `utils/notify.ts`(darwin=`osascript` /
linux=`notify-send`、文字列は **argv 渡し**で注入防止、missing binary 等は握り潰す
best-effort)
- `SessionManager.onTransition` に配線、`config.notifications: false` で無効化

### 4. セッション復元 (#1)
- 永続スナップショットの型・変換・検証は純粋な `core/persistence.ts`(`toPersistedSession` /
`restoredSessionState` / `fromPersistedJson`)
- ファイル I/O は
`utils/state-store.ts`(`<repo>/.codiva/state.json`、破損時は空へフォールバック、起動時に存在しない
worktree を prune)
- `Session` は `resume`(SDK session id)/ `restored`(復元済み
state)を受け、**復元セッションは起動時にサブプロセスを立てず、最初の追加指示で遅延 resume**(起動時のサブプロセス乱立を回避)
- 終了時は `stop()`(quiet 停止)で実行中セッションも resumable のまま保存(`abort()` = failed 化
と区別)
- `SessionManager.restore()` / `persistableState()` を追加。id/slug
を予約して新規セッションとの衝突を回避

## テスト計画

- [x] `npm test` — **234件全緑**(旧131 → +103)
- [x] coverage — Statements 95.5% / Branches 85% / Functions 89.6% /
Lines 96.5%(core・utils とも 80% 閾値超)
- [x] `npm run typecheck` — 緑
- [x] `npm run lint` — 緑(既存の banner.tsx `paint` 未使用引数 warning のみ、本PR無関係)
- [x] `npm run build` — 緑
- 新規 spec: `config`(拡張) / `cost` / `notify`(core+utils) / `persistence`
/ `state-store`
- 統合テスト `tests/restore.test.tsx`: run → persist → 新 manager restore →
追加指示で `resume` が query options に載る、までを実 `Session` で通し検証
- [ ] **手動受け入れ(未実施)**: 実 Claude セッションでの resume 挙動・OS 通知の実表示は TTY +
認証が要るため未確認。仕組みは統合テストで検証済み

## ドキュメント

`TASKS.md`(チェック更新+実績メモ)、`ARCHITECTURE.md`(新モジュール・責務・「Phase 6
機能」節・決定表)、`TECH_NOTES.md`(Options の resume/model/effort/maxBudgetUsd
を実装済みに更新)を同期済み。

🤖 Generated with [Claude Code](https://claude.com/claude-code)
takecchi added a commit that referenced this pull request Jul 19, 2026
## 概要

セッションのブランチに開いている Pull Request を検出し、セッション一覧に `#<number>`
バッジとして表示します。バッジをクリック(または一覧フォーカス時に `p`)すると、既定ブラウザで PR ページを開きます。

要望: 「もしPRが出ていた場合、セッション一覧に #1 のように表示し、クリックで遷移できるようにしてほしい」

## 変更点

### core(純粋ロジック)
- `SessionState` に `pr?: PrInfo`(`{ number, url }`)を追加
- `CodivaEvent` に `pr` イベントを追加し、リデューサで処理(値が同じなら同一参照を返し再描画を抑制)
- `Session.setPr()` を追加
- `SessionManager.refreshPrs()` を追加。live なセッションのブランチを `gh` でポーリングし
`setPr` に反映(archived / worktree 未作成はスキップ、失敗は握りつぶす
best-effort)。`lookupPr` は DI

### utils(I/O ラッパ)
- `lookupPr(cwd, branch)`: `gh pr view <branch> --json number,url` を
`execFile`(argv 固定)で実行。PR 無し・`gh` 未導入・未ログイン・不正 JSON はすべて
`undefined`。exec は注入可能
- `openUrl` / `openUrlCommand`: `notify.ts` に倣った純粋なコマンド生成 +
fire-and-forget 実行(darwin=`open` / linux=`xdg-open` / win32=`cmd /c
start "" <url>`)

### ui
- 行に `#<n>` 列(accent 色・下線)を追加。列幅は定数化し、描画とマウス当たり判定で共有
- SGR マウス press で PR セルの x 範囲を判定し `onOpenPr` を呼ぶ
- 一覧フォーカス時の `p` キーで選択中セッションの PR を開く
- フッタヒント `helpList` に `p: PRを開く` を ja/en 追加

### 配線
- `index.tsx`: `lookupPr` を注入し、起動直後 + 20 秒間隔で `refreshPrs`
をポーリング(`unref` 済み、終了時に `clearInterval`)。`openUrl` を `App` に渡す
- `app.tsx`: `onOpenPr` を `SessionList` へ受け渡し

## テスト

- `status-reducer.spec.ts`: `pr` イベント(保存・no-op・置換・クリア)
- `session-manager.spec.ts`: `refreshPrs`(ブランチ単位のルックアップ、lookupPr 未配線時
no-op、archived スキップ + 失敗耐性)
- `pr.spec.ts`: `gh` コマンド形・成功/PR無し/不正 JSON
- `open-url.spec.ts`: プラットフォーム別コマンド

### テストプラン
- [x] `npm run typecheck`
- [x] `npm run lint`
- [x] `npm test`(360 passed、core/utils カバレッジ 80% 以上)
- [x] `npm run build`
- [x] 実 `gh` で `gh pr view <branch> --json number,url` の出力形と PR
無し時の非ゼロ終了を確認
- [ ] TUI 手動確認: PR のあるブランチのセッションで `#<n>` 表示・クリック/`p` でブラウザ遷移(全画面 +
マウス有効時)
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.

1 participant