言語を増やしたときに最初に壊れるもの\n最初に壊れるのはコードではなく、言葉です。\n\nチャットで作るアプリはプロトタイプとして素早く始まることが多く、「'保存'と書かれたボタンを追加して」とタイプするとUIが出て先へ進みます。数週間後にスペイン語やドイツ語を追加しようとすると、そうした“臨時”のラベルが画面、コンポーネント、メール、エラーメッセージに散らばっていることに気づきます。\n\n文言の変更はコード変更より頻繁に発生します。製品名の変更、法務文の更新、オンボーディングの書き直し、サポートからの明確なエラーメッセージ要求など。テキストが直接UIコードにあると、わずかな言い回しの変更でもリリースがリスクになり、同じ意味が別々に表現されている箇所を見落とします。\n\n翻訳負債の初期症状は次の通りです:\n\n- 1つの画面に混在する言語(いくつかは翻訳済み、他はまだ英語)\n- 小さな違いで重複するラベル("Sign up"、"Sign Up"、"Create account")\n- テキストが長くなると壊れるレイアウト(ボタンのオーバーフロー、見出しの折り返し)\n- モバイルとWebの乖離(同じアクションに違う語を使う)\n- サポート文書やシステムメッセージがUIに遅れをとる\n\n現実的な例:Koder.aiでシンプルなCRMを作ると、Webは「Deal stage」、モバイルは「Pipeline step」、そしてエラートーストは「Invalid status」と表示されることがあります。たとえすべて翻訳されていても、概念が一致していなければユーザーはアプリを一貫性がないと感じます。\n\n「一貫性」とは必ずしも「どこでも同じ文字列」という意味ではありません。次を意味します:\n\n- 同じ概念には同じキーと同じ意味を使う。\n- Webとモバイルが同じ翻訳の唯一の真実源を共有する。\n- 文体と用語が安定している(丁寧かカジュアルか、"customer"と"client"の使い分けなど)。\n- UIレイアウトは長短のフレーズに対応するように設計されている。\n\nテキストを装飾ではなくプロダクトデータとして扱えば、言語追加は慌てる作業ではなく、通常の開発工程の一部になります。\n\n## 基本概念と目標\n\nInternationalization (i18n) はアプリが多言語をサポートできるようにするための仕組みです。Localization (l10n) は特定の言語・地域向けの実際のコンテンツ(例:カナダのフランス語に合った語、日付形式、トーン)です。\n\n目指すべきシンプルな目標:ユーザーに見えるすべてのテキストを、UIコードに直接書き込むのではなく、安定したキーで参照できるようにすること。ReactコンポーネントやFlutterウィジェットを開かずに文章を変更できれば、正しい方向に進んでいます。チャットで生成された文言がハードコーディングされやすいチャット生成アプリにはこれが特に重要です。\n\nユーザー向けテキストにはボタン、ラベル、検証エラー、空状態、オンボーディングのヒント、プッシュ通知、メール、PDF出力、ユーザーが見たり聞いたりするあらゆるメッセージが含まれます。通常、内部ログ、DBカラム名、分析イベントID、フラグ、管理者向けデバッグ出力は含みません。\n\n翻訳はどこに置くべきか? 実務ではフロントエンドとバックエンドの両方に置くことが多く、明確な境界を設けます。\n\n- フロントエンドはナビゲーション、フォーム、メニュー、画面上の大部分のテキスト(UIクローム)を所有します。\n- バックエンドはトランザクションメール、サーバ側の検証エラー、APIエラー応答など、サーバが生成するメッセージを所有します。\n- 注文ステータスのラベルなど共有ドメインは一つの真実の源から供給し、Webとモバイルの乖離を防ぎます。\n\n避けるべきミスは責任を混ぜることです。バックエンドがUIエラーに対して完全な英語の文章を返すと、フロントエンドで綺麗にローカライズできません。より良いパターンは:バックエンドはエラーコード(と安全なパラメーター)を返し、クライアント側がそのコードをローカライズ済みメッセージにマップすることです。\n\nコピーの所有は技術的細部ではなくプロダクトの判断です。誰が言葉を変え、トーンを承認するかを早めに決めましょう。\n\nプロダクトがコピーを管理するなら、翻訳をコンテンツとして扱い、バージョン管理、レビュー、プロダクトが変更を要求できる安全な方法を用意します。エンジニアがコピーを管理するなら、新しいUI文字列はキーとデフォルト翻訳を付けないと出荷できないルールにします。\n\n例:サインアップフローで3つの画面に「Create account」とあるなら、それを1つのキーで共通化します。意味の一貫性を保ち、翻訳者の作業を速くし、小さな文言変更が全画面の清掃作業に発展するのを防げます。\n\n## 文字列キーを安定させる方法\n\nキーはUIと翻訳の間の契約です。その契約が変わり続けると、表示されないテキスト、急ぎの修正、不一致した文言が発生します。チャット生成アプリ向けの国際化アーキテクチャで最初に決めるべきルールは:キーは現在の英語の文を説明するのではなく、意味を表すこと、です。\n\n全文をキーにする(例:"Pay now")のではなく、安定したID(例:)を使ってください。文をキーにすると、句読点や大文字の変更で壊れます。\n\n読みやすく、かつ実用的なパターンは:画面(またはドメイン) + コンポーネント + 意図 を使うこと。退屈で予測可能に保ちます。\n\n例:\n\n- \n- \n- \n- \n- \n\nキーを再利用するか新しく作るかの判断基準は:「すべての場所で意味は完全に同じか?」です。本当に汎用的なアクションには再利用を、文脈が変わる場合は分けてください。たとえばプロフィール画面の「Save」は単純な操作で再利用できるかもしれませんが、複雑なエディタ内の「Save」は言語によって微妙に異なるトーンが必要になることがあります。\n\n共通UIテキストは専用の名前空間に置いて重複を避けます。よく使われるバケツ(名前空間):\n\n- (save, cancel, delete)\n- (loading, success)\n- (search, password)\n- (validation, network)\n- (タブ、メニュー項目)\n\n言い回しが変わっても意味が同じならキーは維持して翻訳文だけ更新します。キーは識別子であり、コピーではありません。意味が変わったら新しいキーを作り、古いキーはすぐに削除せず未使用を確認してから削除するのが安全です。これにより古い翻訳が残って意味がずれるのを防げます。\n\nKoder.aiのようなフローの小さな例:チャットでReactとFlutterを同時生成する場合、両者が を使えば一貫した翻訳が得られます。しかしWebが を使い、モバイルが を使うと、英語は同じに見えても時間とともに乖離します。\n\n## 文字列をどこに置き、翻訳ファイルをどう整理するか\n\nソース言語(多くの場合英語)を唯一の真実と見なし、一箇所に保管し、コードのようにレビューし、文字列が勝手にコンポーネントに現れるのを防ぎます。これがハードコーディングを避ける最速の方法です。\n\nシンプルなルール:アプリはi18nシステムからのテキストのみを表示して良い。新しいコピーが必要な場合は、まずキーとデフォルトメッセージを追加してからUIでそのキーを使う。この習慣が、チャット生成アプリでも国際化アーキテクチャを安定させます。\n\n### 管理しやすいフォルダ構成\n\nWebとモバイルの両方を配布するなら、キーの共有カタログが1つと、機能チームが衝突しない余地が欲しい。実用的な構成例:\n\n- /i18n\n- /i18n/locales/en.json(ソース)\n- /i18n/locales/es.json、/i18n/locales/fr.json、...\n- /i18n/features/billing.json、/i18n/features/auth.json、...\n- /i18n/shared.json(ボタン、共通ラベル、エラー)\n\nプラットフォーム間でキーを同一に保ちます(実装はReactとFlutterで異なっても良い)。Koder.aiのようなプラットフォームでチャットから両方を生成する場合、両プロジェクトが同じキー名とメッセージ形式を参照すると管理が容易になります。\n\n### バージョン管理とレビューで翻訳負債を防ぐ\n\n翻訳は時間とともに変わります。変更を小さく、レビュー付き、追跡可能に扱いましょう。良いレビューは綴りだけでなく意味と再利用を重視します。\n\n- ソースロケールの変更にはPRレビューを必須にする\n- キーを検索と計画なしに削除することをブロックする\n- 文が曖昧な場合は翻訳者向けの注釈を追加する\n- CIで単純な「未翻訳チェック」を行う\n\nチーム間でキーがずれないよう、キーは機能(billing.)に所有させ、文言変更のためにキー名を変更しないルールを設けます。キーは識別子であり、文章ではありません。\n\n## ハックなしでの複数形と文法対応\n\n複数形ルールは言語ごとに異なり、英語の単純なパターン(1 とそれ以外)ではすぐに破綻します。言語によっては0、1、2〜4など別の形があり、他言語では文全体が変わります。UIにif-elseで複数形ロジックを埋め込むと、コピーを重複させ、抜けを生みます。\n\n安全なアプローチは、考え方を一つの柔軟なメッセージにまとめ、i18nレイヤーに正しい形を選ばせることです。ICUスタイルのメッセージはそのために作られており、文法上の判断をコンポーネントではなく翻訳側に移します。\n\nよく忘れられるケースをカバーする小さな例:\n\n\n\nこの1つのキーで0、1、それ以外をカバーします。翻訳者は各言語に合わせて適切な複数形を置き換えられます。\n\n性別や役割に基づく文言が必要な場合、 と のようにキーを分けるのは避け、 を使って文を一つに保ちます:\n\n\n\n文法ケースに縛られないように、文はできるだけ完結に保ち、断片をつなぎ合わせるのは避けてください。 のように断片を結合すると、多くの言語で語順が変えられず対応できません。数字、名詞、周辺の言葉を含むメッセージを一つにまとめましょう。\n\nチャット生成アプリ(Koder.aiプロジェクトを含む)に有効な簡単なルール:文に数字、人物、ステータスが含まれる場合は最初からICUにする。初期コストは少し増えますが、後の翻訳負債を大幅に減らします。\n\n## Webとモバイルの翻訳を一致させる方法\n\nReactのWebアプリとFlutterのモバイルアプリがそれぞれ独自の翻訳ファイルを持つと、必ず乖離します。ボタンの文言が異なったり、キーの意味が異なったりして、「アプリはXと言うがWebはYと言う」とサポートに書かれるようになります。\n\n最も重要で単純な解決策は、真っ先に一つの真実のソースと形式を選び、それをコードのように扱うことです。多くのチームでは、ICUスタイルのメッセージを使ったJSONなど、共通のロケールファイルを1セット用意し、Webとモバイルの両方がそれを参照します。チャットやジェネレーターでアプリを作る場合、同じテキストが2箇所に生まれやすいため、この点は特に重要です。\n\n### 共有の真実の源(single source of truth)\n\n実用的なセットアップは小さな "i18nパッケージ" またはフォルダを用意し、次を含めます:\n\n- 各言語のロケールファイル(キーはどこでも同じ)\n- メッセージ形式ルール(複数形とプレースホルダーにICUを使用)\n- キー追加方法を説明した短いREADME\n\nReactとFlutterは消費者となり、ローカルで新しいキーを発明してはいけません。Koder.aiスタイルのワークフロー(ReactとFlutterを生成)では、同じキーセットから両クライアントを生成し、変更を他のコード変更と同様にレビューします。\n\nバックエンドの整合も同じ話です。エラーや通知、メールをGoの中で英語の文章として書くのは避け、代わりに安定したエラーコード(例:)と安全なパラメーターを返します。クライアント側がそのコードを翻訳済みテキストにマップするか、サーバが同じキーとロケールファイルを使ってテンプレートをレンダリングします。\n\n### 同期を保つルール\n\n小さなルール集を作り、コードレビューで守らせます:\n\n- 新しいUIテキストは共有ロケールファイルに最初にキーを追加すること\n- キーは明瞭な名前空間と意図を含める(画面位置ではなく意図を示す)\n- すべてのキーは一度だけプレースホルダーを定義し、どこでも同じように使うこと\n- 意味が異なる2つのフレーズは決して同じキーを共有しない\n- 意味が同じ2つのキーは1つに統合して残りを削除する\n\n重複キーを防ぐため、翻訳者や将来の自分のために説明フィールド(あるいはコメントファイル)を追加します。例: がバナー用かメール用かを説明する一行があれば、“だいたい同じだから共用”という誤用を止められます。\n\nこの一貫性が、チャットで作るアプリ向けの国際化アーキテクチャの背骨です:一つの共通語彙、多くの表示面、次の言語を出しても驚きがないこと。\n\n## 実プロジェクトで従えるステップバイステップ設定\n\nチャット生成アプリ向けの良い国際化アーキテクチャはシンプルに始まります:一組のメッセージキー、コピーの唯一の真実の源、Webとモバイルで同じルール。Koder.aiのように素早く作るなら、この構造が速度を保ちながら翻訳負債を生みません。\n\n### 実用的なセットアップ(Web + モバイル)\n\nロケールを早めに決め、翻訳が欠けているときにどうするかを決めます。一般的な選択肢は:ユーザーの優先言語を優先、なければ英語にフォールバック、未翻訳キーはログに残して次リリース前に修正、などです。\n\nその上で次を実行します:\n\n- サポートする言語、デフォルトロケール、フォールバック順序を決め、ロケール検出方法(ブラウザ/アプリ設定、ユーザープロファイル)を合意します。\n- 意味ベースの安定したキーを使う(全文をキーにしない)。例: や 。同じキーをどこでも使う。\n- Reactではアプリをi18nプロバイダでラップし、コンポーネント内で を使います。Flutterではローカライズラッパーを使い、同じキーによるルックアップをウィジェット内で呼びます。目的は同じキーを使うことであって、ライブラリを揃えることではありません。\n- 複数形やプレースホルダーにICUスタイルを使う(例: や )。 のようなハックを散らさない。\n- 出荷前に新規/変更文字列をレビュー:キー命名、ハードコーディングの除去、プレースホルダーの整合、Webとモバイルが変更を拾っているか確認する。\n\n最後に、長い単語の言語(ドイツ語)と句読点が異なる言語で実際にテストします。これでボタンのオーバーフロー、見出しの折り返し、英語前提のレイアウト問題が早期に分かります。\n\n翻訳を共有フォルダ(あるいは生成パッケージ)に保ち、コピー変更をコード変更として扱えば、チャットで素早く作ってもWebとモバイルは一貫性を保てます。\n\n## 動的コンテンツ:日付、数値、ユーザーテキスト\n\n翻訳されたUI文字列は問題の半分にすぎません。アプリは日付、通貨、カウント、名前のような動的な値も表示します。それらをプレーンテキストとして扱うと、フォーマットが変、タイムゾーンが間違い、文全体が多くの言語で不自然になります。\n\nまず数値、通貨、日付はロケールルールでフォーマットし、独自実装を避けましょう。フランスのユーザーは「1 234,50 €」を期待し、米国のユーザーは「$1,234.50」を期待します。日付も同様で、「03/04/2026」は曖昧なのでロケールに合わせたフォーマットが必要です。\n\nタイムゾーンは次の落とし穴です。サーバーは中立的な形式(通常はUTC)でタイムスタンプを保存し、ユーザーは自分のタイムゾーンで時刻を見たいと期待します。例えばUTCで23:30に作成された注文は東京では「明日」になるかもしれません。画面ごとにルールを決めましょう:個人のイベントはユーザー現地時間で表示、店舗受け取り時間などはビジネスの固定タイムゾーンを表示してそれを明示する、など。\n\n翻訳された断片を結合して文を作るのは避けてください。語順が言語ごとに異なるため壊れます。次のような結合は避けて:\n\n\n\n代わりにプレースホルダーを用いた1つのメッセージを使います:。翻訳者は語順を安全に並べ替えられます。\n\n### 右から左(RTL)言語\n\nRTLは単なる文字方向の問題ではありません。レイアウトの流れが反転し、一部のアイコン(戻る矢印など)は鏡像化が必要で、英語の製品コードとアラビア語が混在すると表示順が意外になることがあります。単一ラベルだけでなく実際の画面でテストし、コンポーネントが方向変更に対応することを確認してください。\n\n### ユーザー生成コンテンツ\n\nユーザーが書いたもの(名前、住所、サポートチケット、チャットメッセージ)は翻訳しないでください。まわりのラベルやメタデータ(日付、数値)は翻訳/フォーマットできますが、コンテンツ自体は原文のままにします。後で自動翻訳を追加する場合は、明示的な機能(原文/翻訳の切り替えトグル)にしてください。\n\n実用例:Koder.aiで作られたアプリが を表示する場合、これを1つのメッセージにして と はロケールに合わせてフォーマット、時刻はユーザーのタイムゾーンで表示します。このパターンだけで多くの翻訳負債が防げます。\n\nよくあるバグを防ぐ簡単ルール:\n\n- タイムスタンプはUTCで保存し、閲覧者のロケールとタイムゾーンでフォーマットする。\n- 完全な文の中にプレースホルダーを入れ、断片をつなげない。\n- 通貨はロケールルールと正しい通貨コードでフォーマットする。\n- 実画面で少なくとも1つのRTLロケールをテストする。\n- ユーザー生成テキストはデフォルトで翻訳しない。\n\n## 翻訳負債を生む一般的なミス\n\n翻訳負債は通常「ちょっとした文字列」で始まり、後で数週間の手直しになります。チャット生成プロジェクトでは、UIテキストがコンポーネント内やフォーム、バックエンドメッセージで生成されやすく、これが加速します。\n\n### 後で痛い問題\n\n最もコストの高い問題はアプリ中に広がり、見つけにくくなるものです。\n\n- UIコンポーネント内にコピーをハードコーディングする(プレースホルダー、空状態、ボタンラベル含む)。監査や再利用ができず、小さな文言変更でコード修正が必要になる。\n- バックエンドの検証やAPIエラーが英語の文章を返す。UIに混在言語が現れ、エラーを翻訳済みメッセージにマップできない。\n- 英語の全文を翻訳キーに使う。便利に感じるが文言が変わるとキーが入れ替わり、古いキーが残り、翻訳メモリを失う。\n- Webとモバイルでキーをコピーして別々に編集する。時間とともに画面が乖離し、ユーザーは不一致を感じる。\n- 非英語を追加するまで複数形ルールを後回しにする。すると多数の「1 items」バグや簡単に直せない文法上の違和感が発生する。\n\n### 実例(現場での見た目)\n\nReactのWebアプリとFlutterのモバイルアプリが「You have 1 free credit left」という請求バナーを表示しているとします。誰かがWeb側の文言を「You have one credit remaining」に変えてキーを全文のままにしてしまうと、モバイルは古いキーを使い続けます。結果、1つの概念に対し2つのキーが生まれ、翻訳者は両方を翻訳しなければなりません。\n\nより良いパターンは安定したキー(例:)とICUによる複数形対応です。Koder.aiのようなツールを使うなら早めにルールを追加してください:チャットで生成されたユーザー向けテキストはコンポーネントやサーバーエラーの中に置かず、必ず翻訳ファイルに入れる。小さな習慣が、プロジェクトの成長時に国際化アーキテクチャを守ります。\n\n## クイックチェックリスト、実例、次のステップ\n\n国際化が煩雑に感じるときは、基本が文書化されていないことが多いです。小さなチェックリストと具体例がチーム(と将来の自分)を翻訳負債から守ります。\n\n新しい画面で毎回行うクイックチェック:\n\n- 1つの意味につき1つのキー(例:、のように見た目で分けない)。\n- デフォルト言語を定義し、キーが欠けているときの挙動を決める(フォールバック表示、ログ、リリースブロックなど)。\n- カウントにはICUスタイルを使う(0、1、多数)/文字列ハックは避ける。\n- 日付、金額、数値はロケールごとにフォーマット(通貨は言語と分離)。\n- 右から左言語を早期にテストして、画面が増える前にレイアウトの問題を見つける。\n\n簡単な例:請求画面を英語、スペイン語、日本語でローンチするとします。UIには「Invoice」「Paid」「Due in 3 days」「1 payment method / 2 payment methods」「$1,234.50」といった要素があります。国際化アーキテクチャを整えていれば、キーを一度定義してWebとモバイルで共有し、各言語は値を埋めるだけです。 はICUメッセージにし、金額フォーマットはロケール対応のフォーマッタに任せます。\n\n言語サポートは機能単位で展開しましょう、大規模な一斉対応は避ける:\n\n1. トラフィックの多いフロー(請求、オンボーディング、チェックアウト)から始める。\n2. ハードコーディングされたUIコピーを翻訳ファイルへ移し、キーに置き換える。\n3. 同じフローで複数形メッセージとフォーマットを追加する。\n4. 未翻訳キーがほぼゼロになるまで次の機能に進まない。\n\n新機能が一貫するように2つを文書化してください:キー命名ルール(例を含む)と文字列に関する「定義済み完了条件」(ハードコードなし、複数形はICU、日付/数値はフォーマット、共有カタログに追加)。\n\n次のステップ:Koder.aiで開発しているなら、Planning Modeで画面とキーを定義してからUIを生成してください。スナップショットとロールバックを使えば、Webとモバイルでコピーと翻訳を安全に反復できます。\n