Я хочу создать CMS, который может обрабатывать выборку строк локали для поддержки интернационализации. Я планирую хранить строки в базе данных, а затем класть кеш ключа / значения, такой как memcache между базой данных и приложением, чтобы предотвратить падение производительности для попадания в базу данных каждой страницы для перевода.
Это сложнее, чем использование файлов PHP с массивами строк, но этот метод невероятно неэффективен, когда у вас 2000 строк перевода.
Я думал об использовании gettext , но я не уверен, что пользователям CMS будет удобно работать с файлами gettext. Если строки хранятся в базе данных, тогда может быть настроена хорошая система администрирования, позволяющая им вносить изменения, когда захочет, а кеширование в ОЗУ гарантирует, что выборка этих строк будет такой же быстрой или быстрой, как gettext. Я также не чувствую себя в безопасности, используя расширение PHP, учитывая, что даже среда Zend не использует его .
Что-то не так с этим подходом?
Я подумал, что, возможно, я бы добавил больше пищи для размышлений. Одна из проблем с строковыми переводами заключается в том, что они не поддерживают даты, деньги или условные утверждения. Тем не менее, благодаря встроенному PHP теперь есть MessageFormatter, что действительно нужно использовать в любом случае.
// Load string from gettext file $string = _("{0} resulted in {1,choice,0#no errors|1#single error|1<{1, number} errors}"); // Format using the current locale msgfmt_format_message(setlocale(LC_ALL, 0), $string, array('Update', 3));
С другой стороны, одна из вещей, которые мне не нравятся в gettext, заключается в том, что текст встроен в приложение по всему месту. Это означает, что команда, ответственная за первичный перевод (обычно английский), должна иметь доступ к исходному коду проекта, чтобы внести изменения во все места, в которые помещаются заявления по умолчанию. Это почти так же плохо, как приложения с SQL-спагетти-кодом.
Таким образом, имеет смысл использовать такие ключи, как _('error.404_not_found')
которые затем позволяют писателям и переводчикам контента просто беспокоиться о файлах PO / MO, не вступая в код.
Однако, если трансляция gettext не существует для данного ключа, тогда нет способа вернуться к умолчанию (например, с помощью специального обработчика). Это означает, что у вас либо есть скрипт, сбрасывающий в вашем коде, либо показывающий «error.404_not_found» для пользователей, у которых нет языкового перевода!
Кроме того, мне неизвестны какие-либо крупные проекты, в которых используется gettext PHP. Я был бы признателен за любые ссылки на хорошо используемые (и, следовательно, проверенные) системы, которые фактически полагаются на собственное PHP gettext-расширение.
Gettext использует двоичный протокол, который довольно быстр. Также реализация gettext обычно проще, поскольку требует только echo _('Text to translate');
, Он также имеет существующие инструменты для использования переводчиками, и они доказали свою эффективность.
Вы можете хранить их в базе данных, но я чувствую, что это будет медленнее и немного переборщить, тем более, что вам придется создавать систему для самостоятельного редактирования переводов.
Если бы вы могли фактически кэшировать поиск в выделенной части памяти в APC, вы были бы золотыми. К сожалению, я не знаю, как.
Для тех, кто заинтересован, кажется, что полная поддержка локалей и i18n в PHP, наконец, начинается.
// Set the current locale to the one the user agent wants $locale = Locale::acceptFromHttp(getenv('HTTP_ACCEPT_LANGUAGE')); // Default Locale Locale::setDefault($locale); setlocale(LC_ALL, $locale . '.UTF-8'); // Default timezone of server date_default_timezone_set('UTC'); // iconv encoding iconv_set_encoding("internal_encoding", "UTF-8"); // multibyte encoding mb_internal_encoding('UTF-8');
Есть несколько вещей, которые необходимо сжать и определить часовой пояс / локаль, а затем использовать его для правильного анализа и отображения ввода и вывода. Существует библиотека PHP I18N, которая была только что выпущена, которая содержит таблицы поиска для большей части этой информации.
Обработка Вход пользователя важен, чтобы убедиться, что у приложения есть чистые, хорошо сформированные строки UTF-8 из любого входа, который вводит пользователь. iconv отлично подходит для этого.
/** * Convert a string from one encoding to another encoding * and remove invalid bytes sequences. * * @param string $string to convert * @param string $to encoding you want the string in * @param string $from encoding that string is in * @return string */ function encode($string, $to = 'UTF-8', $from = 'UTF-8') { // ASCII is already valid UTF-8 if($to == 'UTF-8' AND is_ascii($string)) { return $string; } // Convert the string return @iconv($from, $to . '//TRANSLIT//IGNORE', $string); } /** * Tests whether a string contains only 7bit ASCII characters. * * @param string $string to check * @return bool */ function is_ascii($string) { return ! preg_match('/[^\x00-\x7F]/S', $string); }
Затем просто запустите вход через эти функции.
$utf8_string = normalizer_normalize(encode($_POST['text']), Normalizer::FORM_C);
Как сказал Андре, кажется, что gettext – это умный выбор по умолчанию для написания приложений, которые можно перевести.
_('Text to translate')
Когда вы достигаете размера facebook, вы можете работать над реализацией RAM-кэшированных альтернативных методов, подобных тем, которые я упоминал в вопросе. Однако для большинства проектов ничего не происходит «просто, быстро и работает».
Однако есть и другие вещи, которые gettext не может обрабатывать. Такие вещи, как отображение дат, денег и чисел. Для тех, кому нужно расширение INTL .
/** * Return an IntlDateFormatter object using the current system locale * * @param string $locale string * @param integer $datetype IntlDateFormatter constant * @param integer $timetype IntlDateFormatter constant * @param string $timezone Time zone ID, default is system default * @return IntlDateFormatter */ function __date($locale = NULL, $datetype = IntlDateFormatter::MEDIUM, $timetype = IntlDateFormatter::SHORT, $timezone = NULL) { return new IntlDateFormatter($locale ?: setlocale(LC_ALL, 0), $datetype, $timetype, $timezone); } $now = new DateTime(); print __date()->format($now); $time = __date()->parse($string);
Кроме того, вы можете использовать strftime для анализа дат с учетом текущей локали.
Иногда вам нужны значения для чисел и дат, вставленных правильно в локальные сообщения
/** * Format the given string using the current system locale * Basically, it's sprintf on i18n steroids. * * @param string $string to parse * @param array $params to insert * @return string */ function __($string, array $params = NULL) { return msgfmt_format_message(setlocale(LC_ALL, 0), $string, $params); } // Multiple choices (can also just use ngettext) print __(_("{1,choice,0#no errors|1#single error|1<{1, number} errors}"), array(4)); // Show time in the correct way print __(_("It is now {0,time,medium}), time());
Для получения дополнительной информации см. Подробности формата ICU .
Убедитесь, что ваше соединение с базой данных использует правильную кодировку, так что на хранение ничего не происходит.
Вам нужно понять разницу между функциями string , mb_string и grapheme .
// 'LATIN SMALL LETTER A WITH RING ABOVE' (U+00E5) normalization form "D" $char_a_ring_nfd = "a\xCC\x8A"; var_dump(grapheme_strlen($char_a_ring_nfd)); var_dump(mb_strlen($char_a_ring_nfd)); var_dump(strlen($char_a_ring_nfd)); // 'LATIN CAPITAL LETTER A WITH RING ABOVE' (U+00C5) $char_A_ring = "\xC3\x85"; var_dump(grapheme_strlen($char_A_ring)); var_dump(mb_strlen($char_A_ring)); var_dump(strlen($char_A_ring));
Функции IDN из библиотеки INTL являются большой помощью для обработки имен доменов, отличных от ascii.
Существует ряд других вопросов и ответов SO, похожих на этот. Я предлагаю вам искать и читать их.
Совет? Используйте существующее решение, такое как gettext или xliff, так как это сэкономит вам много горя, когда вы нажмете на все случаи крафтинга, такие как текст справа налево, форматы дат, разные тома текста, французский на 30% больше, чем английский, например, что винт форматирование и т. д. Даже лучший совет. Не делайте этого. Если пользователи хотят перевести, они сделают клон и переведут его. Поскольку локализация – это больше о внешнем виде и использовании разговорного языка, это обычно происходит. Опять давая и пример, англо-саксонская культура любит прохладные цвета в Интернете и лица с сан-сериной. Латиноамериканская культура, как яркие цвета и серифические / курсивные типы. Для обслуживания вас потребуются разные макеты на каждый язык.
Zend действительно обслуживает следующие адаптеры для Zend_Translate, и это полезный список.
Я использую материал ICU в своих рамках и действительно считаю его простым и полезным в использовании. Моя система основана на XML с запросами XPath, а не с базой данных, которую вы предлагаете использовать. Я не считаю этот подход неэффективным. Я играл с Resource bundles также при исследовании методов, но нашел их довольно сложными для реализации.
Функциональность Locale – это послание Бога. Вы можете сделать это гораздо проще:
// Available translations $languages = array('en', 'fr', 'de'); // The language the user wants $preference = (isset($_COOKIE['lang'])) ? $_COOKIE['lang'] : ((isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) ? Locale::acceptFromHttp($_SERVER['HTTP_ACCEPT_LANGUAGE']) : ''); // Match preferred language to those available, defaulting to generic English $locale = Locale::lookup($languages, $preference, false, 'en'); // Construct path to dictionary file $file = $dir . '/' . $locale . '.xsl'; // Check that dictionary file is readable if (!file_exists($file) || !is_readable($file)) { throw new RuntimeException('Dictionary could not be loaded'); } // Load and return dictionary file $dictionary = simplexml_load_file($file);
Затем я выполняю поиск слов, используя следующий метод:
$selector = '/i18n/text[@label="' . $word . '"]'; $result = $dictionary->xpath($selector); $text = array_shift($result); if ($formatted && isset($text)) { return new MessageFormatter($locale, $text); }
Бонус для моей системы заключается в том, что система шаблонов основана на XSL, что означает, что я могу использовать одни и те же XML-файлы перевода непосредственно в своих шаблонах для простых сообщений, которые не нуждаются в форматировании i18n.
Stick с gettext, вы не найдете более быструю альтернативу в PHP.
Что касается способа , вы можете использовать базу данных для хранения своего каталога и разрешить другим пользователям переводить строки с помощью дружественного gui. Когда новые изменения будут рассмотрены / одобрены, нажмите кнопку, скомпилируйте новый файл .mo
и разверните.
Некоторые ресурсы для вас:
Как насчет csv-файлов (которые можно легко редактировать во многих приложениях) и кэширования в memcache (wincache и т. Д.)? Этот подход хорошо работает в пурпуре. Все языковые фразы в коде завернуты в функцию __()
, например
<?php echo $this->__('Some text') ?>
Затем, например, перед выпуском новой версии, вы запускаете простой скрипт, который анализирует исходные файлы, находит весь текст, заключенный в __()
и помещается в CSV-файл. Вы загружаете файлы csv и кэшируете их в memcache. В функции __()
вы просматриваете свой memcache, где переводы кэшируются.
В недавнем проекте мы рассмотрели использование gettext, но оказалось, что проще просто написать нашу собственную функциональность. Это очень просто: создайте файл JSON для каждой локали (например, strings.en.json, strings.es.json и т. Д.) И создайте функцию где-то, называемую «translate ()» или что-то еще, а затем просто вызовите это. Эта функция определит текущую локаль (из URI или сеанса var или что-то еще) и вернет локализованную строку.
Единственное, что нужно помнить, – убедиться, что любой HTML, который вы выводите, закодирован в UTF-8 и помечен как таковой в разметке (например, в doctype и т. Д.),
Возможно, это не ответ на ваш вопрос, но, может быть, вы можете получить некоторые идеи из компонента перевода Symfony? Мне это очень хорошо, хотя я должен признаться, что еще не использовал его.
Документацию для компонента можно найти по адресу
http://symfony.com/doc/current/book/translation.html
и код для компонента можно найти по адресу
https://github.com/symfony/Translation .
Это должно быть легко использовать компонент Translation, поскольку компоненты Symfony предназначены для использования в качестве автономных компонентов.
С другой стороны, одна из вещей, которые мне не нравятся в gettext, заключается в том, что текст встроен в приложение по всему месту. Это означает, что команда, ответственная за первичный перевод (обычно английский), должна иметь доступ к исходному коду проекта, чтобы внести изменения во все места, в которые помещаются заявления по умолчанию. Это почти так же плохо, как приложения с SQL-спагетти-кодом.
На самом деле это не так. У вас может быть файл заголовка (извините, бывший программист C), например:
<?php define(MSG_404_NOT_FOUND, 'error.404_not_found') ?>
Затем, когда вы хотите получить сообщение, используйте _(MSG_404_NOT_FOUND)
. Это намного более гибко, чем требовать от разработчиков помнить точный синтаксис нелокализованного сообщения каждый раз, когда они хотят выплюнуть локализованную версию.
Вы можете сделать еще один шаг и сгенерировать файл заголовка на этапе сборки, возможно, из CSV или базы данных, а также перекрестные ссылки с переводом для обнаружения отсутствующих строк.
есть плагин zend, который работает очень хорошо для этого.
<?php /** dependencies **/ require 'Zend/Loader/Autoloader.php'; require 'Zag/Filter/CharConvert.php'; Zend_Loader_Autoloader::getInstance()->setFallbackAutoloader(true); //filter $filter = new Zag_Filter_CharConvert(array( 'replaceWhiteSpace' => '-', 'locale' => 'en_US', 'charset'=> 'UTF-8' )); echo $filter->filter('ééé ááá 90');//eee-aaa-90 echo $filter->filter('óóó 10aáééé');//ooo-10aaeee
если вы не хотите использовать zend framework, можете использовать только плагин.
объятие!