How could you implement a database-approach to Angular internationalization?
I can suggest you to use ngx-translate
(known as ng2-translate
before) library, which has way better API than internationalization built-in into Angular... This way you can load translations from static json file (can be generated by backend) or e.g. add toml-loader
and store your translations like in the example below (in file assets/i18n/en.toml
):
[homepage]
title = "Homepage title"
contact = "Contact"
[auth]
login = "Log in"
logout = "Log out"
[settings]
settings = "Settings"
body = "<b>HTML</b> is fine here"
and use it this way:
<h2>{{'settings.settings' | translate}}</h2>
<p [innerHTML]="'settings.body' | translate"></p>
{{'auth.logout' | translate}}
To set it all up you will need basically few lines of code:
import * as translation_en from 'toml-loader!../assets/i18n/en.toml';
@Injectable()
export class AppService {
constructor(private translateService: TranslateService) {
translateService.setDefaultLang('en');
translateService.setTranslation('en', translation_en);
translateService.use('en');
}
}
Hope it helps!
I would suggest you use angular-l10n library. This is an open source alternative to i18n for angular localization. It uses the JSON format and supports also loading from services. Here is also the link to the documentation. angular-l10n configuration Search for "Loading the translation data" on the page to find the information about loading from a Web API.
Please keep in mind that the creator of ngx-translate
is now in the Angular i18n core team, and as part of Angular 5.x is working on making i18n much better. E.g. translation service, runtime translation switching in AOT, and much more.
See here: https://github.com/angular/angular/issues/11405#issuecomment-343933617
So I would recommend to stick with Angular out-of-the-box i18n.
For the website of my company we are using Text United for translations, and that works pretty well. The only issue we have is that by default the HTML tags end up in the translation tools. Our solution is:
- using XTB
- In Text United, using custom XML parser. Exclude
ph
elements.
Text United has paid options, to hire translators to do the work for you in any language. Of course you can do it yourself as well. Every time you just upload the source language, and it matches already translated items.
Here's my approach on dealing with i18n, including the use of ngx-translate while loading the translations from the database.
When it comes to translations my backend and frontend are seperated. The translations are not shipped within an angular build or a server bundle, but through an HTTP rest call which got the information from the underlying database. All translations are loaded on startup, brought into a JSON structure and can then be delivered to the frontend, where ngx-translate takes care of the rest.. Here's a simple order of events to successfully load translation from a database and make them accessible to the frontend.
- save translations in the database
- load translations on backend startup (or implement reload mechanism, maybe through REST)
- map translations to key-value-pair JSON objects
- make the JSON objects accessible through a REST api
- frontend loads JSON objects through this REST api
- use JSON object in angular with ngx-translate
benefits
I will later explain more on how this can look, just a quick note on which benefits this database-rest-approach brings:
- all translations stored in one place (a single table)
- missing translations for a language can be avoided (NULL-checks)
- double key allocation can be avoided (PRIMARY KEY)
- update of translations during runtime is possible
- translation process can be held outside of the project (updating files in project structure not necessary)
Let's see how this can be achieved.
database
Translations usually consist of simple key value pairs based on translation files, which I never really liked. So instead, I save my translations in a single table with a key column and a translation column for every language I have, for example something like KEY | EN | FR | DE
with values like button.close | close | près | schließen
. The key represents the same key it would in a normal file, but instead of a seperated file per language the translations are saved in a single column each.
backend mapping to JSON objects
I like to load the whole table at once to prepare every language for the frontend delivery at once. This can usually be done once on the backend startup and the result can be kept in memory to avoid to many database calls. The table has to be seperated into key-value-pair JSON objects for every language column. Every resulting language object then contains the database keys as their keys and the translations as their values.
var EN = {
...
"button.close": "close",
...
}
var FR = {
...
"button.close": "près",
...
}
var DE = {
...
"button.close": "schließen",
...
}
This is just an array-to-object mapping which, depending on the server language, is usually pretty simple (I can share my code for node.js if needed). The result is a list of JSON language objects, each having their translation as key-value-pairs, that can afterwards be accessed.
HTTP rest call
The translations are now pretty much in the same format as a normal translation file would be (key-value-pairs), just kept in memory and not in a file. With a simple HTTP api call for a specific language, you can access this list, take the translation object of this language and send it straight the frontend. Here's a node.js
express example.
translationRouter.route('/:lang').get(function (request, response) {
// load translation key-value-pair object for requested language
response.send(translationService.getTranslations(request.params.lang));
});
ngx-translate
The way ngx-translate works is pretty straight forward. Translations are loaded into the angular app, the translation keys are specified in the app and then are dynamcially replaced by the translation values from the provided language. As stated by others it supports different ways to load the translations, for example plain old translation files or self implemented loaders, like HTTP loaders. Here's a simple HTTP loader that loads translations through a REST call (see above).
import { TranslateLoader } from '@ngx-translate/core';
import { Observable } from 'rxjs/Observable';
import { HttpClient } from '@angular/common/http';
import '../rxjs-operators';
export class TranslationLoader implements TranslateLoader {
constructor(private http: HttpClient) { }
getTranslation(lang: string): Observable<any> {
return this.http.get("/api/translation/" + lang);
}
}
The only trick is to specify this loader as the main loader, which can be done in the app.module. Here's an example that uses the above HTTP loader (and is also working for AOT).
import { TranslateModule, TranslateLoader } from '@ngx-translate/core';
export function HttpLoaderFactory(http: HttpClient) {
return new TranslationLoader(http);
}
...
@NgModule({
imports: [...,
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useFactory: (HttpLoaderFactory),
deps: [HttpClient]
}
}), ...]
})
Instead of requesting a file, ngx-translate uses the specified TranslationLoader to get its key-value-pairs, which is exactly what we're providing through our rest call. Easy peasy. For this case a language can be specified to be loaded, plus a fall back language in case a value is not found. Here's an example for loading the translations of a default language plus the ones for the browser language.
// fallback language
this.translate.setDefaultLang('en');
// browser language
this.translate.use(this.translate.getBrowserLang());
The documentation of ngx-translate is pretty good, there are different ways to use it, for example through a service, a directive or a pipe, plus you can also parameterize the translations.
additional info
reloading translations
As stated in the benefits list, you can also reload the translations during runtime, which is probably a more complicated thing when bundling an app for delivery. You can simply provide a HTTP rest call for admins that executes exactly the same translation loading procedure as on startup. This way translations can be reloaded, remapped and stored in memory. Newer page requests will automatically use the reloaded translation objects.
live language change
Some ways of using ngx-translate allow instant translation switches (for example through the directive). This way loading a different language in angular (through a simple this.translate.use(lang)
call) will instantly switch the shown translations without relaoding the page or the visible components, that's actually pretty neat, but unfortunately not working for all ways of usage.
limits of ngx-translate
Although ngx-translate is really easy to use, it has limits. One of it is for example the usage of the ngx-translate directive in combination with most of the angular material directives, because angular material directives (for example buttons) will create subtree structures and ngx-translate only translate keys on the first child (at least I think it does). So it's cool to use, but sometimes a bit tricky.
I think that's it. I'm currently using this approach and I'm very happy with how it turned out. It's a little work to get it started but once everything is rolling it can be pretty useful.