The LanguageManager class provides JSON-based multi-language support for JiFramework applications. It handles language detection, translation string loading, placeholder substitution, and language switching — all with automatic fallback to the default language when a key is missing.
- Class:
JiFramework\Core\Language\LanguageManager - Access:
$app->language— returnsnullwhenmulti_langisfalse
Features at a glance:
- JSON language files — one file per language in a
lang/directory - Two detection methods: URL parameter or browser cookie
- Automatic fallback to the default language for missing translation keys
{placeholder}token substitution in translation strings- RTL (right-to-left) language support flag per language
- Runtime language switching via
setLanguage()
$app = new App();
$lm = $app->language; // LanguageManager, or null if multi_lang is disabled
// Basic usage
echo $lm->translate('welcome');
echo $lm->translate('hello_name', ['name' => 'Alice']);
// Current language info
echo $lm->getCurrentLanguageCode(); // "en"
echo $lm->isCurrentLanguageRTL(); // false
All localization settings are defined in your jiconfig.php file. The feature is disabled by default and must be opted in explicitly.
// jiconfig.php
return [
'multi_lang' => true, // Enable multi-language support
'multi_lang_method' => 'url', // 'url' or 'cookie'
'multi_lang_default_lang' => 'en', // Default / fallback language code
'multi_lang_key' => 'lang', // GET param name (url) or cookie name (cookie)
// 'multi_lang_dir' => '/path/to/lang/', // Custom lang directory (optional)
];
multi_lang— (bool) Enable or disable the entire localization system. Default:false.multi_lang_method— (string) How the active language is detected.urlreads a GET parameter on every request;cookiereads a browser cookie. Default:url.multi_lang_default_lang— (string) Language code used when no valid language is detected, or as the fallback when a key is missing from the active language file. Default:en.multi_lang_key— (string) Name of the GET parameter (URL method) or cookie (cookie method). Default:lang.multi_lang_dir— (string, optional) Absolute path to the directory containing language JSON files. Defaults to alang/folder at the detected project root.
URL method — the language is determined from ?lang=en on every request. No state is stored server-side. setLanguage() performs a redirect to rebuild the URL with the new parameter.
Cookie method — the language is persisted in a browser cookie for one year. setLanguage() writes the cookie and switches the active language immediately for the rest of the current request.
Each language is a single JSON file named after its language code and placed in the lang/ directory. The file must contain three metadata keys followed by any number of translation string keys.
// lang/en.json
{
"langCode": "en",
"langName": "English",
"isRTL": false,
"welcome": "Welcome",
"hello_name": "Hello, {name}!",
"items_count": "You have {count} items in your cart.",
"page_not_found": "Page not found"
}
// lang/ar.json — RTL language, partial file (missing items_count falls back to English)
{
"langCode": "ar",
"langName": "Arabic",
"isRTL": true,
"welcome": "أهلاً وسهلاً",
"hello_name": "مرحباً، {name}!"
}
Required metadata keys:
langCode— language code matching the filename (e.g.en,ar,bn). Files without this key are ignored by the loader.langName— human-readable language name used in switcher UIs (e.g.English,Arabic). Files without this key are also ignored.isRTL— (bool, optional) set totruefor right-to-left languages such as Arabic, Hebrew, and Urdu. Omitting this key defaults tofalse.
Partial files are allowed. A language file does not need to contain every translation key. Any key missing from the active language automatically falls back to the default language file at runtime. This lets you ship incomplete translations without breaking the application.
translate(string $key, array $placeholders = []): string
Returns the translated string for the given key in the currently active language. If the key does not exist it falls back to the default language. If it cannot be found anywhere the key string itself is returned unchanged — never HTML, never an exception.
$key— (string) The translation key to look up.$placeholders— (array, optional) Key-value pairs to substitute into{token}markers in the translation string.
$lm = $app->language;
// Basic translation
echo $lm->translate('welcome'); // "Welcome"
echo $lm->translate('page_not_found'); // "Page not found"
// Missing key returns the key string (never HTML)
echo $lm->translate('unknown_key'); // "unknown_key"
Placeholders
Use {token} syntax inside translation strings and pass a key-value array as the second argument to substitute them at runtime. Any unmatched {token} in the string is left as-is.
// lang/en.json
{
"hello_name": "Hello, {name}!",
"items_count": "You have {count} items in your cart.",
"welcome_msg": "Hello, {name}! You have {count} messages."
}
// Single placeholder
echo $lm->translate('hello_name', ['name' => 'Alice']);
// "Hello, Alice!"
// Multiple placeholders
echo $lm->translate('welcome_msg', ['name' => 'Bob', 'count' => 5]);
// "Hello, Bob! You have 5 messages."
// Numeric value
echo $lm->translate('items_count', ['count' => 3]);
// "You have 3 items in your cart."
Translation resolution follows a strict three-step cascade. Each step is only reached if the previous one fails to find the key:
- Active language — look up the key in the currently loaded language file.
- Default language — if the key is absent and the active language is not already the default, look it up in the default language file.
- Key string — if the key is not found anywhere, return the key string itself unchanged.
// Active language: Bengali (bn)
// lang/bn.json has "welcome" but not "page_not_found"
// lang/en.json (default) has both
$lm->translate('welcome'); // "স্বাগতম" (from bn.json)
$lm->translate('page_not_found'); // "Page not found" (fallback to en.json)
$lm->translate('totally_missing');// "totally_missing" (not in any file)
This means you can ship partial language files during development or when a translation is incomplete — the application will gracefully degrade to the default language rather than showing errors or blank strings.
The default language file is loaded lazily and cached in memory on first access, so the fallback check adds no extra disk reads after the first miss.
hasTranslation(string $key): bool
Checks whether a translation key can be resolved without actually returning the translated string. Applies the same fallback logic as translate() — returns true if the key exists in either the active language or the default language fallback.
$key— (string) The translation key to check.
$lm = $app->language; // active: Bengali (bn)
$lm->hasTranslation('welcome'); // true (exists in bn.json)
$lm->hasTranslation('page_not_found'); // true (missing in bn, found in en fallback)
$lm->hasTranslation('totally_missing');// false (not in any file)
// Use it to conditionally render optional content
if ($lm->hasTranslation('promo_banner')) {
echo $lm->translate('promo_banner');
}
If hasTranslation() returns true, calling translate() with the same key will always return a real translated string — never the key string itself.
setLanguage(string $lang): bool
Switches the active language at runtime. Returns false if the given language code does not match any available language file. Behaviour differs between the two detection methods.
$lang— (string) Language code to switch to (must match a file in thelang/directory).
URL method — performs an HTTP redirect to the current URL with the language parameter updated, then exits. The function never returns. Call before any output.
// multi_lang_method = 'url'
// Current URL: /products
// After call: /products?lang=fr (redirect)
$app->language->setLanguage('fr');
Cookie method — writes a cookie valid for one year and immediately switches translations in memory for the rest of the current request. Call before output so the cookie header can be sent.
// multi_lang_method = 'cookie'
$result = $app->language->setLanguage('fr');
// $result = true, language is now French for this request
// Cookie written: lang=fr (1 year)
echo $app->language->translate('welcome'); // French translation
// Invalid code
$result = $app->language->setLanguage('zz'); // false, no change
The active language is resolved once during LanguageManager construction. Invalid or missing values silently fall back to the configured default language — unknown language codes never cause an error.
URL method — reads the GET parameter defined by multi_lang_key (default: lang).
// Request: /page?lang=bn → detected: bn
// Request: /page?lang=xx → detected: en (invalid, falls back to default)
// Request: /page → detected: en (no param, default)
The language persists only for the current request. To retain it across page loads you must include the parameter in every link, or call setLanguage() which handles this automatically via redirect.
Cookie method — reads the cookie named by multi_lang_key. Once written via setLanguage() the cookie persists for one year, so the user's preference is remembered across visits.
// Cookie lang=ar → detected: ar
// Cookie lang=xx → detected: en (invalid, falls back to default)
// No cookie → detected: en (default)
getAvailableLanguages(): array
Returns an indexed array of all languages discovered in the lang/ directory. Only JSON files that contain both langCode and langName metadata keys are included.
Each entry in the array is an associative array with the following keys:
langCode— (string) Language code matching the filename (e.g.en,ar).langName— (string) Human-readable language name (e.g.English,Arabic).isRTL— (bool) Whether the language is right-to-left.
$languages = $app->language->getAvailableLanguages();
// [
// ['langCode' => 'ar', 'langName' => 'Arabic', 'isRTL' => true ],
// ['langCode' => 'bn', 'langName' => 'Bengali', 'isRTL' => false],
// ['langCode' => 'en', 'langName' => 'English', 'isRTL' => false],
// ]
foreach ($languages as $lang) {
echo $lang['langCode']; // "en"
echo $lang['langName']; // "English"
echo $lang['isRTL'] ? 'RTL' : 'LTR'; // "LTR"
}
This method is most commonly used to render a language switcher navigation element. See the Building a Language Switcher section for a complete example.
getCurrentLanguageCode(): string
Returns the active language code as a plain string. Use this to set the HTML lang attribute or to check which language is currently active.
$code = $app->language->getCurrentLanguageCode();
// "en" | "bn" | "ar" | ...
echo '<html lang="' . htmlspecialchars($code) . '">';
getCurrentLanguageInfo(): array|null
Returns the full metadata array for the active language — the same structure as entries returned by getAvailableLanguages(). Returns null only in the unlikely case the active code has no matching metadata entry.
langCode— (string) Active language code.langName— (string) Human-readable language name.isRTL— (bool) Whether the active language is right-to-left.
$info = $app->language->getCurrentLanguageInfo();
if ($info) {
echo $info['langCode']; // "ar"
echo $info['langName']; // "Arabic"
var_dump($info['isRTL']); // bool(true)
}
isCurrentLanguageRTL(): bool
Returns true when the active language is right-to-left (Arabic, Hebrew, Urdu, etc.). Returns false for all left-to-right languages or when the isRTL key is absent from the language file.
$lm = $app->language;
$dir = $lm->isCurrentLanguageRTL() ? 'rtl' : 'ltr';
echo '<html lang="' . $lm->getCurrentLanguageCode() . '" dir="' . $dir . '">';
You can also use it to load a dedicated RTL stylesheet:
<link rel="stylesheet" href="/css/app.css">
<?php if ($app->language->isCurrentLanguageRTL()): ?>
<link rel="stylesheet" href="/css/rtl.css">
<?php endif; ?>
Enable RTL for a language by setting isRTL to true in its JSON file:
// lang/ar.json
{
"langCode": "ar",
"langName": "Arabic",
"isRTL": true,
...
}
A complete example combining getAvailableLanguages(), getCurrentLanguageCode(), and setLanguage() to build a language switcher UI.
URL method — render links that append ?lang=xx to the current path. No form or session needed.
<?php
$lm = $app->language;
$current = $lm->getCurrentLanguageCode();
$path = strtok($_SERVER['REQUEST_URI'], '?');
?>
<nav>
<?php foreach ($lm->getAvailableLanguages() as $lang): ?>
<a href="<?= htmlspecialchars($path) ?>?lang=<?= $lang['langCode'] ?>"
<?= $lang['langCode'] === $current ? 'class="active"' : '' ?>>
<?= htmlspecialchars($lang['langName']) ?>
</a>
<?php endforeach; ?>
</nav>
Cookie method — use a POST form. Handle the switch before any output so the cookie header can be written.
<?php
// At the very top of the page, before any output:
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['lang'])) {
$app->language->setLanguage($_POST['lang']);
header('Location: ' . $_SERVER['REQUEST_URI']);
exit;
}
?>
<form method="post">
<?php foreach ($app->language->getAvailableLanguages() as $lang): ?>
<button type="submit" name="lang" value="<?= $lang['langCode'] ?>">
<?= htmlspecialchars($lang['langName']) ?>
</button>
<?php endforeach; ?>
</form>
Always set lang and dir on the <html> tag for correct browser rendering and screen reader support:
<html lang="<?= $app->language->getCurrentLanguageCode() ?>"
dir="<?= $app->language->isCurrentLanguageRTL() ? 'rtl' : 'ltr' ?>">