i18n and Localization (resuxjs/i18n)
Resux i18n is integrated with file-based routing, SSR, client navigation, head/SEO output, and resumability. Create each page once. Resux injects the locale variants into the generated route manifest.
The production example is the Resux Lab page at pages/features/i18n.vue: there is no pages/en/ or pages/ar/ copy.
Enable the module
export default defineResuxConfig({
modules: ['resuxjs/i18n'],
i18n: {
defaultLocale: 'en',
fallbackLocale: 'en',
strategy: 'prefix_except_default',
locales: [
{ code: 'en', name: 'English', dir: 'ltr' },
{ code: 'ar', name: 'العربية', dir: 'rtl' }
],
messages: {
en: './locales/en.json',
ar: './locales/ar.json'
},
seo: {
hreflang: true
}
}
})A typical app stays simple:
pages/
index.vue
about.vue
products/
[id].vue
docs/
[...slug].vue
locales/
en.json
ar.jsonDo not add pages/en/** and pages/ar/** just to obtain localized URLs.
Where localization happens
The compiler pipeline is:
pages/**
↓
file-based route records
↓
module/page extension hooks
↓
i18n route transform
↓
final route manifest
↓
SSR matcher + client route-payload navigationLocalized paths are therefore real routes, not strings produced only by localePath(). A direct request or browser refresh on /ar/products/42 is matched by the server route manifest and rendered with Arabic locale context before client JavaScript runs.
Only page routes are localized. API, server/routes/**, and framework-internal /__resux/** endpoints are not duplicated as locale pages.
Route strategies
Assume en is the default locale and ar is another locale.
| Page source | prefix | prefix_except_default | no_prefix |
|---|---|---|---|
pages/index.vue | /en, /ar | /, /ar | / |
pages/about.vue | /en/about, /ar/about | /about, /ar/about | /about |
pages/products/[id].vue | /en/products/:id, /ar/products/:id | /products/:id, /ar/products/:id | /products/:id |
pages/docs/[...slug].vue | /en/docs/:slug*, /ar/docs/:slug* | /docs/:slug*, /ar/docs/:slug* | /docs/:slug* |
prefix
Every locale is prefixed. The bare / route redirects to the default locale root, for example /en.
prefix_except_default
The default locale is canonical without a prefix. Resux does not generate duplicate /en/** page records, so /about is the English route and /ar/about is the Arabic route.
no_prefix
Only the original pathname exists. On an initial SSR request the pathname does not identify a non-default locale, so SSR begins with the configured default locale. On the client, setLocale() updates the per-app locale state and document lang/dir without changing the URL. If an application needs a non-default locale to survive a full reload in no_prefix, store that preference in application state/cookies and apply it at request time when such server-side preference handling is available.
Message files
locales/en.json:
{
"product": {
"title": "Product",
"reviews": "Reviews"
},
"greeting": "Hello {name}"
}locales/ar.json:
{
"product": {
"title": "المنتج",
"reviews": "المراجعات"
},
"greeting": "مرحبًا {name}"
}Message sources may also be inline objects or supported loader functions. Keep secrets out of messages because resolved catalogs are public runtime configuration.
useI18n()
const {
locale,
dir,
locales,
defaultLocale,
fallbackLocale,
strategy,
t,
tm,
resolveLocalized,
localePath,
switchLocalePath,
setLocale
} = useI18n()t()
const greeting = t('greeting', { name: 'Developer' })tm()
Use tm() when a key contains an object/array rather than a final string:
const navigation = tm('navigation')resolveLocalized()
const label = resolveLocalized({
en: 'Read more',
ar: 'اقرأ المزيد'
})Locale-aware navigation
const localePath = useLocalePath()
const switchLocalePath = useSwitchLocalePath()
localePath('/products/42', 'ar')
// prefix/prefix_except_default: /ar/products/42
switchLocalePath('ar')
// same logical page in ArabicsetLocale() uses the Resux client router when the strategy changes the pathname:
await setLocale('ar')With prefix or prefix_except_default, this performs real route navigation. With no_prefix, it changes the active client locale without inventing a locale path.
Params, query strings, and hashes
Locale switching operates on the logical pathname while preserving the URL suffix.
From:
/products/42?tab=reviews#topArabic under prefix_except_default becomes:
/ar/products/42?tab=reviews#topAlready-localized inputs are normalized first, so repeated switching does not produce paths such as /ar/en/products/42 or /ar/ar/products/42.
SSR, refresh, and browser history
For prefixed strategies the locale comes from the request path on the server. This means all of these are normal framework behavior:
- direct
GET /ar/features/i18n, - refresh while on
/ar/features/i18n, - English → Arabic navigation,
- Arabic → English navigation,
- Back/Forward between localized entries.
The initial payload and the resumed client use the same route-derived locale, preventing the common “English SSR, Arabic after hydration” mismatch.
RTL and document language
For an Arabic SSR request Resux emits document attributes before hydration:
<html lang="ar" dir="rtl">English emits:
<html lang="en" dir="ltr">Use the returned dir value for component-level layout where useful, but do not rely on a client-only DOM patch for the document root.
Canonical URLs and hreflang
When i18n SEO is enabled, Resux derives canonical and alternate URLs from the canonical pathname, not its query/hash suffix. It emits locale alternates and an x-default entry for the configured default locale.
For prefix_except_default:
canonical English: /products/42
Arabic alternate: /ar/products/42
x-default: /products/42Missing translations
t() first looks in the active locale, then the configured fallback locale, then returns the key if no message can be resolved. Translation traversal reads own properties only and rejects prototype-sensitive keys such as __proto__, prototype, and constructor.
Common mistakes
Do not solve i18n by creating manual locale page trees:
pages/en/** # unnecessary
pages/ar/** # unnecessaryDo not replace framework i18n with a local component-only translation object:
const locale = ref('en')
const messages = { en: {}, ar: {} }
function t(key) { /* ... */ }That can change text in one component, but it does not create SSR routes, localized URLs, canonical links, hreflang, route-aware history, or server/client locale consistency.
Resux Lab example
The regression application uses:
pages/features/i18n.vue
locales/en.json
locales/ar.jsonThe same source page serves /features/i18n and /ar/features/i18n under its prefix_except_default configuration. Its E2E/browser tests verify direct SSR, lang/dir, localized text, switching, Back/Forward, and refresh-compatible URLs.