Internationalization

AdonisJs has first class support for internationalization built on top of formatjs.io standards.

Using the Antl Provider, you can easily translate numbers, dates, and messages into multiple languages.

Setup

As the Antl Provider is not installed by default, we need to pull it from npm:

> adonis install @adonisjs/antl

Next, we need to register the provider inside the start/app.js file:

start/app.js
const providers = [
  '@adonisjs/antl/providers/AntlProvider'
]

Your locales configuration object must be saved inside the config/app.js file with the following options:

Option Value Description

locale

ISO 639

The default application locale (must be one of the available locales from ISO 639 codes).

loader

database or file

The loader to use for loading your different language translations.

config/app.js
module.exports = {
  locales: {
    loader: 'file',
    locale: 'en'
  }
}

Locales Storage

File

When using the file loader, all locales are stored inside the resources/locales directory.

Each locale directory should contain a list of group translation files, like so:

└── resources
  └── locales
      ├── en
      │ ├── alerts.json
      │ ├── cart.json
      │ └── store.json
      └── fr
        ├── alerts.json
        ├── cart.json
        └── store.json
In the example above, each locale contains 3 hypothetical translation groups: alerts, cart and store. Create as many group files per locale as per your application needs.

You can also create a directory named fallback to store messages which are used when the message for the current language can’t be found:

└── resources
  └── locales
      ├── en
      │ └── messages.json
      ├── fallback
      │ └── messages.json
      └── fr
        └── messages.json

Database

When using the database loader, all locales are fetched from the locales database table.

The adonis install command creates the migration for the locales table.
You can always reference the latest migration source file on Github.

An example locales database table might look like so:

id locale group item text

1

en

messages

greeting

Hello {name}

2

fr

messages

greeting

Bonjour {name}

3

en

cart

total

Cart total is {total, number, usd}

4

fr

cart

total

Le panier est total {total, number, usd}

You must define a group value for each locales item.

Accessing Locales

You can access the current and default locale via the Antl object:

const Antl = use('Antl')

Antl.currentLocale()
Antl.defaultLocale()

ICU Message Syntax

AdonisJs uses the industry standard ICU Message syntax to format messages.

The following topics define the usage of the ICU message syntax.

Values

To retrieve a translation value, simply reference it by its group.item key:

resources/locales/en/messages.json
{
  "greeting": "Hello"
}
Antl.formatMessage('messages.greeting')

Arguments

You can pass dynamic arguments to inject into placeholders which are defined by { } curly braces inside your messages:

resources/locales/en/messages.json
{
  "greeting": "Hello {name}"
}
Antl.formatMessage('messages.greeting', { name: 'virk' })

Formatted arguments

The values passed to a message can be optionally formatted by type.

You must register your formats before you can use them (see Registering Formats).

For example, when passing a number we can format it as a currency:

resources/locales/en/cart.json
{
  "total": "Cart total is {total, number, usd}"
}

For the placeholder {total, number, usd} in the message above:

  1. total is the value passed.

  2. number is the type of the value.

  3. usd is the format for that type of value.

As the ICU message syntax doesn’t understand formats directly, we need to pass them manually when formatting a message:

const Antl = use('Antl')
const Formats = use('Antl/Formats')

Antl.formatMessage(
  'cart.total',
  { total: 20 },
  [Formats.pass('usd', 'number')]
)

In the example above, we are simply calling formatMessage with 3 arguments:

  1. cart.total is the reference to the message to be formatted.

  2. { total: 20 } is the data passed to that message.

  3. [Formats.pass('usd', 'number')] is an array of possible formats.

Select format

The select format defines conditional output based on the passed value:

{gender, select,
    male {He}
    female {She}
    other {They}
} will respond shortly
Try and edit the message above in your browser.

Plural format

The plural format defines plurilization options based on the passed value:

{count, plural,
   =0 {No candy left}
   one {Got # candy left}
   other {Got # candies left}
}
Try and edit the message above in your browser.

Formatting values

Below is the list of methods you can use to format messages or raw values.

formatMessage(key, [data], [formats])

The formatMessage method expects the key to be formatted (group.item):

const Antl = use('Antl')

Antl.formatMessage('messages.greeting')

It can also accept an object of dynamic data to pass to the message:

const Antl = use('Antl')

Antl.formatMessage('response.eta', { gender: 'male' })

Finally, it can also accept an array of formats to parse passed data with:

const Antl = use('Antl')
const Formats = use('Antl/Formats')

Antl.formatMessage(
  'cart.total',
  { total: 20 },
  [
    Formats.pass('usd', 'number')
  ]
)

formatNumber(value, [options])

Format value as a number (accepts NumberFormat options as defined here):

Antl.formatNumber(10)

// as currency
Antl.formatNumber(10, {
  style: 'currency',
  currency: 'usd'
})

// as percentage
Antl.formatNumber(10, {
  style: 'percent'
})

formatAmount(value, currency, [options])

Format value with style set to currency:

Antl.formatAmount(100, 'usd')

formatDate(value, [options])

Format value as date (accepts DateTimeFormat options as defined here):

Antl.formatDate(new Date())

// pull weekday for the date
Antl.formatDate(new Date(), {
  weekday: 'long'
})

// pull day only
Antl.formatDate(new Date(), {
  day: '2-digit'
})

formatRelative(value, [options])

Format a date relative to the current date/time (accepts RelativeFormat options as defined here):

Antl.formatRelative(new Date())

// always in numeric style
Antl.formatRelative(new Date(), {
  style: 'numeric'
})

Registering Formats

The formatMessage method only accepts an array of pre-registered formats.

To register your formats for a given type:

const Formats = use('Antl/Formats')

Formats.add('usd', {
  style: 'currency',
  currency: 'usd'
})

Use it as follows:

Antl.formatMessage(
  'cart.total'
  { total: 20 },
  [
    Formats.pass('usd', 'number')
  ]
)

The Formats.pass method takes two arguments:

  1. The first argument is the format to be used.

  2. The second argument is the type to which the format should be applied.

Multiple type formats

You can pass multiple formats to a given type. For example:

resources/locales/en/cart.json
{
  "total": "USD total { usdTotal, number, usd } or in GBP { gbpTotal, number, gbp }"
}

Next, register the usd and gbp formats.

Formats.add('usd', {
  style: 'currency',
  currency: 'usd'
})

Formats.add('gbp', {
  style: 'currency',
  currency: 'gbp'
})

Finally, you can format the message as follows:

Antl.formatMessage(
  'cart.total',
  { usdTotal: 20, gbpTotal: 13 },
  [
    Formats.pass('usd', 'number'),
    Formats.pass('gbp', 'number')
  ]
)
Output
USD total $20.00 or in GBP £13.00

Switch locale

The Antl Provider makes it simple to format the locale at runtime.

To do so, simply call forLocale before formatMessage:

Antl
  .forLocale('fr')
  .formatMessage('response.eta')

Switch Loader

You can switch between loaders at runtime by calling the loader method:

const Antl = use('Antl')

// asynchronous
await Antl.bootLoader()

// get antl instance for a booted loader
const AntlDb = Antl.loader('database')

// all methods are available
AntlDb.formatMessage()
Always call bootLoader before Antl.loader (you only need to call bootLoader once).

Http Request Locale

The Antl Provider binds the locale property to the Http Context object:

Route.get('/', ({ locale }) => {
  return `User language is ${locale}`
})

The locale property is resolved as follows:

  1. The Accept-Language HTTP header or lang query parameter is examined to detect the user language.

  2. The user language is matched against the list of available locales configured by your application. The configured locales are determined by messages saved inside the database or file system for given languages.

  3. If the user language is not supported by your application, it will fallback to the default locale defined inside the config/app.js file.

Http Formatting

Since we can access the user locale based on standard conventions, you can format messages in one of the following ways.

Import globally

You can import the Antl Provider globally and manually call the forLocale method when formatting values:

const Antl = use('Antl')

Route.get('/', ({ locale }) => {
  return Antl
    .forLocale(locale)
    .formatNumber(20, { style: 'currency', currency: 'usd' })
})

Context instance

You can also use the antl object which is passed to all route handlers like request and response:

Route.get('/', ({ antl }) => {
  return antl
    .formatNumber(20, { style: 'currency', currency: 'usd' })
})

For example, you could then switch locale for a view like so:

Route.get('/', ({ antl, view }) => {
  antl.switchLocale('fr')
  return view.render('some-view')
}

View Global

As the antl context instance is shared with all views, you can access its methods inside your view templates like so:

{{ antl.formatNumber(20, currency = 'usd', style = 'currency')  }}

Alternatively, you can use the @mustache tag to write multiple lines:

@mustache(antl.formatNumber(
  20,
  { currency: 'usd', style: 'currency }
))
There is no way to switch loader inside templates.