Skip to content

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.

ChangeAffectsAction
ESM-onlyEveryone using require()Switch to import / a bundler
React 18+ baseline@comvi/react, @comvi/next on React 16/17Upgrade React, or stay on @comvi/react@0.2.x
Svelte 5 only@comvi/svelte on Svelte 4Migrate to runes
New CLI file layoutcomvi pull/push, @comvi/vite-pluginAccept the new layout, or pin the old fileTemplate
Reactive Vue/Nuxt API@comvi/vue, @comvi/nuxtRead .value on the new reactive properties

Nothing in your translation content changes — these are all code/config concerns.

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';

@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.x until 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).

@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.

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.

.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.json

Custom fileTemplate values are still matched literally — set "fileTemplate": "{languageTag}/{namespace}.json" to keep the v0.2 layout unchanged.

@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.

  • Wider Node support. Runtime packages now declare engines.node >=18 (down from >=22), so they install on Node 18/20 LTS. @comvi/cli and @comvi/vite-plugin keep >=22 (Vite 7+ requires it).
  • New React/Next selector hooks. useLocale(), useIsLoading(), useSetLocaleTransition(), and useFormatters() — narrow subscriptions that skip unrelated re-renders. In Next they’re now re-exported from @comvi/next/client (no extra @comvi/react dependency).
  • Per-call formatter locale. formatNumber(), formatDate(), formatCurrency(), and formatRelativeTime() accept an optional trailing locale argument; existing calls without it keep working.
  • Nuxt 3 and 4. @comvi/nuxt supports both.

Bump every @comvi/* package together — they version in lockstep:

Terminal window
npm install @comvi/core@^0.3 @comvi/react@^0.3
# add whichever bindings/plugins you use, all at ^0.3

Then 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.