Sitemap
Angular In Depth

The place where advanced Angular concepts are explained

Typed translations in Angular

Translations and i18n using Angular’s dependency injection, TypeScript and lazy-loading

6 min readNov 21, 2018

--

Press enter or click to view image in full size
Press enter or click to view image in full size

AngularInDepth is moving away from Medium. More recent articles are hosted on the new platform inDepth.dev. Thanks for being part of indepth movement!

If you have ever had a problem with maintenance of internationalization and translation in your projects, there might be a simple solution for you. If not, then you are pretty satisfied with your current solution, but maybe you’ll realize what can be improved.

What people usually do?

In most of our projects, we use the ngx-translate for handling translations. Angular CLI’s i18n does not support runtime translations yet (issue #16447). Using i18n in a project seems (and usually is) complicated and a lot of developers are stuck with solutions that are not always reliable in long-term.

How can we improve?

Let’s see what are the basic needs:

  • A way to check if all translation files are correct
  • A way to check that keys used to access translation are valid

After some failed concepts on validation tools, we found a solution. TypeScript knows all we need and dependency injection will provide the way, with just a little bit of router’s elegant lazy-loading.

What to expect

  • Translations have a type definition inferred from a default translation
export type Translation = typeof en;
  • Translation data are typescript object

Example translation object

  • The translation is injected via dependency injection provider

Example of translation injection and its usage in component and template

  • Each site mutation is lazy loaded as a module that consists of imported wrapped site module (shared for all mutations) and its own i18n stuff.

Pros

  • No missing (untranslated) keys and parts of the application
  • A quick way to validate multiple translate files (objects) compatibility
  • Type-safe translation — the compiler will tell if something is not in order
  • Translation data lazy-loaded with route module
  • Instant translation — nothing needs to be dynamically loaded
  • No need for in-template impure pipes
  • Translation type can be inherited from default translation
  • Routes are prefixed with language/i18n abbreviation and can be localized entirely
  • Translations can be simple strings, functions with a string literal or any other custom format that you decide to support

Cons

  • A requirement of lazy-loading route modules (if we want the i18n modules and translations lazy-loaded)
  • Loading translation dynamically from another source (server) is a bit tricky and require extra steps

Minimal setup

It is a lot easier to set up on a greenfield project, in future I would even consider creating a schematic for Angular CLI to generate such a project. But for now, there is this 5 step setup guide and demo repository.

If you’d like to see the code first take a look at the demo, and come back for more details. You can also have a quick look and try it on stackblitz.

  1. Lazy loading site routes
    Minimal setup requires lazy-loaded routes only at the first level. This will enable your site to have separate language sections all using the shared “site” module and separate “site” translation modules for language-specific configuration.
    One of the crucial things is creating site routing it is just a route configured to lazy-load translated site modules.
  2. Wrapping your app into a shared site module
    Create one “site” module that will contain your whole site. It is basically a module containing all of the stuff necessary for your project views. It covers almost the same role as app.module.ts had, with the exception that it is not bootstrapped but imported in language specific “site” modules.
  3. Internationalization (i18n)
    Create one module for each translation you want. Each has a separate file (module) and can be easily added later so don’t bother by creating all of them now. Just pick one or two so you can have a working demo soon.
  4. Translations
    The translation file itself is just a basic TS file with a constant object. The amazing thing about this over classic JSON is that we can define properties that are any type-able value. Usually, they are string or key-value pair nested object, but they also can be a function handling a specific use case.
  5. Using translation data
    Inject the translation data in the constructor and they should now be available for usage in both component and template.

After each big step, we should verify everything is in order, so we eliminate the problems when they started. At this point, you should be able to load your application same as before but routing/en/my-route and /cs/my-route should have different languages loaded.

Network lazy-loaded modules

Press enter or click to view image in full size
Demo of lazy-loaded i18n modules

Interpolation, plurals and message formatting

Imagine classic interpolation scenario You need to do {{n}} to unlock this or plurals both can be handled pretty easily with code and for each language separately.

If the project does not require advanced concepts of custom messages, using a function for interpolation/plural is easy and hackable.

// Instead of generic message `Supported languages: ${n}`,
// we can have this custom message
(n: number) => `Demo supports ${n} language${n === 1 ? '' : 's'}.`,

If this is not enough, it is easy to integrate support for the ICU message format. There are multiple implementations in JavaScript and all you need is a formatting string that will be passed to render function along with parameters.

Intellisense and auto-completion IDE support

Intellisense for translations (WebStorm)
Press enter or click to view image in full size
Auto-completion of the available translation values (VS Code)
Type is supported even in a template (VS Code)
Help with inherited type interface and error for an invalid key (VS Code)

Validation and compilation

All errors and inconsistencies in translations are caught at compile time.

Press enter or click to view image in full size
Error after adding subtitle to only one of the translations
Press enter or click to view image in full size
Error when trying to access non-existing translation with an invalid key

Advanced concepts

There are potential improvements available for some more advanced concepts that are not currently built into this setup but could be later included. Few to mention are:

  • Localized routing (translated routes)
  • Internationalization by region (country) instead of the translation
  • Splitting translations to multiple lazy-loaded files

Conclusion

It seems like we managed to cover all of the requirements we defined at the beginning. This solution was tested on multiple projects, even with the server side rendering via universal and so far no issues were found.

Try it out yourself on this small example project. In case of some questions on this topic feel free to ask in comments.

PS: As it may require a little more steps if the project already uses some translations, I will be posting a migration guide for projects using ngx-translate. Follow me here or on twitter for updates.

--

--

Angular In Depth
Angular In Depth

Published in Angular In Depth

The place where advanced Angular concepts are explained

Vojtech Mašek
Vojtech Mašek

Written by Vojtech Mašek

Head of Engineering @ FlowUp.cz ⬆️ Angular & TypeScript enthusiast, Speaker 🔊, Stay at home astronaut 🌍