/ angular

Config for Angular Translate: Preferred, Available & Fallback Languages

We’ll be starting out where we left off in the post on Angular Translate’s static files loader. Here was our code at that point:

var app = angular.module('MyApp', ['ui.router', 'pascalprecht.translate']);

app.config(function ($translateProvider) {
  // i18n fun goes here

  var fileNameConvention = {
    prefix: 'locale-',
    suffix: '.json'
  };  

  $translateProvider.useStaticFilesLoader(fileNameConvention);
});

Detect the user’s preferred language

Angular Translate makes a best effort to figure out the user’s preferred language using the sometimes experimental tools currently provided by browsers.

As mentioned in the docs:

[Angular Translate] searches for values in the window.navigator object in the following properties (in this order):
navigator.languages[0]
navigator.language
navigator.browserLanguage
navigator.systemLanguage
navigator.userLanguage

We get access to this Angular Translate feature through the determinePreferredLanguage() method:

app.config(function ($translateProvider) {
  // i18n fun goes here

  var fileNameConvention = {
    prefix: 'locale-',
    suffix: '.json'
  };  

  $translateProvider
    .useStaticFilesLoader(fileNameConvention)
    .determinePreferredLanguage();
});

Nice! Done and done, right? Well, probably not…

A map available languages

Here’s the thing about determining the user language: a browser language key might not be the same as the language key you’re using in your app, even though both are perfectly valid. This is because your browser is likely to be more precise with its language keys, indicating not only the standard language key, but also the country (e.g. Canadian English vs American English).

You can try this out in your browser console right now by typing in navigator.language. For my computer I get en-US. For my iPhone, which is set to Japanese, I get ja-JP in the Safari console.

If my app is using 'en' for the language key and my browser is set to 'en-US' for its language key, no language interpolation will happen because 'en' !==en-US’`. The same will be true for any other language.

Further, unless you are localizing for all flavors of English out there (hey, nice job!) you’ll want to map all en-COUNTRY codes to the en language in your app.

Here’s the map for the Meaniscule demo site, which supports English and Japanese:

  var langMap = {
      'en_AU': 'en',
      'en_CA': 'en',
      'en_NZ': 'en',
      'en_PH': 'en',
      'en_UK': 'en',
      'en_US': 'en',
      'ja_JP': 'ja'
  };

US English? Rock on over to en.
UK English? Get thee to en.
Japan Japanese? (Is there any other kind?) Vamos a ja.

Register the map of available languages

Let’s get this map registered with our app so the right languages are displaying the right language file. Angular Translate has just the method for us, registerAvailableLanguageKeys():

app.config(function ($translateProvider) {
  // i18n fun goes here

  var fileNameConvention = {
    prefix: 'locale-',
    suffix: '.json'
  };  

  var langMap = {
      'en_AU': 'en',
      'en_CA': 'en',
      'en_NZ': 'en',
      'en_PH': 'en',
      'en_UK': 'en',
      'en_US': 'en',
      'ja_JP': 'ja'
  };

  $translateProvider
    .useStaticFilesLoader(fileNameConvention)
    .registerAvailableLanguageKeys() // Oops, we forgot our arguments!
    .determinePreferredLanguage();
});

Our arguments will be an array of available language keys and the language map object:

    .registerAvailableLanguageKeys(['en', 'ja'], langMap)

So altogether, that’s:

app.config(function ($translateProvider) {
  // i18n fun goes here

  var fileNameConvention = {
    prefix: 'locale-',
    suffix: '.json'
  };  

  var langMap = {
      'en_AU': 'en',
      'en_CA': 'en',
      'en_NZ': 'en',
      'en_PH': 'en',
      'en_UK': 'en',
      'en_US': 'en',
      'ja_JP': 'ja'
  };

  $translateProvider
    .useStaticFilesLoader(fileNameConvention)
    .registerAvailableLanguageKeys(['en', 'ja'], langMap)
    .determinePreferredLanguage();
});

A language to fall back on

With the code above, we’ve added i18n infrastructure for English and Japanese, but what happens if someone comes along with a browser set to Spanish (es-ES, es-MX, etc etc), or perhaps even more interestingly, South African English (en-ZA)?

No language interpolation will happen in these situations because we haven’t mapped those languages in our langMap. And just look at all of these language codes. That’s a lot of people left to stare at uninterpolated strings.

Most apps are unlikely to support every language and region out there, so we need a fallback language. With Angular Translate, this couldn’t be easier:

app.config(function ($translateProvider) {
  // i18n fun goes here

  var fileNameConvention = {
    prefix: 'locale-',
    suffix: '.json'
  };  

  var langMap = {
      'en_AU': 'en',
      'en_CA': 'en',
      'en_NZ': 'en',
      'en_PH': 'en',
      'en_UK': 'en',
      'en_US': 'en',
      'ja_JP': 'ja'
  };

  $translateProvider
    .useStaticFilesLoader(fileNameConvention)
    .registerAvailableLanguageKeys(['en', 'ja'], langMap)
    .determinePreferredLanguage()
    .fallbackLanguage(['en']);
});

Now our app will fall back to English if the user’s language isn’t found in our langMap. Nice!


We’re starting have the beginnings of robust language support:

  1. we determine the preferred language
  2. we look up that language in a map and serve the corresponding localization file, if it exists
  3. we fall back to one of our supported languages if the user’s system language isn’t supported by our app

Not bad, but there is still a hole in our strategy. Any ideas what that might be?