Step 3: i18n, multi-lingual support

Fun fact: i18n stands for internationalization and can you guess what 18 means? Hint: it’s the same meaning as 11 has in a11y.

We are going to support English, because everybody speaks it, and Russian, because I speak it. Let’s dive in: https://github.com/laas-sh/laas-sh-app/pull/3/files (deployed to https://03-multilingual.laas.sh).

We’ll need to render pages in correct language in SSR based on a cookie, so we make some changes to support cookies server-side in server.ts:

Just following instructions from @ngx-utils/cookies npm package here. What this does is it attaches parsed cookies to the request object by using cookie-parser and then injects both request and response Express objects to Angular DI container, so the cookies can be used in the app during server side rendering.

There are a few changes app.module.ts to enable multi-lingual support in the app:

Here, we add preboot – a neat npm module that helps with transition between server side rendered HTML and fully bootstrapped app – it’s a nice addition to any app with SSR (should have been part of the previous PR, but oh well). Once server side rendered HTML is delivered to the user, the browser will display the page and then begin bootstrapping the SPA code. So there’s a period of time when user can interact with the HTML already, but our Javascript is not bootstrapped yet, so we will lose these user interactions and leave the user frustrated. Preboot solves this problem by buffering the interactions and then replaying them once the app is ready to process user-initiated events.

For internationalization we are using ngx-translate – it’s a much easier solution than the one explained in the official Angular docs; the official one doesn’t actually support Russian correctly – there’s no ru-RU locale for example (USA sanctions?), and the other ru-* locales didn’t work for me. On the other hand, ngx-translate is very simple and free of problems. On the browser side, we are using TranslateHttpLoader to load translation files over HTTP, note how TranslateModule is bootstrapped for DI. We want to preload the translation before the app is considered bootstrapped – Angular provides APP_INITIALIZER mechanism just for that and we are going to add appInitializerService.init()to the bootstrapping process: the function provided for useFactory just needs to return a Promise so Angular can wait until that’s resolved before the app is considered bootstrapped.

On the server side, TranslateModule is bootstrapped in app.server.module.ts:

TranslateServerLoader is a simple service to load translation by language from the file system on the server side, it’s defined in translate.server.loader.ts:

The actual translation is performed by translate pipe; here’s how it looks like in footer.component.html:

If you pull this branch (git checkout 03-multilingual) and npm start, you can see how this works in your browser at http://localhost:4200/:

We can also validate that server side rendering reflects selected language by parsing the cookies; to do that, run with SSR (npm run build:ssr && npm run serve:ssr and open http://localhost:4000), select Russian language in the dropdown, then refresh the page and view source, you’ll notice that page is rendered correctly, with the Russian translations:

Log In button in the header.component.html is translated the same way:

All language-related functionality is encapsulated in language.service.ts:

Here, we expose the currentLanguage$ observable, init() method returns a Promise used in APP_INITIALIZER bootstrapping process, and setCurrentLanguage is used to switch language (when user makes a selection in the footer dropdown). We also deal with cookies here. Note how language will default to translateService.getBrowserLang() if no cookie is set.

Translation files are found under apps/frontend/src/assets/i18n/. English “translation” in en.json is actually an empty object at this point:

The reason this object is empty is that when translation key is not found, it’s used as is, so we don’t have to translate “Blog” for example (to English that is), as it’s the key for other languages and can be used as is. When we need to translate bigger blocks of text, we can give them names – same idea as variable names, – and then provide “translation” from the variable name (key) to English as well as other languages.

Russian translations are found in ru.json:

Even though we only deal with one cookie at this point, we’re introducing enum Cookie with a single key – it’s always a good idea to list things like cookies in an enum, so that we can quickly see which cookies we work with by looking at the file cookie.enum.ts:

For the languages, we extend language.enum.ts with language names – these are never translated and are used in the footer as is, each language name specified both in English and in the native language to make sure the users are never confused:

I hope you enjoyed this tutorial, and found it helpful. We’re getting ready for world domination, so i18n is a must. And, oh! – btw, – 18 stands for the number of omitted characters in the word internationalization (same as 11 stands for the number of skipped letters in accessibility) 🙂

Cheers,
Andrew

Published by Andrew Leschinsky

Founder and tech lead. https://twitter.com/ALeschinsky