en is the source locale you translate from, and
any locale left untranslated falls back to it.
Your app has two kinds of translatable text, and both flow through the same
locales/ catalog:
- Manifest labels — object and field names, view titles, menu items, and other strings declared in your app’s metadata.
- Front-component strings — the UI text your React front components render.
Marking front-component strings
Import the translation helpers fromtwenty-sdk/front-component:
When to use which
Trans— static text in JSX. Use themessageandvaluesprops for interpolation, as inTrans message="Hi {name}" values={{ name }}; interpolating directly in the children is not statically extractable.useTranslate().t— dynamic strings inside a component. Re-renders when the user switches language. Prefer this inside render.t(...)(imported directly) — eager translation usable anywhere, including event handlers, helpers, and module scope — not only inside render.msg(...)— a lazy descriptor for strings declared as data (constants, config). Resolve it later witht(descriptor).
Context
Passcontext to disambiguate identical source strings that translate
differently:
"<context>": { "<message>": "<translation>" }. Manifest labels are
contextual by construction — each one carries the metadata role it plays
(objectMetadata.labelSingular, fieldMetadata.label, view.name, …), so
the same word can translate differently as an object name and as a view name.
Extracting and translating
Run the extract command from your app directory:t()/msg()/Trans
strings from your front-component source into locales/<locale>.json, keyed by
source string. Fill in the translations:
{name} are substituted at runtime — keep them in the
translation. Any string left empty falls back to the source text.
How it runs
twenty dev:build compiles the catalogs and serves the right language for the
current user: manifest labels are resolved server-side, and front-component
catalogs are baked into each component bundle. At runtime a component reads the
locale from its execution context (the host’s current language) and resolves
each string against its catalog, falling back to the source when a translation
is missing. Switching language in the host re-renders Trans and
useTranslate().t strings live.
Manifest labels are compiled on every sync — twenty dev:build, twenty apply
and the continuous twenty dev watch alike — so editing a locale file and
syncing is enough to see the new text. Front component catalogs are baked into
each component bundle at build time, so changing one of those still means
re-running twenty dev:build (and redeploying).
Pulling translations
twenty pull writes an application’s published translations back into
locales/. A published catalog is keyed by message id, so pull recovers the
readable form only for strings whose source it can see: the labels of the
metadata it pulled and the t()/msg()/Trans strings of front components
present in the project. Everything else is kept as is in
locales/compiled/<locale>.json, keyed by message id. The build merges those
files into the catalog (an entry in locales/<locale>.json wins over a
compiled one with the same id), dev:translations-extract leaves them alone,
and every pull rewrites them, so they shrink as more of the app’s source lands
in the tree. Do not edit them by hand; translate in locales/<locale>.json.
A project without a locales/ folder says nothing about translations, so a
sync from it leaves whatever the app already published untouched. To remove
published translations on purpose, keep the folder and delete the locale files
inside it: a locales/ folder that compiles to no locale is an explicit
declaration, and the sync prunes accordingly.
Trans text children may span multiple lines — whitespace is collapsed the
same way JSX collapses it, so both of these extract to the key Welcome back: