Skip to content
fluenti

SolidJS SPA

Fluenti works with any SolidJS SPA built on Vite. See the Quick Start for initial setup and installation.

Wrap your app with I18nProvider and pass in your compiled message catalogs:

src/App.tsx
import { I18nProvider } from '@fluenti/solid'
import { Home } from './Home'
import en from './locales/compiled/en'
import zhCN from './locales/compiled/zh-CN'
import ja from './locales/compiled/ja'
export function App() {
return (
<I18nProvider
locale="en"
fallbackLocale="en"
messages={{ en, 'zh-CN': zhCN, ja }}
>
<Home />
</I18nProvider>
)
}

I18nProvider provides the same reactive i18n runtime that createFluenti() exposes, wired into Solid’s context API for the component tree. All child components can access it through useI18n().

For larger apps, load non-default locales on demand:

<I18nProvider
locale="en"
fallbackLocale="en"
messages={{ en }}
lazyLocaleLoading
chunkLoader={(locale) => import(`./locales/compiled/${locale}.ts`)}
>
<App />
</I18nProvider>

When setLocale('ja') is called, the chunk loader fetches the Japanese catalog before switching. Use preloadLocale('ja') to load it in the background (e.g., on hover).

The primary DX path. The Vite plugin transforms tagged templates at build time:

import { t } from '@fluenti/solid'
function Greeting() {
const [name, setName] = createSignal('World')
return (
<div>
<h1>{t`Welcome to Fluenti`}</h1>
<p>{t`Hello, ${name()}!`}</p>
</div>
)
}

Because t reads the internal locale() signal, Solid’s fine-grained reactivity tracks it automatically. When the locale changes, only the text nodes that call t re-evaluate — the component function itself does not re-run.

useI18n() returns the full i18n context from the nearest I18nProvider:

import { useI18n } from '@fluenti/solid'
function Dashboard() {
const { t, locale, setLocale, d, n, format, isLoading, preloadLocale } = useI18n()
return (
<div>
<p>{t`Current locale: ${locale()}`}</p>
<p>{t('Hello, {name}!', { name: 'Developer' })}</p>
<p>{d(Date.now(), 'long')}</p>
<p>{n(1234.5, 'currency')}</p>
<button onClick={() => setLocale('ja')}>日本語</button>
</div>
)
}

Key properties returned by useI18n():

PropertyTypeDescription
locale()Accessor<string>Reactive accessor for the current locale
setLocale(loc)(locale: string) => Promise<void>Switch locale (async when lazy loading)
tfunctionTranslate by ID or tagged template
d(value, style?)functionFormat dates for the current locale
n(value, style?)functionFormat numbers for the current locale
format(msg, values?)functionInterpolate an ICU message directly
isLoading()Accessor<boolean>Whether a locale chunk is being loaded
preloadLocale(loc)functionPreload a locale in the background
te(key, loc?)functionCheck if a translation key exists
tm(key, loc?)functionGet the raw compiled message

Define translations outside components — in route definitions, stores, or constants:

import { msg } from '@fluenti/solid'
const PAGE_TITLES = {
home: msg`Home`,
settings: msg`Settings`,
}
// Later, inside a component:
function PageHeader(props: { page: keyof typeof PAGE_TITLES }) {
const { t } = useI18n()
return <h1>{t(PAGE_TITLES[props.page])}</h1>
}

msg returns a MessageDescriptor that records the message text without translating it. Resolve it at render time with t().

The <Trans> component renders translations that contain embedded components:

import { t, Trans } from '@fluenti/solid'
const Bold = (props) => <strong>{props.children}</strong>
const Link = (props) => <a href="/docs">{props.children}</a>
function Welcome() {
return (
<Trans
message={t`Welcome to <bold>Fluenti</bold> for <link>SolidJS</link>!`}
components={{ bold: Bold, link: Link }}
/>
)
}

The message prop contains the translated string with XML-style tags. The components map provides the Solid components used to render each tag.

import { Plural, Select } from '@fluenti/solid'
function CartStatus() {
const [count, setCount] = createSignal(0)
const [gender, setGender] = createSignal('female')
return (
<div>
<Plural
value={count()}
zero="Your cart is empty."
one="# item in your cart."
other="# items in your cart."
/>
<Select
value={gender()}
male="He liked this"
female="She liked this"
other="They liked this"
/>
</div>
)
}

Both components are runtime-capable and work without the build plugin. # in plural forms is replaced with the formatted count value.

SolidJS components run their function body once. Reactivity comes from signals, not re-renders. This has important implications for i18n:

function ReactivityDemo() {
const { t, locale } = useI18n()
const [count, setCount] = createSignal(0)
// This log runs only once — the component body is not re-executed
console.log('Component created')
// t() reads locale() internally, so it's a reactive expression
// When locale changes, only the text nodes update — not the whole component
return (
<div>
<p>{t`Counter value: ${count()}`}</p>
<p>{t`Current locale: ${locale()}`}</p>
<button onClick={() => setCount((c) => c + 1)}>+</button>
</div>
)
}

Use createEffect to run side effects when the locale changes:

import { createEffect } from 'solid-js'
import { getDirection } from '@fluenti/core'
function App() {
const { locale } = useI18n()
createEffect(() => {
document.documentElement.dir = getDirection(locale())
document.cookie = `locale=${locale()};path=/;max-age=31536000`
})
return <Main />
}
SolidJSReact
ReactivityFine-grained signals — locale() is an accessorRe-render cycle — locale is a string from state
Component executionFunction body runs onceFunction body re-runs on every render
t() trackingAuto-tracked via signal readsRelies on React re-render
Provider<I18nProvider> (context API)<I18nProvider> (React context)
Locale accesslocale() — call the accessorlocale — read the value directly
SolidJSVue
Setup<I18nProvider> wraps the appapp.use(fluenti) plugin
Template directiveNone — use t in JSXv-t compile-time directive
Reactivity modelSignals (name())Refs (name.value)
Rich text<Trans message={...} components={...} />v-t with child elements or <Trans> slot