Подтвердить что ты не робот

Реализация интернационализации (языковые строки) в приложении PHP

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

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

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

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

Update

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

4b9b3361

Ответ 1

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

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

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

Ответ 2

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

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

Когда вы достигаете размера facebook, вы можете работать над реализацией RAM-кэшированных альтернативных методов, подобных тем, которые я упоминал в вопросе. Однако для большинства проектов ничего не происходит "просто, быстро и работает".

Однако есть и другие вещи, которые gettext не может обрабатывать. Такие вещи, как отображение дат, денег и чисел. Для тех, кому нужен INTL extionsion.

/**
 * 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 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 и функции 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));

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

Ответ 3

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

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

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

  • Массив: - Используйте массивы PHP для малых страниц; простейшее использование; только для программистов
  • Csv: - использовать файлы с разделителями-запятыми (.csv/.txt) для формата простого текстового файла; быстро; возможные проблемы с символами Unicode.
  • Gettext: - использовать двоичные файлы gettext (*.mo) для стандарта GNU для linux; потокобезопасный; нужны инструменты для перевода
  • Ini: - Используйте простые файлы INI (*.ini) для формата простого текстового файла; быстро; возможные проблемы с символами Unicode.
  • 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 для разных других адаптеров могут быть реализованы в будущем

Ответ 4

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

Ответ 5

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

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

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

Ответ 6

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

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

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

Ответ 7

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

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

Ответ 8

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

Документацию для компонента можно найти в

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

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

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

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

Ответ 9

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

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

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

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

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

Ответ 10

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

объятие! ​​