diff --git a/.gitignore b/.gitignore index ba2a97b..648ea07 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ node_modules coverage +npm-debug.log diff --git a/.jscs.js b/.jscs.js index 1a4e7bf..28bd2ab 100644 --- a/.jscs.js +++ b/.jscs.js @@ -2,7 +2,8 @@ module.exports = { excludeFiles: [ 'node_modules', 'exlib', - 'coverage' + 'coverage', + 'test/fixtures' ], requireSpaceAfterKeywords: ['if', 'else', 'for', 'while', 'do', 'switch', 'return', 'try', 'catch'], requireSpaceBeforeBlockStatements: true, diff --git a/.jshintignore b/.jshintignore index b9e5d6d..16211e9 100644 --- a/.jshintignore +++ b/.jshintignore @@ -1,3 +1,4 @@ node_modules exlib coverage +test/fixtures diff --git a/README.md b/README.md index 266cb13..3c1d935 100644 --- a/README.md +++ b/README.md @@ -23,11 +23,11 @@ npm install --save-dev enb-bem-i18n ---------- * [i18n-keysets-xml](#i18n-keysets-xml) -* [i18n-lang-js](#i18n-lang-js) +* [i18n-js](#i18n-js) * [i18n-merge-keysets](#i18n-merge-keysets) * [i18n-bemjson-to-html](#i18n-bemjson-to-html) -### i18n-lang-js +### i18n-js Собирает `?.lang.<язык>.js`-файлы на основе `?.keysets.<язык>.js`-файлов. @@ -43,8 +43,8 @@ npm install --save-dev enb-bem-i18n ```javascript nodeConfig.addTechs([ - [ require('enb-bem-i18n/techs/i18n-lang-js'), { lang: 'all'} ], - [ require('enb-bem-i18n/techs/i18n-lang-js'), { lang: '{lang}'} ] + [ require('enb-bem-i18n/techs/i18n-js'), { lang: 'all'} ], + [ require('enb-bem-i18n/techs/i18n-js'), { lang: '{lang}'} ] ]); ``` diff --git a/lib/compile.js b/lib/compile.js new file mode 100644 index 0000000..da5c469 --- /dev/null +++ b/lib/compile.js @@ -0,0 +1,67 @@ +var EOL = require('os').EOL, + format = require('util').format, + serialize = require('serialize-javascript'), + tanker = require('../exlib/tanker'); + +/** + * Compile code of i18n. + * + * @param {Object} parsed info from keysets file, + * @param {String} language - language of keysets, + * example: `{ scope: { key: 'val' } }`. The value can be a function, + * example: `{ scope: { key: function (params, i18n) { return 'res'; } } }`. + * + * @returns {String} + */ +module.exports = function (parsed, language) { + return parsed.version === 2 ? + '((' + parsed.core + ')()).decl(' + serialize(parsed.keysets) + ')' : + compileForVersion1(parsed.core, parsed.keysets, language); +}; + +/** + * Compile i18n function for bem-bl localization system + * @param {String} core - bem-core i18n core code string + * @param {Object} keysets - keysets hash + * @param {String} language - language of keysets + * @returns {String} + */ +function compileForVersion1(core, keysets, language) { + return [ + '(function () {', + ' var __bem__ = {};', + ' (function (BEM) {', + core, + ' }(__bem__));', + ' var i18n = __bem__.I18N;', + Object.keys(keysets).sort().reduce(function (prev, keysetName, index) { + index === 0 && prev.push(EOL); + prev.push(_getKeysetBuildResult(keysetName, keysets[keysetName], language)); + return prev; + }, []).join(EOL), + format('\ti18n.lang(\'%s\');', language), + ' return i18n;', + '}())' + ].join(EOL); +} + +/** + * Return decl function call code string for given keyset scope + * @param {String} scopeName - name of scope + * @param {Object} scope hash object + * @param {String} lang - language option value + * @returns {string|*} + * @private + */ +function _getKeysetBuildResult(scopeName, scope, lang) { + var res = Object.keys(scope).reduce(function (prev, key, i, arr) { + tanker.xmlToJs(scope[key], function (js) { + var endSymbol = i === arr.length - 1 ? '' : ','; + prev.push(format('\t %s: %s%s', JSON.stringify(key), js, endSymbol)); + }); + return prev; + }, []); + res.unshift(format('\ti18n.decl(\'%s\', {', scopeName)); + res.push(format('\t}, {%s\t "lang": "%s"%s\t});', EOL, lang, EOL)); + return res.join(EOL); +} diff --git a/lib/keysets.js b/lib/keysets.js new file mode 100644 index 0000000..59ecf38 --- /dev/null +++ b/lib/keysets.js @@ -0,0 +1,51 @@ +/** + * Parse object with keysets. + * + * It separates the core of i18n from other keysets. The core will be searched in the `i18n` scope with `i18n` key. + * + * @param {(Object.|Object.)} keysets Object with keysets, + * example: `{ scope: { key: 'val' } }`. The value can be a function, + * example: `{ scope: { key: function (params, i18n) { return 'res'; } } }`. + * + * @throws Will throw an error if the core is not found. + * + * @returns {{ core: Function|String, keysets: (Object.|Object.) }} + */ +function parse(keysets) { + var core, + data = {}, + version = 2; + + // Pull core from source keysets + keysets && Object.keys(keysets).forEach(function (scope) { + // Search core in `i18n:i18n` + if (scope === 'i18n') { + var keyset = keysets[scope]; + + if (keyset && typeof keyset.i18n === 'function') { + core = keyset.i18n; + } + // Search deprecated core in `all` scope + } else if (scope === 'all') { + var possibleCore = keysets[scope]['']; + if (possibleCore && (typeof possibleCore === 'string') && possibleCore.match(/BEM\.I18N/g)) { + version = 1; + core = possibleCore; + } + } else { + data[scope] = keysets[scope]; + } + }); + + if (!core) { + throw new Error('Core of i18n is not found!'); + } + + return { + core: core, + keysets: data, + version: version + }; +} + +exports.parse = parse; diff --git a/package.json b/package.json index a8da2b7..8c2a777 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,8 @@ }, "dependencies": { "dom-js": "0.0.9", - "vow": "0.4.9" + "vow": "0.4.9", + "serialize-javascript": "1.0.0" }, "devDependencies": { "enb": ">= 0.11.0 < 1.0.0", diff --git a/techs/i18n-js.js b/techs/i18n-js.js new file mode 100644 index 0000000..ebd0ada --- /dev/null +++ b/techs/i18n-js.js @@ -0,0 +1,82 @@ +/** + * i18n-js + * ============ + * + * Собирает `?.lang.<язык>.js`-файлы на основе `?.keysets.<язык>.js`-файлов. + * + * Используется для локализации в JS с помощью BEM.I18N. + * + * **Опции** + * + * * *String* **target** — Результирующий таргет. По умолчанию — `?.lang.{lang}.js`. + * * *String* **lang** — Язык, для которого небходимо собрать файл. + * + * **Пример** + * + * ```javascript + * nodeConfig.addTechs([ + * [ require('enb-bem-i18n/techs/i18n-js'), { lang: 'all'} ], + * [ require('enb-bem-i18n/techs/i18n-js'), { lang: '{lang}'} ], + * ]); + * ``` + */ +var EOL = require('os').EOL, + path = require('path'), + asyncRequire = require('enb/lib/fs/async-require'), + dropRequireCache = require('enb/lib/fs/drop-require-cache'), + keysets = require('../lib/keysets'), + compile = require('../lib/compile'); + +module.exports = require('enb/lib/build-flow').create() + .name('i18n') + .target('target', '?.lang.{lang}.js') + .defineRequiredOption('lang') + .useSourceFilename('keysetsFile', '?.keysets.{lang}.js') + .builder(function (keysetsFilename) { + var cache = this.node.getNodeCache(this._target), + cacheKey = 'keysets-file-' + path.basename(keysetsFilename), + promise; + + if (cache.needRebuildFile(cacheKey, keysetsFilename)) { + dropRequireCache(require, keysetsFilename); + promise = asyncRequire(keysetsFilename) + .then(function (keysets) { + cache.cacheFileInfo(cacheKey, keysetsFilename); + + return keysets; + }); + } else { + promise = asyncRequire(keysetsFilename); + } + + return promise + .then(function (sources) { + var parsed = keysets.parse(sources); + return [ + '(function () {', + ' var __i18n__ = ' + compile(parsed, this._lang) + ',', + ' defineAsGlobal = true;', + '', + ' // CommonJS', + ' if (typeof exports === "object") {', + ' module.exports = __i18n__;', + ' defineAsGlobal = false;', + ' }', + '', + ' // YModules', + ' if (typeof modules === "object") {', + ' modules.define("i18n", function (provide) {', + ' provide(__i18n__);', + ' });', + ' defineAsGlobal = false;', + ' }', + '', + ' if (defineAsGlobal) {', + ' global.BEM || (global.BEM = {});', + ' global.BEM.I18N = __i18n__;', + ' }', + '})();' + ].join(EOL); + }, this); + }) + .createTech(); diff --git a/techs/i18n-lang-js.js b/techs/i18n-lang-js.js deleted file mode 100644 index 5723c42..0000000 --- a/techs/i18n-lang-js.js +++ /dev/null @@ -1,86 +0,0 @@ -/** - * i18n-lang-js - * ============ - * - * Собирает `?.lang.<язык>.js`-файлы на основе `?.keysets.<язык>.js`-файлов. - * - * Используется для локализации в JS с помощью BEM.I18N. - * - * **Опции** - * - * * *String* **target** — Результирующий таргет. По умолчанию — `?.lang.{lang}.js`. - * * *String* **lang** — Язык, для которого небходимо собрать файл. - * - * **Пример** - * - * ```javascript - * nodeConfig.addTechs([ - * [ require('enb-bem-i18n/techs/i18n-lang-js'), { lang: 'all'} ], - * [ require('enb-bem-i18n/techs/i18n-lang-js'), { lang: '{lang}'} ], - * ]); - * ``` - */ -var path = require('path'), - tanker = require('../exlib/tanker'), - asyncRequire = require('enb/lib/fs/async-require'), - dropRequireCache = require('enb/lib/fs/drop-require-cache'); - -module.exports = require('enb/lib/build-flow').create() - .name('i18n-lang-js') - .target('target', '?.lang.{lang}.js') - .defineRequiredOption('lang') - .useSourceFilename('keysetsFile', '?.keysets.{lang}.js') - .optionAlias('keysetsFile', 'keysetsTarget') - .builder(function (keysetsFilename) { - var node = this.node, - cache = node.getNodeCache(this._target), - basename = path.basename(keysetsFilename), - cacheKey = 'keysets-file-' + basename, - promise; - - if (cache.needRebuildFile(cacheKey, keysetsFilename)) { - dropRequireCache(require, keysetsFilename); - promise = asyncRequire(keysetsFilename) - .then(function (keysets) { - cache.cacheFileInfo(cacheKey, keysetsFilename); - return keysets; - }); - } else { - promise = asyncRequire(keysetsFilename); - } - - return promise.then(function (keysets) { - var lang = this._lang, - res = Object.keys(keysets).sort().reduce(function (prev, keysetName) { - prev.push(this.__self.getKeysetBuildResult(keysetName, keysets[keysetName], lang)); - return prev; - }.bind(this), []); - return this.getPrependJs(lang) + res.join('\n\n') + this.getAppendJs(lang); - }, this); - }) - .methods({ - getPrependJs: function (lang) { - return lang === 'all' ? '' : 'if (typeof BEM !== \'undefined\' && BEM.I18N) {'; - }, - getAppendJs: function (lang) { - return lang === 'all' ? '' : '\n\nBEM.I18N.lang(\'' + lang + '\');\n\n}\n'; - } - }) - .staticMethods({ - getKeysetBuildResult: function (keysetName, keyset, lang) { - var res = []; - if (keysetName === '') { - res.push(keyset); - } else { - res.push('BEM.I18N.decl(\'' + keysetName + '\', {'); - Object.keys(keyset).map(function (key, i, arr) { - tanker.xmlToJs(keyset[key], function (js) { - res.push(' ' + JSON.stringify(key) + ': ' + js + (i === arr.length - 1 ? '' : ',')); - }); - }); - res.push('}, {\n"lang": "' + lang + '"\n});'); - } - return res.join('\n'); - } - }) - .createTech(); diff --git a/test/fixtures/core-v1.js b/test/fixtures/core-v1.js new file mode 100644 index 0000000..4f520ea --- /dev/null +++ b/test/fixtures/core-v1.js @@ -0,0 +1,274 @@ +/* jshint browser:true, node:true */ +/* global BEM, i18n, oninit:true */ + +if(typeof oninit === 'undefined') oninit = function(cb) { return cb() }; + +oninit(function() { + + (function(global_, bem_, undefined) { + + // Check if BEM.I18N was already initialized + if(typeof bem_.I18N === 'function' && bem_.I18N._proto) { + return bem_.I18N; + } + + /** + * Support tanker-like syntax of keys in `i-bem__i18n` + * @example + * i18n['prj']['keyset']['key'](params) + */ + if(typeof i18n === 'undefined') { + /* jshint -W020 */ + i18n = {}; + /* jshint +W020 */ + } + + /* jshint -W020 */ + BEM = bem_; + /* jshint +W020 */ + + var MOD_DELIM = '_', + ELEM_DELIM = '__', + DEFAULT_LANG = 'ru', + cache = {}, + // {String[]} A stack used for restoring context of dynamic keysets + stack = [], + debug = false, + // @see http://whattheheadsaid.com/2011/04/internet-explorer-9s-problematic-console-object + hasConsole = typeof console !== 'undefined' && typeof console.log === 'function'; + + function log() { + if(debug && hasConsole) { + console.log.apply(console, arguments); + } + } + + function bemName(decl) { + typeof decl === 'string' && (decl = { block : decl }); + + return decl.block + + (decl.elem? (ELEM_DELIM + decl.elem) : '') + + (decl.modName? MOD_DELIM + decl.modName + MOD_DELIM + decl.modVal : ''); + } + + function bemParse(name) { + var bemitem = {}; + + name.split(ELEM_DELIM).forEach(function(item, i) { + var keys = [i? 'elem' : 'block', 'mod', 'val']; + + item.split(MOD_DELIM).forEach(function(part, j) { + bemitem[keys[j]] = part; + }); + }); + + return bemitem; + } + + function _pushStack(name) { + if(!name) return false; + return stack.push(name); + } + + function _popStack() { + return stack.length && stack.pop(); + } + + /** + * @constructor + */ + function _i18n() { + this._lang = ''; + this._prj = 'bem-core'; + this._keyset = ''; + this._key = ''; + } + + _i18n.prototype = { + + lang : function(name) { + this._lang = name; + return this; + }, + + project : function(name) { + this._prj = name; + return this; + }, + + keyset : function(name, saveCtx) { + saveCtx && _pushStack(this._keyset); + + this._keyset = bemName(name); + return this; + }, + + key : function(name) { + this._key = name; + return this; + }, + + /** + * FIXME: Move legacy-syntax support into separate method + * @param {Object|Function} v + */ + decl : function(v) { + var bemitem = bemParse(this._keyset), + // tanker legacy syntax + prj = bemitem.block === 'i-tanker'? 'tanker' : this._prj, + keyset = bemitem.elem || this._keyset, + key = this._key; + + prj = i18n[prj] || (i18n[prj] = {}); + keyset = prj[keyset] || (prj[keyset] = {}); + keyset[key] = typeof v === 'function'? v : (function() { return (v); }); + + // `BEM.I18N` syntax + var l = cache[this._lang] || (cache[this._lang] = {}), + k = l[this._keyset] || (l[this._keyset] = {}); + + k[key] = v; + }, + + val : function(params, ctx) { + var value = cache[this._lang] && cache[this._lang][this._keyset], + debugString = 'keyset: ' + this._keyset + ' key: ' + this._key + ' (lang: ' + this._lang + ')'; + + if(!value) { + log('[I18N_NO_KEYSET] %s', debugString); + return ''; + } + + value = value[this._key]; + + var valtype = typeof value; + if(valtype === 'undefined') { + log('[I18N_NO_VALUE] %s', debugString); + return ''; + } + + if(valtype === 'string') { + return value; + } + + ctx || (ctx = this); + + // TODO: try/catch + return value.call(ctx, params); + }, + + _cache : function() { return cache; } + + }; + + /** + * @namespace + * @lends BEM.I18N + */ + bem_.I18N = (function(base) { + + /** + * Shortcut to get key value + * + * @param {String|Object} keyset + * @param {String} key + * @param {Object} [params] + * @returns {String} + */ + var klass = function(keyset, key, params) { + return klass.keyset(keyset).key(key, params); + }; + + klass._proto = base; + + /** + * @param {String} name + * @returns {BEM.I18N} + */ + klass.project = function(name) { + this._proto.project(name); + return this; + }; + + /** + * @param {String} name + * @returns {BEM.I18N} + */ + klass.keyset = function(name) { + this._proto.keyset(name, true); + return this; + }; + + /** + * @param {String} name Key name + * @param {Object} params + * @returns {String} + */ + klass.key = function(name, params) { + var proto = this._proto, + result, + ksetRestored; + + proto.lang(this._lang).key(name); + + // TODO: kiss + result = proto.val.call(proto, params, klass); + + // restoring keyset's context + // NOTE: should not save current ctx, `saveCtx = false` + ksetRestored = _popStack(); + ksetRestored && proto.keyset(ksetRestored, false); + + return result; + }; + + /** + * Declaration of translations + * + * @param {String|Object} bemitem + * @param {Object} keysets + * @param {Object} [params] declaration params + * @returns {BEM.I18N} + */ + klass.decl = function(bemitem, keysets, params) { + var proto = this._proto, + k; + + params || (params = {}); + params.lang && proto.lang(params.lang); + + proto.keyset(bemitem); + + for(k in keysets) { + if(keysets.hasOwnProperty(k)) { + proto.key(k).decl(keysets[k]); + } + } + + return this; + }; + + /** + * Get/set current language + * + * @param {String} [lang] + * @returns {String} + */ + klass.lang = function(lang) { + typeof lang !== 'undefined' && (this._lang = lang); + return this._lang; + }; + + klass.debug = function(flag) { + debug = !!flag; + }; + + klass.lang(DEFAULT_LANG); + + return klass; + + }(new _i18n())); + + })(this, typeof BEM === 'undefined'? {} : BEM); + +}); // oninit diff --git a/test/fixtures/core-v2.js b/test/fixtures/core-v2.js new file mode 100644 index 0000000..ab7755b --- /dev/null +++ b/test/fixtures/core-v2.js @@ -0,0 +1,42 @@ +module.exports = function() { + var data; + + /** + * @exports + * @param {String} keyset + * @param {String} key + * @param {Object} [params] + * @returns {String} + */ + function i18n(keyset, key, params) { + if(!data) { + throw Error('i18n need to be filled with data'); + } + var val = data[keyset] && data[keyset][key]; + return typeof val === 'undefined'? + keyset + ':' + key : + typeof val === 'string'? + val : + val.call(i18n, params, i18n); + } + + i18n.decl = function(i18nData) { + if(!data) { + data = i18nData; + return this; + } + + for(var ks in i18nData) { + var dataKs = data[ks] || (data[ks] = {}), + i18nDataKs = i18nData[ks]; + + for(var k in i18nDataKs) { + dataKs[ k ] = i18nDataKs[ k ]; + } + } + + return this; + }; + + return i18n; +}; diff --git a/test/techs/i18n-js.test.js b/test/techs/i18n-js.test.js new file mode 100644 index 0000000..7aebdf7 --- /dev/null +++ b/test/techs/i18n-js.test.js @@ -0,0 +1,241 @@ +var fs = require('fs'), + path = require('path'), + mock = require('mock-fs'), + serializeJS = require('serialize-javascript'), + TestNode = require('enb/lib/test/mocks/test-node'), + dropRequireCache = require('enb/lib/fs/drop-require-cache'), + Tech = require('../../techs/i18n-js'), + corev1, + corev2 = require('../fixtures/core-v2.js'); + +describe('i18n-js', function () { + before(function () { + corev1 = fs.readFileSync(path.resolve('./test/fixtures/core-v1.js'), { encoding: 'utf-8' }); + }); + + afterEach(function () { + mock.restore(); + }); + + it('must throw err if i18n is not found', function () { + var keysets = {}; + + return build(keysets) + .fail(function (err) { + err.must.a(Error); + err.message.must.be('Core of i18n is not found!'); + }); + }); + + it('must throw err if i18n is not function', function () { + var keysets = { + i18n: { + i18n: 'val' + } + }; + + return build(keysets) + .fail(function (err) { + err.must.a(Error); + err.message.must.be('Core of i18n is not found!'); + }); + }); + + it('must return value', function () { + var keysets = { + i18n: { + i18n: corev2 + }, + scope: { + key: 'val' + } + }; + + return build(keysets) + .then(function (i18n) { + i18n('scope', 'key').must.be('val'); + }); + }); + + it('must build fake key if keysets is empty', function () { + var keysets = { + i18n: { + i18n: corev2 + } + }; + + return build(keysets) + .then(function (i18n) { + i18n('scope', 'key').must.be('scope:key'); + }); + }); + + it('must build key by params', function () { + var keysets = { + i18n: { + i18n: corev2 + }, + scope: { + key: function (params) { + return params.join(); + } + } + }; + + return build(keysets) + .then(function (i18n) { + i18n('scope', 'key', ['p1', 'p2']).must.be('p1,p2'); + }); + }); + + it('must provide i18n instance to function', function () { + var keysets = { + i18n: { + i18n: corev2 + }, + 'scope-1': { + key: 'val' + }, + 'scope-2': { + key: function (params, i18n) { + return i18n(params.scope, params.key); + } + } + }; + + return build(keysets) + .then(function (i18n) { + i18n('scope-2', 'key', { scope: 'scope-1', key: 'key' }).must.be('val'); + }); + }); + + describe('corev1', function () { + it('must get valid *.lang from *.keyset file', function () { + var keysets = { + all: { '': corev1 }, + scope1: { key11: 'val11', key12: 'val12' }, + scope2: { key21: 'val21' } + }; + return build(keysets) + .then(function (i18n) { + i18n('scope1', 'key11').must.be('val11'); + i18n('scope1', 'key12').must.be('val12'); + i18n('scope2', 'key21').must.be('val21'); + }); + }); + + it('must get valid *.lang from empty *.keyset file (only core)', function () { + var keysets = { + all: { '': corev1 } + }; + + return build(keysets) + .then(function (i18n) { + i18n('scope', 'key').must.be(''); + }); + }); + }); + + describe('cache', function () { + it('must get result from cache', function () { + var time = new Date(1); + + mock({ + bundle: { + 'bundle.keysets.lang.js': mock.file({ + content: serialize({ + i18n: { i18n: corev2 }, + scope: { key: 'val' } + }), + mtime: time + }) + } + }); + + var bundle = new TestNode('bundle'), + cache = bundle.getNodeCache('bundle.lang.lang.js'), + basename = 'bundle.keysets.lang.js', + filename = path.resolve('bundle', basename); + + dropRequireCache(require, filename); + require(filename); + cache.cacheFileInfo('keysets-file-' + basename, filename); + + mock({ + bundle: { + 'bundle.keysets.lang.js': mock.file({ + content: serialize({ + i18n: { i18n: corev2 }, + scope: { key: 'val2' } + }), + mtime: time + }) + } + }); + + return bundle.runTechAndRequire(Tech, { lang: 'lang' }) + .spread(function (i18n) { + i18n('scope', 'key').must.be('val'); + }); + }); + + it('must ignore outdated cache', function () { + mock({ + bundle: { + 'bundle.keysets.lang.js': mock.file({ + content: serialize({ + i18n: { i18n: corev2 }, + scope: { key: 'val' } + }), + mtime: new Date(1) + }) + } + }); + + var bundle = new TestNode('bundle'), + cache = bundle.getNodeCache('bundle.lang.lang.js'), + basename = 'bundle.keysets.lang.js', + filename = path.resolve('bundle', basename); + + dropRequireCache(require, filename); + require(filename); + cache.cacheFileInfo('keysets-file-' + basename, filename); + + mock({ + bundle: { + 'bundle.keysets.lang.js': mock.file({ + content: serialize({ + i18n: { i18n: corev2 }, + scope: { key: 'val2' } + }), + mtime: new Date(2) + }) + } + }); + + return bundle.runTechAndRequire(Tech, { lang: 'lang' }) + .spread(function (i18n) { + i18n('scope', 'key').must.be('val2'); + }); + }); + }); +}); + +function build(keysets) { + mock({ + bundle: { + 'bundle.keysets.lang.js': serialize(keysets) + } + }); + + var bundle = new TestNode('bundle'); + + return bundle.runTechAndRequire(Tech, { lang: 'lang' }) + .spread(function (i18n) { + return i18n; + }); +} + +function serialize(js) { + return 'module.exports = ' + serializeJS(js) + ';'; +} diff --git a/test/techs/i18n-lang-js.test.js b/test/techs/i18n-lang-js.test.js deleted file mode 100644 index 3b87f26..0000000 --- a/test/techs/i18n-lang-js.test.js +++ /dev/null @@ -1,185 +0,0 @@ -var mockFs = require('mock-fs'), - TestNode = require('enb/lib/test/mocks/test-node'), - Tech = require('../../techs/i18n-lang-js'); - -describe('i18n-lang-js', function () { - afterEach(function () { - mockFs.restore(); - }); - - it('must get valid *.lang from *.keyset file', function () { - var keysets = [ - { - 'lang.js': { - scope1: { - key11: 'val11', - key12: 'val12' - }, - scope2: { - key21: 'val21', - key22: 'val22' - } - } - } - ], - expected = [ - 'if (typeof BEM !== \'undefined\' && BEM.I18N) {BEM.I18N.decl(\'scope1\', {', - ' "key11": \'val11\',', - ' "key12": \'val12\'', - '}, {', - '"lang": "lang"', - '});', - '', - 'BEM.I18N.decl(\'scope2\', {', - ' "key21": \'val21\',', - ' "key22": \'val22\'', - '}, {', - '"lang": "lang"', - '});', - '', - 'BEM.I18N.lang(\'lang\');', - '', - '}', - '' - ].join('\n'); - - return build(keysets, 'lang') - .then(function (res) { - res.must.eql(expected); - }); - }); - - it('must get valid *.lang from empty *.keyset file', function () { - var keysets = [ - { - 'lang.js': {} - } - ], - expected = [ - 'if (typeof BEM !== \'undefined\' && BEM.I18N) {', - '', - 'BEM.I18N.lang(\'lang\');', - '', - '}', - '' - ].join('\n'); - - return build(keysets, 'lang') - .then(function (res) { - res.must.eql(expected); - }); - }); - - it('must provide core', function () { - var keysets = [ - { - 'all.js': { - '': 'core' - } - } - ]; - - return build(keysets, 'all') - .then(function (res) { - res.must.be('core'); - }); - }); - - describe('cache', function () { - it('must get keyset from cache', function () { - var time = new Date(1), - initial = { val: 'val', mtime: time }, - modified = { val: 'val2', mtime: time }, - expected = [ - 'if (typeof BEM !== \'undefined\' && BEM.I18N) {BEM.I18N.decl(\'scope\', {', - ' "key": \'val\'', - '}, {', - '"lang": "lang"', - '});', - '', - 'BEM.I18N.lang(\'lang\');', - '', - '}', - '' - ].join('\n'); - - return buildWithCache(initial, modified, 'lang') - .then(function (res) { - res.must.eql(expected); - }); - }); - - it('must ignore outdated cache', function () { - var initial = { val: 'val', mtime: new Date(1) }, - modified = { val: 'val2', mtime: new Date(2) }, - expected = [ - 'if (typeof BEM !== \'undefined\' && BEM.I18N) {BEM.I18N.decl(\'scope\', {', - ' "key": \'val2\'', - '}, {', - '"lang": "lang"', - '});', - '', - 'BEM.I18N.lang(\'lang\');', - '', - '}', - '' - ].join('\n'); - - return buildWithCache(initial, modified, 'lang') - .then(function (res) { - res.must.eql(expected); - }); - }); - }); -}); - -function build(keysets, lang) { - var fsScheme = { - bundle: {} - }; - - for (var i = 0; i < keysets.length; ++i) { - var keyset = keysets[i], - basename = Object.keys(keyset)[0], - data = keyset[basename]; - - fsScheme.bundle['bundle.keysets.' + basename] = 'module.exports = ' + JSON.stringify(data) + ';'; - } - - mockFs(fsScheme); - - var bundle = new TestNode('bundle'); - - return bundle.runTechAndGetContent(Tech, { lang: lang }) - .spread(function (res) { - return res.toString(); - }); -} - -function buildWithCache(initial, modified, lang) { - function buildScheme(data) { - return { - bundle: { - 'bundle.keysets.lang.js': mockFs.file({ - content: 'module.exports = { scope: { key: "' + data.val + '" } };', - mtime: data.mtime - }) - } - }; - } - - mockFs(buildScheme(initial)); - - var bundle = new TestNode('bundle'); - - return bundle.runTechAndGetContent(Tech, { lang: lang }) - .then(function () { - return mockFs(buildScheme(modified)); - }) - .then(function () { - return bundle.runTechAndGetContent(Tech, { lang: lang }); - }) - .then(function (res) { - return res.toString(); - }); -}