Internationalization (i18n)
Overview
MysticX supports 12 locales using next-intl with inline dictionaries rather than static JSON translation files.
Supported Locales
| Code | Language | Script Direction |
|---|---|---|
en | English | LTR |
zh_CN | Simplified Chinese | LTR |
zh_TW | Traditional Chinese | LTR |
ja | Japanese | LTR |
ko | Korean | LTR |
pt | Portuguese | LTR |
es | Spanish | LTR |
fr | French | LTR |
de | German | LTR |
ar | Arabic | RTL |
id | Indonesian | LTR |
nl | Dutch | LTR |
URL Structure
Routes follow the pattern /{locale}/path:
/en/pricing
/zh-CN/pricing
/ja/pricingURL locales use hyphen format (next-intl convention): zh-CN, zh-TW.
Note: Inside TypeScript code and database JSON keys, the underscore form is used (
zh_CN,zh_TW). The URL segment is always hyphenated.
Translation Approach
User-Facing Pages
Use the useTrans (client) and getTrans (server) hooks with inline dictionaries:
const trans = useTrans();
return (
<h1>{trans({
en: 'Welcome to MysticX',
zh_CN: '欢迎来到 MysticX',
ja: 'MysticX へようこそ',
ko: 'MysticX에 오신 것을 환영합니다',
pt: 'Bem-vindo ao MysticX',
es: 'Bienvenido a MysticX',
fr: 'Bienvenue sur MysticX',
de: 'Willkommen bei MysticX',
ar: 'مرحبا بكم في MysticX',
zh_TW: '歡迎來到 MysticX',
id: 'Selamat datang di MysticX',
nl: 'Welkom bij MysticX',
})}</h1>
);Admin Panel
English-only. Do NOT use useTrans in admin pages.
Database Content
Localized database content uses TLocalizedString JSON fields:
{
"en": "The Fool",
"zh_CN": "愚者",
"ja": "愚者",
"ko": "바보"
}Types: TLocalizedString for strings, TLocalized<T> for generic typed content.
Adding a New Locale
Follow these phases when adding a new locale:
- Database migration — Add the new locale key to all JSON fields across 13 models
- Infrastructure — Register in routing config, type definitions, and normalization bridge
- Localization — Auth error messages, SEO metadata
- App Router pages — Update all
trans({})calls (127+ files across 3 batches) - Components and lib — Update shared component translations
Phase 1 (DB) must complete before deploying code changes. TypeScript will flag missing locale keys at compile time.
Tarot Spread Names
Canonical spread name translations are in docs/tarot-spreads.md. Do NOT invent new translations for spread names.
Key Rules
- Never use
Record<string, string>for localized content. UseTLocalized<T>orTLocalizedString. - Never use
\uXXXXunicode escapes. Write actual characters directly (e.g.,'正在解读…'). - Use escaped single quotes for strings containing apostrophes:
'L\'IA'. - Admin panel is English-only — no translation needed.