Embedding translations in your JS bundles with ngx-translate
Embedding translations in your JS bundles can be useful to speed things up if you know that users need to switch between languages regularly. Here's how to do it
You may not always want to fetch your translations through additional HTTP requests. Let me show you how you can safely embed them in your JS bundles using TypeScript and ngx-translate.
As far as I know, it seems that most people prefer to load translations for the current language at runtime, through HTTP requests as the application starts and even later on when the user decides to switch to another language. In my application though, I’ve chosen to include all of the translations in the main JS bundle.
With this approach, there’s no need to load translations later on; everything is there right from the start. It does indeed put an additional weight on the initial load time, but since is this going to be an offline-first application, this is not a big issue for me. It can also be optimized, for instance by isolating translations in their own bundle, limiting the impact and allowing for longer term caching. I’m not going to cover that here though.
In this article, I’ll describe how I’ve implemented this. In addition, I’ll also shared the types I’m using with TypeScript to prevent some mistakes.
Translation files
First of all, let’s create two translations files. Here I’ll assume en_US and fr_BE:
en_US.json:
fr_BE.json:
As you can see, nothing fancy here. I’m just wrapping all translations in an “I18N” object so that all translations are prefixed by “I18N.” whenever they’re used within the app; making it easy to locate them later on.
Safely importing the translations at compile time
In order to include the translations in our bundle, we’re first going to load these using TypeScript:
The import statements above takes advantage of a feature introduced in TypeScript 2.9 called “resolveJsonModule”, which allow to import JSON files easily. This feature can be enabled by setting the corresponding property to true
in tsconfig.json:
"resolveJsonModule": true,
The last line is interesting because it creates a custom type that represents the shape of a translations file. I’ll use this in a second to ensure that all of the translation files have the same exact shape, which allows me to ensure that I haven’t forgotten to translate something in one of the languages supported by the application. It’s not perfect, but it helps. What I do is that I write the “master” translation first (English), then I translate to the other supported languages.
Once loaded, I add the translations into a map/dictionary structure:
Pay attention to the type associated with each key in the map; it is indeed the custom type that we have created based on the english translations file. This is how I ensure that all the translation files have the expected shape. This code will not compile if any of the translation files diverges from the “master”.
Next up is the representation of a single language. I’ve taken this one 1:1 from my current project; you can enrich or simplify it as you wish:
It simply gives the base information about a language:
- An ISO (ISO 639–1 + ISO 3166–1 alpha-2) code
- Each of its parts separately (code and region)
- A translation key for the language: useful for language change components) (e.g., English)
- A short translation key for the language: useful for condensed language change components (e.g., EN)
We can also define a class to hold the list of languages:
This class is useful because it allows to get a specific language with a nice/readable syntax:
Languages.EN_US
In addition, thanks to the supportedLanguages
array, we can easily implement a function that looks up a Language
object based on its code or ISO code:
Great, now we have easy and type-safe means to access our languages and translations.
Bonus: noticing missing translations
Sometimes, we’re so focused on getting to the next task that we forget to add the required translations. And, if we do, then we need to see that they’re missing.
One way to make missing translations more apparent is to implement the MissingTranslationHandler
interface of ngx-translate:
At least that won’t go unnoticed for long ;-)
Injecting our translations into ngx-translate
Now, let’s see how to inject our translations into ngx-translate.
Luckily for us, ngx-translate has the notion of a TranslateLoader. The goal of this interface is to provide means retrieve translations for a given language.
ngx-translate defaults to the HTTP loader, which uses Angular’s HttpClient to retrieve the translations at runtime through HTTP requests. Since we want to skip that and embed our translations, we need a custom implementation:
Since we have already imported our translations, there’s no need to fetch them; we simply need to extract the corresponding language from the translations map.
To configure ngx-translate, we need to create a TranslateModuleConfig
object:
Finally, to make use of it, we need to pass it along when we import the module:
By the way, notice the calls that I make at the end to the registerLocaleData
method of Angular. This is how we can load additional locale information provided by Angular, which is necessary if you use things like the DatePipe
, CurrencyPipe
, DecimalPipe
or PercentPipe
. By default, Angular only includes locale data for en-US. Since we have this codified in our application through the Languages
class, we can easily reuse it here. Check out the internationalization guide of Angular if you want to know more.
Conclusion
In this article, I’ve shared the code that I use in my current application to statically load the translations and include them as part of the main JS bundle.
Then, I’ve explained how to integrate the static translations with ngx-translate using a custom loader. This should also be possible with libraries like Transloco, but I didn’t dive into that one yet ;-)
Finally, I’ve also explained how to avoid forgetting some translations by leveraging some cool TypeScript features.
That's it for today! ✨
About Sébastien
I am Sébastien Dubois. You can follow me on X 🐦 and on BlueSky 🦋.
I am an author, founder, and coach. I write books and articles about Knowledge Work, Personal Knowledge Management, Note-taking, Lifelong Learning, Personal Organization, and Zen Productivity. I also craft lovely digital products . You can learn more about my projects here.
If you want to follow my work, then become a member.
Ready to get to the next level?
To embark on your Knowledge Management journey, consider investing in resources that will equip you with the tools and strategies you need. Check out the Obsidian Starter Kit and the accompanying video course. It will give you a rock-solid starting point for your note-taking and Knowledge Management efforts.
If you want to take a more holistic approach, then the Knowledge Worker Kit is for you. It covers PKM, but expands into productivity, personal organization, project/task management, and more:
If you are in a hurry, then do not hesitate to book a coaching session with me: