Skip to content
Payment Emulator LabDocumentationOpen console

Frontends

Console internationalization architecture

Payment Emulator treats internationalization as presentation/runtime infrastructure, not as a responsibility of domain entities, gateways, server-state or HTTP transport. The Nuxt

6 min read

English documentation · Español

#Goal

Payment Emulator treats internationalization as presentation/runtime infrastructure, not as a responsibility of domain entities, gateways, server-state or HTTP transport. The Nuxt console supports canonical English (en) and Spanish (es) through one SSR-safe locale authority without changing visual design, backend contracts or functional route shapes.

#Canonical contract and authority

ts
export const APP_LOCALES = ['en', 'es'] as const
export type AppLocale = (typeof APP_LOCALES)[number]

export const DEFAULT_LOCALE: AppLocale = 'es'
export const FALLBACK_LOCALE: AppLocale = 'en'

Regional browser values such as en-GB, en-US, es-ES or es-CU are normalized before entering the application boundary. Spanish remains the default to preserve the public / URL contract; both console locales are implemented.

Application code reads or changes locale through:

text
useAppLocale()
  ├─ locale
  ├─ availableLocales
  ├─ setLocale()
  └─ isLocaleSupported()

Nuxt I18n owns the reactive Composer, lazy message catalogs and emulator_locale cookie. Components do not write that cookie, use localStorage as locale state or mutate $i18n directly.

#Runtime pipeline

text
APP_LOCALES / AppLocale

normalizeLocale()

useAppLocale()

Nuxt I18n
   ├── reactive locale
   ├── lazy feature-owned catalogs
   └── emulator_locale cookie

   presentation projections
   ├── <html lang>
   ├── PrimeVue locale
   └── Intl formatters (Phase 6)

The public-route middleware gives an explicit localized URL priority and delegates synchronization to useAppLocale().setLocale(); it never invents new localized routes.

#Routing

The existing physical public URLs remain authoritative for SEO:

text
/                  Spanish landing
/en                English landing
/docs/es/**        Spanish documentation
/docs/en/**        English documentation

Authenticated routes such as /login, /applications, /banks, /bank-accounts, /gateways, /scenarios and /users remain unprefixed. A UI-language change must not rewrite these functional routes.

#Message ownership

Lazy entrypoints i18n/locales/en.ts and i18n/locales/es.ts compose core messages with catalogs owned by app/modules/<context>/<feature>/i18n/. Business copy is not concentrated in one ownership-free global JSON file. JSON owners have matching EN/ES key structures. Landing owns a typed public/landing/i18n/catalog.ts, with English structurally typed from Spanish, mounted under public.landing; Docs owns its JSON pair under public.docs. Both use Nuxt I18n at runtime. There are 13 catalog owners, including core and Landing.

Stable namespaces follow application contexts such as administration, banking, configuration, integrations, observability, payments and public; common is reserved for truly shared presentation concepts.

#PrimeVue synchronization — Phase 5

Vue I18n does not automatically translate PrimeVue's internal vocabulary. Payment Emulator therefore owns complete PrimeVue adapters:

text
app/core/i18n/primevue/types.ts
app/core/i18n/primevue/en.ts
app/core/i18n/primevue/es.ts
app/core/i18n/primevue/index.ts

PrimeVueAppLocale requires the entire locale and ARIA contract of the installed PrimeVue version at compile time. This covers filtering, paginator, dates/calendar, FileUpload, password feedback, search/selection announcements and accessibility labels, including surfaces not yet present on every route.

Runtime synchronization is isolated in app/plugins/primevue-locale.ts:

text
useAppLocale().locale

primeVueLocaleFor(locale)

PrimeVue config.locale

The plugin applies the SSR-resolved value immediately and watches it reactively. PrimeVue remains a consumer only: it does not own persistence, routing or a second locale preference. Direct PrimeVue.config.locale mutation anywhere else is architecture-validator forbidden.

Canonical English is en-GB, so PrimeVue uses Monday-first weeks and dd/mm/yy instead of its US defaults; Spanish uses the same calendar shape.

#Architectural boundary

text
AppLocale
  ├─ Vue/Nuxt I18n
  ├─ PrimeVue locale
  ├─ Intl formatters
  └─ Presentation
       └─ use{Entity}Collection / UI composables
            └─ RestBaseQuery
                 └─ feature gateway
                      └─ RestTransport

Changing UI language must not refetch locale-neutral server state or change TanStack Query keys. Only an endpoint that genuinely returns localized content may include locale in its request/cache identity.

#Dates, numbers and currency

Phase 6 centralizes Intl.DateTimeFormat, Intl.NumberFormat, currency, percentage and related formatting behind helpers driven by AppLocale. Canonical backend/domain values remain unchanged; only their representation changes.

#Statuses, errors and validation

Backend semantic codes such as NOT_FOUND, CONFLICT, FORBIDDEN and UNAUTHENTICATED remain language-neutral. useAppError().explain(error, localizedFallback) maps codes or HTTP status to translation keys and never exposes backend human-readable messages. Unknown failures receive the caller's safe localized fallback.

Inline failures retain the semantic cause and compute their visible text. Persistent error toasts pass getters to useNotify().failed(), so changing language updates an existing notification without another request. The tests cover network failure, timeout, 400/401/403/404/409/422/500 and unknown failures in both languages. The browser suite proves rejected login and safe reactive form/action errors. Help-tour state is scoped to each Nuxt application to prevent SSR requests sharing locale-bound getters.

#Public URLs and SEO

Nuxt I18n owns the locale runtime with no_prefix; physical public routes preserve / for Spanish, /en for English and /docs/{es,en}/** for documentation. /login and authenticated routes remain unprefixed. Public middleware synchronizes explicit URL locale through useAppLocale() before rendering, so public URL identity takes precedence over the preference cookie.

publicLocalePath(locale, path) is the pure public URL helper consumed by public navigation, SEO alternates, sitemap alternates and global-error recovery. Canonical URLs, hreflang, structured data, SSR and hydration are checked in both languages. useLocale.ts, the old core public dictionaries and core/rest/explainFailure.ts are removed; compatibility wrappers are not part of the runtime.

#Translation workflow

For new copy, add equivalent semantic keys to the owning feature's EN/ES catalogs, consume them through useI18n() or the existing presentation facade, and keep translated configuration reactive. For a new feature, register its catalogs in both lazy entrypoints under its own namespace. For a new language, extend APP_LOCALES, metadata, every catalog including Landing, the PrimeVue projection, Intl policy and public URL/SEO tests. Never add locale to neutral query keys or translate domain values sent to the API.

#Verification and governance

npm run validate:architecture --workspace @payment-emulator/console runs architecture and i18n validators plus i18n:audit; zero violations are required without migration exclusions. validate:i18n and i18n:audit can also run individually in that workspace. The Console suite covers locale contract/runtime/catalogs, real composables, REST normalization, public routing, PrimeVue and Intl. Full CI adds typecheck, repository lint, tests, production builds, Compose, SSR/public/docs smoke and browser/payment E2E. Run smoke:stack before smoke:i18n:browser to bootstrap its operator; browser tooling is installed as declared in .github/workflows/ci.yml.

Closure uses two full CI rounds: successful implementation S1, followed by governance S2 and another complete run. The phase-10-14-certification artifact records the final SHA, run URL and COMPLETE for each phase only after the required jobs pass; the full run must finish SUCCESS. Browser evidence is uploaded as console-i18n-browser. A green run for an older SHA does not certify the current HEAD. PR #4 remains draft, open and unmerged during this process.

Docker diagnosis found an SSR 500 from removed useLocale() consumers and a worker crash because BullMQ forbids colons in queue names. The first is fixed by the canonical locale boundary; the second uses BullMQ's prefix while retaining the existing Redis key namespace. CI runs real Redis queue tests, captures container diagnostics and verifies all six services remain running after E2E without relaxing healthchecks.

The detailed rollout is governed by apps/console/.nuxt-refactor/.agents/i18n/implementation-plan.md. Changes to the production i18n contract must update implementation, .agents, both documentation languages and relevant tests together.

Payment Emulator Lab · RonuSoftwareMIT