-多言語(i18n)リソースを別リポジトリに分離し、自動検証・デプロイまで組み込んだ経験-
1. はじめに
私が担当していた観光地コンテンツサービスでは、韓国語を基準に、英語とウズベク語(ラテン文字・キリル文字の2表記)、カラカルパク語まで、合計4言語・5つの言語パックをサポートしています。
画面のボタン文言から案内メッセージ、バリデーションメッセージまで、すべてのテキストが言語ごとのJSON翻訳ファイルに依存しています。
この記事では、フロントエンドコード内で管理していたこれらの翻訳ファイルを別リポジトリに分離し、翻訳漏れを自動的に検出する検証スクリプトとデプロイスクリプトを作成した経験をまとめます。
2. 従来の方式の限界
分離する前は、フロントエンドプロジェクト内に言語ごとのJSONファイルを配置し、文言の修正依頼が来るたびに基準言語のファイルを修正した後、残り4つの言語ファイルにも同じキーを1つずつ追加していました。
この方式には、大きく3つの問題がありました。
-
文言を1つ修正するだけの軽微な作業でも、フロントエンド全体を再ビルドしてデプロイしなければ反映されませんでした。
-
誤字を1つ直すだけでも、CIビルドが終わるまで待つ必要がありました。
-
言語ごとにキーを手作業で移していたため、特定の言語だけキーが抜けるミスが頻発し、その言語を使用するユーザーの画面に翻訳キーの値がそのまま表示されたり、空文字列が出力されたりする問題が繰り返し発生しました。
-
このような漏れを検出する検証手順がなかったため、デプロイ後にQAや実際のユーザーが発見するまで、問題を知る方法がありませんでした。
-
ウズベク語はラテン文字表記とキリル文字表記を別ファイルで管理していたため、2つの表記間でもキーがずれるケースが特に頻繁に発生しました。
3. 改善方針:言語パックのリポジトリ分離
翻訳リソースは静的ファイルなので、アプリケーションコードとデプロイ周期を必ずしも合わせる必要はないという点に着目し、翻訳JSONだけを個別に管理するリポジトリを作成しました。
実際のサービスインフラには静的ファイルを配信するKubernetes nginx Podがあるため、このPod内のJSONファイルだけを更新すれば、フロントエンドを再デプロイせず、次のリクエストから新しい翻訳をすぐに反映できます。
リポジトリを分離すると、検証ロジックもはるかに組み込みやすくなりました。
対応言語と構成は以下のとおりです。
|
言語コード |
言語 |
備考 |
|---|---|---|
|
ko-KR |
韓国語 |
基準言語(base language) |
|
en-US |
英語 |
- |
|
uz-Latn / uz-Cyrl |
ウズベク語(ラテン文字・キリル文字表記) |
表記体系ごとに個別管理 |
|
kaa-Latn |
カラカルパク語(ラテン文字表記) |
- |
各言語フォルダは、画面領域ごとにcommon、admin、validation、userの4つのファイルに分けました。
1つの大きなファイルを複数人が同時に修正することで発生する競合を減らし、問題が起きたときにどの領域かをすぐに把握できるようにするためです。
4. 翻訳キーの自動検証:validate.js
Node.jsの組み込みモジュール(fs、path)だけで検証スクリプトを作成しました。
核心は、韓国語を基準にして他の言語ファイルのキー集合を比較することです。JSONがbutton.saveのようなネスト構造を持つため、これをドット(.)表記のパスに平坦化(flatten)してから比較します。
function collectKeys(obj, prefix = "") {
const keys = [];
Object.keys(obj).forEach((key) => {
const currentPath = prefix ? `${prefix}.${key}` : key;
if (typeof obj[key] === "object" && obj[key] !== null) {
keys.push(...collectKeys(obj[key], currentPath));
} else {
keys.push(currentPath);
}
});
return keys;
}
基準言語にはあるものの対象言語にないキーはError、反対に対象言語にだけあるキーはWarningとして区別しました。
画面表示を壊す本当の問題であるErrorが1つでもあれば、process.exit(1)で終了コードを返すようにしました。このスクリプトはデプロイスクリプトの最初の段階で呼び出されるため、ここで異常終了すると、その後のデプロイ手順自体が実行されません。
翻訳が欠落した状態では、物理的にデプロイできない構成になっています。
5. 自動デプロイパイプライン
デプロイは、検証 → tar圧縮 → nginx Podの自動検出 → kubectl cpによるPodへのコピー → Pod内部での解凍とクリーンアップという、5つの段階で構成されています。
チームにはmacOSとWindowsのユーザーが混在しているため、bash用とPowerShell用のスクリプトをそれぞれ作成し、どちらも常に同じ順序で動作するように合わせました。
Pod名は再デプロイのたびに変わるため、ハードコーディングせず、毎回取得するようにしました。
$POD = kubectl get pod -n $NAMESPACE --no-headers |
Where-Object { $_ -match "^nginx" } |
ForEach-Object { ($_ -split "\s+")[0] } |
Select-Object -First 1
なお、macOSでtarを実行すると、`._*`形式のメタデータファイルも一緒に生成されます。このファイルがPod内にそのまま残ると、静的ディレクトリが散らかった状態になります。
bashスクリプトではCOPYFILE_DISABLE=1環境変数と--excludeオプションでこれを除外しますが、Windowsのtarはそもそもこのようなメタファイルを作成しないため、PowerShellスクリプトには該当するロジックがありません。
2つのスクリプトを無理に1つへ統合せず、プラットフォームごとに実際に必要な処理だけを残したのも、それぞれの環境で直接実行して確認したうえでの判断でした。
トラブルシューティング:Windowsでのみ失敗していたデプロイ
最初は、Pod内部で実行する複数のシェルコマンド(移動、解凍、削除、一覧表示)を1つのhere-stringにまとめ、kubectl execへ一括で渡していました。
# 최초 버전 — 여러 명령을 하나의 here-string으로 묶어 전달
$cleanupScript = @"
cd $NGINX_HTML
tar -xvf $TAR_NAME
rm $TAR_NAME
"@
kubectl exec -n $NAMESPACE $POD -- /bin/sh -c $cleanupScript
macOS bashではこの方式が問題なく動作しましたが、Windows PowerShellでは外部プロセス(kubectl.exe)に引数を渡す方法が異なるため、改行を含む文字列が途中で切れたり、行単位に分割されたりしました。その結果、Pod内でコマンドが途中までしか実行されないエラーが発生しました。
原因を把握した後は、複数のコマンドを一つにまとめる方式自体をやめ、kubectl execの呼び出しをコマンド単位に分割しました。
# 수정 버전 — 명령을 kubectl exec 호출 단위로 분리
kubectl exec -n $NAMESPACE $POD -- rm "${NGINX_HTML}/locales"
kubectl exec -n $NAMESPACE $POD -- tar -xvf "${NGINX_HTML}/${TAR_NAME}" -C $NGINX_HTML
kubectl exec -n $NAMESPACE $POD -- rm "${NGINX_HTML}/${TAR_NAME}"
コマンドを個別の呼び出しに分けると、PowerShellが引数を切り取る問題そのものが解消されました。
呼び出し回数は4回に増えましたが、1つのPodに対するローカル操作だったためコストは無視できる程度で、この経験を通じて、クロスプラットフォームスクリプトは両方の環境で実際に実行してみるまでは完成したとはいえないということを改めて実感しました。
6. 導入効果とまとめ
リポジトリを分離して自動化を導入してからは、文言の修正がフロントエンドのデプロイと完全に分離され、スクリプトを一度実行するだけで数秒以内に反映されるようになりました。また、翻訳漏れはデプロイ前に機械的に検出されるため、特定の言語圏のユーザーだけが壊れた画面を見るという事態もなくなりました。
さらに、macOSでもWindowsでも同じ手順でデプロイできるようになり、デプロイ方法を知っている人が一人に偏る負担も軽減されました。
華やかな技術を新たに導入した作業ではありませんでした。
数百行にも満たない検証スクリプトと、kubectlやtarのような使い慣れたツールを組み合わせただけでしたが、実際に繰り返し発生していた問題(再デプロイの負担、翻訳漏れ、プラットフォームごとのスクリプトエラー)を正確に見極めて解決したという点で意義がありました。
特に、Windowsでのみ再現していたkubectl execの引数受け渡し問題のように、実際に実行してみなければ明らかにならない部分を一つずつ解決しながら、次はGitLab CIに検証ステップを接続し、マージ前に自動で検出できるよう改善していきたいと考えています。
ゼロ