О насБлогКонтакты
Веб-разработка30 мая 2012 г. 5 мин 20

Мультиязычность в PHP 2012: KG + RU + EN без i18n фреймворков

AunimedaAunimeda
📋 Содержание

Мультиязычность в PHP 2012: KG + RU + EN без i18n фреймворков

В 2012 году правительственный портал, образовательная платформа, или корпоративный сайт в Кыргызстане обязаны были работать на трёх языках: кыргызском (ky), русском (ru) и английском (en). Larevel 4 только вышел, Symfony 2 был молодым. Большинство проектов строилось на CodeIgniter или чистом PHP.

Встроенной поддержки i18n в CodeIgniter не хватало для наших требований. Symfony Translation Component существовал, но использовать только его компонент отдельно было непривычно. Мы построили свою систему — простую, предсказуемую, с SEO-friendly URL.


URL-структура

Первое решение: как язык попадает в URL?

Вариант А: yoursite.kg/ky/about, yoursite.kg/ru/about
Вариант Б: ky.yoursite.kg, ru.yoursite.kg
Вариант В: yoursite.kg/about?lang=ky

Вариант В — плохой: параметр не индексируется отдельно в поисковиках. Вариант Б — хорош для SEO, но усложняет инфраструктуру (три домена, три SSL-сертификата в 2012 году стоили денег). Выбрали Вариант А.

// routes.php — определение языка из первого сегмента URL
class Router {
    private array $supportedLangs = ['ky', 'ru', 'en'];
    private string $defaultLang   = 'ky'; // Кыргызский — основной
    
    public function parse(string $uri): array {
        $segments = explode('/', trim($uri, '/'));
        
        if (!empty($segments[0]) && in_array($segments[0], $this->supportedLangs)) {
            $lang     = array_shift($segments);
            $path     = implode('/', $segments);
        } else {
            // Нет языка в URL — редирект на кыргызскую версию
            $lang = $this->defaultLang;
            $path = $uri;
            header('Location: /' . $lang . $uri, true, 301);
            exit;
        }
        
        return ['lang' => $lang, 'path' => $path];
    }
}

Языковые файлы: простой формат с fallback

// languages/ky.php — кыргызский (основной)
return [
    'site.title'          => 'Кыргызстан Порталы',
    'nav.home'            => 'Башкы бет',
    'nav.about'           => 'Биз жөнүндө',
    'nav.services'        => 'Кызматтар',
    'nav.contact'         => 'Байланыш',
    'footer.rights'       => 'Бардык укуктар корголгон',
    'error.not_found'     => 'Бет табылган жок',
    'form.submit'         => 'Жөнөтүү',
    'form.required'       => 'Милдеттүү талаа',
];

// languages/ru.php — русский (все ключи переведены)
return [
    'site.title'          => 'Портал Кыргызстана',
    'nav.home'            => 'Главная',
    'nav.about'           => 'О нас',
    // ... и т.д.
];

// languages/en.php — английский (может быть неполным — используем fallback)
return [
    'site.title'          => 'Kyrgyzstan Portal',
    'nav.home'            => 'Home',
    // nav.contact ещё не переведён — будет использоваться fallback
];
// Класс перевода с fallback-цепочкой: en → ru → ky
class Translator {
    private array $translations = [];
    private array $fallbackChain;
    
    public function __construct(string $lang) {
        // Fallback: если ключ не найден в en, берём ru, если нет в ru — ky
        $this->fallbackChain = match($lang) {
            'en'    => ['en', 'ru', 'ky'],
            'ru'    => ['ru', 'ky'],
            default => ['ky'],
        };
        
        foreach (array_unique($this->fallbackChain) as $l) {
            $file = __DIR__ . '/languages/' . $l . '.php';
            if (file_exists($file)) {
                $this->translations[$l] = require $file;
            }
        }
    }
    
    public function t(string $key, array $params = []): string {
        foreach ($this->fallbackChain as $lang) {
            if (isset($this->translations[$lang][$key])) {
                $text = $this->translations[$lang][$key];
                // Подстановка параметров: ":name" → значение
                foreach ($params as $k => $v) {
                    $text = str_replace(':' . $k, $v, $text);
                }
                return $text;
            }
        }
        
        // Ключ не найден нигде — вернуть сам ключ (видно разработчику)
        return '{' . $key . '}';
    }
}

// Глобальный хелпер
function __t(string $key, array $params = []): string {
    return app()->translator->t($key, $params);
}

Мультиязычный контент в базе данных

Для контента (статьи, страницы, категории) — отдельные колонки per язык:

-- Простая схема: колонки для каждого языка
CREATE TABLE pages (
    id         INT AUTO_INCREMENT PRIMARY KEY,
    slug       VARCHAR(200) NOT NULL,  -- URL-идентификатор (одинаковый для всех языков)
    
    -- Контент на каждом языке
    title_ky   VARCHAR(500),
    title_ru   VARCHAR(500),
    title_en   VARCHAR(500),
    body_ky    MEDIUMTEXT,
    body_ru    MEDIUMTEXT,
    body_en    MEDIUMTEXT,
    
    -- SEO-мета для каждого языка
    meta_desc_ky VARCHAR(300),
    meta_desc_ru VARCHAR(300),
    meta_desc_en VARCHAR(300),
    
    is_published TINYINT(1) DEFAULT 0,
    created_at   DATETIME
) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_unicode_ci;
// Получение контента с fallback
class Page {
    public function getTitle(string $lang): string {
        // Приоритет: запрошенный язык → русский → кыргызский
        $field = 'title_' . $lang;
        if (!empty($this->$field)) return $this->$field;
        if (!empty($this->title_ru)) return $this->title_ru;
        return $this->title_ky ?? '';
    }
    
    public function getBody(string $lang): string {
        $field = 'body_' . $lang;
        if (!empty($this->$field)) return $this->$field;
        if (!empty($this->body_ru)) return $this->body_ru;
        return $this->body_ky ?? '';
    }
}

SEO: hreflang для поисковиков

Чтобы Google понимал, что три URL — это одна страница на разных языках:

// В шаблоне <head>: hreflang для всех языков
$currentSlug = $page->slug; // Например: 'about-us'
?>
<link rel="alternate" hreflang="ky" href="https://yoursite.kg/ky/<?= $currentSlug ?>">
<link rel="alternate" hreflang="ru" href="https://yoursite.kg/ru/<?= $currentSlug ?>">
<link rel="alternate" hreflang="en" href="https://yoursite.kg/en/<?= $currentSlug ?>">
<!-- x-default: язык по умолчанию для пользователей без подходящего -->
<link rel="alternate" hreflang="x-default" href="https://yoursite.kg/ky/<?= $currentSlug ?>">
<?php

Языковой переключатель

// Компонент переключателя языков
function languageSwitcher(string $currentLang, string $currentSlug): string {
    $langs = ['ky' => 'Кыргызча', 'ru' => 'Русский', 'en' => 'English'];
    $output = '<div class="lang-switcher">';
    
    foreach ($langs as $code => $label) {
        $url    = '/' . $code . '/' . $currentSlug;
        $active = $code === $currentLang ? ' class="active"' : '';
        $output .= "<a href=\"{$url}\"{$active}>{$label}</a> ";
    }
    
    return $output . '</div>';
}

Что изменилось с тех пор

Symfony Translation Component, Laravel Localization, next-intl — сегодня зрелые, документированные решения. gettext + PO-файлы — стандарт для многих CMS.

Но архитектурные решения, которые мы принимали в 2012, остаются актуальными: fallback-цепочка языков, hreflang для SEO, разделение UI-строк и контента. Инструменты изменились. Задачи, которые они решают — нет.

Для сайтов, обслуживающих Кыргызстан в 2025 году, трёхязычность (KY + RU + EN) по-прежнему стандарт для государственных и образовательных проектов. Принципы построения такой системы — те же самые.

Читайте также

Многоязычный сайт для бизнеса в Бишкеке: русский, кыргызский, английский и китайскийaunimeda
Веб-разработка

Многоязычный сайт для бизнеса в Бишкеке: русский, кыргызский, английский и китайский

Как сделать сайт на нескольких языках и не потерять SEO: i18n архитектура, hreflang, переводы и подводные камни. Реальный опыт разработки мультиязычных проектов в Кыргызстане.

Кыргызская локализация в 2013: Unicode, шрифты и клавиатурные раскладки в веб-приложенияхaunimeda
Веб-разработка

Кыргызская локализация в 2013: Unicode, шрифты и клавиатурные раскладки в веб-приложениях

В 2013 году добавить кыргызский язык в веб-приложение означало решить три нетривиальные задачи: корректная работа с символами ң, ү, ө, ж в базе данных, отображение в браузерах без поддержки кыргызских шрифтов, и ввод с устройств без кыргызской раскладки. Вот как мы это решали.

Веб-аналитика для бизнеса в Бишкеке: Google Analytics 4, Яндекс.Метрика и что реально важноaunimeda
Веб-разработка

Веб-аналитика для бизнеса в Бишкеке: Google Analytics 4, Яндекс.Метрика и что реально важно

Как настроить аналитику сайта и принимать решения на основе данных, а не интуиции: GA4, Яндекс.Метрика, Hotjar и реальные метрики для бизнеса в Кыргызстане.

Нужна IT-разработка для вашего бизнеса?

Разрабатываем сайты, мобильные приложения и AI-решения для бизнеса в Кыргызстане. Бесплатная консультация.

Получить консультацию Все статьи