GlitchTip 2.0 adds support for multiple languages. This post will explain how we did it.
/en/foo
.There are a number of packages to add i18n support to Angular.
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.
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:
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.