Files
lasuite-messages/docs/internationalization.md
T

168 lines
6.1 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Internationalization (i18n)
Only the **frontend** is translated today. It is powered by
[i18next](https://www.i18next.com/),
[react-i18next](https://react.i18next.com/), and
[i18next-cli](https://github.com/i18next/i18next-cli).
The **backend has no translation catalog**: `USE_I18N` is `False`
(`src/backend/messages/settings.py`), there are no `locale/` directories, and
no `gettext` call anywhere in `core/`. Every string the backend emits is
English. See [Backend strings](#backend-strings-not-translated-yet) below.
------------------------------------------------------------------------
## Development Workflow
### Best practices during development
- Always write strings in **English** using `i18next`.
👉 Translations are updated **before each release**.
- Frontend strings are stored in:
`src/frontend/public/locales/{ns}/{locale}.json`
------------------------------------------------------------------------
### Extraction and download of translations
The process is **automated by the CI pipeline**:
- Whenever the `main` branch is updated, the CI
will:
- extract translations
- upload them to **Crowdin**
- Whenever a branch with the prefix `release/` is created, the CI
will:
- download the updated translations
- create a pull request with the changes
Those processes can also be triggered manually.
#### Running the process locally
You can perform these steps locally using the **Makefile**.
⚠️ Make sure you have Crowdin environment variables configured in:
`deploy/env/crowdin.local`
and that you have **sufficient permissions** on the Crowdin project.
- **Extract and upload translations to Crowdin:**
``` sh
make i18n-generate-and-upload
```
- **Download and compile translations:**
``` sh
make i18n-download-and-compile
```
#### Updating translations locally (not recommended)
It is possible (but discouraged) to manually edit translations locally:
1. Generate translation files:
``` sh
make i18n-generate
```
2. Edit missing translations directly in the generated files.
3. Commit your changes.
The JSON catalogs are read as-is at runtime, so there is no compilation
step.
⚠️ **Warning: these local changes are likely to be overwritten**
**by the next Crowdin update.**
------------------------------------------------------------------------
## Backend strings (not translated yet)
Some user-facing text is produced by the backend rather than the frontend, and
is therefore **English only**. The main case today is the mailbox export
notification email (`core/services/exporter/tasks.py`,
`_create_notification_message`), which carries the download link to the
mailbox the requester picked as recipient when queueing the export.
### Why not Django i18n
Reintroducing `gettext` is not the plan: it means a second translation format
(`.po`), a compile step, a second Crowdin file type, and `USE_I18N = True`
across the whole app, for a handful of strings.
### Why the backend cannot read `public/locales/common`
Two blockers:
1. **Build context.** The backend image builds from context `src/backend`
(`compose.yaml`) with `COPY . /app/` (`src/backend/Dockerfile`).
`src/frontend/public/locales` is outside that context, so those files are
in no backend image. Sharing them means moving the backend build context to
the repository root, which touches every backend/worker/flower build stanza,
`.dockerignore`, and the CI publish workflow.
2. **The `common` namespace is generated.** `i18next-cli extract`
(`src/frontend/i18next.config.ts`) rewrites `common/en-US.json` from a
static scan of `src/**/*.tsx`. A key added by hand with no `t()` call behind
it is dropped on the next `make i18n-generate-front`.
### Planned approach
Give the backend its own namespace in the same format and the same pipeline:
- `src/backend/core/locales/{en-US,fr-FR,nl-NL}.json` — flat natural-key JSON,
identical in shape to the frontend catalogs (the key *is* the English source
string, `{{var}}` interpolation). It lives inside the backend build context,
so it ships in every image with no Dockerfile change.
- A third entry in `crowdin/config.yml` (source
`/backend/core/locales/en-US.json`, dest `/backend.json`, translation
`/backend/core/locales/%locale%.json`), so `make i18n-upload` and
`make i18n-download` cover it with no new tooling.
- A small loader, following `core/ai/thread_summarizer.py`
(`Path(__file__).parent / …`, cached): `t(key, lang, **vars)` doing `{{var}}`
substitution, falling back to `en-US` then to the key itself.
`User.language` already stores `en-us` / `fr-fr` / `nl-nl`. The export
notification lands in a mailbox, which has no language of its own: it
would follow the language of the user who queued the export.
One caveat: the `_one` / `_many` / `_other` suffixes in the frontend catalogs
are CLDR plural rules (French uses one/many/other, Dutch one/other).
Reimplementing that selection in Python is the awkward part; backend strings can
avoid it by wording counts as `Messages exported: {{count}}` rather than
`{{count}} messages exported`.
------------------------------------------------------------------------
## Contributing as a translator or proofreader
We use [Crowdin](https://crowdin.com) to manage translations.
It allows translators and proofreaders to contribute in the languages
they know best.
👉 For more information, see the [Crowdin
documentation](https://support.crowdin.com).
------------------------------------------------------------------------
### Adding a new language
If the language you need is not yet available:
- Click **Request New Language** on the [project
page](https://crowdin.com/project/lasuite-messages).
- We will review and may add it.
⚠️ If you request a new language, you are expected to help keep it **up
to date** whenever strings are added or modified --- especially before
each release.
If your language already exists in a different variant (e.g. Brazilian
Portuguese vs. European Portuguese), consider contributing to the
existing one unless you have enough resources to maintain a separate
variant.