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:
ReloadableResourceBundleMessageSource: reads.propertiesfiles, one per language, and can re-read them when they change. This is the usual one.StaticMessageSource: holds messages added in code. Use it in tests or in a small program.MessageSourceAccessor: a wrapper that remembers a default locale, so lookups need not pass one.MessageFormat: the formatter for the{0}syntax of the texts. Call it directly to format a pattern that does not come from a message source.
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:
- Locales are strings (
de,ru_RU,ru-RU,zh_Hant_TW), normalized byLocales.normalize.Locales.systemLocalecan be set in tests, likeLocale.setDefault. - The
localeargument is optional. There is noLocaleContextHolder. - Files are read as UTF-8 by default. Spring's
.propertiesdefault is ISO-8859-1. - The source does not receive the context's resource loader by itself. Pass
resourceLoaderwhen the basenames use a custom protocol. - Reloading compares the modification time for file system files only. Other resources are re-read on every check, and preloaded ones change only through another
preload(). MessageFormatwrites dates and times in US English styles whatever the locale, and has no scientific notation.