BookTranslator
BookTranslator

キーやプレースホルダーを壊さずにJSON言語ファイルを翻訳する方法

en.jsonファイル内のどの値が翻訳されるのか、ファイル翻訳ツールが何を書き換えるのか(コメント、キーの順序、大きなID)、そして無料のスクリプトとテストファイルを使ってプレースホルダー、ICU複数形、キーを確認する方法を説明します。

BookTranslator

BookTranslator Team

23 分で読了

en.jsonのようなJSON言語ファイルを翻訳するには、JSONを理解する翻訳ツールにファイル全体を渡してください。生のテキストをチャットウィンドウやWeb翻訳ツールに貼り付けるのは危険な方法です。JSONを理解する翻訳ツールはファイルを読み取り、テキスト値だけを翻訳し、同じキーを書き戻すので、アプリは引き続きファイルを読み込めます。コピー&ペーストでは、キーが翻訳されたり、引用符が変更されたり、波括弧が抜け落ちたりする可能性があります。そのいずれかがファイルを壊します。

キーを維持するのは簡単な部分です。問題のほとんどはその後で起こります。{name}プレースホルダーが{nom}になって返ってくる、ICU複数形がotherケースを失う、URLが他のテキストと同様に翻訳に送られる、翻訳ツールがファイル自体に加えるいくつかの変更などです。このガイドではそうした点を扱います。

私たちは、そうしたすべてのケースを含むテストファイルを作成し、ソースファイルとその翻訳を比較する無料チェッカーを書きました。また、BookTranslatorが使用するJSON解析・書き込みコードのローカルコピーでテストファイルを実行し、モデルの代わりにマーカーを置くことで、どの値が送信され、ファイルがどのように返ってくるかを正確に確認できるようにしました。

簡単な答え:ファイルの各部分はどうなるか

ファイルの部分テストからの例BookTranslatorの動作確認すべきこと
キー"greeting", "error"そのまま維持なし
プレーンテキストの値"Reading list"翻訳される意味、画面上の長さ
プレースホルダーを含む値"Welcome back, {name}!", "Saved %s of %d chapters"翻訳され、プレースホルダーは送信テキストに含まれるプレースホルダー名と数(チェッカーが行う)
ICU複数形メッセージ"{count, plural, one {# book} other {# books}}"1つの文字列として送信otherケース、および対象言語の複数形カテゴリ
インラインHTML"accept the <b>Terms</b>."翻訳され、翻訳後にタグ数がチェックされるタグ名とその数(チェッカーが確認します)
テキストとして保存されたURL"https://example.com/help"翻訳に送信される変更されずに返ってくること
文字を含まない値"2.4.1", "—", ""そのまま維持されるなし
数値、true/false、null20, trueそのまま維持される非常に大きなID(後述)
文字列の配列["Open", "Save", "Export as EPUB"]翻訳済みファイルでは翻訳され、バイリンガルファイルでは翻訳されずに残るバイリンガルファイル(使用する場合)
コメント(JSONC)// Shown on the home screen削除される。値に//も含まれている場合、ファイルの読み込みに失敗することがあるアップロード前にコメントを削除する

このガイドの残りの部分では、各行がどこから来るのか、そしてそれに対して何をすべきかを説明します。

ステップ1:アップロード前にソースファイルを確認する

翻訳する予定のファイルに対してチェッカーを実行します。必要なのはPython 3だけで、他には何も要りません:

python3 check_i18n_json.py en.json

テストファイルでは次のように出力されました:

WARN  limits.projectId: 9007199254740993 is above 2^53 and can change in a JavaScript round trip; store IDs as strings
WARN  error.network: a flat key with dots and a nested key resolve to the same dot path; tools that address strings by dot path can mix them up
INFO  (root): 3 keys look like integers; JavaScript sorts those first and in numeric order when it rewrites the object
INFO  source: 17 strings, 2 numbers

2つの警告と整数のようなキーに関する注意は、いずれもテストファイルを翻訳ツールに通したときに見られた変更に対応しています。ファイルにこれらのいずれも表示されない場合は、ステップ3に進んで構いません。

また、チェッカーはコメントや末尾のカンマを含むなど、厳密なJSONでないファイルを拒否します。それが次の問題になるからです。

テストファイルから返ってきたもの

テストは2026年11月2日に、BookTranslatorバックエンドの現在のJSON処理に対して実行されました。モデルを呼び出す代わりに、テストでは翻訳に送信されるすべての値の前に[ZH] と元のテキストを付けました。これにより結果が予測可能になります。先頭に[ZH]が付いているものは送信され、付いていないものは送信されていません。両方の出力が公開されています:翻訳済みファイルとバイリンガルファイルです。

翻訳済みファイル

すべてのキーは変更されずに返され、ファイルは引き続きパースできました。文字を含む値は送信されました。{name}、%s、ICUメッセージ、<b> タグ、ヘルプURLなどです。バージョン文字列 "2.4.1"、ダッシュ、空文字列は文字を含まないため送信されませんでした。数値とブール値はそのまま残されました。

他に4つの点が変更されました:

  1. 1つの文字列が誤ったキーに入りました。 テストでは、ファイルにフラットキー "error.network" とテキスト「Check your connection.」があり、さらにネストされた "error": { "network": "Network error" } もありました。翻訳ツールは各文字列をパーツ間をドットで区切ったパスで識別するため、両方の文字列が同じアドレス error.network を持っていました。出力では、両方のキーが「Network error」の翻訳を保持していました。「Check your connection.」は失われました。これには同じファイル内に両方のスタイルが必要で、稀なケースですが、サイレントに失敗します。チェッカーはアップロード前に警告します。
  2. 整数のようなキーが並べ替えられました。 "steps": { "10": ..., "2": ..., "1": ... } は "1"、"2"、"10" として返されました。JavaScriptはオブジェクトを再構築する際、整数のようなキーを常に数値順で最初に列挙します。キーと値は依然として一致しているためアプリは問題ありませんが、ソースファイルとの差分はノイズが多くなります。
  3. 大きなIDが精度を失いました。 9007199254740993 は 9007199254740992 として返されました。JavaScriptは数値を64ビット浮動小数点として保存し、2^53を超える整数はすべて正確に表現できません(MDNの Number.MAX_SAFE_INTEGER を参照)。長いIDは文字列として保存するか、言語ファイルから除外してください。
  4. フォーマットが書き換えられました。 出力は2スペースのインデントを使用し、改行で終わります。ソースがタブや4スペースを使用していた場合、差分に空白の変更が現れます。

テストファイルには重複キーはありませんでしたが、知っておく価値があります: JavaScriptのJSONパーサーは繰り返されたキーの最後の値のみを保持するため、以前のテキストは翻訳されません。アプリは同じ最後の値を読み込むため、目に見える問題はありませんが、チェッカーが重複キーをリストアップするのでクリーンアップできます。

バイリンガルファイル

バイリンガルファイルは各元の値を保持し、末尾に _translated を付けた兄弟キーを追加します:

"greeting": "Welcome back, {name}!",
"greeting_translated": "[ZH] Welcome back, {name}!",

これはレビューに適しています。ソースと翻訳が並んで表示されるからです。ただし、すべてのキーに双子ができたため、アプリが読み込めるファイルではありません。アプリでは翻訳済みファイルを使用してください。

1つのギャップ: 配列内の文字列には _translated の兄弟キーが付きません。配列項目には追加するキーがないためです。テストでは、"menu": ["Open", "Save", "Export as EPUB"] はバイリンガルファイルでは英語のまま返されましたが、翻訳済みファイルでは翻訳されていました。バイリンガルファイルからレビューする場合は、翻訳済みファイルの配列を確認してください。

コメント付きファイル

多くのプロジェクトでは言語ファイルにコメント(JSONC)を保持していますが、厳密なJSONでは許可されていません。翻訳ツールはまず厳密なJSONを試します。失敗した場合、コメントと末尾のカンマを削除して再試行します。コメントの削除とは、行内の // 以降をすべて削除することを意味し、https:// 内の // も含まれます。

コメントとURLを含むファイルはまったく読み込まれず、最初のコメントでパースが失敗しました。コメントはあるが値内に // がないファイルは正常に読み込まれ、コメントは出力から削除されました。

つまり、ファイルにコメントがある場合は、アップロード前に削除するか、コメント付きのコピーを保持して翻訳にコメントが含まれないことを受け入れてください。

ステップ2: ファイルを準備する

翻訳前にこれらの問題のほとんどを修正できます:

  • 厳密なJSONにする。 コメントと末尾のカンマを削除します。ビルドツールがJSONCを読み込む場合は、コメント付きファイルをソースとして保持し、翻訳用にクリーンなコピーをエクスポートします。
  • 1つのキースタイルを使用する。 キーをネストするかドットでフラット化するかのどちらかで、1つのファイルに両方を使用しないでください。i18nextを使用する場合、keySeparator オプションがキー内のドットの読み取り方法を制御するため、フラット化する前にその設定を確認してください。
  • テキスト以外のデータを移動する。 ID、URL、機能フラグ、制限は言語ファイルではなく設定に属します。URLを保持する必要がある場合は保持しますが、翻訳後に確認することを想定してください。翻訳しないガイドでは、他に何を元の言語のままにすべきかを判断する方法を説明しています。
  • プレースホルダーをどこでも同じ方法で書く。 {name} のような1つのスタイルは、{name}、{{name}}、%s の混在よりも確認が容易です。

ステップ3: ファイル全体を翻訳する

クリーンアップしたファイルをBookTranslatorのドキュメント翻訳ツールにアップロードします。このツールは、受け入れる他のローカリゼーションおよび構造化テキスト形式とともに .json をリストしています。価格はチェックアウト前に表示されます。タスクが完了すると、履歴ページから翻訳済みファイルとバイリンガルファイルを別々にダウンロードできます。

テキストの翻訳方法に関して、結果に影響する点が2つあります。

  • モデルはキーを見ることができません。 各値はキー名なしで番号付きのセグメントとして送信されます。"button.open" と "status.open" がどちらも「Open」と書かれている場合、モデルは一方が動詞で他方が形容詞であることを知る方法がありません。短く曖昧な文字列こそ、手動で確認すべきものです。
  • 各値は個別に翻訳されます。 2つのキーを結びつけるものは何もないため、同じ単語が2か所にあると異なる訳で返ってくることがあり、異なる翻訳が必要な単語が同じ訳で返ってくることもあります。BookTranslatorのオプションの自動用語集は、人名、地名、専門用語を収集して一貫性を保ちますが、独自の固定訳を指定することはできません。正確に1つの訳に固定しなければならない用語については、用語リストを用意し、翻訳後にそれらの文字列を確認してください。用語集の計画方法は用語集ガイドで説明しています。

インラインHTMLタグは翻訳後にカウントされ、タグが一致しないセグメントは再試行されます。中括弧のプレースホルダー、%s形式のプレースホルダー、ICU構文はテキストの一部として送信されますが、同じようにはチェックされません。すべての翻訳に対してステップ4を実行してください。

チャットモデルやローカルモデルで翻訳したい場合は、生のファイルではなく値だけを送信し、スクリプトでキーごとに戻してください。以下のチェックはどちらの場合にも当てはまります。

ステップ4:翻訳ファイルを確認する

翻訳を原文と比較します。

python3 check_i18n_json.py en.json fr.json

何を検出できるかを示すために、意図的にエラーを仕込んだフランス語ファイルを作成しました。これは手作業で作ったもので、モデルの出力ではありません。ステップ1と同じソース警告の後、チェッカーは次のように出力しました。

ERROR app.greeting: placeholders differ; lost ['name'] extra ['nom']
ERROR app.help_url: urls differ; lost ['https://example.com/help'] extra ['https://example.com/aide']
ERROR app.items: ICU message has no 'other' case
ERROR app.legacy: placeholders differ; lost ['%d'] extra []
ERROR limits.maxFiles: type changed from int to str
ERROR limits.projectId: non-text value changed: 9007199254740993 -> 9007199254740992
INFO  menu[1]: identical to source: 'Save'
INFO  compared 20 source values, 1 unchanged strings

これらはそれぞれアプリのバグとして現れます。空の名前、壊れたリンク、複数形がレンダリングされるときのクラッシュや生のメッセージ、欠落した章数、テキストとして比較される上限値などです。INFO行は変更されずに返ってきた文字列を一覧表示します。ブランド名のように問題ないものもありますが、フランス語メニューの「Save」はそうではありません。

チェッカーはキー、値の型、テキスト以外の値、プレースホルダー({name}、{{name}}、%s、%1$s、%@)、ICU引数名、HTMLタグ、URLを比較します。翻訳自体を評価するものではありません。

複数形のケースは変わるのが当然です

ICUの複数形は、複数カテゴリごとに1つのメッセージをリストします。英語にはoneとotherの2つのカテゴリがありますが、多くの言語では異なるセットを持っています。私たちのitemsメッセージをロシア語にうまく翻訳すると、通常はone、few、many、otherを使用し、日本語ではotherのみになります。したがって、ここでの不一致は必ずしもエラーではありません。

カテゴリはUnicode CLDRの複数形ルールに由来し、otherはすべての言語が持つカテゴリです。以下は、CLDR 48を使用したNode.jsのIntl.PluralRulesによって報告される基数カテゴリです。

言語複数形カテゴリ
英語、ドイツ語one、other
フランス語、スペイン語、ポルトガル語one、many、other
ロシア語、ポーランド語one、few、many、other
アラビア語zero、one、two、few、many、other
日本語、中国語、韓国語その他

フランス語、スペイン語、ポルトガル語では、many は100万のような非常に大きな丸められた数にのみ使われるため、小さい数のメッセージには通常不要です。チェッカーは other の欠落をエラーとして報告し、その他のカテゴリの変更はこの表と比較するための警告として報告します。任意の言語について、自分でリストを出力できます:

node -e 'console.log(new Intl.PluralRules("ru").resolvedOptions().pluralCategories)'

翻訳者には英語のケースが渡されるので、返ってきた内容を確認してください。ロシア語やアラビア語のメッセージにまだ one と other しかない場合は、不足しているケースを手動で追加するか、翻訳者に追加してもらいましょう。

次に、アプリで読み込みます

チェッカーが証明するのは、ファイルを読み込んでも安全であることであり、読みやすいことではありません。アプリを新しい言語に切り替えて画面を確認しましょう。ドイツ語やフィンランド語の長い単語がボタンからはみ出していないか、アラビア語やヘブライ語の右から左へのテキスト、コードが言語ファイルとは別にフォーマットする日付や数値などです。翻訳とローカリゼーションの違いに関するガイドでは、言葉以外に市場間で何が変わるかを説明しています。

よくある問題

翻訳したファイルがアプリで読み込めません。 チェッカーを実行してください。チェッカーが解析できない場合、ファイルは翻訳後に編集されたか、そもそも有効ではありませんでした。解析できる場合は、キーや型に関する ERROR 行を探してください。

文字列が {count, plural, ...} のような生の ICU メッセージとして表示されます。 翻訳で ICU 構文が壊れています。通常は中括弧の欠落か other ケースの欠落です。ソースから構造をコピーし、各ケース内の単語だけを翻訳してください。

ファイルが YAML、.po ファイル、または Markdown です。 BookTranslator はそれらも受け付けますが、各形式には独自のルールがあります。ドキュメントについては Markdown 翻訳ガイド を、ウェブページについては HTML ファイルガイド をご覧ください。

新しいキーだけを翻訳できますか? 1回のアップロード内ではできません。ファイル全体が翻訳されます。新しいキーを別のファイルに入れて翻訳し、結果をキーでマージしてください。チェッカーはマージされたファイルにすべてのキーがあることを確認します。

キーを翻訳すべきですか? いいえ。キーはコードが参照する識別子です。値だけが人が読むためのテキストです。

関連記事