---
title: Translations
description: Every word of the app in every language it speaks, edited live in the page by translators working together.
section: Frontend
order: 7
---

# Translations

<p class="lead">Translations live in <code>i18n.json</code>, one entry per phrase with a value per language. Translators edit them in the admin UI or directly on the page, and every open page updates without a reload.</p>

## The file

```json title="app/i18n.json"
{
  "locales": ["en", "fr", "de"],
  "classes": { "en": "classes", "fr": "classes", "de": "klassen" },
  "welcome": { "en": "welcome to {{app}}, {{name}}", "fr": "bienvenue sur {{app}}, {{name}}" }
}
```

- Keys are lower case.
- `{{name}}` is interpolated from the params you pass. `{{app}}` is always the app's name.
- Missing languages fall back to English. A key missing in English too renders as `?[key.locale]`, so gaps are visible instead of blank.

## In the app

```tsx title="app/i18n.ts"
import { createI18n } from "sluurp/i18n";
import table from "./i18n.json" with { type: "json" };

export const i18n = createI18n({ table, appName: "My app" });
export const t = i18n.t;
i18n.connect(sluurp);   // live updates
```

```tsx title="app/header.tsx"
<h1>{() => t("classes|title")}</h1>          // "Classes"
<p>{() => t("welcome", { name: me.first_name })}</p>
```

- `|title`, `|upper` and `|lower` change case, so "classes" and "Classes" don't need separate entries.
- Wrapping in an arrow function makes it reactive: `i18n.setLocale("fr")` or a translator's edit re-renders it. A bare `t("x")` is evaluated once.
- The server merges published edits into `i18n.json` when serving it, so the import is always current.

## Translating

- **Admin UI:** **Translations** lists every key with a field per language.
- **In place:** turn on translate mode, click any text and edit it where it appears. Clicks go to the editor, so links don't navigate.
- **Collaboratively:** a translation session holds drafts visible only to its translators. Everyone's cursor shows in their colour, typing appears live, and the session is published in one step.
- **Guests:** a session link lets someone without an account edit (but not publish).

Only users with the `translator` role can translate. The [UI kit](/docs/ui-kit)'s built-in strings (Close, Search…, month names) come pre-translated. `sluurp i18n` lists unused keys.
