⌘K

Translation editor & per-context locales

SynaptikCMS ships with a built-in translation editor that lets you edit any locale file from the admin panel, and pick different languages for the public site and the admin panel.


Two independent locales

Two settings drive language selection, persisted in settings.json:

SettingScopeSource
active_languageFront-end (public site)lang/{locale}.json
admin_languageAdmin panel onlylang/admin/{locale}.json

admin_language is optional. When absent, the admin panel falls back to active_language — existing installs keep working without configuration.

The two locales are fully independent. You can run the public site in French and the admin panel in English, or any other combination.

Configure both from Settings → General. Each selector lists only the locales available in its own scope (front-end locales for the public site, admin locales for the admin panel).

What stays tied to the front-end locale

Even when admin and front locales differ, URLs generated from the admin panel always use front-end slugs (url_slug_article, url_slug_category, etc.). This guarantees that "View on site" links from the admin match the real public routes.


Translation editor

Access it from:

  • Tools → Translations in the admin sidebar
  • The New translation button next to either language selector in Settings → General

The editor lists every key from en.json (used as the schema reference) side-by-side with the current locale's value. Empty translations are highlighted in orange; modified rows in green.

Pick a scope (front-end / admin) and a locale, edit values, then click Save all changes.

Features

  • Live filter by key name or reference text (useful for the admin scope with several hundred strings).
  • Placeholder validation: if the reference string contains %s or %d and your translation does not, a warning is shown — you can still save, but the mismatch is reported.
  • Stat bar: total strings, missing translations, unsaved changes.
  • Unsaved-changes guard: navigating away triggers a confirmation if edits are pending.

Creating a new locale

Click New locale at the top of the editor. Provide:

  • Language code — 2 lowercase letters (ko, ja, de) or a regional variant (pt_BR, en_GB).
  • Display name — the label shown in the language dropdown (Korean, 日本語, etc.).
  • Also create the matching admin locale — recommended. Keeps front and admin in sync.

The new file is duplicated from en.json with the _meta block updated. The editor reloads on the new locale, ready for translation.

The en locale cannot be created or overwritten through this UI — it is the reference schema and must remain canonical.


How the locales are loaded

The lang loader (lang-cache.php) is context-aware. It reads LANG_CONTEXT, which is defined as 'admin' by admin/includes/admin-functions.php and defaults to 'front' everywhere else.

LANG_CONTEXT = 'admin'  →  read admin_language  →  fallback to active_language  →  fallback to 'en'
LANG_CONTEXT = 'front'  →  read active_language  →  fallback to 'en'

Both contexts have their own cache (cache/lang/front/{locale}.php and cache/lang/admin/{locale}.php), so changing the admin locale never invalidates front-end cache, and vice versa.


Security

All write operations go through admin/translations-api.php, which:

  • Requires an active admin session.
  • Validates CSRF token on every POST.
  • Whitelists scope (front|admin) and locale codes via strict regex (^[a-z]{2}(_[A-Z]{2})?$).
  • Rejects any key not present in en.json — prevents arbitrary keys being injected via a forged payload.
  • Caps string length at 20 000 characters per value.
  • Refuses to overwrite an existing locale on create (returns 409).
  • Writes atomically (tmp + rename) and invalidates the relevant cache file immediately.

Notes for developers

The editor is the only place that should write to locale files. Direct edits on disk still work (the cache picks up source-file changes automatically via mtime check), but lose the schema validation and placeholder consistency checks.

url_slug_* keys live inside the regular locale files. They appear in the editor like any other string and can be translated, but changing them will affect the public URL routes — verify with parseRequestUri() if you customise them.

Adding new strings to the codebase: add the key to lang/en.json (or lang/admin/en.json) as the source of truth, then translate via the editor for every other locale. The editor highlights missing keys automatically.

lang_available() vs lang_available_for_scope(): the first returns locales for the current context (uses LANG_CONTEXT). The second takes an explicit scope ('front' or 'admin') and is used by the settings page to list both side by side.