サイト全体を日本語とブラジルポルトガル語に対応させたところ、料金は31ユーロでした

9月29日、globalize.nowを日本語とブラジルポルトガル語に対応させました。全ページ、全ての料金表、そしてブログ記事61本すべてです。翻訳自体は8分で完了し、アプリの請求額は約31ユーロでした。globalize.nowはAIを活用したローカライゼーション基盤で、これは実際に自社サイトで使ってみた記録です。数字はすべて記憶ではなく、ジョブページとGitの履歴から取得したものです。

2017年当時のローカリゼーション成長戦略にかかったコストについての記事は、「自社の数字はまだ公開していない」という一文で終わっています。これがその最初の公開です。ただし成長の数字ではなく、コストの数字です。/ja/にはまだ誰も訪れていませんから。

実際に翻訳したのは何か?

サイト全体を、2言語分。リポジトリから数え上げた内訳は以下の通りです。

  • インターフェース: 5つのメッセージカタログ(コアサイト、料金、比較ページ、インテグレーション、翻訳例)にわたる英語文字列1,782件、約21,000語。
  • ブログ: 記事61本、約110,000語。
  • 翻訳先言語: 日本語(ja)とブラジルポルトガル語(pt-BR)。

原文で約13万語を2言語に翻訳した計算になります。プルリクエストは170ファイルに変更を加え、34,681行を追加しました。マージ後、サイトマップには11ロケールにわたる811のURLが並び、サイトは962の静的ページをビルドします。

どれくらい時間がかかったのか?

翻訳にかかった時間は8分、その後にもう少し長めの品質チェックが入ります。CTOのArtursがプロジェクトに2言語を追加し、ジョブはmainのUTC08:56に開始されました。詳細はジョブページで次のように内訳が示されています。

工程所要時間
翻訳(新規文字列11,264件、各言語5,632件)8分1秒
自動QAレビュー27分11秒
プルリクエストへの納品5分29秒

両言語を含むコミットはUTC09:29に完了しました。このジョブに含まれる残りの45,106件の文字列は翻訳メモリから取得されたもので、既存の8言語には変更がなかったためです。

Artursはチームチャットでこの件を話したとき「10分くらいかな」と予想していました。翻訳にかかった時間についてはほぼ正確でしたが、ジョブ全体については少し楽観的で、その差はQA工程の分です。私たちとしては、チェックを省略するよりも、27分の機械時間をかけて確認する方を選びます。

自動QAは何を検出したのか?

新規文字列11,264件のうち72件にフラグが立ちましたが、いずれも軽微な指摘でした。11,192件は問題なしとされ、その過程でQAは182件の自動修正を適用しています。

フラグが立った指摘の多くは、日本語に対して長さチェックが慎重になりすぎたことによるものでした。たとえば「Pricing」は「料金」という2文字になり、長さの比率は0.29となって、しきい値である0.3をわずかに下回ります。これはアルファベット言語向けに設計されたヒューリスティックが正しい翻訳に反応してしまっただけで、誤りではありません。それでも私たちはこのフラグを非表示にはしていません。一度も反応しないチェックは、チェックとして機能していないのと同じだからです。

かかった費用は?

約31ユーロという金額について、プロジェクトの言語別コスト表示を見ると、日本語が15.72ユーロ、ブラジルポルトガル語が16.15ユーロとなっています。これはアプリが実際に請求する金額であり、同じ量を翻訳する顧客が目にする金額と同じです。私たちの内部的なモデルコストでも、割引後の金額でもありません。

翻訳語数は合計で約26万語、単価にすると1,000語あたり約12セントです。それ以外の追加請求は一切ありません。言語ごとの課金も、座席ごとの課金もないため、12番目の言語を追加するのも3番目の言語を追加するのも同じコストです。現在のプランは料金ページをご覧ください。

エンジニアリング作業の実際の内容は?

プルリクエストは1件、同日午後にマージされましたが、その中身は翻訳作業ではありませんでした。このプルリクエストが開かれた際、翻訳ジョブが再度実行され、56,390件中56,370件の文字列をそのまま翻訳メモリから取得したため、すでに翻訳済みのカタログが新しいファイルに反映されるまでわずか7分でした。自動化されたのは翻訳の部分です。Next.jsサイトで新しいロケールを登録する作業は、依然として自分たちのコードの領域であり、計画を立てる前に「自分たちのコード」が具体的に何を意味するのかを知っておく価値があります。

next-intlにロケールを追加すること自体は、ルーティング設定のたった1行です。URLを構築したり、ロケールを読み取ったり、対応言語一覧を表示したりする処理はすべて、それ以外の差分部分にあります。

  • ルーティング: ロケール一覧、加えてpt-BR用のURLプレフィックスの上書き(次のセクションで詳述)。
  • SEOメタデータ: hreflangのクラスター、canonical、og:locale(ja_JP、pt_BR)、そしてメタデータ用ヘルパーがループ処理するロケール一覧。
  • フォーマット: Intlタグのja-JPとpt-BRにより、日付や数値が正しく表示されるようにする対応。
  • 言語切り替えスイッチャー: すべてのロケールに実際の<a href hreflang>付きでリンクしており、クローラーがそれをたどれるようになっています。
  • サイトマップとチェック処理: サイトマップ生成器、lastmodのチェック、内部リンクのチェック、翻訳検証ツール、それぞれが新しい2つの言語コードを認識できるよう対応が必要でした。
  • カタログ: 2つのロケール向けに、ヘッダーのみの空の.poファイルを用意し、同じプルリクエスト内で翻訳メモリから内容を埋めました。

もしあなたのサイトが私たちのものよりカスタム度が低ければ、このリストの大半は設定ファイルで済むはずです。ただ、どこかでURLを手作業で組み立てている箇所があれば(多くのサイトにはそういう場所があります)、リンクが最初に崩れたときにそれを見つけることになります。セットアップについては開発者ガイドを、自動化されている部分とされていない部分の切り分けについてはAIローカリゼーションエージェントが実際に行うことをご覧ください。

ブラジルポルトガル語が/pt-BR/ではなく/pt-br/なのはなぜか?

言語タグとURLセグメントは、役割の異なる別のものだからです。

BCP 47に従った正しいタグはpt-BRです。これは、ファイル名、<html lang>、hreflang、og:locale、Intlなど、機械が言語を読み取るすべての場所で使用しています。小文字化されるのはURLだけです。

// i18n/routing.ts (trimmed)
const prefixes = {
  "pt-BR": "/pt-br",
} as const;

export const routing = defineRouting({
  locales: ["en", "it", "de", "fr", "es", "lv", "hi", "lt", "et", "ja", "pt-BR"],
  defaultLocale: "en",
  localePrefix: { mode: "always", prefixes },
});

/** The URL path segment for a locale: `pt-BR` → `pt-br`, `de` → `de`. */
export function localeUrlSegment(locale: string): string {
  const prefix = (prefixes as Record<string, string | undefined>)[locale];
  return prefix ? prefix.slice(1) : locale;
}

大文字・小文字が混在するパスは入力ミスが起きやすく、/pt-BR/と/pt-br/の両方で応答してしまうサイトでは、すべてのページが2つずつコピーとして存在し互いに競合してしまいます。そのため/pt-BR/は/pt-br/にリダイレクトされ、localeUrlSegment()は手作業で作成したすべてのURLをプレフィックスマップから生成します。信頼できる情報源を一つに絞ることで、言語切り替えスイッチャー、canonical、サイトマップがずれてしまうことを防いでいます。

なぜ日本語とブラジルポルトガル語を選んだのか?

これらは、まだ対応していなかった中でも規模の大きなウェブ言語だったからです。公開当日のW3Techsの集計によると、日本語はウェブサイトのコンテンツ言語の4.9%、ポルトガル語は4.1%を占めています。

そもそも多言語対応をすべきだという根拠は、私たちよりずっと古くから存在します。CSA Researchが2020年に29カ国8,709人の消費者を対象に行った調査では、76%が「自分の母語で情報がある製品を購入したい」と回答しています。これが需要側の話です。変わったのは供給側です。かつて2言語対応といえばベンダーとの契約が必要でしたが、今では31ユーロで済みます。

言語選定における私たち自身のルールは、成長戦略の記事で紹介したものと同じです。すでにトラフィックが来ている場所を見て、2言語を追加し、他は何も変えず、四半期にわたって計測する。今回もまさにその方針で、この2言語に取り組みます。

まだ確認していないことは?

ネイティブによるレビューです。日本語やブラジルポルトガル語を母語とする人は、まだ出力内容を確認していません。

これまでに確認できたこと:すべての文字列に対する自動QA、ビルド前チェックをすべて通過するビルド、そしてステージング環境で抜き取り確認したページが正しいlang、canonical、hreflangを伴って200を返し、完全にレンダリングされることです。一方で、ビルドでは検証できないのは、文章が人間が書いたように自然に響くかどうかです。これは私たち自身が自社のドイツ語とフランス語を監査した際に痛感したことで、ページ全体にわたって不適切な文体(レジスター)が使われていることが判明しました。

そのため、次はレビューを行います。何か問題が見つかった場合も含め、その結果をこの記事に追記していく予定です。

あなたのサイトでも同じことができるか?

文字列がすでにカタログ形式で管理されていれば、可能です。翻訳部分は数分で完了します。まだコンポーネント内にハードコードされている場合は、まず変換作業が必要です。これはアプリ内の接続フローの中で一度だけ実行され、それ以降は新しい文字列がプッシュジョブによって翻訳されます。バイブコーダー向けガイドでは、LovableやBolt、Cursorで構築されたアプリのように、文字列のハードコードが一般的な環境について解説しています。

コストは翻訳する語数に応じて変わるものであり、言語数やチームの人数には依存しません。だからこそ、「言語を追加する価値はあるのか?」という問いへの答えが変わったのです。今では、その検討会議そのものよりも、実際に計測する方が安く済みます。

自分のリポジトリで試してみる

2言語対応にかかったのは、翻訳時間8分と費用約31ユーロだけでした。リポジトリを接続し、アクセス解析ですでに需要が見えている言語を2つ選んで、あなた自身の「明細」がどうなるか確かめてみてください。

globalize.nowは、ハードコードされたアプリ内テキストを翻訳可能なロケールファイルに変換し、リリースのたびに自動で最新の状態を保ちます。

globalize.nowを無料で試す