Skip to main content

Internationalization (i18n)

This step consists in proposing the website in several languages.

Final result

The result of this step is as follows:

New in this step

The website is now available in English and French. You can change the language via a link in the navivation bar
Please note the text "item" on the Cart page which is more complex to handle (styling + plural)

Configuration

You specify the languages supported by the website via the configuration file src/settings.ts:

src/settings.ts
export default {
i18n: {
locales: [
{
locale: 'en',
path: '/en',
},
{
locale: 'fr',
path: '/fr',
},
],
defaultLocale: 'en',
},
...
} as AppSettings;

Translations

The translations are stored in the folders public/locales/{locale}/{namespace}.json (in JSON format)

note

onekijs supports splitting translations into multiple files that are called namespaces.

By convention, the default namespace is named common and is automatically loaded.
It's a good practice to put everything that is common to the whole application in this file (e.g: the text in the navigation bar).
For small applications, it's common to put all translations only in this file.

My-App
├─ public
│ ├─ locales
│ │ └─ en
│ │ │ └─ common.json // locales in english
│ │ └─ fr
│ │ │ └─ common.json // locales in french

Translation files

Let's create these translation files for our application

English

public/locales/en/common.json
{
"Phone XL": "Phone XL",
"Phone Mini": "Phone Mini",
"Phone Invalid": "Phone Invalid",
"A large phone with one of the best screens": "A large phone with one of the best screens",
"A great phone with one of the best cameras": "A great phone with one of the best cameras",
"The product has been shared!": "The product has been shared!",
"You will be notified when the product goes on sale": "You will be notified when the product goes on sale",
"Product added succesfully to the cart!": "Product added succesfully to the cart!",
"Notify me": "Notify me",
"Share": "Share",
"Description": "Description",
"Product Details": "Product Details",
"Buy": "Buy",
"Loading": "Loading",
"Empty the cart": "Empty the cart",
"An error occured while cleaning the cart": "An error occured while cleaning the cart",
"There is no item in the shopping cart !": "There is no item in the shopping cart !",
"Buy another products": "Buy another products",
"Use any username / password (no check performed)": "Use any username / password (no check performed)",
"Username": "Username",
"Password": "Password",
"Submit": "Submit",
"Submitting": "Submitting",
"Error": "Error",
"Logging out": "Logging out",
"An error occured": "An error occured",
"stacktrace": "stacktrace",
"My Store": "My Store",
"Log out": "Log out",
"Checkout": "Checkout"
}

French

public/locales/fr/common.json
{
"Phone XL": "Téléphone XL",
"Phone Mini": "Téléphone Mini",
"Phone Invalid": "Téléphone Invalide",
"A large phone with one of the best screens": "Un grand téléphone avec l'un des meilleurs écran",
"A great phone with one of the best cameras": "Un bon téléphone avec l'un des meilleurs appareil photo",
"The product has been shared!": "Ce produit a été partagé!",
"You will be notified when the product goes on sale": "Vous serez notifié quand le produit sera disponible à la vente",
"Product added succesfully to the cart!": "Le produit a été ajouté au panier avec succès!",
"Notify me": "Me notifier",
"Share": "Partager",
"Description": "Description",
"Product Details": "Détails du produit",
"Buy": "Acheter",
"Loading": "Chargement",
"Empty the cart": "Vider le panier",
"An error occured while cleaning the cart": "Une erreur est survenue pendant le nettoyage du panier",
"There is no item in the shopping cart !": "Il n'y a aucun éléments dans le panier !",
"Buy another products": "Acheter d'autres produits",
"Use any username / password (no check performed)": "Utilisez n'importe quel identifiant / mot de passe (pas de check effectué)",
"Username": "Identifiant",
"Password": "Mot de passe",
"Submit": "Envoyer",
"Submitting": "Envoi",
"Error": "Erreur",
"Logging out": "Déconnexion",
"An error occured": "Une erreur est survenue",
"stacktrace": "stacktrace",
"My Store": "Mon Magasin",
"Log out": "Déconnexion",
"Checkout": "Panier"
}

Update of the application

useTranslation hook

info

To display a string (or even a component) in the right locale, onekijs offers the useTranslation hook. This hook returns

  • a component <T /> to translate something inside a JSX
  • a function t() to translate a typescript string

It often happens that the element to be translated is not a simple string.

Let's update the Cart page to use this hook and complexe string to be translated

getting-started/cra//step08-i18n/src/pages/cart.tsx
loading...

useI18nService hook

info

onekijs offers a service providing some i18n related helper methods like switching between locales.
The service can be instancied via the useI18nService hook.

Also note that the useTranslation hook returns the active locale as the third parameter.

Let's add a link to the <Navbar /> component to select the appropriate locale (English or French) and to hightlight the active one.

getting-started/cra//step08-i18n/src/modules/core/components/Navbar.tsx
loading...

Now a user can display the application in english or in french

Advanced use cases

Complex string

It often happens that the element to be translated is not a simple string. To illustrate this, let's update the Cart component like this:

src/modules/core/components/Cart.tsx
const Cart: FC<CartOptions> = ({ cart }) => {
const [T] = useTranslation();
return (
<div>
<h3>
<T>Cart</T>
</h3>

{cart.products.map((item, index) => (
<div key={`item-${index}`} className="cart-item">
<span>
<T>{item.name}</T>
</span>
<span>
<T>{currency(item.price)}</T>
</span>
</div>
))}
{cart.products.length === 0 && (
<h4>
<T>
There is <u>no item</u> in the shopping cart !
</T>
</h4>
)}
<p>
<Link href="/products">
<T>Buy another products</T>
</Link>
</p>
</div>
);
};

export default Cart;

The key There is no item in the shopping cart ! in locales/en/common.json is no more valid. To become valid, the key must be upated like this:
locales/en/common.json
{
...
"There is <1>no item</1> in the shopping cart !": "There is <1>no item</1> in the shopping cart !",
...
}

locales/fr/common.json
{
...
"There is <1>no item</1> in the shopping cart !": "Il n'y a <1>aucun éléments</1> dans le panier !",
...
}

Plural and variables

Plurals are essential when dealing with internationalization.
Let's update the Cart component to add a plural and also a variable

src/modules/core/components/Cart.tsx
const Cart: FC<CartOptions> = ({ cart }) => {
const nbItems = cart.products.length;
const [T] = useTranslation();
return (
<div>
<h3>
<T>Cart</T>
</h3>

{cart.products.map((item, index) => (
<div key={`item-${index}`} className="cart-item">
<span>
<T>{item.name}</T>
</span>
<span>
<T>{currency(item.price)}</T>
</span>
</div>
))}
{nbItems === 0 && (
<h4>
<T>
There is <u>no item</u> in the shopping cart !
</T>
</h4>
)}
{nbItems > 0 && (
<h4>
<T count={nbItems}>
There is <u>{{ nbItems }} item</u> in the shopping cart !
</T>
</h4>
)}
<p>
<Link href="/products">
<T>Buy another products</T>
</Link>
</p>
</div>
);
};

export default Cart;

Update the translation file to support plural:

locales/en/common.json
{
...
"There is <1>{{nbItems}} item</1> in the shopping cart !": "There is <1>{{nbItems}} item</1> in the shopping cart !",
"plural::There is <1>{{nbItems}} item</1> in the shopping cart !": "There are <1>{{nbItems}} items</1> in the shopping cart !",
...
}

locales/fr/common.json
{
...
"There is <1>{{nbItems}} item</1> in the shopping cart !": "Il y a <1>{{nbItems}} élement</1> in the shopping cart !",
"plural::There is <1>{{nbItems}} item</1> in the shopping cart !": "Il y a <1>{{nbItems}} éléments</1> in the shopping cart !",
...
}

The following text is displayed if there is one element in the cart:

  • There is 1 item in the shopping cart !

The following text is displayed if there are two or more elements in the cart:

  • There are 2 items in the shopping cart !

Next step

In the next step, we present how to build powerful forms with onekijs.