Локализация XSL, PHP и JavaScript из одного XML источника
Изящный способ минимальными средствами сделать полную локализацию веб-приложения.
В проекте WikiCrowd нам понадобилось сделать локализацию. Мы используем XSL для генерации UI, backend написан на PHP и еще есть много JavScript'а. И на всех уровнях нужно выводить локализованный текст.
XSL
С XSL все достаточно просто. Очень хорошее описание локализации XSL есть на сайте Студии Лебедева. Для каждого языка получается отдельный XML файл:
<locale language="English">
<message id="AccessRights" text="New user rights"/>
...
Который подключается к XSL-шаблону:
<xsl:variable name="locale" select="document(concat('locale/', $LOCALE, '.xml'))/locale"/>
Локализованную строку подставляем так:
<xsl:value-of select="$locale//message[@id='Logout']/@text"/>
Его же мы решили использовать для локализации PHP и JavaScript компонент.
PHP
В PHP мы решили сделать из локализованных строк константы в отдельном namespace (чтобы случайно не пересечься) и определили функцию для обращения к ним:
$dom = new DOMDocument();
$dom->load('xml/locale/'.LOCALE.'.xml');
$messages = $dom->getElementsByTagName('message');
for($i = 0; $i < $messages->length; $i++) {
$message = $messages->item($i);
define('locale\\'.
$message->getAttribute('id'), $message->getAttribute('text'));
}
function getMessage($id) {
return str_replace('\\n', "\n", constant('locale\\'.$id));
}
Там, где нужна локализованная строка, используем вызов getMessage('Configure')
.
JavaScript
С JavaScript оказалось все немного сложнее, потому что понадобилось локализовать не только статические строки, но и алгоритм :). Для удобства чтения мы выводим дату в формате "изменение произошло 3 часа 25 минут назад", и простой локализацией "часов" и "минут" оказалось недостаточно, потому что в русском языке алгоритм определения окончания не такой тривиальный, как, например, в английском (1 minute, 2+ minutes).
Так в XML-файле с локализованными строками появились локализованные функции:
<function id="getDaysText" params="days"><![CDATA[return days + " day" + (days > 1 ? "s" : "");]]></function>
Из локализованных строк и функций с помощью XSL генерируется исходник на JavaScript:
<?xml version="1.0" encoding="iso-8859-1" standalone="yes"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:php="http://php.net/xsl"
exclude-result-prefixes="">
<xsl:output
method="text"
version="1.0"
indent="no"
encoding="utf-8"
omit-xml-declaration="yes"
media-type="text/plain"
cdata-section-elements=""/>
<xsl:template match="/">
var Locale = {
<xsl:apply-templates select="//message"/>
<xsl:apply-templates select="//function"/>
getMessage: function(id) {
var text = this[id];
for(var i = 1; i <xsl:text disable-output-escaping="yes"><</xsl:text> arguments.length; i++)
text = text.replace('%' + i, arguments[i]);
return text;
}
};
</xsl:template>
<xsl:template match="message">
<xsl:variable name="ap">'</xsl:variable>
<xsl:value-of select="@id"/>: '<xsl:value-of select="php:functionString('jsStringReplace', @text)" disable-output-escaping="yes"/>',
</xsl:template>
<xsl:template match="function">
<xsl:value-of select="@id"/>: function(<xsl:value-of select="@params"/>) {
<xsl:value-of select="text()" disable-output-escaping="yes"/>
},
</xsl:template>
</xsl:stylesheet>
Как вы могли заметить, мы используем в PHP-расширение, позволяющее вызывать функции PHP из XSL-шаблона. Функция jsStringReplace()
заменяет некоторые символы, чтобы JavaScript-код получится правильным:
function jsStringReplace($str) {
return strtr($str, array('\''=>'\\\'', '<'=>'<', '>'=>'>'));
}
Обращение к локализованной строке выглядит так: Locale.Change
.
До сих пор этот вариант локализации работает отлично.