Monorepo with shared namespaces
A monorepo with a web app, an admin dashboard and a marketing site has strings that belong to one app and strings, such as legal footers and error messages, that belong to all of them. One project with namespaces keeps the shared ones in one place, translated once, and lets each app load only what it needs.
| Namespace | Contents | Consumed by |
|---|---|---|
default |
Legal text, generic UI (Save, Cancel), error messages | Every app |
marketing |
Landing pages, pricing, blog UI | Marketing site |
app |
In-product strings for the main app | Web app |
admin |
Internal dashboard strings | Admin dashboard |
One project fits apps that ship in the same languages, to the same markets, on the same review schedule. Apps with independent language sets, such as an English-only admin next to a customer app in fifteen languages, or teams that must not see each other’s content, need separate projects; translation memory is organization-level, so they still share matches.
Set up
Section titled “Set up”-
Use
defaultfor the shared strings. It is served at the root of the CDN endpoint and stored as{languageTag}.jsonby the CLI. The dashboard cannot rename or delete it, and the REST API refuses withCANNOT_DELETE_DEFAULT, so treat it as the shared layer rather than trying to turn it intocommon. -
Add one namespace per app. On the Translations page, open the namespace selector, pick Create new namespace… and name it in the Add Namespace dialog. Match the repository layout where you can, so
apps/webbecomesapp. A namespace that holds keys cannot be deleted, so pick names you can live with. -
Give each app its own
.comvirc.jsonlisting the namespaces it loads:apps/admin/.comvirc.json {"apiBaseUrl": "https://api.comvi.io","translationsPath": "./src/locales","fileTemplate": "{namespace}/{languageTag}.json","format": "json","namespaces": ["default", "admin"]}comvi pullwrites only those namespaces andcomvi pushreads only them.--nson the command line replaces the list rather than adding to it. -
Load the namespaces at runtime. Lookups do not cross namespaces: a key is found in the namespace you asked for, or in the same namespace under a fallback locale, and nowhere else. Pass an explicit namespace (
t(key, { ns })) when the key is not in the app’s default one.
my-monorepo/├── apps/│ ├── web/│ │ ├── .comvirc.json # namespaces: ["default", "app"]│ │ └── src/locales/│ ├── admin/│ │ ├── .comvirc.json # namespaces: ["default", "admin"]│ │ └── src/locales/│ └── marketing/│ ├── .comvirc.json # namespaces: ["default", "marketing"]│ └── src/locales/└── packages/ └── ui/ # shared components read the default namespaceDecide what is shared
Section titled “Decide what is shared”- Legal, brand and error text goes in
default. It has to be identical everywhere. - Navigation, buttons and form labels go in
defaultonly if they are identical across apps in every language, not only in English. Otherwise per app. - Domain copy for checkout, admin actions or marketing headlines goes in the app’s own namespace.
Start app-specific and promote a key to default once a second app needs the same value:
tick the rows on the Translations page and choose More → Change Namespace. Values,
history and comments move with the key.
Translators and deploys
Section titled “Translators and deploys”Translators see every namespace in the filter dropdown. Access is per project and,
optionally, per language; there is no namespace-level permission, so scoping an agency to
marketing is a briefing instruction and a filter they set themselves. Translation memory
works across namespaces: “Payment failed” translated in app comes back as a match in
marketing.
Content Deploy publishes one bundle per namespace and language for the namespaces ticked
under Settings → Content Deploy. Tick every namespace your apps fetch; an unticked one is
not deployed. A save in any namespace triggers a deploy of the whole selected set, and what
keeps the apps apart is the fetch: only the marketing app requests the marketing bundles.
See Content Deploy / CDN.
What to avoid
Section titled “What to avoid”- One namespace for everything. Every app then ships every string.
- One namespace per page or component. The filter turns into a list nobody reads. Namespaces are for apps and major feature areas.
- A namespace per language. Languages are columns inside a namespace. Splitting them breaks the side-by-side view and leaves translation memory nothing to match.
- A shared string that drifted in one language. Someone edited it in one namespace and
not the other. Translation memory shows the mismatch as a near match next time; a
quarterly read of
defaultcatches the rest.