プログラマがシステム開発において共通で必要となる、技術と業務の狭間の共通知識を解説します。連載第4回は国際化編です。
0. 今回の概要
システム開発で必要となる標準規格の話、第4回は国際化編です。
グローバルな環境で利用される、あるいはグローバルな情報を扱うシステムを構築する際に参考になりそうな話題を取り上げます。
1. 国際化と地域化
ソフトウェアを特定の言語や地域に適合させる工程を「地域化 (localization, L10N)」と言います。しかし、多くの言語や地域に対して個別に地域化を行うと、開発や保守に多くの時間と費用がかかるため、ソフトウェア本体に変更を加えることなく多様な言語や地域に適合できるように予め設計しておく手法が生まれました。これを「国際化 (internatioalization, i18n)」と言います。
L10N、i18n は最初と最後の文字とその間の文字数を使った略語です。L10N が大文字、i18n が小文字なのは、小文字の l (エル)、大文字の I (アイ)、数字の 1 が紛らわしいためです。
国際化では一般に以下の項目が対象となります。
- テキスト処理
- 文字コード (文字集合、符号化方式)
- テキスト出力 (方向性、文言、フォント)
- テキスト入力
- 日付・時刻
- 暦
- タイムゾーン
- 日付・時刻の書式
- 数値
- 数値の書式
- 通貨情報
今回は言語・地域の識別、テキスト出力を主に扱います。文字コードについては、連載第1回、第2回を参照してください。日付・時刻、数値については、長くなりそうなので次回へ回します。
2. ロケール
ソフトウェアの国際化・地域化にあたって、対象となる言語・地域の単位を定義する必要があります。これを「ロケール (ロカール, locale)」といいます。
2-1. IETF 言語タグ
ロケールの識別子として、インターネット関連や Java 7 以降では、IETF (Internet Engineering Task Force) の BCP 47 という仕様が使われます。現在、BCP 47 は RFC 5646「Tags for Identifying Languages」、RFC 4647「Matching of Language Tags」の2つの文書で構成されています。RFC は更新時に新たな番号が振られるため、番号が変わらない BCP 47 を一連の RFC を取りまとめる名前として使用します。BCP は RFC の分類の1つで、Best Current Practice (現時点における最良の方法) を意味します。Language Tag を「言語タグ」と訳します。
制定年 | 規格 | 概要 |
---|---|---|
1995年 | ISO 639-1 とサブタグで構成。RFC 3066 により廃止。 | |
2001年 | ISO 639-2 を追加、数字が利用可能に。RFC 4646 により廃止。 | |
2006年 | 全面改訂。サブタグレジストリを整備。RFC 5646 により廃止。 | |
RFC 4647 | マッチングの仕様を分離。 | |
2009年 | RFC 5646 | 拡張言語を定義。RFC 3066 以前の基準外のタグを列挙。 |
言語タグは当初 en-US
のように language - subtag の最大2つの部分からなる構成でしたが、現在の RFC 5646 では language - script - region - variant - extension - privateuse の形式で書くことができます。このうち、language のみ必須です。language、script、region の各サブタグの値は、次章で説明する国際規格に基づいています。
サブタグ | 説明 | 派生元規格 |
---|---|---|
language | 言語 | ISO 639-1、ISO 639-2、ISO 639-3、ISO 639-5 |
script | 文字体系 (用字系) | ISO 15924 |
region | 地域 | ISO 3166-1 alpha-2、UN M.49 |
variant | 異体 (変種) | - |
extension | 拡張 | - |
privateuse | 私用 | - |
利用可能なサブタグは IANA (Internet Assigned Numbers Authority) の Language Subtag Registry で管理されています。
言語タグの例を以下に挙げます。
言語タグ | 説明 | 形式 |
---|---|---|
en | 英語 | language |
en-US | 英語 (米国) | language-region |
en-GB | 英語 (英国) | language-region |
es-005 | スペイン語 (南アメリカ) | language-region |
ja | 日本語 | language |
ja-Latn | 日本語 (ローマ字) | language-script |
ja-Latn-hepburn | 日本語 (ローマ字、ヘボン式) | language-script-variant |
jp-JP | 日本語 (日本) | language-region |
ja-JP-u-ca-japanese | 日本語 (日本、和暦) | language-region-extension |
zh | 中国語 | language |
zh-Hans | 中国語 (簡体字) | language-script |
zh-Hans-CN | 中国語 (簡体字、中国) | language-script-region |
zh-CN | 中国語 (中国) | language-region |
zh-Hant | 中国語 (繁体字) | language-script |
zh-Hant-TW | 中国語 (繁体字、台湾) | language-script-region |
zh-TW | 中国語 (台湾) | language-region |
大文字・小文字は区別されませんが、2番目以降の2文字のサブタグ (region など) はすべて大文字、2番目以降の4文字のサブタグ (script など) は先頭だけ大文字、それ以外のサブタグはすべて小文字が推奨されています。
言語タグの使用例としては、HTML 文書の属性で lang="ja"
のように使われたりします。
extension の例として、ja-JP-u-ca-japanese (和暦) を挙げていますが、詳細は「4-1. Unicode ロケール識別子」で説明します。
2-2. POSIX ロケール
Linux などの OS で指定する識別子です。language _ territory . codeset @ modifier の形式で表すことができます。
サブコード | 説明 | 派生元規格 |
---|---|---|
language | 言語 | ISO 639-1、ISO 639-2 |
territory | 地域 | ISO 3166-1 alpha-2 |
codeset | 文字コード | - |
modifier | 修飾子 | - |
POSIX ロケールの例を以下に挙げます。
ロケール | 説明 |
---|---|
C | デフォルト、英語 (米国、ASCII) |
POSIX | C と同じ |
en_US | 英語 (米国、ISO-8859-1) |
en_US.UTF-8 | 英語 (米国、UTF-8) |
fr_FR@euro | フランス語 (フランス、ユーロ、ISO-8859-15) |
ja_JP.EUC-JP | 日本語 (日本、EUC-JP) |
ja_JP.UTF-8 | 日本語 (日本、UTF-8) |
POSIX ロケールは、以下の環境変数に設定してシステムの動作を変更します。
環境変数 | 説明 |
---|---|
LANG | 一般的な言語設定 |
LANGUAGE | 翻訳の優先順位 (複数指定可) |
LC_CTYPE | 文字の種類、比較・分類 |
LC_COLLATE | 文字の照合や整列 |
LC_MESSAGES | メッセージの表示 |
LC_MONETARY | 通貨、金額の表示 |
LC_NUMERIC | 数値の表示 |
LC_TIME | 日付・時刻の表示 |
LC_ALL | すべての設定を上書き |
LANG より LC_*、LC_* より LC_ALL の設定が優先されます。
3. 国際化に関する国際規格
本章では、国際化に関する国際規格を紹介します。
ロケールの定義以外にも、グローバルな事象を扱うシステムで、データの保存・交換を行う際のキーの候補として検討するとよいと思います。
3-1. 言語
言語は ISO-639 シリーズで規定されており、日本では JIS X 0412 が対応します。
最初に2文字コードが規定されましたが、より多くの言語を表現するため3文字コードが追加されました。
制定年 | 規格 | 説明 |
---|---|---|
2002年 | ISO 639-1 (JIS X 0412-1) | 2文字コード、ISO 639:1988 を改訂 |
1998年 | ISO 639-2 (JIS X 0412-2) | 3文字コード (T: 用語学用、B: 書誌用) |
2007年 | ISO 639-3 | 3文字コードの拡張 (国際SIL) |
2008年 | ISO 639-5 | 3文字コード (言語グループ) |
2009年 | 4文字コード (言語変種)、2014年廃止 |
規定されているコードの例を以下に挙げます。
ISO 639-2 は用途によって一部異なったコードが振られています (太字)。また、mis (その他) や und (不明) のように特殊なコードが定義されています。日本関連では、ISO 639-2 でアイヌ語が、ISO 639-3 で沖縄語などの琉球諸語が追加されています。
(表は横にスクロールしてご覧ください)
言語 | 639-1 | 639-2/T | 639-2/B | 639-3 | 639-5 | Scope | Type | |
---|---|---|---|---|---|---|---|---|
英語 | en | eng | eng | eng | Individual | Living | ||
フランス語 | fr | fra | fre | fra | Individual | Living | ||
ドイツ語 | de | deu | ger | deu | Individual | Living | ||
イタリア語 | it | ita | ita | ita | Individual | Living | ||
スペイン語 | es | spa | spa | spa | Individual | Living | ||
ポルトガル語 | pt | por | por | por | Individual | Living | ||
ギリシア語 | el | ell | gre | ell | Individual | Living | ||
ロシア語 | ru | rus | rus | rus | Individual | Living | ||
インド・ヨーロッパ語族 | ine | ine | ine | Collective | ||||
アラビア語 | ar | ara | ara | ara | Macrolanguage | Living | ||
アフロ・アジア語族 | afa | afa | afa | Collective | ||||
アイヌ語 | ain | ain | ain | Individual | Living | |||
沖縄語 | ryu | Individual | Living | |||||
日本語 | ja | jpn | jpn | jpn | Individual | Living | ||
日本手話 | jsl | Individual | Living | |||||
中古日本語 | ojp | Individual | Historical | |||||
日琉語族 | jpx | Collective | ||||||
韓国語 | ko | kor | kor | kor | Individual | Living | ||
中国語 | zh | zho | chi | zho | Macrolanguage | Living | ||
シナ・チベット語族 | sit | sit | sit | Collective | ||||
その他の言語 | mis | mis | mis | Special | ||||
複数言語 | mul | mul | mul | Special | ||||
不明言語 | und | und | und | Special | ||||
言語ではない | zxx | zxx | zxx | Special |
3-2. 文字体系
文字体系は ISO 15924 で規定されています。
4文字のアルファベットと3桁の数字が定義されています。
規定されているコードの例を以下に挙げます。
文字 | alpha-4 | numeric | Property Value Alias |
---|---|---|---|
ラテン文字 | Latn | 215 | Latin |
ギリシア文字 | Grek | 200 | Greek |
キリル文字 | Cyrl | 220 | Cyrillic |
アラビア文字 | Arab | 160 | Arabic |
平仮名 | Hira | 410 | Hiragana |
片仮名 | Kana | 411 | Katakana |
仮名 (平仮名+片仮名) | Hrkt | 412 | Katakana_Or_Hiragana |
日本語 (漢字+平仮名+片仮名) | Jpan | 413 | |
ハングル | Hang | 286 | Hangul |
韓国語 (ハングル+漢字) | Kore | 287 | |
漢字 | Hani | 500 | Han |
漢字 (簡体字) | Hans | 501 | |
漢字 (繁体字) | Hant | 502 | |
絵文字 | Zsye | 993 | |
数学記号 | Zmth | 995 | |
シンボル | Zsym | 996 |
もし、通常の日本語の文書のほかにローマ字で記述した文書を作るケースがあれば、lang="ja-Latn"
のように指定することになるかと思います。
3-3. 国・地域
国・地域は ISO 3166-1 で規定されています。日本では JIS X 0304 が対応します。
2文字のアルファベット、3文字のアルファベット、3桁の数字の3種類のコードが定義されています。3桁の数字コードは国際連合統計部が定義する UN M.49 と同じコードです。
規定されているコードの例を以下に挙げます。
国・地域 | alpha-2 | alpha-3 | numeric (UN M.49) |
---|---|---|---|
アメリカ合衆国 (米国) | US | USA | 840 |
グアム | GU | GUM | 316 |
カナダ | CA | CAN | 124 |
イギリス (英国) | GB | GBR | 826 |
フランス | FR | FRA | 250 |
ドイツ | DE | DEU | 276 |
イタリア | IT | ITA | 380 |
スペイン | ES | ESP | 724 |
ポルトガル | PT | PRT | 620 |
ギリシャ | GR | GRC | 300 |
ロシア | RU | RUS | 643 |
エジプト | EG | EGY | 818 |
日本 | JP | JPN | 392 |
大韓民国 (韓国) | KR | KOR | 410 |
中華人民共和国 (中国) | CN | CHN | 156 |
香港 | HK | HKG | 344 |
台湾 | TW | TWN | 158 |
南極 | AQ | ATA | 010 |
ISO 3166-2 は各国の行政区画名を規定するコードです。
グアム、香港、台湾などは ISO 3166-1、ISO 3166-2 両方にコードが定義されています。
国・地域 | 行政区画 | ISO 3166-2 |
---|---|---|
アメリカ合衆国 | ニューヨーク州 | US-NY |
カリフォルニア州 | US-CA | |
ワシントン D.C. | US-DC | |
グアム | US-GU | |
日本 | 北海道 | JP-01 |
東京都 | JP-13 | |
沖縄県 | JP-47 | |
中華人民共和国 | 北京市 | CN-BJ |
四川省 | CN-SC | |
新疆ウイグル自治区 | CN-XJ | |
香港特別行政区 | CN-HK | |
(台湾省) | CN-TW |
国際連合統計部の UN M.49 では、国より広い地域のコードも定義されています。その一部を抜粋します。
地域 | UN M.49 | |
---|---|---|
世界 | 001 | |
アフリカ | 002 | |
アメリカ | 019 | |
北アメリカ | 003 | |
南アメリカ | 005 | |
アジア | 142 | |
東アジア | 030 | |
南アジア | 034 | |
東南アジア | 035 | |
中央アジア | 143 | |
西アジア | 145 | |
ヨーロッパ | 150 | |
南ヨーロッパ | 039 | |
東ヨーロッパ | 151 | |
北ヨーロッパ | 154 | |
西ヨーロッパ | 155 | |
オセアニア | 009 | |
南極 | 010 |
3-4. 通貨
通貨は ISO 4217 で規定されています。
3文字のアルファベット、3桁の数字が定義されています。原則として、3文字のアルファベットは、最初の2文字が国を表す ISO 3166-1 のコード、残りの1文字が通貨名の頭文字です。3桁の数字は ISO 3166-1 すなわち UN M.49 と同じになっています。
通貨以外に、貴金属や国際金融で使用される特定の金融商品にも X
で始まる通貨コードが割り当てられています。
規定されているコードの例を以下に挙げます。
通貨 | alpha | numeric | 小数桁 | (通貨記号) |
---|---|---|---|---|
アメリカ合衆国ドル | USD | 840 | 2 | $ |
カナダ・ドル | CAD | 124 | 2 | $ / C$ |
スターリング・ポンド | GBP | 826 | 2 | £ |
ユーロ | EUR | 978 | 2 | € |
ロシア・ルーブル | RUB | 643 | 2 | ₽ |
エジプト・ポンド | EGP | 818 | 2 | E£ |
日本円 | JPY | 392 | 0 | ¥ |
韓国ウォン | KRW | 410 | 0 | ₩ |
人民元 | CNY | 156 | 2 | ¥ |
新台湾ドル | TWD | 901 | 2 | $ / NT$ |
金 (1トロイオンス) | XAU | 959 | ||
銀 (1トロイオンス) | XAG | 961 |
4. Common Locale Data Repository
Unicode コンソーシアムの Common Locale Data Repository (CLDR) では、国際化に関わる以下のデータを提供しています。
- 言語タグの拡張属性
- 言語名、文字体系、国名・地域名、通貨名等の訳
- 日付・時刻、数値の書式
4-1. Unicode ロケール識別子
Unicode ロケール識別子は、CLDR の記述言語 Unicode Locale Data Markup Language (LDML) で使用される、IETF 言語タグの拡張で、u-
に続けて、以下のような属性を付加することができます。
key | type | 説明 | |
---|---|---|---|
ca | カレンダー | iso8601 | ISO 8601 |
gregory | グレゴリオ暦 | ||
japanese | 和暦 | ||
fw | 週の開始 | sun | 日曜始まり |
mon | 月曜始まり | ||
hc | 時間周期 | h11 | 12時制 (0-11) |
h12 | 12時制 (1-12) | ||
h23 | 24時制 (0-23) | ||
h24 | 24時制 (1-24) | ||
tz | タイムゾーン | utc | Etc/UTC、協定世界時 |
usnyc | America/New_York、ニューヨーク (米国) | ||
gblon | Europe/London、ロンドン (英国) | ||
jptyo | Asia/Tokyo、日本標準時 | ||
nu | 数値書式 | latn | 算用数字 |
roman | ローマ数字 (大文字) | ||
jpan | 漢数字 (日本) | ||
cu | 通貨 | usd | 米ドル |
eur | ユーロ | ||
jpy | 日本円 | ||
ms | 単位系 | metric | メートル法 |
uksystem | ヤード・ポンド法 (英国) | ||
ussystem | ヤード・ポンド法 (米国) | ||
rg | 地域 | (言語以外の地域依存ロケール) | |
sd | 行政区画 | usny | ニューヨーク州 |
jp13 | 東京都 | ||
va | バリアント | posix | POSIX ロケール |
利用可能な属性は、Unicode コンソーシアムの GitHub で見ることができます。
ロケール識別子の例を以下に挙げます。
ロケール識別子 | 説明 |
---|---|
ja-JP-u-ca-japanese | 和暦 |
ja-JP-u-ca-iso8601-tz-jptyo | ISO 8601 形式の日付・時刻、日本標準時 |
en-GB-u-rg-uszzzz | イギリス英語だが、カレンダー、通貨など他の属性は米国式 |
Java 9 から ca と nu、Java 10 から cu、fw、rg、tz のサポートが追加されています。
4-2. CLDR data
CLDR が提供する訳語や書式などのデータは、Unicode コンソーシアムの GitHub で見ることができます。
どんなデータが定義されているか、参考までに見ておくとよいかと思います。一部を抜粋します。
field | type | en (en-US) | en-GB | ja (ja-JP) |
---|---|---|---|---|
language | en | English | English | 英語 |
en_GB | British English | British English | イギリス英語 | |
en_US | American English | American English | アメリカ英語 | |
ja | Japanese | Japanese | 日本語 | |
und | Unknown language | ↑↑↑ | 言語不明 | |
script | Latn | Latin | ↑↑↑ | ラテン文字 |
Jpan | Japanese | ↑↑↑ | 日本語の文字 | |
Zzzz | Unknown Script | ↑↑↑ | 不明な文字 | |
territory | 019 | Americas | ↑↑↑ | アメリカ大陸 |
150 | Europe | ↑↑↑ | ヨーロッパ | |
142 | Asia | ↑↑↑ | アジア | |
US | United States | United States | アメリカ合衆国 | |
GB | United Kingdom | United Kingdom | イギリス | |
JP | Japan | Japan | 日本 | |
EU | European Union | ↑↑↑ | 欧州連合 | |
UN | United Nations | ↑↑↑ | 国際連合 | |
ZZ | Unknown Region | ↑↑↑ | 不明な地域 | |
type (calendar) | iso8601 | ISO-8601 Calendar | ↑↑↑ | ISO-8601 |
gregory | Gregorian Calendar | ↑↑↑ | 西暦(グレゴリオ暦) | |
japanese | Japanese Calendar | ↑↑↑ | 和暦 | |
type (number) | latn | Western Digits | ↑↑↑ | 算用数字 |
roman | Roman Numerals | ↑↑↑ | ローマ数字 | |
jpan | Japanese Numerals | ↑↑↑ | 漢数字 | |
dateFormat | full | EEEE, MMMM d, y | EEEE, d MMMM y | y年M月d日EEEE |
short | M/d/yy | dd/MM/y | y/MM/dd | |
timeFormat | full | h:mm:ss a zzzz | HH:mm:ss zzzz | H時mm分ss秒 zzzz |
short | h:mm a | HH:mm | H:mm |
↑↑↑ は上位のロケール (en-GB であれば en) の値を継承することを意味します。
Java 9 からこれらのデータを参照するようになりました。
5. プログラミング言語での扱い (Java)
本章では、Java を例に、国際化に関する処理の例を紹介します。
コード例は Java 11 を基準にしています。
5-1. Locale
Java ではロケールの情報を java.util.Locale クラスで扱います。
英語、日本語、アメリカ合衆国、イギリス、日本など、いくつかの言語、国・地域に対しては定数が定義されているので、それを使うとよいでしょう。それ以外のロケールについては、言語タグを引数にしたファクトリメソッドで取得するのが簡単です。
class LocaleTest { @Test void testJapaneseCalendar() { // ファクトリによる生成。 Locale locale = Locale.forLanguageTag("ja-JP-u-ca-japanese"); // 各フィールドの取得。 assertThat(locale.getLanguage()).isEqualTo("ja"); assertThat(locale.getCountry()).isEqualTo("JP"); assertThat(locale.hasExtensions()).isTrue(); assertThat(locale.getExtension('u')).isEqualTo("ca-japanese"); assertThat(locale.getUnicodeLocaleType("ca")).isEqualTo("japanese"); // 言語タグへの変換。 assertThat(locale.toLanguageTag()).isEqualTo("ja-JP-u-ca-japanese"); } }
フィルタリング
Locale クラスでは、RFC 4647 のフィルタリングの機能を提供しています。資料検索などで、利用者が読むことができるものだけにフィルタリングして返すなどのユースケースで使えると思います。
@Test void testFilterTags() { List<String> tags = List.of( "en", "en-US", "en-GB", "ja", "ja-JP", "zh-CN", "zh-TW", "zh-Hans-CN", "zh-Hant-TW"); // 言語・地域を指定。 assertThat(Locale.filterTags(LanguageRange.parse("zh-TW, en-US"), tags)) .isEqualTo(List.of("zh-TW", "en-US")); // 拡張言語範囲、言語のみを指定。 assertThat(Locale.filterTags(LanguageRange.parse("zh-*-TW, en"), tags)) .isEqualTo(List.of("zh-TW", "zh-Hant-TW", "en", "en-US", "en-GB")); }
ルックアップ
Locale クラスでは、RFC 4647 のルックアップの機能も提供しています。利用者にとって最適な言語のデータを1つ返すなどのユースケースで利用できます。
@Test void testLookupTag() { List<String> tags = List.of( "en", "en-US", "en-GB", "ja", "ja-JP", "zh-CN", "zh-TW", "zh-Hans-CN", "zh-Hant-TW"); // 言語・地域を指定。 assertThat(Locale.lookupTag(LanguageRange.parse("zh-TW, en-US"), tags)) .isEqualTo("zh-TW"); // 拡張言語範囲、言語のみを指定。 assertThat(Locale.lookupTag(LanguageRange.parse("zh-*-TW, en"), tags)) .isEqualTo("zh-Hant-TW"); }
5-2. Currency
Java では通貨の情報を java.util.Currency クラスで扱います。
通貨コードを引数にして Currency オブジェクトを取得することができます。数値コードでは取得できないようです。また、ロケールを引数にして現在の通貨を取得することができます。日時は指定できないようです。
class CurrencyTest { @Test void testUsd() { // 通貨コードで取得。 Currency currency = Currency.getInstance("USD"); // 各フィールドの取得。 assertThat(currency.getCurrencyCode()).isEqualTo("USD"); assertThat(currency.getNumericCode()).isEqualTo(840); assertThat(currency.getNumericCodeAsString()).isEqualTo("840"); assertThat(currency.getDefaultFractionDigits()).isEqualTo(2); assertThat(currency.getSymbol(Locale.US)).isEqualTo("$"); assertThat(currency.getSymbol(Locale.JAPAN)).isEqualTo("$"); // ロケールで取得。 assertThat(Currency.getInstance(Locale.US)).isEqualTo(currency); } }
5-3. ResourceBundle
ロケールごとに異なる情報を取得する場合は、java.util.ResourceBundle クラスを使用します。
例えば、以下のような3つの設定ファイル (プロパティファイル) を用意します。ファイル名は、ja-JP ではなく ja_JP なので注意してください。
なお、プロパティファイルが ASCII 以外の文字を含む場合、Java 8 以前は native2ascii コマンドでファイルを変換する必要がありましたが、Java 9 から PropertyResourceBundle のデフォルトエンコーディングが UTF-8 になったため、ファイルを UTF-8 で作成すれば変換の必要はありません。
resources.properties
locale.id = locale.name = (Unknown) locale.language = und locale.script = locale.region =
resources_ja.properties
locale.id = ja locale.name = 日本語 locale.language = ja locale.script = Jpan
resources_ja_JP.properties
locale.id = ja-JP locale.name = 日本語 (日本) locale.region = JP
ロケール ja-JP を引数に ResourceBundle を取得し、各リソース (ここでは文字列) を取得すると、ja_JP → ja → (root) の順に検索して、最初に見つかったものを返してくれます。
class ResourceBundleTest { @Test void testJapan() { Locale locale = Locale.JAPAN; assertThat(locale.toLanguageTag()).isEqualTo("ja-JP"); ResourceBundle resources = ResourceBundle.getBundle("resources", locale); assertThat(resources.getString("locale.id")).isEqualTo("ja-JP"); assertThat(resources.getString("locale.name")).isEqualTo("日本語 (日本)"); assertThat(resources.getString("locale.language")).isEqualTo("ja"); assertThat(resources.getString("locale.script")).isEqualTo("Jpan"); assertThat(resources.getString("locale.region")).isEqualTo("JP"); } }
中国語の場合は、少し検索順が異なっており、以下のように検索するようです。
https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/util/ResourceBundle.Control.html#getCandidateLocales(java.lang.String,java.util.Locale)
- ロケール zh-Hans-CN の場合: zh_Hans_CN → zh_Hans → zh_CN → zh → (root)
- ロケール zh-CN の場合: zh_Hans_CN → zh_Hans → zh_CN → zh → (root)
- ロケール zh-Hant-TW の場合: zh_Hant_TW → zh_Hant → zh_TW → zh → (root)
- ロケール zh-TW の場合: zh_Hant_TW → zh_Hant → zh_TW → zh → (root)
ドキュメントに記載がありませんが、zh-Hans、zh-Hant を指定した場合は、以下のようになるようです。
- ロケール zh-Hans の場合: zh_Hans → zh_CN → zh → (root)
- ロケール zh-Hant の場合: zh_Hant → zh_TW → zh → (root)
zh-CN、zh-TW を簡体字、繁体字の代替として使っていた名残りでしょうか。
また、ロケール en-US を指定し、かつ resources_en_US.properties も resources_en.properties も存在しない場合、resources.properties を参照しそうに思いますが、システムのデフォルトロケールが検索されてしまい、日本語環境であれば ja_JP → ja → (root) の順に探してしまいます。
当該のリソースが存在しない場合、英語のリソースを返したい場合は、システムのデフォルトロケールを en-US に設定する必要がありそうです。
5-4. MessageFormat
ロケールによって出力するメッセージを変えたい場合は、ResourceBundle に加えて java.text.MessageFormat クラスを使用します。
先ほどと同じように、ロケールごとに設定ファイル (プロパティファイル) を作成し、メッセージのひな型を記述します。
messages_en.properties
hello = Hello, {0}!
messages_ja.properties
hello = {0}さん、こんにちは!
ResourceBundle から取得した文字列を引数に MessageFormat を生成し、置換するデータを引数に format
メソッドを呼び出すと、メッセージの該当部分が引数に置換されます。
class MessageFormatTest { @Test void testUS() { Locale locale = Locale.US; assertThat(getMessage(locale, "hello", "Yamada")) .isEqualTo("Hello, Yamada!"); } @Test void testJapan() { Locale locale = Locale.JAPAN; assertThat(getMessage(locale, "hello", "山田")) .isEqualTo("山田さん、こんにちは!"); } static String getMessage(Locale locale, String code, Object... args) { ResourceBundle messages = ResourceBundle.getBundle("messages", locale); return new MessageFormat(messages.getString(code), locale).format(args); } }
とは言え、実際にシステムを開発する場合、Spring Framework のように何かしらのフレームワークを使うことが多いと思います。フレームワークがメッセージ変換の機能を持ってることも多いので、その場合は、フレームワークの機能を確認してください。
6. まとめと次回の予告
システム開発で必要となる標準規格の話、連載第4回は国際化編と題して、ロケールと関連する国際規格、を紹介しました。今回取り上げた標準規格や仕組みを使える場合は、独自のコードや仕組みを再発明せずに、あるものを使った方が相互運用性や保守性の観点で好ましいと思います。
次回は、日付・時刻、数値に関する話題を取り上げようと思います。
A1. 参考文献・URL
- RFC 5646: Tags for Identifying Languages - IETF
https://datatracker.ietf.org/doc/html/rfc5646 - RFC 4647: Matching of Language Tags - IETF
https://datatracker.ietf.org/doc/html/rfc4647 - Language Subtag Registry - IANA
https://www.iana.org/assignments/language-subtag-registry - IETF言語タグ - Wikipedia
https://ja.wikipedia.org/wiki/IETF%E8%A8%80%E8%AA%9E%E3%82%BF%E3%82%B0 - HTMLとXMLにおける言語タグ - W3C
https://www.w3.org/International/articles/language-tags/index.ja - BCP 47 - SuikaWiki
https://wiki.suikawiki.org/n/BCP%2047 - ISO 639-1コード一覧 - Wikipedia
https://ja.wikipedia.org/wiki/ISO_639-1%E3%82%B3%E3%83%BC%E3%83%89%E4%B8%80%E8%A6%A7 - ISO 639-2コード一覧 - Wikipedia
https://ja.wikipedia.org/wiki/ISO_639-2%E3%82%B3%E3%83%BC%E3%83%89%E4%B8%80%E8%A6%A7 - ISO 15924 - Wikipedia
https://ja.wikipedia.org/wiki/ISO_15924 - 国名コード - Wikipedia
https://ja.wikipedia.org/wiki/%E5%9B%BD%E5%90%8D%E3%82%B3%E3%83%BC%E3%83%89 - ISO 3166-1 - Wikipedia
https://ja.wikipedia.org/wiki/ISO_3166-1 - ISO 4217 - Wikipedia
https://ja.wikipedia.org/wiki/ISO_4217 - Unicode Locale Data Markup Language (LDML)
http://www.unicode.org/reports/tr35/ - Unicode CLDR Project
https://cldr.unicode.org/ - Unicode CLDR Project - GitHub
https://github.com/unicode-org/cldr - 参考資料 - CyberLibrarian 図書館員のコンピュータ基礎講座
http://www.asahi-net.or.jp/~ax2s-kmtn/ref/index.html - Java SE 17 国際化ガイド - Oracle
https://docs.oracle.com/javase/jp/17/intl/toc.htm - Java SE 8 国際化の概要 - Oracle
https://docs.oracle.com/javase/jp/8/docs/technotes/guides/intl/overview.html