Реализация интернационализации (языковые строки) в приложении PHP - PullRequest
19 голосов
/ 19 октября 2011

Я хочу создать CMS, которая может обрабатывать выборки строк локали для поддержки интернационализации.Я планирую хранить строки в базе данных, а затем помещать кеш ключа / значения, такой как memcache, между базой данных и приложением, чтобы предотвратить падение производительности при попадании в базу данных на каждой странице для перевода.

Это большесложнее, чем использование PHP-файлов с массивами строк - но этот метод невероятно неэффективен, когда у вас 2000 строк перевода.

Я думал об использовании gettext , но я не уверен, что пользователиCMS будет удобно работать с файлами gettext.Если строки хранятся в базе данных, то можно настроить хорошую систему администрирования, чтобы они могли вносить изменения в любое время, а кэширование в ОЗУ гарантирует, что выборка этих строк будет такой же быстрой или более быстрой, чем gettext.Я также не чувствую себя в безопасности, используя расширение PHP, учитывая, что , даже Zend Framework не использует его .

Что-то не так с этим подходом?

Обновление

Я подумал, что, возможно, я добавлю больше пищи для размышлений.Одна из проблем перевода строк заключается в том, что они не поддерживают даты, деньги или условные выражения.Тем не менее, благодаря intl 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» пользователям, у которых нет перевода локали!

Кроме того, я не знаю ни о каких крупных проектахкоторые используют PHP gettext.Буду признателен за любые ссылки на хорошо используемые (и поэтому проверенные) системы, которые на самом деле полагаются на собственное расширение gettext для PHP.

Ответы [ 10 ]

6 голосов
/ 19 октября 2011

Gettext использует бинарный протокол, который довольно быстрый. Также реализация gettext обычно проще, так как требует только echo _('Text to translate');. Он также имеет существующие инструменты для использования переводчиками, и они доказали свою эффективность.

Вы можете хранить их в базе данных, но я чувствую, что это будет медленнее и немного излишним, тем более что вам придется создавать систему для самостоятельного редактирования переводов.

Если бы вы только могли на самом деле кэшировать поиски в выделенной части памяти в APC, вы были бы прекрасны. К сожалению, я не знаю как.

5 голосов
/ 26 октября 2011

Для тех, кому это интересно, кажется, что полная поддержка locales и 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 - это умный выбор по умолчанию для написания приложений, которые можно переводить.

  1. Gettext использует довольно быстрый бинарный протокол.
  2. Реализация gettext обычно проще, так как требует только _('Text to translate')
  3. Существующие инструменты для использования переводчиками, и они доказали свою эффективность.

Когда вы достигнете размера фейсбука, вы сможете работать над реализацией кэшированных в ОЗУ альтернативных методов, подобных тому, который я упоминал в этом вопросе. Однако ничто не сравнится с «простым, быстрым и работающим» для большинства проектов.

Однако есть и дополнительные вещи, которые 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 .

База данных

Убедитесь, что ваше соединение с базой данных использует правильную кодировку, чтобы при хранении ничего не прерывалось.

Строковые функции

Вам необходимо понять разницу между строкой , mb_string и функциями графемы .

// '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));

Доменное имя TLD's

Функции IDN из библиотеки INTL - большая помощь при обработке доменных имен, отличных от ascii.

3 голосов
/ 28 октября 2011

Я использую вещи ICU в моей структуре и действительно нахожу это простым и полезным для использования.Моя система основана на XML с запросами XPath, а не база данных, как вы предлагаете использовать.Я не нашел этот подход неэффективным.Я также играл с пакетами ресурсов при исследовании методов, но нашел их довольно сложными для реализации.

Функциональность 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.

3 голосов
/ 22 октября 2011

Существует ряд других SO вопросов и ответов, похожих на этот. Я предлагаю вам поискать и прочитать их.

Совет? Используйте существующее решение, такое как gettext или xliff, так как оно избавит вас от печали, когда вы столкнетесь со всеми крайними случаями перевода, такими как текст справа налево, форматы даты, различные объемы текста, французский на 30% более многословен, чем английский, например, этот винт форматирование и т.д. Еще лучший совет Не делай этого. Если пользователи захотят перевести, они сделают клон и переведут его. Поскольку локализация - это больше внешний вид и использование разговорного языка, обычно так и происходит. Снова приводя и пример англосаксонской культуре нравятся прохладные веб-цвета и лица типа сан-засечек. Латиноамериканская культура любит яркие цвета и шрифт с засечками. Для того, чтобы обслуживать вас, нужны разные макеты для каждого языка.

Zend на самом деле обслуживает следующие адаптеры для Zend_Translate, и это полезный список.

  • Массив: - используйте массивы PHP для небольших страниц; простейшее использование; только для программистов
  • Csv: - Использовать разделенные запятыми ( .csv / .txt) файлы для простого текстового формата файла; быстро; возможные проблемы с символами Юникода
  • Gettext: - Используйте двоичные файлы gettext (* .mo) для стандарта GNU для linux; потокобезопасный; нужны инструменты для перевода
  • Ini: - использовать простые файлы INI (* .ini) для простого текстового формата; быстро; возможные проблемы с символами юникода
  • Tbx: - Использовать обмен терминологической базой ( .tbx / .xml) для отраслевого стандарта для строк терминологии между приложениями; Формат XML
  • Tmx: - Используйте файлы tmx ( .tmx / .xml) для отраслевого стандарта для межприкладного перевода; Формат XML; человек читаемый
  • Qt: - Использовать файлы qt linguist (* .ts) для кроссплатформенной среды приложения; Формат XML; человек читаемый
  • Xliff: - Используйте файлы xliff ( .xliff / .xml) для более простого формата TMX, но связанного с ним; Формат XML; человек читаемый
  • XmlTm: - Используйте файлы xmltm (* .xml) для промышленного стандарта памяти переводов документов XML; Формат XML; человек читаемый
  • Другое: - * .sql для различных других адаптеров может быть реализовано в будущем
1 голос
/ 22 октября 2011

Как насчет файлов CSV (которые можно легко редактировать во многих приложениях) и кэширования в memcache (wincache и т. Д.)?Этот подход хорошо работает в magento.Все языковые фразы в коде обернуты в функцию __(), например

<?php echo $this->__('Some text') ?>

Затем, например, перед выпуском новой версии, вы запускаете простой скрипт, который анализирует исходные файлы, находит весь текст, обернутый в __() и помещает в файл .csv.Вы загружаете CSV-файлы и кэшируете их в memcache.В функции __() вы просматриваете свою кэш-память, где кешируются переводы.

1 голос
/ 22 октября 2011

Придерживайтесь gettext, вы не найдете более быстрой альтернативы в PHP.

Что касается how , вы можете использовать базу данных для хранения вашего каталога и позволить другим пользователям переводитьСтроки с использованием дружественного интерфейса.Когда новые изменения будут рассмотрены / одобрены, нажмите кнопку, скомпилируйте новый файл .mo и разверните.

Некоторые ресурсы, которые помогут вам в работе:

0 голосов
/ 28 октября 2011

имеет плагин 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, можете использовать только плагин.

hug!

0 голосов
/ 27 октября 2011

С другой стороны, в gettext мне не нравится то, что текст встроен в приложение повсюду. Тот означает, что команда, ответственная за первичный перевод (обычно Английский) должен иметь доступ к исходному коду проекта для внесения изменений во всех местах размещены операторы по умолчанию. Это почти как плохо, как приложения, которые имеют SQL-спагетти-код во всем.

На самом деле это не так. Вы можете иметь файл заголовка (извините, бывший программист C), например:

<?php
define(MSG_404_NOT_FOUND, 'error.404_not_found')
?>

Тогда всякий раз, когда вы хотите сообщение, используйте _(MSG_404_NOT_FOUND). Это гораздо более гибко, чем требовать от разработчиков запоминать точный синтаксис нелокализованного сообщения каждый раз, когда они хотят выплюнуть локализованную версию.

Вы можете пойти еще дальше и сгенерировать заголовочный файл на этапе сборки, может быть, из CSV или базы данных, и получить перекрестную ссылку с переводом для обнаружения пропущенных строк.

0 голосов
/ 26 октября 2011

Возможно, на самом деле не ответ на ваш вопрос, но, может быть, вы можете получить некоторые идеи из компонента перевода Symfony? Это выглядит очень хорошо для меня, хотя я должен признаться, что сам еще не использовал его.

Документацию по компоненту можно найти по адресу

http://symfony.com/doc/current/book/translation.html

и код для компонента можно найти на

https://github.com/symfony/Translation.

Компонент Translation должен быть простым, поскольку компоненты Symfony предназначены для использования в качестве автономных компонентов.

0 голосов
/ 19 октября 2011

В недавнем проекте мы рассматривали возможность использования gettext, но оказалось, что проще написать нашу собственную функциональность. Это действительно очень просто: создайте JSON-файл для каждой локали (например, strings.en.json, strings.es.json и т. Д.) И создайте функцию где-нибудь с именем «translate ()» или что-то подобное, а затем просто вызовите это. Эта функция будет определять текущую локаль (из URI или сеанса var или чего-то еще) и возвращать локализованную строку.

Единственное, что нужно помнить, это убедиться, что любой выводимый вами HTML кодируется в UTF-8 и помечается как таковой в разметке (например, в типе документа и т. Д.)

...