Skip to content

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:

React — src/main.tsx
import { I18nProvider } from '@comvi/react';
import { i18n } from './i18n';
createRoot(document.getElementById('root')!).render(
<I18nProvider i18n={i18n}>
<App />
</I18nProvider>,
);
Vue — src/main.ts
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:

Next.js — app/[locale]/layout.tsx
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

  1. 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).
  2. Set fallbackLocale so a missing key falls back to a complete locale:
    createI18n({ locale: 'de', fallbackLocale: 'en', translation });
  3. During development, turn on diagnostics to get a console warning the moment a key is missing:
    createI18n({ locale: 'en', strict: 'dev', translation });
  4. To control what a missing key renders (e.g. return empty string, or report to your error tracker), use the onMissingKey option — 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:

src/i18n.ts
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.

Fixawait 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 with setI18n(i18n).
  • “Locale not set”: getI18n() couldn’t determine the request locale because middleware isn’t matching this path and you didn’t call setRequestLocale(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:

src/i18n/index.ts
import 'server-only';
import { setI18n } from '@comvi/next/server';
import { i18n } from './config';
setI18n(i18n);
export { loadTranslations } from '@comvi/next/server';
app/[locale]/layout.tsx
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”

SymptomuseI18n 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:

nuxt.config.ts
export default defineNuxtConfig({
modules: ['@comvi/nuxt'],
comvi: {
locales: [{ code: 'en' }, { code: 'uk' }],
defaultLocale: 'en',
},
});

Then register your loader/translations in comvi.setup.ts:

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.