Настройка локализации на PHP через index.php: ключевые подходы и примеры
Основы локализации в PHP: выбор механизма для index.php
Как организовать переключение языка сайта через параметр в URL (index.php?lang=ru)?
Наиболее распространённый и простой способ - передавать код языка через GET-параметр lang. Этот метод легко кэшируется, понятен поисковым системам и не требует работы с сессиями. Ниже приведён базовый сценарий для файла index.php.
<?php
// Определяем доступные языки
$available_langs = ['ru', 'en', 'de'];
// Получаем язык из запроса или устанавливаем по умолчанию
$lang = isset($_GET['lang']) && in_array($_GET['lang'], $available_langs)
? $_GET['lang']
: 'ru';
// Подключаем файл с переводами для выбранного языка
$lang_file = __DIR__ . "/lang/{$lang}.php";
if (file_exists($lang_file)) {
$translations = require $lang_file;
} else {
die("Language file not found: " . $lang_file);
}
// Функция для удобного вывода перевода
function __($key) {
global $translations;
return $translations[$key] ?? $key;
}
?>
<!DOCTYPE html>
<html lang="<?= $lang ?>">
<head>
<title><?= __('site_title') ?></title>
</head>
<body>
<h2><?= __('welcome_message') ?></h2>
<nav>
<a href="?lang=ru">Русский</a>
<a href="?lang=en">English</a>
<a href="?lang=de">Deutsch</a>
</nav>
</body>
</html>
Index php lang (настройка локализации в php)
Файл перевода lang/ru.php может выглядеть так:
<?php
return [
'site_title' => 'Мой сайт',
'welcome_message' => 'Добро пожаловать!',
];
Типичная ошибка и её решение
Если файл перевода отсутствует или в нём допущена синтаксическая ошибка, скрипт остановится с помощью die(). Лучше заменить это на graceful fallback - использовать язык по умолчанию и логировать ошибку.
if (!file_exists($lang_file)) {
error_log("Missing language file: $lang_file");
$translations = require __DIR__ . '/lang/ru.php'; // fallback
}
Как хранить выбор языка в сессии, чтобы не терять его между страницами?
Использование сессии позволяет запомнить язык пользователя на всё время его визита без необходимости передавать параметр в каждом URL. Этот подход удобен для административных панелей или одностраничных приложений.
<?php
session_start();
$available_langs = ['ru', 'en'];
// Если в GET передан новый язык, обновляем сессию
if (isset($_GET['lang']) && in_array($_GET['lang'], $available_langs)) {
$_SESSION['lang'] = $_GET['lang'];
}
$lang = $_SESSION['lang'] ?? 'ru';
// Подключаем переводы
$lang_file = __DIR__ . "/lang/{$lang}.php";
if (file_exists($lang_file)) {
$translations = require $lang_file;
} else {
$translations = require __DIR__ . '/lang/ru.php';
}
?>
При таком подходе ссылки на переключение языка будут содержать GET-параметр, который обновляет сессию и затем происходит редирект на ту же страницу без параметра, чтобы URL был чистым. Либо можно использовать форму с POST.
Проблема: если пользователь закроет браузер, сессия потеряется. Для долговременного запоминания языка лучше использовать cookie.
Как реализовать локализацию через cookie без привязки к сессии?
Cookie позволяют хранить выбор языка даже после закрытия браузера. Пример ниже устанавливает cookie на 30 дней.
<?php
$available_langs = ['ru', 'en'];
if (isset($_GET['lang']) && in_array($_GET['lang'], $available_langs)) {
setcookie('lang', $_GET['lang'], time() + 60*60*24*30, '/');
$lang = $_GET['lang'];
} elseif (isset($_COOKIE['lang']) && in_array($_COOKIE['lang'], $available_langs)) {
$lang = $_COOKIE['lang'];
} else {
$lang = 'ru';
}
// Загрузка переводов...
?>
Важно помнить: cookie отправляются с каждым запросом, что может увеличить размер заголовков. Также нужно учитывать настройки безопасности (HttpOnly, Secure).
Какие преимущества даёт использование gettext (MO-файлы) для профессиональной локализации?
Gettext - стандартный инструмент UNIX для интернационализации. Он позволяет отделить переводы от кода, использовать плюральные формы и контексты. В PHP расширение gettext должно быть установлено.
<?php
// Устанавливаем локаль и домен
$locale = 'ru_RU.UTF-8';
putenv("LANG=$locale");
setlocale(LC_ALL, $locale);
bindtextdomain('messages', __DIR__ . '/locales');
textdomain('messages');
// Определяем язык из GET-параметра
$lang = $_GET['lang'] ?? 'ru';
if (in_array($lang, ['ru', 'en'])) {
$locale = ($lang === 'en') ? 'en_US.UTF-8' : 'ru_RU.UTF-8';
putenv("LANG=$locale");
setlocale(LC_ALL, $locale);
}
// Вывод перевода
echo gettext('Hello World');
// или _('Hello World');
?>
Для работы с gettext необходимы файлы .po и скомпилированные .mo. Расположение: locales/ru_RU/LC_MESSAGES/messages.mo.
Распространённая проблема: gettext не работает в некоторых окружениях (например, в Windows без установки локалей). Решение - использовать альтернативную библиотеку-обёртку или перейти на собственные массивы.
Как организовать локализацию с автозагрузкой языковых файлов по заголовкам Accept-Language?
Автоматическое определение языка браузера полезно для первого визита. Алгоритм: проверяем cookie/сессию, если нет - анализируем HTTP-заголовок.
function detect_language() {
$available = ['ru', 'en', 'de'];
if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
$browser_langs = explode(',', $_SERVER['HTTP_ACCEPT_LANGUAGE']);
foreach ($browser_langs as $lang) {
$lang_code = substr($lang, 0, 2);
if (in_array($lang_code, $available)) {
return $lang_code;
}
}
}
return 'ru'; // по умолчанию
}
$lang = $_GET['lang'] ?? $_COOKIE['lang'] ?? $_SESSION['lang'] ?? detect_language();
Выбор подходящего решения
Каждый вариант имеет свою область применения. GET-параметр - для публичных сайтов, важен для SEO. Сессия - для приложений с авторизацией. Cookie - для постоянных пользователей, не требующих регистрации. Gettext - для больших проектов с командами переводчиков. Определение по браузеру - для первого контакта с пользователем.
Расширенные примеры реализации локализации
Пример 1: Использование gettext с плюральными формами и контекстом
Gettext поддерживает правила множественного числа, которые зависят от локали. Для русского языка три формы: 1, 2-4, 5-20. Пример кода с функциями ngettext и pgettext.
<?php
// Установка локали и домена (аналогично базовому примеру)
$locale = 'ru_RU.UTF-8';
putenv("LANG=$locale");
setlocale(LC_ALL, $locale);
bindtextdomain('app', __DIR__ . '/locale');
textdomain('app');
// Вывод с учётом количества (плюральные формы)
$count = 5;
echo ngettext('{n} item', '{n} items', $count);
// В .po файле должно быть: msgid "{n} item" / msgid_plural "{n} items" / msgstr[0] "{n} элемент" / msgstr[1] "{n} элемента" / msgstr[2] "{n} элементов"
// Контекстный перевод (например, "bank" как финансовое учреждение и "bank" как берег реки)
echo pgettext('finance', 'bank');
echo pgettext('geography', 'bank');
?>
5 элементов (в зависимости от .po файла)
Пример 2: Загрузка переводов из базы данных с кэшированием в файл
Для динамических приложений удобно хранить переводы в таблице MySQL. Чтобы не делать запрос при каждом вызове, можно кэшировать результат в PHP-файл или memcached.
<?php
function load_translations_from_db($lang) {
$cache_file = __DIR__ . "/cache/lang_{$lang}.php";
if (file_exists($cache_file) && (time() - filemtime($cache_file) < 3600)) {
return require $cache_file;
}
// Запрос к БД (пример с PDO)
$pdo = new PDO('mysql:host=localhost;dbname=site', 'user', 'pass');
$stmt = $pdo->prepare("SELECT `key`, `value` FROM translations WHERE lang = ?");
$stmt->execute([$lang]);
$translations = $stmt->fetchAll(PDO::FETCH_KEY_PAIR);
// Сохраняем кэш
$content = '<?php return ' . var_export($translations, true) . ';';
file_put_contents($cache_file, $content, LOCK_EX);
return $translations;
}
$lang = $_GET['lang'] ?? 'ru';
$translations = load_translations_from_db($lang);
function __($key) { global $translations; return $translations[$key] ?? $key; }
echo __('welcome');
?>
Добро пожаловать
Пример 3: Сборка строк с подстановками (sprintf) в переводах
Часто нужно вставлять динамические данные в перевод. Нельзя склеивать строки, так как порядок слов в разных языках различается. Используем sprintf с именованными плейсхолдерами (собственная реализация).
// lang/ru.php
return [
'greeting' => 'Привет, %s!',
'items_count' => 'У вас %d новых сообщений.',
];
// index.php
function __($key, ...$args) {
global $translations;
$text = $translations[$key] ?? $key;
return count($args) ? sprintf($text, ...$args) : $text;
}
echo __('greeting', 'Иван');
echo __('items_count', 3);
?>
Привет, Иван! У вас 3 новых сообщений.
Пример 4: Многоуровневый fallback языков (например, en как запасной для de)
Если перевод для выбранного языка не найден, можно использовать английский, а если и его нет - сам ключ.
$lang = 'de';
$available_fallback = ['de' => 'en', 'en' => 'en', 'ru' => 'ru'];
$translations = [];
$fallback_lang = $available_fallback[$lang] ?? 'en';
foreach ([$lang, $fallback_lang] as $try_lang) {
$file = __DIR__ . "/lang/{$try_lang}.php";
if (file_exists($file)) {
$translations = array_merge($translations, require $file);
}
}
function __($key) {
global $translations;
return $translations[$key] ?? $key;
}
?>
Если в de.php нет ключа 'hello', но есть в en.php, то будет выведено 'Hello'.
Пример 5: JavaScript-локализация в связке с PHP (передача переводов на клиент)
Для динамических элементов интерфейса может потребоваться передать массив переводов на JavaScript. Делаем это через JSON.
<?php
$lang = $_GET['lang'] ?? 'ru';
$translations = require __DIR__ . "/lang/{$lang}.php";
?>
<!DOCTYPE html>
<html>
<head>
<script>
var i18n = <?= json_encode($translations, JSON_UNESCAPED_UNICODE) ?>;
function __(key) { return i18n[key] || key; }
console.log(__('js_alert'));
</script>
</head>
<body>
<button onclick="alert(__('click_me'))">Click</button>
</body>
</html>
В консоли будет выведено значение ключа 'js_alert' из языкового файла.