Quick Start
This guide gets you from zero to translated UI in a few minutes.
-
Install Comvi i18n
Terminal window npm install @comvi/reactTerminal window npm install @comvi/next server-onlyTerminal window npm install @comvi/vueTerminal window npm install @comvi/nuxtTerminal window npm install @comvi/solidTerminal window npm install @comvi/svelteTerminal window npm install @comvi/core -
Create and configure your i18n instance
src/i18n.ts import { createI18n } from '@comvi/react';export const i18n = createI18n({locale: 'en',translation: {en: {'hello': 'Hello, {name}!','legal.tos': 'Read our <link>Terms of Service</link>',},de: {'hello': 'Hallo, {name}!','legal.tos': 'Lies unsere <link>Nutzungsbedingungen</link>',},},});src/i18n/config.ts import { createNextI18n } from '@comvi/next';export const nextI18n = createNextI18n({locales: ['en', 'de'],defaultLocale: 'en',translation: {en: {'hello': 'Hello, {name}!','legal.tos': 'Read our <link>Terms of Service</link>',},de: {'hello': 'Hallo, {name}!','legal.tos': 'Lies unsere <link>Nutzungsbedingungen</link>',},},});export const { i18n, routing } = nextI18n;src/i18n/index.ts import 'server-only';import { setI18n } from '@comvi/next/server';import { i18n, routing } from './config';setI18n(i18n);export { i18n, routing };export { loadTranslations } from '@comvi/next/server';src/i18n.ts import { createI18n } from '@comvi/vue';export const i18n = createI18n({locale: 'en',translation: {en: {'hello': 'Hello, {name}!','legal.tos': 'Read our <link>Terms of Service</link>',},de: {'hello': 'Hallo, {name}!','legal.tos': 'Lies unsere <link>Nutzungsbedingungen</link>',},},});nuxt.config.ts export default defineNuxtConfig({modules: ['@comvi/nuxt'],comvi: {locales: [{ code: 'en' }, { code: 'de' }],defaultLocale: 'en',},});Then create a setup hook to register your translations:
comvi.setup.ts import type { NuxtI18nSetup } from '@comvi/nuxt';export default (({ i18n }) => {i18n.addTranslations({en: {'hello': 'Hello, {name}!','legal.tos': 'Read our <link>Terms of Service</link>',},de: {'hello': 'Hallo, {name}!','legal.tos': 'Lies unsere <link>Nutzungsbedingungen</link>',},});}) satisfies NuxtI18nSetup;src/i18n.ts import { createI18n } from '@comvi/solid';export const i18n = createI18n({locale: 'en',translation: {en: {'hello': 'Hello, {name}!','legal.tos': 'Read our <link>Terms of Service</link>',},de: {'hello': 'Hallo, {name}!','legal.tos': 'Lies unsere <link>Nutzungsbedingungen</link>',},},});src/lib/i18n.ts import { createI18n } from '@comvi/svelte';export const i18n = createI18n({locale: 'en',translation: {en: {'hello': 'Hello, {name}!','legal.tos': 'Read our <link>Terms of Service</link>',},de: {'hello': 'Hallo, {name}!','legal.tos': 'Lies unsere <link>Nutzungsbedingungen</link>',},},});src/i18n.ts import { createI18n } from '@comvi/core';export const i18n = createI18n({locale: 'en',translation: {en: {'hello': 'Hello, {name}!','legal.tos': 'Read our <link>Terms of Service</link>',},de: {'hello': 'Hallo, {name}!','legal.tos': 'Lies unsere <link>Nutzungsbedingungen</link>',},},});await i18n.init(); -
Wire i18n into your app
src/main.tsx import { StrictMode } from 'react';import { createRoot } from 'react-dom/client';import { I18nProvider } from '@comvi/react';import { i18n } from './i18n';import App from './App';createRoot(document.getElementById('root')!).render(<StrictMode><I18nProvider i18n={i18n}><App /></I18nProvider></StrictMode>,);useI18n()throws if used outside<I18nProvider>.Add middleware, a client provider wrapper, and the locale layout:
middleware.ts import { createMiddleware } from '@comvi/next/middleware';import { routing } from './src/i18n/config';export default createMiddleware(routing);export const config = { matcher: ['/((?!api|_next|.*\\..*).*)'] };app/[locale]/I18nClientProvider.tsx 'use client';import { I18nProvider, type MessagesMap } from '@comvi/next/client';import { i18n, routing } from '@/i18n/config';export function I18nClientProvider({children,locale,messages,}: {children: React.ReactNode;locale: string;messages?: MessagesMap;}) {return (<I18nProvider i18n={i18n} locale={locale} messages={messages} routing={routing}>{children}</I18nProvider>);}app/[locale]/layout.tsx import { loadTranslations } from '@/i18n';import { I18nClientProvider } from './I18nClientProvider';export default async function LocaleLayout({children,params,}: {children: React.ReactNode;params: Promise<{ locale: string }>;}) {const { locale } = await params;const messages = await loadTranslations(locale);return (<html lang={locale}><body><I18nClientProvider locale={locale} messages={messages}>{children}</I18nClientProvider></body></html>);}The wrapper exists because Next.js can’t serialize the
i18nclass instance from server to client — importingi18ninside a"use client"file keeps it on the client side. Thesrc/i18n/index.tsfile is server-only and registers the same instance forloadTranslations(). See Next.js guide for the full setup, includingsetRequestLocaleand server-sidegetI18n().src/main.ts import { createApp } from 'vue';import App from './App.vue';import { i18n } from './i18n';createApp(App).use(i18n).mount('#app');The Nuxt module wires everything automatically — no extra step.
useI18n()is auto-imported in pages and components.src/index.tsx import { render } from 'solid-js/web';import { I18nProvider } from '@comvi/solid';import { i18n } from './i18n';import App from './App';render(() => (<I18nProvider i18n={i18n}><App /></I18nProvider>),document.getElementById('root')!,);src/App.svelte <script>import { setI18nContext } from '@comvi/svelte';import { i18n } from './lib/i18n';import Greeting from './Greeting.svelte';setI18nContext(i18n);</script><Greeting />No wrapper needed — vanilla JS uses the
i18ninstance directly. Skip to the next step. -
Use translations in your components
src/App.tsx import { useI18n, T } from '@comvi/react';function App() {const { t } = useI18n();return (<div><h1>{t('hello', { name: 'World' })}</h1><Ti18nKey="legal.tos"components={{ link: <a href="/terms" /> }}/></div>);}export default App;app/[locale]/page.tsx 'use client';import { useI18n, T } from '@comvi/next/client';export default function Page() {const { t } = useI18n();return (<div><h1>{t('hello', { name: 'World' })}</h1><Ti18nKey="legal.tos"components={{ link: <a href="/terms" /> }}/></div>);}src/App.vue <script setup lang="ts">import { useI18n, T } from '@comvi/vue';const { t } = useI18n();</script><template><div><h1>{{ t('hello', { name: 'World' }) }}</h1><T i18n-key="legal.tos"><template #link="{ children }"><a href="/terms">{{ children }}</a></template></T></div></template>pages/index.vue <script setup lang="ts">const { t } = useI18n();</script><template><div><h1>{{ t('hello', { name: 'World' }) }}</h1><T i18n-key="legal.tos"><template #link="{ children }"><a href="/terms">{{ children }}</a></template></T></div></template>src/App.tsx import { useI18n, T } from '@comvi/solid';function App() {const { t } = useI18n();return (<div><h1>{t('hello', { name: 'World' })}</h1><Ti18nKey="legal.tos"components={{link: (props) => <a href="/terms">{props.children}</a>,}}/></div>);}src/Greeting.svelte <script>import { useI18n, T } from '@comvi/svelte';const { t } = useI18n();</script><div><h1>{$t('hello', { name: 'World' })}</h1><Ti18nKey="legal.tos"components={{link: { tag: 'a', props: { href: '/terms' } },}}/></div>src/main.ts import { i18n } from './i18n';document.querySelector('h1')!.textContent = String(i18n.t('hello', { name: 'World' }),);
That’s it — your app renders translated strings reactively. The t() function returns translations, and framework bindings use the <T> component for tags embedded in strings.