Skip to content
fluenti

Locale Switching

<script setup>
import { useI18n } from '@fluenti/vue'
const { locale, setLocale, getLocales, isLoading, preloadLocale } = useI18n()
</script>
<template>
<div class="locale-switcher">
<button
v-for="l in getLocales()"
:key="l"
:class="{ active: locale === l }"
@click="setLocale(l)"
@mouseenter="preloadLocale(l)"
>
{{ l }}
</button>
<span v-if="isLoading">Loading...</span>
</div>
</template>
function LocaleSwitcher() {
const { locale, setLocale, getLocales, isLoading, preloadLocale } = useI18n()
return (
<div>
{getLocales().map(l => (
<button
key={l}
className={locale === l ? 'active' : ''}
onClick={() => setLocale(l)}
onMouseEnter={() => preloadLocale(l)}
>
{l}
</button>
))}
{isLoading && <span>Loading...</span>}
</div>
)
}
function LocaleSwitcher() {
const { locale, setLocale, getLocales, isLoading, preloadLocale } = useI18n()
return (
<div>
{getLocales().map(l => (
<button
classList={{ active: locale() === l }}
onClick={() => setLocale(l)}
onMouseEnter={() => preloadLocale(l)}
>
{l}
</button>
))}
{isLoading() && <span>Loading...</span>}
</div>
)
}

setLocale() updates the in-memory state, but the browser needs a few extra steps to stay in sync:

  1. Cookie — so the server (SSR) and client agree on the locale
  2. <html lang> — for accessibility and SEO
  3. <html dir> — for RTL languages (Arabic, Hebrew, etc.)

These are standard DOM operations that vary by app, so Fluenti provides the building blocks (getDirection, isRTL) and lets you wire them up:

<script setup>
import { watch } from 'vue'
import { useI18n } from '@fluenti/vue'
import { getDirection } from '@fluenti/core'
const { locale, setLocale } = useI18n()
watch(locale, (loc) => {
document.cookie = `locale=${loc};path=/;max-age=31536000`
document.documentElement.lang = loc
document.documentElement.dir = getDirection(loc)
})
</script>
import { useEffect, useCallback } from 'react'
import { useI18n } from '@fluenti/react'
import { getDirection } from '@fluenti/core'
function useLocaleSync() {
const { locale } = useI18n()
useEffect(() => {
document.cookie = `locale=${locale};path=/;max-age=31536000`
document.documentElement.lang = locale
document.documentElement.dir = getDirection(locale)
}, [locale])
}
import { createEffect } from 'solid-js'
import { useI18n } from '@fluenti/solid'
import { getDirection } from '@fluenti/core'
function useLocaleSync() {
const { locale } = useI18n()
createEffect(() => {
const loc = locale()
document.cookie = `locale=${loc};path=/;max-age=31536000`
document.documentElement.lang = loc
document.documentElement.dir = getDirection(loc)
})
}

When you call setLocale():

  • Vue: v-t output and useI18n().t() expressions update as locale.value changes. Direct-import t does not create a ComputedRef wrapper by itself.
  • React: The provider creates a new createFluentiCore() instance, triggering re-renders for all children consuming the context.
  • Solid: JSX expressions that call t or useI18n().t() re-run inside Solid’s reactive scopes. Direct-import t does not create a createMemo wrapper.

Every $d() and $n() call also updates automatically.

Call preloadLocale() on hover so the catalog is already cached when the user clicks. The switch feels instant:

<button
onMouseEnter={() => preloadLocale('ja')}
onClick={() => setLocale('ja')}
>
日本語
</button>

Use msg for translations that need to be defined outside of components — in constants, stores, or route definitions.

// This won't work — t`` needs a component context
const ROLES = {
admin: t`Administrator`, // Error: no component context
}
import { msg } from '@fluenti/core'
const ROLES = {
admin: msg`Administrator`,
user: msg`Regular User`,
}

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

<template>
<span>{{ t(ROLES.admin) }}</span>
</template>
  • Route meta: { meta: { title: msgDashboard } }
  • Store constants: Error messages, role names, status labels
  • Config objects: Menu items, navigation labels
  • Enum-like maps: Any mapping where values need translation