PHP Gettext с пользовательским сканированием файлов для ключей сообщений и фильтром Twig для эха

У меня проблема с получением перевода строки из пользовательских сгенерированных.po и.mo файлов.

Прежде всего, я установил все настройки gettext

$config['text_domain'] = 'wc';
$config['languages'] = array(
    'hr' => array(
        'locale' => 'hr_HR.utf8',
        'date_format' => '',
        'time_format' => '',
        'timezone' => '',
        'lang_name' => 'Croatian'
    ),
    'en' => array(
        'locale' => 'en_EN',
        'date_format' => '',
        'time_format' => '',
        'timezone' => '',
        'lang_name' => 'English'
    ),
    'rs' => array(
        'locale' => 'rs_RS',
        'date_format' => '',
        'time_format' => '',
        'timezone' => '',
        'lang_name' => 'Serbian'
    )
);
$config['current_lang'] = 'hr';

putenv('LC_ALL=' . $config['languages'][$config['current_lang']]['locale']);
setlocale(LC_ALL, $config['languages'][$config['current_lang']]['locale']);
bindtextdomain($config['text_domain'], '/var/www/webcore.lan/public_html/Languages');
bind_textdomain_codeset($config['text_domain'], 'UTF-8'); 
textdomain($config['text_domain']);

Структура каталогов Gettext:

public_html / Языки / hr_HR.utf8 / LC_MESSAGES /

(файлы mo и po хранятся в формате: domain_locale.po & domain_locale.mo), поэтому в этом случае:

wc_hr_HR.utf8.mo

wc_hr_HR_utf8.po

Для рендеринга шаблона я использую Twig2 с пользовательским фильтром

$gettext = new Twig_SimpleFunction('message', function($message) {
    return _($message);
});
$twig->addFunction($gettext);

Итак, мой пример шаблона представления, который использует этот фильтр сообщений:

{{ message("test") }}

И теперь я делаю в CMS функцию, которая сканирует все шаблоны представлений, затем ищет функцию ветки "message" и извлекает ее атрибут (который всегда является ключом сообщения), затем дает пользователю возможность написать свой собственный перевод и затем генерирует файлы.po и.mo,

Это контроллер, который делает это (начало этой операции в методе 'сканирования')

class LanguageController extends Controller
{
    protected $_container;

    protected $_db;

    protected $_twig;

    protected $_config;

    protected $_data = array();

    public function __construct($c) 
    {
        $this->_container = $c;
        $this->_db = $c->get('db_mysqli');
        $this->_twig = $c->get('Twig');
        $this->_config = $c->get('settings');

        $this->_data['page_template'] = 'Languages/index.twig';
        $this->_data['base_url'] = $this->_config['base_url'];

        $this->_data['default_lang'] = $this->_config['current_lang'];
        $this->_data['languages'] = $this->_config['languages'];
    }

    public function index(Request $request, Response $response, $args) 
    {

        $get = $request->getQueryParams();

        $this->_twig->display('layout.twig', $this->_data);
    }

    public function scan(Request $request, Response $response, $args) 
    {
        $base_folder = APP . DIRECTORY_SEPARATOR . APP_DIRECTORY . DIRECTORY_SEPARATOR . APP_VIEW_DIRECTORY;
        $scaned = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($base_folder));

        $messageKeys = array();
        foreach($scaned as $file)
        {
            if($file->isDir()) { 
                continue;
            }

            $file_content = file_get_contents($file);
            $messageKeys = array_unique(array_merge($messageKeys, $this->get_string_between($file_content, 'message("', '")')));
        }
//echo '<pre>' .print_r($messageKeys, true) . '</pre>';

        // Sada idi po svakom jeziku i provjeri dali .po file postoji
        // ukoliko ne postoji kreiraj ga prvi puta sa message keyevima, a za defaultni jezik
        // će nađeni message keyevi ujedno biti i prijevod
        foreach($this->_config['languages'] as $langKey => $lang)
        {
            $poFile = LANG_DIRECTORY . DIRECTORY_SEPARATOR . $lang['locale'] . DIRECTORY_SEPARATOR . 'LC_MESSAGES' . DIRECTORY_SEPARATOR . $lang['locale'] . '.po';
            $moFile = LANG_DIRECTORY . DIRECTORY_SEPARATOR . $lang['locale'] . DIRECTORY_SEPARATOR . 'LC_MESSAGES' . DIRECTORY_SEPARATOR . $lang['locale'] . '.mo';
            if(!file_exists($poFile))
            {
                $po = fopen($poFile, "w");

                $poTemplate =
'msgid ""
msgstr ""
"Language: '.$lang['locale'].'\n"
"Project-Id-Version: '.$this->_config['text_domain'].'\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
"X-Generator: Poedit 1.8.1\n"
"X-Poedit-SourceCharset: UTF-8\n"
"X-Poedit-KeywordsList: __;_e;_n:1,2;_x:1,2c;_ex:1,2c;_nx:4c,1,2;esc_attr__;"
"X-Textdomain-Support: yes\n"
"X-Poedit-SearchPath-0: .\n"
"X-Poedit-SearchPathExcluded-0: *.js\n"
';

                foreach($messageKeys as $key)
                {
                    $quotedKey = '"' . $key . '"';

                    $translation = '"' . $key . '-a-a-a--a-a"';

                    $poTemplate .= '
msgid '.$quotedKey;
                    $poTemplate .= '
msgstr '.($this->_config['current_lang'] == $langKey ? $translation : '""').'
';
                }

                fwrite($po, $poTemplate);
                fclose($po);
                chmod($poFile, 0777);

                $this->phpmo_convert($poFile, $moFile);
            }
        }

        //echo '<pre>' .print_r($messageKeys, true) . '</pre>';
        exit;
    }

    public function get_string_between($string, $start, $end) 
    {
        $last_end = 0;
        $matches = array();
        while (($ini = strpos($string, $start, $last_end)) !== false) 
        {
            $ini += strlen($start);
            $len = strpos($string,$end,$ini) - $ini;
            $matches[] = substr($string, $ini, $len);
            $last_end = $ini + $len + strlen($end);
        }
        return $matches;
    }

    public function phpmo_convert($input, $output = false) {
        if ( !$output )
            $output = str_replace( '.po', '.mo', $input );

        $hash = $this->phpmo_parse_po_file( $input );
        if ( $hash === false ) {
            return false;
        } else {
            $this->phpmo_write_mo_file( $hash, $output );
            return true;
        }
    }

    public function phpmo_clean_helper($x) {
        if (is_array($x)) {
            foreach ($x as $k => $v) {
                $x[$k] = $this->phpmo_clean_helper($v);
            }
        } else {
            if ($x[0] == '"')
                $x = substr($x, 1, -1);
            $x = str_replace("\"\n\"", '', $x);
            $x = str_replace('$', '\\$', $x);
        }
        return $x;
    }

    public function phpmo_parse_po_file($in) {
        $fh = fopen($in, 'r');
        if ($fh === false) {
            return false;
        }

        $hash = array ();
        $temp = array ();
        $state = null;
        $fuzzy = false;

        while(($line = fgets($fh, 65536)) !== false) {
            $line = trim($line);
            if ($line === '')
                continue;
            list ($key, $data) = preg_split('/\s/', $line, 2);

            switch ($key) {
                case '#,' : 
                    $fuzzy = in_array('fuzzy', preg_split('/,\s*/', $data));
                case '#' : 
                case '#.' : 
                case '#:' :
                case '#|' : 
                    if (sizeof($temp) && array_key_exists('msgid', $temp) && array_key_exists('msgstr', $temp)) {
                        if (!$fuzzy)
                            $hash[] = $temp;
                        $temp = array ();
                        $state = null;
                        $fuzzy = false;
                    }
                    break;
                case 'msgctxt' :
                case 'msgid' :
                case 'msgid_plural' :
                    $state = $key;
                    $temp[$state] = $data;
                    break;
                case 'msgstr' :
                    $state = 'msgstr';
                    $temp[$state][] = $data;
                    break;
                default :
                    if (strpos($key, 'msgstr[') !== FALSE) {
                        $state = 'msgstr';
                        $temp[$state][] = $data;
                    } else {
                        switch ($state) {
                            case 'msgctxt' :
                            case 'msgid' :
                            case 'msgid_plural' :
                                $temp[$state] .= "\n" . $line;
                                break;
                            case 'msgstr' :
                                $temp[$state][sizeof($temp[$state]) - 1] .= "\n" . $line;
                                break;
                            default :
                                // parse error
                                fclose($fh);
                                return FALSE;
                        }
                    }
                    break;
            }
        }
        fclose($fh);

        if ($state == 'msgstr')
            $hash[] = $temp;

        $temp = $hash;
        $hash = array ();
        foreach ($temp as $entry) {
            foreach ($entry as & $v) {
                $v = $this->phpmo_clean_helper($v);
                if ($v === FALSE) {
                    return FALSE;
                }
            }
            $hash[$entry['msgid']] = $entry;
        }

        return $hash;
    }

    public function phpmo_write_mo_file($hash, $out) {
        ksort($hash, SORT_STRING);
        $mo = '';
        $offsets = array ();
        $ids = '';
        $strings = '';

        foreach ($hash as $entry) {
            $id = $entry['msgid'];
            if (isset ($entry['msgid_plural']))
                $id .= "\x00" . $entry['msgid_plural'];
            if (array_key_exists('msgctxt', $entry))
                $id = $entry['msgctxt'] . "\x04" . $id;
            $str = implode("\x00", $entry['msgstr']);
            $offsets[] = array (
                strlen($ids
            ), strlen($id), strlen($strings), strlen($str));
            $ids .= $id . "\x00";
            $strings .= $str . "\x00";
        }

        $key_start = 7 * 4 + sizeof($hash) * 4 * 4;
        $value_start = $key_start +strlen($ids);
        $key_offsets = array ();
        $value_offsets = array ();
        foreach ($offsets as $v) {
            list ($o1, $l1, $o2, $l2) = $v;
            $key_offsets[] = $l1;
            $key_offsets[] = $o1 + $key_start;
            $value_offsets[] = $l2;
            $value_offsets[] = $o2 + $value_start;
        }
        $offsets = array_merge($key_offsets, $value_offsets);

        $mo .= pack('Iiiiiii', 0x950412de, 
        0, 
        sizeof($hash),
        7 * 4, 
        7 * 4 + sizeof($hash) * 8,
        0, 
        $key_start 
        );
        foreach ($offsets as $offset)
            $mo .= pack('i', $offset);
        $mo .= $ids;
        $mo .= $strings;
        file_put_contents($out, $mo);
    }
}

Когда мои файлы генерируются, они выглядят примерно так:

Файл wc_hr_HR.utf8.po:

msgid ""
msgstr ""
"Language: hr_HR.utf8\n"
"Project-Id-Version: wc\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
"X-Generator: Poedit 1.8.1\n"
"X-Poedit-SourceCharset: UTF-8\n"
"X-Poedit-KeywordsList: __;_e;_n:1,2;_x:1,2c;_ex:1,2c;_nx:4c,1,2;esc_attr__;"
"X-Textdomain-Support: yes\n"
"X-Poedit-SearchPath-0: .\n"
"X-Poedit-SearchPathExcluded-0: *.js\n"

msgid "test"
msgstr "test-a-a-a--a-a"

А для файла.mo я использую все эти сложные алгоритмы, но я попытался сгенерировать.mo с помощью командной строки, используя

msgfmt hr_HR.utf8.po -o hr_HR.utf8.mo

И я до сих пор не получаю переведенную строку {{ message("test") }}

ОС: Ubuntu 16.04 LTS PHP Версия: 7.1.12 PHP модули:

[PHP Modules]
bcmath
calendar
Core
ctype
curl
date
dom
exif
fileinfo
filter
ftp
gd
gettext
hash
iconv
intl
json
libxml
mbstring
mcrypt
mysqli
mysqlnd
openssl
pcntl
pcre
PDO
pdo_mysql
Phar
posix
readline
Reflection
session
shmop
SimpleXML
soap
sockets
SPL
standard
sysvmsg
sysvsem
sysvshm
tokenizer
wddx
xml
xmlreader
xmlwriter
xsl
Zend OPcache
zlib

[Zend Modules]
Zend OPcache

Локали (основанные на 'local -a':

C
C.UTF-8
en_AG
en_AG.utf8
en_AU.utf8
en_BW.utf8
en_CA.utf8
en_DK.utf8
en_GB.utf8
en_HK.utf8
en_IE.utf8
en_IN
en_IN.utf8
en_NG
en_NG.utf8
en_NZ.utf8
en_PH.utf8
en_SG.utf8
en_US.utf8
en_ZA.utf8
en_ZM
en_ZM.utf8
en_ZW.utf8
hr_HR.utf8
POSIX

Извините за кучу кода и длинный вопрос, я очень устал. Не могу понять проблему. Может кто-нибудь помочь?

Извините за неубедительный английский.

0 ответов

Другие вопросы по тегам