Internationalization (i18n) =========================== This chapter covers how CARE localizes UI text: frontend and backend patterns, and how to add a new language. Where files live ---------------- All translation keys live under ``utils/modules/i18n/``: * ``en/*.json``, ``de/*.json`` — key catalogs per UI language (one file = one namespace, e.g. ``users.json``) * ``en/index.js``, ``de/index.js`` — import JSON files and bundle namespaces for the frontend * ``i18n-bundles.js`` — bundles ``en`` / ``de`` namespaces for ``vue-i18n`` Related code: * ``frontend/src/assets/utils.js`` — ``resolveApiMessage``, ``translateMaybeKey``, ``formatLocalized*`` * ``frontend/src/assets/locale.js`` — language switcher, ``app.locale`` * ``backend/utils/i18n.js`` — ``translateMaybeKey``, ``hasKey`` (backend runtime and migrations) Translation guidelines ---------------------- Do **not** translate ~~~~~~~~~~~~~~~~~~~~ * **Table content** — study titles, document names, workflow names, etc. * **Server logs** — dashboard log table and log files are **always English** (see :ref:`logging-i18n` below) * Developer ``console.log`` / debug output * **Template body content** — multi-language content, not UI strings How to translate ---------------- .. code-block:: text ┌─────────────────────────┐ │ What do you │ │ need? │ └───────────┬─────────────┘ ┌─────────────────┴─────────────────┐ │ │ New UI language UI text │ │ ▼ ▼ ┌──────────────────────┐ ┌──────────────────┐ │ 5. Adding a new UI │ │ 1. Adding a key │ │ language │ │ & │ │ │ │ 2. Adding a │ └──────────┬───────────┘ │ namespace │ │ └────────┬─────────┘ │ │ │ ┌──────────────────┘ │ ▼ │ ┌─────────────────────────┐ │ │ Frontend or │ │ │ backend? │ │ └───────────┬─────────────┘ │ ┌───────┴───────┐ │ ▼ ▼ │ 3. Frontend 4. Backend │ │ │ └─────────┴───────┬───────┘ ▼ ┌─────────────────────────┐ │ 6. Test it out │ └─────────────────────────┘ Sections in this chapter : | :ref:`i18n-add-key` — new key in an existing JSON file | :ref:`i18n-add-namespace` — new ``*.json`` namespace file | :ref:`i18n-frontend` — ``$t``, ``resolveApiMessage``, table columns, … | :ref:`i18n-backend` — ``TranslatableError``, backend ``translateMaybeKey``, … | :ref:`i18n-add-language` — new UI language | :ref:`i18n-test` — verify in the UI and logs | :ref:`i18n-linter` — automated i18n checks .. _i18n-add-key: 1. Adding a key --------------- .. note:: Need a **new JSON file** for a dashboard or feature? See :ref:`i18n-add-namespace`. **Example:** you have a short hint for users in ``Users.vue`` — hardcoded English in a ``

``: .. code-block:: html

Manage team members from this page.

To show it in the user's UI language, replace the literal text with ``$t(...)``. That requires a **key** in the locale JSON files first. **1. Choose a key name** Use dot notation: ``namespace.rest.of.key``. The first segment is the JSON filename (namespace), e.g. ``users.labels.manageHint`` → file ``users.json``, nested path ``labels.manageHint``. **2. Add the key in every active locale** ``utils/modules/i18n/en/users.json``: .. code-block:: json { "labels": { "manageHint": "Manage team members from this page." } } ``utils/modules/i18n/de/users.json`` — same key, translated value: .. code-block:: json { "labels": { "manageHint": "Verwalten Sie Teammitglieder auf dieser Seite." } } Repeat for every locale folder (``en/``, ``de/``, …). **Keys must match** across locales; only the text values differ. **3. Use the key in the template** .. code-block:: html

{{ $t('users.labels.manageHint') }}

When the user switches language in **Preferences**, the paragraph updates automatically. More frontend patterns (placeholders, toasts, table headers, …): :ref:`i18n-frontend`. Backend errors and logging use keys too: :ref:`i18n-backend`. Key layout inside a JSON file ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Organize keys by **screen or feature**. Group related strings under clear subsections: .. code-block:: json { "workflows": { "title": "Workflows", "labels": { "addWorkflow": "Add Workflow" }, "errors": { "importFailed": "Import failed" } } } Useful subsections: * ``title`` — page or modal title * ``labels`` — field labels, short UI phrases, hints * ``errors`` — validation or action errors for that screen * ``columns`` — table header labels * ``toasts`` — toast title/message pairs * ``messages`` — confirm dialogs, longer text Reuse ``common`` for words that appear everywhere. Before adding a new key, check ``utils/modules/i18n/en/common.json`` — shared labels such as ``id``, ``email``, ``save``, ``cancel``. Backend error keys usually go in ``errors.json`` (see :ref:`i18n-backend`). .. _i18n-add-namespace: 2. Adding a new namespace ------------------------- Each ``*.json`` file in a locale folder is one **namespace**. The filename (without ``.json``) becomes the first part of every key inside it. Example: ``reports.json`` with ``"title": "Reports"`` → use ``$t('reports.title')``. **1. Create the file in every active locale** (same keys, translated values): .. code-block:: text utils/modules/i18n/en/reports.json utils/modules/i18n/de/reports.json .. code-block:: json { "title": "Reports", "labels": { "export": "Export" } } **2. Register in** ``index.js`` **for each locale** (``en/index.js``, ``de/index.js``, …) — required for the frontend: .. code-block:: javascript import reports from './reports.json' export default { // ...existing namespaces... reports, } **3. Use the keys** in Vue components: .. code-block:: html

{{ $t('reports.title') }}

.. _i18n-frontend: 3. Frontend ----------- Common patterns — **before** (hardcoded English) and **after** (i18n). Add keys first as in :ref:`i18n-add-key`. Static text in a template ($t) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. code-block:: html

Manage team members from this page.

{{ $t('users.labels.manageHint') }}

Button or attribute label ~~~~~~~~~~~~~~~~~~~~~~~~~ .. code-block:: html Text with a placeholder ~~~~~~~~~~~~~~~~~~~~~~~ .. code-block:: html

Hello, {{ userName }}!

{{ $t('greeting.hello', { name: userName }) }}

Lists of strings ($tm) ~~~~~~~~~~~~~~~~~~~~~~ When a key holds an **array** of messages (e.g. rotating status text), use ``$tm``: .. code-block:: json { "loading": { "messages": [ "Thinking through your request...", "Almost there..." ] } } .. code-block:: javascript data() { return { messages: this.$tm('loading.messages'), index: 0, }; } .. _i18n-t-rich-text: Rich text in translations ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ When a sentence needs **inline markup** (:code:``, :code:`
`, a clickable link) **or** dynamic values that must stay safe, use the vue-i18n :code:`` keypath and tag ~~~~~~~~~~~~~~~ * :code:`keypath` — the translation key (same string you would pass to :code:`$t('…')`). * :code:`tag` — the **HTML element that wraps the finished sentence**. :code:`i18n-t` does not render a bare text node. It builds the full phrase (text + slots) and wraps it in one element — the one named by :code:`tag`: .. list-table:: :widths: 28 72 :header-rows: 0 * - :code:`tag="p"` - paragraph wrapper → :code:`

…

` * - :code:`tag="span"` - inline wrapper → :code:`…` * - :code:`tag="div"` - block wrapper → :code:`
…
` Pick the tag for layout/semantics (paragraph, inline hint, block summary). **How it fits together** **1. Locale** — one key, placeholder :code:`{0}` where the slot goes: .. code-block:: json { "csvTemplateHint": "Please check the format or {0} here.", "downloadTemplate": "download the template" } **2. Vue** — :code:`keypath` loads the key; :code:`tag="p"` wraps the output; the :code:`` fills :code:`{0}`: .. code-block:: vue {{ $t('dashboard.users.downloadTemplate') }}
**3. Result in the browser:** .. code-block:: html

Please check the format or download the template here.

Named slots ~~~~~~~~~~~ Use when placeholders in the JSON have **names** (:code:`{newCount}`, :code:`{br}`, :code:`{dupCount}`). Each name maps to a :code:`