i18n Localizations
SynaptikCMS has a two-track i18n system: one for the front-end and one for the admin panel. The two locales are independent — the public site and the admin can run in different languages.
You don't need to edit JSON files manually. The admin panel ships with a built-in translation editor for editing existing locales and creating new ones. This page covers the underlying system for developers.
Locale files
| Scope | Path | Format |
|---|---|---|
| Front-end | /lang/{locale}.json | JSON key/value |
| Admin | /lang/admin/{locale}.json | JSON key/value |
en.json is the reference schema in both scopes — every other locale must mirror its keys. Missing keys fall back to the reference value at runtime.
Two independent active locales
Two settings in settings.json drive language selection:
| Setting | Scope | Falls back to |
|---|---|---|
active_language | Front-end (public site) | 'en' |
admin_language | Admin panel | active_language → 'en' |
admin_language is optional. On installs that don't define it, the admin panel uses active_language, so existing setups keep working.
The loader is context-aware: LANG_CONTEXT is defined as 'admin' by admin/includes/admin-functions.php and defaults to 'front' everywhere else. The matching locale key is selected automatically.
How it works
On the first request, lang_load() reads settings.json, picks the locale for the current context, loads the locale JSON, generates an OPcache-friendly PHP cache, and stores it in $GLOBALS['_LANG_STRINGS'].
Cache files are split by context:
cache/lang/front/{locale}.php
cache/lang/admin/{locale}.php
Changing the admin locale never invalidates the front-end cache, and vice versa. The cache is invalidated automatically if the source .json or settings.json is newer.
JS bridge — inject in header.php:
<script>window.CMS_LANG = <?php echo lang_js_bridge(); ?>;</script>
URL slugs
Defined per locale in the front-end files only. They drive routing, URL generation, and admin URL helpers simultaneously:
"url_slug_article": "article",
"url_slug_articles": "articles",
"url_slug_project": "project",
"url_slug_projects": "projects",
"url_slug_page": "page",
"url_slug_pages": "pages",
"url_slug_category": "category",
"url_slug_tag": "tag"
Even when the admin runs in a different language, URLs generated from the admin panel always use the front-end slugs — so "View on site" links match the real public routes.
Output helpers
_e('new_article'); // Echo
$label = __t('new_article'); // Return
printf(__t('media_files_size'), admin_format_file_size($s)); // Placeholder
__n('one_item', 'many_items', $count); // Pluralisation
__t() returns the key itself if no translation is found, so missing keys are immediately visible in the UI.
Listing available locales
lang_available(); // Locales for the current context
lang_available_for_scope('front'); // Explicitly front-end locales
lang_available_for_scope('admin'); // Explicitly admin locales
lang_current(); // Active locale for the current context
Use lang_available_for_scope() when you need to list locales of a scope different from the current LANG_CONTEXT — e.g. the settings page that shows both selectors side by side.
Adding a new string
- Add the key/value to
lang/en.json(front-end) orlang/admin/en.json(admin), whichever scope the string lives in. This is the source of truth. - Use the translation editor to fill in every other locale — missing keys are highlighted automatically.
- Never hardcode a user-visible string in PHP — always use an i18n key.
Adding a new language
The translation editor handles this in one click via the New locale button. Manually, the equivalent steps are:
- Copy
lang/en.json→lang/{locale}.json - Copy
lang/admin/en.json→lang/admin/{locale}.json - Translate all values
- Set
"active_language": "{locale}"and/or"admin_language": "{locale}"insettings.json
Purging the cache
lang_cache_purge('fr'); // Purge one locale in the current context
lang_cache_purge_all(); // Purge every cache file (front + admin, all locales)
The translation editor calls these automatically after every successful save. You only need them when editing JSON files directly on disk.
All code comments and function docblocks must be in English. Only the values inside locale JSON files are translated.
