Localization System

Categories Client/Server App TemplatePosted on

Our template client has a multilingual system to facilitate translating the web content into multiple languages. To achieve this, it uses the i18next and react-i18next libraries:

Folder Structure

The system requires a folder within src, named i18n, which will contain all the language specifications.

We can use a file for each language or, if the translations are very extensive, a folder for all the files of each language. This allows us to organize the translations by pages, components, or sections and make site maintenance much more convenient.

Language files are JSON data structures, like the following example:

// en_US.js
export default {
  pages: {
    about: {
      title: "About This Project",
      message: "This is an open-source project that allows users to interact with the WAX blockchain. Created by the 3DK Render team."
    },
    contact: {
      title: "Contact Us",
      message: "Page under construction."
    },
    error: {
      title: "Error",
      message: "Sorry, an unexpected error has occurred."
    },
    transaction: {
      title: "Make a WAX Donation",
      send: "Send",
      placeholder: "Enter amount"
    }
  }
};

Combining and Exporting Languages

All these components will be combined and exported from the index.ts file:

import { TKeyLanguage } from '../types/TKeyLanguage';
import en_US from './en_US';
import es_ES from './es_ES';

export { en_US, es_ES };

export const keyLanguages: TKeyLanguage = {
  en_US: 'English',
  es_ES: 'Español'
};

We have included the keyLanguages object to label the languages and be able to display them in the application’s menu, allowing users to select their language.

The structure of the keyLanguage object is defined in TKeyLanguage as follows:

// TKeyLanguage.ts
export type TKeyLanguage = {
  [key: string]: string;
};

Language Files

As seen in the previous example, each language file is a JSON structure containing a label for each text that should be displayed on the client and will be translated into the selected language.

Each language must have the same JSON structure with the same labels and the definitions translated into the corresponding language. Following the previous example, this would be the translation of the same object but in Spanish:

// es_ES.js
export default {
  pages: {
    about: {
      title: "Acerca de este proyecto",
      message: "Este es un proyecto de código abierto que permite a los usuarios interactuar con la cadena de bloques de WAX. Creado por el equipo de 3DK Render."
    },
    contact: {
      title: "Contacto",
      message: "Página de contacto en construcción."
    },
    error: {
      title: "Error",
      message: "Lo siento, se ha producido un error inesperado."
    },
    transaction: {
      title: "Hacer una donación WAX",
      send: "Enviar",
      placeholder: "Introduzca la cantidad"
    }
  }
};

Initializing the System

To load the localization system in our application, we need to add a file called Translations.ts, which we will save in the services folder. This file configures internationalization (i18n) for a React application using the i18next and react-i18next libraries.

Imports

import { en_US, es_ES } from '../i18n';
import i18n from 'i18next';
import { initReactI18next } from "react-i18next";
import store from '../redux/store';
  • en_US and es_ES are imported from the localization file index.ts.
  • i18n is the core of i18next.
  • initReactI18next is a plugin to integrate i18next with React.
  • store is imported from the Redux configuration file (../redux/store).

Default Language Configuration

let defaultLang: string = 'en_US';

if (localStorage.getItem('lang')) {
  defaultLang = localStorage.getItem('lang') || 'en_US';
}
  • The defaultLang is set to ‘en_US’.
  • If there is a language value stored in localStorage, that value is used as the default language.

Initializing i18n

i18n
  .use(initReactI18next)
  .init({
    interpolation: { escapeValue: false },
    lng: defaultLang,
    resources: {
      en: {
        translation: en_US
      },
      es: {
        translation: es_ES
      }
    }
  });
  • initReactI18next is used to integrate i18next with React.
  • i18n is initialized with the following options:
    • interpolation: { escapeValue: false }: prevents escaping interpolated values.
    • lng: defaultLang: sets the initial language.
    • resources: provides the translation resources for the supported languages (in this case, English and Spanish).

Subscribing to the Redux Store

store.subscribe(() => {
  if (store.getState().config.lang !== defaultLang) {
    defaultLang = store.getState().config.lang;
    i18n.changeLanguage(defaultLang);
  }
});
  • Subscribes to the Redux store to detect changes in the state and to keep the selected language persistent throughout the user’s session.
  • If the language in the Redux state (store.getState().config.lang) changes and does not match defaultLangdefaultLang is updated and the language is changed in i18n using i18n.changeLanguage(defaultLang).

Loading the Component

Finally, the component is loaded at the root of the application by importing this file into main.tsx.

Edit the main.tsx file located at the root of your project (/src) and import the Translations file:

import "./services/Translations";

Now, the localization service will be available from the moment the webpage loads on the client.

Inserting Translation Fields in Documents

To take advantage of this localization system, we must use the elements of the JSON objects we created with the translations wherever we want the text to be displayed in the selected language, instead of writing the text directly.

For example, to display the title and subtitle of the “About” page:

<p>About this Project</p>
<p>This is an open-source project that allows users to interact with the WAX blockchain.</p>

To use our i18n system, we need to import a hook provided by the react-i18next framework:

import { useTranslation } from "react-i18next";

We create an object that will help us inject the translations by extracting the t function using destructuring from the useTranslation hook:

const { t } = useTranslation();

And replace the direct text labels with the created t function:

<p>{t('pages.about.title')}</p>
<p>{t('pages.about.message')}</p>

From this point on, the title and subtitle of the “About” page will be displayed in the selected language.

Dynamic Translations (with Parameters)

i18next supports variable interpolation, allowing you to insert dynamic values into your translations.

Defining Translations with Dynamic Parameters

First, define your translation strings with placeholders for the dynamic values. These placeholders are enclosed in double curly braces {{ }}.

For example, in the translation files (en_US.js and es_ES.js):

// en_US.js
export const en_US = {
  welcomeMessage: "Welcome to our application, {{name}}!",
  description: "This is a sample description."
};

// es_ES.js
export const es_ES = {
  welcomeMessage: "¡Bienvenido a nuestra aplicación, {{name}}!",
  description: "Esta es una descripción de ejemplo."
};

Using the t Function with Parameters

Within the React component, we can pass an object with the dynamic values as a second argument to the t function.

import React from 'react';
import { useTranslation } from 'react-i18next';

const MyComponent = () => {
  const { t } = useTranslation();
  const userName = 'John';

  return (
    <div>
      <h1>{t('welcomeMessage', { name: userName })}</h1>
      <p>{t('description')}</p>
    </div>
  );
};

export default MyComponent;

In this example, t('welcomeMessage', { name: userName }) will interpolate the value of userName into the translation string.

More Examples with Multiple Parameters

If you need to pass multiple parameters, simply add them to the parameters object.

// en_US.js
export const en_US = {
  transactionMessage: "You have sent {{amount}} {{currency}} to {{recipient}}.",
  description: "This is a sample description."
};

// es_ES.js
export const es_ES = {
  transactionMessage: "Has enviado {{amount}} {{currency}} a {{recipient}}.",
  description: "Esta es una descripción de ejemplo."
};
``

`

And when interpolating the parameters:

```javascript
import React from 'react';
import { useTranslation } from 'react-i18next';

const TransactionComponent = () => {
  const { t } = useTranslation();
  const amount = 100;
  const currency = 'WAX';
  const recipient = 'alice.wax';

  return (
    <div>
      <p>{t('transactionMessage', { amount, currency, recipient })}</p>
    </div>
  );
};

export default TransactionComponent;

Language Selection from the Application Menu

The last step is to provide an option for the user to select their desired language. To do this, we edit the Menu.tsx component and import the available language labels initialized in the index.ts file in the i18n folder.

Step 1: Import the Language Labels

First, we import the available language labels:

import { keyLanguages } from "../../i18n";

Step 2: Extract the Labels

Extract the available language labels into an array:

const languages = Object.keys(keyLanguages);

Step 3: Create a Dropdown Submenu

We will create a dropdown submenu in the main menu, within the Navbar element:

<NavbarContent
  justify="end"
  className="hidden sm:flex"
  key={'language'}
>
  <Dropdown aria-label="language-menu">
    <DropdownTrigger aria-label="access-language-menu">
      <Button variant="bordered" aria-label="Language options menu">
        <span className="text-white">{keyLanguages[selectedLang]}</span>
        <IconDownOpenMini />
      </Button>
    </DropdownTrigger>
    <DropdownMenu aria-label="language-menu-options">
      {
        languages.map((lang: string, index: number) => (
          <DropdownItem aria-label="language" key={index} onPress={() => handleSelectLanguage(lang)}>
            <div className="flex flex-row items-center">
              <span className="text-black">{keyLanguages[lang]}</span>
            </div>
          </DropdownItem>
        ))
      }
    </DropdownMenu>
  </Dropdown>
</NavbarContent>

Step 4: Language Selection Handler

The submenu will call the handleSelectLanguage handler each time a language is selected from the dropdown list:

const handleSelectLanguage = (lang: string) => {
  setSelectedLang(lang);
  store.dispatch(setLang(lang));
  i18next.changeLanguage(lang);
  localStorage.setItem('lang', lang);
};

Handler Function

The handleSelectLanguage handler will update the language according to the provided key. It performs the following actions:

  1. Update Local State: Updates the local state of the selected language.
  2. Update Redux Store: Dispatches an action to update the language in the Redux store.
  3. Change the Language in i18next: Changes the current language in i18next.
  4. Save in localStorage: Stores the selected language in localStorage for persistence between sessions.

Conclusion

With these configurations, we have provided the user with an option to select their desired language from the application menu. This localization system ensures that users can dynamically and persistently change the language of the interface.

Leave a Reply

Your email address will not be published. Required fields are marked *