Pull to refresh

gettext: рецепт жаркое из антилопы в Javascript

Reading time 3 min
Views 8.3K

При разработке CMF я столкнулся с необходимостью грамотно реализовать i18n (мультиязычность), и стал рассматривать различные варианты…

Сначала, исходя из прошлого опыта, я хотел сделать «языковые константы» и мытарствах с хаками для числительных. Но потом к счастью остановил свой выбор на GNU gettext, на мощном и популярном (в Unix-среде) инструменте. Вскоре я понял что нет смысла излишне нагружать сервер переводами фраз, которые не индексируются поисковиками, и что в ряде случаев лучше переводить на клиенте. Однако, необходима была унифицированная система, позволяющая делать переводы в едином формате.
Прогуглив gettext javascript, я увидел несколько реализаций.

Первой попалась — code.google.com/p/gettext-js
Плюс в том что она не требует дополнительной переконвертации исходного po-файла, минус — нету ngettext.

Потом я нашел plugins.jquery.com/project/gettext
Её я и решил использовать. Однако, для работы плагина требуется подготовка специального JSON-файла из MO-файла.

Для конвертации приводится функция на Python:
import simplejson as enc
import gettext
def gettext_json(domain, path, lang = [], indent = False):
    try:
        tr = gettext.translation(domain, path, lang)
        # for unknown reasons, instead of having plural entries like 
        # key: [sg, pl1...]
        # tr._catalog has (key, n): pln, 
        keys = tr._catalog.keys()
        keys.sort()
        ret = {}
        for k in keys:
            v = tr._catalog[k]
            if type(k) is tuple:
                if k[0] not in ret:
                    ret[k[0]] = []
                ret[k[0]].append(v)
            else:
                ret[k] = v
        return enc.dumps(ret, ensure_ascii = False, indent = indent)
    except IOError:
        return None


Пришлось потратить… дцать минут на гуглеж и изучение доки, чтоб поправить код и заставить работать. В результате родил нормальную Unix-программу.

gettext2json
#!/usr/bin/python

import sys
import simplejson as enc
import gettext
    
def gettext_json(domain, path, lang = [], indent = False):
    try:
        tr = gettext.translation(domain, path, lang)
        # for unknown reasons, instead of having plural entries like 
        # key: [sg, pl1...]
        # tr._catalog has (key, n): pln, 
        keys = tr._catalog.keys()
        keys.sort()
        ret = {}
        for k in keys:
            v = tr._catalog[k]
            if type(k) is tuple:
                if k[0] not in ret:
                    ret[k[0]] = []
                ret[k[0]].append(v)
            else:
                ret[k] = v
        return enc.dumps(ret, ensure_ascii = True, indent = indent)
    except IOError as (errno, strerror):
			print "I/O error({0}): {1}".format(errno, strerror)	
        
print gettext_json(sys.argv[1],sys.argv[2],[sys.argv[3]], True)


Также, решил автоматизировать процесс создания бинарных MO-файлов из текстовых PO-файлов:

BuildLocales:
#!/usr/bin/php -q
<?php
chdir(__DIR__);
$lcPath = './locale';
$jsPath = './static/locale';

foreach (glob($lcPath.'/*/LC_MESSAGES/*.po') as $poFile) {
	
	$locale = pathinfo(dirname(dirname($poFile)), PATHINFO_FILENAME);
	$domain = pathinfo($poFile, PATHINFO_FILENAME);
	
	$moFile = dirname($poFile).'/'.$domain.'.mo';
	$jsFile = $jsPath.'/'.$locale.'/'.$domain.'.json';
	shell_exec('mkdir -p '.escapeshellarg($jsPath.'/'.$locale));
	shell_exec('msgfmt -o '.escapeshellarg($moFile).' '.escapeshellarg($poFile));
	$cmd = 'gettext2json '.escapeshellarg($domain).' '.escapeshellarg($lcPath ).' '.escapeshellarg($locale).' > '.escapeshellarg($jsFile);
	shell_exec($cmd);
}

Таким образом, для подготовки всех файлов достаточно лишь создать/изменить текстовый .po файлы в папке locale и запустить скрипт BuildLocales.

Для подключения gettext к Javascript необходимо указать атрибут lang у тега html, добавить в head элемент link с путем до json-файла и подгрузить jquery.gettext.js.

Начало HTML-кода страницы будет выглядеть примерно так:

<!DOCTYPE html> 
<html lang="ru"> 
...
<link href="/locale/ru/mydomain.json" lang="ru" rel="gettext"/>
<script type="text/javascript" src="/js/jquery.gettext.js" />
...


Затем можно вызывать функцию _(«Hello world!») и наслаждаться. Упс! Не работает!

Придется кое-что поправить в jquery.gettext.js, накладываем patch:

63,66c63,70
<                                     try {
<                                       var messages = eval('(' + data + ')');
<                                     } catch(e) {
<                                       return;
---
>                                     if (typeof(data) == 'object') {
>                                       var messages = data;
>                                     } else {
>                                       try {
>                                               var messages = eval('(' + data + ')');
>                                       } catch(e) {
>                                               return;
>                                       }



Те кому лень накладывать патч берут jquery.gettext.js.

Надеюсь жаркое вам понравилось, спасибо за внимание.
Tags:
Hubs:
+37
Comments 43
Comments Comments 43

Articles