Multi-Language Support
Configure language options for your merchants and their customers.
Supported Languages
JoPay supports four languages across the payment UI:
| Code | Language |
|---|---|
en | English |
es | Spanish |
fr | French |
pt | Portuguese |
Partner Configuration
Each partner record has three language-related fields:
ui_primary_language— the default language for all merchant and payment UIs under this partner. Required.ui_secondary_language— an optional second language. When set, users see a language switcher in the UI.ui_enabled_languages— the full list of languages available for this partner. Only languages in this list can be selected by users. Must be a subset of["en", "es", "fr", "pt"].
All translation packs are bundled in the app. A language is only available if its translation pack is complete (every key has a non-empty translation). JoPay validates this at runtime using the
packComplete() check.How Language Selection Works
Language preference is stored in a cookie called p2ppay_lang. Here is the flow:
- Default — when a user first visits, the UI renders in the partner's
ui_primary_language. - Language switcher — if the partner has more than one enabled language, a language selector appears in the UI. The user picks their preferred language.
- Form submission — selecting a language submits a
POST /api/langrequest with the chosen language code, a CSRF token, and a return URL. - Validation — the server checks that the selected language is in the partner's
ui_enabled_languageslist and that the translation pack is complete. If either check fails, the request is rejected. - Cookie set — on success, the server sets the
p2ppay_langcookie (HTTP-only, same-site strict, secure in production) and redirects back to the return URL. - Subsequent requests — all pages read the
p2ppay_langcookie and render in the selected language.
Translation Architecture
Translations are organized as flat JSON files in the messages/ directory:
messages/en.json— English (baseline)messages/es.json— Spanishmessages/fr.json— Frenchmessages/pt.json— Portuguese
The i18n.ts module flattens nested JSON into dot-separated keys (e.g. dashboard.title) and provides a getMessage(lang, key) function that falls back to the English translation if a key is missing in the selected language.
Best Practices
- Keep enabled languages minimal — only enable languages that your merchants and customers actually need. Each language adds a switcher option and requires complete translations.
- Test the payment page — open a payment link and switch languages to verify that all labels, buttons, and error messages are correctly translated.
- English fallback — if a translation key is missing in a non-English pack, JoPay falls back to English. This ensures the UI never shows raw keys to users.
The language cookie is scoped to the domain. If your partner uses a custom domain, the cookie is set on that domain. Users switching between different partner domains will have separate language preferences.