mcpField
F
find-unwrapped-strings

find-unwrapped-strings

github.com/linguiテスト・品質10138 回インストール1 週間前に更新
リポジトリ

find-unwrapped-strings について

find-unwrapped-stringsは、Linguiプロジェクトでローカライズ用のラッパーがないユーザー向け文字列を監査するスキルです。ESLintでの検出、用途の確認、範囲を区切った修正、結果報告の手順を示します。

主な機能

  1. 01LinguiルールによるESLintスキャン
  2. 02文字列の役割に基づく検査
  3. 03マクロで文字列をラップする指針
  4. 04修正回数を限定した再スキャン
  5. 05対応状況と残件の報告

活用シーン

  1. 01Lingui移行後のローカライズ監査
  2. 02カタログ確認だけでは見つからない文字列の検出
  3. 03データや設定内の文字列の確認
  4. 04未解決の監査結果の追跡

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/lingui/skills --skill find-unwrapped-strings

インストール後、Claude Code で/find-unwrapped-stringsを入力して読み込まれたか確認します。

同梱ファイルreferences/eslint-config.md

詳細

ラップされていない文字列を見つける

実行するタイミング

  • Lingui のセットアップ後、または Lingui への移行後。どちらも変更した箇所はラップしますが、コードベースの残りの部分まで対応済みだとは証明できません。
  • 「ビルドは成功してカタログも充実しているのに、どのロケールでもこの画面に英語が表示される」という報告があったとき。
  • i18n がすでに確立されているプロジェクトで、定期的にカバレッジを監査するとき。

見落としはランダムではなく、系統的に発生します。JSX 内の文字列は UI に見えるためラップされますが、それ以外は残ります。たとえば、データモジュール内の表示テキスト(export const products = [{ name: "AeroPress Go" }])、トーストやエラーヘルパー内のメッセージマップ、設定オブジェクト内のラベルなどです。

スキャンの対象範囲は、意図的に広めに設定しています。 精度は、ヒットをスキップリストに照らして判断することで確保します。スキャナーが静かになるまで調整するのではありません。静かなスキャナーは、クリーンなコードベースと見分けがつきません。そして、価値があるのはそのうち一方だけです。

言語ではなく、役割で判断してください。スキャナーは文字列リテラルを構造的に検出し、スキップリストによってユーザー向けかどうかを判断します。文字列がどの自然言語で書かれているように見えるかは、その判断材料ではなく、推測するのはまったくの無駄です。

ステップ 1 — ガードレールを整える

eslint-plugin-lingui をインストールし、references/eslint-config.md に記載された調整済みオプションを使って、no-unlocalized-strings を warn で有効にします。

npm install --save-dev eslint-plugin-lingui

最新のリリースを使ってください。これは lint 専用の開発依存関係なので、バージョン範囲を固定しても保護できるものはありません。

このインストールは恒久的なものです。 監査は一度きりの総点検ですが、このルールが次の再発を防ぎます。30 個の文字列を見つけても、ガードレールを残さない監査では、数週間分の効果しかありません。

ルールは明示的に有効にしてください。プラグインの recommended および flat/recommended プリセットには含まれていないため、プリセットを拡張するだけではカバレッジを確保できません。詳しくはリファレンスを参照してください。

ユーザーが新しい開発依存関係の追加を断った場合は、1 回限りの方法があります。調整済みの設定をリポジトリの外に書き、そこを ESLint に指定すれば、プロジェクトには変更を加えずに済みます。

npx eslint -c /tmp/lingui-audit.config.js --no-config-lookup 'src/**/*.{ts,tsx}' --format json

この方法を提案するのは、ユーザーが追加を断った後だけにしてください。また、代償も伝えます。監査は実行できますが、次にラップされていない文字列が追加されるのを防ぐものはなく、次の監査はゼロから始まります。インストールを推奨してください。

いずれの場合も、スキャンが実行され、ルールがレポートを出せば、このステップは完了です。

ステップ 2 — スキャンする

npx eslint 'src/**/*.{ts,tsx}' --format json > /tmp/lingui-scan.json

ruleId === "lingui/no-unlocalized-strings" で絞り込み、結果を file:line と該当するソース行にまとめます。jq のレシピはリファレンスにあります。このリストが作業リストです。上から順に対応してください。

ルールのオプションは設定ファイルに記述したままにし、CLI ではルールのフラグを指定せずに実行してください。オプション付きの --rule は、設定済みのオプションとマージされるのではなく置き換えるため、クラス名やログ引数まで含まれ、ヒット数がおおよそ 2 倍になります。また、リストが長いほど、より徹底的に見えるため、識別子をラップして、そのノイズが「修正」されてしまいます。測定結果はリファレンスを参照してください。

疑わしい理由のあるコードベースでスキャン結果がクリーンだった場合は、結果を信じる前に glob を確認してください。files パターンが src/**/*.ts では .tsx が lint の対象にならず、それでも実行は終了コード 0 で終わります。

Step 3 — 各ヒットを判定する

すべてのヒットに、ラップするか、理由を記録してスキップするかの処置を割り当てます。処置が決まらないまま、このステップを終えるヒットがあってはなりません。

スキップリストは、lingui-best-practicesスキルの「Don't Wrap Non-UI Strings」セクションにあります。ラップしないものを判断する際の基準となるため、処置を決める前にそこで確認してください。確認できない場合は、以下の役割テストに基づいて判断します。

処置を決めるのは文字列の形ではなく、文字列の役割です。データモジュール内の"Best seller"は、買い物客が読むバッジなのでラップします。同じオブジェクト内の"aero-press-go"はURLのセグメントなのでスキップします。判断が難しい場合は、次の2つの質問で決めます。

  • 何かを壊すことなく、翻訳者がこの文字列を変更できますか?(ステータス値としての"DRAFT"なら、できません。)
  • これが日本語で表示された場合、正しいですか、それともバグですか?(日本語のskuはバグです。)

その行だけでは判断できない場合は、使用箇所を確認してください。文字列の役割は定義されている場所ではなく、使われている場所で決まります。

Step 4 — 範囲を絞って修正する

lingui-best-practicesのマクロ判定ツリーに従ってラップします。この監査で特によく見つかるケースは次のとおりです。

  • JSXのコンテンツ → Trans
  • コンポーネント内の文字列(属性、alert、関数引数) → useLingui() + t`…`
  • コンポーネントの外にある文字列 — データモジュール、モジュールレベルのマップ、設定オブジェクト → @lingui/core/macroのmsgディスクリプターを使用し、使用箇所で解決します。

この最後のケースこそ、この監査の目的であり、誤って修正されやすい箇所です。モジュールスコープでのtは、インポート時にその時点で有効だったロケールを使って一度だけ解決され、その後は変更されません。msgで定義し、表示される場所で解決してください。

// src/data/products.ts — definition
import { msg } from "@lingui/core/macro";

export const products = [
  { sku: "sku-1001", slug: "aero-press-go", name: msg`AeroPress Go` },
];
// consuming component — resolution
const { t } = useLingui();
<h2>{t(products[0].name)}</h2>;

フィールドの型がstringからMessageDescriptorに変わるため、TypeScriptがすべての使用箇所を示します。それに従って修正してください。レンダリングのタイミングがないReact以外のコードでは、i18n._(descriptor)が呼び出し時に解決されます。これはトーストヘルパーに適しています。

enhanced-message-contextに従ってラップする際に翻訳者向けコメントを追加してください。データモジュールのラベルと1語のバッジは、周囲のUIから意味を推測できない状態で渡されるため、必ずコメントを付けるケースです。

その後、ラウンドごとに1回、次を実行します。

npx lingui extract --clean
npx lingui compile

それからStep 2から再スキャンします。

上限:修正ラウンドは最大2回、または約40ファイルです。 どちらかに達したら停止して報告してください。人間なら監査を再実行できますが、200ファイルの差分をレビューすることはできません。

Step 5 — 終了する

次のいずれかに該当したら停止し、どれに該当したかを明記してください。

  1. スキャンで問題が見つからなかった。
  2. あるラウンドで新たにラップする文字列がゼロだった — 残りのヒットはすべて、誤検出であることが確認されています。該当する具体例をもとに、リファレンスの指針に従ってルールの設定済み除外項目を追加し、ほかのすべてを引き続き報告しながら、ガードレールが不要な警告を出さないようにします。重大度はwarnのまま、ルールは有効のままにし、ヒットはeslint-disableコメントではなく除外項目で処理します。
  3. 上限に達した(2ラウンド/約40ファイル)。

Step 6 — 報告する

すべてのスキャンで見つかった各ヒットを、file:lineを添えて次の3つの区分のいずれかに記載します。

  • ラップ済み — 変更内容と使用したマクロ。
  • スキップ — スキップリストにある理由(slug、enum value、log output)。
  • 未解決 — 未確認か確認済みかを問わず、未解決のまま残っているものを個別に列挙し、次回の開始地点として示します。

ガードレールの状態も報告してください。重大度と、追加した除外項目および各項目の根拠を記載します。

監査の完了を宣言する前に、すべてのヒットに対応してください。「16個の文字列をラップした」だけでは結果になりません。「16個をラップし、識別子とログ出力として16個をスキップし、未解決は0件で、ルールはwarnのままにした」ことが結果です。未報告の未解決項目をなくすことこそ、このスキルの目的です。そもそも見落としは、誰も記録しなかったために見えなくなっていたのです。

別のワークフローから呼び出されましたか?

オーケストレーター(セットアップスキルや TMS 接続スキル)から、対象範囲(src/features/checkout のみ、または移行で変更されたファイル)、プロジェクトのルールファイルに記載された追加のスキップリスト項目(ブランド名、製品コード)、既知の見落とし(「前回の確認ではデータ層を見落としていました」など)が渡されることがあります。

これら3つすべてに従ってください。パイプラインと報告の取り決めは変わりません。対象範囲によってスキャン範囲は絞られますが、報告内容は変わりません。指定された対象範囲外の検出事項は、省略せず対象範囲外として報告してください。

関連スキル

  • lingui-best-practices — スキップリストと、ステップ3および4で実行するマクロの判断ツリーを管理します。
  • enhanced-message-context — この監査で見つかる、短く文脈のないラベルに対する翻訳者向けコメントを提供します。

コメント