How to build painless multi-language apps with Angular


I'm sure if you're reading this post is because either you're curious or you understand the pain of supporting multiple languages in Angular.

I love Angular and it's my main JS Modern Framework, but if something drives me nuts are its poor multi-lingual support. It's extremely over-complicated to my taste.

I have developed multiple websites and over 11 apps for Windows and Android and I cannot understand how the creators couldn't be inspired by Google's most important OS, Android.

In Android, you only need a couple of XMLs (en, es, etc.), minor adjustments in the UI XMls, or code something in Java/Kotlin and magically, you have an app that supports multiple languages.

After several attempts, I found ngx-translate, which leveraged my work significantly. So, how to use it?

npm install @ngx-translate/core @ngx-translate/http-loader --save

Next, configure your app.module.ts

1. Enable the translation service:

import { TranslateModule, TranslateLoader, TranslateService } from '@ngx-translate/core';

2. Configure the loader:

a. Root domain (www.mydomain.com):

export function translateHttpLoaderFactory(http: HttpClient) { return new TranslateHttpLoader(http); }

b. Sub-domains (myself.github.io/myapp):

export function translateHttpLoaderFactory(http: HttpClient) { return new TranslateHttpLoader(http, './assets/i18n/', '.json'); } 

In this option, you are going to configure the location of your JSON files.

3. Configure your constructor (my version auto-detects the browser language and set it by default):

availableLng = ['en', 'es'];

//start the translation service
constructor(private translateService: TranslateService) {
    //defines the default language
    let tmpLng = 'en';

    //gets the default browser language
    const currentLng = window.navigator.language.substring(0,2);

    if (this.availableLng.includes(currentLng))
       tmpLng = currentLng;

    translateService.setDefaultLang(tmpLng);
}

Your final app.module.ts might look like this one:

import { HttpClientModule, HttpClient } from '@angular/common/http';
//configure translation service
import { TranslateModule, TranslateLoader, TranslateService } from '@ngx-translate/core';
import { TranslateHttpLoader } from '@ngx-translate/http-loader';

/* IMPORTANT:
   This only works if you are setting your app in your main domain: www.mydomain.com
*/
export function translateHttpLoaderFactory(http: HttpClient) {
  return new TranslateHttpLoader(http);
}

/*
   For sub-domains like myself.github.io/myapp
   You need to use this code or a variation with the location of your assets:
*/

/*
export function translateHttpLoaderFactory(http: HttpClient) {
  return new TranslateHttpLoader(http, './assets/i18n/', '.json');
}
*/

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    HttpClientModule,
    TranslateModule.forRoot({
      loader: {
        provide: TranslateLoader,
        useFactory: translateHttpLoaderFactory,
        deps: [HttpClient]
      }
    })
  ],
  providers: [],
  bootstrap: [AppComponent]
})

export class AppModule {
  //define available languages
  availableLng = ['en', 'es'];

  //start the translation service
  constructor(private translateService: TranslateService) {
    //defines the default language
    let tmpLng = 'en';

    //gets the default browser language
    const currentLng = window.navigator.language.substring(0,2);

    if (this.availableLng.includes(currentLng))
       tmpLng = currentLng;

    translateService.setDefaultLang(tmpLng);
  }
}

After, create a new folder in assets called i18n. Inside of it, you are going to create the language assets like en.json:

{
  "Title": "Translation demo",
  "WelcomeMessage": "Welcome to the international demo application"
}

And es.json:

{
  "Title": "Demo de traducción",
  "WelcomeMessage": "Bienvenido a la aplicación de demostración internacional"
}

Now, inside the HTML part of your components, you can call it like this:

<h1 translate>Title</h1> 

Where translate indicates the tag that is going to be translated and Title the JSON key.

Can this be extended beyond HTML tags?

Definitely, what if you have a placeholder in an input?

You can use it like this:

<input placeholder="{{'Title' | translate}}" />

Or how do you use it from the TypeScript file?

First, enable it from the component constructor:

constructor(private translateService: TranslateService) { 
    this.translations = this.translateService;
}

Now, you can access it with a simple piece of code like this one (synchronous, but might return the same key if it hasn't loaded yet):

console.log(this.translateService.instant('WelcomeMessage'));

The best option would be to use the async option:

this.translateService.get('WelcomeMessage').subscribe((data: any) => { console.log(data); });

The greatest benefit in my experience with ngx-translate is that it works and looks similar to common app development in Android or Windows.

This post has some inspiration from Yaser's blog:

https://yashints.dev/blog/2018/01/17/multi-language-angular-applications

Plus, if you want to see a full action app using this framework, you can check it here:

https://fanmixco.github.io/gravitynow-angular

Also, here is its repository:

https://github.com/FANMixco/gravitynow-angular

Comments