# Internationalization

## The `lang` route parameter

All page paths starts with a language parameter, e.g. `/sv/utforska-scandic/wi-fi`.

### Get the language in a client component

We have a hook called `useLang` that directly returns the `lang` parameter from the path.

### Get the language in a server component

In order to not prop drill that all the way from a page we use React's `cache` in a way that resembles React`s context, but on the server side.

For this to work we must set the language with `setLang` on the topmost layout

This was inspired by [server-only-context](https://github.com/manvalls/server-only-context)

## Translations a.k.a. UI labels

### Quickstart: How to use react-intl in the codebase

> These recommendations are temporary. They will be updated once support for Lokalise lands.

- **Do not destructure `formatMessage` from either `getIntl()` or `useIntl()`.**

  ❌ Do not do this:

  ```typescript
  const { formatMessage } = useIntl()
  const message = formatMessage(...)
  ```

  ✅ instead do this:

  ```typescript
  const intl = useIntl()
  const message = intl.formatMessage(...)
  ```

  ❌ Do not do this:

  ```typescript
  const { formatMessage } = await getIntl()
  const message = formatMessage(...)
  ```

  ✅ instead do this:

  ```typescript
  const intl = await getIntl()
  const message = intl.formatMessage(...)
  ```

- **Do not pass variables as id.**

  The id needs to be literal string so that the tooling can properly find and extract defined messages.

  ❌ Do not do this:

  ```typescript
  const data = await getSomeData()
  ...
  const message = intl.formatMessage({
    id: data.type,
  })
  ```

  ✅ instead do this:

  This is a hard one to give a general solution/rule for, but in essence it should use either use a "switch approach" or `defineMessage/defineMessages` ([docs](https://formatjs.github.io/docs/react-intl/api/#definemessagesdefinemessage)) in some way.

  The most common reason for this is the data contains one or several words we want translated.

  The preferred solution is to use a "switch approach" (or something equivalent), checking some property of the entity to decide what message to use. It is explicit about what cases are supported (this helps finding bugs by lowering complexity by making it easier to find where a string is used), the declaration is coupled with the usage (keeping our messages up to date and clutter free over time), the formatjs eslint plugin helps enforce proper usage and TS will force us to keep this list up to date with the typings of `data.type`. Preferably ALL data points of this manner should be an enum or an array of strings `as const` (or equivalent) for the best TS support here.

  ```typescript
  ...
  const data = await getSomeData()
  ...
  let message = intl.formatMessage({id: 'N/A'})) // or some other default/"no match" message
  switch (data.type) {
    case "Restaurant":
      message = intl.formatMessage({id: "Restaurant"})
      break;
    case "Bar":
      message = intl.formatMessage({id: "Bar"})
      break;
    case "Pool":
      message = intl.formatMessage({id: "Pool"})
      break;
    default:
      // TS will throw if it reaches here if typings for `data.type` are properly defined and the above cases are exhaustive.
      // This will help us keep messages up to date.
      const type: never = data.type
      console.warn(`Unsupported type given: ${type}`)
  }
  ```

  If the above is not possible the escape hatch is using something like the following.

  Avoid using this as this decouples the message declaration from message consumption causing stale messages to linger around and clutter up things. It also makes it a lot harder to find where in the code a string is being used. The eslint plugin is also unable to enforce placeholders with this approach decreasing confidence in our messages and potentially hiding bugs.

  ```typescript
  import { defineMessages } from "react-intl"
  ...
  const data = await getSomeData()
  ...
  defineMessage({
    id: "Restaurant",
  })
  defineMessage({
    id: "Bar",
  })
  defineMessage({
    id: "Pool",
  })
  // OR
  defineMessages({
    restaurant: {
      id: "Restaurant",
    },
    bar: {
      id: "Bar",
    },
    pool: {
      id: "Pool",
    }
  })
  // We do not use the return value of defineMessage(s)
  // The keys are also not used, can be anything really
  // defineMessages can be used instead of calling defineMessage multiple times.
  // Both approaches yield the exact same result!
  ...
  const message = intl.formatMessage({
    // eslint-disable-next-line formatjs/enforce-default-message
    id: data.type, // data.type === "Restaurant" | "Bar" | "Pool"
  })
  ```

- **Do not use id key as an alias.**

  The id and the message need to be the same in all the dictionaries.

  This prepares for a future codemod that will transform the ids into default messages.

  ❌ Do not do this:

  ```typescript
  // react
  const message = intl.formatMessage({
    id: "Some alias",
  })

  // dictionary (en.json for example)
  ...
  "Some alias": "The real message is here"
  ...
  ```

  ✅ instead do this:

  ```typescript
  // react
  const message = intl.formatMessage({
    id: "The real message is here",
  })

  // dictionary (en.json for example)
  ...
  "The real message is here": "The real message is here"
  ...
  ```

- **Add translations to all local JSON dictionaries.**

  Even if the message is untranslated when adding it. Even if the id is used as a fallback when a translation is missing, the fallback does not get interpolated.

  ❌ Do not do this:

  ```typescript
  const message = intl.formatMessage({
    id: "An id NOT added to all dictionaries",
  })
  const messageWithVariable = intl.formatMessage({
    id: "An id NOT added to all dictionaries with {someVariable}",
  })
  ```

  ✅ instead do this:

  ```typescript
  // react
  const message = intl.formatMessage({
    id: "An id added to all dictionaries",
  })
  const messageWithVariable = intl.formatMessage({
    id: "An id added to all dictionaries with {someVariable}",
  })

  // dictionary: en.json (original messages)
  ...
  "An id added to all dictionaries": "An id added to all dictionaries",
  "An id added to all dictionaries with {someVariable}": "An id added to all dictionaries with {someVariable}",
  ...

  // dictionary: sv.json (translated messages)
  ...
  "An id added to all dictionaries": "Ett id tillagt i alla filer för uppslag",
  "An id added to all dictionaries with {someVariable}": "Ett id tillagt i alla filer för uppslag med {someVariable}",
  ...

  // dictionary: de.json (untranslated messages, still required)
  ...
  "An id added to all dictionaries": "An id added to all dictionaries",
  "An id added to all dictionaries with {someVariable}": "An id added to all dictionaries with {someVariable}",
  ...
  ```

- **Avoid using ICU special words as placeholder names**

  Some words have meaning in ICU when dealing with placeholders and such. To avoid bugs and confusion do not use them as placehodler names.

  Avoid (list is not exhaustive):

  - `{number}`
  - `{integer}`
  - `{plural}`
  - `{date}`
  - `{time}`
  - `{select}`
  - `{choice}`
  - `{other}`

  Also do not use the current variable name in scope as the placeholder name (unless the variable is named following the below rules). It will confuse translators.

  ❌ Do not do this:

  ```typescript
  const number = getValueSomeWay()
  ...
  const message = intl.formatMessage(
    {
      id: "The number is {number}",
    },
    {
      number,
    }
  )
  ```

  nor this

  ```typescript
  const goodNameForVarButNotForPlaceholder = getValueSomeWay()
  ...
  const message = intl.formatMessage({
    id: "The number is {goodNameForVarButNotForPlaceholder}",
  }, {
    goodNameForVarButNotForPlaceholder
  })
  ```

  ✅ instead do this:

  Prefer a placeholder name that gives context to the message when reading it without the context of the code

  ```typescript
  const goodNameForVarButNotForPlaceholder = getValueSomeWay()
  ...
  const message = intl.formatMessage(
    {
      id: "The number is {membershipNumber}",
    },
    {
      membershipNumber: goodNameForVarButNotForPlaceholder,
    }
  )
  ```

  or if context is hard to give, use generic words like `value`, `count`, `amount`, etc.

  ```typescript
  const number = getValueSomeWay()
  ...
  const message = intl.formatMessage(
    {
      id: "The number is {value}",
    },
    {
      value: number,
    }
  )
  ```
