Solid
With a SolidJS app, @comvi/solid is <I18nProvider> at the root, useI18n() in components,
and <T> for translations that carry markup. t() and locale() are signal-backed, so they
update exactly where Solid is tracking them.
Open the Solid example in StackBlitz and switch the locale: plurals update, <T> keeps its link and the catalog for the new locale arrives on first use.
Install
Section titled “Install”npm install @comvi/solidpnpm add @comvi/solidyarn add @comvi/solidbun add @comvi/solid@comvi/solid peer-depends on solid-js (^1.8) and brings @comvi/core.
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')!,);createI18n() returns the instance and <I18nProvider> initializes it from a createEffect, so
there is no init() to write. Its other props are autoInit, onError, fallback and
ssrLocale. The catalog options are on the Quick start.
Use in components
Section titled “Use in components”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> </> );}locale is an Accessor<string>, so it is locale() in JSX. setLocale() resolves once the new
locale’s catalog has loaded, and every t() Solid is tracking re-runs with the new text.
useI18n('admin') binds a namespace at the type level too. The formatNumber, formatDate,
formatCurrency and formatRelativeTime members follow the locale like t(), and a trailing
locale argument overrides it.
Markup inside a translation
Section titled “Markup inside a translation”{ "help": "For support, <link>visit our docs</link> or <bold>email us</bold>."}import { T } from '@comvi/solid';
export function Help() { return ( <T i18nKey="help" components={{ link: (props) => <a href="/docs">{props.children}</a>, bold: (props) => <strong>{props.children}</strong>, }} /> );}Each tag maps to a plain function or an element name such as { policy: 'a' }. Every prop other
than i18nKey, components, params, ns, locale, fallback, markers and
children is an interpolation param. Plain t() does not parse tags.
Load from Comvi
Section titled “Load from Comvi”import { render } from 'solid-js/web';import { createI18n, I18nProvider } from '@comvi/solid';import { cdnLoader } from '@comvi/plugin-fetch-loader';import App from './App';
const i18n = createI18n({ locale: 'en', fallbackLocale: 'en',}).use(cdnLoader({ cdnUrl: 'https://cdn.comvi.io/<project-token>' }));
render( () => ( <I18nProvider i18n={i18n} fallback={<div class="spinner" />}> <App /> </I18nProvider> ), document.getElementById('root')!,);Install @comvi/plugin-fetch-loader and compose .use(cdnLoader()) inside the createI18n
expression, because the provider’s effect calls init() and init() is what runs the CDN plugin.
The rest of the walkthrough is on Connect to Comvi.
The CDN endpoint URL is under Settings → Content Deploy in your Comvi project, once Content
Deploy is enabled and saved; pass it as cdnUrl. The URL format is on
CDN Deployment.
If your keys use ICU ({count, plural, …}), add compiler: icuCompiler as on
Pluralize with ICU. The platform and the compiler are independent:
either works without the other.
First paint
Section titled “First paint”fallback renders in place of the children until the instance is initialized, so raw keys never
paint. The gate reads the provider’s isInitialized signal, not isInitializing: auto-init starts
from an effect after the tree is built, so a gate on “is it initializing” would be open for exactly
the paint the raw keys would appear in. Leave the prop off when the catalog is inline:
isInitialized() is false during a server render, so the fallback is what the server would paint.
Server-side rendering
Section titled “Server-side rendering”<I18nProvider> initializes from a createEffect, and effects do not run during Solid’s server
render, so a loader fetches nothing there and the instance must already hold its catalog. Load the
messages in a server function with a fresh instance per request, render through
<I18nProvider autoInit={false}>, and pass ssrLocale on both sides so the server render and the
hydrating client agree on the locale. The complete SolidStart example is on
Render on the server.
Tracking scope
Section titled “Tracking scope”import { useI18n } from '@comvi/solid';
export function Greeting() { const { t } = useI18n(); return <h1>{t('greeting', { name: 'Alice' })}</h1>;}t() and tRaw() read the locale, cache-revision and default-namespace signals at call time, so
they are reactive only inside JSX, a createMemo or a createEffect. Solid does not re-render a
component: const greeting = t('greeting') in the component body is computed once and never
updates. tRaw('key', { locale: 'de' }) pins a locale and skips the global locale signal, so a
switch does not recompute it.
How it works
Section titled “How it works”<I18nProvider> does two things in its body, during the render phase. It applies ssrLocale to
the instance when given, before anything reads the locale, so t() and the signal cannot disagree
on the first paint. That holds when the locale’s catalogs are already on the instance, inline or
loaded earlier; when they still have to load, the first render is in the instance’s current locale
and the switch lands when the load completes. And it creates the signals: locale from localeChanged, isLoading and
isInitializing from loadingStateChanged, isInitialized from initialized and destroyed, a
default-namespace signal, and a cache-revision counter over every other revision event. They are
created with Solid’s from() inside a memo over the i18n prop, so swapping the instance rebuilds
them and disposes the old subscriptions.
useI18n() returns functions, not values. Its tRaw() reads the locale signal (unless the call
pins a locale), the cache-revision signal, and the default-namespace signal (unless useI18n(ns)
bound one), then calls the instance. Those reads are the dependencies, which is the entire
reactivity model: a t() inside JSX, a createMemo or a createEffect re-runs when one of those
signals changes, and a t() in a component body runs once because nothing is tracking it. The four
formatters read the locale signal the same way, unless the call passes a trailing locale. Solid
updates the text node, not the component.
init() runs from createEffect once the tree is built, on the client only. It raises
isInitializing, runs the plugins, loads the default namespace, in the current locale and along
the fallback chain, then sets isInitialized and emits initialized, which flips the <Show>
the fallback prop gates on. A failed fallback catalog is reported and nothing more.
With an inline catalog the load is a cache hit; with a loader this is where the first fetch
happens, and the fallback covers it.
setLocale() is the instance’s setLocale(). When a loader is composed, the new locale’s
active namespaces load before the locale is assigned, and localeChanged fires only then, so the
locale signal moves once, after the catalog is complete. When several switches overlap, the latest
wins and the superseded ones apply nothing.
A missing key returns the key itself and emits missingKey, so a raw key on screen means a
missing translation rather than a broken provider; strict: 'dev' adds a console warning in
development. On the server the instance is a mutable object, and the ssrLocale write makes a
module-level one unsafe to share: build it inside the request handler.
Errors
Section titled “Errors”| what happened | message |
|---|---|
useI18n() outside <I18nProvider> |
[@comvi/solid] i18n context not found. Wrap your app with <I18nProvider i18n={i18n}>. |
useI18nLoader() on an instance with no loader composed |
development: [@comvi/core] This i18n instance has no loader capability. Compose it: .use(attachLoader) from "@comvi/core/loader", or loader(map).; a production build throws the short form quoted on Error codes |
More messages and their fixes are on Troubleshooting.
Read next
Section titled “Read next”- Connect to Comvi: the CDN URL, and a wording change that ships without a redeploy.
- Load translations on demand:
loader(map), namespaces anduseI18nLoader(). - Solid API reference: every export, the standalone primitives and
<T>props.