Next.js で多言語化(i18n)を本気でやると、最初に来るのは「枠」ではなく キー切り地獄 です。
t('home.title') を切って、辞書に移して、翻訳を回して、戻して、用語がズレたらまた直す。
弊社音楽系プロダクト Imaju の DOC は、蓋を開けると 約4万語超 × 五か国語 × 50ページ超。
正攻法は正しい。でもこの量でレイアウト調整まで五言語ぶん回すのは、キツいというより非現実的でした。
そこで自作したのが OSS の bracket-i18n です。
JSX に日本語正本を [[…]] で書いて scan すると ID 化され、fill で他言語の下書きが入ります。出荷合否は人(lock)。
たたき台まで(実測イメージ): 約11時間(Gemini 無料枠当時)。最終は人の精査です。
Quick Start
npm install -D bracket-i18n
npx bracket-i18n init
init が i18n-config.js と src/i18n/、npm scripts(i18n:*)をプロジェクト側に用意します。
まずは生成された config を、出荷する locale に合わせて編集してください。
init は保存の設定まで用意します。
autosave 変換には別途 VS Code(や Cursor)用の拡張機能「Run on Save」 が必要です。必要無ければ手動で scan します。
書く:
export default function Pricing({ locale }: { locale: string }) {
return <h1>[[成長に合わせて選べる料金プラン]]</h1>;
}
変換:
npx bracket-i18n scan
(または npm run i18n:scan。エディタの Run on Save でも可)
変換後のイメージ:
import { Languages } from '@/i18n';
export default function Pricing({ locale }: { locale: string }) {
return <h1>{Languages('eb573cd25ff_', locale) /* 成長に合わせて選べる料金プラン */}</h1>;
}
他言語の下書き(任意・API キー必要):
# プロジェクトルートの .env.development / .env.local / .env などへ
# GEMINI_API_KEY=...
npx bracket-i18n fill
fill は 下書きです。通したものだけ lock: true。
大量に [[ ]] を書き直している最中は i18n:pause → 確定後に i18n:resume → まとめて scan が安全です(保存のたびに途中状態で落ちやすい)。
生成される呼び出しは識別子
localeをそのまま使います。スコープにlocaleがあること。
学習コストこれだけ。プロダクトに全集中出来ます。
背景:どうしたもんか
Imaju を手掛けて数年。海外展開を視野に、初めから日本語以外にも広げたいと思っていました。
DOC だけで約4万語以上、五か国語。音楽用語も多い。
翻訳精度もさることながら、翻訳依頼のコストも「それはもう」という感じでした。
薄々、詰むことは少し前から見えていました。
キーを切って、辞書に移して、翻訳を回して、戻して、用語がズレたらまた直す。
正攻法は知っている。正しい。でもこの量は回せない。
そんな困りごとから降りてきた話です。
そうだ、作ってしまえ
思った。そうだ、作ってしまえ。
原型は、いまの bracket-i18n につながるローカル自動スクリプトでした。
JSX に日本語を [[…]] で書いて保存すると、
-
[[日本語]]を拾う - ID を振る
-
Languages(id, locale)に差し替える - 辞書へエントリを足す
まで一気にやる。
翻訳が AI で不安なら、data は空でいい。CSV に落として翻訳さんにお願いできる。
<h1>[[成長に合わせて選べる料金プラン]]</h1>
{/* 複数形 */}
<p>[[--n Steve has {count} dogs.]]</p>
{/* 性別 */}
<p>[[--g He posted.]]</p>
でも壁はあった
もちろん問題もありました。
-
多次元配列・被り — グリッドの
[[4,4]]が i18n の[[…]]と誤検知される類 -
勝手に
{}に置き換え問題 - 複数形・性別・アラビア語まわりなど、動的な単語変換
なかなか壁は高い。でもこれは関数と正規表現の問題で、致命傷ではない、と気づきました。
長文問題
長い文はコンポーネントも嵩みます。囲んで変換すると本文が ID に置き換わるので、「どこに何が書いてあるか」がわからなくならないよう、先頭あたりをコメント化します。
{Languages('……_', locale) /* 正攻法は知っている */}
コンポーネントは縮小しつつ、中身も追える。
Cursor と AI 下書き
ここで Cursor の存在を知りました。流れはこうです。
- 正本言語を config で設定(日本人なら
ja) - 必要ならニュアンス(トーン、製品用語、音楽 Domains で崩したくない語)も config へ
- たたき台が欲しければ API KEY(私は Gemini。たたき台としてのコスパが理由。固定信仰ではない)
- 翻訳対象言語を設定(私は五か国語)
- 本文がまだ固まる前は
pause - コンポーネントで本文を書く →
[[ ]]→scan/fill
実数イメージ:
- 約 40000語のたたき台まで、およそ 11時間
- たたき台側(Gemini)は当時 無料枠
- 最終チェックはたたき台のあと 人が見て
lock(必要なら別モデルで数回見る、も含む)
Languages(...) になった行を消せばデータも追従しやすい。だから 何度でもやり直せる。
比較:正攻法・Next・われら
他人の否定はしません。得意分野が違う。
比較するのは「正しさ」ではなく、DOC 五か国語という仕事の実コストです。
| 項目 | 正攻法の i18n | Next.js の i18n | われら(bracket-i18n) |
|---|---|---|---|
| 書き方 |
t('home.title') などキー中心 |
ルーティング+辞書/メッセージ | JSX に [[日本語]]
|
| 強み | 枯れてる・チーム共通言語 | ロケール URL・Next との一体感 | 執筆速度・AI 下書き・やり直しの軽さ |
| 弱みになりやすいところ | キー設計と往復が先に来る | 「枠」はくれるが翻訳作業自体は別問題 | 自前ツールの学習コスト |
| 向く場面 | 既存規約・大規模チーム | App/Pages の locale 設計 | 日本語正本・大量 DOC・高速展開 |
正攻法
キーを切り、辞書に載せ、t(id) で読む。長年の正解。
ただし作業としては「文言 → キー名 → 辞書 → 画面 → 翻訳レーン」になりやすい。正しさはある。でも正直かなりしんどい。
Next.js の i18n
locale 付き URL、ミドルウェア、メッセージ読み込み。
「どの言語のページか」を Next の世界観で扱えるのは大きい。
ただ誤解しやすい点があります。
Next の i18n = 翻訳が終わる
実際は 配信の枠 であって、4万語・50ページ超の DOC を五言語にしてくれるものではありません。
枠は借りて、中身のパイプラインは別に要る。
bracket-i18n
発想を逆にしました。
- まず正本言語で書く
- 固まったら
[[…]]で囲む -
scanで ID 化(Languages(id, locale)) -
fillで他言語の下書き(不安なら空のまま CSV) - 人が見て、通したものだけ
lock
キー設計を、執筆の前に置かない。
正本は設定言語。翻訳は後工程。
AI 翻訳が不安なら、コスト構造で見る
不安、わかります。では 構造だけで見ます(単価は案件ごとに違うので実額は出しません)。
| 項目 | ゼロから翻訳者に五か国語・約50ページ | AI たたき台あり → 人がチェック・精査・修正 |
|---|---|---|
| 主な作業 | 全文の新規翻訳 × 言語数 | 下書き確認・用語修正・lock |
| 待ち時間 | 長い | 短い(下書きは時間単位で見える) |
| お金の重心 | 語数 × 言語数 × 単価がそのまま乗る | 人の精査時間が本体 |
| やり直し | 高い | 行を消して再挑戦しやすい |
従来:
語数 × 言語数 ×(単価 or 人日)
AI 下書きがあると:
語数 × 言語数 ×(下書きの待ち)+ 人間の精読と lock
「翻訳がタダ」ではない。
最初の下書きが極端に安くなるだけです。
メリットはコストだけじゃない。data 構造にも
五か国語が 1文脈(1エントリ)にまとまっているわかりやすさがあります。
{
// イメージ(実際のスキーマはプロジェクト/パッケージ設定に従う)
id: 'ec2339bf326_',
ja: '成長に合わせて選べる料金プラン',
en: 'Pricing plans that grow with you',
es: '…',
fr: '…',
it: '…',
lock: true,
}
- この文の正本はどれか
- どの言語が空か
- lock したか
が一箇所で追えます。CSV 往復とも相性がいい。
専門用語が多い領域は、やはりこうが無難です。
- 空(または最小)の訳で出す
- CSV で配布し、担当者が記入
- 戻して lock
- lock したものは以後、勝手に潰れない
fill は下書き。出荷の合格線は人側。
おわりに
世界へ配信したい、必要だというニーズが一人でもいたら——主観ですけど、正直便利だと思います。あとは、導入できる環境かどうか、ですね。
creathree の理念 「ない物は作る」 でお役に立てるならと、npm へ出した次第です。
どうしたもんか → 40000語超 × 五言語 × 50ページ超 → そうだ、作ってしまえ。
それが bracket-i18n です。
同じ痛みを持っている人に届いたら嬉しいです。Issue や感想、歓迎です。
creathree が発信する記事は、このような模索ストーリーを中心に不定期で出します。
今後ともどうぞよろしくお願いいたします。