MessageSource

MessageSource turns a message code and a locale into a text in that language, with the arguments filled in.

Why use it

Every text a user reads and that must be translated goes through it: labels, mails, error messages. Inject MessageSource into a bean, or call getMessage on the context, which is a message source too.

The implementations:

Without Spring Boot, declare a bean named messageSource. The context delegates its own getMessage to it. In a child context it falls back to the parent's message source.

Example

# 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 {
  // Looked up as i18n/messages_<locale>.properties, then i18n/messages.properties.
  @Bean()
  MessageSource messageSource() => ReloadableResourceBundleMessageSource(
        basenames: ['resources:i18n/messages'],
        // Re-read changed files at most every 10 seconds.
        cacheSeconds: 10,
      )
        // English when the requested locale has no message.
        ..defaultLocale = 'en'
        // The same answer on every machine, whatever its system locale.
        ..fallbackToSystemLocale = false;
}

@Service()
class Greeter {
  Greeter(this.messages);
  final MessageSource messages;

  // de_AT has no file of its own: messages_de.properties is used.
  String greet(String name) =>
      messages.getMessage('greeting', args: [name], locale: 'de_AT');

  // A missing code with a default text does not throw.
  String items(int count, String locale) => messages.getMessage(
        'cart.items',
        args: [count],
        locale: locale,
        defaultMessage: '{0} items',
      );
}

One locale for all lookups, and formatting without a message source:

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

A missing code without a default message throws NoSuchMessageException. Set useCodeAsDefaultMessage = true to show the code instead while translations are incomplete.

Members

MessageSource:

Member Meaning
getMessage(code, {args, defaultMessage, locale}) The text for code in locale, with args filled into {0}, {1}, ... Without a locale, only the fallback locale and the base file are consulted.
getMessageFromResolvable(resolvable, {locale}) The text for the first code of a MessageSourceResolvable (codes, arguments and a default text), such as a validation error.

ReloadableResourceBundleMessageSource:

Name Type Default Meaning
basenames List<String> const [] Locations of the files without locale and extension (resources:, file:, custom protocols). An earlier basename wins.
resourceLoader ResourceLoader? DefaultResourceLoader() Finds the files. Pass one with a ProtocolResolver for custom locations such as Flutter assets.
cacheSeconds int -1 -1 reads files once; 0 checks them on every lookup; a positive value checks them at most every so many seconds.
fallbackToSystemLocale bool true Whether a lookup falls back to the machine's locale when no defaultLocale is set.
defaultLocale String '' The locale tried after the requested one.
defaultEncoding Encoding utf8 The charset of the files.
useCodeAsDefaultMessage bool false Return the code instead of throwing for an unknown code.
alwaysUseMessageFormat bool false Also format messages without arguments, so '' becomes '.
commonMessages Map<String, String>? null Messages that are the same in every language, tried after all locales.
parentMessageSource MessageSource? null Asked for codes this source does not have.
addBasenames(list) method Adds basenames after the existing ones.
preload(locales) method Reads the files asynchronously. Required before lookups when files can only be read asynchronously (Flutter assets).

For the locale ru_RU and the basename i18n/messages, the lookup order is messages_ru_RU, messages_ru, the default (or system) locale, then messages.properties. All files of the first basename are consulted before the second basename.

MessageFormat supports {0}, quoting ('' is a quote), {0,number} with integer, percent or a DecimalFormat pattern, {0,date} and {0,time} with short, medium, long, full or a SimpleDateFormat pattern, and {0,choice,...}.

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

Differences that matter:

See also