How to Enhance Internationalization (i18n) Support Using @messageformat and ngx-translate-messageformat-compiler in Angular Applications

How to Enhance Internationalization (i18n) Support Using @messageformat and ngx-translate-messageformat-compiler in Angular Applications

In the digital landscape where diverse user bases and global reach are paramount, localizing content for different languages is a critical task. One efficient solution to this task lies within message formatting in JavaScript, particularly in the context of Angular applications. Two libraries, @messageformat and ngx-translate-messageformat-compiler, play pivotal roles in enhancing internationalization (i18n) support. By providing localized content that respects the grammatical and structural peculiarities of different languages, these libraries significantly improve the user experience.

Understanding @messageformat and ngx-translate-messageformat-compiler

@messageformat is a standalone library for handling message formatting in JavaScript. It provides a comprehensive implementation of the MessageFormat syntax, supporting a wide range of language features and customization options. It can be used independently or in conjunction with ngx-translate-messageformat-compiler to handle message formatting in Angular applications.

The ngx-translate-messageformat-compiler library is an extension of the ngx-translate library, and allows for message formatting using the MessageFormat syntax. Message formatting is essential for handling language-specific pluralization, gender, and other complex language patterns. It compiles the MessageFormat strings into JavaScript functions, enabling efficient translation and rendering of dynamic content.

Real-World Applications of @messageformat and ngx-translate-messageformat-compiler

Both libraries are important in Angular applications because they allow for the translation and formatting of user-visible text, accommodating the grammatical and structural nuances of different languages. This improves the user experience by providing localized content that feels natural and appropriate to users in different regions or language preferences.

Tackling Pluralization Differences Across Languages

One of the use cases of this library is tackling pluralization differences between languages. Pluralization rules can vary widely across languages, creating a complex problem when it comes to translating and formatting user-visible text. For instance, in English, it’s fairly straightforward—we typically add an “s” for plurals. But in many other languages, the rules are more complicated, with different forms used depending on the number of items.

For example, consider how the phrase about the number of results found would be presented in English, Russian, and German:

  • English: Found no result. Found one result. Found # results.
  • Russian: Результатов не найдено. Нашел один результат. Найдено 3 результата (In between plural form, and it is not easy to solve). Найдено # результатов.
  • German: Keine Ergebnisse gefunden. Ein Ergebnis gefunden. Es wurden # Ergebnisse gefunden.

In the case of Russian, the in-between form for three results illustrates the complexity of plural forms that cannot be directly translated from English. Similarly, the German phrase changes structure entirely depending on the number of results.

The @messageformat and ngx-translate-messageformat-compiler libraries are designed to handle these kinds of linguistic complexities, ensuring that your application communicates effectively and naturally in any language.

Installation

Now that we understand the significance of @messageformat and ngx-translate-messageformat-compiler, it’s time to get practical. In this section, we offer a step-by-step guide to set up and install ngx-translate in your Angular application. Let’s start with the installation process.

First, let’s set up ngx-translate:

npm install @ngx-translate/core @ngx-translate/http-loader --save
Adding library to root module app.module.ts:
	import { NgModule } from '@angular/core';
	import { BrowserModule } from '@angular/platform-browser';
	import { HttpClient, HttpClientModule } from '@angular/common/http';
	import { TranslateCompiler, TranslateLoader, TranslateModule } from '@ngx-translate/core';
	import { TranslateHttpLoader } from '@ngx-translate/http-loader';
	import { TranslateMessageFormatCompiler } from 'ngx-translate-messageformat-compiler';
	
    import { AppComponent } from './app.component';
	
    export function createTranslateLoader(http: HttpClient) {
        return new TranslateHttpLoader(http, './assets/i18n/', '.json');
	}
	
    @NgModule({
    	declarations: [
        	AppComponent,
    	],
    	imports: [
        	BrowserModule,
        	HttpClientModule,
        	TranslateModule.forRoot({
            	loader: {
                	provide: TranslateLoader,
                	useFactory: (createTranslateLoader),
                	deps: [HttpClient],
            	},
        	})
    	],
    	bootstrap: [AppComponent],
	})
	export class AppModule {
	}

Then, set up simple application with translation:

app.component.html:
<ul>
  <li *ngFor="let lang of languages">
	<a (click)="changeLangTo(lang)">
  	{{ lang }}
	</a>
  </li>
</ul>
 
<section>
  <p>
	{{ "GREETING" | translate }}
  </p>
</section>
app.component.ts:
import { Component } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
 
@Component({
	selector: 'app-root',
	templateUrl: './app.component.html',
	styleUrls: ['./app.component.css'],
})
export class AppComponent {
	languages = ['en', 'ru', 'de'];
 
	constructor(private readonly translateService: TranslateService) {
    	translateService.setDefaultLang('en');
  	  translateService.use('en');
	}
 
	changeLangTo(lang: string): void {
    	this.translateService.use(lang);
	}
}
en.json:
{
  "GREETING": "Hello!"
}
ru.json:
{
  "GREETING": "Привет!"
}
de.json:
{
  "GREETING": "Hallo!"
}

And the result will look like this:

Result showing a cursor toggling between languages

Now, it’s time to add @messageformat and ngx-translate-messageformat-compiler:

npm install @messageformat/core ngx-translate-messageformat-compiler --save

After installation, TranslateModule needs to be configured to use TranslateMessageFormatCompiler:

	import { TranslateCompiler } from '@ngx-translate/core';
	import { TranslateMessageFormatCompiler } from 'ngx-translate-messageformat-compiler';
	
    ....
	
    @NgModule({
    	declarations: [
        	AppComponent,
    	],
    	imports: [
        	BrowserModule,
        	HttpClientModule,
        	TranslateModule.forRoot({
            	loader: {
                	provide: TranslateLoader,
                	useFactory: (createTranslateLoader),
                	deps: [HttpClient],
            	},
            	compiler: {
                	provide: TranslateCompiler,
                	useClass: TranslateMessageFormatCompiler,
            	},
        	})
    	],
    	bootstrap: [AppComponent],
	})
	export class AppModule {
	}

It’s also possible to configure MessageFormat by providing a configuration object for the MESSAGE_FORMAT_CONFIG injection token. Here’s the default:

{
	biDiSupport: false,
	formatters: {},
	strictNumberSign: false,
	currency: "USD"
}

MessageFormat instances offer various options to control their behavior. These options include customFormatters, biDiSupport, and strict mode, which allow for customization and influence over how MessageFormat operates. Learn more in the official docs.

Usage

After setting up ngx-translate, let’s now tackle our initial problem: incorporating these translations into our .json files using the appropriate format. When using MessageFormat in conjunction with ngx-translate, it’s important to note some key differences from ngx-translate’s default syntax:

  1. Accessing object properties in placeholders is not supported. For instance, syntax like Hello {name.prop} {name.prop2} won’t work.
  2. Simple placeholders are enclosed in single curly braces instead of double. So, we need to use Hello {name} instead of the default Hello {{name}}.

Let’s examine the basic structure of a formatter to better understand these concepts:

{variable, formatter, option {#(variable placeholder) interpolated text}}

Here are the translation strings:

	en.json
	{
    	"RESULT_TEXT": "{count, plural, =0{Found no results} one{Found one result} other{Found # results} }"
	}
 
	de.json
	{
    	"RESULT_TEXT": "{count, plural, =0{Keine Ergebnisse gefunden} one{Ein Ergebnis gefunden} other{Es wurden # Ergebnisse gefunden} }"
	}
 
	ru.json
	{
    	"RESULT_TEXT": "{count, plural, =0{Результатов не найдено} one{Нашел один результат} few{Найдено # результата} other{Найдено # результатов} }"
	}

Let’s modify our code to see the results:

app.component.html:
....
 
<p>
	{{ "RESULT_TEXT" | translate : { count: resultCount } }}
</p>
<button (click)="decrement()">
	-
</button>
|
<button (click)="increment()">
	+
</button>
 
....
app.component.ts:
....
 
resultCount = 0;
 
....
 
increment(): void {
	this.resultCount++;
}
 
decrement(): void {
	this.resultCount--;
}
 
....

Finally, we get:

Finished result showing a cursor toggling between languages and number of results

Extend Capabilities with Additional Formatters

It doesn’t end here. The versatility of MessageFormat extends beyond the basic usage, offering an array of ready-to-use formatters to accommodate a variety of scenarios, enhancing its adaptability and functionality. The options include:

  • Select
  • Plural – what we used here
  • Date
  • Duration
  • Number
  • Time

MessageFormat’s flexibility also allows us to create custom formatters for unique or exceptional cases that may arise. This ability to tailor the library to our specific needs underlines the power and utility of MessageFormat in the world of internationalization.

Conclusion

In summary, @messageformat simplifies the task of creating dynamic and localized messages by allowing placeholders and variable substitution. It eliminates the need for manual string concatenation and provides support for language-specific rules and variations, making it easier to build multi-language applications.

Subscribe to our newsletter

Stay informed about the latest updates, news, and insights.