Working with message bundles

Dojo’s concept of a message bundle is a map of keyed text messages, with message content for each key represented across one or more languages.

A Dojo application refers to a particular message via its key when needing to display that message to an end user. This avoids hard-coding a single language’s text within code, and instead provides an externalized set of messages in one or more languages that can be maintained independently of the application’s code.

At render time, Dojo’s i18n framework handles the replacement of message keys with their text content for a particular language, depending on the current locale setting within the widget that is referencing the message keys.

Dojo applications can choose to use a single message bundle across the entire application, or they can decompose messages to be more fine-grained and scoped more closely to the widget(s) they are referenced from, ending up with an application containing several message bundles.

Bundle default language

Each message bundle has its own set of supported language translations. One language within the set is required to act as the default module for the rest of the bundle. The default language module acts as the primary import/reference to the bundle, and serves two main requirements:

  • Provides a comprehensive set of message keys and their content (represented in the default language) that are used as a fallback if other languages within the bundle do not provide overrides for a given key
  • Lists the bundle’s other supported languages, as well as the mechanism to load the set of messages from each supported language’s module

TypeScript structure

Every language within a bundle is a TypeScript module, and is required to export a default object representing a map of message keys to their translated values within the particular language.

For example, a French language module within a bundle:

nls/fr/main.ts

  1. export default {
  2. hello: 'Bonjour',
  3. goodbye: 'Au revoir'
  4. };

Default language module

The language module designated as the bundle’s default is formatted slightly differently to other languages. The default module needs to export an object with the following properties

  • messages
    • A map of message keys to values in the default language, structured in the same way as the object exported by other languages in the bundle. This represents the canonical set of message keys supported by the bundle.When the application locale is set to the default value, these messages are used as a regular lookup when resolving message keys. When a non-default locale is in use, these messages are used as fallbacks for any keys not included in the bundle’s additional language modules.
  • locales
    • An optional property that represents a map of locale identifiers to functions that can load the message set for each language/locale supported by the bundle.

For example, a bundle with English as the default that also supports French, Arabic and Japanese:

nls/main.ts

  1. export default {
  2. locales: {
  3. fr: () => import('./fr/main'),
  4. ar: () => import('./ar/main'),
  5. ja: () => import('./ja/main')
  6. },
  7. messages: {
  8. hello: 'Hello',
  9. goodbye: 'Goodbye'
  10. }
  11. };

Importing and using bundles

The default language module for a bundle is imported like any other TypeScript module into each widget that requires use of the set of messages contained within the bundle.

For example, given a default bundle:

nls/en/MyI18nWidget.ts

  1. export default {
  2. messages: {
  3. hello: 'Hello',
  4. welcome: 'Welcome to your application'
  5. }
  6. };

This can be imported and referenced within a widget such as:

widgets/MyI18nWidget.tsx

  1. import { create, tsx } from '@dojo/framework/core/vdom';
  2. import i18n from '@dojo/framework/core/middleware/i18n';
  3. import myWidgetMessageBundle from '../nls/en/MyI18nWidget';
  4. const factory = create({ i18n });
  5. export default factory(function MyI18nWidget({ middleware: { i18n } }) {
  6. const { messages } = i18n.localize(myWidgetMessageBundle);
  7. return <div title={messages.hello}>{messages.welcome}</div>;
  8. });

As this example widget loads its messages through the i18n middleware’s .localize method, it will continue to work as new language translations are added and referenced within the bundle’s nls/en/MyI18nWidget.ts default language module. Users will see localized messages from MyI18nWidget instances if a message set for their currently configured language is available.

Applications that want to override user default languages and allow changing locales within the application itself require additional setup, covered in Internationalizing a Dojo application.

Lazy vs. static loading

It is preferable to use functions in the default language’s locales map to load other language translation modules, as this allows locale message bundles to be lazily loaded, only if required.

Some applications may however prefer certain languages to be statically loaded along with the bundle’s default language module, and can do so by returning a compatible object structure directly.

An example of both types of loading within a bundle:

  1. import fr from './fr/main';
  2. export default {
  3. locales: {
  4. // Locale providers can load translations lazily...
  5. ar: () => import('./ar/main'),
  6. 'ar-JO': () => import('./ar-JO/main'),
  7. // ... or return translations directly.
  8. fr: () => fr
  9. },
  10. // Default/fallback messages
  11. messages: {
  12. hello: 'Hello',
  13. goodbye: 'Goodbye'
  14. }
  15. };