PricingBlogDocumentationDocsLoginRegister

Adding Internationalization to Angular

GlitchTip 2.0 adds support for multiple languages. This post will explain how we did it.

Requirements

  • Language should be automatically detected.
  • GlitchTip is a web application and not a content site. There should be no language specific URLS like /en/foo.
  • It should be easy for a non-programmer to contribute translations.
  • i18n support should add as little maintaince needs as possible, because GlitchTip is run by a very small team.

Tooling

There are a number of packages to add i18n support to Angular.

  • ngx-translate - an older library that is now in maintenance only mode. This won't make sense for a new project.
  • Transloco - a newer and full featured library.
  • @angular/localize - Angular's native i18n library. It's most basic features work well and are documented. However these features aren't enough for most use cases. Notably, it's runtime feature currently isn't documented at all.

I found it a tough choice between Transloco and localize. Transloco looks much more practical with guides to explain the entire process. Localize is maintained by the core Angular team, which likely means it will receive continued support and easy upgrades. Localize, however, can feel like a work in progress. Many features are undocumented.

Transloco might be the better choice, at this time, for rapid development. However, I choose localize for GlitchTip. GlitchTip has very few dependencies outside of Angular and this makes maintenance a breeze. I'd rather spend more time initially and keep maintenance low. Localize is adding features and will presumably become full featured in the future.

Runtime translations with Angular localize

By runtime translations, I mean that the web app detects the language and gives the user their translation automatically without any need to generate seperate app bundles for each language. If I send a link to /foo to a colleague, I expect it to load in their language, regardless of mine. They shouldn't need to change the URL to get the desired langauge. It's not clear from Localize's documentation that it supports runtime translation. It has partial support through the usage of loadTranslations. This function loads JSON-based translations at app runtime. However there is no explaination on how to actually go about doing this. The basic process is:

  • Mark translation strings
  • Extract them into a format such as XLIFF
  • Translate the files
  • Convert the transation files to a JSON format that loadTranslations can read
  • Write code that can detect the language and load the JSON file
  • Update the translation files as code changes, without deleting old translations

How to add runtime translations

Before starting, read through Angular's i18n documentation. Ensure you are able to mark strings, extract the translation files, and set the desired translations in angular.json. Here's a snippet of GlitchTip's angular.json that enables the "nb" translation. Note the location of translation files.

      "i18n": {
        "sourceLocale": "en-US",
        "locales": {
          "nb": {
            "translation": "src/locale/messages.nb.xlf"
          }
        }
      },
...
        "extract-i18n": {
          "builder": "ng-extract-i18n-merge:ng-extract-i18n-merge",
          "options": {
            "browserTarget": "glitchtip-frontend:build",
            "format": "xlf2",
            "outputPath": "src/locale",
            "targetFiles": ["../locale/messages.nb.xlf"]
          }
        },

By default, ng extract-i18n overwrites translation files. You likely want to update them instead. ng-extract-i18n-merge is a small package that extends extract-i18n to merge instead of overwrite.

Next we need to convert the translation files to JSON. Angular CLI technically supports extracting as JSON, but this is rarely helpful as it won't show metadata. Instead I used the Angular CLI default XLIFF 2.0 format, which is editable with Poedit. I wrote a custom script based on xliff-to-json. This modified script supports XLIFF 2.0 and converts it to a loadTranslations-compatible JSON format. Feel free to copy it.

So with these tools we can run ng extract-i18n to add locale files to /src/locale/ and bin/update-i18n.sh to convert the xlf files to JSON at src/assets/i18n/. These paths are set in angular.json. I decided to commit both xlf and json files in git to keep things simple, even though the xlf file is the source of truth.

With the json files set, it's time to load them at runtime. These files need to be loaded immediately when the application starts up. Therefore we should add this logic in src/main.ts. This code is based on snippets in this issue - Improve built-in i18n "runtime translation" support.

// First locale is default, add additional after it
const availableLocales = ["en", "nb"];
// Direct macrolanguages to specific ones. Example: Norwegian becomes Bokmål
const localeMappings: { [key: string]: string } = { no: "nb" };

let locale =
  availableLocales.find((l) => navigator.language.startsWith(l)) ??
  availableLocales[0];
window.document.documentElement.lang = locale;
if (locale in localeMappings) {
  locale = localeMappings[locale];
}

if (locale === availableLocales[0]) {
  platformBrowserDynamic()
    .bootstrapModule(AppModule)
    .catch((err) => console.error(err));
} else {
  // fetch resources for runtime translations. this could also point to an API endpoint
  fetch(`static/assets/i18n/messages.${locale}.json`)
    .then((response) => {
      if (!response.ok) {
        throw new Error(`HTTP error ${response.status}`);
      }

      return response.json();
    })
    .then((result) => {
      loadTranslations(result);

      platformBrowserDynamic()
        .bootstrapModule(AppModule)
        .catch((err) => console.error(err));
    });
}

This detects the user's prefered language via navigator.language and if it's not the default it loads the necessary json file. The last step is documenting how to add new translations. See the README file in glitchtip-frontend.

GlitchTip is a lightweight and open source application monitoring tool that works great with Angular. It's also sentry sdk compatible. If you found this article useful, consider giving GlitchTip a try.