Quick start
Install one package, render a translated heading and switch its locale. Pick the framework in the header; the tabs follow it.
Install
Section titled “Install”npm install @comvi/reactOr open the React example in StackBlitz to see the switcher, plurals and lazy catalogs running.
npm install @comvi/nextOr open the Next.js example in StackBlitz to see server rendering, the locale in the URL and plurals running.
npm install @comvi/vueOr open the Vue example in StackBlitz to see the switcher, plurals and lazy catalogs running.
npm install @comvi/nuxtOr open the Nuxt example in StackBlitz to see server rendering, the locale in the URL and plurals running.
npm install @comvi/svelteOr open the Svelte example in StackBlitz to see the switcher, plurals and lazy catalogs running.
npm install @comvi/solidOr open the Solid example in StackBlitz to see the switcher, plurals and lazy catalogs running.
npm install @comvi/coreOr open the Vanilla example in StackBlitz to see the switcher, plurals and lazy catalogs running.
The binding depends on @comvi/core, so one install is the whole of it. pnpm, yarn and
bun take the same package names.
Create the i18n instance
Section titled “Create the i18n instance”import { createRoot } from 'react-dom/client';import { createI18n, I18nProvider } from '@comvi/react';import App from './App';
const i18n = createI18n({ locale: 'en', fallbackLocale: 'en', messages: { en: { greeting: 'Hello, {name}!' }, uk: { greeting: 'Привіт, {name}!' }, },});
createRoot(document.getElementById('root')!).render( <I18nProvider i18n={i18n}> <App /> </I18nProvider>,);import { withComvi } from '@comvi/next/plugin';
export default withComvi({ config: './src/i18n/config.ts' })({});import { defineRouting } from '@comvi/next/routing';
export const routing = defineRouting({ locales: ['en', 'uk'], defaultLocale: 'en' });import { createNextI18n } from '@comvi/next';import { routing } from './routing';
export const { i18n } = createNextI18n({ ...routing, messages: { en: { greeting: 'Hello, {name}!' }, uk: { greeting: 'Привіт, {name}!' } },});
export { routing };import { notFound } from 'next/navigation';import { hasLocale } from '@comvi/next/routing';import { ComviProvider } from '@comvi/next/server';import { routing } from '@/i18n/routing';
type Props = { children: React.ReactNode; params: Promise<{ locale: string }> };
export default async function LocaleLayout({ children, params }: Props) { const { locale } = await params; if (!hasLocale(routing.locales, locale)) notFound();
return ( <html lang={locale}> <body> <ComviProvider locale={locale}>{children}</ComviProvider> </body> </html> );}import { createMiddleware } from '@comvi/next/middleware';import { routing } from './i18n/routing';
export default createMiddleware(routing);
export const config = { matcher: ['/((?!api|_next/static|_next/image|favicon.ico|.*\\..*).*)'],};If your app still has a root src/app/layout.tsx and src/app/page.tsx, delete them: the [locale] layout is the root layout. The middleware sends / to the right locale and redirects when the URL and the locale disagree.
import { createApp } from 'vue';import { createI18n } from '@comvi/vue';import App from './App.vue';
const i18n = createI18n({ locale: 'en', fallbackLocale: 'en', messages: { en: { greeting: 'Hello, {name}!' }, uk: { greeting: 'Привіт, {name}!' }, },});
createApp(App).use(i18n).mount('#app');import { defineNuxtConfig } from 'nuxt/config';
export default defineNuxtConfig({ modules: ['@comvi/nuxt'], comvi: { defaultLocale: 'en', locales: ['en', 'uk'], messages: { en: { greeting: 'Hello, {name}!' }, uk: { greeting: 'Привіт, {name}!' }, }, },});<script lang="ts"> import { createI18n, setI18nContext } from '@comvi/svelte'; import type { Snippet } from 'svelte';
const { children }: { children: Snippet } = $props();
const i18n = createI18n({ locale: 'en', fallbackLocale: 'en', messages: { en: { greeting: 'Hello, {name}!' }, uk: { greeting: 'Привіт, {name}!' }, }, });
setI18nContext(i18n);</script>
{@render children()}import { render } from 'solid-js/web';import { createI18n, I18nProvider } from '@comvi/solid';import App from './App';
const i18n = createI18n({ locale: 'en', fallbackLocale: 'en', messages: { en: { greeting: 'Hello, {name}!' }, uk: { greeting: 'Привіт, {name}!' }, },});
render( () => ( <I18nProvider i18n={i18n}> <App /> </I18nProvider> ), document.getElementById('root')!,);import { createI18n } from '@comvi/core';
export const i18n = createI18n({ locale: 'en', fallbackLocale: 'en', messages: { en: { greeting: 'Hello, {name}!' }, uk: { greeting: 'Привіт, {name}!' }, },});
await i18n.init();
i18n.t('greeting', { name: 'Alice' }); // "Hello, Alice!"locale is the language that renders now, fallbackLocale is where t() looks for a key the
current locale lacks, and messages is the catalog, one object per locale.
Translate and switch the locale
Section titled “Translate and switch the locale”import { useI18n } from '@comvi/react';
export default function App() { const { t, locale, setLocale } = useI18n();
return ( <> <h1>{t('greeting', { name: 'Alice' })}</h1> <select value={locale} onChange={(e) => setLocale(e.target.value)}> <option value="en">English</option> <option value="uk">Українська</option> </select> </> );}import Link from 'next/link';import { getI18n } from '@comvi/next/server';
export default async function HomePage({ params }: { params: Promise<{ locale: string }> }) { const { locale } = await params; const { t } = await getI18n({ locale });
return ( <div> <h1>{t('greeting', { name: 'Alice' })}</h1> <Link href={locale === 'en' ? '/uk' : '/en'}>Switch</Link> </div> );}<script setup lang="ts">import { useI18n } from '@comvi/vue';
const { t, locale, setLocale } = useI18n();</script>
<template> <h1>{{ t('greeting', { name: 'Alice' }) }} ({{ locale }})</h1> <button @click="setLocale('uk')">Українська</button> <button @click="locale = 'en'">English</button></template><template> <NuxtPage /></template><script setup lang="ts">const { t, locale } = useI18n();</script>
<template> <h1>{{ t('greeting', { name: 'Alice' }) }}</h1> <NuxtLinkLocale to="/" :locale="locale === 'en' ? 'uk' : 'en'">Switch</NuxtLinkLocale></template>A fresh Nuxt app ships an app/app.vue that renders <NuxtWelcome />; replace it with this one so your pages render.
<script lang="ts"> import { useI18n } from '@comvi/svelte';
const { t, locale, setLocale } = useI18n();</script>
<h1>{$t('greeting', { name: 'Alice' })}</h1><select value={$locale} onchange={(e) => setLocale(e.currentTarget.value)}> <option value="en">English</option> <option value="uk">Українська</option></select>import { useI18n } from '@comvi/solid';
export default function App() { const { t, locale, setLocale } = useI18n();
return ( <> <h1>{t('greeting', { name: 'Alice' })}</h1> <select value={locale()} onChange={(e) => setLocale(e.currentTarget.value)}> <option value="en">English</option> <option value="uk">Українська</option> </select> </> );}import { i18n } from './i18n';
await i18n.setLocale('uk');console.log(i18n.t('greeting', { name: 'Аліса' })); // "Привіт, Аліса!"import './switch';Put that one line at the top of your src/main.ts (or replace a scaffold’s main.ts with it);
nothing runs until main.ts imports it. Top-level await needs a build target that has it:
on Vite 5 and 6 set build.target: 'es2022' in vite.config.ts, or wrap the calls in an
async function.
t('greeting', { name: 'Alice' }) looks up greeting in the current locale and fills {name}
from the second argument. setLocale() switches the locale, and every component that read a
translation re-renders with the new text.
Switch the locale and the heading changes; on Vanilla the translated line is in the browser console.
Read next
Section titled “Read next”- Connect to Comvi: move the catalog to the platform CDN so a wording change ships without a redeploy.
- Your framework page, React, Next.js, Vue, Nuxt, Svelte, Solid or Vanilla JavaScript: markup in translations, first paint and server rendering.
- Important defaults: what a missing key renders, what
fallbackLocaleloads, and the other behaviours worth knowing before the first bug report.