# Translation

> LO-TEK has almost no text. You write it with the translation tool your app already uses, and the few texts the package has can be replaced.

LO-TEK has almost no text. Whoever renders the markup writes the text in it, in their own language and with the translation tool the app already uses. The few texts built into the package are defaults, and each of them can be replaced. They are listed in the table at the bottom.

## The text is in your markup

A button, a label and a dialog are markup you write yourself, and the text in them is yours. In React with i18next, the session timeout looks like this:

```tsx
import { useId } from "react"
import { useTranslation } from "react-i18next"
import { lt } from "@lo-tek/interfaces/react"


export function SessionWarning() {
  const { t } = useTranslation()
  const session = lt.sessionTimeout({ titleId: useId() })


  return (
    <lt-session-timeout {...session.host}>
      <dialog {...session.dialog}>
        <h2 {...session.title}>{t("session.title")}</h2>
        <p {...session.text}>
          {t("session.beforeCount")} <span {...session.count} /> {t("session.afterCount")}
        </p>
        <span {...session.live} />
        <form {...session.actions}>
          <button {...session.extend}>{t("session.continue")}</button>
          <button {...session.logout}>{t("session.logOut")}</button>
        </form>
      </dialog>
    </lt-session-timeout>
  )
}
```

On a server it is the same markup, with the text coming from the server’s own setup. In a Go template it can be a function the app has registered itself:

```html
<h2 class="lt-session-timeout__title" id="session-title">{{ T "session.title" }}</h2>
```

## Tools

LO-TEK does not care which tool you use, as long as it produces text. Some common choices:

| Where the markup is made | Common tools |
| - | - |
| React | i18next with react-i18next, FormatJS (react-intl), Lingui |
| Other JavaScript | i18next, Paraglide, FormatJS |
| Server | gettext, go-i18n in Go, resource files in .NET, `ResourceBundle` in Kotlin and Java |

## Why LO-TEK has no integration

- **The package has no dependencies, and is not getting any.** An integration with i18next would make i18next part of every app that uses LO-TEK.
- **Most of the tools have a separate package per framework,** such as react-i18next. An integration would tie LO-TEK to one framework again, which is what the whole system is built to avoid.
- **A server in Go or Kotlin cannot use a JavaScript library.** Its text has to come from the server anyway.
- **The text belongs to the markup, and the markup comes from whoever renders it.** See [Markup and behaviour](/markup-and-behaviour.md).

## Numbers and durations

The components format numbers and durations with the browser’s own `Intl`, following `lang` on the page. The announcement in [Session Timeout](/components/session-timeout.md) says “1 minute and 30 seconds” on an English page and “1 minutt og 30 sekunder” on a Norwegian one, without anyone having translated it. [Suggestion](/components/suggestion.md) picks the plural form with `Intl.PluralRules`, so Polish gets all four of its forms.

Set `lang` on `<html>`, therefore, and on any element in a different language from the rest of the page. Without `lang`, the browser’s own language is used.

## The texts in the package

These are all the texts LO-TEK writes into the page itself. A guard in the build fails on a new text that is not listed here, on a text in CSS that is not read from a variable, and on a text written directly in a component.

| Component | Default text | How to replace it |
| - | - | - |
| [Toast](/components/toast.md), the region | Notifications | `label` on `<lt-toast>`, or `label` in `lt.toast()` |
| [Toast](/components/toast.md), the close button | Close message | `close-label` on `<lt-toast>`, `closeLabel` in `lt.toast()`, or `closeLabel` in `show()` for a single message |
| [Connection Status](/components/connection-status.md), no connection | No connection. What you type is not being saved. | `offline-text` |
| [Connection Status](/components/connection-status.md), back | Connection restored. | `online-text` |
| [Suggestion](/components/suggestion.md), no matches | No matches | `count-none` |
| [Suggestion](/components/suggestion.md), one match | One match | `count-one` |
| [Suggestion](/components/suggestion.md), several matches | `{n} matches` | `count-other`, and `count-zero`, `count-two`, `count-few` and `count-many` for languages that have them |
| [Label](/components/label.md) and [Fieldset](/components/fieldset.md), required with a symbol | \* | `--lt-label-required-symbol` |
| [Label](/components/label.md) and [Fieldset](/components/fieldset.md), required with text | (required) | `--lt-label-required-text` |
| [Label](/components/label.md) and [Fieldset](/components/fieldset.md), optional | (optional) | `--lt-label-optional-text` |

[Session Timeout](/components/session-timeout.md) is not in the table. It has no text: you write the dialog yourself.

The texts in the stylesheets are replaced with a variable, for example by the language of the page. This example translates them into Norwegian on purpose, to show a language other than the defaults:

```css
:root:lang(nb) {
  --lt-label-required-text: " (påkrevd)";
  --lt-label-optional-text: " (valgfri)";
}
```

In the suggestion field, `{n}` stands for the count. Write it in every form except `count-none`, including `count-one`: in French that form also covers 0 matches, and in Russian also 21. The Norwegian example is on purpose too:

```html
<lt-suggestion lang="nb" count-none="Ingen treff" count-one="{n} treff" count-other="{n} treff">
```

If you use i18next, you can pass the translation straight in, as `count-other={t("matches.other")}`. i18next leaves `{n}` with single braces alone, so it can stay in the translation file.
