Upgrade to Comvi i18n v0.3
v0.3 moves every @comvi/* package to a single modern baseline: ESM-only output, React 18+, Svelte 5, and a cleaner default file layout. All packages release in lockstep, so a v0.3 install moves the whole set to the same version. This guide lists every breaking change with an old→new example, then the additive improvements you get for free.
At a glance
Section titled “At a glance”| Change | Affects | Action |
|---|---|---|
| ESM-only | Everyone using require() | Switch to import / a bundler |
| React 18+ baseline | @comvi/react, @comvi/next on React 16/17 | Upgrade React, or stay on @comvi/react@0.2.x |
| Svelte 5 only | @comvi/svelte on Svelte 4 | Migrate to runes |
| New CLI file layout | comvi pull/push, @comvi/vite-plugin | Accept the new layout, or pin the old fileTemplate |
| Reactive Vue/Nuxt API | @comvi/vue, @comvi/nuxt | Read .value on the new reactive properties |
Nothing in your translation content changes — these are all code/config concerns.
Breaking changes
Section titled “Breaking changes”ESM-only
Section titled “ESM-only”Every @comvi/* package now ships a single ES-module build. The CommonJS entry (require() / main / .cjs) and the dual .d.cts declarations are no longer published. Consume them via import or any modern bundler (Vite, webpack 5, esbuild, Rollup, Rspack, Next).
const { createI18n } = require('@comvi/core');import { createI18n } from '@comvi/core';React 18+ baseline
Section titled “React 18+ baseline”@comvi/react (and therefore @comvi/next) dropped React 16.8–17 support. The peer range is now react@^18.0.0 || ^19.0.0, and the internal use-sync-external-store shim was removed in favor of the native useSyncExternalStore.
- On React 18 or 19: no code change needed for the upgrade itself.
- Still on React 17: stay on
@comvi/react@^0.2.xuntil you can upgrade React.
One behavior change to be aware of: the t / tRaw functions returned by useI18n() now rebuild on locale change (their identity is no longer stable across a locale flip). This closes a tearing bug during startTransition-wrapped locale changes. If you memoized on t’s identity, depend on locale instead:
const greeting = useMemo(() => t('hello'), [t]);const { t, locale } = useI18n();const greeting = useMemo(() => t('hello'), [locale]);useI18nContext() still works in 0.3 but is deprecated — prefer useI18n() (finer-grained subscriptions).
Svelte 5 only
Section titled “Svelte 5 only”@comvi/svelte now requires Svelte 5 (peerDependencies.svelte is ^5.0.0). The stores (useI18n, createLocaleStore, …) are unchanged and svelte/store is fully supported — but examples and the <T> component use runes. Migrate legacy component syntax:
<script lang="ts"> import { setI18nContext } from '@comvi/svelte'; import { i18n } from '$lib/i18n';
let { children } = $props(); setI18nContext(i18n);</script>
<slot />{@render children()}<button on:click={() => setLocale('de')}>Switch</button><button onclick={() => setLocale('de')}>Switch</button>$: cartMessage = $t('cart.items', { count });const cartMessage = $derived($t('cart.items', { count }));If you can’t move to Svelte 5 yet, stay on the previous @comvi/svelte minor. See the Svelte guide for the full runes setup.
New CLI file layout
Section titled “New CLI file layout”The default local layout changed. In v0.2 the default template was {languageTag}/{namespace}.json. In v0.3 the namespace marked default in the TMS maps to root locale files (en.json), and every other namespace maps to {namespace}/{languageTag}.json (admin/en.json). comvi pull/push and type generation now resolve the default namespace from the backend, not from .comvirc.json.
{ "apiBaseUrl": "https://api.comvi.io", "translationsPath": "./src/locales" // v0.2 default layout // omit fileTemplate to use the v0.3 default ({namespace}/{languageTag}.json), // or pin the line below to keep the v0.2 layout "fileTemplate": "{languageTag}/{namespace}.json"}The new on-disk layout:
src/locales/├── en.json # default namespace (root)├── de.json├── admin/│ ├── en.json│ └── de.json└── marketing/ └── en.jsonCustom fileTemplate values are still matched literally — set "fileTemplate": "{languageTag}/{namespace}.json" to keep the v0.2 layout unchanged.
Reactive Vue/Nuxt API
Section titled “Reactive Vue/Nuxt API”@comvi/vue and @comvi/nuxt expose loadedLocales, activeNamespaces, and defaultNamespace as reactive ComputedRef properties (not methods). Read .value in <script setup>; templates auto-unwrap.
<script setup lang="ts">import { useI18n } from '@comvi/vue';
const { getLoadedLocales } = useI18n();const locales = getLoadedLocales();const { loadedLocales } = useI18n();const locales = loadedLocales.value;</script>The method forms (getLoadedLocales() etc.) remain available on @comvi/react and @comvi/core, where the API is method-based.
What you get for free (no action needed)
Section titled “What you get for free (no action needed)”- Wider Node support. Runtime packages now declare
engines.node >=18(down from>=22), so they install on Node 18/20 LTS.@comvi/cliand@comvi/vite-pluginkeep>=22(Vite 7+ requires it). - New React/Next selector hooks.
useLocale(),useIsLoading(),useSetLocaleTransition(), anduseFormatters()— narrow subscriptions that skip unrelated re-renders. In Next they’re now re-exported from@comvi/next/client(no extra@comvi/reactdependency). - Per-call formatter locale.
formatNumber(),formatDate(),formatCurrency(), andformatRelativeTime()accept an optional trailinglocaleargument; existing calls without it keep working. - Nuxt 3 and 4.
@comvi/nuxtsupports both.
Upgrading
Section titled “Upgrading”Bump every @comvi/* package together — they version in lockstep:
npm install @comvi/core@^0.3 @comvi/react@^0.3# add whichever bindings/plugins you use, all at ^0.3Then work through the breaking-change sections above for the packages you use, and re-run your build. See Compatibility for the full per-package version matrix.