Статьи

Управление переводами Gettext на Shared Hosting

Если вы работаете в большой компании, есть вероятность, что рано или поздно ваши работодатели начнут ориентироваться на мировой рынок. С этим стремлением придет необходимость перевести сайт компании на один или несколько языков. Даже если вы не работаете в большой компании, вам может потребоваться запустить новую службу на вашем родном языке (если вы не являетесь носителем английского языка), чтобы ориентироваться на ваш местный рынок, а на английском — для глобального. Как разработчики, наша роль заключается не в переводе текстов, а в подготовке веб-сайта для поддержки переводов. Самый популярный способ сделать это в PHP — через Gettext. Это отличный метод, потому что он позволяет отделить переводы от приложения, обеспечивая параллелизацию процесса. Проблема в том, что Apache кэширует переводы, поэтому, если вы не сможете перезапустить движок, никакого обновления файла перевода не будет видно. Этот факт особенно раздражает, если вы работаете на виртуальном хостинге, где у вас нет прав администратора. В этой статье я подробно опишу эту проблему и объясню решение, которое я нашел, чтобы избежать этого.

Примечание . Если вы не знакомы с концепциями I18N, переводами и Gettext, я настоятельно рекомендую вам прочитать эту серию, прежде чем углубляться в эту статью. Он предоставит вам более подробную информацию, чем краткий обзор, который вы найдете здесь, и поможет вам лучше понять эти темы.

Настройка среды

Существует множество способов использовать переводы в PHP. Самое простое, что я могу вспомнить, — это иметь ассоциативный массив, содержащий все переведенные строки, а затем использовать ключи для получения прав. Как вы можете догадаться, это решение плохо масштабируется и его следует избегать, если вы не работаете над очень маленьким проектом (возможно, с длиной не более 5 строк кода). Для серьезных переводов мы можем использовать Gettext . Этот подход позволяет нам иметь разные файлы для каждого целевого языка, что помогает поддерживать разделение между бизнес-логикой, уровнем представления и переводами (которые мы можем видеть как дополнение уровня представления). С помощью Gettext мы можем распараллелить процесс, потому что, хотя мы работаем над некоторыми функциями веб-сайта, переводчики могут по-прежнему работать над переводами с использованием программного обеспечения, такого как Poedit .

Переводы должны храниться в пути, имеющем фиксированную структуру. Прежде всего, у нас будет корневая папка с именем на ваш вкус (например, «languages»). Внутри него мы должны создать папку для каждого целевого языка, имя которого должно соответствовать стандарту ISO 3166. Итак, допустимыми именами для итальянского перевода могут быть «it_IT» (итальянский из Италии), «it_CH» (итальянский из Швейцарии), «en_US» (английский из США) и так далее. В папке с языковым кодом у нас должна быть папка с именем «LC_MESSAGES», где, наконец, мы будем хранить файлы перевода.

Poedit, анализируя источник веб-сайта, извлекает строки для перевода на основе одного или нескольких шаблонов, которые мы установили в программном обеспечении. Он сохраняет строки в одном файле, имеющем расширение .po (Portable Object) в качестве расширения, которое данное программное обеспечение (или один эквивалент) будет компилировать в двоичный файл .mo. Последний формат интересует нас и функцию PHP gettext() . Файл .mo — это файл, который мы должны поместить в каталог «LC_MESSAGES», который мы создали ранее.

Пример кода, который использует gettext() выглядит следующим образом (код комментируется, чтобы дать вам быстрое представление о том, что он делает):

 <?php // The name of the root folder containing the translation files $translationsPath = 'languages'; // The language into which to translate $language = 'it_IT'; // The name of the translation file (referred as domain in gettext) $domain = 'audero'; // Instructs which language will be used for this session putenv("LANG=" . $language); setlocale(LC_ALL, $language); // Sets the path for the current domain bindtextdomain($domain, $translationsPath); // Specifies the character encoding bind_textdomain_codeset($domain, 'UTF-8'); // Choose domain textdomain($domain); // Call the gettext() function (it has an alias called _()) echo gettext("HELLO_WORLD"); // equivalent to echo _("HELLO_WORLD"); ?> 

Как только вы сохраните предыдущий код на странице и загрузите его в браузере, если gettext() сможет найти файл перевода, вы увидите сделанные вами переводы на экране.

Все идет нормально. Плохая новость заключается в том, что после загрузки перевода Apache его кэширует. Поэтому, если мы не сможем перезапустить движок, никакого обновления файла перевода не будет видно. Это особенно раздражает, если мы работаем на виртуальном хостинге, где у нас нет прав администратора. Как решить эту проблему? Audero Shared Gettext на помощь!

Что такое Audero Shared Gettext

Audero Shared Gettext — это PHP-библиотека (на самом деле это всего лишь один класс, но позвольте мне мечтать), которая позволяет обойти проблему переводов, загружаемых с помощью функции gettext() , которые кэшируются Apache. В библиотеке используется простой, но эффективный прием, благодаря которому у вас всегда будет самый актуальный используемый перевод. Для Audero Shared Gettext требуется PHP версии 5.3 или выше, поскольку он использует пространства имен и наличие структуры, описанной в предыдущем разделе. У него есть два основных метода: updateTranslation() и deleteOldTranslations() . Первый — это ядро ​​библиотеки и метод, который реализует трюк. Но что это за хитрость? Давайте посмотрим его код, чтобы узнать больше. Чтобы полностью понять это, стоит подчеркнуть, что конструктор класса принимает путь, в котором хранятся переводы, язык, на который нужно переводить, и имя файла перевода (домена).

 /** * Create a mirror copy of the translation file * * @return string The name of the created translation file (referred as domain in gettext) * * @throws \Exception If the translation's file cannot be found */ public function updateTranslation() { if (!self::translationExists()) { throw new \Exception('The translation file cannot be found in the given path.'); } $originalTranslationPath = $this->getTranslationPath(); $lastAccess = filemtime($originalTranslationPath); $newTranslationPath = str_replace(self::FILE_EXTENSION, $lastAccess . self::FILE_EXTENSION, $originalTranslationPath); if(!file_exists($newTranslationPath)) { copy($originalTranslationPath, $newTranslationPath); } return $this->domain . $lastAccess; } 

Первое, что делает метод, это проверяет, существует ли оригинальный двоичный файл перевода (файл .mo). Если он не существует, метод генерирует исключение. Затем он вычисляет полный путь к файлу перевода на основе параметров, заданных конструктору, и отметки времени последней модификации файла. После этого он создает новую строку, объединяющую исходный домен с предварительно рассчитанной временной меткой. После того, как сделано, и вот хитрость, он создает зеркальную копию файла перевода. Класс достаточно умен, чтобы избежать этой копии, если файл с таким именем уже существует. Во-первых, он возвращает новое имя, которое мы будем использовать в bindtextdomain() , bind_textdomain_codeset() и textdomain() . При этом Apache будет видеть перевод так, как будто он не имеет отношения к исходному, избегая проблемы с кэшированием. Как я уже сказал, просто, но эффективно!

«Великий Аурелио!», Вы думаете, «но таким образом мои папки будут раздуты этими репликациями». Верно. Вот почему я создал deleteOldTranslations() . Он удаляет все зеркальные копии, кроме последней, из папки выбранного перевода.

Теперь, когда вы знаете, что такое Audero Shared Gettext и что он может сделать для вас, давайте посмотрим, как его получить.

Установка Audero Shared Gettext

Вы можете получить «Audero Shared Gettext» через Composer, добавив следующие строки в ваш
composer.json :

 "require": { "audero/audero-shared-gettext": "1.0.*" } 

А затем запустите команду install чтобы разрешить и загрузить зависимости:

 php composer.phar install 

Composer установит библиотеку в каталог vendor/audero вашего проекта.

Если вы не хотите использовать Composer (вам действительно нужно), вы можете получить Audero Shared Gettext через Git, выполнив команду:

 git clone https://github.com/AurelioDeRosa/Audero-Shared-Gettext.git 

Последний вариант — зайти в хранилище и загрузить его в архив.

Как использовать Audero Shared Gettext

Предполагая, что вы получили Audero Shared Gettext с помощью Composer, вы можете положиться на его автозагрузчик для динамической загрузки класса. Затем вам нужно создать экземпляр SharedGettext и вызвать SharedGettext метод. Вы можете использовать один из ранее процитированных методов, как показано в следующем примере.

 <?php // Include the Composer autoloader require_once 'vendor/autoload.php'; $translationsPath = 'languages'; $language = 'it_IT'; $domain = 'audero'; putenv('LC_ALL=' . $language); setlocale(LC_ALL, $language); try { $sharedGettext = new Audero\SharedGettext\SharedGettext($translationsPath, $language, $domain); // Create the mirror copy of the translation and return the new domain $newDomain = $sharedGettext->updateTranslation(); // Sets the path for the current domain bindtextdomain($newDomain, $translationsPath); // Specifies the character encoding bind_textdomain_codeset($newDomain, 'UTF-8'); // Choose domain textdomain($newDomain); } catch(\Exception $ex) { echo $ex->getMessage(); } ?> 

Выводы

В этой статье вы познакомились с Audero Shared Gettext, простой библиотекой (класс emh…), которая позволяет обойти проблему переводов, загружаемых с помощью функции gettext() , кешируемой Apache. Audero Shared Gettext имеет широкую совместимость, потому что он требует, чтобы у вас был по крайней мере PHP 5.3 (выпущенный на некоторое время) из-за его использования пространств имен. Не стесняйтесь играть с демо-версией и файлами, включенными в репозиторий, отправлять запросы на извлечение и проблемы, если вы их найдете. Я выпустил Audero Shared Gettext под лицензией CC BY-NC 4.0, поэтому его можно использовать бесплатно.

Вы когда-нибудь сталкивались с этой проблемой? Как ты это решил? Не стесняйтесь и оставляйте свои решения в комментариях!