Skip to content

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.

Terminal window
pnpm add @comvi/solid @comvi/core @comvi/plugin-fetch-loader
  1. 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',
    }));
  2. 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';
    await i18n.init();
    render(
    () => (
    <I18nProvider i18n={i18n}>
    <App />
    </I18nProvider>
    ),
    document.getElementById('root')!,
    );
  3. 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>
    );
    }

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:

PropertyTypeDescription
t(key, params?) => stringTranslate a key with optional parameters
localeAccessor<string>Current language (reactive signal)
isLoadingAccessor<boolean>Whether translations are being loaded
setLocale(lang: string) => Promise<void>Switch to a different locale

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>
);
}

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>,
}}
/>
);
}

Load translations from a specific namespace:

function Dashboard() {
const { t } = useI18n('dashboard');
return <h1>{t('page.title')}</h1>;
}

SolidJS signals make Comvi translations fine-grained reactive. Here are common patterns.

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>;
}

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;
}

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>
);
}

Combine the fetch loader with lazy loading to fetch translations on demand:

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',
// Only load translations when needed (default behavior)
}));

Translations are fetched when the user switches locales. Already-loaded locales are served from cache.

For server-side rendering with SolidStart, initialize translations in the route’s data function so they are available before rendering.

src/routes/index.tsx
import { createAsync } from '@solidjs/router';
import { i18n } from '../i18n';
export const route = {
load: () => {
return i18n.init();
},
};

Set the HTML lang attribute in your root layout:

src/root.tsx
import { useI18n, I18nProvider } from '@comvi/solid';
import { i18n } from './i18n';
export default function Root() {
return (
<I18nProvider i18n={i18n}>
<Html />
</I18nProvider>
);
}
function Html() {
const { locale } = useI18n();
return (
<html lang={locale()}>
<head>
<meta charset="utf-8" />
</head>
<body>
{/* your app */}
</body>
</html>
);
}

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:

src/i18n.ts
import { createI18n } from '@comvi/core';
// Side-effect import — augments @comvi/core's TranslationKeys interface
import './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.