I would like to know how I can implement internationalization in an Angular v11 project by separating the translation files by module or by page.
I’m learning about i18n internationalization, and the examples I found use a single file for each language, with all translations for that language stored in that one file. However, I would like to at least separate it by modules because my project contains a lot of text, and a single file would end up becoming large and confusing. Does anyone have any tips or suggestions?
I’ve seen that it’s possible to do this, but I haven’t had much luck finding examples.
I was working on an example project, but it became very apparent that you're trading the size of the translation file for the overhead of having to combine the various translation files and/or synchronizing multiple instances of TranslateService
.
tl;dr this is a bad idea. The documentation recommends a centralized approach (only specifically calling out using a folder to group translation files, but implying that all translation files should be there):
We recommend using an
i18n
folder within theassets
orpublic
folder to organize your translation files in one place.
Regarding your concern that "a single file would end up becoming large and confusing", the managing translations section of the documentation covers a way to streamline editing. I'm not including it in my answer because it promotes a specific (paid) product.
Additionally, the why [...] contextual ids [...] section of the documentation recommends using nested translation definitions that allow for grouping by developer-centric aspects of the codebase like pages, modules, components, or features which makes organizing (and therefore finding) translations easier:
{
"app": {
"language": "current language",
"settings": "Application settings",
"log-out": "Log out"
},
"product-page": {
"call-to-action": "Sign up today!",
"pricing": "pricing",
"license-notice": "Licenses are per-user and subject to certain terms",
"terms-of-license": "license terms"
},
"print-feature": {
"header": "Printing has never been so easy",
"from-dialog": {
"header": "Intuitive dialogs guide users through the printing process",
"legal": "All dialogs conform to WCAG 2 standards"
},
"from-shortcuts": {
"header": "Keyboard shortcuts are provided for power users",
}
},
}
But if you did want to do this...
In the default usage case, each instance of TranslateService
opens a single <lang>.json
file. If you want a <lang>.json
file per module/component, then you either need to manually add the translations from that module/component's translation file to the translation keys known to the existing TranslateService
or you need a new instance of TranslateService
for the module/component's translation file.
The first approach should be possible using setTranslation
but you'll have to load the <lang>.json
file into memory and parse it into a InterpolatableTranslationObject
yourself.
I'm not confident this approach works as expected and you might encounter some issues (maybe having to manually trigger change detection, maybe having to manually switching the language away from and back to the current language to trigger re-translations, maybe having to defer loading components until the current module/component has finished patching its translations, etc), I don't know.
But here's what a rough sketch could look like:
import { Component } from '@angular/core';
import { InterpolatableTranslationObject, TranslateService } from '@ngx-translate/core';
// This component should be loaded at the root of this
// feature module so that translations for the components
// in this module can be patched.
@Component({ /*...*/ })
export class FooModuleRootComponent {
// The `HttpClient` instance can be provided by the application
// because we don't have any specific requirements.
// The `TranslateService` instance is also provided by the
// application so that we have a single source of truth for
// setting the current language and getting translations for
// the current language.
constructor(
private readonly http: HttpClient,
private readonly translateService: TranslateService
) {
this.appendTranslations('en');
this.appendTranslations('es');
}
/**
* Fetches this module's translation file for the provided language
* and appends the contained translations to the current translation
* service.
*/
appendTranslations(lang: string) {
// InterpolatableTranslationObject and the child InterpolatableTranslation
// can be reduced to effective aliases for `Record<string, string | string[]>` and
// `string` respectively, so we should be fine to choose to type the raw JSON
// response as `InterpolatableTranslationObject` here.
// This assumes simple use cases.
this.http.get<InterpolatableTranslationObject>(`path/to/feature/${lang}.json`).subscribe({
next: translations => {
// The last parameter (shouldMerge) must be true or this will overwrite
// the translations loaded elsewhere.
this.translateService.setTranslation(lang, translations, true);
},
});
}
}
You might also try following this answer by daniel-sc to Update/Merge i18n translation files in Angular.
An alternative/new solution (to the unmaintained ngx-i18nsupport) is https://github.com/daniel-sc/ng-extract-i18n-merge
After installing via
ng add ng-extract-i18n-merge
translations will be extracted and merged with the command:
ng extract-i18n
The second approach requiring a meta service (or injection token, more likely) to synchronize several instances of the TranslateService
is an immediate red flag to me that this is not a good approach. I wouldn't try it unless it was the last possible option.