enhanced-message-context について
enhanced-message-context は、実装やレビュー時に Lingui のメッセージへ翻訳者向けコメントを加えるスキルです。意味が曖昧な UI 文言を見つけ、自明な文やベンダー提供コンポーネントには手を加えない方針を示します。
主な機能
- 01曖昧さと文脈で対象の優先度を判断
- 02Lingui マクロでコメントを追加
- 03コメントの表現と長さを案内
- 04優先度に沿って抽出カタログを監査
- 05ベンダー提供 UI コンポーネントを除外
- 06ESLint の require-comment ルールで検査
活用シーン
- 01短い UI ラベルの意味を明確化
- 02プレースホルダーの内容を翻訳者に説明
- 03抽出後の Lingui カタログを確認
- 04ESLint でソースメッセージを検査
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ lingui/ skills --skill enhanced-message-contextインストール後、Claude Code で/enhanced-message-contextを入力して読み込まれたか確認します。
詳細
強化されたメッセージコンテキスト
Lingui i18n を実装するときは、翻訳者が適切なトーン、長さ、表現を選べるよう、必要なメッセージに翻訳者向けコメントを追加してください。どのメッセージにコメントが必要かが重要なポイントです。以下の段階がその判断基準になります。短いラベルはそれ自体が持つ文脈がほとんどなく、全文には多くの文脈が含まれるため、それぞれに必要な対応は大きく異なります。
まずアプリのドメインを把握する
コメントを書く前に、製品が何を扱っているかを確認してください。package.json の説明、README、ルート名やコンポーネント名を調べます。ドメインを把握すると、それだけでは意味を特定できない語句を判別できます。たとえば、駐車アプリでの「Park」は自然公園ではなく駐車スペースです。ソーシャルアプリでの「Post」は名詞である可能性が高いです。ドメインによって意味が変わる語句には、コメントでドメインを示してください。
コメントを追加するタイミング
以下の段階に優先順位を付けてください。
必ずコメントする — コメントがないと翻訳が誤るおそれがあります。
- 曖昧な短い文字列:複数の意味や品詞を持つ1〜2語のフレーズ
- 「Back」(名詞か動詞か?)、「Delete」(ボタンか確認メッセージか?)、「Close」(動詞か形容詞か?)
- 対象が画面上に表示されないアクションラベル:コードから操作対象はわかりますが、翻訳者には見えません
- 「Remove」(何を削除するのか?)、「Apply」(何に適用するのか?)
- ドメインによって意味が変わる語句:製品に応じて意味が異なる語句
- 「Post」(動詞か名詞か?)、「Tag」(名詞か動詞か?)、「Park」(駐車スペースか緑地か?)
- 文法上の性に左右される表現:メッセージが何を指すかによって翻訳が変わります
- 「Selected」(選択された対象によって男性形、女性形、中性形が変わる)
- 意味が不明なプレースホルダー:名前から内容を判断できません
{count}(何の数か?)、「{name}」(ユーザー名、ファイル名、プロジェクト名のどれか?)
コメントを付けるとよい — 翻訳の質が大きく向上します。
- UI 用語:「Toast」、「Drawer」、「Chip」、「Modal」など、翻訳者が文字どおりに解釈する可能性のあるコンポーネント名
- 略語:「Qty」、「Avg」、「N/A」
- 文の断片:周囲の UI によって補完されるテキスト(「月あたり」、「24件中」など)
- 周囲の文脈から切り離されたラベル:テーブルの列見出し、ツールチップ、メニュー項目
コメントなしで出荷する — メッセージ自体に必要な文脈がすでに含まれています。
- 完全で、意味が明確な文章。 「サーバーに接続できませんでした。接続を確認して、もう一度お試しください。」のような文は、画面の場所を示すコメントがなくても翻訳者に必要な情報を伝えています。40文字以内のテーブルセルや、定められた文体が必要な法律用語など、トーンや長さに本当に制約がある場合に限ってコメントを付けてください。
- コメントがテキストやファイルパスを言い換えるだけの文字列。 3文の段落に「features ページにある機能カードの本文」というコメントを付けても、情報は増えず、負担が増えるだけです。
複数形の分岐に個別のコメントは不要です。メッセージ全体にコメントを1つ付ければ、すべての形式をカバーできます。
網羅性を目指し、コメントを増やしすぎない
ほぼすべてのメッセージにコメントが付いたカタログは、品質の証拠ではなく、段階ごとの基準を見落とした証拠です。必ずコメントする段階を100%カバーするのが目標であり、カタログ全体を100%カバーすることではありません。
この考え方が重要なのは、ツールではなく人に負担がかかるコストが2つあるためです。すべてにコメントを付けると、翻訳者はコメントを読み飛ばすようになり、重要なコメントまで読まれなくなります。また、コメントはどれも UI についての説明であり、UI が変わると内容が古くなります。
既存のカタログを監査するときは、全体の #. カバレッジではなく、必ずコメントする段階を個別に測定してください。確認すべきなのはコメントのない短いメッセージであり、全体の割合ではそれが見えにくくなります。次のコマンドは、コメントがないエントリーをすべて #: の参照とともに一覧表示するため、各段階への該当をひと目で分類できます。
awk -v RS='' '/msgid "[^"]/ && !/#\./' src/locales/en/messages.po
段落モード(RS='')はエントリー全体を照合するため、パターンの先頭や末尾にアンカーを付けないでください。^msgid は msgid で始まるエントリーだけに一致しますが、ほとんどのエントリーは #: の参照行で始まります。
t tagged templates cannot carry a comment of their own
ラップ処理でコンテキストが付かない最も一般的な原因はこれです。タグ付きテンプレート形式には、コメントを付ける場所がありません。
// ❌ No comment possible — there is no argument to attach one to
<img alt={t`Company logo`} />
// ✅ Object form takes `comment`
<img alt={t({ comment: "Alt text for the logo in the site header", message: "Company logo" })} />
msg`…` と msg({ … }) の場合も同様です。また、plural(count, { … }) をそのまま使う場合も同じです。
コメントは後からではなく、ラップするときに決めてください。 コメント必須の階層に該当するものには、最初からオブジェクト形式を使ってください。タグ付きテンプレート形式は自然に入力できるため、後回しにすると、コードベース内のすべての属性、プレースホルダー、トーストに対して機械的な編集が必要になります。
Trans にはこの制限はありません。comment は単なるプロパティなので、JSX コンテンツにはいつでもその場でコメントを付けられます。
<Trans comment="Button in the toolbar that returns to the previous page">Back</Trans>
ディレクティブを使った回避策
lingui-set ディレクティブは、タグ付きテンプレートを含め、ファイル内で後に続くすべてのマクロの comment を設定します。t`…` を書き換えずにコメントを付ける唯一の方法です。
// lingui-set comment="Alt text for the logos in the site header"
<img alt={t`Company logo`} />
<img alt={t`Partner logo`} />
// lingui-reset
メッセージのまとまりに本当に共通する説明がある場合に限って使ってください。たとえば、ツールバー、テーブル、空の状態などです。関連のない文字列が並ぶファイル全体にわたるディレクティブは、すべての文字列に同じ文言を付けます。これは階層によって防ごうとしているコメントの飽和状態を招くものであり、オブジェクト形式を使わずに済ませるための近道ではありません。
lingui-reset で閉じてください。ディレクティブを閉じないままにすると、後からファイルに追加したメッセージにも適用され続けます。その結果、コメントは本来対象としていない文字列を説明することになり、context も引き継ぐ文字列があれば、その抽出 ID も変わります。
ベンダー提供のコンポーネントライブラリには手を加えないでください
components/ui/** 内のコピー(shadcn/ui、または生成ツールによって取り込まれた Radix ラッパーなど)は、コメントを付けたりラップしたりする対象ではありません。shadcn add はこれらのファイルを丸ごと上書きするため、そこに追加したマクロやコメントは次回の更新時に、ビルドが成功したまま気付かれずに消えてしまいます。
これはこのスキルの監査パスにも当てはまります。カタログ項目の #: 参照がベンダー提供ディレクトリを指している場合は、既知の残存項目として記録し、次の項目に進んでください。そのファイルには手を加えません。
そのコピーを本当に翻訳する必要がある場合は、文字列を保持し、プリミティブに表示を委譲する、プロジェクト所有のラッパーコンポーネントを作成してください。これはプロジェクトで意図的に決める設計方針であり、コンテキストの追加処理で導入するものではありません。
効果的なコメントの書き方
優れた翻訳者向けコメントには、次の要素が含まれます。
-
場所:メッセージが UI のどこに表示されるか
- 「上部ナビゲーションバーのボタン」
- 「保存アイコンのツールチップ」
- 「ユーザーテーブルの列見出し」
-
操作/目的:何が起こるか、または何を意味するか
- 「前のページに戻ります」
- 「選択した項目を完全に削除します」
- 「未読通知の数を表示します」
-
曖昧さの解消:品詞や意味の一部を明確にする
- 「名詞ではなく、動詞として使います」
- 「郵便住所ではなく、メールアドレスを指します」
- 「単数形です。数に応じて、ユーザーには『item』または『items』と表示されます」
品質に関するルール
- 単語の意味ではなく、表示される場所と対象を説明してください。
- 悪い例:「Save — 保存するという意味」
- 良い例:「ドキュメントエディターのツールバーにある保存ボタン」
- 約 80 文字以内に収めてください。 コメントはヒントであり、ドキュメントではありません。
- 用語がドメインによって意味の異なる場合は、アプリのドメインを明記してください。:「Park — 駐車スペースであり、自然公園ではありません」
- コメントはプロジェクトのソース言語で書いてください。
- すべてのコメントで用語を統一してください(同じ UI 領域には同じ言葉を使います)。
comment と context の違い
これらは異なる問題を解決するものです。混同しないでください。
commentは翻訳者への助言です。メッセージの識別情報は変わりません。contextはメッセージ ID を変更します。同じテキストでもcontextの値が異なると、カタログに別々のエントリーが作成され、それぞれ独立して翻訳されます。同じ原文でも本当に異なる翻訳が必要な場合(方向を表す「right」と正しさを表す「right」など)に限って使用してください。contextを名前空間として使用しないでください(auth.login、settings.titleなど)。意味が同じ同一の文字列は、1つのカタログエントリーを共有する必要があります。名前空間を付けると、重複した翻訳作業が発生します。
API リファレンス
Lingui には、翻訳者向けコメントを追加する方法が3つあります。
1. JS マクロ(t)
JSX の外部にある JavaScript コードの場合:
import { t } from "@lingui/core/macro";
// With comment
const backLabel = t({
comment: "Button in the navigation bar that returns to the previous page",
message: "Back",
});
// With comment and variable
const uploadSuccess = t({
comment: "Success message showing the name of the file that was uploaded",
message: `File ${fileName} uploaded successfully`,
});
2. React マクロ(Trans)
JSX 要素の場合:
import { Trans } from "@lingui/react/macro";
// With comment
<Trans comment="Button that deletes the selected email message">Delete</Trans>
// With comment in a component
<button>
<Trans comment="Label for button that saves changes to user profile">
Save
</Trans>
</button>
3. 遅延メッセージ/遅延評価メッセージ(defineMessage / msg)
使用箇所とは別に定義されたメッセージの場合:
import { defineMessage } from "@lingui/core/macro";
const messages = {
deleteButton: defineMessage({
comment: "Button that permanently removes the item from the database",
message: "Delete",
}),
statusLabel: defineMessage({
comment: "Shows whether the service is currently operational. Values: 'Active', 'Inactive', 'Pending'",
message: "Status: {status}",
}),
};
例
例 1:意味が曖昧な短い単語
変更前(context なし):
<button onClick={goBack}>
<Trans>Back</Trans>
</button>
変更後(context あり):
<button onClick={goBack}>
<Trans comment="Button in the toolbar that navigates to the previous page">
Back
</Trans>
</button>
例 2:context のない UI ラベル
変更前(context なし):
const columns = [
{ key: "name", label: t`Name` },
{ key: "status", label: t`Status` },
];
変更後(context あり):
const columns = [
{
key: "name",
label: t({
comment: "Column header in the projects table showing project name",
message: "Name"
})
},
{
key: "status",
label: t({
comment: "Column header showing project status: Active, Inactive, or Archived",
message: "Status"
})
},
{
key: "created",
label: t({
comment: "Column header showing the date when the project was created",
message: "Created"
})
},
];
例 3:ドメイン固有の用語
変更前(曖昧):
<button onClick={handlePost}>
<Trans>Post</Trans>
</button>
変更後(動詞として明確化):
<button onClick={handlePost}>
<Trans comment="Button that publishes the content. Used as a verb (to post), not a noun (a post)">
Post
</Trans>
</button>
例 4:意味が明確でない変数
変更前(何の数を表すのか不明):
const message = t`${count} items selected`;
変更後(明確化):
const message = t({
comment: "Shows the number of email messages currently selected in the inbox",
message: `${count} items selected`,
});
例 5:説明不要のメッセージ(優先度は低めですが、付ける価値はあります)
// Message is clear on its own; adding a comment with location still helps translators
<Trans comment="Validation hint shown below the password field on the sign-up form">
Your password must contain at least 8 characters, including one uppercase letter and one number.
</Trans>
ワークフロー
Lingui のメッセージを実装またはレビューする際は、次の手順に従います。
- ドメインを把握する:アプリの内容を確認し(上記を参照)、コメントでドメイン用語の曖昧さを解消できるようにします
- メッセージを読む:文字列自体を確認します
- コンテキストを確認する:コード内での使用箇所と使われ方を考慮します
- 自問する:「UI を見ずに、このメッセージを翻訳者が誤解する可能性はあるか?」
- ある場合:ラップするタイミングでオブジェクト形式(
t({ comment, message })、Trans comment=…)を使用し、場所、目的、曖昧さを解消する情報を記述します - ない場合:コメントを付けずに次へ進みます。説明不要の文に場所の注記は必要ありません。追加しても得られるものより手間のほうが大きくなります
抽出後のレビュー
i18n 作業をひとまとまり終えたら、作業中にコメントを追加したはずだと考えるのではなく、カタログを監査します。
lingui extractを実行します- ソースロケールの
.poファイルを確認し、#.行がないエントリーを探します(commentが記載される行です) - 優先度別に振り分けます。コメントのないエントリーをすべて不備とみなしてはいけません。 短い文字列や曖昧な文字列、意味が明確でないプレースホルダー、ドメイン用語については、ソースに戻ってコメントを付けます。説明不要の文は問題がない状態であり、抜けではありません
#:の参照先がベンダー提供ディレクトリ(components/ui/**)内のエントリーは除外し、既知の残存項目として記録しますlingui extractを再実行し、#.行が追加されたことを確認します
結果は、必ずコメントを付ける対象のカバー率と、意図的に対応しなかった項目を併記して報告します。「短いラベルにはすべてコメントを付け、説明不要の文は120件そのままにし、ベンダー提供の残存項目は25件」などの形式です。カタログ全体に対する単一の割合では、これら3種類を区別できません。
ESLint で強制する
eslint-plugin-lingui のバージョン 0.16 以降には require-comment ルールが含まれています。このルールは、説明のないメッセージを抽出後ではなく、ソース内でファイル名と行番号とともに報告します。空のコメントも報告されます。
このルールはデフォルトでは無効です。また、初期設定ではすべてのメッセージにコメントを求めますが、これは階層化で避けるべきだとしている、まさにコメントの飽和状態です。目標は、これをグローバルに有効にすることではありません。このルールを活用できる方法は 2 つあります。
- 一時的な監査として使います。 ディレクトリに対して
warnで有効にし、コメントのない項目を一覧化して階層ごとに分類したら、無効に戻します。前述のawkによるスキャンを置き換え、lingui extractより前に実行できます。 - 必ずコメントを付ける階層の割合が高いディレクトリに限定します。 ツールバー、テーブルと列の定義、アイコンボタンのラベル、空の状態などが該当します。ここでは「すべてのメッセージ」と「コメントが必要なすべてのメッセージ」がほぼ同じ集合になるため、ESLint の
overridesエントリを恒久的に追加しても負担はわずかです。
このスキルのアドバイスに関係するオプションは 2 つあります。allowContext: true を指定すると、comment の代わりに context を使用できます。また、ignorePatterns は [CHAR_LIMIT=40] などのメタデータタグを除外するため、メタデータだけのコメントも説明がないものとして報告されます。
lingui-set comment="…" ディレクティブは、対象となるメッセージについてルールを満たします。ディレクティブをそのように使う場合は、あわせて require-directive-reset を有効にしてください。閉じられていない lingui-set が報告されます。
#. Button in the toolbar that navigates to the previous page
#: src/components/Toolbar.tsx:24
msgid "Back"
msgstr ""
メモ
- コメントは翻訳者向けにメッセージカタログへ抽出されます。
- コメントは本番ビルドから取り除かれるため、実行時のコストはゼロです。ただし、コストがゼロという意味ではありません。コストとなるのは翻訳者の注意とメンテナーの保守作業であり、それらをどの程度割り当てるかが階層化の目的です。
- コメントは翻訳管理システム(TMS)に表示されます。
- プロジェクト内のすべてのコメントで、一貫した用語を使用してください。