FrançaisPlayground

Internationalization

Fluixi ships built-in i18n. createI18n gives you a reactive, SSR-correct translator:

import { createI18n } from '@fluixi/core/i18n';

const i18n = createI18n({
  locale: 'en',
  locales: ['en', 'fr'] as const,
  messages: { en: { hello: 'Hello, {name}' }, fr: { hello: 'Bonjour, {name}' } },
});

export const { t, locale, setLocale } = i18n;

t('hello', { name: 'Ada' }); // "Hello, Ada"

t reads the locale reactively, so a translated string inside a template updates when the locale changes — no re-mount, no reload.

Message keys

Keys can be flat or nested, and both resolve through the same dotted path:

messages: {
  en: {
    'nav.dashboard': 'Dashboard',        // flat, dotted
    nav: { settings: 'Settings' },       // nested
  },
}

t('nav.dashboard');
t('nav.settings');

Flat keys are checked first, so a dotted key wins over a nested path of the same name. That matters when merging a translator's flat export into a hand-written nested tree.

t('…') autocompletes the keys from your messages, so a typo is a type error rather than a missing string at runtime.

Interpolation, plurals and formats

{name} placeholders are filled from the second argument. Pluralization uses Intl.PluralRules; numbers and dates use Intl.NumberFormat / Intl.DateTimeFormat, so the rules come from the platform rather than from a table that has to be maintained.

Switching locale

setLocale('fr');

On the client that sets a signal and persists the choice, so it survives a reload. On the server it sets the locale for the current request only and seeds it into the page.

SSR and why it does not leak

This is the part worth understanding. On the server the active locale lives in the request context, not in a module-level signal. Two requests being handled concurrently in the same process therefore cannot see each other's locale — the failure mode where one user's page renders in another user's language simply cannot arise.

On the client there is one user, so the locale is an ordinary reactive signal.

The server-resolved locale is serialized into the page and read back before the first client render, so hydration matches what was rendered — no flash of the wrong language, no hydration mismatch.

Auto-discovery

Put translations in src/i18n/<locale>.json and @fluixi/start exposes them as virtual:fluixi-i18n — typed off the JSON, so keys flow straight into createI18n without a hand-written type.

import messages from 'virtual:fluixi-i18n';

const i18n = createI18n({ locale: 'en', locales: ['en', 'fr'] as const, messages });

Adding a locale is then adding a file.

Choosing the initial locale

detectLocale picks from what the request offers — the Accept-Language header, a persisted cookie — narrowed to the locales you actually ship, with your default as the fallback.

That wraps up the v1 guide. Explore the reactive core live in the Playground.