# 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 a.k.a. Lokalise

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

> For more information read about [the workflow below](#markdown-header-the-workflow).

- **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 defaultMessage.**

  The `defaultMessage` needs to be a literal string so that the tooling can properly find and extract defined messages. Do not use template strings with variable interpolation either.

  ❌ Do not do this:

  ```typescript
  const data = await getSomeData()
  ...
  const message = intl.formatMessage({
    defaultMessage: data.type,
  })
  ...
  const message = intl.formatMessage({
    defaultMessage: `Certification: ${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 scenario is that the data contains one or several words/sentences 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.
  - TypeScript will force us to keep the 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({defaultMessage: 'N/A'})) // or some other default/"no match" message
  switch (data.type) {
    case "Restaurant":
      message = intl.formatMessage({defaultMessage: "Restaurant"})
      break;
    case "Bar":
      message = intl.formatMessage({defaultMessage: "Bar"})
      break;
    case "Pool":
      message = intl.formatMessage({defaultMessage: "Pool"})
      break;
    case "Some certification":
      message = intl.formatMessage({defaultMessage: "Certification: {name}"}, { name: "Some certification"})
      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 because:

  - This decouples the message declaration from the message consumption: causing stale messages to linger around and clutter up the codebase.
  - It makes it a lot harder to find where a string is being used in the codebase.
  - The formatjs eslint plugin is unable to enforce placeholders: this decreases confidence in our messages and potentially hiding bugs.

  ```typescript
  import { defineMessages } from "react-intl"
  ...
  const data = await getSomeData()
  ...
  const restaurantMessage = defineMessage({
    defaultMessage: "Restaurant",
  })
  const barMessage = defineMessage({
    defaultMessage: "Bar",
  })
  const poolMessage = defineMessage({
    defaultMessage: "Pool",
  })
  // OR
  const messages = defineMessages({
    restaurant: {
      defaultMessage: "Restaurant",
    },
    bar: {
      defaultMessage: "Bar",
    },
    pool: {
      defaultMessage: "Pool",
    }
  })
  ...
  return (
    <p>{intl.formatMessage(messages.restaurant)}</p> // or .bar or .pool, etc.
  )
  ...

  // Since calls to defineMessage/defineMessages get their messages extracted,
  // technically you can do the following instead of accessing the key like above.
  // But it is not encouraged as this decoupling leads to had to track usage and decay over time:
  const message = intl.formatMessage({
    // eslint-disable-next-line formatjs/enforce-default-message
    defaultMessage: data.type, // data.type === "Restaurant" | "Bar" | "Pool"
  })
  ```

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

  The `defaultMessage` should be the actual message you want to display (and send to Lokalise). It must be written in American/US English.

  ❌ Do not do this:

  ```typescript
  const message = intl.formatMessage({
    defaultMessage: "some.alias",
  })
  ```

  ✅ instead do this:

  ```typescript
  const message = intl.formatMessage({
    defaultMessage: "The real message is here in US English",
  })
  ```

- **Do not use conditionals when defining messages.**

  ❌ Do not do this:

  ```typescript
  const message = intl.formatMessage({
    defaultMessage: someVariable ? "Variable is truthy" : "Variables i falsey",
  })
  ```

  ✅ instead do this:

  ```typescript
  const truthyMessage = intl.formatMessage({
    defaultMessage: "Variable is truthy",
  })
  const falseyMessage = intl.formatMessage({
    defaultMessage: "Variable is falsey",
  })
  const message = someVariable ? truthyMessage : falseyMessage
  ```

- **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(
    {
      defaultMessage: "The number is {number}",
    },
    {
      number,
    }
  )
  ```

  nor this

  ```typescript
  const goodNameForVarButNotForPlaceholder = getValueSomeWay()
  ...
  const message = intl.formatMessage({
    defaultMessage: "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(
    {
      defaultMessage: "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(
    {
      defaultMessage: "The number is {value}",
    },
    {
      value: number,
    }
  )
  ```

- Do not give id to messages.

  The eslint plugin will automatically fix this (removes the id on save/fix).

  ❌ Do not do this:

  ```typescript
  const message = intl.formatMessage({
    id: "some-id",
    defaultMessage: "This is a message",
  })
  ```

  ✅ instead do this:

  ```typescript
  const message = intl.formatMessage({
    defaultMessage: "This is a message",
  })
  ```
