# Monorepo with shared namespaces

Organize one Comvi project for several apps in a monorepo: shared strings in the default namespace, one namespace per app, and a CLI config per app.

Canonical: https://comvi.io/docs/platform/recipes/monorepo-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](/docs/platform/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

1. **Use `default` for the shared strings.** It is served at the root of the CDN endpoint
   and stored as `{languageTag}.json` by the CLI. The dashboard cannot rename or delete it,
   and the REST API refuses with `CANNOT_DELETE_DEFAULT`, so treat it as the shared layer
   rather than trying to turn it into `common`.

2. **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/web` becomes `app`. A namespace that holds keys
   cannot be deleted, so pick names you can live with.

3. **Give each app its own `.comvirc.json`** listing the namespaces it loads:

   `apps/admin/.comvirc.json`

   ```json
   {
     "apiBaseUrl": "https://api.comvi.io",
     "translationsPath": "./src/locales",
     "fileTemplate": "{namespace}/{languageTag}.json",
     "format": "json",
     "namespaces": ["default", "admin"]
   }
   ```

   `comvi pull` writes only those namespaces and `comvi push` reads only them. `--ns` on the
   command line replaces the list rather than adding to it.

4. **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 })`](/docs/i18n/namespaces))
   when the key is not in the app's default one.

```text
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 namespace
```

> **No shared fallback layer**
>
> Putting a key in `default` does not make it visible to a lookup aimed at `admin`. Decide
> per call which namespace a key lives in.

## 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 `default` only 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

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](/docs/platform/cdn).

## 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 `default` catches the rest.

## Next steps

- [Namespaces](/docs/platform/namespaces): The full feature reference.
- [CLI](/docs/cli): Per-app config with namespace filters.
- [Translation memory](/docs/platform/translation-memory): Reuse across namespaces.
- [CI/CD integration](/docs/platform/recipes/ci-cd-integration): Automate per-app pulls.
