Skip to content

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 ​

ts
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:

txt
pages/
  index.vue
  about.vue
  products/
    [id].vue
  docs/
    [...slug].vue

locales/
  en.json
  ar.json

Do not add pages/en/** and pages/ar/** just to obtain localized URLs.

Where localization happens ​

The compiler pipeline is:

txt
pages/**
  ↓
file-based route records
  ↓
module/page extension hooks
  ↓
i18n route transform
  ↓
final route manifest
  ↓
SSR matcher + client route-payload navigation

Localized 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 sourceprefixprefix_except_defaultno_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:

json
{
  "product": {
    "title": "Product",
    "reviews": "Reviews"
  },
  "greeting": "Hello {name}"
}

locales/ar.json:

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() ​

ts
const {
  locale,
  dir,
  locales,
  defaultLocale,
  fallbackLocale,
  strategy,
  t,
  tm,
  resolveLocalized,
  localePath,
  switchLocalePath,
  setLocale
} = useI18n()

t() ​

ts
const greeting = t('greeting', { name: 'Developer' })

tm() ​

Use tm() when a key contains an object/array rather than a final string:

ts
const navigation = tm('navigation')

resolveLocalized() ​

ts
const label = resolveLocalized({
  en: 'Read more',
  ar: 'اقرأ المزيد'
})

Locale-aware navigation ​

ts
const localePath = useLocalePath()
const switchLocalePath = useSwitchLocalePath()

localePath('/products/42', 'ar')
// prefix/prefix_except_default: /ar/products/42

switchLocalePath('ar')
// same logical page in Arabic

setLocale() uses the Resux client router when the strategy changes the pathname:

ts
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:

txt
/products/42?tab=reviews#top

Arabic under prefix_except_default becomes:

txt
/ar/products/42?tab=reviews#top

Already-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
<html lang="ar" dir="rtl">

English emits:

html
<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:

txt
canonical English: /products/42
Arabic alternate: /ar/products/42
x-default: /products/42

Missing 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:

txt
pages/en/**   # unnecessary
pages/ar/**   # unnecessary

Do not replace framework i18n with a local component-only translation object:

ts
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:

txt
pages/features/i18n.vue
locales/en.json
locales/ar.json

The 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.

Source-aligned documentation for the Resux resumable web framework.