MessageSource
MessageSource は、メッセージコードとロケールを、引数が埋め込まれたその言語のテキストに変換します。
使う理由
ユーザーが読み、翻訳が必要なテキストはすべてこれを通します。ラベル、メール、エラーメッセージなどです。MessageSource を Bean に注入するか、それ自身もメッセージソースであるコンテキストの getMessage を呼びます。
実装:
ReloadableResourceBundleMessageSource: 言語ごとに一つの.propertiesファイルを読み、変更されたときに読み直すこともできます。通常はこれを使います。StaticMessageSource: コードで追加したメッセージを保持します。テストや小さなプログラムで使います。MessageSourceAccessor: デフォルトのロケールを覚えておくラッパーで、検索のたびにロケールを渡す必要がなくなります。MessageFormat: テキストの{0}構文を処理するフォーマッターです。メッセージソースから来ないパターンを整形するときは直接呼びます。
Spring Boot を使わない場合は、messageSource という名前の Bean を宣言します。コンテキストは自身の getMessage をこれに委譲します。子コンテキストでは、親のメッセージソースにフォールバックします。
例
# lib/resources/i18n/messages.properties
greeting=Hello {0}!
cart.items=You have {0,choice,0#no items|1#one item|1<{0} items}.
# lib/resources/i18n/messages_de.properties
greeting=Hallo {0}!
import 'package:spring_core_dart/spring_core_dart.dart';
@Configuration()
class I18nConfig {
// i18n/messages_<locale>.properties、次に i18n/messages.properties の順に探す。
@Bean()
MessageSource messageSource() => ReloadableResourceBundleMessageSource(
basenames: ['resources:i18n/messages'],
// 変更されたファイルを最大 10 秒ごとに読み直す。
cacheSeconds: 10,
)
// 要求したロケールにメッセージがなければ英語を使う。
..defaultLocale = 'en'
// マシンのシステムロケールに関係なく、どこでも同じ結果にする。
..fallbackToSystemLocale = false;
}
@Service()
class Greeter {
Greeter(this.messages);
final MessageSource messages;
// de_AT 専用のファイルはないので messages_de.properties が使われる。
String greet(String name) =>
messages.getMessage('greeting', args: [name], locale: 'de_AT');
// デフォルトのテキストがあれば、コードが見つからなくても例外にならない。
String items(int count, String locale) => messages.getMessage(
'cart.items',
args: [count],
locale: locale,
defaultMessage: '{0} items',
);
}
すべての検索に一つのロケールを使う例と、メッセージソースを使わない整形の例:
const log = SpringLog('neon.cli');
final messages = MessageSourceAccessor(context, defaultLocale: 'de');
log.info(messages.getMessage('greeting', args: ['Ada'])); // Hallo Ada!
MessageFormat.format("It''s {0,number,#.##}", [3.14159]); // It's 3.14
MessageFormat.format('{0,number}', [1234.5], locale: 'de'); // 1.234,5
デフォルトメッセージなしで見つからないコードは NoSuchMessageException を投げます。翻訳がそろっていない間は、useCodeAsDefaultMessage = true にすると代わりにコードが表示されます。
メンバー
MessageSource:
| メンバー | 意味 |
|---|---|
getMessage(code, {args, defaultMessage, locale}) |
locale での code のテキスト。args が {0}、{1} ... に埋め込まれる。ロケールがなければ、フォールバックのロケールと基本ファイルだけを参照する。 |
getMessageFromResolvable(resolvable, {locale}) |
MessageSourceResolvable (コード、引数、デフォルトのテキスト。たとえばバリデーションエラー) の、最初に見つかったコードのテキスト。 |
ReloadableResourceBundleMessageSource:
| 名前 | 型 | デフォルト | 意味 |
|---|---|---|---|
basenames |
List<String> |
const [] |
ロケールと拡張子を除いたファイルの場所 (resources:、file:、独自プロトコル)。先の basename が優先される。 |
resourceLoader |
ResourceLoader? |
DefaultResourceLoader() |
ファイルを探す。Flutter のアセットなど独自の場所には、ProtocolResolver を持つものを渡す。 |
cacheSeconds |
int |
-1 |
-1 はファイルを一度だけ読む。0 は検索のたびに確認する。正の値は、最大でその秒数ごとに確認する。 |
fallbackToSystemLocale |
bool |
true |
defaultLocale がないとき、マシンのロケールにフォールバックするかどうか。 |
defaultLocale |
String |
'' |
要求したロケールの次に試すロケール。 |
defaultEncoding |
Encoding |
utf8 |
ファイルの文字コード。 |
useCodeAsDefaultMessage |
bool |
false |
未知のコードで例外を投げず、コードを返す。 |
alwaysUseMessageFormat |
bool |
false |
引数のないメッセージも整形し、'' を ' にする。 |
commonMessages |
Map<String, String>? |
null |
どの言語でも同じメッセージ。すべてのロケールの後に試される。 |
parentMessageSource |
MessageSource? |
null |
このソースにないコードを問い合わせる先。 |
addBasenames(list) |
メソッド | 既存の basename の後に追加する。 | |
preload(locales) |
メソッド | ファイルを非同期に読む。非同期でしか読めないファイル (Flutter のアセット) では、検索の前に必要。 |
ロケール ru_RU と basename i18n/messages の場合、検索順は messages_ru_RU、messages_ru、デフォルト (またはシステム) のロケール、そして messages.properties です。2 番目の basename の前に、最初の basename のすべてのファイルが参照されます。
MessageFormat が対応するのは、{0}、クォート ('' は引用符一つ)、integer・percent・DecimalFormat パターンを指定できる {0,number}、short・medium・long・full・SimpleDateFormat パターンを指定できる {0,date} と {0,time}、そして {0,choice,...} です。
Spring (Java) との対応
@Bean
MessageSource messageSource() {
var source = new ReloadableResourceBundleMessageSource();
source.setBasenames("classpath:i18n/messages");
source.setCacheSeconds(10);
source.setDefaultLocale(Locale.ENGLISH);
source.setFallbackToSystemLocale(false);
return source;
}
String text = messageSource.getMessage("greeting", new Object[] {"Ada"},
Locale.GERMAN);
重要な違い:
- ロケールは文字列 (
de、ru_RU、ru-RU、zh_Hant_TW) で、Locales.normalizeで正規化されます。テストではLocale.setDefaultと同じようにLocales.systemLocaleを設定できます。 locale引数は省略できます。LocaleContextHolderはありません。- ファイルはデフォルトで UTF-8 として読まれます。Spring の
.propertiesのデフォルトは ISO-8859-1 です。 - ソースはコンテキストのリソースローダーを自動では受け取りません。basename が独自プロトコルを使う場合は
resourceLoaderを渡してください。 - 再読み込みで更新時刻を比べるのは、ファイルシステム上のファイルだけです。その他のリソースは確認のたびに読み直され、プリロードしたものは再度の
preload()でしか変わりません。 MessageFormatは、ロケールに関係なく日付と時刻を US 英語の形式で書き、指数表記には対応しません。