diff --git a/.gitignore b/.gitignore
index 28f91f1..6162761 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,3 +5,4 @@
node_modules
benchmark
spec/tmp
+reports
diff --git a/Gruntfile.js b/Gruntfile.js
index d80f2f1..6c784dc 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -115,7 +115,7 @@ module.exports = function (grunt) {
grunt.util.spawn({
cmd: cmd,
- args: [options.specs],
+ args: [options.specs, '--junitreport'],
opts: {
env: grunt.util._.extend(
{}, process.env, { FEST_COMPILE: JSON.stringify(options.compile) }
diff --git a/README.md b/README.md
index 7ade75d..ce5ad2c 100644
--- a/README.md
+++ b/README.md
@@ -524,56 +524,22 @@ person.xml:
```
## Интернационализация
-
-### fest:plural
-
-По умолчанию доступна поддержка плюрализации для русского и английского языка. В параметрах `fest.compile` можно передать любую другую функцию плюрализации.
-
-```xml
-один рубль|%s рубля|%s рублей
-```
-Или англоязычный вариант:
-
-```xml
-one ruble|%s rubles
-```
-
-Чтобы вывести символ “%” внутри тега `fest:plural` используйте “%%”:
-
-```xml
-…1%%…|…%s%%…|…%s%%…
-```
-
-### fest:message и fest:msg
-
-Позволяет указать границы фразы для перевода и контекст для снятия многозначности. Например,
+Для работы интернационализации в `fest` добавлен новый неймспейс i18n. В нем определн
+тег msg, пример:
```xml
-Лук
-Лук
-```
-
-Для каждого `fest:message`, `fest:msg`, обычного текста, заключенного между XML тегами (опция `auto_message`), или текстового значения некоторых атрибутов компилятор вызывает функцию `events.message` (если такая была указана в параметрах). Данный механизм используется в `fest-build` утилите для построения оригинального PO-файла.
-
-Пример вызова `fest-build` для создания PO-файла:
-
-```
-$ fest-build --dir=fest --po=ru_RU.po --compile.auto_message=true
-```
-
-Пример компиляции локализованных шаблонов:
+
+
+ Замок {name}
+
+ {
+ text: Строка
+ }
+
-```
-$ fest-build --dir=fest --translate=en_US.po
+
```
-Пример компиляции одного шаблона:
-
-```
-$ fest-compile path/to/template.xml
-$ fest-compile --out=path/to/compiled.js path/to/template.xml
-$ fest-compile --out=path/to/compiled.js --translate=path/to/en_US.po path/to/template.xml
-```
## Contribution
@@ -594,4 +560,4 @@ Grunt используется для валидации JS (тестов) и з
```
$ ./bin/fest-build --dir=spec/templates --exclude='*error*' --compile.beautify=true --out=spec/expected/build/initial
$ ./bin/fest-build --dir=spec/templates --exclude='*error*' --compile.beautify=true --out=spec/expected/build/translated --translate=spec/templates/en_US.po
-```
+```
\ No newline at end of file
diff --git a/lib/compile.js b/lib/compile.js
index 88be38c..6f9db50 100644
--- a/lib/compile.js
+++ b/lib/compile.js
@@ -786,6 +786,7 @@ var compile = (function(){
'__fest_jschars=/' + jschars.source + '/g,' +
'__fest_jschars_test=/' + jschars.source + '/,' +
'__fest_jshash=' + JSON.stringify(jshash) + ',' +
+ 'i18n=__fest_self && typeof __fest_self.i18n === "function" ? __fest_self.i18n : function (str) {return str;},' +
'___fest_log_error;' +
(options.mode === 'function' ? 'function __fest_pushstr(_,s){__fest_buf+=s}' : '') +
'if(typeof __fest_error === "undefined"){___fest_log_error = (typeof console !== "undefined" && console.error) ? function(){return Function.prototype.apply.call(console.error, console, arguments)} : function(){};}else{___fest_log_error=__fest_error};' +
diff --git a/lib/compile_tmpl.js b/lib/compile_tmpl.js
old mode 100644
new mode 100755
index e4924a2..34e8402
--- a/lib/compile_tmpl.js
+++ b/lib/compile_tmpl.js
@@ -1,9 +1,11 @@
-function compile_tmpl(file, source, wrapper) {
+function compile_tmpl(file, source, wrapper, dir) {
var wrappers = {
fest: ";(function(){var x=Function('return this')();if(!x.fest)x.fest={};x.fest['##file##']=##source##})();",
loader: ";(function(){var x=Function('return this')();if(!x.fest)x.fest={};x.fest['##file##']=##source##; if(x.jsLoader!==undefined&&x.jsLoader.loaded!==undefined&&typeof x.jsLoader.loaded==='function'){x.jsLoader.loaded('{festTemplate}##file##')};})();",
source: "##source##",
+ amd_ajs: "define('festTemplate/##file##', [], function() {var x=Function('return this')();if(!x.fest)x.fest={};x.fest['##file##']=##source##;return x.fest['##file##'];});",
amd: "define(function(){return ##source##});",
+ amd_domains: "define('##file##', [], function() {var x=Function('return this')();if(!x.fest)x.fest={};x.fest['##file##']=x.fest['##file_short##']=##source##;return x.fest['##file##'];});",
variable: "var ##file##=##source##;"
};
@@ -13,7 +15,24 @@ function compile_tmpl(file, source, wrapper) {
}
file = file.replace(/\'/g, "\\'").replace(/\"/g, '\\"').replace(/\\/g, '\\');
- return wrappers[wrapper || 'fest'].replace(/##file##/g, file).replace(/##source##/g, source);
+
+ var fileShort = file;
+
+ if (wrapper === 'amd_domains') { // нормальный путь
+
+ var packageName = dir.replace(/\/.*(mail\.[^\/]+)\/fest.*/, "$1");
+
+ file = packageName + '/fest/' + file;
+
+ if (packageName !== 'mail.core') { // для обратной совместимости
+ fileShort = file;
+ }
+ }
+
+ return wrappers[wrapper || 'fest']
+ .replace(/##file##/g, file)
+ .replace(/##file_short##/g, fileShort)
+ .replace(/##source##/g, source);
}
if (typeof module !== 'undefined' && module.exports){
diff --git a/lib/proxy.js b/lib/proxy.js
old mode 100644
new mode 100755
index dfbc787..8c04eed
--- a/lib/proxy.js
+++ b/lib/proxy.js
@@ -195,11 +195,11 @@ function compile(file, locale, dir, options, config){
return compile_error_tmpl(file, 'fest.compile failed', options);
}
- return compile_tmpl(file, source, options);
+ return compile_tmpl(file, source, options, dir);
}
-function compile_tmpl(file, source, options){
- return fest.compile_tmpl(file, source, options ? options.wrapper : null);
+function compile_tmpl(file, source, options, dir){
+ return fest.compile_tmpl(file, source, options ? options.wrapper : null, dir);
}
function compile_error_tmpl(file, txt, options){
@@ -224,4 +224,4 @@ function mkdir(filename, mode) {
fs.mkdirSync(dir, mode);
}
}
- }
\ No newline at end of file
+ }
diff --git a/lib/translate.js b/lib/translate.js
index 80021a3..55141e9 100644
--- a/lib/translate.js
+++ b/lib/translate.js
@@ -124,6 +124,54 @@ function translate(data) {
}
}
+ function wrap(text) {
+ var node = stack[stack.length - 1],
+ parent = stack[stack.length - 2],
+ regex = /^data\-/,
+ paramRegex = /^\{.+\}$/,
+ tokenOpen = '#%',
+ tokenClose = '%#',
+ params = {},
+ value;
+
+ if (!node || node.ns[node.prefix] !== fest_i18n_ns) {
+ return escapeHTML(text);
+ }
+
+ text = translate_message(new Message(text));
+ text = 'i18n("' + escapeHTML(text) + '"';
+
+ for (var key in node.attributes) {
+ if (regex.test(key)) {
+ value = node.attributes[key].value;
+
+ if (paramRegex.test(value)) {
+ value = value.replace(/^{/, tokenOpen).replace(/}$/, tokenClose);
+ }
+
+ params[key.replace(regex, '')] = value;
+ }
+ }
+
+ if (node.attributes.context) {
+ text += ',' + '"' + node.attributes.context.value + '"';
+ }
+
+ if (Object.keys(params).length) {
+ text += ',' + escapeHTML(JSON.stringify(params))
+ .split(tokenOpen).join('" + ')
+ .split(tokenClose).join(' + "');
+ }
+
+ text += ')';
+
+ if (parent && ['get', 'script', 'value'].indexOf(parent.local) === -1) {
+ text = '' + text + ';' + '';
+ }
+
+ return text;
+ }
+
parser.onprocessinginstruction = function(instruction){
result += '' + instruction.name + ' ' + instruction.body + '?>';
};
@@ -178,7 +226,7 @@ function translate(data) {
parser.ontext = function(text){
var xml;
closetag();
- ontext(text, escapeHTML);
+ ontext(text, wrap);
};
parser.oncdata = function(text){
diff --git a/package.json b/package.json
index 7696d82..fb283c8 100644
--- a/package.json
+++ b/package.json
@@ -2,7 +2,7 @@
"name": "fest",
"description": "JavaScript Templates",
"keywords": ["template", "templating", "html", "xml"],
- "version": "0.9.0",
+ "version": "0.10.5",
"repository": {
"type": "git",
"url": "http://github.com/mailru/fest.git"
@@ -34,6 +34,6 @@
"grunt-contrib-clean": "~0.4.0",
"grunt-contrib-jshint": "~0.1.1",
"grunt-contrib-watch": "~0.2.0",
- "jasmine-node": "~1.3.0"
+ "jasmine-node": "~1.14.5"
}
}
diff --git a/spec/build.spec.js b/spec/build.spec.js
deleted file mode 100644
index 17d1410..0000000
--- a/spec/build.spec.js
+++ /dev/null
@@ -1,71 +0,0 @@
-var fs = require('fs');
-
-
-describe('fest-build', function () {
-
- it('should compile directories with templates', function () {
- var actualFiles = fs.readdirSync(__dirname + '/tmp/build/initial'),
- expectedFiles = fs.readdirSync(__dirname + '/expected/build/initial');
- expect(
- actualFiles.length
- ).toBe(
- expectedFiles.length
- );
- expectedFiles.forEach(function (fn) {
- var actual = fs.readFileSync(__dirname + '/tmp/build/initial/' + fn, 'utf8'),
- expected = fs.readFileSync(__dirname + '/expected/build/initial/' + fn, 'utf8');
- expect(actual).toBe(expected);
- });
- });
-
- it('should translate and compile directories with templates', function () {
- var actualFiles = fs.readdirSync(__dirname + '/tmp/build/translated'),
- expectedFiles = fs.readdirSync(__dirname + '/expected/build/translated');
- expect(
- actualFiles.length
- ).toBe(
- expectedFiles.length
- );
- expectedFiles.forEach(function (fn) {
- var actual = fs.readFileSync(__dirname + '/tmp/build/translated/' + fn, 'utf8'),
- expected = fs.readFileSync(__dirname + '/expected/build/translated/' + fn, 'utf8');
- expect(actual).toBe(expected);
- });
- });
-
-});
-
-
-describe('fest-compile', function () {
-
- it('should compile directories with templates', function () {
- var actualFiles = fs.readdirSync(__dirname + '/tmp/compile/initial'),
- expectedFiles = fs.readdirSync(__dirname + '/expected/compile/initial');
- expect(
- actualFiles.length
- ).toBe(
- expectedFiles.length
- );
- expectedFiles.forEach(function (fn) {
- var actual = fs.readFileSync(__dirname + '/tmp/compile/initial/' + fn, 'utf8'),
- expected = fs.readFileSync(__dirname + '/expected/compile/initial/' + fn, 'utf8');
- expect(actual).toBe(expected);
- });
- });
-
- it('should translate and compile directories with templates', function () {
- var actualFiles = fs.readdirSync(__dirname + '/tmp/compile/translated'),
- expectedFiles = fs.readdirSync(__dirname + '/expected/compile/translated');
- expect(
- actualFiles.length
- ).toBe(
- expectedFiles.length
- );
- expectedFiles.forEach(function (fn) {
- var actual = fs.readFileSync(__dirname + '/tmp/compile/translated/' + fn, 'utf8'),
- expected = fs.readFileSync(__dirname + '/expected/compile/translated/' + fn, 'utf8');
- expect(actual).toBe(expected);
- });
- });
-
-});
diff --git a/spec/message.spec.js b/spec/message.spec.js
index 84b224a..8709158 100644
--- a/spec/message.spec.js
+++ b/spec/message.spec.js
@@ -53,14 +53,32 @@ describe('fest:message', function () {
expect(
render('templates/message-with-i18n-ns.xml', {}, {
messages: {
- 'Строка': 'Line'
+ 'Use {link}redirect{linkclose}.': 'Какой-то перевод '
}
}).contents
).toBe(
- 'Line'
+ 'tstUse {link}redirect{linkclose}.Hello, {name}'
);
});
+ it('should support external i18n function', function () {
+
+ expect(
+ render('templates/message-with-i18n-ns.xml', {}, {
+ messages: {
+ 'Строка ': 'Line '
+ }
+ }, {
+ i18n: function (str) { return str + ' test'; }
+ }).contents
+ ).toBe(
+ 'tst testUse {link}redirect{linkclose}. testHello, {name} test'
+ );
+
+ delete global.__fest_i18n;
+ });
+
+
it('should allow redefine messages via events', function () {
var sourceMap = {
'Логотип {json.name}': '0',
diff --git a/spec/templates/include.xml b/spec/templates/include.xml
index bd57e79..d42a991 100644
--- a/spec/templates/include.xml
+++ b/spec/templates/include.xml
@@ -3,5 +3,4 @@
-
diff --git a/spec/templates/message-with-i18n-ns.xml b/spec/templates/message-with-i18n-ns.xml
index 70cd75a..e42b034 100644
--- a/spec/templates/message-with-i18n-ns.xml
+++ b/spec/templates/message-with-i18n-ns.xml
@@ -1,10 +1,13 @@
- params.text
+ params.text + params.text2 + params.text3
{
- text: 'Строка'
+ text: tst,
+ text2: Use {link}redirect{linkclose}.,
+ text3: Hello, {name}
}
-
\ No newline at end of file
+
+