Мультиязычные приложения - energy-coresky/air GitHub Wiki

Быстрый старт

В мультиязычных SKY-приложениях, в первую очередь, необходимо определить язык по умолчанию в конфигурационном файле main/config.yaml:

core:
  define:
    DEFAULT_LG: en # ISO 639-1 code

В общем контроллере main/mvc/common_c.php, установите список допустимых языков в common_c::langs_h(). Позже этот список можно будет изменить:

<?php

class common_c extends Controller
{
    use HOOK_C;

    static $locales = [
        'en' => 'en_US',
        'de' => 'de_DE',
        'da' => 'da_DK',
        'es' => 'es_ES',
        'fr' => 'fr_FR',
        'it' => 'it_IT',
        'no' => 'no_NO',
        'pt' => 'pt_PT',
        'ru' => 'ru_RU',
        'uk' => 'uk_UA',
    ];
. . .
    static function langs_h() {
        global $sky;
        $sky->langs = array_keys(self::$locales);
    }
. . .

Задайте произвольное имя таблицы в БД для SkyLang на вкладке Development в dev-tools. Эта таблица не будет использоваться на production, поэтому, хорошо если имя таблицы будет начаться с подчеркивания. Создайте папку main/lng, где будут храниться переведенные тексты. Откройте SkyLang, сгенерируйте таблицу и пустые файлы перевода, нажав " sync".

Установите выбранный язык в системе, в common_c::head_y($action), примерно так:

self::langs_h();
SKY::lang($user->v_lg);
# и далее по нуждам конкретного приложения, например, для MySQL:
sqlf('set lc_time_names = %s', self::$locales[LG]);

Окружение CoreSky для i18n

  • метод SKY::lang(string $lg, string $page = false) - создает константу LG содержащую выбранный язык и подключает к системе файл(ы) перевода для этого языка. Всегда подключается общий файл, но также, может быть подключен и второй файл, соответствующий $page.

  • файлы перевода хранятся в папке main/lng. Для каждого языка из списка в $sky->langs, SkyLang генерирует файлы в формате: main/lng/XX.php (всегда) и возможно страничные файлы main/lng/XX_YYY.php, где XX - двух символьный код языка, YYY - имя страницы соответствует \w+.

  • таблица в БД для SkyLang (только DEV режим). Источником данных, является эта таблица, но на production используются только сгенерированные из неё файлы.

  • функция t(...) - может использоваться для перевода строк с языка по умолчанию на другой язык. В режиме DEV, также может добавлять новые строки для перевода в таблицу SkyLang. Функция может работать с шаблонами семейства printf и извлекать строки из "output buffer". Примеры:

echo t('sky'); # небо
echo t('total %d items', 8); # всего 8 штук
t(); # в парном применении без параметров используются функции семейства ob_
echo 'heaven';
t(); # небеса
# в парсере Jet есть аналогичный оператор:
@t(also work) {- тоже работает -}
  • SkyLang утилита - инструмент в dev-tools для создания приложений поддерживающих i18n. Генерирует файлы для production и реализует все необходимые действия с данными для перевода

  • Language::names() - возвращает массив из 181 элемента. Ключи - двух буквенные обозначения языков (ISO 639-1), значения - имена на английском

Languages page

Автоматический перевод

Перевод мультиязычных SKY-приложений, можно делать почти автоматически. Программист создаёт приложение обычным образом. Но строки, которые требуется перевести с языка по умолчанию, на другие языки, заключает в функцию t(...). Эта функция добавляет новые строки в таблицу SkyLang. А внешний сервер перевода, подключается к API SkyLang и делает перевод строк в фоновом режиме. Программисту останется только проверить такой перевод открыв утилиту SkyLang.

Инициатором является сервер. Он делает запрос на endpoint http(s)://site-dev-instance/_lang?api методом POST. В теле запроса (и ответе API) всегда передается JSON кодированный массив одной и той же структуры:

# запрос сервера перевода 1:
array (
    'tell' => 'hallo', # сервер: hallo или translate
    'langs' => '?', # строка '?' или массив языков
    'list' => array(), # пустой массив или переведенные строки
    'i' => '', # при "hallo" пусто
    'max' => 2,
)

# ответ API:
array (
    'tell' => 'translate', # API: translate, idle, bye
    'langs' => array('en', 'ru', 'de', 'cn'), # массив языков приложения
    'list' => array( # массив строк для перевода
         100 => 'translate string',
         123 => 'Heaven',
    ),
    'i' => 'ru.*.123', # итерация "язык на который перевести.страница.ID"
    'max' => 2, # что было в запросе, передаем обратно в ответ
)

# запрос сервера перевода 2:
array (
    'tell' => 'translate',
    'langs' => array('en', 'ru', 'cn'), # в арсенале нет немецкого языка
    'list' => array( # массив переведённых строк
         100 => 'переведите строку',
         123 => 'Небеса',
    ),
    'i' => 'ru.*.123', # что было в ответе, передаем обратно в запрос
    'max' => 8, # максимальное кол-во строк увеличено
)

Если режим работы SkyLang не установлен в "API ON", API ответит "idle" и сервер должен сделать большую паузу перед следующим запросом. Такой же ответ даётся, если все переведено на текущий момент. Если API ответил "bye" - сервер перевода бесполезен, последующие запросы не нужны.

В массиве "langs", API всегда передает массив языков приложения и первый в списке (с индексом 0), всегда язык по умолчанию, т.е. тот язык, с которого требуется делать перевод строк на другие языки. Сервер может уменьшить этот список, если не владеет языком из списка. В "i" - первые два символа - язык на который требуется сделать перевод строк.

SkyLang

Если строк для перевода не очень много или быстродействие не критично, проще использовать только одну общую страницу перевода и не использовать страничные файлы, так как их использование может создать дополнительные трудности. Но если создается приложение для Интернет с потенциально возможно высокой нагрузкой, нужно использовать страничные файлы перевода для страниц (или группы страниц приложения), где есть много строк для перевода на отличные языки от языка по умолчанию.

Если код приложения подключает страничные файлы перевода во втором параметре метода SKY::lang($lg, $page);, то новые строки, функцией t(..) будут добавляться именно в эту страницу в таблице SkyLang. Программист разрабатывающий такое приложение должен понимать, что общий файл перевода, содержит строки, использующиеся, в основном, в layout приложения, а страничные файлы перевода, содержат перевод строк в разделах приложения. В SkyLang можно отсортировать строки для перевода всех страниц в одном листинге. Если обнаружится, что одна и та же строка для перевода содержится в разных страничных файлах, то нужно переместить такую строку в общую страницу, вместе со строками перевода layout. Строки содержащиеся в layout, могут случайно попасть в страничный список, и тогда их, нужно вручную переместить на общую страницу перевода.

Кроме использования функции t(...) в приложениях, в SkyLang, можно также определить ключи (KEY) для строк перевода. В этом случае, вместо использовании функции t(...), в приложении необходимо использовать константы с префиксом L_. Например, если для строки перевода "Are you sure want to delete this file?", определен ключ "SURE_DEL", то в приложении, вместо t('Are you sure want to delete this file?'), нужно использовать константу L_SURE_DEL, это укорачивает строковую лексему в приложении.

Функция t(...) в коде, может не добавить строки для перевода в нужное время в таблицу, если её вызов алгоритмически затруднён. В SkyLang есть альтернативный способ - можно активировать парсинг файлов. Новые найденные строки для перевода автоматически добавятся в общую страницу (всегда этим способом). Позже их можно переместить на нужные страницы. Парсинг также выявляет строки для перевода, которые не найдены в файлах приложения, их можно удалить из таблицы. Парсинг проходит все файлы с расширением "php" в плане app, кроме файлов в папке main/lng и все jet файлы в плане view.

Выбор языка для приложений

На веб сайтах или CRM приложениях, выбор языка, может осуществляться, в основном, одним из способов:

  1. Мультиязычное CRM приложение, может быть ориентировано, на то что, во время установки пользователь выбирает один нужный ему язык, а во время работы приложения, язык переключить нельзя. В этом случае, определите константу USE_LG в файле main/config.yaml, с тем расчётом, что установщик перезапишет этот файл с выбранным нужным языком. А в common_c::head_y($action), установите язык: SKY::lang(USE_LG);

  2. Запишите выбранный язык в БД: в $sky->s_lg или $user->v_lg или $user->u_lg. Во время создании новой сессии в таблице visitors, в visitors.vmemo.lg записывается язык пользователя, основанный на $_SERVER['HTTP_ACCEPT_LANGUAGE'] и сопоставленный со списком языков в $sky->langs. Так, что можно, сразу писать: SKY::lang($user->v_lg);.

  3. Нужный язык может определяться в URL: под-доменом или, например, семантической частью URL.

Wikipedia style: в глобальном вики язык и мобильная версия определяются под-доменом:

  • en.m.wikipedia.org - английский, мобильная версия
  • en.wikipedia.org - английский, desktop версия

В этом случае, код должен быть примерно таким:

<?php

class common_c extends Controller
{
    use HOOK_C;

    static $locales = [
        'en' => 'en_US',
        'ru' => 'ru_RU',
    ];

    static function langs_h() {
        global $sky;
        $sky->langs = array_keys(self::$locales);
    }

    static function user_h(&$lg = null) {
        if (!preg_match("/^([a-z]{2}\.)?(m\.)?(.*)$/", DOMAIN, $m))
            throw new Error('User domain match');
        self::langs_h(); # set languages list to system
        global $sky;
        in_array($lg = substr($m[1], 0, 2), $sky->langs) or $lg = DEFAULT_LG;
        $sky->is_mobile = (bool)$m[2];
        return new USER('.' . $m[3]); # give to USER class parsed domain
    }

    function head_y($action) {
        $this->fly_main(); # open DB, make $user & set LG
        . . .
    }

    function fly_main() { # main middleware code sequence
        global $sky, $user;

        $sky->open(); # connect to database
        $user = self::user_h($lg); # make USER instance
        SKY::lang($lg); # set LG in system
        # next string if required layout for mobile version:
        $sky->fly or MVC::$layout = $sky->is_mobile ? 'mobile' : 'desktop';
        # next string if required to set lc_time_names in MySQL:
        sqlf('set lc_time_names = %s', self::$locales[LG]);

        . . .
    }
. . .
}

Для Wikipedia style, нужно пропарсить домен запроса и установить домен для PHP функции setcookie(..), начинающийся с точки. В примере приведенном выше, он должен быть .wikipedia.org, тогда установленные куки, будут высылаться браузером, на все варианты поддоменов.

Хук common_c::user_h(..) вызывается из HOOK_D::j_init(..), а хук common_c::langs_h(..) вызывается из Language::__construct(). Другой код класса common_c, в том числе common_c::head_y($action), из этих "посторонних" по отношению к коду приложения мест, не используется.

Если язык определён в семантической части URL, добавьте rewrite "Url-lang" из библиотеки самым первым. Используйте значение common_c::$lg для установки языка в системе в common_c::head_y. Для j_ TOP-VIEW действий, язык можно сохранять в ghost память сессий или передавать в заголовке HTTP подобным образом как передается CSRF токен:

common_c::langs_h();
if ($cnt && in_array($surl[0], $sky->langs)) {
    common_c::$lg = array_shift($surl);
    $cnt--;
}

Аналогичный подход может быть применён и в случае, если язык определён в query string.

⚠️ **GitHub.com Fallback** ⚠️