# Translations (i18n) in the client Client-side conventions only. How keys are stored, named, and written — naming, interpolation, plurals, formatters, what the linter enforces — is in [docs/i18n.md](../i18n.md), and applies here too. ## Reading translations Use `useTranslation` from `react-i18next`: ```tsx import { useTranslation } from 'react-i18next'; const DeleteRoomButton = () => { const { t } = useTranslation(); return ; }; ``` ❌ Not `useTranslation` from `@rocket.chat/ui-contexts` — it is `@deprecated` and re-renders every consumer of the translation context on language change. ~125 client modules still import it against ~890 for the `react-i18next` one; don't add to the count. [`TranslationProvider`](../../apps/meteor/client/providers/TranslationProvider.tsx) bundles `en` statically and sets it as `fallbackLng`, so English keys resolve immediately. Only a non-English active locale is fetched over HTTP (`partialBundledLanguages: true`); until it arrives, and for any key it omits, the English string is used. The server, by contrast, holds all 68 locales in memory. ## Never build keys at runtime `t()` does not reject unknown keys (see [docs/i18n.md](../i18n.md#types-are-generated-from-en)), so a computed key has nothing checking it — and it makes the key invisible to anyone grepping for it. ❌ Incorrect: ```tsx {t(`App_status_${instance.status}` as TranslationKey)} ``` ✅ Correct — map the value to a literal key: ```tsx const APP_STATUS_LABELS = { installed: 'App_status_installed', updating: 'App_status_updating', } as const satisfies Record; {t(APP_STATUS_LABELS[instance.status])} ``` The map is the thing that tells you a key is missing when a new status is added, and `satisfies` is what makes the map itself checkable. `as TranslationKey` appears about 70 times in the client; treat every one as debt rather than precedent. ## Rich text: the `Trans` component When a sentence contains a link, bold run, or nested element, don't split it into several keys. Use `Trans` and put the markup in the translation: ```json { "Unique_ID_change_detected_learn_more_link": "Read the documentation before proceeding." } ``` ```tsx import { Trans } from 'react-i18next'; }} />; ``` Name your tags. Indexed placeholders (`<1>`, `<3>`) appear in older keys and are effectively unreadable for a translator, who can't tell what index 3 wraps: ❌ Avoid: ```json { "Limits_reached": "Your workspace reached the <1>{{val}} limit. <3>Manage subscription." } ``` ✅ Prefer: ```json { "Limits_reached": "Your workspace reached the {{val}} limit. Manage subscription." } ``` The base locale is currently split about evenly between the two (~50 values each), so this is a direction, not an established majority. Pass interpolation values through `values` and `t` options through `tOptions`: ```tsx ``` ## Interpolated values are not HTML-escaped `escapeValue` is `false`. That is safe only because React escapes strings when it renders them as text nodes — it is not a property of the translation itself. So if a translated string is injected as HTML, sanitize it, as the two existing call sites do: ```tsx ``` Prefer `Trans` over injected HTML wherever possible — it needs no sanitizing because the components are real React elements, not parsed markup.