55ed4b7adb
## What
Lets app **front components** localize the strings they render,
extending the
existing application-translation pipeline (which today only covers
manifest
labels) to component source. App authors mark strings with a small,
familiar
API; the build extracts and bakes them; the runtime resolves them for
the
user's locale.
```tsx
import { Trans, t, msg, useTranslate } from 'twenty-sdk/front-component';
<Trans>Loading postcard…</Trans>
<Trans context="card-title">Untitled</Trans> // disambiguation
const empty = t('No content yet…'); // works outside JSX
<p>{t('Saved {count} cards', { count })}</p> // interpolation
const STATUSES = [{ id: 'draft', label: msg('Draft') }]; // lazy descriptor
```
## How
- **Runtime** (`twenty-sdk/front-component`): `t()` (eager, usable
anywhere —
event handlers, helpers, module scope), `msg()` (lazy descriptor),
`<Trans>`
(reactive JSX), `useTranslate()` / `useLocale()`. Source-string
fallback,
`{name}` interpolation, and `context` disambiguation. No build-time
macro —
these are plain runtime functions.
- **Extraction**: a `ts-morph` scan collects `t()`/`msg()`/`<Trans>`
strings
from component source into the same `locales/*.json` catalogs the
manifest
pipeline already writes (`twenty dev:translations-extract`).
- **Delivery**: `twenty dev:build` bakes the compiled per-locale catalog
into
each front-component bundle via an esbuild banner, so the runtime
resolves
with **no server or renderer changes**. Locale comes from the execution
context that already flows to the worker.
The catalog key and `generateMessageId` hashing are shared between the
node
extractor and the browser runtime; `<Trans>` text whitespace is
normalized
identically on both sides so multi-line elements resolve.
## Design notes
- Reuses the existing `extract → compile → manifest.translations`
contract and
`generateMessageId`, so component strings flow through the same
machinery as
manifest labels.
- Self-contained in `twenty-sdk` + a shared pure helper; the server is
untouched.
## Scope / follow-ups
- `twenty dev` (watch) does not bake catalogs yet — preview shows source
strings; use `twenty dev:build` (documented). Wiring the watcher is a
follow-up.
- Usage is documented in twenty-docs under **Apps → Translations**
(`developers/extend/apps/translations`).
## Tests
Unit tests for the catalog-key/interpolation helpers, the runtime
resolver
(hit/miss/context/fallback/interpolation), and the ts-morph extractor
(static `t`/`msg`/`<Trans>`, dynamic-skip, dedup, multi-line
whitespace), plus a
compile test for context→messageId. Verified with an adversarial review
pass.
https://claude.ai/code/session_01NiE7o3cd3zCLZarVkJa6UA
---
_Generated by [Claude
Code](https://claude.ai/code/session_01NiE7o3cd3zCLZarVkJa6UA)_
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22301?utm_source=github"
rel="nofollow noreferrer noopener" target="_blank">``<img alt="Review
in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg">``</a>
---------
Co-authored-by: github-actions <github-actions@twenty.com>
196 lines
4.6 KiB
JSON
196 lines
4.6 KiB
JSON
{
|
|
"tabs": {
|
|
"gettingStarted": {
|
|
"label": "Getting Started",
|
|
"groups": {
|
|
"welcome": {
|
|
"label": "Welcome"
|
|
},
|
|
"coreConcepts": {
|
|
"label": "Core Concepts"
|
|
}
|
|
}
|
|
},
|
|
"userGuide": {
|
|
"label": "User Guide",
|
|
"groups": {
|
|
"userGuideOverview": {
|
|
"label": "Overview"
|
|
},
|
|
"dataModel": {
|
|
"label": "Data Model",
|
|
"groups": {
|
|
"dataModelReference": {
|
|
"label": "Reference"
|
|
},
|
|
"dataModelHowTos": {
|
|
"label": "How-Tos"
|
|
}
|
|
}
|
|
},
|
|
"dataMigration": {
|
|
"label": "Data Migration",
|
|
"groups": {
|
|
"dataMigrationReference": {
|
|
"label": "Reference"
|
|
},
|
|
"dataMigrationHowTos": {
|
|
"label": "How-Tos"
|
|
}
|
|
}
|
|
},
|
|
"calendarEmails": {
|
|
"label": "Calendar & Emails",
|
|
"groups": {
|
|
"calendarEmailsReference": {
|
|
"label": "Reference"
|
|
},
|
|
"calendarEmailsHowTos": {
|
|
"label": "How-Tos"
|
|
}
|
|
}
|
|
},
|
|
"workflows": {
|
|
"label": "Workflows",
|
|
"groups": {
|
|
"workflowsReference": {
|
|
"label": "Reference"
|
|
},
|
|
"workflowsHowTos": {
|
|
"label": "How-Tos",
|
|
"groups": {
|
|
"crmAutomations": {
|
|
"label": "CRM Automations"
|
|
},
|
|
"connectToOtherTools": {
|
|
"label": "Connect to Other Tools"
|
|
},
|
|
"advancedConfigurations": {
|
|
"label": "Advanced Configurations"
|
|
},
|
|
"needMoreHelp": {
|
|
"label": "Need More Help"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"ai": {
|
|
"label": "AI",
|
|
"groups": {
|
|
"aiReference": {
|
|
"label": "Reference"
|
|
},
|
|
"aiHowTos": {
|
|
"label": "How-Tos"
|
|
}
|
|
}
|
|
},
|
|
"layout": {
|
|
"label": "Layout",
|
|
"groups": {
|
|
"layoutReference": {
|
|
"label": "Reference",
|
|
"groups": {
|
|
"layoutViews": {
|
|
"label": "Views"
|
|
}
|
|
}
|
|
},
|
|
"layoutHowTos": {
|
|
"label": "How-Tos"
|
|
}
|
|
}
|
|
},
|
|
"dashboards": {
|
|
"label": "Dashboards",
|
|
"groups": {
|
|
"dashboardsReference": {
|
|
"label": "Reference"
|
|
},
|
|
"dashboardsHowTos": {
|
|
"label": "How-Tos"
|
|
}
|
|
}
|
|
},
|
|
"permissionsAccess": {
|
|
"label": "Permissions & Access",
|
|
"groups": {
|
|
"permissionsAccessReference": {
|
|
"label": "Reference"
|
|
},
|
|
"permissionsAccessHowTos": {
|
|
"label": "How-Tos"
|
|
}
|
|
}
|
|
},
|
|
"billing": {
|
|
"label": "Billing",
|
|
"groups": {
|
|
"billingReference": {
|
|
"label": "Reference"
|
|
},
|
|
"billingHowTos": {
|
|
"label": "How-Tos"
|
|
}
|
|
}
|
|
},
|
|
"settings": {
|
|
"label": "Settings",
|
|
"groups": {
|
|
"settingsReference": {
|
|
"label": "Reference"
|
|
},
|
|
"settingsHowTos": {
|
|
"label": "How-Tos"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"developers": {
|
|
"label": "Developers",
|
|
"groups": {
|
|
"developersOverview": {
|
|
"label": "Overview"
|
|
},
|
|
"apps": {
|
|
"label": "Apps",
|
|
"groups": {
|
|
"appsGettingStarted": {
|
|
"label": "Getting Started"
|
|
},
|
|
"appsConfig": {
|
|
"label": "Config"
|
|
},
|
|
"appsData": {
|
|
"label": "Data"
|
|
},
|
|
"appsLogic": {
|
|
"label": "Logic"
|
|
},
|
|
"appsLayout": {
|
|
"label": "Layout"
|
|
},
|
|
"appsOperations": {
|
|
"label": "Operations"
|
|
},
|
|
"appsTranslations": {
|
|
"label": "Translations"
|
|
}
|
|
}
|
|
},
|
|
"api": {
|
|
"label": "API"
|
|
},
|
|
"selfHost": {
|
|
"label": "Self-Host"
|
|
},
|
|
"contribute": {
|
|
"label": "Contribute"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|