How Major Frontend Libraries Handle i18n

Tags: frontend, flutter, ionic, react
Published: 2021-10-04 13:20:44

## How Major Frontend Libraries Handle i18n

One way for companies to reach new clients is to speak their language. To do that, developers need to use internationalization and localization in their applications to offer products and content in users’ native languages. 

*Internationalization*, or i18n (18 is the number of letters between *i* and *n*), is the process of building your product to support multiple languages. This can include separating your text from your code and using a library to format your dates based on different countries and time zones. Once your product is ready to add support for specific languages, you can move to localization.

*Localization*, or l10n, is the process of adding support for a specific region, country, or language. This is different from translating text into another language, though localization can include translation. Here are some things to keep in mind when localizing a product:

* Date formatting, such as DD/MM/YYYY vs. MM/DD/YYYY
* Name formatting, since in some countries, last names are displayed before first names
* Currency
* Measurements (imperial vs. metric system)

Images also must be adapted to a particular market, especially those displaying text.

This article will show how three major frontend libraries handle localization and how you can use them to create multilingual applications. You can see the code shown here on [GitHub](https://github.com/mariesta/how-major-libraries-handle-i18n).

Keep in mind that a headless CMS can help you achieve localization easily. [Strapi](https://strapi.io/), the leading open-source headless CMS with a 135,000-plus community of users, offers customizable solutions for managing and localizing your content. 

In less than an hour, you can use Strapi to have API endpoints and an admin panel ready to go. With GraphQL or Rest, you can consume any Strapi API endpoints from any client (Vue, React or Angular, for instance), which gives you great flexibility. 

## Flutter

Created by Google in 2017, Flutter is a library that is quickly gaining traction. As expected of a global company like Google, internationalization is part of the library and can be implemented almost instantly.

Flutter supports not only translated text but also plurals, number-and-date formatting, and right-to-left or left-to-right text. This makes it a solid choice for developers.

### Internationalize Your Flutter App

To start, update your `pubspec.yaml`. Add generate `true` to automatically generate the `.dart` files necessary for every locale that you will add.

```
    # ...
    dependencies:
      flutter:
        sdk: flutter
      flutter_localizations:  //Add this
        sdk: flutter.   // this
      intl: ^0.17.0.    // this
    
    flutter:
      generate: true     // and finally, this       
      uses-material-design: true
    
    # ...
```

Run `flutter pub get` to get the necessary packages.

Create a `l10n.yaml` file in your root directory. This tells Flutter where to find your translations and where to generate the dart files.

```
    arb-dir: lib/l10n
    template-arb-file: app_en.arb
    output-localization-file: app_localizations.dart
```

Then create an I10n directory in your lib folder and create your translation files. Here is an example of an `app_en.arb` file:

```
    {
      "appTitle": "Home Page"
    }
```
 
In your `main.dart` file, import the `flutter_localizations` dart package and add the localizations delegates and the supported languages. I used English and French here, but of course you can add your own.

```
    import 'package:flutter/material.dart';
    import 'package:flutter_localizations/flutter_localizations.dart'; // New import
    
    return MaterialApp(
      title: 'Flutter Demo App',
    // The Material, Cupertino packages and widgets will now be correctly localized
      localizationsDelegates: const [
        GlobalMaterialLocalizations.delegate,
        GlobalWidgetsLocalizations.delegate,
        GlobalCupertinoLocalizations.delegate,
      ],
      supportedLocales: const [
        Locale('en', ''), // English 
        Locale('fr', ''), // French
      ],
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );
```

Run the app with `flutter run`. You should see these files in your .dart-tool:

* `.dart_tool/flutter_gen/gen_l10n/app_localizations.dart`
* `.dart_tool/flutter_gen/gen_l10n/app_localizations_en.dart`
* `.dart_tool/flutter_gen/gen_l10n/app_localizations_fr.dart`

Now let’s add our localized message.

```
    import 'package:flutter/material.dart';
    import 'package:flutter_localizations/flutter_localizations.dart';
    //Our newly generated gen_l10n file
    import 'package:flutter_gen/gen_l10n/app_localizations.dart'; 
    
    return MaterialApp(
      title: 'Localizations Sample App',
      localizationsDelegates: const [
        AppLocalizations.delegate, // New delegate
        GlobalMaterialLocalizations.delegate,
        GlobalWidgetsLocalizations.delegate,
        GlobalCupertinoLocalizations.delegate,
      ],
      supportedLocales: const [
        Locale('en', ''),
        Locale('fr', ''),
      ],
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );
```

You can now access your translations through AppLocalizations. You could, for example, pass in a title to your HomePage like this:

```
    MyHomePage(title: AppLocalizations.of(context)!.appTitle)
```

### Limitations of Flutter

The internationalization package has few limitations, supporting many necessary features such as the handling of plurals or bidirectional text. Being a very new language, though, Flutter doesn’t possess the wealth of third-party packages offered with Ionic or React. Additionally, the bundle size is typically larger than 4 MB.

## Ionic

Older than Flutter, Ionic was created in 2013 and is a solid library offering developers the ability to have one codebase for any platform. Ionic offers support for many frameworks including Angular, Vue, and even React. I will focus on Angular here, as React will be covered below.

While Angular has a built-in internationalization module, the setup is harder for Ionic applications. As a result, two third-party libraries have emerged:

* [ngx-translate](https://github.com/ngx-translate/core)
* [transloco](https://github.com/ngneat/transloco)
 
While transloco is newer and offers features like SSR support, ngx-translate is a solid, reliable library that has been around longer and is loved by Angular developers. We’ll use ngx-translate as our translation library here.

### Internationalize Your Ionic App

To start, you will need to install the necessary library.

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

In your `src/app/assets`, add an i18n folder with your translations. For example, here is a `en.json` file:

```
    {
      "title": "Welcome",
      "description": "This is an Ionic app translated by ngx-translate"
    }
```

Head to your `app.module.ts` and add your modules (TranslateModule, TranslateLoader, etc.). This will tell your application where your translations are located and how to load them.

```
    import { NgModule } from '@angular/core';
    import { BrowserModule } from '@angular/platform-browser';
    import { RouteReuseStrategy } from '@angular/router';
    import { TranslateModule, TranslateLoader } from '@ngx-translate/core';
    import { TranslateHttpLoader } from '@ngx-translate/http-loader';
    import { HttpClientModule, HttpClient } from '@angular/common/http';
    import { IonicModule, IonicRouteStrategy } from '@ionic/angular';
    import { AppComponent } from './app.component';
    import { AppRoutingModule } from './app-routing.module';
    
    /* New function to load our translation files*/
    export function HttpLoaderFactory(http: HttpClient) {
      return new TranslateHttpLoader(http, "./assets/i18n/", ".json");
    }
    /* Add HttpClientModule & TranslateModule to our imports */
    @NgModule({
      declarations: [AppComponent],
      entryComponents: [],
      imports: [HttpClientModule, BrowserModule, 
        TranslateModule.forRoot({
            loader: {
              provide: TranslateLoader,
              useFactory: HttpLoaderFactory,
              deps: [HttpClient]
            }
          }),
        , IonicModule.forRoot(), AppRoutingModule],
      providers: [{ provide: RouteReuseStrategy, useClass: IonicRouteStrategy }],
      bootstrap: [AppComponent],
    })
    export class AppModule {}
```
  
In `app.component.ts`, set your default language.

```
    import { Component } from '@angular/core';
    import { TranslateService } from '@ngx-translate/core';
    @Component({
      selector: 'app-root',
      templateUrl: 'app.component.html',
      styleUrls: ['app.component.scss'],
    })
    export class AppComponent {
      constructor(public translate: TranslateService) {
        this.initializeApp();
      }
      initializeApp() {
        this.translate.addLangs(['en', 'fr']);
        this.translate.setDefaultLang('en');
      }
    }
```
  
Finally, try displaying some translated text.

```
      <div id="container">
        <strong>{{ 'title' | translate }} </strong>
        <p>{{ 'description' | translate }}</p>
      </div>
```
 
### Limitations of Ionic

There are specific aspects of Ionic that require some workarounds. 

#### Lazy-loaded Modules and Translations

For lazy-loaded modules, you will need to import translation modules there as well; otherwise, the translation will not work. Don’t forget to use `forChild` instead of `forRoot`.

```
    import { NgModule } from '@angular/core';
    import { CommonModule } from '@angular/common';
    import { IonicModule } from '@ionic/angular';
    import { FormsModule } from '@angular/forms';
    import { HomePage } from './home.page';
    import { HomePageRoutingModule } from './home-routing.module';
    import { TranslateModule, TranslateLoader } from '@ngx-translate/core';
    import { HttpClient } from '@angular/common/http';
    import { TranslateHttpLoader } from '@ngx-translate/http-loader';
    
    /* Once again, load your translations*/
    export function HttpLoaderFactory(http: HttpClient) {
      return new TranslateHttpLoader(http, "./assets/i18n/", ".json");
    }
    
    /* Add the translation module again, but this time, with forChild() */
    @NgModule({
      imports: [
        TranslateModule.forChild({
          loader: {
            provide: TranslateLoader,
            useFactory: HttpLoaderFactory,
            deps: [HttpClient]
          }
        }),
        CommonModule,
        FormsModule,
        IonicModule,
        HomePageRoutingModule
      ],
      declarations: [HomePage]
    })
    export class HomePageModule {}
```

#### Pluralization and Gender

Pluralization and gender formatting aren’t included with ngx-translate. However, there is a [plugin](https://github.com/lephyrus/ngx-translate-messageformat-compiler) to handle these features, and it’s recognized by the official ngx-translate library.

## React

React needs little introduction. Created by Facebook in 2013, it quickly became a fan favorite for many frontend developers.

Two major libraries are available for internationalization in React:

* [react-intl](https://github.com/formatjs/formatjs) (now called format.js)
* [react-i18next](https://github.com/i18next/react-i18next)

While both are popular (12,000 and 6,000 GitHub stars, respectively), react-i18next seems to have won developers over. This library has the added benefit of belonging to the [i18next](https://www.i18next.com/) ecosystem, a translation framework offering support to React, React Native, and Electron, among others. Developers can learn it once and easily translate it into many different frameworks. 

### Internationalize Your React App

To use react-i18next, first install the library:

```bash
    npm install react-i18next i18next --save
```

In your src folder, beside your `index.js`, create an `i18n.js` file where you will add your translations and connect react-i18next to React.

```
    import i18n from "i18next";
    import { initReactI18next } from "react-i18next";
    // Pro tip: Move them into their own JSON files
    const resources = {
      en: {
        translation: {
          "welcome_message": "Hello and Welcome to React"
        }
      },
      fr: {
        translation: {
          "welcome_message": "Bonjour et bienvenue à React"
        }
      }
    };
    i18n
      .use(initReactI18next) // Connect react-i18next to React
      .init({
        resources,
        lng: "en", // default language
        interpolation: {
          escapeValue: false // react already safe from xss
        }
      });
      export default i18n;
```

Then, in your `index.js`, import your newly created `i18n.js` file:

```
    import React from 'react';
    import ReactDOM from 'react-dom';
    import './index.css';
    import './i18n';
    import App from './App';
    
    ReactDOM.render(
      <React.StrictMode>
        <App />
      </React.StrictMode>,
      document.getElementById('root')
    );
```

You can access your translation through, for example, the `useTranslation` hook.

```
    import logo from './logo.svg';
    import './App.css';
    import { useTranslation } from 'react-i18next';
    function App() {
      const { t } = useTranslation();
      return (
        <div className="App">
          <header className="App-header">
            <img src={logo} className="App-logo" alt="logo" />
            <p>
            {t('welcome_message')}
            </p>
          </header>
        </div>
      );
    }
    export default App;
```

### Limitations of React

The library is comprehensive and covers many necessary features. Plurals, interpolation, formatting, nesting, and more are handled by react-i18next.

The only thing that gets a bit tricky is translating text with HTML. For example, `“Hello, <i>{{name}}</i>! Go to your <Link to=”/inbox”>inbox</Link> to see your new messages”`. 

React-i18next handles this use case by transforming your string into a tree node and using replacement tags.

Your string would then be split up:

```
Trans.children = [
'Hello, ', // 0: a string
{ name: ‘Marie’ }, // 1: <strong> with interpolation
‘! Go to your ’, // 2: a string
{ children: ['inbox'] }, // 3: <Link> with a string child
' to see your new messages' // 4: another string
]
```

In your translation files, you would have `Hello, <1>{{name}}</1>! Go to your <3>inbox</3> to see your new messages`. The mental gymnastics of figuring out the right index can be confusing.

## Conclusion

Users are far more likely to interact with products in their own language, so offering support for more languages and regions could bring you users that your competitors can’t access. If you internationalize your product early, you’ll be better able to add support for other locales as you scale up. 

You can cut down on development time with [Strapi](https://strapi.io/). With [its internationalization plugin](https://strapi.io/features/internationalization), you can create different content versions for each language and country in an easy-to-use editor. All your content is available through API endpoints, allowing you to easily connect your frontend. Whether you’re developing for the web or for mobile, Strapi can help you with your localization process.