それは、i18nの設定があなたの頭の中や設定ファイルにあるだけで、エージェントが見ているコード自体には存在しないからです。Cursorは目の前にあるコンテキストからコンポーネントを再生成し、プレーンなJSXテキストが最も手早く動く方法です。リポジトリ内には異議を唱える仕組みが何もないため、そのままリテラルが本番へ出てしまいます。globalize.nowは、AIを活用したローカライゼーション基盤として、このループを閉じます。新しい文字列を抽出し、キーを生成し、Gitへのプッシュのたびに翻訳を同期するのです。
これは誰も警告してくれない失敗パターンです。i18nの初期設定自体は半日で終わる作業です。本当の問題は、エージェントが1日に2回もコンポーネントを書き換える中で、その設定を維持し続けることにあります。
i18nを設定したのに、なぜCursorはハードコードされた文字列を追加し続けるのか?
3つの事実が同時に成り立っており、それらが合わさることで漏れが確実に発生します。
エージェントが見ているのは規約ではなく、一つのファイルです。 設定パネルの追加をCursorに依頼すると、編集対象のコンポーネントと、せいぜい近隣の数ファイルしか読み込みません。ロケールディレクトリ、名前空間の設計、そしてユーザー向け文字列はすべて翻訳関数を経由するという規則は、あなた自身がその視野の中に含めない限り、そこには存在しません。
未翻訳のテキストが統計的なデフォルトです。 どのモデルの学習データを見ても、React コードの圧倒的多数は文字列をインラインで書いています。エージェントがボタンラベルとして次に来る最も確率の高いトークンを選ぶとき、単なる出現頻度の点でSave changesがt('settings.save')に勝ります。あなたは毎回、より珍しいパターンを選ぶよう求めていることになりますが、それに応じないときのフィードバックは一切ありません。
何も壊れません。 これが決定的な要因です。ハードコードされた文字列は型チェックを通過します。ビルドも通ります。レンダリングもされます。テストも通過します。ドイツのユーザーがまもなく英語のトーストを目にすることになる、と教えてくれる赤い印は、パイプラインのどこにもありません。この不具合は、誰かがロケールを切り替えて画面をスクロールするまで、見えないままなのです。
これを、インポート漏れや不正なprop型と比べてみてください。それらはエージェントが数秒以内に修正します。未翻訳のテキストは、あなたのツールチェーンが黙って受け入れてしまう唯一のバグの種類であり、だからこそ蓄積していくのです。
どの文字列が最も漏れやすいのか?
最後に追加され、最もチェックされていないものです。
多言語対応からスタートしたAI支援のコードベースをどれでも見てみると、生き残っている英語は決まった場所に存在します。
- エラーおよび成功時のトースト通知。特に、無関係なバグ修正のついでに追加されたもの
- 空の状態表示や読み込み中のテキスト。一度書かれたきり、二度とレビューされない
- フォームのバリデーションメッセージ。多くの場合、スキーマ内にインラインで生成される
- アクセシビリティ属性:
aria-label、alt、title - 確認ダイアログや、破壊的な操作に関する文言
catchブロック内のすべて
このパターンはいつも同じです。主要なフローは目にする機会が多いので翻訳される一方、端の部分は誰も別ロケールで開かないため、ハードコードされたまま残ってしまいます。
これには二次的なバージョンもあります。エージェントが既存の翻訳済みコードの隣にリテラル文字列を追加する際、既存の名前空間ルールに合わないキーを「親切にも」作り出してしまうことがあり、結果としてあるファイルにはerrors.saveFailed、別のファイルにはsettings_save_errorという状態になります。こうなると未翻訳文字列に加えてキー体系そのものが分断されてしまいます。この段階でまだ初期のクリーンアップに取り組んでいるなら、AI生成アプリのローカライズに関するガイドが参考になります。
.cursor/rulesやAGENTS.mdでこれを防げるか?
ある程度役には立ちますし、実際に用意すべきものですが、あくまでガイドラインであって強制力はありません。
指示ファイル層は最近標準化が進みました。AGENTS.mdは2025年にベンダー中立の規約として始まり、現在はLinux Foundation傘下のAgentic AI Foundationが管理しています。Cursor、Codex、Copilot、Gemini CLI、Aider、Windsurf、Zedがネイティブに読み込むため、リポジトリのルートに1つファイルを置くだけでチームが使うすべてのエージェントに行き渡ります。Cursorはさらに、.cursor/rules/配下の.mdcファイルとしてスコープ付きプロジェクトルールもサポートしており、フロントマターで説明文やファイルのglobパターン、常時適用するかどうかを制御できます。2026年時点での実用的な構成は、可搬性のためにルートにAGENTS.mdを置き、自動アタッチが効果を発揮する場面ではスコープ付きCursorルールを併用する形です。
実用的なi18nブロックの例はこちらです:
## Internationalization
- Never write user-facing text as a literal in JSX or in a string passed to a UI component.
- All user-facing text goes through the translation function from `next-intl`.
- Keys follow `feature.element.state` in lowerCamelCase, e.g. `settings.save.error`.
- Add the English value to `messages/en.json` only. Never hand-edit other locale files.
- This includes toasts, empty states, validation messages, `aria-label`, `alt` and `title`.
これで漏れの発生率は目に見えて減らせます。ただしゼロにはなりません。ルールファイルはコンテキストウィンドウ内の他の情報と注意を奪い合いますし、長時間のエージェントセッションでは徐々にずれが生じるからです。この層は頻度を下げるものであって、保証ではないと捉えてください。
未翻訳の文字列をビルド失敗にする方法は?
JSX内のリテラル文字列をエラー扱いにするlintルールを追加し、それを継続的インテグレーション(CI)で実行します。
標準的な選択肢は、eslint-plugin-i18nextが提供するno-literal-stringルールです。デフォルトではファイル内のすべてのリテラルではなく、JSXマークアップ内のプレーンテキストのみを検証するため、大量の誤検知に埋もれることなく実際のコードベースで運用できます。正規表現による無視パターン、安全と分かっている関数を除外するコールイー除外、マークアップのみを対象とするモードにも対応しています。
初期設定の例:
// eslint.config.js
import i18next from 'eslint-plugin-i18next';
export default [
{
files: ['**/*.{jsx,tsx}'],
plugins: { i18next },
rules: {
'i18next/no-literal-string': [
'error',
{
markupOnly: true,
ignoreAttribute: ['data-testid', 'className'],
ignore: ['^[0-9]+$', '^\\s*$'],
},
],
},
},
];
エディタ内だけでなく、パイプラインでも実行しましょう:
npx eslint . --max-warnings=0
これで目に見えなかった不具合に赤いマークが付きます。エージェントは未翻訳の文字列に対しても、型エラーと同じフィードバックループを得られるため、別のロケールを使うユーザーに発見される前に、同じセッション内で自ら修正できるようになります。Cursorプロジェクトにゼロからi18nを組み込む全体像を知りたい場合は、15分でわかるCursorとNext.jsのウォークスルーとCursor連携ページをご覧ください。
lintルールでエラーになるだけでは不十分な理由は?
ビルドが赤くなるのは、文字列が存在することを教えてくれるだけだからです。それを翻訳してくれるわけではありません。
ルールが発火した後も、既存の名前空間に合ったキーを選び、英語の元の値を追加し、他のロケール分を用意する作業は誰かがやらなければなりません。1日に何度もリリースする個人開発プロジェクトでは、この作業は後回しにされがちです。先延ばしが長引くと、結局「無視リストにファイルを追加する」という手っ取り早い対処に落ち着き、余計な設定が増えただけで振り出しに戻ってしまいます。
ここが、解決したはずの問題を延々と続く負債に変えてしまう抜け穴です。1層目と2層目は問題の発生頻度や発覚の速さを変えるだけで、実際の作業そのものは肩代わりしてくれません。
3層構成のセットアップとは、どのようなものか?
指示、強制、自動化。それぞれの層が、前の層で見逃したものを捕まえます。
第1層、指示。 リポジトリのルートにあるAGENTS.mdにi18nブロックを書き込み、コンポーネントディレクトリ用のglobパターンを指定したスコープ付き.cursor/rules/i18n.mdcを追加します。コスト: 最初の一度きり15分。効果: そもそもリテラル文字列が書かれる回数が減ります。
第2層、強制。 no-literal-stringを有効にし、これが引っかかったら継続的インテグレーションを失敗させます。コスト: 既存コードベースで無視パターンを調整する作業に半日程度。効果: リテラルが誰にも気づかれずにメインブランチに入ることがなくなり、エージェントも自ら修正できるようになります。
第3層、自動化。 抽出、キー付け、翻訳をあなたの手を介さずに行わせます。globalize.nowが担うのがこの層です。既存のi18nランタイムを置き換えるのではなく、その上位に位置します。next-intlやi18nextは引き続き文字列をレンダリングします。globalize.nowは新たにハードコードされたテキストを検知し、プロジェクト内の既存の名前空間に沿ったキーを生成し、設定済みのすべてのロケールに翻訳し、Gitへのプッシュのたびに更新済みのロケールファイルをコミットし直します。一度セットアップすれば——globalize.nowでリポジトリを接続するだけです——後は手作業で飛ばしてしまう工程はありません。エージェントにも次のようにしてインストールできます:
npx skills add globalize-now/globalize-skills
プランは月額5ユーロから。20ユーロのStarterプランには、月あたり20ユーロ分の翻訳クレジット(約20万語相当)が含まれ、それを超えた分は同じ文字単価で従量課金されます。シート数課金や言語数課金は一切なく、サインアップ時にはカード登録不要で5ユーロ分の翻訳クレジットが付与されます。エンジニア向けの詳細はdevelopersページに、AIツールで開発している方向けの簡潔版はvibe codersページにあります。
この3層構成のポイントは、どの層も「あなたの規律が何百回ものエージェントセッションを通じて維持され続けること」に依存していない点です。ルールファイルが発生率を下げ、lintルールが失敗を目立たせ、同期処理が修正コストをゼロにします。この組み合わせこそが、エージェントがコードを書き換え続ける中でも、多言語対応アプリを多言語のまま保ってくれるのです。
壊れやすいのは、あなたのi18n設定そのものではありません。エージェントが文字列を書いてから、それがロケールファイルに届くまでのループこそが弱点なのです。globalize.nowは、プッシュのたびにそのループを閉じます。
globalize.nowは、ハードコードされたアプリ内テキストを翻訳可能なロケールファイルに変換し、リリースのたびに自動で最新の状態を保ちます。
globalize.nowを無料で試す