Skip to main content

Localization (i18n)

The API negotiates a locale per request and returns all human-readable strings (error and success messages, validation messages, e-mail content) in that locale. Translation never affects machine-readable fields — only message, validation errors, and notification copy are localized.

Supported locales​

The supported-locale allowlist is the single source of truth in config/locales.php. As of this draft:

CodeNameNative nameDirection
enEnglishEnglishltr
trTurkishTürkçeltr

The default (and fallback) locale is en. Any locale outside the allowlist is ignored and the request falls back to the default — unknown or injected locale strings are never honoured.

Discovering locales (for clients)​

Clients (the admin SPA, the docs site) read the list at runtime so language switchers stay in sync with the server. This endpoint is public, unauthenticated and tenant-agnostic:

GET /api/locales
{
"default": "en",
"active": "en",
"dir": "ltr",
"locales": [
{ "code": "en", "name": "English", "native_name": "English", "dir": "ltr" },
{ "code": "tr", "name": "Turkish", "native_name": "Türkçe", "dir": "ltr" }
]
}
  • default — the fallback locale.
  • active — the locale this request negotiated (so a client can confirm the outcome of Accept-Language negotiation).
  • dir — the text direction (ltr / rtl) of the active locale.
  • locales[].dir — per-locale direction, so a front-end can set document.dir / the dir HTML attribute. Direction metadata is provided for every locale up front so the layout is RTL-ready before any RTL language ships.

How the locale is chosen​

For each request the server resolves the locale by this priority, restricted to the allowlist at every step:

  1. Authenticated user preference — users.settings.locale.
  2. Tenant default — the locale key in tenant_settings, when a tenant is bound.
  3. Accept-Language negotiation — the highest-ranked language tag in the header that is in the allowlist. Quality values (q=) and region subtags (tr-TR → tr) are honoured.
  4. Fallback — the default locale (en).

Examples​

GET /api/locales
Accept-Language: tr,en;q=0.8

→ active is tr.

GET /api/locales
Accept-Language: de-DE,fr;q=0.9

→ neither de nor fr is supported → active falls back to en.

A failed login illustrates the localized body:

POST /api/login
Accept-Language: tr

{ "email": "[email protected]", "password": "wrong" }
{
"message": "...",
"errors": { "email": ["Bu kimlik bilgileri kayıtlarımızla eşleşmiyor."] }
}

Setting a persistent preference​

Accept-Language is a per-request hint. To persist a choice, store it on the user (settings.locale) or the tenant default (tenant_settings.locale); the stored preference then takes precedence over Accept-Language on every request.

E-mails and queued notifications​

Notification e-mails (verification, magic-link, password reset) are localized to the recipient's preferred locale, resolved at send time — not to the locale of whoever triggered the send. This matters for queued jobs, which run outside the request and re-resolve the locale from the target user/tenant.