# i18n — multi-language web apps

`gipity add i18n` makes a web app multi-language: a language picker, `localStorage` persistence, automatic RTL (`<html dir>`), and plural/translation lookup. It installs into `src/packages/i18n/` and **transparently upgrades the app's existing copy calls** — no edits to `main.js`.

## The seam: keep copy in `strings.js`

Web starter apps (`web-simple` and friends) keep all user-facing text in `src/js/strings.js` and read it with `t('key')`, importing the reader from the `@gipity/i18n` specifier:

```js
import { t, tn, getLang } from '@gipity/i18n';

el.textContent = t('welcome');
el.textContent = t('greeting', { name: 'Sam' });   // "Hello, Sam!" — {placeholder} interpolation
el.textContent = tn('items', cart.length);          // plural-aware: items_one / items_other
```

```js
// src/js/strings.js — the one place copy lives
export const strings = {
  welcome: 'Welcome',
  greeting: 'Hello, {name}!',
  items_one: '{count} item',
  items_other: '{count} items',
};
```

Out of the box `@gipity/i18n` resolves to a tiny **monolingual** reader (`src/js/i18n.js`): `t()` just returns the string with interpolation. No picker, no weight. So even a single-language app should route copy through `t()` — it's good structure, and it's what makes step 2 free.

## Add multi-language

```
gipity add i18n
```

The kit's install overrides the `@gipity/i18n` import to its full runtime and adds an `@app/strings` alias pointing at your `src/js/strings.js`. Every `t('key')` call site upgrades at once — translation lookup, the language `<select>` (auto-mounted once translations exist), persistence, and RTL. Then:

> **"translate strings to Spanish and Japanese"**

Translations land in `src/packages/i18n/translations.js`, mirroring the keys in `strings.js`. English stays the base in `strings.js` and never appears there.

```js
export const translations = {
  es: { welcome: 'Bienvenido', items_one: '{count} artículo', items_other: '{count} artículos' },
  ja: { welcome: 'ようこそ', items_other: '{count}個' },
};
```

## The runtime API

```js
import { t, tn, getLang, setLang, availableLangs, isRtl } from '@gipity/i18n';
setLang('es');          // switch + persist; getLang()/<html lang|dir>/listeners all updated synchronously
availableLangs();       // ['en', ...languages present in translations.js]
```

The picker is a `<select>` **auto-mounted** to `<body>` once at least one translation exists — nothing to wire. Style it via the `.lang-picker` class (the web templates already position it top-right). To place it yourself, `mountLangPicker(i18n, parentEl)`.

## Re-rendering on language change

`setLang()` (and the picker) fires an `i18n:changed` event on `document`, synchronously — by the time it returns, `getLang()` and `<html lang>/<html dir>` are already updated. Re-pull your text in a `render()` you wire to it — the web starter's `main.js` already does:

```js
function render() { /* your t('key') → DOM assignments */ }
document.addEventListener('i18n:changed', render);
render();
```

## Notes

- **Add it early.** Routing copy through `t()` as you build the UI is what makes the install zero-edit. Bolting it onto an app with hard-coded strings means moving that copy into `strings.js` first.
- **Plurals** use `_one`/`_other` (plus `_zero`/`_two`/`_few`/`_many` where a language needs them) per `Intl.PluralRules`.
- **RTL** scripts (`ar`, `he`, `fa`, `ur`, `yi`) flip layout automatically.
- **Default language is predictable.** First load defaults to English (the base copy in `strings.js`); the kit only auto-switches to a detected browser language when the app actually *ships* that language (i.e. it's in `translations.js`). A guest on an `ar` browser visiting an English-only app stays English (no surprise RTL flip) — the always-visible picker lets them switch if the guess was wrong, and that explicit choice persists in `localStorage`. To make a different language the default instead of English, set the owner's language as the first/base copy.
- **Number/date/currency** aren't wrapped — `Intl.*` is a one-liner with `getLang()`: `new Intl.NumberFormat(getLang(), { style: 'currency', currency: 'USD' }).format(99.5)`.
- **Web only.** For server-side / function copy, format with `Intl.*` directly.

