エンジニアリング//8 読了時間

i18nヘルスチェック:欠落した翻訳を本番リリース前に検出する

Eray Gündoğmuş
共有

欠落した翻訳は、パイプラインのあらゆる段階をすり抜けてしまうタイプのバグです。ユニットテストは翻訳ファイルをチェックしないためパスします。インテグレーションテストはデフォルト言語で実行されるためパスします。QAは手動テストがすべての12言語をカバーすることがほとんどないためパスします。そしてブラジルのユーザーが、ボタンラベルが表示されるべき場所に checkout.confirm_button が表示されているのを見つけ、チームが不注意に見えるバグレポートが届きます。

問題は、チームが翻訳を忘れることではありません。型チェッカーが型エラーを検出したり、リンターがコードスタイルの問題を検出したりするように、翻訳の欠落を自動的に検出する仕組みがないことが問題です。コードにはESLint、Prettier、TypeScript、そして完全なCIパイプラインがあります。翻訳には……誰かがうまく更新してくれていることを願うJSONファイルがあります。

この記事では、欠落した翻訳、プレースホルダーの不一致、孤立したキー、ハードコードされた文字列を本番環境に届く前に検出する自動i18nヘルスチェックの実装方法を解説します。


i18nヘルスチェックは実際に何をチェックするのか?

包括的なi18nヘルスチェックは、翻訳設定の4つの側面を評価します。

1. カバレッジ:すべてのキーが翻訳されているか?

カバレッジは最も直接的で、最も影響力のあるチェックです。ソースコードで使用されているすべての翻訳キーについて、すべてのターゲット言語に翻訳が存在するでしょうか?

ソースコードの参照数: 1,247キー
英語(ソース):      1,247/1,247 (100%)
スペイン語:          1,235/1,247 (99%)
フランス語:          1,198/1,247 (96%)
日本語:              1,150/1,247 (92%)
韓国語:              1,089/1,247 (87%)

カバレッジチェックは最も一般的なシナリオを検出します:開発者が新機能を追加し、英語の文字列を書いて、次のタスクに移ります。キーは英語のJSONファイルに入りますが、翻訳のために送られることはありません。カバレッジチェックがなければ、ユーザーが問題に遭遇するまで欠落は見えないままです。

カバレッジチェックは名前空間の不一致も検出します。コードが t('checkout.confirm') を参照しているが、韓国語に checkout 名前空間が存在しない場合、韓国語ユーザーに生のキーを表示するカバレッジのギャップになります。

2. 品質:翻訳は構造的に正しいか?

カバレッジは翻訳が存在するかどうかを教えてくれます。品質は、実行時に正しく動作するかどうかを教えてくれます。

最も重要な品質チェックはプレースホルダーの検証です。英語の文字列が次のようなものであれば:

"You have {count} items in your cart, {name}."

その文字列のすべての翻訳は、{count}{name} を正確に含んでいなければなりません。{count} の代わりに {nombre} と書いたフランス語の翻訳者は実行時のバグを生み出します——補間エンジンは {nombre} の値を見つけられず、生のプレースホルダーを表示するかエラーをスローします。

その他の品質チェックには以下が含まれます:

  • 空の値:言語ファイルに存在するが空の文字列を持つキー。これらは通常、実際の翻訳なしにプログラム的にキーが作成されていることを示しています。
  • ソースと同一の文字列:ソース言語と一文字一文字同じ翻訳。一部の文字列(ブランド名、URL)は正当に同一ですが、件数が多い場合は通常、翻訳されていないコンテンツを意味します。
  • 過度な長さ:ソースよりも著しく長い翻訳で、UIコンテナーをあふれさせる可能性があります。ドイツ語の翻訳は英語より30〜40%長いことで知られています。

3. 構造:翻訳ファイルはクリーンか?

構造チェックは翻訳ファイルの整理とクリーン度を評価します:

  • 孤立したキー:翻訳ファイルに存在するがソースコードで参照されていないキー。機能が削除されても翻訳ファイルがクリーンアップされないと蓄積されます。翻訳者の労力を無駄にし、混乱を生じさせます。
  • 重複キー:単一ファイル内で2回定義された同じキー。JSONは重複キーでエラーにならず——最後のものを黙って使用し、混乱した動作につながる可能性があります。
  • 命名の不一致:キーの90%が snake_case を使用しているのに一部が camelCase を使用している場合、不一致によりキーの検索とメンテナンスが困難になります。

4. コード:文字列は適切に国際化されているか?

コード分析はAST解析を使用して、翻訳関数でラップされるべきソースファイル内のハードコードされた文字列を見つけます。

// フラグ付き:ハードコードされたユーザー向け文字列
<h1>Welcome to our app</h1>

// フラグなし:適切に国際化済み
<h1>{t('home.welcome_title')}</h1>

// フラグなし:ユーザー向けでない(CSSクラス、データ属性)
<div className="container" data-testid="home">

このチェックはi18nの技術的負債をソースで検出します。i18n設定に慣れていない新しい開発者はハードコードされた文字列を書きます。自動チェックなしでは、翻訳の監査中に誰かが気づくまでこれらの文字列が残り続けます。


ヘルススコア:複雑さを1つの数値に集約する

個々のチェックは詳細なレポートを生成しますが、CIインテグレーションとトレンド追跡には1つの数値が必要です:ヘルススコアです。

適切に設計されたヘルススコアは、ユーザーへの影響によってカテゴリーに重みを付けます:

カテゴリー重み根拠
カバレッジ40%欠落した翻訳はユーザーに直接影響する
品質30%プレースホルダーのバグは実行時エラーを引き起こす
構造20%孤立したキーは労力を無駄にするがUXを壊さない
コード10%ハードコードされた文字列は技術的負債であり、即時の破壊ではない

87/100のスコアを得たプロジェクトは次のように分解されるかもしれません:

総合: 87/100 合格

カバレッジ   92/100  ████████████████████  3つの欠落キー
品質         85/100  █████████████████░░░  2つのプレースホルダー不一致
構造         78/100  ███████████████░░░░░  12の孤立したキー
コード        90/100  ██████████████████░░  4つのハードコードされた文字列

合格/不合格のしきい値は設定可能です。80のしきい値はほとんどのチームにとって実用的です——実際の問題を検出するのに十分厳格で、軽微な警告がデプロイをブロックしないほど寛容です。


CI/CDでのi18nヘルスチェックのセットアップ

ヘルスチェックの真の価値は、すべてのプルリクエストで自動的に実行されることから来ます。GitHub Actionsワークフローのセットアップ方法は次のとおりです:

# .github/workflows/i18n-doctor.yml
name: i18n Health Check

on:
  pull_request:
    paths:
      - "locales/**"
      - "src/**"
      - "messages/**"

jobs:
  doctor:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup Bun
        uses: oven-sh/setup-bun@v2

      - name: Install dependencies
        run: bun install

      - name: Run i18n Doctor
        run: bunx @better-i18n/cli doctor --ci --threshold 80
        env:
          BETTER_I18N_API_KEY: ${{ secrets.BETTER_I18N_API_KEY }}

--ci フラグの役割

--ci フラグはCI環境向けにDoctorの動作を変更します:

  1. 終了コード:スキャンが失敗した場合に終了コード1を返し、GitHub Actionsジョブを失敗させます
  2. GitHubアノテーション:問題をGitHub Actionsアノテーション形式で出力し、PRのdiffにインラインコメントとして表示されます
  3. サマリー:GitHub Actionsのチェック出力のための構造化されたサマリーを生成します
  4. 非インタラクティブ:プログレスバーとカラー出力を抑制します

パスフィルターの重要性

ワークフロー設定の paths フィルターはパフォーマンスのために重要です。フィルターなしでは、ドキュメントのみを変更するPRや翻訳への影響がないバックエンドコードのPRを含む、すべてのPRでヘルスチェックが実行されます。翻訳ファイルのディレクトリとソースコードのディレクトリにフィルタリングしてください。

プラットフォームへの結果レポート

翻訳管理プラットフォームに結果を送信するには --report フラグを追加します:

- name: Run i18n Doctor
  run: bunx @better-i18n/cli doctor --ci --report --threshold 80
  env:
    BETTER_I18N_API_KEY: ${{ secrets.BETTER_I18N_API_KEY }}

レポートにはコミットのSHA、ブランチ名、ファイル数、キー数が含まれます。時間の経過とともに、改善の追跡、回帰の検出、チーム目標の設定に使用できるi18nヘルスの履歴が構築されます。

自動生成されるワークフロー

GitHub Actionsを手動で設定することが不必要な手間に思える場合、一部の翻訳プラットフォーム(Better i18nを含む)はワークフローファイルを自動的に作成できます。プラットフォームはGitHub APIを使用して、事前設定されたワークフローファイルを含むPRをリポジトリに作成します。レビューしてマージすれば、ヘルスチェックが有効になります。


チェックが失敗した場合の対応

失敗したヘルスチェックは、単なる赤いXではなく、実行可能な情報を提供すべきです。有用な失敗の例:

欠落した翻訳キー

エラー: 12のキーがターゲット言語に欠落

  checkout.confirm_order
    欠落: fr, de, ja, ko
    コミットで追加: abc1234 (2日前)
    ファイル: src/pages/Checkout.tsx:45

  checkout.payment_method
    欠落: fr, de, ja, ko
    コミットで追加: abc1234 (2日前)
    ファイル: src/pages/Checkout.tsx:52

開発者はどのキーが欠落しているか、どの言語で、いつ追加されたか、どこで使われているかを正確に確認できます。修正は明確です:マージ前にこれらのキーの翻訳をリクエストします。

プレースホルダーの不一致

エラー: notifications.new_messages (de) でプレースホルダーの不一致
  ソース:  "You have {count} new messages from {sender}"
  ターゲット:  "Sie haben {anzahl} neue Nachrichten von {sender}"
  欠落: {count}
  余分: {anzahl}

開発者または翻訳者は正確な不一致を確認し、ドイツ語の翻訳を {anzahl} の代わりに {count} を使用するように修正できます。

ハードコードされた文字列

警告: JSXにハードコードされた文字列 (src/components/Header.tsx:23)
  <h1>Welcome back!</h1>
  提案: <h1>{t('header.welcome_back')}</h1>

これは警告であり、エラーではありません——デフォルトではPRをブロックしません。しかしレポートに表示され、コード分析スコアに貢献します。


実世界での影響:導入前と導入後

導入前:手動プロセス

  1. 開発者が30の新しいキーを持つ新機能を追加
  2. 開発者が英語の翻訳を追加
  3. 開発者がPRを作成し、レビューされてマージされる
  4. 2週間後、QAがフランス語でその機能をテスト——30の生のキーを発見
  5. QAがバグレポートを提出
  6. 開発者が翻訳のためのチケットを作成
  7. 翻訳者がフランス語の翻訳を提供
  8. 開発者が翻訳ファイルをコミットし、新しいPRを作成
  9. ドイツ語、日本語、韓国語などで繰り返す

コードのマージから完全に翻訳された機能まで:3〜6週間。

導入後:自動ヘルスチェック

  1. 開発者が30の新しいキーを持つ新機能を追加
  2. 開発者が英語の翻訳を追加
  3. 開発者がPRを作成
  4. CIがi18n Doctorを実行——「fr, de, ja, koで30のキーが欠落」で失敗
  5. 開発者がプラットフォーム経由で翻訳をリクエスト
  6. 翻訳が届く(AIによって数分で生成され、人間によって数時間でレビューされる)
  7. 開発者が翻訳をPRに追加
  8. CIが再実行——合格
  9. PRがすべての言語を完備した状態でマージ

コードのマージから完全に翻訳された機能まで:同日。

違いは速さだけではありません——問題を適切な場所で検出することに関わっています。CIチェックは、キーが追加された同じPRで欠落した翻訳を検出します。開発者がまだ機能について完全なコンテキストを持っている段階で。3週間後のバグレポートは、すでに次の作業に移った機能へのコンテキストの切り替えが必要です。


時間をかけてヘルスを追跡する

単一のヘルススコアは合格/不合格のゲートに役立ちます。ヘルススコアの履歴はトレンドを理解するのに役立ちます。

Doctorのレポートをプラットフォームのダッシュボードに送信すると、次のことを追跡できます:

  • スコアの軌跡:i18nのヘルスは改善、安定、または低下していますか?
  • カテゴリーのトレンド:カバレッジは優秀でも孤立したキーが蓄積しているかもしれません。カテゴリーの内訳は、クリーンアップ作業をどこに集中すべきかを示します。
  • ブランチごとの比較:フィーチャーブランチは通常スコアが低くなります(翻訳のない新しいキー)。メインブランチは一貫して高いスコアを維持すべきです。
  • プロジェクト間の比較:複数の製品を持つ組織の場合、どれが注意を必要としているかを特定するために、プロジェクト間でi18nのヘルスを比較します。

チーム目標の設定

ヘルススコアにより、測定可能なi18n目標を設定することができます:

  • 「メインブランチで90以上のヘルススコアを維持」——品質基準
  • 「四半期末までに孤立したキーを200から50に削減」——クリーンアップの取り組み
  • 「プレースホルダーの不一致ゼロ」——最も重要なチェックのゼロ欠陥目標

よくある反論と回答

「言語が2つしかないので、これは必要ありません。」 2つの言語でも、カバレッジのギャップとプレースホルダーの不一致がユーザーに見えるバグを引き起こすのに十分です。ヘルスチェックは軽量です——CIパイプラインに数分ではなく、数秒を追加するだけです。

「翻訳者が品質を担当しています。」 翻訳者は言語的品質を確保します。ヘルスチェックは技術的品質を確保します——プレースホルダーの正確さ、キーのカバレッジ、ファイル構造。これらは異なる問題です。翻訳者はキーがソースコードで参照されているかどうかを知ることができません。

「もっと多くの言語ができてから追加します。」 i18nの技術的負債は複利で増加します。2つの言語で蓄積した孤立したキー、ハードコードされた文字列、不一致な命名は、3番目、4番目、5番目の言語を追加するとはるかに修正が困難になります。早期にヘルスチェックを開始することは、後から遡及して適用するよりも安上がりです。

「CIがすでに遅い。」 8言語の10,000キーのプロジェクトのDoctorスキャンは10秒未満です。--skip-code を使用してAST分析を省略し、3秒未満に短縮します。GitHub Actions設定のパスフィルターにより、翻訳関連ファイルに触れるPRでのみチェックが実行されます。


はじめに

Better i18nを使用している場合、DoctorコマンドはCLIに組み込まれています:

# CLIをインストール
bun add -g @better-i18n/cli

# 最初のヘルスチェックを実行
bi18n doctor

# レポート付きのCIモードで実行
bi18n doctor --ci --report --threshold 80

Better i18nを使用していない場合、この記事の原則はあらゆる翻訳設定に適用できます。カスタムスクリプトで同様のチェックを構築できます:

  1. 翻訳キーの参照のためにソースコードを解析する
  2. 参照されたキーを翻訳ファイルと比較する
  3. プレースホルダーの一貫性を検証する
  4. 結果をCIシステムのアノテーション形式で出力する

重要なのはどのツールを使うかではありません。重要なのは、欠落した翻訳がユーザーによって発見されるサプライズではなく、開発者によって発見されるCIチェックになることです。


欠落した翻訳は防げるバグです。CIで検出を始めましょう——Better i18n Doctorをセットアップして、今日初めてのヘルスチェックを実行してください。

Comments

Loading comments...