Tutorial (Beginner) - Antl Internationalization/Localization Hack


#1

Note to Admin: Not sure if this is the correct category?

Tutorial (Beginner) - Antl Internationalization/Localization Hack

I wanted to provide a short tutorial of a “hack” I use with Antl to localize strings in my application. The purpose is to give back a little to the community. I received some help here, so hopefully this little “hack” can help others.

Why the “hack”?

We don’t always need the feature of placeholders and formatting when translating strings in our app. In such cases, the following becomes tedious.

Antl.formatMessage('app.email')

__('email') is not only more elegant, it is less verbose and also decoupled from Antl, i.e. you could change the localizer in the future and easily keep the same code when localizing simple strings.

How the hack?

First, we need to create a powerful one-liner. We’ll create a file called app/Helpers/string-localize.js and fill it with the following code:

'use strict'

const Antl = use('Antl')

module.exports = message => Antl.formatMessage('app.' + message)

We can now create the file resources/locales/en/app.js to keep our localized strings. A short example of app.js:

{
  "Your email is required.": "Your email is required.",
  "email": "email"
}

Now, when we want to create a French version, our translator can look at the object keys to know what to translate. In resources/locales/fr/app.js we have:

{
  "Your email is required.": "Votre courriel est obligatoire.",
  "email": "courriel"
}

How to use it in our controllers?

'use strict'

const __ = use('App/Helpers/string-localize')

class AuthController {
  async login ({ request, session, response, auth }) {
    const rules = {
      email: 'required|email',
      password: 'required'
    }
    const messages = {
      'email.required': __('Your email is required.'),
      'email.email': __('Your email address is invalid.'),
      'password.required': __('Your password is required.')
    }
    const validation = await validateAll(request.all(), rules, messages)
    // ...
  }
}

How to use it in our views?

By adding our “hack” to View.global we can access it in all our views.

Let’s create the file start/hooks.js with the following code:

'use strict'

const { hooks } = require('@adonisjs/ignitor')

hooks.after.providersBooted(() => {
  const View = use('View')
  const __ = use('App/Helpers/string-localize')
  View.global('__', __)
})

Now we call it like so in any view:

<p>{{ __('username') }}</p>

Hack the hack

Using our code, we will receive an error if a string we call is not defined in the called localized file. What if our translations are not completed in our French file, or even completed in our English file? Perhaps we simply want to show the default version of the string, instead of crashing the app?

Since our strings are pure, we could print them as is when they don’t exist in our localized file, by-passing Antl entirely. To do this, we simply modify our helper. The code app/Helpers/string-localize.js becomes

'use strict'

const Antl = use('Antl')

module.exports = message => {
  try {
    return Antl.formatMessage('app.' + message)
  } catch(error) {
    return message
  }
}

#2

Thanks, it helped me quite a bit, the Antl implementation is a bit clunky imho. I added the following to your helper (using database as loader) to easily get a table with (todo) translations. You need to create a Lucid model first.

'use strict'

const Locale = use('App/Models/Locale')
const Antl = use('Antl')

async function unknownTranslation (message) {
  const langs = ['en', 'fr', 'es', 'de', 'nl']
  langs.forEach(async e => {
    const locale = new Locale()
    locale.locale = e
    locale.group = 'app'
    locale.item = message
    locale.text = message
    await locale.save()
  })
  await Antl.bootLoader()
}

module.exports = message => {
  try {
    return Antl.formatMessage('app.' + message)
  } catch (error) {
    unknownTranslation(message)
    console.log('Translation not found')
    return message
  }
}