
# 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 `.properties` files, 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

```properties
# 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}!
```

```dart
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:

```dart
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)

```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 by `Locales.normalize`. `Locales.systemLocale` can be set in tests, like `Locale.setDefault`.
- The `locale` argument is optional. There is no `LocaleContextHolder`.
- Files are read as UTF-8 by default. Spring's `.properties` default is ISO-8859-1.
- The source does not receive the context's resource loader by itself. Pass `resourceLoader` when 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()`.
- `MessageFormat` writes dates and times in US English styles whatever the locale, and has no scientific notation.

## See also

- [Messages](index.md)
- [Resource](../resources/resource.md)
- [Validation](../validation/index.md)
- [@Bean](../beans/bean.md)
- [ApplicationContext](../beans/application-context.md)
