# Limitations

Everything the Comvi i18n library does not do, in one list: the ICU grammar gaps, SSR constraints, rich-text boundaries, platform requirements, tooling and environment floors.

Canonical: https://comvi.io/docs/i18n/limitations

Every gap in one list, each with what happens instead. The i18n instance is the object `createI18n()` returns: text with `{param}` interpolation, the cache, events and default params, plus whatever you composed with `.use()`.

## ICU grammar

- **`number`, `date` and `time` argument types are not implemented, and are not planned.** `{price, number}` fails with `E_ICU_SYNTAX` rather than rendering something wrong. Format the value in code and interpolate it as a plain `{param}`, or quote the braces (`'{'…'}'`) to keep the text literal. See [Format numbers, dates and currency](/docs/i18n/formatting).
- **The plural `offset:` form is not implemented.** Drop `offset:` and write the branches out.
- **`plural`, `selectordinal` and `select` are the whole of what the compiler implements**, each with a choice list. See [ICU reference](/docs/i18n/icu-reference#what-icu-does-not-implement).
- **A missing interpolation parameter renders as its own placeholder.** `t('greet')` on `"Hello, {name}!"` renders `"Hello, {name}!"`, plus one development warning per template and parameter. An explicit `null` still renders as an empty string. Opt out with `missingParam: 'drop'`.
- **ICU is off unless you name it.** No constructor in the library turns ICU on by itself, `createNextI18n` included.

## Server-side rendering

- **SolidJS: server-side *loading* is not supported.** `<I18nProvider>` initializes from a `createEffect`, and effects do not run during Solid's server render, so a catalog a loader would have fetched is absent server-side. An inline catalog renders fine.
- **The module-scope rule is not the same in every framework.** Next.js requires one shared i18n instance; Solid, Svelte, Vue and Nuxt each forbid it. The four rules sit side by side on [Render on the server](/docs/i18n/ssr).
- **Ambient string-API tags must be imported in both bundles.** One `import "@comvi/core/tags"` registers the grammar per bundle, and an SSR app has two. See [Tag interpolation](/docs/i18n/tag-interpolation).
- **Next.js: a client-side locale switch without a `routing` prop writes the default cookie.** `<I18nProvider>` from `@comvi/next/client` records a switch in `NEXT_LOCALE` with the default attributes unless it is given `routing`, even when `defineRouting()` renamed the cookie or set `localeCookie: false`. Pass the routing config to the provider.

## Memory

- **A catalog is never evicted.** Everything `addMessages` and a loader merge into an i18n instance stays in its translation cache until `clearMessages()` or `destroy()`. The three caches that are bounded, compiled templates, `Intl` formatters and plural rules, hold 1000 entries each, FIFO, and are shared by every instance in the process; that is an entry limit, not a byte limit, so 1000 long messages cost more than 1000 short ones. A client app with a fixed set of locales has a bounded model and nothing here to tune.
- **A long-lived server that loads many locales or namespaces into one instance needs a lifecycle instead of a limit**, and which code provides that lifecycle differs by framework: `@comvi/nuxt` destroys its per-request instances for you; a per-request instance you build yourself (Vue SSR, Solid, Svelte) is garbage-collected, and collection runs no plugin cleanup, so an instance with a plugin that holds a timer or a socket needs `await i18n.destroy()`; the shared `@comvi/next` server instance holds every catalog it ever loaded for the life of the process. See [Render on the server](/docs/i18n/ssr).

## Rich text

- **`<T>` maps only the tags you give it.** A tag with no mapping falls back to its inner text. There is no HTML passthrough, because there is no HTML sink anywhere in the render path.
- **`t()` does not parse tags unless `@comvi/core/tags` is imported**, and that import is process-wide and retroactive, with no per-instance opt-out.

## Platform features

- **The in-context editor needs a Comvi account.** `@comvi/plugin-in-context-editor` requires the [Comvi platform](https://comvi.io) and does not work standalone. Its production entry is a no-op installer, so a production build that composes it carries no editor bytes.
- **The browser extension sees only an instance that composed devtools.** Discovery is opt-in, through `.use(devtools())`, which publishes the instance on `window.__COMVI__`; `inContextEditor()` composes it for you.
- **`cdnLoader` reads a public distribution only.** Draft translations, private projects and auth-gated content need API mode: `fetchLoader` from `@comvi/plugin-fetch-loader/api` plus an `apiKey` on the i18n instance. The `/api` entry carries the API request path in the bundle either way, which is why the root entry is CDN-only. See [Fetch loader](/docs/i18n/plugins/fetch-loader).
- **A fallback catalog that does not exist is requested once per namespace activation.** With a loader, `init()` and `addActiveNamespace()` fetch each locale in `fallbackLocale` for the namespace; a missing fallback catalog fails, is reported through `onLoadError`, and is requested again only when a namespace is activated again, never by a locale switch.

## Tooling

- **Typed keys are generated, not inferred.** `TranslationKeys` is a declaration-merging interface, and `@comvi/cli` (from the platform) or `@comvi/vite-plugin` (from local JSON) writes it. Nothing infers key types from a runtime catalog. See [Type-check translation keys](/docs/i18n/type-safety).

## Typed keys: three shapes `t()` does not check

`t()` has one call signature, which is what makes a wrong call report the real problem on its first line ([Read the type errors](/docs/i18n/type-safety#read-the-type-errors)). Three shapes fall outside it, each a consequence of that one signature.

- **A key the call site could not narrow to one literal gets loose params.** `t(cond ? 'greeting' : 'farewell')` compiles even when `greeting` declares required params, and so does a params object valid for neither arm. The key itself is still checked: an arm that is not a key is rejected. When the params differ between arms, branch the **call**; hoisting the key into a `const` does not help. Closing this would need the arms' params intersected, which costs the check on `t('key', { ns })`: a silently accepted wrong namespace, which is worse.
- **`Parameters<typeof t>` is `never` once keys are generated.** The key parameter's type depends on a second, inferred type parameter, and `Parameters` cannot see through that. Reach the argument list through `ArgsFor<'your.key'>` instead, which resolves normally. With no generated keys, `Parameters` still works.
- **`t` is not assignable to a `(key: string) => string` callback once keys are generated.** The key is a closed union of your keys, and `string` is not one of them. Hand such a helper `t as (k: string) => string`. The two-argument shape `(key: string, params?: Record<string, unknown>) => string` never accepted `t` and still does not.

## Environment

- **Node 20 or newer** for the runtime packages; **Node 22 or newer** for `@comvi/cli` and `@comvi/vite-plugin`. Exact ranges are each package's `engines` field.
- **Peer ranges** are each package's `peerDependencies` field. Svelte is `^5.3.0`; Svelte 4 is not supported, because the bindings use runes. The full table is on [Compatibility](/docs/i18n/compatibility).
- **No `eval` and no `new Function`** in any runtime package, so every package runs under a strict CSP without `unsafe-eval`.

## Not on the roadmap

An ICU format grammar, a runtime translation-memory or fuzzy-match layer, and automatic key extraction from source. Key extraction is the platform's job, not the library's.

## Read next

- [Troubleshooting](/docs/i18n/troubleshooting): the errors you are most likely to hit, and the fix for each.
- [Error codes](/docs/i18n/errors): every error code the library raises, and what it means.
- [Compatibility](/docs/i18n/compatibility): peer ranges and Node floors for every package.
