# Localization

Songwhip is translated into several languages by using the external service [POEditor](https://poeditor.com/projects/view?id=505611) and our own private node packages:

- [@theorchard/frontend-cli-i18n](https://github.com/theorchard/orchard-suite/tree/master/packages/frontend-cli-i18n) - Syncs with POEditor ( dev dependency )
- [@theorchard/suite-i18n-localization](https://github.com/theorchard/orchard-suite/tree/master/packages/suite-i18n-localization) - Runtime translation utils

## Setup

To get your dev environment up and running with multiple languages you need a `POEDITOR_API_TOKEN`. Get it from your team mates or manager and add the token to your `.env` file.

```bash
POEDITOR_API_TOKEN=[YOUR_TOKEN]
POEDITOR_PROJECT_ID=505611
```

Once you have the token, you can download the remote translations:

```bash
yarn i18n:download
```

## How it works

Before we build and deploy new versions of Songwhip we also sync with POEditor. New English terms will be uploaded to Songwhip and any new/updated translations in other languages are downloaded.
Webpack will bundle these up and make them part of the final application.

**The English translations are never translated, but maintained by the team.**

### i18n.json

English translations and terms are stored inside the source folders as `i18n.json` files. These files can be placed anywhere within the `components` and `pages` folders.

A typical `i18n.json` file will define a top level node and some terms / translations. e.g

```json
{
  "auth": {
    "loginRequiredText": "You need to login to continue",
    "login": "Login"
  }
}
```

```json
{
  "home": {
    "welcomeText": "Welcome to Songwhip"
  }
}
```

The top level names `account` and `home` are treated as i18n _fragments_.

**Note** that the `i18n.json` files may define multiple top level _fragments_, but it is recommended to only define one.

#### The `app` fragment

The `app` top level node is bundled in a special `app` bundle. This bundle is always loaded as part of the application, making it available to every page and component with the `useI18n` hook.

```json
{
  "app": {
    "home": "Home"
  }
}
```

```tsx
const { t } = useI18n('app');

t('home'); // => "Home"
```

## Localizing pages

### `withI18n`

Each page that uses localized content needs to depend on one or more i18n.json _fragments_. This will ensure the translations for the given page is available when it renders.

In the following example, we define that the `HomePage` is dependent the `home` _fragment_. This allows us to use the translations defined under `home` within the page and sub-components using the `useI18n` hook.

```tsx
import { withI18n } from '~/src/lib/i18n';
import HomePage from '~/src/views/homePage';

const HomePageWithI18n = withI18n(HomePage, ['home']);

export default HomePageWithI18n;
```

**Note** a page can depend on multiple _fragments_, but it is generally recommended to have a one-2-one match between a page and _fragment_.

### `useI18n`

The `useI18n` hook looks up the requested _fragment_ in the React context and returns scoped formatting functions.

```tsx
import { useI18n } from '~/src/lib/i18n';

const HomePage = () => {
  const { t } = useI18n('home');

  return <h1>{t('welcomeText')}</h1>;
};
```

**Note** if you try using a fragment not listed as a dependency for the given page, the `useI18n` hook will throw an error saying "the requested key was not found".

## Localizing shared components

Components shared between two or more pages requires a different localization setup than pages. Shared components can **not** use the `withI18n` HOC since that logic is tied to Next.js page loading. So we need a way to load translations just for that single component. We support two ways for doing this.

- Dynamically loaded translations with the `useI18nDynamic` hook.
- Statically loaded translations with the `useI18nStatic` hook.

### `useI18nStatic`

Imports **all** locales for the given _fragment_. This means that the locales are bundled by Webpack's algorithm. If the component itself is dynamically loaded then the locales are included into the component bundle. If the component is statically imported, then the locales are included into the app main bundle or a chunk that Webpack sees fit.

```tsx
import locales from '~/locales/fragments/auth';
import { useI18nStatic } from '~/src/lib/i18n';

const AuthGuard = () => {
  const { t } = useI18nStatic<'auth'>(locales);

  return <div>{t('loginRequiredText')}</div>;
};
```

### `useI18nDynamic`

This loads the translations for a given _fragment_ dynamically when rendered. Only the translations for the current locale are loaded from single i18n bundle. This means you need to handle the loading state in your component.

```tsx
import { useI18nDynamic } from '~/src/lib/i18n';

const AuthGuard = () => {
  const { loading, t } = useI18nDynamic('auth');

  if (loading) return <LoadingIndicator />;

  return <div>{t('loginRequiredText')}</div>;
};
```

**So when to use what ?**

Use the `useI18nStatic` hook when:

- Your component is always dynamically loaded ( React.lazy ).
- Your component does not have a a lot of translated text.
- Your component is not time critical to render.

Use the `useI18nDynamic` hook when:

- Your component is statically or dynamically imported.
- Your component has a lot of text to be translated.
- Your component has already async loading logic.

## Formatting functions

Using any of the `useI18n` hooks you will get back the formatters `t` and `tx`.

- `t` - returns plain text and accepts text arguments.
- `tx` - returns a React element and accepts React components / elements as arguments.

### `t`

Use the `t` function whenever you can, it is lightweight and fast.

```json
{
  "home": {
    "welcome": "Welcome {user}"
  }
}
```

```tsx
import { useI18n } from '~/src/lib/i18n';

const HomePage = () => {
  const { t } = useI18n('home');

  return <div>{t('welcome', { user: 'Timmy' })}</div>;
  // => <div>Welcome Timmy</div>
};
```

### `tx`

Use the `tx` function when you need to translate rich text content, like a link or a text copy with styled text.

```json
{
  "home": {
    "linkToOrchard": "Click {link}here{/link} to go to The Orchard"
  }
}
```

```tsx
import { useI18n } from '~/src/lib/i18n';

const HomePage = () => {
  const { tx } = useI18n('home');

  return (
    <div>
      {tx('linkToOrchard', {
        link: ({ children }) => <a href="https://theorchard.com">{children}</a>,
      })}
    </div>
  );
  // => <div>Click <a href='https://theorchard.com'>here</a> to go to The Orchard</div>
};
```

### Plural form

Both `t` and `tx` supports plural forms of a single term. Use the `one` and `other` sub terms to define singular and plural forms.

```json
{
  "home": {
    "numDays": {
      "one": "one day",
      "other": "{count} days"
    }
  }
}
```

```tsx
  const { t } = useI18n('home');

   t('numDays', { count: 1 }); // => "one day"
   t('numDays', { count: 2 }); // => "2 days"
};
```

**Note** that when using plural form, the `count` argument is mandatory.

## i18n CLI

Syncing and managing the `i18n.json` files are done by the `@theorchard/frontend-cli-i18n` library.

The library will scan the source folders for any `i18n.json` files, merge and store them in the `locales/fragments` folders AND all combined into the `locales/en.json` file.

The `locales/en.json` file is the source from which we sync with POEditor. New terms defined in this file will be uploaded and inserted. Terms with changed English translations will be updated and related terms in other locales marked as modified.

### `yarn i18n:init`

Scans for `i18n.json` files and creates the `locales` folder and fragments. This is run after `yarn install` and before `yarn test`. Whenever you change a `i18n.json` file you should run this script ( if you are not running the dev server ).

### `yarn i18n:watch`

Watches for changes to `i18n.json` files and runs the `i18n:init` script on change. This is run as part of the `yarn dev` script.

### `yarn i18n:download`

Only downloads remote translations from POEditor. Run when you want to run Songwhip locally with localized content.

### `yarn i18n:sync`

Runs the full sync with POEditor. Run as part of the CI deployment.
