Setup
createI18n options
import { createI18n } from "@domphy/i18n"
import i18next from "i18next"
const i18n = createI18n({
/** i18next instance to use. Always pass your own configured instance. */
instance: i18next,
/** Supported locale codes. */
locales: ["en", "fr", "vi"],
/** Fallback locale if detection fails. */
defaultLocale: "en",
/** Message resources keyed by locale then key. */
messages: {
en: { greeting: "Hello, {{name}}!", items_one: "{{count}} item", items_other: "{{count}} items" },
fr: { greeting: "Bonjour, {{name}}!" },
vi: { greeting: "Xin chào, {{name}}!" },
},
/** Optional: detect locale from browser/URL. Defaults to navigator.language. */
detectLocale: () => localStorage.getItem("lang") ?? navigator.language,
})Reactive translation
Pass a listener to re-render when the locale changes:
const { t } = i18n
const Header = {
h1: (l) => t(l, "greeting", { name: "World" }),
}When no listener is needed (e.g. outside reactive context):
const label = t(null, "greeting", { name: "World" })Locale switching
const { setLocale, getLocale } = i18n
// Switch locale — all reactive t(l, ...) re-render automatically
setLocale("vi")
// Read current locale
console.log(getLocale()) // "vi"Locale detection
const { detectLocale, initI18n } = i18n
// initI18n runs detectLocale + initializes the i18next instance
await initI18n()globalThis dedup
createI18n registers the i18next instance on globalThis under a unique key derived from the locale list. This ensures a single instance even when Vite splits the module across chunks — important for SSR + client hydration consistency.
i18next integration
@domphy/i18n wraps any i18next instance — bring your own plugins (HTTP backend, language detector, pluralisation, etc.):
import i18next from "i18next"
import HttpBackend from "i18next-http-backend"
await i18next.use(HttpBackend).init({
lng: "en",
backend: { loadPath: "/locales/{{lng}}.json" },
})
const { t } = createI18n({ instance: i18next, locales: ["en", "fr"], defaultLocale: "en", messages: {} })