stories/guidelines/i18n/I18n.stories.mdx

Summary

Maintainability
Test Coverage
import { Meta } from '@storybook/addon-docs'

<Meta title="Guidelines/Internationalization" />

# Internationalization

## Description

OKP4 design system provides an internationalization service (i18n) that allows to change the text inside components
and to support different languages or easily customize strings inside the web component.

The components exposed by the library already have their own translations (english and french available), which allows them to be used without any additional configuration.  
However, it is quite possible for the user of our library to dynamically add their own files and translation keys in order,
either to enhance the basic translations, or to create translations for high-level elements
like a web page for example without having to configure a new i18n instance.

## Language detection

Our internationalization system is based on advanced user language detection.
Indeed, there are multiple ways to determine the language used in order to be able to load the correct translations.
So, we use in order:

### Local storage

Each time the i18n instance is created or updated, a search in the local storage of the browser is carried out in order
to retrieve a possible key whose value bears the user language:

```
i18nextLng=LANGUAGE
```

It is thus quite possible to update this value according to a user interaction and to force a refresh of the translations and i18n keys.
For example:

```tsx
import { updateLanguage } from '@okp4/ui'

export const FrenchLanguageSwitcher: React.FC = (): JSX.Element => {
  const handleLanguageUpdate = (language: string) => (): void => {
    updateLanguage(language)
  }

  return (
    <div>
      <Button onClick={handleLanguageUpdate('fr')}>Switch to French</Button>
    </div>
  )
}
```

### Browser

If none of the previous detection elements have made it possible to recover the user language, the internationalization
system will automatically recover the language of the browser in which the instance was launched and save it as the usual language,
which will thus allow show translations in this language.  
There is no particular manipulation required to activate detection by browser language, this is done automatically.

### Check current language

---

The `isCurrentLanguage` method can be used to facilitate the detection of the currently used language and improve user experience.  
This method takes a language as a parameter and returns a boolean response depending on whether the detected language is the current language or not.

```ts
const isCurrentLanguage = (lng: string) => boolean
```

An example of usage in a React component:

##### **`MyComponent.tsx`**

```tsx
import React from 'react'
import { isCurrentLanguage } from '@okp4/ui'

export const MyComponent: React.FC = () => {
  return <p>{isCurrentLanguage('fr') ? 'French Language On' : 'French Language Off'}</p>
}
```

## Usage

### Load dynamic translations

The internationalization system offered by OKP4 allows you to dynamically add translation files that can be used in any React component contained in the webapp.

There are 3 possibilities to download the translations dynamically:

- add new keys and their translations to the file (extension)
- edit existing translations (overwrite)
- add translations into a new language

To do this, just use the `loadTranslations` method at the top level of the application.  
`loadTranslations` accepts 2 parameters:

- an array of translations as first mandatory parameter
- an optional `overwrite` boolean parameter

The translations array parameter must respect the following type:

```typescript
type I18nResource = Readonly<{
  readonly lng: string
  readonly namespace: string
  readonly resource: JSONValue //the JSON file itself
}>
```

#### The 3 ways to implement translations explained step by step

Given an `AwesomeComponent` that comes with its own translations which are stored in the `AwesomeComponent_en.json` file.  
It looks like this:

```json
{
  "languages": "Languages:"
}
```

##### 1- First way: extension

We want to add a new translation, so we have to create a new file which has to be named exactly like its file known translations file: `AwesomeComponent_en.json`, but it will look like this now:

```json
{
  "languages": "Languages:",
  "all": "All languages" // New translation added
}
```

We now have to import our translations file `./AwesomeComponent_en.json` and to use `loadTranslations` method at the top level of the application.

##### **`main.ts`**

```typescript
import { loadTranslations } from '@okp4/ui'
import type { I18nResource } from '@okp4/ui'
import awesomeComponent_en from './AwesomeComponent_en.json'

const translationsToLoad: I18nResource[] = [
  { lng: 'en', namespace: 'awesomeComponent', resource: awesomeComponent_en }
]

loadTranslations(translationsToLoad)
```

##### 2- Second way: overwrite

We want to change an existing translation, so we have to create a new file which has to be named exactly like its file known translations file: `AwesomeComponent_en.json`, but it will look like this now:

```json
{
  "languages": "All languages spoken in the world" // Previously : "languages": "Languages:"
}
```

So, we kept the key but updated its translation.  
We now have to import our translations file `./AwesomeComponent_en.json` and to use `loadTranslations` method at the top level of the application.
The only thing that will change from the extension method is that we have to add the `overwrite` parameter to `true` to the `loadTranslations` method
so that the existing keys and their new translations are taken into account.

##### **`main.ts`**

```typescript
import { loadTranslations } from '@okp4/ui'
import type { I18nResource } from '@okp4/ui'
import awesomeComponent_en from './AwesomeComponent_en.json'

const translationsToLoad: I18nResource[] = [
  { lng: 'en', namespace: 'awesomeComponent', resource: awesomeComponent_en }
]

loadTranslations(translationsToLoad, true)
```

##### 3- Third way: add new language file

We want to add a translation file in a new language that was not implemented, so we have to create a new file which has to be named exactly
like its existing translations file, but with a new language extension.  
Languages extensions must look like `es` for Spanish, `de` for German ... and so on.  
For instance, if we want to add Spanish language translations, the new file will be named `AwesomeComponent_es.json`, and it will look like this:

```json
{
  "languages": "Idiomas"
}
```

We now have to import our translations file `./AwesomeComponent_es.json` and to use `loadTranslations` method at the top level of the application.

##### **`main.ts`**

```typescript
import { loadTranslations } from '@okp4/ui'
import type { I18nResource } from '@okp4/ui'
import awesomeComponent_es from './AwesomeComponent_es.json'

const translationsToLoad: I18nResource[] = [
  { lng: 'es', namespace: 'awesomeComponent', resource: awesomeComponent_es }
]

loadTranslations(translationsToLoad)
```

### Translation service

Once the translations have been added dynamically, it is possible to directly use the built-in `UseTranslation` hook in the component.
The hook returns a `UseTranslationResponse` which is exported by the library:

##### **`MyComponent.tsx`**

```tsx
import React from 'react'
import { useTranslation } from '@okp4/ui'
import type { UseTranslationResponse } from '@okp4/ui'

export const MyComponent: React.FC = () => {
  const { t }: UseTranslationResponse = useTranslation()
  return <p>{t('languages:all')}</p>
}
```

The `t` function accepts the translation key as parameter that could be prefixed by the namespace containing the key to translate.  
The `namespace` could also be passed as parameter to the `useTranslation` function if all the keys to be translated into the component
belong to the same namespace: in that case, we don't have to prefix the translation with the namespace.

So, it would look like this:

##### **`MyComponent.tsx`**

```tsx
import React from 'react'
import { useTranslation } from '@okp4/ui'
import type { UseTranslationResponse } from '@okp4/ui'

export const MyComponent: React.FC = () => {
  const { t }: UseTranslationResponse = useTranslation('languages')
  return <p>{t('all')}</p>
}
```