JavaScript 翻訳ファイルのキーの追加漏れを自動で見つける
日本語の画面に項目を追加したのに、英語の翻訳ファイルを更新し忘れることがあります。
毎回すべての画面を言語ごとに開いて確認する代わりに、翻訳ファイルのキーがそろっているかを機械で確認できます。
今回は、追加のライブラリを使わず、Node.jsで2つのJSONファイルを比較する方法を紹介します。文字列の内容は言語ごとに違ってよいものとし、項目名だけを比べます。
入れ子を含むキーを比較する
locales/ja.json{
"profile": {
"title": "プロフィール",
"save": "保存"
}
}locales/en.json{
"profile": {
"title": "Profile"
}
}この例では英語側のprofile.saveが不足しています。
一番上のキーだけなら、どちらにもprofileがあるため違いを見つけられません。入れ子をたどり、文字列に到達するまでのキーをprofile.saveのようにまとめます。
ファイルを配置する
- project
- locales
- ja.json
- en.json
- scripts
- check-locale-keys.mjs
- locales
今回は値が文字列、または入れ子のオブジェクトである形式だけを扱います。キー自体に.を含めないルールにし、配列や数値はエラーとして検出します。
比較するスクリプトを書く
scripts/check-locale-keys.mjsimport { readFileSync } from 'node:fs';
const collectKeys = (value, prefix = '', keys = new Set()) => {
if (typeof value === 'string' && prefix !== '') {
keys.add(prefix);
return keys;
}
if (value === null || typeof value !== 'object' || Array.isArray(value)) {
throw new Error(`文字列またはオブジェクトが必要です: ${prefix || '(root)'}`);
}
for (const [key, child] of Object.entries(value)) {
if (key === '' || key.includes('.')) {
throw new Error(`空のキーやドットを含むキーは使えません: ${key}`);
}
collectKeys(child, prefix ? `${prefix}.${key}` : key, keys);
}
return keys;
};
const readKeys = (relativePath) => {
const file = new URL(relativePath, import.meta.url);
return collectKeys(JSON.parse(readFileSync(file, 'utf8')));
};
const ja = readKeys('../locales/ja.json');
const en = readKeys('../locales/en.json');
const errors = [];
for (const key of [...ja].sort()) {
if (!en.has(key)) errors.push(`en: ${key} がありません`);
}
for (const key of [...en].sort()) {
if (!ja.has(key)) errors.push(`ja: ${key} がありません`);
}
if (errors.length > 0) {
console.error(errors.join('\n'));
process.exitCode = 1;
} else {
console.log('翻訳キーが一致しています。');
}Setには比較したいキーを入れ、hasで相手側に存在するか確認します。日本語から英語、英語から日本語の両方向を比べるため、どちらにだけ追加された場合も検出できます。
ファイルの位置はスクリプト自身を基準にしています。実行した作業ディレクトリの違いで、別のファイルを読むことを避けるためです。
コマンドで実行する
プロジェクトのルートから実行します。Node.js 20以降で使える例です。
翻訳キーの確認node scripts/check-locale-keys.mjs最初のJSONの例なら、次の内容が表示され、終了コードは1になります。
不足がある場合en: profile.save がありません英語へ"save": "Save"を追加すると成功します。JSONの構文が不正な場合やファイルがない場合も、例外により失敗します。
npmスクリプトから呼ぶ場合は、既存のpackage.jsonのscriptsへ追加できます。
package.jsonへ追加する項目{
"scripts": {
"i18n:check-keys": "node scripts/check-locale-keys.mjs"
}
}既存のscripts全体を置き換えるのではなく、項目を追加します。ビルド前やCIで実行すれば、翻訳キーの追加漏れを早めに見つけられます。
キーが同じでも翻訳が正しいとは限らない
このチェックが確認するのは項目名です。英語側へ日本語をコピーした場合や、値が空文字列の場合でも、キーが一致すれば成功します。
また、{{name}}のような差し込み変数の一致も検査していません。
| 問題 | このスクリプトで検出できるか |
|---|---|
| 英語側だけキーがない | できる |
| 日本語側だけキーがない | できる |
| 値に配列や数値がある | 形式エラーになる |
| 訳文の意味が間違っている | できない |
| 差し込み変数が不足している | できない |
| 使われていないキーが残っている | できない |
言語ごとに複数形のキー構成が異なるなど、翻訳ライブラリの仕様で差が必要な場合は、完全一致のルールをそのまま使えません。例外にする範囲を明示するか、その形式に対応した検証を使います。
この例は文字列を持たない空のオブジェクトをキーとして数えません。空の翻訳グループも検出したい場合は、別の条件を追加します。
使用箇所の抽出とは分けて考える
ソースコードから翻訳キーを抽出する処理と、言語間のキーを比較する処理は目的が違います。
抽出では「どのキーを使っているか」を確認し、今回の比較では「言語間で項目がそろっているか」を確認します。両方を組み合わせると、未使用の翻訳と追加漏れを別々に扱えます。
小さなスクリプトでも、手作業で見比べていた構造の違いを検出できます。訳文の品質は人が確認し、機械で比べられるキーの有無は自動化すると、確認作業を分担できます。