SolidJS i18n Integration
Comvi’s SolidJS integration uses signals and context providers for fine-grained reactivity. Translations update only the DOM nodes that depend on them, with no unnecessary re-renders.
Installation
Section titled “Installation”pnpm add @comvi/solid @comvi/core @comvi/plugin-fetch-loader-
Create the i18n instance
src/i18n.ts import { createI18n } from '@comvi/core';import { FetchLoader } from '@comvi/plugin-fetch-loader';export const i18n = createI18n({locale: 'en',fallbackLocale: 'en',}).use(FetchLoader({cdnUrl: 'https://cdn.comvi.io/your-distribution-id',})); -
Wrap your app with the provider
src/index.tsx import { render } from 'solid-js/web';import { I18nProvider } from '@comvi/solid';import { i18n } from './i18n';import App from './App';// SPA only: awaiting init() before render() avoids a flash of untranslated content.// <I18nProvider> auto-initializes anyway; you can drop this line if a brief flash is fine.await i18n.init();render(() => (<I18nProvider i18n={i18n}><App /></I18nProvider>),document.getElementById('root')!,); -
Use in components
src/components/Greeting.tsx import { useI18n } from '@comvi/solid';function Greeting() {const { t } = useI18n();return (<div><h1>{t('hello.world')}</h1><p>{t('welcome.message', { name: 'Alice' })}</p></div>);}
useI18n() Hook
Section titled “useI18n() Hook”The primary hook for accessing translations and locale state. All returned values are reactive signals.
import { useI18n } from '@comvi/solid';
function MyComponent() { const { t, locale, isLoading, setLocale } = useI18n();
return ( <Show when={!isLoading()} fallback={<p>Loading...</p>}> <h1>{t('page.title')}</h1> <p>Current locale: {locale()}</p> </Show> );}Returned values:
| Property | Type | Description |
|---|---|---|
t | (key, params?) => string | Translate a key with optional parameters |
locale | Accessor<string> | Current language (reactive signal) |
isLoading | Accessor<boolean> | Whether translations are being loaded |
setLocale | (lang: string) => Promise<void> | Switch to a different locale |
Changing Language
Section titled “Changing Language”Use the setLocale function returned from useI18n(). Because Solid uses signals, all translated strings update automatically:
import { useI18n } from '@comvi/solid';
function LanguageSwitcher() { const { locale, setLocale } = useI18n();
return ( <select value={locale()} onChange={(e) => setLocale(e.target.value)} > <option value="en">English</option> <option value="de">Deutsch</option> <option value="fr">Francais</option> </select> );}The <T> Component
Section titled “The <T> Component”For translations containing HTML or nested components, use <T> for safe interpolation:
import { T } from '@comvi/solid';
function LegalNotice() { return ( // Translation: "Read our <link>terms of service</link>" <T i18nKey="legal.tos" components={{ link: (props) => <a href="/terms">{props.children}</a>, }} /> );}Namespaces
Section titled “Namespaces”Load translations from a specific namespace:
function Dashboard() { const { t } = useI18n('dashboard');
return <h1>{t('page.title')}</h1>;}Missing Keys & Error Handling
Section titled “Missing Keys & Error Handling”useI18n() exposes onMissingKey and onLoadError (bound to the core instance). Register
them once near the top of your app and clean up with onCleanup. The onMissingKey callback
may return a replacement (TranslationResult | void):
import { onCleanup } from 'solid-js';import { useI18n } from '@comvi/solid';
function I18nDiagnostics() { const { onMissingKey, onLoadError } = useI18n();
const offMissing = onMissingKey((key, locale, namespace) => { console.warn(`Missing: ${namespace}:${key} (${locale})`); return `[${key}]`; // optional fallback }); const offError = onLoadError((locale, namespace, error) => { console.error(`Failed to load ${namespace} for ${locale}:`, error); });
onCleanup(() => { offMissing(); offError(); });
return null;}You can also handle a missing key inline with <T>’s fallback prop or slotted fallback
content:
<T i18nKey="optional.banner" fallback="Welcome!" />
<T i18nKey="optional.note"> <p>Default note shown when the key is missing.</p></T>Reactive Patterns
Section titled “Reactive Patterns”SolidJS signals make Comvi translations fine-grained reactive. Here are common patterns.
Derived Translations
Section titled “Derived Translations”Use createMemo when you need a derived translation value:
import { createMemo } from 'solid-js';import { useI18n } from '@comvi/solid';
function Greeting(props: { name: string }) { const { t } = useI18n();
const greeting = createMemo(() => t('greeting', { name: props.name }) );
return <p>{greeting()}</p>;}Side Effects on Language Change
Section titled “Side Effects on Language Change”Run logic whenever the locale changes with createEffect:
import { createEffect } from 'solid-js';import { useI18n } from '@comvi/solid';
function DocumentLang() { const { locale } = useI18n();
createEffect(() => { document.documentElement.lang = locale(); });
return null;}Conditional Loading
Section titled “Conditional Loading”Show loading states while translations are fetched:
import { Switch, Match } from 'solid-js';import { useI18n } from '@comvi/solid';
function Page() { const { t, isLoading } = useI18n();
return ( <Switch> <Match when={isLoading()}> <div class="skeleton">Loading...</div> </Match> <Match when={!isLoading()}> <h1>{t('page.title')}</h1> </Match> </Switch> );}Lazy Loading
Section titled “Lazy Loading”Combine the fetch loader with lazy loading to fetch translations on demand:
import { createI18n } from '@comvi/core';import { FetchLoader } from '@comvi/plugin-fetch-loader';
export const i18n = createI18n({ locale: 'en', fallbackLocale: 'en',}) .use(FetchLoader({ cdnUrl: 'https://cdn.comvi.io/your-distribution-id', // Only load translations when needed (default behavior) }));Translations are fetched when the user switches locales. Already-loaded locales are served from cache.
SSR with SolidStart
Section titled “SSR with SolidStart”@comvi/solid does not ship a SolidStart adapter. For SSR, load translations in a server
function (a fresh i18n instance per request), pass them to the route, and render through
<I18nProvider autoInit={false}> so hydration starts from the same cache.
Loading Translations Server-Side
Section titled “Loading Translations Server-Side”import { createI18n } from '@comvi/solid';import { FetchLoader } from '@comvi/plugin-fetch-loader';import { query } from '@solidjs/router';
export const loadI18n = query(async (locale: string) => { 'use server';
const i18n = createI18n({ locale, fallbackLocale: 'en' }).use( FetchLoader({ cdnUrl: 'https://cdn.comvi.io/your-distribution-id' }), ); await i18n.init();
const namespace = i18n.getDefaultNamespace(); return { locale, messages: { [`${locale}:${namespace}`]: i18n.getTranslations(locale, namespace) ?? {}, }, };}, 'comvi-i18n');Route + Provider
Section titled “Route + Provider”autoInit={false} tells <I18nProvider> not to call init() again — the data is already
seeded via translation. Set the HTML lang attribute from the reactive locale:
import { createAsync } from '@solidjs/router';import { Show } from 'solid-js';import { createI18n, I18nProvider, useI18n } from '@comvi/solid';import { loadI18n } from '~/lib/i18n';
export default function Home() { const data = createAsync(() => loadI18n('en'));
return ( <Show when={data()}> {(ready) => { const i18n = createI18n({ locale: ready().locale, fallbackLocale: 'en', translation: ready().messages, });
return ( <I18nProvider i18n={i18n} autoInit={false}> <Content /> </I18nProvider> ); }} </Show> );}
function Content() { const { t, locale } = useI18n(); return ( <html lang={locale()}> <body> <h1>{t('home.title')}</h1> </body> </html> );}If you lazy-load extra namespaces, include them in the returned messages before rendering
any component that needs them.
TypeScript
Section titled “TypeScript”Enable type-safe translations with auto-generated types. The Comvi CLI emits a .d.ts that augments the TranslationKeys interface in @comvi/core — importing it once anywhere in your project gives t() autocomplete and parameter validation:
import { createI18n } from '@comvi/core';// Side-effect import — augments @comvi/core's TranslationKeys interfaceimport './types/i18n';
export const i18n = createI18n({ locale: 'en', fallbackLocale: 'en',});Now t('home.title') autocompletes keys and t('greeting', { name }) validates parameters at compile time. See Type-Safe Translations for the full setup.