- 技術 💻
ローカライズとは?i18n・l10nの違いと多言語対応の作り方を実行例で
結論
ローカライズ(l10n)とは、製品を特定の言語・地域に合わせて作り替えることです。その前に、翻訳や書式の差し替えができる作りにしておくことを国際化(i18n)と呼びます。翻訳は、ローカライズの作業の一部です。
この記事でわかること
- ローカライズ・i18n・l10n・翻訳の違いと、作業の順番
- gettext の .po/.mo で日本語・英語・ポーランド語を切り替えた実行結果
- 書式・複数形・RTL・ドイツ語での文字の伸びを Babel・Intl・Chromium で実測した表
多言語対応は「国際化で作りを整える」→「言語・地域ごとにローカライズする」→「その言語の画面で確かめる」の順に進みます。国際化を飛ばして翻訳から始めると、言語を足すたびにコードを直すことになります。
flowchart TD
A[国際化 i18n<br>文字列を外に出す・書式を任せる] --> B[翻訳ファイル<br>.po や JSON]
B --> C[ローカライズ l10n<br>翻訳・書式・画像・法令]
C --> D[その言語の画面で確認<br>LQA]
D -->|直す| B
D --> E[公開]
よく調べられることの答えを先にまとめます。
| 知りたいこと | 答え | 詳しい節 |
|---|---|---|
| ローカライズとは | 製品を特定の言語・地域・文化に合わせて作り替えること(l10n) | ローカライズとは |
| 翻訳との違い | 翻訳は文章の言い換え。ローカライズは日付・通貨・画像・法令まで含む | ローカライズとは |
| i18n とは | internationalization(国際化)の略。i と n の間が18文字 | i18nとl10nの違い |
| 国際化とローカライズの違い | 国際化は「訳せる作り」にする設計、ローカライズは各地域向けの中身 | i18nとl10nの違い |
| 多言語対応の作り方 | 文字列をコードから出し、翻訳ファイルを言語ごとに読み込む | gettextで文字列を外に出す |
| 翻訳が変になる原因 | 文字列の連結・複数形・語順の違い | 文字列の連結/複数形 |
| 日付・通貨の表示 | 自分で組み立てず、Babel や Intl に地域を渡して任せる | 日付・数値・通貨の書式 |
| アラビア語の表示 | dir="rtl"・bdi 要素・論理プロパティで左右を入れ替える | RTLの表示 |
| どこまでやるか | 国際化は早めに、ローカライズは利用者と売上を見て決める | 判断表 |
確認環境:Ubuntu 24.04.5 LTS の上で、Python 3.13.18 の venv に Babel 2.18.0(CLDR 47 のデータを同梱)を入れ、標準ライブラリの gettext と pybabel の extract・init・update・compile で日本語・ポーランド語・アラビア語の翻訳ファイルを作って実行しました。書式と複数形は Babel と Node.js 22.23.4 の Intl(ICU 78.3・CLDR 48.0)で同じ値を出して比べています。言語の交渉は 127.0.0.1 に立てた自作の HTTP サーバーへ curl 8.5.0 で Accept-Language を変えて送り、RTL の表示と文字の伸びは 127.0.0.1 の自分のページを Chromium 153.0.8010.12(playwright 1.63.0)で開き、getBoundingClientRect で幅と位置を測りました。用語の定義は W3C と MDN、多言語サイトの URL と hreflang は Google 検索セントラル、翻訳での文字の伸びの目安は W3C の記事(IBM の指針を引用)で確かめています。ゲームの翻訳工程などは一般的な説明の範囲です。図は mermaid 11.16.0 で描画を確かめました(確認日 2026年10月29日)。
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「ローカライズとは?i18n・l10nの違いと多言語対応の作り方を実行例で」
ローカライズとは(意味・翻訳との違い・対象範囲)
この節では、ローカライズの意味と、翻訳だけでは終わらない理由、対象になるものの一覧が分かります。
ローカライズ(localization、略して l10n)は、製品・アプリ・文書を、特定の市場(ロケール)の言語・文化などの要件に合わせて作り替えることです。 W3C は「a product, application or document content を、特定の対象市場(a locale)の言語・文化その他の要件に合うように適応させること」と定義しています。日本語では「地域化」「現地化」とも書きます。
「ロケール」は、言語と地域の組み合わせです。ja-JP(日本の日本語)、en-US(米国の英語)、en-GB(英国の英語)のように書き、同じ英語でも米国と英国で日付の順番が違います。書き方は BCP 47 という規格で決まっていて、ブラウザの lang 属性や Accept-Language ヘッダーもこの形です。
翻訳との違い
翻訳とローカライズ
見分ける問い:「訳した文を並べただけで、その国の人が迷わず使えるか」
文化的な意味(色・絵・ジェスチャー・宗教や歴史に関わる表現)に合わせる部分を、特に「カルチャライズ」と呼ぶことがあり、ローカライズの中の一分野として扱われます。
ローカライズの対象になるもの
W3C が挙げる項目に、アプリ開発でよく出るものを足して整理しました。
| 分類 | 対象 | 例 |
|---|---|---|
| 文言 | 画面の文字・エラーメッセージ・メール・通知・ヘルプ | 「保存しました」「Saved」 |
| 書式 | 日付・時刻・数値・通貨・パーセント・単位 | 2026/10/29 と 29.10.2026、1,234.5 と 1.234,5 |
| 文法 | 複数形・語順・性・敬語 | 1 message/3 messages |
| 並べ替え | 辞書順(照合順序) | ドイツ語の Ä の位置 |
| 書く向き | 右から左(RTL)の言語 | アラビア語・ヘブライ語 |
| 画面 | 文字の長さ・フォント・改行・入力方法 | ドイツ語で長くなるボタン |
| 画像・色・記号 | 画像の中の文字・アイコン・色の意味 | 郵便受けの絵、赤と緑の意味 |
| 住所・氏名・電話 | 項目の順番・郵便番号の形・姓名の順 | 姓が先か名が先か |
| 法令・商習慣 | 個人情報・表示義務・税・決済手段 | 消費税の表示、Cookie の同意 |
この一覧のうち、コードで先に準備できるもの(文言の外出し・書式・複数形・書く向き)を整えるのが、次の節の国際化です。
i18nとl10nの違い(国際化とローカライズの役割分担)
この節では、i18n・l10n という略し方と、国際化とローカライズで誰が何をするのかが分かります。
i18n は internationalization(国際化)の略で、i と n の間に18文字あることから来ています。l10n は localization の l と n の間が10文字です。 W3C はこの略し方を説明したうえで、国際化を「文化・地域・言語の違う対象に、簡単にローカライズできるように製品・アプリ・文書を設計・開発すること」と定義しています。MDN も i18n を「さまざまな地域、言語、文化が異なるターゲットオーディエンスに簡単に適応できるようにシステムを設計すること」と説明しています。
国際化(i18n)=作りを整える
文字列をコードから出し、翻訳ファイルに置く
日付・通貨はライブラリにロケールを渡して作る
複数形・RTL・長い文字に耐える画面にする
主に開発者が、1回だけ行う
ローカライズ(l10n)=中身を入れる
言語ごとの翻訳を書く
その地域の書式・画像・法令に合わせる
その言語の画面で見て直す(LQA)
主に翻訳者と確認担当が、言語の数だけ行う
W3C は、国際化で行うことに「ローカライズの障害を取り除く設計」「ローカライズされるまで使わない機能(書く向きなど)を先に用意する」「ローカライズできる要素をソースコードから分ける」を挙げています。国際化は言語を足す前の準備、ローカライズは言語を足すたびの作業と覚えると混ざりません。
似た略語:g11n・m17n
| 略語 | 元の語 | 意味 |
|---|---|---|
| i18n | internationalization(国際化) | どの地域にも合わせられる作りにする |
| l10n | localization(ローカライズ) | 特定の地域に合わせる |
| g11n | globalization(グローバル化) | i18n と l10n をまとめた呼び方として使われることが多い |
| m17n | multilingualization(多言語化) | 1つのプログラムで複数の言語・文字を同時に扱えるようにする |
ライブラリの名前にも i18n が多く使われます(vue-i18n・i18next など)。中身は「翻訳ファイルを読み、ロケールに合わせて文字列を返す」道具で、国際化を楽にするためのものです。
gettextで文字列を外に出す(.po/.moで日英切り替えの実例)
この節では、国際化のいちばん基本の作業「文字列をコードから出して翻訳ファイルに置く」を、Python の gettext で最初から最後まで動かした結果が分かります。
gettext は、ソースコードの中の文字列を _("…") で囲んでおき、実行時にロケールに合う翻訳へ差し替える仕組みです。 翻訳者が編集するのはテキストの .po ファイルで、プログラムが読むのはそれを変換したバイナリの .mo ファイルです。Python では標準ライブラリの gettext で読み、ファイルの抽出と変換は Babel の pybabel で行いました。
import gettext, sys
lang = sys.argv[1] if len(sys.argv) > 1 else "ja"
t = gettext.translation("app", localedir="locales", languages=[lang], fallback=True)
_ = t.gettext
ngettext = t.ngettext
user = "Sato"
print(_("Settings"))
print(_("Hello, {name}!").format(name=user))
for n in [0, 1, 3]:
print(ngettext("You have {n} new message.", "You have {n} new messages.", n).format(n=n))
# 悪い例:連結
print(_("Delete") + " " + _("file") + "?")
抽出する(extract)
pybabel extract -F babel.cfg -o app.pot . で、_() と ngettext() の文字列を集めた雛形 app.pot を作る。
言語ごとに作る(init)
pybabel init -i app.pot -d locales -D app -l ja で locales/ja/LC_MESSAGES/app.po を作る。複数形の式(Plural-Forms)は言語に合わせて自動で入る。
訳す
.po の msgstr に訳を書く。Poedit などの専用エディタや翻訳管理サービスでも開ける形式。
変換して動かす(compile)
pybabel compile -d locales -D app --statistics で .mo を作り、言語を指定して実行する。
抽出した app.pot の中身です(先頭のヘッダーを省いています)。#: は文字列のある場所、python-brace-format は {name} のような置き換えがある印です。
#: app.py:10
#, python-brace-format
msgid "Hello, {name}!"
msgstr ""
#: app.py:12
#, python-brace-format
msgid "You have {n} new message."
msgid_plural "You have {n} new messages."
msgstr[0] ""
msgstr[1] ""
init を3言語で実行すると、言語ごとに複数形の式が入りました。日本語の init では警告も出ています(雛形の msgstr が2つあり、日本語は1つしか使わないため。訳を書けば消えます)。
$ pybabel init -i app.pot -d locales -D app -l ja
creating catalog locales/ja/LC_MESSAGES/app.po based on app.pot
WARNING: msg has more translations than num_plurals of catalog
WARNING: Problem on line 31: ''
locales/ja/LC_MESSAGES/app.po: "Plural-Forms: nplurals=1; plural=0;\n"
locales/pl/LC_MESSAGES/app.po: "Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && "
"(n%100<10 || n%100>=20) ? 1 : 2);\n"
locales/ar/LC_MESSAGES/app.po: "Plural-Forms: nplurals=6; plural=(n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : " ...
日本語とポーランド語に訳を書いた .po の一部です。
# locales/ja/LC_MESSAGES/app.po
msgid "Hello, {name}!"
msgstr "こんにちは、{name}さん!"
msgid "You have {n} new message."
msgid_plural "You have {n} new messages."
msgstr[0] "新着メッセージが{n}件あります。"
# locales/pl/LC_MESSAGES/app.po
msgid "You have {n} new message."
msgid_plural "You have {n} new messages."
msgstr[0] "Masz {n} nową wiadomość."
msgstr[1] "Masz {n} nowe wiadomości."
msgstr[2] "Masz {n} nowych wiadomości."
compile して、言語を変えて実行した結果です。
$ pybabel compile -d locales -D app --statistics
5 of 5 messages (100%) translated in locales/pl/LC_MESSAGES/app.po
compiling catalog locales/pl/LC_MESSAGES/app.po to locales/pl/LC_MESSAGES/app.mo
5 of 5 messages (100%) translated in locales/ja/LC_MESSAGES/app.po
compiling catalog locales/ja/LC_MESSAGES/app.po to locales/ja/LC_MESSAGES/app.mo
$ python app.py en $ python app.py ja $ python app.py pl
Settings 設定 Ustawienia
Hello, Sato! こんにちは、Satoさん! Cześć, Sato!
You have 0 new messages. 新着メッセージが0件あります。 Masz 0 nowych wiadomości.
You have 1 new message. 新着メッセージが1件あります。 Masz 1 nową wiadomość.
You have 3 new messages. 新着メッセージが3件あります。 Masz 3 nowe wiadomości.
Delete file? 削除 ファイル? Usuń plik?
- en には翻訳ファイルを作っていません。
fallback=Trueのため、ファイルが無い言語ではコードに書いた英語がそのまま出ました。fallback=FalseにするとFileNotFoundError: [Errno 2] No translation file found for domain: 'app'で止まります。 - ソースの言語(ここでは英語)は、そのまま「翻訳が無いときの表示」になります。ソースの文言は、それだけで意味が通る文にしておきます。
- 最後の行の「削除 ファイル?」が、次の節の連結の問題です。
.mo の大きさは、日本語 714 バイト、ポーランド語 795 バイトでした。Django・Flask(Flask-Babel)・WordPress・PHP・C の多くも同じ gettext の形式を使います。JavaScript の世界では JSON の翻訳ファイルが多く、Java では messages.properties、Android は res/values-ja/strings.xml のように、形式は違っても「文字列を外に出し、ロケールで選ぶ」考え方は同じです。
文字列の連結で翻訳が壊れる例と直し方
この節では、国際化でいちばん多い失敗である「文字列を足し算で組み立てる」書き方が、なぜ翻訳で壊れるのかが分かります。
言語ごとに語順が違うため、文を部品に分けて + でつなぐと、訳者が正しい語順に並べ替えられません。 前の節の _("Delete") + " " + _("file") + "?" は、英語では「Delete file?」と正しく見えますが、日本語では「削除 ファイル?」になりました。日本語なら「ファイルを削除しますか?」で、語順も助詞も疑問符も違います。
文は1つのまとまりで訳に出す
_("Delete") + " " + _("file") + "?" 部品ごとに訳され、日本語は「削除 ファイル?」_("Delete {item}?").format(item=…) のように文ごと1つの文字列にし、置き換え部分に名前を付ける訳者は「{item}を削除しますか?」と自由に並べ替えられる
置き換えに名前を付けるのも大切です。%s や {} が2つ以上あると、語順が変わったときに入れ替えられません。{name}・%(count)d のように名前を付けると、訳者は位置を自由に動かせます。
| 書き方 | 何が起きるか | 直し方 |
|---|---|---|
部品を + でつなぐ |
語順・助詞を訳者が直せない(手元で「削除 ファイル?」) | 文全体を1つの msgid にする |
名前の無い置き換え %s … %s |
語順が変わると入れ替えられない | {user}・%(user)s と名前を付ける |
n + " messages" |
1件でも messages になる。ポーランド語は3通り必要 | ngettext・ICU の plural を使う(次の節) |
| 同じ英単語を使い回す(Open) | 「開く」と「営業中」のように意味で訳が分かれる | msgctxt(文脈)や別のキーで分ける |
| HTML タグごと訳に出す | 訳者がタグを壊す | タグは置き換えにし、文だけを訳に出す |
| 画像の中に文字を書く | 言語ごとに画像を作り直す | 文字は HTML のテキストで重ねる |
同じ英語でも意味が違う語には、gettext の pgettext("menu", "Open") のように文脈(msgctxt)を付けると、.po では別の項目として訳せます。
複数形のルール(日本語1つ・英語2つ・ポーランド語3つ・アラビア語6つ)
この節では、言語によって複数形の数が違うことと、それを実際に数で確かめた結果が分かります。
日本語は「1件」も「3件」も同じ形ですが、英語は2通り、ポーランド語は3通り(数え方の分類は4つ)、アラビア語は6通りに変わります。 どの数でどの形になるかは Unicode CLDR が言語ごとに決めていて、JavaScript の Intl.PluralRules で確かめられます。Node.js 22.23.4(CLDR 48.0)で数ごとの分類を出しました。
for (const loc of ["ja","en","fr","pl","ru","ar"]) {
const pr = new Intl.PluralRules(loc);
console.log(loc, pr.resolvedOptions().pluralCategories.join(","),
[0,1,2,3,5,11,21,22,100,1.5].map(n => n + ":" + pr.select(n)).join(" "));
}
ja other 0:other 1:other 2:other 3:other 5:other 11:other 21:other 22:other 100:other 1.5:other
en one,other 0:other 1:one 2:other 3:other 5:other 11:other 21:other 22:other 100:other 1.5:other
fr many,one,other 0:one 1:one 2:other 3:other 5:other 11:other 21:other 22:other 100:other 1.5:one
pl few,many,one,other 0:many 1:one 2:few 3:few 5:many 11:many 21:many 22:few 100:many 1.5:other
ru few,many,one,other 0:many 1:one 2:few 3:few 5:many 11:many 21:one 22:few 100:many 1.5:other
ar few,many,one,two,zero,other 0:zero 1:one 2:two 3:few 5:few 11:many 21:many 22:many 100:other 1.5:other
| 言語 | 分類の数 | 手元で分かったこと |
|---|---|---|
| 日本語 | 1(other) | どの数でも同じ形。「{n}件」1つで済む |
| 英語 | 2(one・other) | 0 は other(0 messages) |
| フランス語 | 3(one・many・other) | 0 と 1.5 も one(単数)。英語と違う |
| ポーランド語 | 4(one・few・many・other) | 22 は few、21 は many。末尾の数字で決まる |
| ロシア語 | 4 | 21 は one(単数の形)。ポーランド語とも違う |
| アラビア語 | 6(zero・one・two・few・many・other) | 0・1・2 がそれぞれ別の形 |
gettext で作ったポーランド語の翻訳に、いろいろな数を渡した結果です。.po の Plural-Forms の式どおりに3つの形が選ばれました。
0 Masz 0 nowych wiadomości.
1 Masz 1 nową wiadomość.
2 Masz 2 nowe wiadomości.
4 Masz 4 nowe wiadomości.
5 Masz 5 nowych wiadomości.
12 Masz 12 nowych wiadomości.
21 Masz 21 nowych wiadomości.
22 Masz 22 nowe wiadomości.
112 Masz 112 nowych wiadomości.
訳の無い複数形が「1 new messages」になった
アラビア語の .po を作り、複数形の項目を訳さないまま compile して実行すると、次のようになりました。
$ python app.py ar
Settings
Hello, Sato!
You have 0 new message.
You have 1 new messages.
You have 3 new messages.
.mo の中身を読むと、Babel 2.18.0 の compile は、訳が空の複数形を「0番=msgid(単数)、1〜5番=msgid_plural(複数)」で埋めていました。アラビア語の式では 0 件が0番、1 件が1番を選ぶため、英語として誤った文が出ます。複数形を持つ言語の翻訳ファイルを作ったら、訳が埋まるまでその言語を公開しないか、言語の一覧から外しておきます。
i18next・vue-i18n・FormatJS など JavaScript のライブラリの多くは、この CLDR の分類(one・few・many など)をキーにして訳を書く形です。ICU MessageFormat の書き方では {count, plural, one {# message} other {# messages}} のように1つの文字列の中に分岐を書きます。
日付・数値・通貨の書式を地域ごとに出す(BabelとIntlの実行結果)
この節では、同じ日付・数値・金額が地域ごとにどう表示されるかと、Python の Babel と JavaScript の Intl で結果が違った点が分かります。
日付や金額の形は、自分で文字列を組み立てず、ライブラリにロケールを渡して作らせます。 2026年10月29日 15:30(日本時間)、1234567.891、1234.5 円・ユーロ、0.256 を7つのロケールで出しました。
from babel.dates import format_date
from babel.numbers import format_decimal, format_currency, format_percent, format_compact_decimal
for loc in ["ja_JP", "en_US", "en_GB", "de_DE", "fr_FR", "ar_EG", "hi_IN"]:
print(loc, format_date(d, "medium", locale=loc), format_decimal(1234567.891, locale=loc),
format_currency(1234.5, "JPY", locale=loc), format_currency(1234.5, "EUR", locale=loc),
format_percent(0.256, locale=loc), format_compact_decimal(12345678, locale=loc))
Babel 2.18.0(CLDR 47)の結果です。
| ロケール | 日付(medium) | 数値 | 円/ユーロ | 12345678 の短縮 |
|---|---|---|---|---|
| ja_JP | 2026/10/29 | 1,234,567.891 | ¥1,234/€1,234.50 | 1235万 |
| en_US | Oct 29, 2026 | 1,234,567.891 | ¥1,234/€1,234.50 | 12M |
| en_GB | 29 Oct 2026 | 1,234,567.891 | JP¥1,234/€1,234.50 | 12M |
| de_DE | 29.10.2026 | 1.234.567,891 | 1.234 ¥/1.234,50 € | 12 Mio. |
| fr_FR | 29 oct. 2026 | 1 234 567,891 | 1 234 JPY/1 234,50 € | 12 M |
| ar_EG | 29/10/2026 | 1,234,567.891 | 1,234 JP¥/1,234.50 € | 12 مليون |
| hi_IN | 29 अक्टू॰ 2026 | 12,34,567.891 | JP¥1,234/€1,234.50 | 1 क॰ |
同じ値を Node.js 22.23.4 の Intl(ICU 78.3・CLDR 48.0)で出した結果です。
const d = new Date(Date.UTC(2026, 9, 29, 6, 30));
new Intl.DateTimeFormat(loc, { dateStyle: "medium", timeZone: "Asia/Tokyo" }).format(d);
new Intl.NumberFormat(loc).format(1234567.891);
new Intl.NumberFormat(loc, { style: "currency", currency: "JPY" }).format(1234.5);
new Intl.RelativeTimeFormat(loc, { numeric: "auto" }).format(-1, "day");
new Intl.ListFormat(loc).format(["A", "B", "C"]);
ja-JP | 2026/10/29 | 1,234,567.891 | ¥1,235 | €1,234.50 | 26% | 1235万 | 昨日 | A、B、C
en-US | Oct 29, 2026 | 1,234,567.891 | ¥1,235 | €1,234.50 | 26% | 12M | yesterday | A, B, and C
en-GB | 29 Oct 2026 | 1,234,567.891 | JP¥1,235 | €1,234.50 | 26% | 12m | yesterday | A, B and C
de-DE | 29.10.2026 | 1.234.567,891 | 1.235 ¥ | 1.234,50 € | 26 % | 12 Mio. | gestern | A, B und C
fr-FR | 29 oct. 2026 | 1 234 567,891 | 1 235 JPY | 1 234,50 € | 26 % | 12 M | hier | A, B et C
ar-EG | ٢٩/١٠/٢٠٢٦ | ١٬٢٣٤٬٥٦٧٫٨٩١ | ١٬٢٣٥ JP¥ | ١٬٢٣٤٫٥٠ € | ٢٦٪ | ١٢ مليون | أمس | A وB وC
hi-IN | 29 अक्टू॰ 2026 | 12,34,567.891 | JP¥1,235 | €1,234.50 | 26% | 1.2 क॰ | कल | A, B, और C
1円の端数の丸めが違った
1234.5 円は Babel で ¥1,234、Intl で ¥1,235。Babel は偶数への丸め(1235.5 は ¥1,236)、Intl は四捨五入(halfExpand)が既定。Intl に roundingMode: "halfEven" を渡すと ¥1,234 になった。
2アラビア語の数字が違った
ar-EG の既定の数字は、Intl ではアラビア・インド数字(١٬٢٣٤)、Babel では 0〜9 だった。Intl で 0〜9 にするには ar-EG-u-nu-latn と書く。
3区切りの空白は普通の空白ではない
fr_FR の「1 234 567,891」の空白は U+202F(狭い改行しない空白)だった。テストで "1 234" と普通の空白で比べると一致しない。
4インドは2桁ごとに区切る
hi_IN は 12,34,567.891。3桁ごとの区切りを前提にした入力チェックや表示幅の計算は合わない。
サーバー(Python)とブラウザ(JavaScript)で同じ金額を出すと、ライブラリと CLDR の版の違いで表示が一致しないことがあります。 金額の計算と丸めはどちらか一方で行い、もう一方は表示だけにすると食い違いを避けられます。
タイムゾーン・暦・並べ替え
new Intl.DateTimeFormat("ja-JP-u-ca-japanese", {dateStyle:"long", timeZone:"Asia/Tokyo"}) → 令和8年10月29日
同じ時刻を timeZone: "America/New_York"(en-US) → Oct 29, 2026, 2:30 AM
同じ時刻を timeZone: "Asia/Tokyo"(ja-JP) → 2026/10/29 15:30
["Äpfel","Zebra","Apfel","Österreich","Ofen"].sort() → Apfel,Ofen,Zebra,Äpfel,Österreich
同じ配列を .sort(new Intl.Collator("de").compare) → Apfel,Äpfel,Ofen,Österreich,Zebra
同じ配列を .sort(new Intl.Collator("sv").compare) → Apfel,Ofen,Zebra,Äpfel,Österreich
new Intl.DisplayNames(["ja"], {type:"language"}).of("de") → ドイツ語
- 時刻は UTC で保存し、表示のときに利用者のタイムゾーンで変換します。 ロケール(言語)とタイムゾーンは別の設定で、日本語を使う人が米国にいることもあります。
sort()だけでは文字コードの順になり、Ä が Z より後ろに来ました。ドイツ語の辞書順では A の近く、スウェーデン語では Z の後ろが正しい並びで、Collator はその違いを再現しました。- 和暦は
-u-ca-japaneseを付けると Intl が出します。元号の変わり目もライブラリのデータに任せます。
Intl の書式を目的別に引ける形は、関連記事「JavaScript Intlの使い方|数値・通貨・日付フォーマット逆引き一覧」で扱っています。
言語の決め方(Accept-Languageの交渉と多言語サイトのURL)
この節では、サーバーが利用者の言語を選ぶ仕組みを実際に動かした結果と、多言語サイトの URL の決め方が分かります。
ブラウザは、設定した言語の順番を Accept-Language ヘッダーで送ります。 例えば ja,en-US;q=0.9,en;q=0.8 は「日本語が第一、次に米国英語、次に英語」という意味です。サーバーは、自分が対応している言語の中から最も合うものを選びます(言語の交渉)。
対応言語を ja・pl・en にした自作サーバーを 127.0.0.1 に立て、Babel の negotiate_locale で選ぶようにしました。地域付きの名前(pl-PL など)で合わなければ、言語の部分(pl)で選び直します。
$ curl --noproxy '*' -i -H 'Accept-Language: ja,en-US;q=0.9,en;q=0.8' http://127.0.0.1:48197/
Content-Language: ja
Vary: Accept-Language
設定 / 2026年10月29日
| 送った Accept-Language | 選ばれた言語 | 本文 |
|---|---|---|
| ja,en-US;q=0.9,en;q=0.8 | ja | 設定 / 2026年10月29日 |
| en-US,en;q=0.9 | en | Settings / October 29, 2026 |
| pl-PL,pl;q=0.9 | pl | Ustawienia / 29 października 2026 |
| de-DE,de;q=0.9(未対応) | en(既定) | Settings / October 29, 2026 |
| fr-CA,fr;q=0.9,ja;q=0.5 | ja(2番目の候補) | 設定 / 2026年10月29日 |
| (空) | en(既定) | Settings / October 29, 2026 |
ヘッダーで中身を変えるときは、応答に Vary: Accept-Language を付け、CDN やブラウザのキャッシュが別の言語の中身を返さないようにします。Content-Language は、返した中身の言語を伝えるヘッダーです。
Web サイトは言語ごとに URL を分ける
検索に出したい Web サイトでは、ヘッダーや Cookie だけで言語を切り替えず、言語ごとに別の URL を用意します。 Google 検索セントラル(2025年12月10日更新)は、Googlebot は主に米国から巡回し Accept-Language を送らないため、動的に切り替えると一部の版を見つけられない可能性があること、利用者の言語を推測して自動で別の言語の版へ転送するのは避け、ほかの言語の版へのリンクを置いて選べるようにすることを勧めています。言語の判定はページの表示内容で行い、lang 属性や URL は使わない、とも書いています。
| URL の形 | 例 | Google の説明の要点 |
|---|---|---|
| 国別ドメイン(ccTLD) | example.de | 国が明確。費用が高く、取れない場合がある |
| サブドメイン | de.example.com | 設定が簡単でサーバーを分けられる。de が言語か国か分かりにくい |
| サブディレクトリ | example.com/de/ | 設定が簡単で保守が楽。サーバーは1か所 |
| URL パラメータ | example.com?loc=de | 推奨しない |
言語の版どうしは hreflang で結びます。各ページの head に、自分自身も含めたすべての言語の版を書きます。
<link rel="alternate" hreflang="ja" href="https://example.com/ja/pricing/">
<link rel="alternate" hreflang="en" href="https://example.com/en/pricing/">
<link rel="alternate" hreflang="x-default" href="https://example.com/pricing/">
Vue や Angular のアプリで言語を切り替える具体的な手順は、関連記事「vue-i18nの使い方|useI18nで多言語対応と言語切り替え」と関連記事「Angular多言語対応(i18n)の手順|$localizeと翻訳ファイル」で扱っています。
RTL(アラビア語・ヘブライ語)の表示をChromiumで確かめる
この節では、右から左に書く言語で画面がどう変わるかと、崩れやすい場所の直し方が、Chromium で測った位置で分かります。
アラビア語やヘブライ語は右から左(RTL)に書きます。HTML では <html lang="ar" dir="rtl"> と書くと、文字の向きだけでなく、行の揃え・flex の並び・論理プロパティの向きが入れ替わります。 dir="rtl" のページを 127.0.0.1 で開き、各文字の画面上の位置(左からの順)を測りました。
<html lang="ar" dir="rtl">
<p id="p1" lang="en">Hello, world!</p>
<p id="p2" dir="auto" lang="en">Hello, world!</p>
<p id="p3" dir="ltr">User <span>إيمان</span>: 3 posts.</p>
<p id="p4" dir="ltr">User <bdi>إيمان</bdi>: 3 posts.</p>
| 書いたもの | 画面での並び(左から) | 読み取れること |
|---|---|---|
| RTL の中の英文 Hello, world! | !Hello, world(右寄せ) | 文末の「!」が左端に移った |
同じ英文に dir="auto" |
Hello, world!(左寄せ) | 最初の強い文字(H)で向きが ltr に決まった |
| 英文の中のアラビア語の名前(span) | User 3 :(名前) posts. | 数字の 3 とコロンが名前の側に吸い寄せられた |
同じ名前を <bdi> で囲む |
User (名前): 3 posts. | 名前を周りから切り離し、正しい順に戻った |
利用者の名前・商品名・検索語など、中身の言語が分からない値を文に差し込むときは <bdi> で囲むか dir="auto" を付けます。 MDN は、空白や区切り文字のような向きを持たない文字は周りの文字に従って向きが決まるため、RTL の文字列を LTR の文に埋め込むと、続く「- 1」のような部分まで RTL として扱われて表示が乱れる、と説明しています。手元の「User 3 :名前 posts.」は、その崩れを Chromium で再現したものです。<bdi> の dir の初期値は auto で、親から受け継ぎません。
margin-left ではなく margin-inline-start
幅 300px の枠の中に、margin-left: 40px と margin-inline-start: 40px の要素を置き、RTL のページで余白の位置を測りました。
| CSS | 左の余白 | 右の余白 | RTL での見え方 |
|---|---|---|---|
margin-left: 40px |
45px | 5px | 文の終わり側に余白が残った |
margin-inline-start: 40px |
5px | 45px | 文の始まり(右)に余白が付いた |
display: flex に 1・2・3 |
1 が右端(x=965)、3 が左(x=944) | 並びが自動で逆になった | |
| アラビア語の段落 | text-align は start、右寄せ | left を指定しなければ揃えも入れ替わる | |
(5px は枠の padding です。)left・right で書いた余白・位置・角丸・矢印のアイコンは RTL で反対になりません。margin-inline-start・padding-inline-end・inset-inline-start・text-align: start のような論理プロパティで書くと、dir に合わせて自動で入れ替わります。「戻る」「次へ」の矢印のように向きに意味があるアイコンは、RTL では左右を反転させます。
画面の伸び縮み:ドイツ語で何割長くなるかを実測
この節では、同じボタンを6つの言語で表示したときの幅と、固定の幅で文字が切れたボタンが分かります。
英語から訳すと、ドイツ語・フランス語・スペイン語は3〜4割長くなり、短い語ほど伸び方が大きくなりました。 6つのボタン(Settings・Save・Cancel・Download・Sign in・Show password)を、16px の同じ書体(欧文 DejaVu Sans、和文 Noto Sans CJK JP)で並べ、Chromium で幅を測りました。訳語は画面の部品として一般的な語を選びました。
| 言語 | 6つのボタンの幅(px) | 合計 | 英語との比 | 幅110pxで切れたボタン |
|---|---|---|---|---|
| 英語 | 94・67・82・108・83・152 | 586 | 1.00 | Show password |
| 日本語 | 60・60・108・124・92・156 | 600 | 1.02 | ダウンロード・パスワードを表示 |
| ドイツ語 | 136・108・114・144・109・177 | 788 | 1.34 | Einstellungen・Abbrechen・Herunterladen・Passwort anzeigen |
| フランス語 | 119・116・90・121・133・223 | 802 | 1.37 | 5つ(Annuler 以外すべて) |
| スペイン語 | 139・93・99・110・133・184 | 758 | 1.29 | Configuración・Iniciar sesión・Mostrar contraseña |
| フィンランド語 | 107・94・90・73・150・151 | 665 | 1.13 | Kirjaudu sisään・Näytä salasana |
W3C の記事「Text size in translation」は、IBM の指針を引いて、英語の原文が10文字以下なら訳文は平均で200〜300%、70文字を超えると130%程度になる、と紹介しています。手元の測定でも、短い語(Settings)のほうが文(Show password)より伸び方の差が大きく出ました。日本語は文字数は少なくても全角で幅を取るため、英語とほぼ同じ幅でした。
- ボタンやタブは
widthを固定せず、min-widthとpaddingで作る。 - どうしても幅が決まっている場所は、省略(text-overflow)ではなく折り返しを許し、高さが伸びても崩れない作りにする。
- 文字を画像にしない。ボタンの文字を画像にすると、言語ごとに作り直しになる。
疑似ローカライズで、訳す前に崩れを見つける
訳が揃う前に画面の崩れを探す方法として、英語を「アクセント付きの文字に変え、4割伸ばした文字」に置き換えて表示する疑似ローカライズがあります。置き換え部分({name})は変えないのがこつです。
M = str.maketrans("aceinoruyAEIOU", "àçéîñöŕüýÀÉÎÖÜ")
def pseudo(s):
parts = re.split(r"(\{[^}]*\})", s) # 置き換え部分は変えない
body = "".join(p if p.startswith("{") else p.translate(M) for p in parts)
return f"[{body}{'~' * max(1, round(len(s) * 0.4))}]"
'Settings' -> [Séttîñgs~~~]
'Hello, {name}!' -> [Héllö, {name}!~~~~~~]
'You have {n} new messages.' -> [Yöü hàvé {n} ñéw méssàgés.~~~~~~~~~~]
'Show password' -> [Shöw pàsswöŕd~~~~~]
画面に [ と ] が両方見えなければ文字が切れている、アクセントの無い英語が残っていれば _() で囲み忘れた文字列(ハードコード)だ、と目で見て分かります。
文字コード・全角半角・文字数の数え方
この節では、多言語の文字を扱うときに「文字数」「バイト数」「見た目の幅」がずれる例が、実行結果で分かります。
国際化の前提は、内部の文字コードを UTF-8(Unicode)にそろえることです。 そのうえで、「1文字」の数え方が場面ごとに違う点に気をつけます。Node.js の length(UTF-16 の単位)、コードポイント数、見た目の1文字(書記素。Intl.Segmenter)、UTF-8 のバイト数を比べました。
| 文字列 | length | コードポイント | 見た目の文字数 | UTF-8 バイト |
|---|---|---|---|---|
| ABC | 3 | 3 | 3 | 3 |
| ABC(全角) | 3 | 3 | 3 | 9 |
| ガギ(半角カナ) | 4 | 4 | 2 | 12 |
| 𠮷野家 | 4 | 3 | 3 | 10 |
| 👨👩👧(家族の絵文字) | 8 | 5 | 1 | 18 |
- 「20文字まで」の入力制限を
lengthで作ると、絵文字1つが8文字と数えられます。 利用者に見える文字数で制限するなら書記素で数え、DB の列の大きさはバイトで考えます。 - NFKC で正規化すると、全角英字「ABC」は「ABC」に、半角カナ「ガギ」は「ガギ」に、「㍿」は「株式会社」になりました(Python の unicodedata)。検索や重複の判定の前にそろえると、全角・半角の違いで見落とすことが減ります。
- 「か」+濁点(U+3099)の2文字は、見た目は「が」でも
===で一致せず、NFC にすると一致しました。macOS から来たファイル名で起きやすい違いです。 - 「𠮷」を Shift_JIS(cp932)に変換すると
UnicodeEncodeErrorになりました。CSV などを Shift_JIS で出す連携では、出せない文字の扱いを決めておきます。 - 大文字・小文字の変換も言語で変わります。
"title".toLocaleUpperCase("tr")はトルコ語の規則で「TİTLE」になりました。
全角・半角を判定する East Asian Width(Python の unicodedata.east_asian_width)は、ABCが F(全角)、ガ が H(半角)、ABC が Na(狭い)でした。等幅の画面で桁をそろえる処理に使えますが、表示幅は書体で変わるため、Web 画面の幅は CSS に任せます。文字化けの原因の切り分けは、関連記事「文字化けの原因8選|濁点分離・utf8mb4・絵文字の文字数」で扱っています。
ローカライズの工程と翻訳ファイルの更新(fuzzyの落とし穴)
この節では、アプリやゲームのローカライズが一般にどの順で進むかと、ソースの文言を変えたときに翻訳ファイルで起きることが分かります。
ローカライズは「訳して終わり」ではなく、訳した画面を、その言語の人が実際に使う形で確かめるところまでが1回分の工程です。 一般的な流れを並べます。
- 1対象の言語・地域と範囲を決める全画面か、登録・購入などの主な流れだけか
- 2国際化の点検文字列の外出し・書式・複数形・RTL・画面の伸び(疑似ローカライズ)
- 3用語集とスタイルガイドを作る製品名・機能名の訳、敬体か常体か、訳さない語
- 4翻訳(機械翻訳+人の確認を含む)画面の文脈(スクリーンショット・文字数の上限)を訳者に渡す
- 5組み込みとビルド.po を compile、JSON を読み込む
- 6LQA(言語の品質確認)実際の画面で、切れ・誤訳・不自然さ・文化的な問題を見る
- 7公開と、その後の文言変更の追従新しい文言・変わった文言だけを訳に回す
ゲームでは、台詞や固有名詞、ジョーク、文化に関わる表現が多く、LQA でプレイしながら確かめる工程が大きくなるのが一般的です。法令・料金・安全に関わる文は、機械翻訳だけで出さず、人が確かめる範囲に入れます。
ソースの文言を変えると訳は「fuzzy」になり、表示されなくなる
app.py の "Hello, {name}!" を "Hello, {name}! Welcome back." に変えて、extract → update → compile しました。
$ pybabel update -i app.pot -d locales -D app -l ja
updating catalog locales/ja/LC_MESSAGES/app.po based on app.pot
#: app.py:10
#, fuzzy, python-brace-format
msgid "Hello, {name}! Welcome back."
msgstr "こんにちは、{name}さん!"
$ pybabel compile -d locales -D app -l ja --statistics
5 of 5 messages (100%) translated in locales/ja/LC_MESSAGES/app.po
$ python app.py ja
設定
Hello, Sato! Welcome back.
update は、似た前の訳を引き継いで fuzzy(要確認)の印を付けます。compile は既定で fuzzy の訳を .mo に入れないため、画面には英語が出ました。 統計は「100% translated」と出ていて、数字だけでは気づけません。-f(--use-fuzzy)を付けると前の訳「こんにちは、Satoさん!」が出ましたが、新しい文の後半が訳されていない状態です。fuzzy の印は、訳者が確かめてから外します。
- 1文言を変えたら extract と update をセットで回すCI で pot を作り直し、差分があれば知らせる
- 2fuzzy と空の msgstr を数える--statistics の「100%」だけで判断しない。grep で fuzzy を探す
- 3置き換えの名前が原文と同じか確かめる{name} の綴り違いは compile で止まらなかった(次の節)
- 4その言語の画面で一度は見る切れ・RTL・複数形は、ファイルだけでは分からない
Java の messages.properties で同じことをする方法は関連記事「Spring Bootの多言語対応|messages.propertiesと切り替え」で、DB に入る商品名などの翻訳の持ち方は関連記事「多言語対応のDB設計パターン比較|翻訳テーブルのDDLと選び方」で扱っています。
多言語対応のよくあるエラー・つまずき(原文つき)
この節では、手元で実際に出たエラーや誤表示と、その原因・直し方が一覧で分かります。
| 出たもの(原文) | 原因 | 直し方 |
|---|---|---|
KeyError: 'nmae' |
訳文の {name} を {nmae} と書き間違えた。compile は通った |
置き換えの名前を原文とそろえる。CI で訳文の置き換えを検査する |
error: …x.po:6: unknown named placeholder 'cnt'/1 errors encountered. |
%(count)d の訳を %(cnt)d にした(python-format は compile が検査した) |
名前を原文と同じにする |
| 訳を書いたのに英語が出る(エラーなし) | fuzzy の訳は compile が .mo に入れない | 確かめて fuzzy を外す |
You have 1 new messages. |
訳の無い複数形を、アラビア語の式で選んだ | 訳が揃うまでその言語を出さない |
FileNotFoundError: [Errno 2] No translation file found for domain: 'app' |
.mo が無い(compile 忘れ・localedir 違い・言語名違い) | compile する。fallback=True で原文に戻す |
WARNING: msg has more translations than num_plurals of catalog |
日本語(複数形1つ)の .po を、2つの msgstr を持つ雛形から作った | msgstr[0] に訳を書けば消える |
ValueError expected only letters, got 'ja-jp' |
Babel の Locale.parse は既定で ja_JP の形を求める |
Locale.parse("ja-JP", sep="-") |
UnknownLocaleError: unknown locale 'jp' |
jp は国のコード。日本語の言語コードは ja | ja/ja_JP と書く |
RangeError: Incorrect locale information provided |
Intl に ja_JP(アンダースコア)を渡した |
Intl は ja-JP(ハイフン) |
RangeError: Invalid currency code : JP |
通貨コードは3文字 | JPY |
YEN 1,234.50(エラーにならない) |
形の正しい3文字なら、存在しない通貨でもそのまま出る | ISO 4217 の JPY を使う。値を検査する |
UnsupportedNumberingSystemError: Unknown numbering system native for Locale ar_EG. |
Babel に存在しない数字の体系名を渡した | numbering_system="arab" など実在の名前 |
「1 errors encountered.」のように、ツールの出力そのものが単数・複数を区別していないことがあります。英語の原文でも複数形を正しく扱うと、ローカライズのときに訳者が迷いません。
どこまで国際化・ローカライズするか(判断表)
この節では、自分のプロダクトで何から手を付けるか、どこまでやるかを決める目安が分かります。
国際化(文字列の外出しと書式のライブラリ任せ)は、作り始めに入れるほど安く済みます。ローカライズ(言語を足す)は、その言語の利用者・売上・問い合わせを見て決めます。
| 状況 | やること | 理由 |
|---|---|---|
| 国内向けで、海外の予定が無い | UTF-8・UTC 保存・書式はライブラリ任せ、までは入れる | 後から多言語にするときの作り直しが小さい |
| 1年以内に英語版を出す可能性がある | 最初から _() などで文字列を外に出す |
後から全画面の文字列を探して外に出すのは大仕事 |
| 英語版を出す | 主な流れ(登録・購入・問い合わせ)から訳す。LQA を入れる | 全画面を一度に訳すより早く価値が出る |
| 欧州の言語を足す | 画面の伸び(3〜4割)・小数点のカンマ・複数形を点検 | 手元の測定でボタンの多くが切れた |
| アラビア語・ヘブライ語を足す | 論理プロパティ・bdi・アイコンの反転を先に入れる | CSS の left/right を全部見直すことになる |
| 検索から集客したい Web サイト | 言語ごとの URL と hreflang。自動転送はしない | Google 検索セントラルの推奨 |
| 社内向けの管理画面 | 表示言語の切り替えだけ。書式はロケール任せ | 利用者が限られ、法令や文化の対応が小さい |
| 海外からの利用がごく少ない | ローカライズは見送り、国際化だけ保つ | 訳の更新を続ける費用のほうが大きい |
言語・フレームワーク別の道具
| 環境 | 文字列の外出し | 書式・複数形 |
|---|---|---|
| Python | gettext(.po/.mo)+pybabel、Django の翻訳 | Babel |
| JavaScript(ブラウザ・Node) | i18next・vue-i18n・Angular の $localize など | Intl(標準) |
| Java・Spring | messages.properties(ResourceBundle・MessageSource) | java.text・java.time の書式 |
| PHP・WordPress | gettext(__()・.po/.mo) |
intl 拡張 |
| Android | res/values-(言語)/strings.xml | plurals リソース |
よくある質問
Q. ローカライズとは、簡単に言うと何ですか? A. 製品やアプリを、特定の国・地域の人が自然に使えるように作り替えることです。言葉の翻訳に加え、日付・通貨・単位の表記、画像、法令への対応まで含みます。英語では localization、略して l10n と書きます。
Q. ローカライズと翻訳の違いは何ですか? A. 翻訳は文章を別の言語に置き換えることで、ローカライズの作業の一部です。ローカライズは、翻訳した文が画面に収まるか、日付や金額がその国の形か、画像や色に文化的な問題が無いかまで扱います。
Q. i18n は何と読みますか? A. 「アイ・エイティーン・エヌ」と読むのが一般的で、internationalization(国際化)の略です。最初の i と最後の n の間に18文字あるため、こう書きます。localization は l10n、globalization は g11n です。
Q. 国際化とローカライズの違いは何ですか? A. 国際化は、どの言語・地域にも合わせられるように作りを整えること(文字列を外に出す、書式をライブラリに任せる、RTL に耐える画面にする)です。ローカライズは、その作りの上で、特定の言語・地域向けの中身(訳・書式・画像)を入れることです。国際化は1回、ローカライズは言語の数だけ行います。
Q. 多言語化(m17n)とは違いますか? A. 多言語化は、1つのプログラムが複数の言語や文字を同時に扱えるようにすることを指します。国際化の一部、または近い意味で使われます。
Q. 機械翻訳だけで多言語対応してよいですか? A. 画面の短い語は文脈が無いため、機械翻訳では誤訳が起きやすくなります。料金・法令・安全に関わる文は人が確かめる範囲に入れ、LQA で実際の画面を見ます。
Q. ブラウザの言語設定で自動的に言語を切り替えてよいですか? A. アプリの画面なら Accept-Language で初期の言語を選ぶのは一般的です。検索に出したい Web サイトでは、Google 検索セントラルが言語の推測による自動転送を避け、言語ごとの URL と、ほかの言語へのリンクを置くことを勧めています。
Q. 翻訳ファイルの形式はどれを選べばよいですか? A. 使うフレームワークの標準に合わせるのが近道です。Python・PHP・C なら gettext の .po、JavaScript のライブラリは JSON、Java は properties、Android は strings.xml が標準です。翻訳会社や翻訳管理サービスの多くは、これらの形式を読み書きできます。
Q. 日付や通貨は自分で書式を組み立ててはいけませんか? A. 組み立てると、地域ごとの順番・区切り・通貨記号の位置を全部自分で持つことになります。手元では、ドイツ語は「1.234,50 €」、フランス語は狭い空白で区切り、インドは2桁ごとに区切りました。ライブラリ(Babel・Intl)にロケールを渡して作らせます。
Q. RTL 対応は何から始めればよいですか?
A. html 要素に dir="rtl" を付けて画面を開き、崩れた場所を見ます。CSS の left/right を論理プロパティ(margin-inline-start など)に置き換え、利用者の入力を差し込む場所を <bdi> で囲み、向きのあるアイコンを反転させます。
Q. 日本のサービスを海外向けにするとき、最初に何をしますか? A. 文字列がコードに直接書かれている場所を探し、翻訳ファイルへ出すことから始めます。並行して、時刻を UTC で保存しているか、金額や日付を自分で組み立てていないかを点検します。そのうえで、対象の言語と画面の範囲を決めて訳に回します。
まとめ
- ローカライズ(l10n)は特定の言語・地域向けに作り替えること、国際化(i18n)はその前に「訳せる作り」にすること。翻訳はローカライズの一部。
- gettext では
_()で囲んだ文字列を extract → init → 訳す → compile の順で .mo にした。ファイルの無い言語は原文に戻り、文言を変えた訳は fuzzy になって表示されなかった。 - 文字列を
+でつなぐと語順を直せない(手元で「削除 ファイル?」)。複数形は日本語1つ・英語2つ・ポーランド語3通り・アラビア語6つで、ngettext や Intl.PluralRules に任せる。 - Babel と Intl では、円の端数の丸め(¥1,234 と ¥1,235)やアラビア語の数字が違った。計算と丸めは一方で行い、時刻は UTC で保存する。
- Chromium の実測で、ドイツ語・フランス語のボタンは英語の1.3〜1.4倍に伸び、RTL では文末の「!」や差し込んだ名前の位置が崩れた。幅を固定せず、論理プロパティと bdi で作る。
次に読むとよい記事です。画面の言語切り替えは関連記事「vue-i18nの使い方|useI18nで多言語対応と言語切り替え」で、Intl の書式は関連記事「JavaScript Intlの使い方|数値・通貨・日付フォーマット逆引き一覧」で扱っています。
参考・出典
確認日はいずれも 2026年10月29日です。
- W3C「Localization vs. Internationalization」:https://www.w3.org/International/questions/qa-i18n
- W3C「Text size in translation」:https://www.w3.org/International/articles/article-text-size
- MDN「国際化対応 (Internationalization)」:https://developer.mozilla.org/ja/docs/Glossary/Internationalization_and_localization
- MDN「Intl」:https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Intl
- MDN「<bdi> 書字方向分離要素」:https://developer.mozilla.org/ja/docs/Web/HTML/Reference/Elements/bdi
- MDN「CSS 論理的プロパティと値」:https://developer.mozilla.org/ja/docs/Web/CSS/CSS_logical_properties_and_values
- Google 検索セントラル「多地域、多言語のサイトの管理」:https://developers.google.com/search/docs/specialty/international/managing-multi-regional-sites
- Python ドキュメント「gettext --- 多言語国際化サービス」:https://docs.python.org/ja/3/library/gettext.html
- Babel ドキュメント「Command-Line Interface」:https://babel.pocoo.org/en/latest/cmdline.html
- Unicode CLDR「Language Plural Rules」:https://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html
- IETF「BCP 47 Tags for Identifying Languages」:https://www.rfc-editor.org/info/bcp47
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「ローカライズとは?i18n・l10nの違いと多言語対応の作り方を実行例で」
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。