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. Not all components need localizing, as for the most part snippets of text are set per instance. For example, the label of a 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. 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 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 for the rest):
<html lang="fi"></html>A MutationObserver watches the attribute, so changing it switches every component on the page without a reload:
document.documentElement.lang = "sv";Set lang correctly even if you only use Finnish. A missing or wrong value is an accessibility failure 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:
<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 instead.
Server rendering #
If your app renders its HTML on the server, for example with Next.js, 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. 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:
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. 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:
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:
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:
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, build the translation from it, so all text stays in one place:
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:
// 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>
);
}<!-- 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.