forked from akai/readest
43f72720f2
* feat(i18n): add Uzbek (Oʻzbek) translation Adds `uz` as a first-class supported locale across the Readest app and the readest.koplugin companion. Also refactors the supported locale set to source from a single ground-truth file (`apps/readest-app/i18n-langs.json`) consumed by both the i18next runtime and the i18next-scanner config, and replaces a NUL-byte sentinel in `extract-i18n.js#unescapePo` with a single-pass regex so git no longer treats the script as binary. Closes #4053 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(i18n): add Brazilian Portuguese (pt-BR) translation Adds `pt-BR` as a regional variant supported alongside `pt`. Falls back to `pt` then `en` for any future missing keys, so European Portuguese gracefully covers gaps. Translations follow Brazilian conventions (arquivo / tela / excluir / salvar / baixar / senha, gerundive verb forms) rather than verbatim copying the European catalog. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
73 lines
3.5 KiB
Markdown
73 lines
3.5 KiB
Markdown
## i18n Guide
|
|
|
|
Readest uses a **key-as-content** approach — English strings are the translation keys. The English locale (`en/translation.json`) is empty because keys serve as content. Other locales contain actual translations.
|
|
|
|
### In React Components
|
|
|
|
```tsx
|
|
import { useTranslation } from '@/hooks/useTranslation';
|
|
|
|
const _ = useTranslation();
|
|
_('Progress synced');
|
|
```
|
|
|
|
### In Non-React Modules
|
|
|
|
Two-step process:
|
|
|
|
**1. Declaration** — Use `stubTranslation` to mark strings for scanner extraction (returns key as-is, does NOT translate):
|
|
|
|
```ts
|
|
import { stubTranslation as _ } from '@/utils/misc';
|
|
|
|
// These calls only register keys for extraction
|
|
_('Reveal in Finder');
|
|
_('Reveal in Explorer');
|
|
```
|
|
|
|
**2. Usage** — In the React component that consumes the value, apply the real `_()` from `useTranslation`:
|
|
|
|
```tsx
|
|
const _ = useTranslation();
|
|
const label = _(getRevealLabel()); // translates at runtime
|
|
```
|
|
|
|
### Extraction & Translation
|
|
|
|
```bash
|
|
pnpm i18n:extract # Scans codebase, adds new keys with __STRING_NOT_TRANSLATED__
|
|
```
|
|
|
|
- Translation files: `public/locales/<locale>/translation.json`
|
|
- Only `_('KEY')` and `_('KEY', options)` patterns are recognized by i18next-scanner
|
|
|
|
### Adding a New Translation Language
|
|
|
|
The supported language set has a single ground truth: [`i18n-langs.json`](../i18n-langs.json). Both the i18next runtime (`src/i18n/i18n.ts`) and the extractor (`i18next-scanner.config.cjs`) read from it, so adding a locale is a two-file change plus a translation pass.
|
|
|
|
1. **Add the locale code** to [`i18n-langs.json`](../i18n-langs.json). Use the exact code i18next will emit (e.g. `hu`, `zh-CN`). Do not add `en` — it's the source language and lives outside this list.
|
|
|
|
2. **Add a display label** to `TRANSLATED_LANGS` in [`src/services/constants.ts`](../src/services/constants.ts). The key is the locale code, the value is the language's native name (e.g. `hu: 'Magyar'`). This is what users see in the language picker.
|
|
|
|
3. **Generate the translation file**:
|
|
|
|
```bash
|
|
pnpm i18n:extract
|
|
```
|
|
|
|
This creates `public/locales/<code>/translation.json` with every key set to `__STRING_NOT_TRANSLATED__`.
|
|
|
|
4. **Translate** every `__STRING_NOT_TRANSLATED__` placeholder in the new file. The `/i18n` skill automates this; the singular `en/translation.json` only holds plural variants and proper nouns, so use the JSON keys themselves as the English source.
|
|
|
|
5. **Verify** with `grep -r "__STRING_NOT_TRANSLATED__" public/locales/<code>/` — the result should be empty.
|
|
|
|
6. **Translate the KOReader companion plugin** (`apps/readest.koplugin`). It pulls the locale set from the same `i18n-langs.json` via the scanner config, but the catalog format is gettext `.po`, not JSON. Steps:
|
|
- Add a `LANG_META` entry (label + Plural-Forms) for the new code in [`apps/readest.koplugin/scripts/extract-i18n.js`](../../readest.koplugin/scripts/extract-i18n.js). Without it the extractor prints `<code> skipped (no metadata in extract-i18n.js)` and the catalog is never created.
|
|
- Trigger the `/i18n-koplugin` skill to run extraction and fill every empty `msgstr ""` in `apps/readest.koplugin/locales/<code>/translation.po`.
|
|
|
|
### Rules
|
|
|
|
- `stubTranslation` is for extraction only — always apply `_()` from `useTranslation` in the component for runtime translation.
|
|
- Fallback: when no translation exists, the English key itself is displayed.
|
|
- Error messages: register keys with `stubTranslation` in utility modules (e.g. `src/services/errors.ts`), return the English key from helpers, wrap with `_()` in the component.
|