本文へスキップ

詳しく

SaaSの多言語対応は初日から:9言語と右から左へのアラビア語

公開日:· 1分で読めます· Anton Shubin

要点

  • Seedのアプリ、管理画面、Webサイト、メールは、アラビア語を含む9言語に対応しています。各言語の文言は約3,900件です。
  • 文言の欠落、古い翻訳、プレースホルダーの消失があるとテストがビルドを止めます。アラビア語で反転しない「左」「右」の余白も別のテストが止めます。
  • 言語は人ごとの設定としてサーバーに保存され、日付の形式、タイムゾーン、時計の表示とは別に選べます。
  • 10番目の言語の追加は、リストへの1行、カタログごとのファイル、読み込み用の数行で済みます。

右から左に書くアラビア語も含めて、SaaSを多言語対応させるには、どうすれば後で作り直さずに済むでしょうか。答えは、最初の画面から始めて、ルールの番はテストに任せることです。Seedは9言語で動きます。日本語、英語、ロシア語、スペイン語、ドイツ語、ブラジル・ポルトガル語、アラビア語、フランス語、簡体字中国語です。アプリ、管理画面、Webサイト、メールのすべてが対象で、アラビア語を選ぶとレイアウト全体が左右反転します。

この記事は、「翻訳は後でやろう」が「翻訳は永遠にやらない」と同じ意味だと知っている創業者や開発者のためのものです。文言がどこにあるのか、テストが何を見つけるのか、そして右から左(RTL)への対応に実際どれだけかかるのかをお見せします。

なぜ初日からなのか

後から翻訳すると、すべての画面を二度触ることになるからです。コードに直接書かれた文言、手で組み立てた日付、margin-left を一つ残らず探して直す必要があります。しかもその頃には、コードは機能でいっぱいです。最初から翻訳していれば、新しい画面は作るそばから翻訳されます。かかるのは数分で、誰も手をつけたがらない大仕事にはなりません。

もっと単純な理由もあります。インターネットを使う人の大半は、英語を第一言語として読んでいません。相手の言語で話せるプロダクトは、それだけで一歩先を行けます。

文言はどこにあるのか

Seedの画面に出る言葉は、すべてカタログに入っています。領域と言語ごとに1つのTypeScriptファイルで、i18n/app/ja.ts や i18n/site/de.ts のような形です。領域は5つ。アプリ、管理画面、Webサイト、メール、APIのエラーメッセージです。英語が原文で、ほかのファイルは同じキーを持ちます。

領域 1言語あたりの文言数
アプリ 1,503
管理画面 1,487
Webサイト 682
エラーコード 164
メール 79

1言語あたり約3,900件で、2026年11月2日に数えた数です。preact-componentsのコンポーネントは、これとは別に自前のカタログを持っています。

アプリは、使っている言語の分だけを小さな別ファイルとして読み込みます。日本語で使う人がドイツ語までダウンロードすることはありません。

翻訳が古くならない仕組み

i18n/i18n.test.ts というテストがあります。次のような場合に失敗します。

  • どれかの言語で文言が足りない、または英語にはもうない文言が残っている。
  • 翻訳した後で英語が変わった(言語ごとのロックファイルに、どの英語から訳したかが記録されています)。
  • {count} のようなプレースホルダーや、リンクを囲むタグが消えている、または綴りが違う。
  • 複数形で、その言語に必要な形が欠けている。アラビア語には6つの形があります。
  • どのコードからも使われていない英語の文言がある。

そのため、Notes.create と表示されるボタンや、英語より1週間遅れたドイツ語の画面が本番に出ることはありません。

右から左への対応に本当にかかるもの

レイアウトが「左」「右」と一度も言わなければ、思ったほどではありません。Seedのスタイルは論理的な方向、つまり「始まり」と「終わり」を使います。左から右のページでは始まりが左、アラビア語では右です。反転させるのはブラウザーの仕事です。

これを願いではなくルールにするのがテストです。tests/spacing.test.ts はすべてのアプリを調べ、物理的な方向を名指しする余白のクラスを見つけると失敗します。ページの dir は言語に合わせて設定されます。Webサイトはサーバー側で、アプリは言語を切り替えた瞬間にです。

後で痛い目を見やすい点がもう2つあります。

  • 文の中の名前。 「AnnaさんがあなたをTeamグループに招待しました」のような文は、アラビア語では2つの方向が混ざり、ラテン文字の名前が句読点を反対側に引っぱることがあります。ユーザーが入力した値は <bdi> で囲み、その方向が周りの文に影響しないようにしています。
  • 日付、数値、時刻。 すべての画面の日付は、1つのフォーマッターを通ります。別のテスト、tests/no-direct-date-format.test.ts は、toLocaleString() を直接呼ぶコードを見つけると失敗します。

言語、地域の形式、時計は3つの別の設定

言語はプロフィールと一緒にサーバーに保存されるので、どの端末でも、どのメールでも同じです。地域の形式、タイムゾーン、12時間制か24時間制かは、それぞれ別の設定です。ベルリンに住む日本人は、日本語の画面とベルリンの時刻を見たいはずです。東京で働くイギリス人なら、英語の画面と 2026/11/03 のような日付かもしれません。これを1つの設定に結びつけると、ユーザーの半分に間違った日付を見せることになります。

Webサイトでは、言語はアドレスに入ります(/ja/pricing、/ar/pricing)。ブラウザーやIPによる自動リダイレクトはなく、各ページは検索エンジンに、どの言語版があるかを伝えます。

翻訳は誰がしているのか

AIエージェントです。書かれたガイド i18n/TRANSLATING.md に従って翻訳します。ガイドは言語ごとの口調(日本語なら、です・ます調で「あなた」を使わない)、同じものを全画面で同じ名前で呼ぶための用語集、そして「言葉ではなく状況を訳す」というルールを定めています。新しい翻訳は、別のエージェントが英語に訳し戻して意味を比べます。プロの翻訳会社ではありませんし、そのふりをするつもりもありません。それでも、誰も読めない画面よりはずっと良いものです。

10番目の言語を足すには

言語のリストに1行、領域ごとにカタログファイルを1つ、そして各アプリに読み込み用の数行です。あとはテストが新しい言語に足りない文言をすべて挙げ、そろうまで赤いままです。

試してみてください

デモを開き、プロフィールで言語をアラビア語に切り替えてみてください。アプリ全体がくるりと反転する様子を見たら、日本語に戻しましょう。この記事も9言語すべてで読めますし、コードはMITライセンスで公開しています。

次の記事をメールで受け取る

新しい記事が出たときや何かをリリースしたときに、短いメールをお届けします。メールで一度確認するだけで登録でき、購読解除もワンクリックです。

すべての記事