はじめに
Claude CodeやCodexを複数セッション同時に走らせていると、ターミナルが追いつかなくなります。通知が埋もれる、タブが見分けられない、どのペインでどのエージェントが動いているかわからない。
cmuxはこの問題を正面から解決するターミナルです。エージェントが入力待ちになるとペインにリングが光り、サイドバーでgitブランチやPRステータスまで一覧できます。Ghosttyベースなので、既存の設定ファイルをそのまま引き継げます。
使い始めて気に入ったものの、UIが英語のままでした。日本語化されていないなら自分でやろうと思い、Claude Codeと一緒にローカライズPRを出しました。624エントリの翻訳と14コミットのレビュー対応を経てマージされています。

この記事では、cmuxの紹介、i18n PRの進め方、Claude Codeをどう使ったか、そしてレビュー対応で学んだことを書きます。
cmuxとは
cmuxは、AIコーディングエージェントとの並列作業を前提に設計されたmacOSネイティブのターミナルアプリです。Swift/AppKitで作られており、ターミナル描画にはGhosttyのlibghosttyを使っています。

主な特徴は3つあります。
通知リング
エージェントが入力を待っているペインに青いリングが表示されます。サイドバーのタブも光るので、複数のペインを開いていても「どのエージェントが自分を呼んでいるか」がひと目でわかります。Cmd+Shift+Uで最新の未読に飛べます。
縦タブとサイドバー
サイドバーにはgitブランチ、PRステータス、作業ディレクトリ、リッスン中のポート、最新の通知テキストが表示されます。横タブでは収まりきらない情報が一覧できます。
アプリ内ブラウザ
ターミナルの横にブラウザペインを分割して配置できます。agent-browserから移植されたスクリプタブルなAPIを持ち、エージェントがアクセシビリティツリーの取得やクリック、フォーム入力、JavaScript実行を行えます。
Ghostty互換
既存の ~/.config/ghostty/config をそのまま読みます。テーマ、フォント、カラー設定を引き継げるので、Ghosttyユーザーは設定なしで使い始められます。
日本語化しようと思った理由
cmuxを使い始めて、メニューや設定画面が英語のままでも特に困りはしませんでした。ただ、コマンドパレットの項目やエラーメッセージが日本語で読めると、認知負荷が下がるのは確かです。
READMEには既に多言語版(中国語、韓国語など)がありましたが、アプリのUI自体のローカライズはされていませんでした。SwiftのString Catalog(.xcstrings)がXcode 15から使えるようになり、ローカライズの基盤が整っていたこともあって、「自分で出してみよう」と考えました。
i18n PRで意識したこと
PRの技術的な詳細へ入る前に、i18nのOSS貢献で意識したポイントをまとめます。これから同じようなPRを出したい方の参考になれば幸いです。
プロジェクトの既存パターンに合わせる
cmuxには既にREADMEの多言語版がありました。アプリ内UIのローカライズは未実装でしたが、国際化への関心はあると判断できました。プロジェクトのi18n方針やコーディングスタイルに合わせることで、レビュー負荷を下げられます。
翻訳しないものを明確にする
デバッグログ、内部キー、アクセシビリティ識別子は翻訳対象外としました。PRの説明に「Not translated」セクションを設け、意図的に除外したものを明記しています。レビュアーが「これは漏れか、意図的か」と迷わずに済みます。
mainの変更をキャッチアップする
大きなPRはレビュー期間が長くなります。その間もmainの開発は進み、コンフリクトが発生します。今回は3回mainをマージし、新規UIのローカライズも追加しました。PRを出したら終わりではなく、マージされるまで面倒を見る姿勢が大事です。
PRの作成からマージまで
コードを読む
まず、ソースコード全体からUIに表示されるハードコードの文字列を洗い出しました。18のSwiftソースファイルに約620のハードコード文字列が散らばっていました。
対象は以下のカテゴリに分かれます。
| カテゴリ | ファイル数 | エントリ数 |
|---|---|---|
| 設定画面 | 1 | 約180 |
| コマンドパレット・サイドバー | 1 | 約200 |
| メニュー・ダイアログ | 1 | 約70 |
| アップデートUI | 4 | 約80 |
| タブ管理 | 2 | 約50 |
| ブラウザ | 3 | 約35 |
| その他 | 5 | 約45 |
翻訳方針を決める
翻訳を始める前に、以下のルールを決めました。
- Apple公式の用語ガイドラインに従う — 「キャンセル」「適用」など、macOSの標準的な表現を使う
- 翻訳しないもの —
#if DEBUGブロック、ログメッセージ、UserDefaultsキー、アクセシビリティ識別子 - キー命名規則 —
{section}.{subsection}.{descriptor}の形式(例:settings.app.theme、command.newWorkspace.title)
ほぼClaude Codeとの対話だけで進めた
この作業は、ほとんどClaude Codeとの対話だけで完了しています。自分がやったのは方針を決めることと、翻訳の品質をチェックすることだけです。
- 「18ファイルからハードコード文字列を洗い出して」
- 「
String(localized:defaultValue:)に置き換えて」 - 「624エントリ分の
.xcstringsを生成して」 - 「レビューでこの指摘が来たから直して」
こうした指示を出すと、Claude Codeがコードを読み、修正し、コミットまで行います。624エントリ分のJSONを手書きするのは非現実的ですし、18ファイルにまたがる一括置き換えも手作業では厳しい。Claude CodeがなければこのPRは出していなかったでしょう。
PRを出す
最初のコミットでString Catalogの基盤と日本語翻訳を追加し、2つ目のコミットでハードコード文字列の置き換えを行いました。
// Before
alert.messageText = "Connection isn't secure"
// After
alert.messageText = String(
localized: "browser.error.insecure.title",
defaultValue: "Connection isnu{2019}t secure"
)
String(localized:defaultValue:) APIを使うことで、翻訳カタログにエントリがなくてもdefaultValueの英語テキストがフォールバックとして表示されます。
レビュー対応
PRを出した後、自動レビューツール(cubic)から10件の指摘がありました。主なものは以下です。
- 同じキーに異なる
defaultValue—browser.error.insecure.titleで普通のアポストロフィ(')と活字用のアポストロフィ(u{2019})が混在していた - ローカライズ漏れ — エラーメッセージのタイトルは翻訳したが本文が英語のまま残っていた箇所
StaticString制約 —String(localized:)のキーはStaticStringである必要があり、変数を渡せない
これらの指摘もClaude Codeとの対話で修正しました。指摘内容を伝えて「直して」と言うだけです。
コンフリクトとの戦い
一番大変だったのはレビュー対応そのものではなく、コンフリクトの解消でした。
cmuxは開発ペースが速いプロジェクトです。PRのレビューを待っている間にも、他のPRが次々とmainにマージされていきます。UIに文字列を追加する変更が入るたび、自分のPRとコンフリクトが発生します。i18nのPRは全ファイルのUI文字列に触れているため、ほぼどんな変更とも衝突します。
結果として、mainのマージを3回行いました。そのたびにコンフリクトを解消し、新しく追加されたUI(検索バー、通知設定、コピーモード切替)のローカライズも追加しています。
:::details 14コミットの全ログを見る。
437f486 Add i18n infrastructure with String Catalog and Japanese translations
94fa290 Replace hardcoded UI strings with String(localized:defaultValue:)
a2b9624 Fix localization gaps from review feedback
4224ce6 Fix String(localized:defaultValue:) keys to use StaticString
3563cb7 Localize remaining UI strings across all source files
814512d Address review feedback: fix missing localizations and terminology
05fe052 fix: address remaining PR 819 review feedback
018d5e6 fix: use a single localized key for close-other-tabs
49e27cd fix: avoid inflection markup in close-other-tabs message
44e69e0 Merge origin/main into feature/i18n-japanese
b80bb27 Merge origin/main: add toggleTerminalCopyMode localization
960da38 Merge main into feature/i18n-japanese to resolve conflicts
4ecad69 Address review feedback: localize tooltip, fix subtitle concat, unify keys
3fc58d0 Merge origin/main: resolve conflicts with find bar refactor and debug logs



コメント