
# Localization

> Localization adapts a product to the language and region of its users. This guide explains how our components pick theirs, and how to add a translation of your own.

Kesko Design System provides a lightweight way to localize its [components](/components/). Not all components need localizing, as for the most part snippets of text are set per instance. For example, the [label of a Button](/components/button/) will likely be changed every time you use it. Those cases belong to your app.

However, some components have text which has no reason to change per instance. For example, an announcement a component makes [while it is loading](/components/spinner/). For the latter cases, we provide a localization mechanism and ship translations with the package for Finnish, Swedish, Norwegian and English.

## How it works

Components read the [`lang`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/lang) attribute of the `<html>` element and use that language. A plain `fi`, `sv`, `no` or `en` is enough. A language without a translation, or no `lang` at all, falls back to Finnish (see [Adding a language](#adding-a-language) for the rest):

```html
<html lang="fi"></html>
```

A [`MutationObserver`](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver) watches the attribute, so changing it switches every component on the page without a reload:

```js
document.documentElement.lang = "sv";
```

Set `lang` correctly even if you only use Finnish. A missing or wrong value is an [accessibility failure](/a11y-checklist/#markup-0-13) on its own, and it also decides the language of the components.

## Overriding one instance

The translations above apply to every component on the page. To override the text of a single instance, pass it as a prop. A prop always wins over the built-in translations:

```tsx
<Spinner label="Haetaan tuotteita" />
<Button loading loadingLabel="Lähetetään tilausta">Tilaa</Button>
<Pagination previousLabel="Uudemmat" nextLabel="Vanhemmat" />
```

The props that override a translation are listed on each component’s documentation page. Use them when the text depends on the context, such as what is being loaded. If every instance in your app needs the same new text, [add a translation](#adding-a-language) instead.

## Server rendering

If your app renders its HTML on the server, for example [with Next.js](/components/usage/#nextjs-usage), the components cannot read the `lang` attribute of the page while they’re being rendered. In this case, the browser would only switch the language once the page has loaded.

To avoid that, tell the components the language directly with the `locale` prop of [`KeskoProvider`](/components/usage/#client-side-routing). The prop also takes precedence over `<html lang>` in the browser, and you can wrap part of a page in another `KeskoProvider` to give that part a different language:

```tsx
import { KeskoProvider } from "@kesko/components";

export function Providers({ children, locale }) {
  return <KeskoProvider locale={locale}>{children}</KeskoProvider>;
}
```

`KeskoProvider` is the same component that handles [client-side routing](/components/usage/#client-side-routing). Pass `navigate` alongside `locale` and wrap your app once.

## Adding a language

A translation is a plain JavaScript object with the language code, the language name, and the text for each component. Pass it to `KeskoProvider` and it is used whenever the page language or `locale` matches it:

```tsx
import { KeskoProvider } from "@kesko/components";
import type { Translation } from "@kesko/components";

const da: Translation = {
  $lang: "da",
  $name: "Dansk",
  button: { loading: "Indlæser" },
  spinner: { label: "Indlæser" },
  pagination: { label: "Sidenavigation", previous: "Forrige", next: "Næste" },
};

<KeskoProvider translations={[da]}>{children}</KeskoProvider>;
```

If you use TypeScript, the `Translation` type lists every piece of text the components need, and your editor flags any you forgot. Text that is still missing when the app runs falls back to Finnish.

If your app supports many languages, you do not have to load every translation up front. Instead, you can load the one the user picks, hand it to `KeskoProvider`, and set the page language at that point:

```tsx
const [translations, setTranslations] = useState<Translation[]>([]);

async function changeLanguage(lang: string) {
  const { default: translation } = await import(`./translations/${lang}.ts`);
  setTranslations([translation]);
  document.documentElement.lang = lang;
}

<KeskoProvider translations={translations}>{children}</KeskoProvider>;
```

## Example translation

The Finnish translation that ships with the package, as a reference:

```ts
const fi: Translation = {
  $lang: "fi",
  $name: "Suomi",
  button: { loading: "Ladataan" },
  spinner: { label: "Ladataan" },
  pagination: { label: "Sivutus", previous: "Edellinen", next: "Seuraava" },
};
```

## Using an existing library

If your app already keeps its text in a translation library such as [i18next](https://www.i18next.com/), build the translation from it, so all text stays in one place:

```ts
import type { Translation } from "@kesko/components";
import i18next from "i18next";

export function keskoTranslation(locale: string): Translation {
  const t = i18next.getFixedT(locale);
  return {
    $lang: locale,
    $name: t("language.name"),
    button: { loading: t("kesko.button.loading") },
    spinner: { label: t("kesko.spinner.label") },
    pagination: {
      label: t("kesko.pagination.label"),
      previous: t("kesko.pagination.previous"),
      next: t("kesko.pagination.next"),
    },
  };
}
```

Pass the result to `KeskoProvider` with the same `locale`, and the components change language together with the rest of your app.

## Using one language per build

If your product ships one language per build, for example a build per country, the language is a constant. Set `lang` once in the HTML template and the components use it from the first render and don’t switch it at runtime:

```tsx [Next.js]
// app/layout.tsx
import { KeskoProvider } from "@kesko/components";

const locale = process.env.NEXT_PUBLIC_LOCALE;

export default function RootLayout({ children }) {
  return (
    <html lang={locale}>
      <body>
        <KeskoProvider locale={locale}>{children}</KeskoProvider>
      </body>
    </html>
  );
}
```

```html [Vite]
<!-- index.html -->
<html lang="%VITE_LOCALE%"></html>
```

A server-rendered build also passes the same constant as `locale` so the HTML the server sends is already in the right language, as explained [in this section](#server-rendering).
