
セーフティネットなしで30言語を出荷する
GeekBye v1.8.3は`react-i18next`を組み込み、アプリ全体を30言語へ翻訳しました。ライブラリの部分はお決まりの作業です。面白いのは欠けていたもののほうです: キーの整合性チェックも文字列抽出ツールもなかった — だからハードコードされた英語が漏れ出し、デンマーク語のタイポが出荷され、変更履歴はアプリが28か29か30のどの言語を話すのかで意見が一致しませんでした。
機能そのものがリリースであることもあります。GeekBye v1.8.3は一見そう見えます — 「アプリはいま30言語を話す」 — が、機能は簡単な部分であり、この記事の主題ではありません。2026年2月17日から19日にかけて、二つのプルリクエストがreact-i18nextをアプリに配線し、ハードコードされたすべての英語文字列を翻訳呼び出しへリファクタリングし、30のロケールファイルを同梱しました。これは本物の仕事ですが、ライブラリがお決まりの作業に変えるべく設計された類の仕事です。面白いエンジニアリングは、このリリースが_作らなかった_すべてのもの — 壊れて出荷されたものを捕まえていたはずのセーフティネット — のほうにあります。
配線は簡単な80%だった
インフラのコミットはi18nextの上にreact-i18nextを追加し、お定まりのことをしました: initReactI18nextを呼び、30のロケールJSONをすべて静的にインポートし、それらを一つのresourcesオブジェクトの下に登録するi18n/index.tsです。遅延読み込みはなし — あらゆる言語がバンドルに同梱されます。翻訳文字列はsrc/i18n/locales/*.jsonに、平坦でドット区切りの名前空間を持つマップとして存在します: sidebar.meetings、startMeeting.start、一つのキーに一つの文字列です。
ハードコードされた英語からt('key')への移行は正直なやり方で、コンポーネントのグループを一つずつ進めて行われ、その掃き掃除はコミットログから読み取れます: まずサイドバー、ナビゲーション、チャット; 次に設定、ログイン、オンボーディング; 続いてミーティング、ダッシュボード、トースト、ナビバーのツールチップ。各コミットが一群のリテラル文字列を翻訳呼び出しへ置き換えました。英語以外の29ファイルは一つのコミットで着地しました — 九千行の投下で、英語の原文をブルガリア語からベトナム語まであらゆるものへ翻訳しました。
そのどれも正しくやるのが難しくはありません。なぜなら_間違える_と派手に壊れるからです: プロバイダの欠落はクラッシュし、不正なJSONはパースに失敗し、悪いインポートはビルドを壊します。ライブラリは機械的なミスに対するガードレールを与えてくれます。それが与えてくれないのは、実際に起きたミスに対するガードレールなのです。
三つの言語設定、そしてそれが正しい理由
何が壊れたかへ進む前に、v1.8.3がまさに正しくやった一つのこと。多くのアプリが押し潰してしまう、しかし押し潰すべきでない区別だからです。このリリース以降、GeekByeは_三つ_の独立した言語設定を持ちます:
- AI応答言語(
output_language) — アシスタントが何語で答えるか。三つの中で最も古く、2025年11月からのもの。 - トランスクリプト言語(
transcriptLanguage) — 音声エンジンがあなたのマイクを何語として書き起こすか。数リリース前、v1.7.6で配管されたもの。 - App language(
app_language) — ボタン、メニュー、オンボーディングの言語。v1.8.3で新登場。
これらは本当に異なる軸です。ベルリンの開発者は、ドイツ語のインターフェース、英語のAI回答(貼り付けているドキュメントの言語が英語だから)、そして英語の書き起こし(ミーティングが英語だから)を望むかもしれません。これらを一つの「言語」設定に押し潰すのはその人にとって間違いであり、このリリースで追加されたオンボーディングのステップは、まさにその理由から三つすべてを別々のドロップダウンとして提示します。
気の利いた仕上げはアップグレードの経路です。一度きりのマイグレーションが、更新後の初回起動時に各既存ユーザーのoutput_languageを新しいapp_languageキーへコピーし、プロバイダはフォールバックの連鎖 — app_language、次にoutput_language、次に'en' — を通じて言語を解決します。だからスペイン語のAI回答が欲しいとすでにアプリに伝えていたユーザーは、英語にリセットされてその設定を探し回らされる代わりに、スペイン語のインターフェースで目覚めました。新しい設定を関連する古い設定から初期化するのは、一つのマイグレーションで済み、すべてのアップグレードユーザーの小さな煩わしさを取り除く、ちょっとした親切です。
再描画してくれなかったオンボーディング
i18nの作業から取り上げる価値のある本物のバグが一つあります。ランタイムで言語を切り替える人なら誰もがはまる、Reactの罠だからです。オンボーディングウィザードは選択肢リストの一部をuseMemoの中で組み立てます。_言語ステップそのものの上で_言語を変えたとき、UIは即座に新しい言語で再描画されるはずでした — が、そうならなかったのです。tがメモの依存配列に入っていなかったからです。翻訳関数は言語が変わるとその同一性(アイデンティティ)を変えます; メモ化した値がtから組み立てられているのにそれに依存していなければ、Reactは平然と、古い、前の言語のバージョンをあなたに手渡します。この修正はログに二度現れます — 一度はウィザードのために、一度は設定のドロップダウンリストのために — それはつまり、翻訳されたコンテンツをメモ化するあらゆる場所で、常にtを書き並べることを学ぶまで犯し続ける類のミスだということです。
オンボーディングは変更履歴が触れていない皺(しわ)も抱えていました: これはホワイトラベルのコードベースなので、翻訳すべきオンボーディングの木が_二つ_あるのです — GeekByeのウィザードとPavleurのウィザードが、並行するonboarding.*とonboarding.pavleur.*のキー名前空間の下に存在します。あらゆるオンボーディング文字列が、二つのブランドの声へ、29言語すべてにわたって、二度翻訳されました。
ビルドが欠けていると気づけなかったもの
ここが、これをチェックリストではなく教訓にする部分です。v1.8.3はハードコード文字列を抽出する自動的な手段も、ロケール横断のキー整合性のCIチェックも持っていませんでした。だから「完了」の定義は「誰かが覚えているコンポーネントを訪ね、目に入った文字列を包んだ」でした。その定義は漏れます。
リリースの二週間後、あるクリーンアップのコミットが "translate all hardcoded English strings across 10 components into 30 languages" というメッセージとともに着地しました。10のコンポーネント — 問題報告フォーム、サマリータブとメトリクスタブ、レポートタブ、トランスクリプトとチャットの内容、利用上限のプロンプト、チャットメッセージ、コードコピーボタン、フローティングアクションボタン — が、v1.8.3ではなお、すべての言語で、誰に対しても英語を話す状態で出荷されていたのです。誰のビルドも失敗しませんでした。どのテストも赤くなりませんでした。ドイツ語のユーザーはただレポートタブに英語を見ただけで、唯一の検出器は、いつか誰か人間がそれを見ることでした。
同じ不在が、より小さな気恥ずかしさたちも生みました。あるデンマーク語の翻訳が誤った単語 — Luk applikationen heltではなくLuk application helt — で出荷され、後のリリースでパッチされました。ネイティブのレビュアーのゲートがなく、パイプラインがそれを知る術もなかったからです。「アプリを終了」のキーは最初のバッチから欠けていて、数日後に埋め戻されました。そして以後に出荷されたあらゆる機能 — 修飾キーのプリセット、マイクミュート、リキャップチップ、オーバーレイの不透明度 — は、自分自身の「新しいキーを29ロケールへ翻訳する」コミットを、永遠に引きずりました。不変条件を自動で強制するものが何もなかったからです。
無秩序はコミットメッセージからさえ測れます。同じ30のロケールファイルが、リリースごとに「30 languages」「29 languages」そして — あるコミットでは「28 locales」と描写されています。何も追加も削除もされていません; v1.8.3からv2まで、どのタグにも30のファイルがあります。数字がぶれるのは、「私たちは何言語を出荷しているのか」の唯一の真実の源がなかったからで、だから各コミットの作者は手で数え、時に英語を一つの言語として数え、時に原文として数えたのです。自分の数を安定して保てない変更履歴は、それが描写している当のものが機械によって強制されていないという、小さく正直な信号です。
複数形の地雷
もう一つ、旗を立てておく価値のあること。派手に壊れたからではなく、この構成が招き、そして警告してくれない類のバグだからです。これらのロケールファイルにおける複数形処理は、ペアになったキーで手作業で行われています: meetings.meetingCountとmeetings.meetingCountPlural、profiles.filesAttachedとfilesAttachedPlural。これは英語の単数/複数のきれいな二分割です。それはまた、アプリがいまサポートすると謳う言語のうち相当数にとって間違ってもいます。ロシア語、ポーランド語、チェコ語は数に応じて三つや四つの複数形を持ちます — 「1個のファイル」「2個のファイル」「5個のファイル」「21個のファイル」がそれぞれ異なる語尾を取り得ます。二キーの単数/複数の仕組みではそれを表現できないので、それらの数え方は文法的にずれて描かれます。i18nextはまさにこのための本物の複数形機構を持っています; 手作業のペアはそれを回避してしまい、どのコミットもそのギャップを認めていません。これはハードコード文字列の漏れの複数形版です: ビルドには見えず、ネイティブスピーカーだけに見えるのです。
このリリースが教えてくれた三つのこと
- ライブラリは簡単な80%; ツールこそが難しい20%だ。
react-i18nextを配線して文字列を一括翻訳するのは解決済みの問題です。30のファイルを永遠に整合させ続けるのはそうではなく — そしてそれにはハードコード文字列の抽出ツールと欠損キーのCIチェックが要り、そのどちらもライブラリはあなたのために出荷しません。「完全なi18n」を謳う_前に_それらを作りなさい。さもなくば、保つ術のない不変条件を謳ったことになります。 - 本当に異なる言語設定は分けよ。 UI言語、トランスクリプト言語、AI応答言語は三つの軸であり、v1.8.3はそれぞれに独自のキーを与えたのが正しく — 新しいものを関連する古いものからマイグレーションで初期化して、アップグレードがユーザーに何のコストも課さないようにしたのはさらに正しかったのです。
- あなたの変更履歴の数え方は規律の指標だ。 同じファイル一式が三つのコミットにまたがって28、29、30言語であるとき、問題は数字ではありません — 欠けている真実の源です。機械があなたの言語を数えていないなら、誰も二度同じようには数えていないのです。
v1の物語の前の章については、ダッシュボードを1枚のウェーブチャートを中心に再設計する(v1.8.2)を; そして弧の全体については、ソフトウェアを完璧に出荷することの解剖学をご覧ください。