Troubleshooting
Each entry below is symptom → cause → fix. If your problem isn’t here, check the Error Handling guide for the runtime hooks (onError, onMissingKey, onLoadError) and events.
useI18n() (or <T>) throws “must be used within a provider”
Section titled “useI18n() (or <T>) throws “must be used within a provider””Symptom — a runtime error like one of these:
- React / Next.js:
[i18n] useI18nContext must be used within an I18nProvider. Make sure your component is wrapped with <I18nProvider>. - Vue:
[i18n] useI18n must be used within a Vue app with i18n plugin installed. Make sure you called app.use(i18n) before using this composable. - SolidJS:
[@comvi/solid] i18n context not found. Wrap your app with <I18nProvider i18n={i18n}>. - Svelte:
[@comvi/svelte] i18n context not found. Call setI18nContext(i18n) in your root component (e.g., App.svelte).
Cause — the component calling useI18n() / rendering <T> is rendered outside the provider, or the provider was never mounted. Common variants: the provider is below the component in the tree; you app.use(i18n) after a component already rendered; in Next.js you imported useI18n from @comvi/next (server) instead of @comvi/next/client; in tests you rendered the component without wrapping it.
Fix — mount the provider at the root, above everything that uses i18n:
import { I18nProvider } from '@comvi/react';import { i18n } from './i18n';
createRoot(document.getElementById('root')!).render( <I18nProvider i18n={i18n}> <App /> </I18nProvider>,);import { createApp } from 'vue';import { i18n } from './i18n';createApp(App).use(i18n).mount('#app'); // .use(i18n) BEFORE .mount()In Next.js Client Components, import the hook from @comvi/next/client; in Server Components use await getI18n() from @comvi/next/server instead. In Svelte, call setI18nContext(i18n) once in App.svelte (or your root layout) so descendants can getI18nContext() / useI18n().
SSR / hydration mismatch — text flickers or React warns about mismatched HTML
Section titled “SSR / hydration mismatch — text flickers or React warns about mismatched HTML”Symptom — the server renders one locale, the client renders another; you see a hydration warning, or a flash of the wrong language before it corrects itself.
Cause — the client’s i18n instance starts on a different locale than the server used, or the server-rendered translations weren’t handed to the client so the client re-fetches and re-renders.
Fix — pass the locale and the pre-loaded messages from the server into the provider so the client hydrates with exactly what the server rendered:
import { loadTranslations } from '@/i18n';import { ComviProvider } from '@/i18n/ComviProvider';
export default async function LocaleLayout({ children, params }) { const { locale } = await params; const messages = await loadTranslations(locale); return ( <html lang={locale}> <body> <ComviProvider locale={locale} messages={messages}>{children}</ComviProvider> </body> </html> );}For Nuxt, register your loader in comvi.setup.ts — the module pre-loads the active locale on the server and hydrates the client automatically; you don’t pass messages manually. See the SSR guide for the full pattern in each framework.
A translation renders as the key string instead of the text
Section titled “A translation renders as the key string instead of the text”Symptom — the UI shows home.title (or legal.tos) literally instead of the translated sentence.
Cause — the key is missing in the active locale (and in any fallbackLocale chain), the namespace it lives in wasn’t loaded, or there’s a typo in the key. By default a missing key returns the key string.
Fix —
- Confirm the key exists in your translation source for the active locale, and that you loaded the right namespace (
createI18n({ ns: ['default', 'admin'] }), or load on demand). - Set
fallbackLocaleso a missing key falls back to a complete locale:createI18n({ locale: 'de', fallbackLocale: 'en', translation }); - During development, turn on diagnostics to get a console warning the moment a key is missing:
createI18n({ locale: 'en', strict: 'dev', translation });
- To control what a missing key renders (e.g. return empty string, or report to your error tracker), use the
onMissingKeyoption — its callback receives{ key, locale, namespace }and may return a string/parts to use instead:createI18n({locale: 'en',translation,onMissingKey: ({ key, locale, namespace }) => {reportToSentry(`Missing i18n key: ${namespace}:${key} (${locale})`);return ''; // render nothing instead of the key},});
See Error Handling for the difference between the onMissingKey option and the i18n.onMissingKey(cb) instance method (the method takes positional (key, locale, namespace) and returns a cleanup function).
CDN returns 403, 404, or CORS error when fetching translations
Section titled “CDN returns 403, 404, or CORS error when fetching translations”Symptom — translations don’t load; the network tab shows 403 Forbidden, 404 Not Found, or a CORS failure on a request to cdn.comvi.io/... (or your API host).
Cause — almost always a misconfigured Fetch Loader: wrong cdnUrl (it must include your project’s CDN id, e.g. https://cdn.comvi.io/<projectCdnId>), or in dev mode an apiBaseUrl/API key that doesn’t match the project, or the project hasn’t been published to the CDN yet so the file genuinely doesn’t exist.
Fix — point cdnUrl at the exact CDN base URL shown in your project settings, and make sure the project has been published at least once:
import { createI18n } from '@comvi/core';import { FetchLoader } from '@comvi/plugin-fetch-loader';
export const i18n = createI18n({ locale: 'en' }).use( FetchLoader({ cdnUrl: 'https://cdn.comvi.io/<your-project-cdn-id>', // dev mode (Next.js / Nuxt) also needs the API base URL: apiBaseUrl: process.env.NEXT_PUBLIC_COMVI_API_URL ?? 'https://api.comvi.io', }),);The loader fetches {cdnUrl}/{lang}.json for the default namespace and {cdnUrl}/{namespace}/{lang}.json for others — a 404 on those URLs means the file (locale or namespace) hasn’t been deployed. See the Fetch Loader reference for fallback imports (offline/PWA) and SSR cache options.
setLocale() ran but the UI still shows the old language for a moment
Section titled “setLocale() ran but the UI still shows the old language for a moment”Symptom — after a language switch, the page briefly shows old text, then updates; or in non-reactive (vanilla) code the next render reads the old locale.
Cause — switching locale loads the new locale’s namespaces asynchronously. The synchronous i18n.locale = 'de' setter (and framework setLocale() helpers) fires that load and returns immediately. If you read translations on the very next tick, the new namespace may not be loaded yet.
Fix — await the async form before doing anything that depends on the new locale:
await i18n.setLocaleAsync('de'); // resolves once new namespaces are loaded// now it's safe to render / navigate / read translations(There is no i18n.setLocale() method on the core instance — it’s i18n.locale = x for fire-and-forget, or await i18n.setLocaleAsync(x) to wait. The setLocale you get back from useI18n() in a framework binding is a separate hook helper.) In framework UIs you usually don’t need to await — the binding re-renders when loading finishes. See Language Switching.
Next.js: getI18n() throws “i18n not configured” or “Locale not set”
Section titled “Next.js: getI18n() throws “i18n not configured” or “Locale not set””Symptom — a Server Component throws [comvi/next] i18n not configured. Call setI18n(i18n) in your i18n configuration file. — or [comvi/next] Locale not set. Call setRequestLocale(locale) in your layout/page first, or configure middleware.
Cause —
- “i18n not configured”: you called
getI18n()(server) but never registered the instance withsetI18n(i18n). - “Locale not set”:
getI18n()couldn’t determine the request locale because middleware isn’t matching this path and you didn’t callsetRequestLocale(locale)in the layout/page.
Fix — register the instance once in a server-only module, and make sure the locale is established before getI18n() runs:
import 'server-only';import { setI18n } from '@comvi/next/server';import { i18n } from './config';
setI18n(i18n);export { loadTranslations } from '@comvi/next/server';import { setRequestLocale } from '@comvi/next/server';
export default async function LocaleLayout({ children, params }) { const { locale } = await params; setRequestLocale(locale); // makes getI18n() work in nested Server Components // ...}Also confirm middleware.ts exports createMiddleware(routing) with a matcher that covers your routes. For generateMetadata, pass the locale explicitly: await getI18n({ locale }). See the Next.js guide.
Nuxt: useI18n() is undefined / auto-imports don’t resolve
Section titled “Nuxt: useI18n() is undefined / auto-imports don’t resolve”Symptom — useI18n is not defined in a page or component, or your IDE can’t find it; NuxtLinkLocale doesn’t render.
Cause — the Comvi Nuxt module isn’t registered, so it never installs the auto-imports — or the dev server wasn’t restarted after editing nuxt.config.ts.
Fix — add @comvi/nuxt to modules in nuxt.config.ts and restart the dev server:
export default defineNuxtConfig({ modules: ['@comvi/nuxt'], comvi: { locales: [{ code: 'en' }, { code: 'uk' }], defaultLocale: 'en', },});Then register your loader/translations in comvi.setup.ts:
import { defineComviSetup } from '@comvi/nuxt/setup';
export default defineComviSetup(({ i18n }) => { i18n.registerLoader({ en: () => import('./locales/en.json'), uk: () => import('./locales/uk.json'), });});After the module is registered and the dev server restarted, useI18n(), <T>, and NuxtLinkLocale are auto-imported in pages and components. See the Nuxt guide.