MessageSource

MessageSource は、メッセージコードとロケールを、引数が埋め込まれたその言語のテキストに変換します。

使う理由

ユーザーが読み、翻訳が必要なテキストはすべてこれを通します。ラベル、メール、エラーメッセージなどです。MessageSource を Bean に注入するか、それ自身もメッセージソースであるコンテキストの getMessage を呼びます。

実装:

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);

重要な違い:

関連項目