Typed translations in Angular

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

Vojtech Mašek
Nov 21, 2018 · 6 min read

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?

How can we improve?

  • 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

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

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

Demo of lazy-loaded i18n modules

Interpolation, plurals and message formatting

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)
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

Error after adding subtitle to only one of the translations
Error when trying to access non-existing translation with an invalid key

Advanced concepts

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

Conclusion

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

The place where advanced Angular concepts are explained

Vojtech Mašek

Written by

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

Angular In Depth

The place where advanced Angular concepts are explained

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade