From 9a024471b039caa0ac80e6e1ef63a185b50893b9 Mon Sep 17 00:00:00 2001 From: Karel Ledru-Mathe Date: Thu, 25 Jan 2018 09:20:10 -0500 Subject: [PATCH 1/8] Refactor helpers --- .babelrc | 5 + .editorconfig | 17 + .travis.yml | 4 +- .vscode/launch.json | 35 + README.md | 2 +- index.js | 6 +- package.json | 21 +- src/helpers.js | 167 +-- test/helpers.js | 232 ----- test/helpers/dotPathToHash.test.js | 46 + test/helpers/mergeHashes.test.js | 157 +++ test/helpers/populateHash.test.js | 73 ++ test/parser.js | 7 + test/test.js | 17 - yarn.lock | 1521 ++++++++++++++++++++++++++++ 15 files changed, 1965 insertions(+), 345 deletions(-) create mode 100644 .babelrc create mode 100644 .editorconfig create mode 100644 .vscode/launch.json delete mode 100644 test/helpers.js create mode 100644 test/helpers/dotPathToHash.test.js create mode 100644 test/helpers/mergeHashes.test.js create mode 100644 test/helpers/populateHash.test.js delete mode 100644 test/test.js create mode 100644 yarn.lock diff --git a/.babelrc b/.babelrc new file mode 100644 index 00000000..86da1138 --- /dev/null +++ b/.babelrc @@ -0,0 +1,5 @@ +{ + "presets": ["env"], + "sourceMaps": true, + "retainLines": true +} diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..12b0275a --- /dev/null +++ b/.editorconfig @@ -0,0 +1,17 @@ +# EditorConfig helps developers define and maintain consistent +# coding styles between different editors and IDEs +# editorconfig.org + +root = true + + +[*] +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true +indent_style = space +indent_size = 2 + +[*.{diff,md}] +trim_trailing_whitespace = false diff --git a/.travis.yml b/.travis.yml index 62f8bba7..a32bed45 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,8 @@ language: node_js node_js: + - '8' + - '7' - '6' - '5' - '4' @@ -10,4 +12,4 @@ before_install: - npm --version after_success: - - npm run test \ No newline at end of file + - npm run test diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000..750ad3a5 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,35 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "node", + "request": "launch", + "name": "Mocha Tests", + "cwd": "${workspaceRoot}", + "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha", + "args": [ + "-u", "tdd", + "--timeout", "999999", + "--colors", + "--require", "babel-register", + "--require", "babel-polyfill", + "--plugins", "transform-object-rest-spread", + "${workspaceFolder}/test/**/*.test.js" + ], + "runtimeArgs": [ + "--nolazy" + ], + "sourceMaps": true, + "internalConsoleOptions": "openOnSessionStart" + }, + { + "type": "node", + "request": "launch", + "name": "Launch Program", + "program": "${workspaceFolder}/src/helpers.js" + } + ] +} diff --git a/README.md b/README.md index bfff7477..d15cba07 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ npm install i18next-parser -g ## Tests ``` -mocha --reporter nyan test/test.js +mocha --require babel-register --require babel-polyfill test/**/*.js ``` --- diff --git a/index.js b/index.js index c6ab52ab..b23ba747 100755 --- a/index.js +++ b/index.js @@ -218,7 +218,7 @@ Parser.prototype._flush = function(done) { // simplify ${dot.separated.variables} into just their tails (${variables}) var key = self.translations[index].key.replace( /\$\{(?:[^.}]+\.)*([^}]+)\}/g, '\${$1}' ); var value = self.translations[index].defaultValue; - translationsHash = helpers.hashFromString( key, self.keySeparator, value, translationsHash ); + translationsHash = helpers.dotPathToHash( key, self.keySeparator, value, translationsHash ); } @@ -274,10 +274,10 @@ Parser.prototype._flush = function(done) { // merges existing translations with the new ones - mergedTranslations = helpers.mergeHash( currentTranslations, translationsHash[namespace], null, this.keepRemoved ); + mergedTranslations = helpers.mergeHashes( currentTranslations, translationsHash[namespace], null, this.keepRemoved ); // restore old translations if the key is empty - mergedTranslations.new = helpers.replaceEmpty( oldTranslations, mergedTranslations.new ); + mergedTranslations.new = helpers.populateHash( oldTranslations, mergedTranslations.new ); // merges former old translations with the new ones mergedTranslations.old = _.extend( oldTranslations, mergedTranslations.old ); diff --git a/package.json b/package.json index d2883ed3..32d9b59a 100644 --- a/package.json +++ b/package.json @@ -1,14 +1,15 @@ { - "author": "Karel Ledru-Mathe", + "author": "Karel Ledru", "description": "Command Line tool for i18next", "name": "i18next-parser", - "version": "0.13.0", + "version": "1.0.0-alpha1", "license": "MIT", + "main": "src/index.js", "bin": { "i18next": "./bin/cli.js" }, "scripts": { - "test": "mocha --reporter nyan test/test.js" + "test": "mocha --require babel-register --require babel-polyfill test/**/*.test.js" }, "repository": { "type": "git", @@ -18,19 +19,23 @@ "colors": "~1.1.2", "commander": "~2.9.0", "concat-stream": "~1.6.0", - "lodash": "~4.17.3", + "eol": "^0.9.1", + "lodash": "~4.17.4", "mkdirp": "~0.5.1", - "plugin-error": "^1.0.1", "readdirp": "~2.1.0", "through2": "~2.0.3", - "vinyl": "~2.0.1" + "vinyl": "~2.0.1", + "vinyl-fs": "^3.0.2" }, "engines": { "node": ">=0.10.22" }, "devDependencies": { - "mocha": "~3.2.0", - "assert": "~1.4.1" + "babel-polyfill": "^6.26.0", + "babel-preset-env": "^1.6.1", + "babel-register": "^6.26.0", + "chai": "^4.1.2", + "mocha": "^5.0.0" }, "keywords": [ "gulpplugin", diff --git a/src/helpers.js b/src/helpers.js index c981553a..aab50686 100644 --- a/src/helpers.js +++ b/src/helpers.js @@ -1,28 +1,28 @@ +import _ from 'lodash' + // Takes a `path` of the form 'foo.bar' and // turn it into a hash {foo: {bar: ""}}. // The generated hash can be attached to an // optional `hash`. -function hashFromString(path, separator, value, hash) { - separator = separator || '.'; - - if ( path.indexOf( separator, path.length - separator.length ) >= 0 ) { - path = path.slice( 0, -separator.length ); +function dotPathToHash(path, separator = '.', value = '', target = {}) { + if (path.endsWith(separator)) { + path = path.slice( 0, -separator.length ) } - var parts = path.split( separator ); - var tmp_obj = hash || {}; - var obj = tmp_obj; - - for( var x = 0; x < parts.length; x++ ) { - if ( x == parts.length - 1 ) { - tmp_obj[parts[x]] = value || ''; - } - else if ( ! tmp_obj[parts[x]] ) { - tmp_obj[parts[x]] = {}; - } - tmp_obj = tmp_obj[parts[x]]; - } - return obj; + let result = {} + const segments = path.split(separator) + + segments.reduce((hash, segment, index) => { + if (index === segments.length - 1) { + hash[segment] = value + } + else { + hash[segment] = {} + } + return hash[segment] + }, result) + + return _.merge(target, result) } @@ -30,77 +30,78 @@ function hashFromString(path, separator, value, hash) { // are pasted in the `target` hash, if the target // hash has the corresponding key (or if keepRemoved is true). // If not, the value is added to an `old` hash. -function mergeHash(source, target, old, keepRemoved) { - target = target || {}; - old = old || {}; - - Object.keys(source).forEach(function (key) { - if ( target[key] !== undefined ) { - if (typeof source[key] === 'object' && source[key].constructor !== Array) { - var nested = mergeHash( source[key], target[key], old[key], keepRemoved ); - target[key] = nested.new; - old[key] = nested.old; - } - else { - target[key] = source[key]; - } - } - else { - // support for plural in keys - pluralMatch = /_plural(_\d+)?$/.test( key ); - singularKey = key.replace( /_plural(_\d+)?$/, '' ); - - // support for context in keys - contextMatch = /_([^_]+)?$/.test( singularKey ); - rawKey = singularKey.replace( /_([^_]+)?$/, '' ); - - if ( - ( contextMatch && target[rawKey] !== undefined ) || - ( pluralMatch && target[singularKey] !== undefined ) - ) { - target[key] = source[key]; - } - else { - if (keepRemoved) { - target[key] = source[key]; - } - old[key] = source[key]; - } - } - }); - - return { - 'new': target, - 'old': old - }; +function mergeHashes(source, target = {}, old, keepRemoved = false) { + old = old || {} + Object.keys(source).forEach((key) => { + const hasNestedEntries = ( + typeof target[key] === 'object' && + !Array.isArray(target[key]) + ) + + if (hasNestedEntries) { + const nested = mergeHashes(source[key], target[key], old[key], keepRemoved) + target[key] = nested.new + old[key] = nested.old + } + else if ( target[key] !== undefined ) { + if (typeof source[key] === 'string' || Array.isArray(source[key])) { + target[key] = source[key] + } + else { + old[key] = source[key] + } + } + else { + // support for plural in keys + const pluralMatch = /_plural(_\d+)?$/.test( key ) + const singularKey = key.replace(/_plural(_\d+)?$/, '') + + // support for context in keys + const contextMatch = /_([^_]+)?$/.test(singularKey) + const rawKey = singularKey.replace(/_([^_]+)?$/, '') + + if ( + (contextMatch && target[rawKey] !== undefined) || + (pluralMatch && target[singularKey] !== undefined) + ) { + target[key] = source[key] + } + else if (keepRemoved) { + target[key] = source[key] + old[key] = source[key] + } + else { + old[key] = source[key] + } + } + }) + + return {old, new: target} } // Takes a `target` hash and replace its empty // values with the `source` hash ones if they // exist -function replaceEmpty(source, target) { - target = target || {}; - - Object.keys(source).forEach(function (key) { - if ( target[key] !== undefined ) { - if (typeof source[key] === 'object') { - var nested = replaceEmpty( source[key], target[key] ); - target[key] = nested; - } - else if ( target[key] === '' ) { - target[key] = source[key]; - } - } - }); - - return target; +function populateHash(source, target = {}) { + Object.keys(source).forEach((key) => { + if ( target[key] !== undefined ) { + if (typeof source[key] === 'object') { + target[key] = populateHash(source[key], target[key]) + } + else if (target[key] === '') { + target[key] = source[key] + } + } + }) + + return target } -module.exports = { - hashFromString: hashFromString, - mergeHash: mergeHash, - replaceEmpty: replaceEmpty -}; +export { + dotPathToHash, + mergeHashes, + populateHash +} diff --git a/test/helpers.js b/test/helpers.js deleted file mode 100644 index 96e2e238..00000000 --- a/test/helpers.js +++ /dev/null @@ -1,232 +0,0 @@ -describe('mergeHash helper function', function () { - it('replaces `target` keys with `source`', function (done) { - var source = { key1: 'value1' }; - var target = { key1: '' }; - var res = mergeHash(source, target); - - assert.deepEqual(res.new, { key1: 'value1' }); - assert.deepEqual(res.old, {}); - done(); - }); - - it('leaves untouched `target` keys not in `source`', function (done) { - var source = { key1: 'value1' }; - var target = { key1: '', key2: '' }; - var res = mergeHash(source, target); - - assert.deepEqual(res.new, { key1: 'value1', key2: '' }); - assert.deepEqual(res.old, {}); - done(); - }); - - it('populates `old` object with keys from `source` not in `target`', function (done) { - var source = { key1: 'value1', key2: 'value2' }; - var target = { key1: '' }; - var res = mergeHash(source, target); - - assert.deepEqual(res.new, { key1: 'value1' }); - assert.deepEqual(res.old, { key2: 'value2' }); - done(); - }); - - it('copies `source` keys to `target` regardless of presence when keepRemoved is enabled', function (done) { - var source = { key1: 'value1', key2: 'value2' }; - var target = { key1: '', key3: '' }; - var res = mergeHash(source, target, null, true); - - assert.deepEqual(res.new, { key1: 'value1', key2: 'value2', key3: '' }); - assert.deepEqual(res.old, { key2: 'value2' }); - done(); - }); - - it('restores plural keys when the singular one exists', function (done) { - var source = { key1: '', key1_plural: 'value1' }; - var target = { key1: '' }; - var res = mergeHash(source, target); - - assert.deepEqual(res.new, { key1: '', key1_plural: 'value1' }); - assert.deepEqual(res.old, {}); - done(); - }); - - it('does not restores plural keys when the singular one does not', function (done) { - var source = { key1: '', key1_plural: 'value1' }; - var target = { key2: '' }; - var res = mergeHash(source, target); - - assert.deepEqual(res.new, { key2: '' }); - assert.deepEqual(res.old, { key1: '', key1_plural: 'value1' }); - done(); - }); - - it('works with deep objects', function (done) { - var source = { - key1: 'value1', - key2: { - key21: 'value21', - key22: { - key221: 'value221', - key222: 'value222' - }, - key23: 'value23' - } - }; - var target = { - key1: '', - key2: { - key21: '', - key22: { - key222: '', - key223: '' - }, - key24: '' - }, - key3: '' - }; - - var res = mergeHash(source, target); - - var expected_target = { - key1: 'value1', - key2: { - key21: 'value21', - key22: { - key222: 'value222', - key223: '' - }, - key24: '' - }, - key3: '' - }; - - var expected_old = { - key2: { - key22: { - key221: 'value221' - }, - key23: 'value23' - } - }; - - assert.deepEqual(res.new, expected_target); - assert.deepEqual(res.old, expected_old); - done(); - }); - - it('leaves arrays of values (multiline) untouched', function (done) { - var source = { key1: ['Line one.', 'Line two.'] }; - var target = { key1: '' }; - var res = mergeHash(source, target); - - assert.deepEqual(res.new, { key1: ['Line one.', 'Line two.'] }); - done(); - }); -}); - -describe('hashFromString helper function', function () { - it('creates an object from a string path', function (done) { - var res = hashFromString('one'); - - assert.deepEqual(res, { one: '' }); - done(); - }); - - it('ignores trailing separator', function (done) { - var res = hashFromString('one..', '..'); - - assert.deepEqual(res, { one: '' }); - done(); - }); - - it('use provided default value', function (done) { - var res = hashFromString('one', null, 'myDefaultValue'); - - assert.deepEqual(res, { one: 'myDefaultValue' }); - done(); - }); - - it('handles nested paths', function (done) { - var res = hashFromString('one.two.three'); - - assert.deepEqual(res, { one: { two: { three: '' } } }); - done(); - }); - - it('handles a different separator', function (done) { - var res = hashFromString('one_two_three.', '_'); - - assert.deepEqual(res, { one: { two: { 'three.': '' } } }); - done(); - }); -}); - -describe('replaceEmpty helper function', function () { - it('replaces `target` empty keys with `source` ones', function (done) { - var source = { key1: 'value1' }; - var target = { key1: '' }; - var res = replaceEmpty(source, target); - - assert.deepEqual(res, { key1: 'value1' }); - done(); - }); - - it('leaves untouched `target` keys that are not empty', function (done) { - var source = { key1: 'value1' }; - var target = { key1: 'value2' }; - var res = replaceEmpty(source, target); - - assert.deepEqual(res, { key1: 'value2' }); - done(); - }); - - it('leaves untouched `target` keys not in `source`', function (done) { - var source = { key1: 'value1' }; - var target = { key1: '', key2: '' }; - var res = replaceEmpty(source, target); - - assert.deepEqual(res, { key1: 'value1', key2: '' }); - done(); - }); - - it('works with deep objects', function (done) { - var source = { - key1: 'value1', - key2: { - key21: 'value21', - key22: { - key221: 'value221', - key222: 'value222' - }, - key23: 'value23' - } - }; - var target = { - key1: '', - key2: { - key21: '', - key22: { - key222: '', - key223: '' - }, - key24: '' - }, - key3: '' - }; - var res = replaceEmpty(source, target); - var expected_target = { - key1: 'value1', - key2: { - key21: 'value21', - key22: { - key222: 'value222', - key223: '' - }, - key24: '' - }, - key3: '' - }; - - assert.deepEqual(res, expected_target); - done(); - }); -}); diff --git a/test/helpers/dotPathToHash.test.js b/test/helpers/dotPathToHash.test.js new file mode 100644 index 00000000..659dfb51 --- /dev/null +++ b/test/helpers/dotPathToHash.test.js @@ -0,0 +1,46 @@ +import { assert } from 'chai' +import { dotPathToHash } from '../../src/helpers' + +describe('dotPathToHash helper function', function () { + it('creates an object from a string path', function (done) { + const res = dotPathToHash('one') + assert.deepEqual(res, { one: '' }) + done() + }) + + it('ignores trailing separator', function (done) { + const res = dotPathToHash('one..', '..') + assert.deepEqual(res, { one: '' }) + done() + }) + + it('ignores duplicated separator', function (done) { + const res = dotPathToHash('one..two', '..') + assert.deepEqual(res, { one: { two: '' } }) + done() + }) + + it('use provided default value', function (done) { + const res = dotPathToHash('one', null, 'myDefaultValue') + assert.deepEqual(res, { one: 'myDefaultValue' }) + done() + }) + + it('use provided default value', function (done) { + const res = dotPathToHash('one', null, 'myDefaultValue') + assert.deepEqual(res, { one: 'myDefaultValue' }) + done() + }) + + it('handles a target hash', function (done) { + const res = dotPathToHash('one.two.three', '.', '', { one: { twenty: '' } }) + assert.deepEqual(res, { one: { two: { three: '' }, twenty: '' } }) + done() + }) + + it('handles a different separator', function (done) { + const res = dotPathToHash('one_two_three.', '_') + assert.deepEqual(res, { one: { two: { 'three.': '' } } }) + done() + }) +}) diff --git a/test/helpers/mergeHashes.test.js b/test/helpers/mergeHashes.test.js new file mode 100644 index 00000000..c42fafad --- /dev/null +++ b/test/helpers/mergeHashes.test.js @@ -0,0 +1,157 @@ +import { assert } from 'chai' +import { mergeHashes } from '../../src/helpers' + +describe('mergeHashes helper function', function () { + it('replaces empty `target` keys with `source`', function (done) { + const source = { key1: 'value1' } + const target = { key1: '' } + const res = mergeHashes(source, target) + + assert.deepEqual(res.new, { key1: 'value1' }) + assert.deepEqual(res.old, {}) + done() + }) + + it('does not replaces empty `target` keys with `source` if it is a hash', function (done) { + const source = { key1: { key11: 'value1'} } + const target = { key1: '' } + const res = mergeHashes(source, target) + + assert.deepEqual(res.new, { key1: '' }) + assert.deepEqual(res.old, { key1: { key11: 'value1' } }) + done() +}) + + it('keeps `target` keys not in `source`', function (done) { + const source = { key1: 'value1' } + const target = { key1: '', key2: '' } + const res = mergeHashes(source, target) + + assert.deepEqual(res.new, { key1: 'value1', key2: '' }) + assert.deepEqual(res.old, {}) + done() + }) + + it('stores into `old` the keys from `source` that are not in `target`', function (done) { + const source = { key1: 'value1', key2: 'value2' } + const target = { key1: '' } + const res = mergeHashes(source, target) + + assert.deepEqual(res.new, { key1: 'value1' }) + assert.deepEqual(res.old, { key2: 'value2' }) + done() + }) + + it('copies `source` keys to `target` regardless of presence when keepRemoved is enabled', function (done) { + const source = { key1: 'value1', key2: 'value2' } + const target = { key1: '', key3: '' } + const res = mergeHashes(source, target, null, true) + + assert.deepEqual(res.new, { key1: 'value1', key2: 'value2', key3: '' }) + assert.deepEqual(res.old, { key2: 'value2' }) + done() + }) + + it('restores plural keys when the singular one exists', function (done) { + const source = { key1: '', key1_plural: 'value1' } + const target = { key1: '' } + const res = mergeHashes(source, target) + + assert.deepEqual(res.new, { key1: '', key1_plural: 'value1' }) + assert.deepEqual(res.old, {}) + done() + }) + + it('does not restores plural keys when the singular one does not', function (done) { + const source = { key1: '', key1_plural: 'value1' } + const target = { key2: '' } + const res = mergeHashes(source, target) + + assert.deepEqual(res.new, { key2: '' }) + assert.deepEqual(res.old, { key1: '', key1_plural: 'value1' }) + done() + }) + + it('restores context keys when the singular one exists', function (done) { + const source = { key1: '', key1_context: 'value1' } + const target = { key1: '' } + const res = mergeHashes(source, target) + + assert.deepEqual(res.new, { key1: '', key1_context: 'value1' }) + assert.deepEqual(res.old, {}) + done() + }) + + it('does not restores context keys when the singular one does not', function (done) { + const source = { key1: '', key1_context: 'value1' } + const target = { key2: '' } + const res = mergeHashes(source, target) + + assert.deepEqual(res.new, { key2: '' }) + assert.deepEqual(res.old, { key1: '', key1_context: 'value1' }) + done() + }) + + it('works with deep objects', function (done) { + const source = { + key1: 'value1', + key2: { + key21: 'value21', + key22: { + key221: 'value221', + key222: 'value222' + }, + key23: 'value23' + } + } + const target = { + key1: '', + key2: { + key21: '', + key22: { + key222: '', + key223: '' + }, + key24: '' + }, + key3: '' + } + + const res = mergeHashes(source, target) + + const expected_target = { + key1: 'value1', + key2: { + key21: 'value21', + key22: { + key222: 'value222', + key223: '' + }, + key24: '' + }, + key3: '' + } + + const expected_old = { + key2: { + key22: { + key221: 'value221' + }, + key23: 'value23' + } + } + + assert.deepEqual(res.new, expected_target) + assert.deepEqual(res.old, expected_old) + done() + }) + + it('leaves arrays of values (multiline) untouched', function (done) { + const source = { key1: ['Line one.', 'Line two.'] } + const target = { key1: '' } + const res = mergeHashes(source, target) + + assert.deepEqual(res.new, { key1: ['Line one.', 'Line two.'] }) + done() + }) +}) diff --git a/test/helpers/populateHash.test.js b/test/helpers/populateHash.test.js new file mode 100644 index 00000000..a394c618 --- /dev/null +++ b/test/helpers/populateHash.test.js @@ -0,0 +1,73 @@ +import { assert } from 'chai' +import { populateHash } from '../../src/helpers' + +describe('populateHash helper function', function () { + it('replaces `target` empty keys with `source` ones', function (done) { + const source = { key1: 'value1' } + const target = { key1: '' } + const res = populateHash(source, target) + + assert.deepEqual(res, { key1: 'value1' }) + done() + }) + + it('leaves untouched `target` keys that are not empty', function (done) { + const source = { key1: 'value1' } + const target = { key1: 'value2' } + const res = populateHash(source, target) + + assert.deepEqual(res, { key1: 'value2' }) + done() + }) + + it('leaves untouched `target` keys not in `source`', function (done) { + const source = { key1: 'value1' } + const target = { key1: '', key2: '' } + const res = populateHash(source, target) + + assert.deepEqual(res, { key1: 'value1', key2: '' }) + done() + }) + + it('works with deep objects', function (done) { + const source = { + key1: 'value1', + key2: { + key21: 'value21', + key22: { + key221: 'value221', + key222: 'value222' + }, + key23: 'value23' + } + } + const target = { + key1: '', + key2: { + key21: '', + key22: { + key222: '', + key223: '' + }, + key24: '' + }, + key3: '' + } + const res = populateHash(source, target) + const expected_target = { + key1: 'value1', + key2: { + key21: 'value21', + key22: { + key222: 'value222', + key223: '' + }, + key24: '' + }, + key3: '' + } + + assert.deepEqual(res, expected_target) + done() + }) +}) diff --git a/test/parser.js b/test/parser.js index d66b393f..6f9ffbe4 100644 --- a/test/parser.js +++ b/test/parser.js @@ -1,3 +1,10 @@ +var fs = require('fs'); +var path = require('path'); +var assert = require('assert'); +var File = require('vinyl'); +var through = require('through2'); +var Parser = require('../index'); + describe('parser', function () { it('parses globally on multiple lines', function (done) { var result; diff --git a/test/test.js b/test/test.js deleted file mode 100644 index 94001027..00000000 --- a/test/test.js +++ /dev/null @@ -1,17 +0,0 @@ -var fs = require('fs'); -var path = require('path'); -var assert = require('assert'); -var File = require('vinyl'); -var through = require('through2'); -var Parser = require('../index'); -var helpers = require('../src/helpers'); -var hashFromString = helpers.hashFromString; -var mergeHash = helpers.mergeHash; -var replaceEmpty = helpers.replaceEmpty; - - -describe('i18next-parser', function () { - /* jshint evil:true */ - eval(fs.readFileSync(__dirname+'/parser.js')+''); - eval(fs.readFileSync(__dirname+'/helpers.js')+''); -}); diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 00000000..f2e455ff --- /dev/null +++ b/yarn.lock @@ -0,0 +1,1521 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +ansi-gray@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/ansi-gray/-/ansi-gray-0.1.1.tgz#2962cf54ec9792c48510a3deb524436861ef7251" + dependencies: + ansi-wrap "0.1.0" + +ansi-regex@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" + +ansi-styles@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" + +ansi-wrap@0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/ansi-wrap/-/ansi-wrap-0.1.0.tgz#a82250ddb0015e9a27ca82e82ea603bbfa45efaf" + +append-buffer@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/append-buffer/-/append-buffer-1.0.2.tgz#d8220cf466081525efea50614f3de6514dfa58f1" + dependencies: + buffer-equal "^1.0.0" + +array-differ@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/array-differ/-/array-differ-1.0.0.tgz#eff52e3758249d33be402b8bb8e564bb2b5d4031" + +array-uniq@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6" + +assertion-error@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b" + +babel-code-frame@^6.26.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b" + dependencies: + chalk "^1.1.3" + esutils "^2.0.2" + js-tokens "^3.0.2" + +babel-core@^6.26.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-6.26.0.tgz#af32f78b31a6fcef119c87b0fd8d9753f03a0bb8" + dependencies: + babel-code-frame "^6.26.0" + babel-generator "^6.26.0" + babel-helpers "^6.24.1" + babel-messages "^6.23.0" + babel-register "^6.26.0" + babel-runtime "^6.26.0" + babel-template "^6.26.0" + babel-traverse "^6.26.0" + babel-types "^6.26.0" + babylon "^6.18.0" + convert-source-map "^1.5.0" + debug "^2.6.8" + json5 "^0.5.1" + lodash "^4.17.4" + minimatch "^3.0.4" + path-is-absolute "^1.0.1" + private "^0.1.7" + slash "^1.0.0" + source-map "^0.5.6" + +babel-generator@^6.26.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.26.0.tgz#ac1ae20070b79f6e3ca1d3269613053774f20dc5" + dependencies: + babel-messages "^6.23.0" + babel-runtime "^6.26.0" + babel-types "^6.26.0" + detect-indent "^4.0.0" + jsesc "^1.3.0" + lodash "^4.17.4" + source-map "^0.5.6" + trim-right "^1.0.1" + +babel-helper-builder-binary-assignment-operator-visitor@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-builder-binary-assignment-operator-visitor/-/babel-helper-builder-binary-assignment-operator-visitor-6.24.1.tgz#cce4517ada356f4220bcae8a02c2b346f9a56664" + dependencies: + babel-helper-explode-assignable-expression "^6.24.1" + babel-runtime "^6.22.0" + babel-types "^6.24.1" + +babel-helper-call-delegate@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-call-delegate/-/babel-helper-call-delegate-6.24.1.tgz#ece6aacddc76e41c3461f88bfc575bd0daa2df8d" + dependencies: + babel-helper-hoist-variables "^6.24.1" + babel-runtime "^6.22.0" + babel-traverse "^6.24.1" + babel-types "^6.24.1" + +babel-helper-define-map@^6.24.1: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-helper-define-map/-/babel-helper-define-map-6.26.0.tgz#a5f56dab41a25f97ecb498c7ebaca9819f95be5f" + dependencies: + babel-helper-function-name "^6.24.1" + babel-runtime "^6.26.0" + babel-types "^6.26.0" + lodash "^4.17.4" + +babel-helper-explode-assignable-expression@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-explode-assignable-expression/-/babel-helper-explode-assignable-expression-6.24.1.tgz#f25b82cf7dc10433c55f70592d5746400ac22caa" + dependencies: + babel-runtime "^6.22.0" + babel-traverse "^6.24.1" + babel-types "^6.24.1" + +babel-helper-function-name@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-function-name/-/babel-helper-function-name-6.24.1.tgz#d3475b8c03ed98242a25b48351ab18399d3580a9" + dependencies: + babel-helper-get-function-arity "^6.24.1" + babel-runtime "^6.22.0" + babel-template "^6.24.1" + babel-traverse "^6.24.1" + babel-types "^6.24.1" + +babel-helper-get-function-arity@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.24.1.tgz#8f7782aa93407c41d3aa50908f89b031b1b6853d" + dependencies: + babel-runtime "^6.22.0" + babel-types "^6.24.1" + +babel-helper-hoist-variables@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-hoist-variables/-/babel-helper-hoist-variables-6.24.1.tgz#1ecb27689c9d25513eadbc9914a73f5408be7a76" + dependencies: + babel-runtime "^6.22.0" + babel-types "^6.24.1" + +babel-helper-optimise-call-expression@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-optimise-call-expression/-/babel-helper-optimise-call-expression-6.24.1.tgz#f7a13427ba9f73f8f4fa993c54a97882d1244257" + dependencies: + babel-runtime "^6.22.0" + babel-types "^6.24.1" + +babel-helper-regex@^6.24.1: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-helper-regex/-/babel-helper-regex-6.26.0.tgz#325c59f902f82f24b74faceed0363954f6495e72" + dependencies: + babel-runtime "^6.26.0" + babel-types "^6.26.0" + lodash "^4.17.4" + +babel-helper-remap-async-to-generator@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-remap-async-to-generator/-/babel-helper-remap-async-to-generator-6.24.1.tgz#5ec581827ad723fecdd381f1c928390676e4551b" + dependencies: + babel-helper-function-name "^6.24.1" + babel-runtime "^6.22.0" + babel-template "^6.24.1" + babel-traverse "^6.24.1" + babel-types "^6.24.1" + +babel-helper-replace-supers@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-replace-supers/-/babel-helper-replace-supers-6.24.1.tgz#bf6dbfe43938d17369a213ca8a8bf74b6a90ab1a" + dependencies: + babel-helper-optimise-call-expression "^6.24.1" + babel-messages "^6.23.0" + babel-runtime "^6.22.0" + babel-template "^6.24.1" + babel-traverse "^6.24.1" + babel-types "^6.24.1" + +babel-helpers@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helpers/-/babel-helpers-6.24.1.tgz#3471de9caec388e5c850e597e58a26ddf37602b2" + dependencies: + babel-runtime "^6.22.0" + babel-template "^6.24.1" + +babel-messages@^6.23.0: + version "6.23.0" + resolved "https://registry.yarnpkg.com/babel-messages/-/babel-messages-6.23.0.tgz#f3cdf4703858035b2a2951c6ec5edf6c62f2630e" + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-check-es2015-constants@^6.22.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-check-es2015-constants/-/babel-plugin-check-es2015-constants-6.22.0.tgz#35157b101426fd2ffd3da3f75c7d1e91835bbf8a" + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-syntax-async-functions@^6.8.0: + version "6.13.0" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz#cad9cad1191b5ad634bf30ae0872391e0647be95" + +babel-plugin-syntax-exponentiation-operator@^6.8.0: + version "6.13.0" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-exponentiation-operator/-/babel-plugin-syntax-exponentiation-operator-6.13.0.tgz#9ee7e8337290da95288201a6a57f4170317830de" + +babel-plugin-syntax-trailing-function-commas@^6.22.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-6.22.0.tgz#ba0360937f8d06e40180a43fe0d5616fff532cf3" + +babel-plugin-transform-async-to-generator@^6.22.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-async-to-generator/-/babel-plugin-transform-async-to-generator-6.24.1.tgz#6536e378aff6cb1d5517ac0e40eb3e9fc8d08761" + dependencies: + babel-helper-remap-async-to-generator "^6.24.1" + babel-plugin-syntax-async-functions "^6.8.0" + babel-runtime "^6.22.0" + +babel-plugin-transform-es2015-arrow-functions@^6.22.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-arrow-functions/-/babel-plugin-transform-es2015-arrow-functions-6.22.0.tgz#452692cb711d5f79dc7f85e440ce41b9f244d221" + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-transform-es2015-block-scoped-functions@^6.22.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-block-scoped-functions/-/babel-plugin-transform-es2015-block-scoped-functions-6.22.0.tgz#bbc51b49f964d70cb8d8e0b94e820246ce3a6141" + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-transform-es2015-block-scoping@^6.23.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-block-scoping/-/babel-plugin-transform-es2015-block-scoping-6.26.0.tgz#d70f5299c1308d05c12f463813b0a09e73b1895f" + dependencies: + babel-runtime "^6.26.0" + babel-template "^6.26.0" + babel-traverse "^6.26.0" + babel-types "^6.26.0" + lodash "^4.17.4" + +babel-plugin-transform-es2015-classes@^6.23.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-classes/-/babel-plugin-transform-es2015-classes-6.24.1.tgz#5a4c58a50c9c9461e564b4b2a3bfabc97a2584db" + dependencies: + babel-helper-define-map "^6.24.1" + babel-helper-function-name "^6.24.1" + babel-helper-optimise-call-expression "^6.24.1" + babel-helper-replace-supers "^6.24.1" + babel-messages "^6.23.0" + babel-runtime "^6.22.0" + babel-template "^6.24.1" + babel-traverse "^6.24.1" + babel-types "^6.24.1" + +babel-plugin-transform-es2015-computed-properties@^6.22.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-computed-properties/-/babel-plugin-transform-es2015-computed-properties-6.24.1.tgz#6fe2a8d16895d5634f4cd999b6d3480a308159b3" + dependencies: + babel-runtime "^6.22.0" + babel-template "^6.24.1" + +babel-plugin-transform-es2015-destructuring@^6.23.0: + version "6.23.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-destructuring/-/babel-plugin-transform-es2015-destructuring-6.23.0.tgz#997bb1f1ab967f682d2b0876fe358d60e765c56d" + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-transform-es2015-duplicate-keys@^6.22.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-duplicate-keys/-/babel-plugin-transform-es2015-duplicate-keys-6.24.1.tgz#73eb3d310ca969e3ef9ec91c53741a6f1576423e" + dependencies: + babel-runtime "^6.22.0" + babel-types "^6.24.1" + +babel-plugin-transform-es2015-for-of@^6.23.0: + version "6.23.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-for-of/-/babel-plugin-transform-es2015-for-of-6.23.0.tgz#f47c95b2b613df1d3ecc2fdb7573623c75248691" + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-transform-es2015-function-name@^6.22.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-function-name/-/babel-plugin-transform-es2015-function-name-6.24.1.tgz#834c89853bc36b1af0f3a4c5dbaa94fd8eacaa8b" + dependencies: + babel-helper-function-name "^6.24.1" + babel-runtime "^6.22.0" + babel-types "^6.24.1" + +babel-plugin-transform-es2015-literals@^6.22.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-literals/-/babel-plugin-transform-es2015-literals-6.22.0.tgz#4f54a02d6cd66cf915280019a31d31925377ca2e" + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-transform-es2015-modules-amd@^6.22.0, babel-plugin-transform-es2015-modules-amd@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-amd/-/babel-plugin-transform-es2015-modules-amd-6.24.1.tgz#3b3e54017239842d6d19c3011c4bd2f00a00d154" + dependencies: + babel-plugin-transform-es2015-modules-commonjs "^6.24.1" + babel-runtime "^6.22.0" + babel-template "^6.24.1" + +babel-plugin-transform-es2015-modules-commonjs@^6.23.0, babel-plugin-transform-es2015-modules-commonjs@^6.24.1: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.0.tgz#0d8394029b7dc6abe1a97ef181e00758dd2e5d8a" + dependencies: + babel-plugin-transform-strict-mode "^6.24.1" + babel-runtime "^6.26.0" + babel-template "^6.26.0" + babel-types "^6.26.0" + +babel-plugin-transform-es2015-modules-systemjs@^6.23.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-systemjs/-/babel-plugin-transform-es2015-modules-systemjs-6.24.1.tgz#ff89a142b9119a906195f5f106ecf305d9407d23" + dependencies: + babel-helper-hoist-variables "^6.24.1" + babel-runtime "^6.22.0" + babel-template "^6.24.1" + +babel-plugin-transform-es2015-modules-umd@^6.23.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-umd/-/babel-plugin-transform-es2015-modules-umd-6.24.1.tgz#ac997e6285cd18ed6176adb607d602344ad38468" + dependencies: + babel-plugin-transform-es2015-modules-amd "^6.24.1" + babel-runtime "^6.22.0" + babel-template "^6.24.1" + +babel-plugin-transform-es2015-object-super@^6.22.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-object-super/-/babel-plugin-transform-es2015-object-super-6.24.1.tgz#24cef69ae21cb83a7f8603dad021f572eb278f8d" + dependencies: + babel-helper-replace-supers "^6.24.1" + babel-runtime "^6.22.0" + +babel-plugin-transform-es2015-parameters@^6.23.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-parameters/-/babel-plugin-transform-es2015-parameters-6.24.1.tgz#57ac351ab49caf14a97cd13b09f66fdf0a625f2b" + dependencies: + babel-helper-call-delegate "^6.24.1" + babel-helper-get-function-arity "^6.24.1" + babel-runtime "^6.22.0" + babel-template "^6.24.1" + babel-traverse "^6.24.1" + babel-types "^6.24.1" + +babel-plugin-transform-es2015-shorthand-properties@^6.22.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-shorthand-properties/-/babel-plugin-transform-es2015-shorthand-properties-6.24.1.tgz#24f875d6721c87661bbd99a4622e51f14de38aa0" + dependencies: + babel-runtime "^6.22.0" + babel-types "^6.24.1" + +babel-plugin-transform-es2015-spread@^6.22.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-spread/-/babel-plugin-transform-es2015-spread-6.22.0.tgz#d6d68a99f89aedc4536c81a542e8dd9f1746f8d1" + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-transform-es2015-sticky-regex@^6.22.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-sticky-regex/-/babel-plugin-transform-es2015-sticky-regex-6.24.1.tgz#00c1cdb1aca71112cdf0cf6126c2ed6b457ccdbc" + dependencies: + babel-helper-regex "^6.24.1" + babel-runtime "^6.22.0" + babel-types "^6.24.1" + +babel-plugin-transform-es2015-template-literals@^6.22.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-template-literals/-/babel-plugin-transform-es2015-template-literals-6.22.0.tgz#a84b3450f7e9f8f1f6839d6d687da84bb1236d8d" + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-transform-es2015-typeof-symbol@^6.23.0: + version "6.23.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-typeof-symbol/-/babel-plugin-transform-es2015-typeof-symbol-6.23.0.tgz#dec09f1cddff94b52ac73d505c84df59dcceb372" + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-transform-es2015-unicode-regex@^6.22.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-unicode-regex/-/babel-plugin-transform-es2015-unicode-regex-6.24.1.tgz#d38b12f42ea7323f729387f18a7c5ae1faeb35e9" + dependencies: + babel-helper-regex "^6.24.1" + babel-runtime "^6.22.0" + regexpu-core "^2.0.0" + +babel-plugin-transform-exponentiation-operator@^6.22.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-exponentiation-operator/-/babel-plugin-transform-exponentiation-operator-6.24.1.tgz#2ab0c9c7f3098fa48907772bb813fe41e8de3a0e" + dependencies: + babel-helper-builder-binary-assignment-operator-visitor "^6.24.1" + babel-plugin-syntax-exponentiation-operator "^6.8.0" + babel-runtime "^6.22.0" + +babel-plugin-transform-regenerator@^6.22.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.26.0.tgz#e0703696fbde27f0a3efcacf8b4dca2f7b3a8f2f" + dependencies: + regenerator-transform "^0.10.0" + +babel-plugin-transform-strict-mode@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.24.1.tgz#d5faf7aa578a65bbe591cf5edae04a0c67020758" + dependencies: + babel-runtime "^6.22.0" + babel-types "^6.24.1" + +babel-polyfill@^6.26.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-polyfill/-/babel-polyfill-6.26.0.tgz#379937abc67d7895970adc621f284cd966cf2153" + dependencies: + babel-runtime "^6.26.0" + core-js "^2.5.0" + regenerator-runtime "^0.10.5" + +babel-preset-env@^1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/babel-preset-env/-/babel-preset-env-1.6.1.tgz#a18b564cc9b9afdf4aae57ae3c1b0d99188e6f48" + dependencies: + babel-plugin-check-es2015-constants "^6.22.0" + babel-plugin-syntax-trailing-function-commas "^6.22.0" + babel-plugin-transform-async-to-generator "^6.22.0" + babel-plugin-transform-es2015-arrow-functions "^6.22.0" + babel-plugin-transform-es2015-block-scoped-functions "^6.22.0" + babel-plugin-transform-es2015-block-scoping "^6.23.0" + babel-plugin-transform-es2015-classes "^6.23.0" + babel-plugin-transform-es2015-computed-properties "^6.22.0" + babel-plugin-transform-es2015-destructuring "^6.23.0" + babel-plugin-transform-es2015-duplicate-keys "^6.22.0" + babel-plugin-transform-es2015-for-of "^6.23.0" + babel-plugin-transform-es2015-function-name "^6.22.0" + babel-plugin-transform-es2015-literals "^6.22.0" + babel-plugin-transform-es2015-modules-amd "^6.22.0" + babel-plugin-transform-es2015-modules-commonjs "^6.23.0" + babel-plugin-transform-es2015-modules-systemjs "^6.23.0" + babel-plugin-transform-es2015-modules-umd "^6.23.0" + babel-plugin-transform-es2015-object-super "^6.22.0" + babel-plugin-transform-es2015-parameters "^6.23.0" + babel-plugin-transform-es2015-shorthand-properties "^6.22.0" + babel-plugin-transform-es2015-spread "^6.22.0" + babel-plugin-transform-es2015-sticky-regex "^6.22.0" + babel-plugin-transform-es2015-template-literals "^6.22.0" + babel-plugin-transform-es2015-typeof-symbol "^6.23.0" + babel-plugin-transform-es2015-unicode-regex "^6.22.0" + babel-plugin-transform-exponentiation-operator "^6.22.0" + babel-plugin-transform-regenerator "^6.22.0" + browserslist "^2.1.2" + invariant "^2.2.2" + semver "^5.3.0" + +babel-register@^6.26.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-register/-/babel-register-6.26.0.tgz#6ed021173e2fcb486d7acb45c6009a856f647071" + dependencies: + babel-core "^6.26.0" + babel-runtime "^6.26.0" + core-js "^2.5.0" + home-or-tmp "^2.0.0" + lodash "^4.17.4" + mkdirp "^0.5.1" + source-map-support "^0.4.15" + +babel-runtime@^6.18.0, babel-runtime@^6.22.0, babel-runtime@^6.26.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe" + dependencies: + core-js "^2.4.0" + regenerator-runtime "^0.11.0" + +babel-template@^6.24.1, babel-template@^6.26.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-template/-/babel-template-6.26.0.tgz#de03e2d16396b069f46dd9fff8521fb1a0e35e02" + dependencies: + babel-runtime "^6.26.0" + babel-traverse "^6.26.0" + babel-types "^6.26.0" + babylon "^6.18.0" + lodash "^4.17.4" + +babel-traverse@^6.24.1, babel-traverse@^6.26.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-traverse/-/babel-traverse-6.26.0.tgz#46a9cbd7edcc62c8e5c064e2d2d8d0f4035766ee" + dependencies: + babel-code-frame "^6.26.0" + babel-messages "^6.23.0" + babel-runtime "^6.26.0" + babel-types "^6.26.0" + babylon "^6.18.0" + debug "^2.6.8" + globals "^9.18.0" + invariant "^2.2.2" + lodash "^4.17.4" + +babel-types@^6.19.0, babel-types@^6.24.1, babel-types@^6.26.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-6.26.0.tgz#a3b073f94ab49eb6fa55cd65227a334380632497" + dependencies: + babel-runtime "^6.26.0" + esutils "^2.0.2" + lodash "^4.17.4" + to-fast-properties "^1.0.3" + +babylon@^6.18.0: + version "6.18.0" + resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.18.0.tgz#af2f3b88fa6f5c1e4c634d1a0f8eac4f55b395e3" + +balanced-match@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" + +beeper@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/beeper/-/beeper-1.1.1.tgz#e6d5ea8c5dad001304a70b22638447f69cb2f809" + +brace-expansion@^1.1.7: + version "1.1.8" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.8.tgz#c07b211c7c952ec1f8efd51a77ef0d1d3990a292" + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +browser-stdout@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.0.tgz#f351d32969d32fa5d7a5567154263d928ae3bd1f" + +browserslist@^2.1.2: + version "2.11.3" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-2.11.3.tgz#fe36167aed1bbcde4827ebfe71347a2cc70b99b2" + dependencies: + caniuse-lite "^1.0.30000792" + electron-to-chromium "^1.3.30" + +buffer-equal@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/buffer-equal/-/buffer-equal-1.0.0.tgz#59616b498304d556abd466966b22eeda3eca5fbe" + +caniuse-lite@^1.0.30000792: + version "1.0.30000792" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000792.tgz#d0cea981f8118f3961471afbb43c9a1e5bbf0332" + +chai@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chai/-/chai-4.1.2.tgz#0f64584ba642f0f2ace2806279f4f06ca23ad73c" + dependencies: + assertion-error "^1.0.1" + check-error "^1.0.1" + deep-eql "^3.0.0" + get-func-name "^2.0.0" + pathval "^1.0.0" + type-detect "^4.0.0" + +chalk@^1.0.0, chalk@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" + dependencies: + ansi-styles "^2.2.1" + escape-string-regexp "^1.0.2" + has-ansi "^2.0.0" + strip-ansi "^3.0.0" + supports-color "^2.0.0" + +check-error@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82" + +clone-buffer@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/clone-buffer/-/clone-buffer-1.0.0.tgz#e3e25b207ac4e701af721e2cb5a16792cac3dc58" + +clone-stats@^0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/clone-stats/-/clone-stats-0.0.1.tgz#b88f94a82cf38b8791d58046ea4029ad88ca99d1" + +clone-stats@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/clone-stats/-/clone-stats-1.0.0.tgz#b3782dff8bb5474e18b9b6bf0fdfe782f8777680" + +clone@^1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.3.tgz#298d7e2231660f40c003c2ed3140decf3f53085f" + +clone@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.1.tgz#d217d1e961118e3ac9a4b8bba3285553bf647cdb" + +cloneable-readable@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/cloneable-readable/-/cloneable-readable-1.0.0.tgz#a6290d413f217a61232f95e458ff38418cfb0117" + dependencies: + inherits "^2.0.1" + process-nextick-args "^1.0.6" + through2 "^2.0.1" + +color-support@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2" + +colors@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/colors/-/colors-1.1.2.tgz#168a4701756b6a7f51a12ce0c97bfa28c084ed63" + +commander@2.11.0: + version "2.11.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.11.0.tgz#157152fd1e7a6c8d98a5b715cf376df928004563" + +commander@~2.9.0: + version "2.9.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.9.0.tgz#9c99094176e12240cb22d6c5146098400fe0f7d4" + dependencies: + graceful-readlink ">= 1.0.0" + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + +concat-stream@~1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.0.tgz#0aac662fd52be78964d5532f694784e70110acf7" + dependencies: + inherits "^2.0.3" + readable-stream "^2.2.2" + typedarray "^0.0.6" + +convert-source-map@^1.5.0: + version "1.5.1" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.5.1.tgz#b8278097b9bc229365de5c62cf5fcaed8b5599e5" + +core-js@^2.4.0, core-js@^2.5.0: + version "2.5.3" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.3.tgz#8acc38345824f16d8365b7c9b4259168e8ed603e" + +core-util-is@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" + +dateformat@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-2.2.0.tgz#4065e2013cf9fb916ddfd82efb506ad4c6769062" + +debug@3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" + dependencies: + ms "2.0.0" + +debug@^2.6.8: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + dependencies: + ms "2.0.0" + +deep-eql@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-3.0.1.tgz#dfc9404400ad1c8fe023e7da1df1c147c4b444df" + dependencies: + type-detect "^4.0.0" + +define-properties@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.2.tgz#83a73f2fea569898fb737193c8f873caf6d45c94" + dependencies: + foreach "^2.0.5" + object-keys "^1.0.8" + +detect-indent@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-4.0.0.tgz#f76d064352cdf43a1cb6ce619c4ee3a9475de208" + dependencies: + repeating "^2.0.0" + +diff@3.3.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/diff/-/diff-3.3.1.tgz#aa8567a6eed03c531fc89d3f711cd0e5259dec75" + +duplexer2@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.0.2.tgz#c614dcf67e2fb14995a91711e5a617e8a60a31db" + dependencies: + readable-stream "~1.1.9" + +duplexify@^3.5.3: + version "3.5.3" + resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.5.3.tgz#8b5818800df92fd0125b27ab896491912858243e" + dependencies: + end-of-stream "^1.0.0" + inherits "^2.0.1" + readable-stream "^2.0.0" + stream-shift "^1.0.0" + +electron-to-chromium@^1.3.30: + version "1.3.31" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.31.tgz#00d832cba9fe2358652b0c48a8816c8e3a037e9f" + +end-of-stream@^1.0.0, end-of-stream@^1.1.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.1.tgz#ed29634d19baba463b6ce6b80a37213eab71ec43" + dependencies: + once "^1.4.0" + +eol@^0.9.1: + version "0.9.1" + resolved "https://registry.yarnpkg.com/eol/-/eol-0.9.1.tgz#f701912f504074be35c6117a5c4ade49cd547acd" + +escape-string-regexp@1.0.5, escape-string-regexp@^1.0.2: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + +esutils@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b" + +extend@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.1.tgz#a755ea7bc1adfcc5a31ce7e762dbaadc5e636444" + +fancy-log@^1.1.0: + version "1.3.2" + resolved "https://registry.yarnpkg.com/fancy-log/-/fancy-log-1.3.2.tgz#f41125e3d84f2e7d89a43d06d958c8f78be16be1" + dependencies: + ansi-gray "^0.1.1" + color-support "^1.1.3" + time-stamp "^1.0.0" + +flush-write-stream@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.0.2.tgz#c81b90d8746766f1a609a46809946c45dd8ae417" + dependencies: + inherits "^2.0.1" + readable-stream "^2.0.4" + +foreach@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99" + +fs-mkdirp-stream@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs-mkdirp-stream/-/fs-mkdirp-stream-1.0.0.tgz#0b7815fc3201c6a69e14db98ce098c16935259eb" + dependencies: + graceful-fs "^4.1.11" + through2 "^2.0.3" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + +get-func-name@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.0.tgz#ead774abee72e20409433a066366023dd6887a41" + +glob-parent@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae" + dependencies: + is-glob "^3.1.0" + path-dirname "^1.0.0" + +glob-stream@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/glob-stream/-/glob-stream-6.1.0.tgz#7045c99413b3eb94888d83ab46d0b404cc7bdde4" + dependencies: + extend "^3.0.0" + glob "^7.1.1" + glob-parent "^3.1.0" + is-negated-glob "^1.0.0" + ordered-read-streams "^1.0.0" + pumpify "^1.3.5" + readable-stream "^2.1.5" + remove-trailing-separator "^1.0.1" + to-absolute-glob "^2.0.0" + unique-stream "^2.0.2" + +glob@7.1.2, glob@^7.1.1: + version "7.1.2" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +globals@^9.18.0: + version "9.18.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a" + +glogg@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/glogg/-/glogg-1.0.1.tgz#dcf758e44789cc3f3d32c1f3562a3676e6a34810" + dependencies: + sparkles "^1.0.0" + +graceful-fs@^4.0.0, graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6: + version "4.1.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" + +"graceful-readlink@>= 1.0.0": + version "1.0.1" + resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725" + +growl@1.10.3: + version "1.10.3" + resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.3.tgz#1926ba90cf3edfe2adb4927f5880bc22c66c790f" + +gulp-util@~3.0.7: + version "3.0.8" + resolved "https://registry.yarnpkg.com/gulp-util/-/gulp-util-3.0.8.tgz#0054e1e744502e27c04c187c3ecc505dd54bbb4f" + dependencies: + array-differ "^1.0.0" + array-uniq "^1.0.2" + beeper "^1.0.0" + chalk "^1.0.0" + dateformat "^2.0.0" + fancy-log "^1.1.0" + gulplog "^1.0.0" + has-gulplog "^0.1.0" + lodash._reescape "^3.0.0" + lodash._reevaluate "^3.0.0" + lodash._reinterpolate "^3.0.0" + lodash.template "^3.0.0" + minimist "^1.1.0" + multipipe "^0.1.2" + object-assign "^3.0.0" + replace-ext "0.0.1" + through2 "^2.0.0" + vinyl "^0.5.0" + +gulplog@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/gulplog/-/gulplog-1.0.0.tgz#e28c4d45d05ecbbed818363ce8f9c5926229ffe5" + dependencies: + glogg "^1.0.0" + +has-ansi@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" + dependencies: + ansi-regex "^2.0.0" + +has-flag@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-2.0.0.tgz#e8207af1cc7b30d446cc70b734b5e8be18f88d51" + +has-gulplog@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/has-gulplog/-/has-gulplog-0.1.0.tgz#6414c82913697da51590397dafb12f22967811ce" + dependencies: + sparkles "^1.0.0" + +has-symbols@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.0.tgz#ba1a8f1af2a0fc39650f5c850367704122063b44" + +he@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd" + +home-or-tmp@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/home-or-tmp/-/home-or-tmp-2.0.0.tgz#e36c3f2d2cae7d746a857e38d18d5f32a7882db8" + dependencies: + os-homedir "^1.0.0" + os-tmpdir "^1.0.1" + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.1, inherits@~2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" + +invariant@^2.2.2: + version "2.2.2" + resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.2.tgz#9e1f56ac0acdb6bf303306f338be3b204ae60360" + dependencies: + loose-envify "^1.0.0" + +is-absolute@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-absolute/-/is-absolute-1.0.0.tgz#395e1ae84b11f26ad1795e73c17378e48a301576" + dependencies: + is-relative "^1.0.0" + is-windows "^1.0.1" + +is-buffer@^1.1.5: + version "1.1.6" + resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" + +is-extglob@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + +is-finite@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-finite/-/is-finite-1.0.2.tgz#cc6677695602be550ef11e8b4aa6305342b6d0aa" + dependencies: + number-is-nan "^1.0.0" + +is-glob@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-3.1.0.tgz#7ba5ae24217804ac70707b96922567486cc3e84a" + dependencies: + is-extglob "^2.1.0" + +is-negated-glob@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-negated-glob/-/is-negated-glob-1.0.0.tgz#6910bca5da8c95e784b5751b976cf5a10fee36d2" + +is-relative@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-relative/-/is-relative-1.0.0.tgz#a1bb6935ce8c5dba1e8b9754b9b2dcc020e2260d" + dependencies: + is-unc-path "^1.0.0" + +is-stream@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" + +is-unc-path@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-unc-path/-/is-unc-path-1.0.0.tgz#d731e8898ed090a12c352ad2eaed5095ad322c9d" + dependencies: + unc-path-regex "^0.1.2" + +is-utf8@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72" + +is-valid-glob@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-valid-glob/-/is-valid-glob-1.0.0.tgz#29bf3eff701be2d4d315dbacc39bc39fe8f601aa" + +is-windows@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.1.tgz#310db70f742d259a16a369202b51af84233310d9" + +isarray@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" + +isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + +js-tokens@^3.0.0, js-tokens@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" + +jsesc@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-1.3.0.tgz#46c3fec8c1892b12b0833db9bc7622176dbab34b" + +jsesc@~0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" + +json-stable-stringify@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz#9a759d39c5f2ff503fd5300646ed445f88c4f9af" + dependencies: + jsonify "~0.0.0" + +json5@^0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821" + +jsonify@~0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" + +lazystream@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/lazystream/-/lazystream-1.0.0.tgz#f6995fe0f820392f61396be89462407bb77168e4" + dependencies: + readable-stream "^2.0.5" + +lead@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/lead/-/lead-1.0.0.tgz#6f14f99a37be3a9dd784f5495690e5903466ee42" + dependencies: + flush-write-stream "^1.0.2" + +lodash._basecopy@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz#8da0e6a876cf344c0ad8a54882111dd3c5c7ca36" + +lodash._basetostring@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/lodash._basetostring/-/lodash._basetostring-3.0.1.tgz#d1861d877f824a52f669832dcaf3ee15566a07d5" + +lodash._basevalues@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/lodash._basevalues/-/lodash._basevalues-3.0.0.tgz#5b775762802bde3d3297503e26300820fdf661b7" + +lodash._getnative@^3.0.0: + version "3.9.1" + resolved "https://registry.yarnpkg.com/lodash._getnative/-/lodash._getnative-3.9.1.tgz#570bc7dede46d61cdcde687d65d3eecbaa3aaff5" + +lodash._isiterateecall@^3.0.0: + version "3.0.9" + resolved "https://registry.yarnpkg.com/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz#5203ad7ba425fae842460e696db9cf3e6aac057c" + +lodash._reescape@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/lodash._reescape/-/lodash._reescape-3.0.0.tgz#2b1d6f5dfe07c8a355753e5f27fac7f1cde1616a" + +lodash._reevaluate@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/lodash._reevaluate/-/lodash._reevaluate-3.0.0.tgz#58bc74c40664953ae0b124d806996daca431e2ed" + +lodash._reinterpolate@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d" + +lodash._root@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/lodash._root/-/lodash._root-3.0.1.tgz#fba1c4524c19ee9a5f8136b4609f017cf4ded692" + +lodash.escape@^3.0.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/lodash.escape/-/lodash.escape-3.2.0.tgz#995ee0dc18c1b48cc92effae71a10aab5b487698" + dependencies: + lodash._root "^3.0.0" + +lodash.isarguments@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a" + +lodash.isarray@^3.0.0: + version "3.0.4" + resolved "https://registry.yarnpkg.com/lodash.isarray/-/lodash.isarray-3.0.4.tgz#79e4eb88c36a8122af86f844aa9bcd851b5fbb55" + +lodash.keys@^3.0.0: + version "3.1.2" + resolved "https://registry.yarnpkg.com/lodash.keys/-/lodash.keys-3.1.2.tgz#4dbc0472b156be50a0b286855d1bd0b0c656098a" + dependencies: + lodash._getnative "^3.0.0" + lodash.isarguments "^3.0.0" + lodash.isarray "^3.0.0" + +lodash.restparam@^3.0.0: + version "3.6.1" + resolved "https://registry.yarnpkg.com/lodash.restparam/-/lodash.restparam-3.6.1.tgz#936a4e309ef330a7645ed4145986c85ae5b20805" + +lodash.template@^3.0.0: + version "3.6.2" + resolved "https://registry.yarnpkg.com/lodash.template/-/lodash.template-3.6.2.tgz#f8cdecc6169a255be9098ae8b0c53d378931d14f" + dependencies: + lodash._basecopy "^3.0.0" + lodash._basetostring "^3.0.0" + lodash._basevalues "^3.0.0" + lodash._isiterateecall "^3.0.0" + lodash._reinterpolate "^3.0.0" + lodash.escape "^3.0.0" + lodash.keys "^3.0.0" + lodash.restparam "^3.0.0" + lodash.templatesettings "^3.0.0" + +lodash.templatesettings@^3.0.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/lodash.templatesettings/-/lodash.templatesettings-3.1.1.tgz#fb307844753b66b9f1afa54e262c745307dba8e5" + dependencies: + lodash._reinterpolate "^3.0.0" + lodash.escape "^3.0.0" + +lodash@^4.17.4, lodash@~4.17.3: + version "4.17.4" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" + +loose-envify@^1.0.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.3.1.tgz#d1a8ad33fa9ce0e713d65fdd0ac8b748d478c848" + dependencies: + js-tokens "^3.0.0" + +minimatch@^3.0.2, minimatch@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + dependencies: + brace-expansion "^1.1.7" + +minimist@0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" + +minimist@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" + +mkdirp@0.5.1, mkdirp@^0.5.1, mkdirp@~0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" + dependencies: + minimist "0.0.8" + +mocha@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/mocha/-/mocha-5.0.0.tgz#cccac988b0bc5477119cba0e43de7af6d6ad8f4e" + dependencies: + browser-stdout "1.3.0" + commander "2.11.0" + debug "3.1.0" + diff "3.3.1" + escape-string-regexp "1.0.5" + glob "7.1.2" + growl "1.10.3" + he "1.1.1" + mkdirp "0.5.1" + supports-color "4.4.0" + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + +multipipe@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/multipipe/-/multipipe-0.1.2.tgz#2a8f2ddf70eed564dff2d57f1e1a137d9f05078b" + dependencies: + duplexer2 "0.0.2" + +normalize-path@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" + dependencies: + remove-trailing-separator "^1.0.1" + +now-and-later@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/now-and-later/-/now-and-later-2.0.0.tgz#bc61cbb456d79cb32207ce47ca05136ff2e7d6ee" + dependencies: + once "^1.3.2" + +number-is-nan@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" + +object-assign@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-3.0.0.tgz#9bedd5ca0897949bca47e7ff408062d549f587f2" + +object-keys@^1.0.11, object-keys@^1.0.8: + version "1.0.11" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.0.11.tgz#c54601778ad560f1142ce0e01bcca8b56d13426d" + +object.assign@^4.0.4: + version "4.1.0" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.0.tgz#968bf1100d7956bb3ca086f006f846b3bc4008da" + dependencies: + define-properties "^1.1.2" + function-bind "^1.1.1" + has-symbols "^1.0.0" + object-keys "^1.0.11" + +once@^1.3.0, once@^1.3.1, once@^1.3.2, once@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + dependencies: + wrappy "1" + +ordered-read-streams@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/ordered-read-streams/-/ordered-read-streams-1.0.1.tgz#77c0cb37c41525d64166d990ffad7ec6a0e1363e" + dependencies: + readable-stream "^2.0.1" + +os-homedir@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" + +os-tmpdir@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" + +path-dirname@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/path-dirname/-/path-dirname-1.0.2.tgz#cc33d24d525e099a5388c0336c6e32b9160609e0" + +path-is-absolute@^1.0.0, path-is-absolute@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + +pathval@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.0.tgz#b942e6d4bde653005ef6b71361def8727d0645e0" + +private@^0.1.6, private@^0.1.7: + version "0.1.8" + resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff" + +process-nextick-args@^1.0.6, process-nextick-args@~1.0.6: + version "1.0.7" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3" + +pump@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/pump/-/pump-2.0.1.tgz#12399add6e4cf7526d973cbc8b5ce2e2908b3909" + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + +pumpify@^1.3.5: + version "1.4.0" + resolved "https://registry.yarnpkg.com/pumpify/-/pumpify-1.4.0.tgz#80b7c5df7e24153d03f0e7ac8a05a5d068bd07fb" + dependencies: + duplexify "^3.5.3" + inherits "^2.0.3" + pump "^2.0.0" + +readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.4, readable-stream@^2.0.5, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3: + version "2.3.3" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.3.tgz#368f2512d79f9d46fdfc71349ae7878bbc1eb95c" + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~1.0.6" + safe-buffer "~5.1.1" + string_decoder "~1.0.3" + util-deprecate "~1.0.1" + +readable-stream@~1.1.9: + version "1.1.14" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9" + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.1" + isarray "0.0.1" + string_decoder "~0.10.x" + +readdirp@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.1.0.tgz#4ed0ad060df3073300c48440373f72d1cc642d78" + dependencies: + graceful-fs "^4.1.2" + minimatch "^3.0.2" + readable-stream "^2.0.2" + set-immediate-shim "^1.0.1" + +regenerate@^1.2.1: + version "1.3.3" + resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.3.3.tgz#0c336d3980553d755c39b586ae3b20aa49c82b7f" + +regenerator-runtime@^0.10.5: + version "0.10.5" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz#336c3efc1220adcedda2c9fab67b5a7955a33658" + +regenerator-runtime@^0.11.0: + version "0.11.1" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9" + +regenerator-transform@^0.10.0: + version "0.10.1" + resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.10.1.tgz#1e4996837231da8b7f3cf4114d71b5691a0680dd" + dependencies: + babel-runtime "^6.18.0" + babel-types "^6.19.0" + private "^0.1.6" + +regexpu-core@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-2.0.0.tgz#49d038837b8dcf8bfa5b9a42139938e6ea2ae240" + dependencies: + regenerate "^1.2.1" + regjsgen "^0.2.0" + regjsparser "^0.1.4" + +regjsgen@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.2.0.tgz#6c016adeac554f75823fe37ac05b92d5a4edb1f7" + +regjsparser@^0.1.4: + version "0.1.5" + resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.1.5.tgz#7ee8f84dc6fa792d3fd0ae228d24bd949ead205c" + dependencies: + jsesc "~0.5.0" + +remove-bom-buffer@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/remove-bom-buffer/-/remove-bom-buffer-3.0.0.tgz#c2bf1e377520d324f623892e33c10cac2c252b53" + dependencies: + is-buffer "^1.1.5" + is-utf8 "^0.2.1" + +remove-bom-stream@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/remove-bom-stream/-/remove-bom-stream-1.2.0.tgz#05f1a593f16e42e1fb90ebf59de8e569525f9523" + dependencies: + remove-bom-buffer "^3.0.0" + safe-buffer "^5.1.0" + through2 "^2.0.3" + +remove-trailing-separator@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" + +repeating@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/repeating/-/repeating-2.0.1.tgz#5214c53a926d3552707527fbab415dbc08d06dda" + dependencies: + is-finite "^1.0.0" + +replace-ext@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-0.0.1.tgz#29bbd92078a739f0bcce2b4ee41e837953522924" + +replace-ext@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-1.0.0.tgz#de63128373fcbf7c3ccfa4de5a480c45a67958eb" + +resolve-options@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/resolve-options/-/resolve-options-1.1.0.tgz#32bb9e39c06d67338dc9378c0d6d6074566ad131" + dependencies: + value-or-function "^3.0.0" + +safe-buffer@^5.1.0, safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" + +semver@^5.3.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab" + +set-immediate-shim@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz#4b2b1b27eb808a9f8dcc481a58e5e56f599f3f61" + +slash@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55" + +source-map-support@^0.4.15: + version "0.4.18" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.18.tgz#0286a6de8be42641338594e97ccea75f0a2c585f" + dependencies: + source-map "^0.5.6" + +source-map@^0.5.6: + version "0.5.7" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" + +sparkles@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/sparkles/-/sparkles-1.0.0.tgz#1acbbfb592436d10bbe8f785b7cc6f82815012c3" + +stream-shift@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.0.tgz#d5c752825e5367e786f78e18e445ea223a155952" + +string_decoder@~0.10.x: + version "0.10.31" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" + +string_decoder@~1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.0.3.tgz#0fc67d7c141825de94282dd536bec6b9bce860ab" + dependencies: + safe-buffer "~5.1.0" + +strip-ansi@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" + dependencies: + ansi-regex "^2.0.0" + +supports-color@4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-4.4.0.tgz#883f7ddabc165142b2a61427f3352ded195d1a3e" + dependencies: + has-flag "^2.0.0" + +supports-color@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" + +through2-filter@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/through2-filter/-/through2-filter-2.0.0.tgz#60bc55a0dacb76085db1f9dae99ab43f83d622ec" + dependencies: + through2 "~2.0.0" + xtend "~4.0.0" + +through2@^2.0.0, through2@^2.0.1, through2@^2.0.3, through2@~2.0.0, through2@~2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.3.tgz#0004569b37c7c74ba39c43f3ced78d1ad94140be" + dependencies: + readable-stream "^2.1.5" + xtend "~4.0.1" + +time-stamp@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/time-stamp/-/time-stamp-1.1.0.tgz#764a5a11af50561921b133f3b44e618687e0f5c3" + +to-absolute-glob@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/to-absolute-glob/-/to-absolute-glob-2.0.2.tgz#1865f43d9e74b0822db9f145b78cff7d0f7c849b" + dependencies: + is-absolute "^1.0.0" + is-negated-glob "^1.0.0" + +to-fast-properties@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-1.0.3.tgz#b83571fa4d8c25b82e231b06e3a3055de4ca1a47" + +to-through@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/to-through/-/to-through-2.0.0.tgz#fc92adaba072647bc0b67d6b03664aa195093af6" + dependencies: + through2 "^2.0.3" + +trim-right@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003" + +type-detect@^4.0.0: + version "4.0.7" + resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.7.tgz#862bd2cf6058ad92799ff5a5b8cf7b6cec726198" + +typedarray@^0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" + +unc-path-regex@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/unc-path-regex/-/unc-path-regex-0.1.2.tgz#e73dd3d7b0d7c5ed86fbac6b0ae7d8c6a69d50fa" + +unique-stream@^2.0.2: + version "2.2.1" + resolved "https://registry.yarnpkg.com/unique-stream/-/unique-stream-2.2.1.tgz#5aa003cfbe94c5ff866c4e7d668bb1c4dbadb369" + dependencies: + json-stable-stringify "^1.0.0" + through2-filter "^2.0.0" + +util-deprecate@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + +value-or-function@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/value-or-function/-/value-or-function-3.0.0.tgz#1c243a50b595c1be54a754bfece8563b9ff8d813" + +vinyl-fs@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/vinyl-fs/-/vinyl-fs-3.0.2.tgz#1b86258844383f57581fcaac081fe09ef6d6d752" + dependencies: + fs-mkdirp-stream "^1.0.0" + glob-stream "^6.1.0" + graceful-fs "^4.0.0" + is-valid-glob "^1.0.0" + lazystream "^1.0.0" + lead "^1.0.0" + object.assign "^4.0.4" + pumpify "^1.3.5" + readable-stream "^2.3.3" + remove-bom-buffer "^3.0.0" + remove-bom-stream "^1.2.0" + resolve-options "^1.1.0" + through2 "^2.0.0" + to-through "^2.0.0" + value-or-function "^3.0.0" + vinyl "^2.0.0" + vinyl-sourcemap "^1.1.0" + +vinyl-sourcemap@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/vinyl-sourcemap/-/vinyl-sourcemap-1.1.0.tgz#92a800593a38703a8cdb11d8b300ad4be63b3e16" + dependencies: + append-buffer "^1.0.2" + convert-source-map "^1.5.0" + graceful-fs "^4.1.6" + normalize-path "^2.1.1" + now-and-later "^2.0.0" + remove-bom-buffer "^3.0.0" + vinyl "^2.0.0" + +vinyl@^0.5.0: + version "0.5.3" + resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-0.5.3.tgz#b0455b38fc5e0cf30d4325132e461970c2091cde" + dependencies: + clone "^1.0.0" + clone-stats "^0.0.1" + replace-ext "0.0.1" + +vinyl@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-2.1.0.tgz#021f9c2cf951d6b939943c89eb5ee5add4fd924c" + dependencies: + clone "^2.1.1" + clone-buffer "^1.0.0" + clone-stats "^1.0.0" + cloneable-readable "^1.0.0" + remove-trailing-separator "^1.0.1" + replace-ext "^1.0.0" + +vinyl@~2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-2.0.2.tgz#0a3713d8d4e9221c58f10ca16c0116c9e25eda7c" + dependencies: + clone "^1.0.0" + clone-buffer "^1.0.0" + clone-stats "^1.0.0" + cloneable-readable "^1.0.0" + is-stream "^1.1.0" + remove-trailing-separator "^1.0.1" + replace-ext "^1.0.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + +xtend@~4.0.0, xtend@~4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" From 85caf295916d09efcbbb8f9bde6afce7e7c631c0 Mon Sep 17 00:00:00 2001 From: Karel Ledru-Mathe Date: Thu, 25 Jan 2018 23:26:40 -0500 Subject: [PATCH 2/8] Add lexers --- .babelrc | 3 + README.md | 6 + package.json | 1 + src/lexers/base-lexer.js | 79 +++++++++ src/lexers/handlebars-lexer.js | 65 ++++++++ src/lexers/html-lexer.js | 76 +++++++++ src/lexers/javascript-lexer.js | 170 +++++++++++++++++++ test/lexers/base-lexer.test.js | 66 ++++++++ test/lexers/handlebars-lexer.test.js | 169 +++++++++++++++++++ test/lexers/html-lexer.test.js | 147 ++++++++++++++++ test/lexers/javascript-lexer.test.js | 181 ++++++++++++++++++++ yarn.lock | 240 ++------------------------- 12 files changed, 977 insertions(+), 226 deletions(-) create mode 100644 src/lexers/base-lexer.js create mode 100644 src/lexers/handlebars-lexer.js create mode 100644 src/lexers/html-lexer.js create mode 100644 src/lexers/javascript-lexer.js create mode 100644 test/lexers/base-lexer.test.js create mode 100644 test/lexers/handlebars-lexer.test.js create mode 100644 test/lexers/html-lexer.test.js create mode 100644 test/lexers/javascript-lexer.test.js diff --git a/.babelrc b/.babelrc index 86da1138..3f070b34 100644 --- a/.babelrc +++ b/.babelrc @@ -1,5 +1,8 @@ { "presets": ["env"], + "plugins": [ + "transform-object-rest-spread" + ], "sourceMaps": true, "retainLines": true } diff --git a/README.md b/README.md index d15cba07..c3f9728b 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,12 @@ Thanks a lot to all the previous [contributors](https://github.com/i18next/i18ne --- +## Lexers + +- `HTMLLexer`: parse `data-i18n` and `data-i18n-options` in html. Useful mostly for users of `jquery-i18next`. + +--- + ## CLI Usage `i18next /path/to/file/or/dir [-orapfnl]` diff --git a/package.json b/package.json index 32d9b59a..9e25847c 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "node": ">=0.10.22" }, "devDependencies": { + "babel-plugin-transform-object-rest-spread": "^6.26.0", "babel-polyfill": "^6.26.0", "babel-preset-env": "^1.6.1", "babel-register": "^6.26.0", diff --git a/src/lexers/base-lexer.js b/src/lexers/base-lexer.js new file mode 100644 index 00000000..3efbfad0 --- /dev/null +++ b/src/lexers/base-lexer.js @@ -0,0 +1,79 @@ +import EventEmitter from 'events' + +export default class BaseLexer extends EventEmitter { + + constructor(options = {}) { + super() + this.keys = [] + this.functions = options.functions || ['t'] + } + + populateKeysFromArguments(args) { + const firstArgument = args.arguments[0] + const secondArgument = args.arguments[1] + const isKeyString = this.validateString(firstArgument) + const isDefaultValueString = this.validateString(secondArgument) + + if (!isKeyString) { + this.emit('warning', `Key is not a string litteral: ${firstArgument}`) + } + else { + const result = { + ...args.options, + key: firstArgument.slice(1, -1) + } + if (isDefaultValueString) { + result.defaultValue = secondArgument.slice(1, -1) + } + this.keys.push(result) + } + } + + validateString(string) { + const regex = new RegExp('^' + BaseLexer.stringPattern + '$', 'i') + return regex.test(string) + } + + functionPattern() { + return '(?:' + this.functions.join( '|' ).replace( '.', '\\.' ) + ')' + } + + static get singleQuotePattern() { + return "'(?:[^\'].*?[^\\\\])?'" + } + + static get doubleQuotePattern() { + return '"(?:[^\"].*?[^\\\\])?"' + } + + static get backQuotePattern() { + return '`(?:[^\`].*?[^\\\\])?`' + } + + static get variablePattern() { + return '(?:[A-Z0-9_.-]+)' + } + + static get stringPattern() { + return ( + '(?:' + + [ + BaseLexer.singleQuotePattern, + BaseLexer.doubleQuotePattern + ].join('|') + + ')' + ) + } + + static get stringOrVariablePattern() { + return ( + '(?:' + + [ + BaseLexer.singleQuotePattern, + BaseLexer.doubleQuotePattern, + BaseLexer.variablePattern + ].join('|') + + ')' + ) + } +} diff --git a/src/lexers/handlebars-lexer.js b/src/lexers/handlebars-lexer.js new file mode 100644 index 00000000..81545af0 --- /dev/null +++ b/src/lexers/handlebars-lexer.js @@ -0,0 +1,65 @@ +import BaseLexer from './base-lexer' + +export default class HandlebarsLexer extends BaseLexer { + + constructor(options = {}) { + super(options) + + this.functions = options.functions || ['t'] + + this.createFunctionRegex() + this.createArgumentsRegex() + } + + extract(content) { + let matches + + while (matches = this.functionRegex.exec(content)) { + const args = this.parseArguments(matches[1] || matches[2]) + this.populateKeysFromArguments(args) + } + + return this.keys + } + + parseArguments(args) { + let matches + const result = { + arguments: [], + options: {} + } + while (matches = this.argumentsRegex.exec(args)) { + const arg = matches[1] + const parts = arg.split('=') + result.arguments.push(arg) + if (parts.length === 2 && this.validateString(parts[1])) { + result.options[parts[0]] = parts[1].slice(1, -1) + } + } + return result + } + + createFunctionRegex() { + const functionPattern = this.functionPattern() + const curlyPattern = '(?:{{)' + functionPattern + '\\s+(.*)(?:}})' + const parenthesisPattern = '(?:\\()' + functionPattern + '\\s+(.*)(?:\\))' + const pattern = curlyPattern + '|' + parenthesisPattern + this.functionRegex = new RegExp(pattern, 'gi') + return this.functionRegex + } + + createArgumentsRegex() { + const pattern = + '(?:\\s+|^)' + + '(' + + '(?:' + + BaseLexer.variablePattern + + '(?:=' + BaseLexer.stringOrVariablePattern + ')?' + + ')' + + '|' + + BaseLexer.stringPattern + + ')' + this.argumentsRegex = new RegExp(pattern, 'gi') + return this.argumentsRegex + } +} diff --git a/src/lexers/html-lexer.js b/src/lexers/html-lexer.js new file mode 100644 index 00000000..42c6a3ff --- /dev/null +++ b/src/lexers/html-lexer.js @@ -0,0 +1,76 @@ +import BaseLexer from './base-lexer' + +export default class HTMLLexer extends BaseLexer { + + constructor(options = {}) { + super(options) + + this.attr = options.attr || 'data-i18n' + this.optionAttr = options.optionAttr || 'data-i18n-options' + + this.createAttributeRegex() + this.createOptionAttributeRegex() + } + + // TODO rewrite to support the BaseLexer.extract() + extract(content) { + let matches + const regex = new RegExp( + '<([A-Z][A-Z0-9]*)([^>]*\\s' + this.attr + '[^>]*)>(?:(.*?)<\\/\\1>)?', + 'gi' + ) + + while (matches = regex.exec(content)) { + const attrs = this.parseAttributes(matches[2]) + + // the attribute can hold multiple keys + const keys = attrs.keys.split(';') + keys.forEach(key => { + // remove any leading [] in the key + key = key.replace(/^\[[a-zA-Z0-9_-]*\]/ , '') + + // if empty grab innerHTML from regex + key = key || matches[3] + + if (key) { + this.keys.push({ ...attrs.options, key }) + } + }) + + } + + return this.keys + } + + createAttributeRegex() { + const pattern = '(?:' + this.attr + ')(?:\\s*=\\s*(' + BaseLexer.stringPattern + ')|$|\\s)' + this.attrRegex = new RegExp(pattern, 'i') + return this.attrRegex + } + + createOptionAttributeRegex() { + const pattern = '(?:' + this.optionAttr + ')(?:\\s*=\\s*(' + BaseLexer.stringPattern + '))?' + this.optionAttrRegex = new RegExp(pattern, 'i') + return this.optionAttrRegex + } + + parseAttributes(args) { + const result = {keys: '', options: {}} + this.attrRegex.lastIndex = 0 + let keysMatch = this.attrRegex.exec(args) + if (keysMatch && keysMatch[1]) { + result.keys = keysMatch[1].slice(1, -1) + } + + this.optionAttrRegex.lastIndex = 0 + const optionsMatch = this.optionAttrRegex.exec(args) + if (optionsMatch && optionsMatch[1]) { + try { + result.options = JSON.parse(optionsMatch[1].slice(1, -1)) + } + finally {} + } + + return result + } +} diff --git a/src/lexers/javascript-lexer.js b/src/lexers/javascript-lexer.js new file mode 100644 index 00000000..a369ea55 --- /dev/null +++ b/src/lexers/javascript-lexer.js @@ -0,0 +1,170 @@ +import BaseLexer from './base-lexer' + +export default class JavascriptLexer extends BaseLexer { + + constructor(options = {}) { + super(options) + + this.functions = options.functions || ['t'] + + this.createFunctionRegex() + this.createArgumentsRegex() + this.createHashRegex() + } + + extract(content) { + let matches + + while (matches = this.functionRegex.exec(content)) { + const args = this.parseArguments(matches[1] || matches[2]) + this.populateKeysFromArguments(args) + } + + return this.keys + } + + parseArguments(args) { + let matches + const result = { + arguments: [], + options: {} + } + while (matches = this.argumentsRegex.exec(args)) { + let arg = matches[1] + + if (arg.startsWith('{')) { + let optionMatches + while (optionMatches = this.hashRegex.exec(args)) { + const key = optionMatches[2] + let value = optionMatches[3] + if (this.validateString(value)) { + result.options[key] = value.slice(1, -1) + } + } + } + else { + arg = this.concatenateString(arg) + } + result.arguments.push(arg) + } + return result + } + + concatenateString(string) { + string = string.trim() + let matches + let containsVariable = false + const parts = [] + const quotationMark = string.charAt(0) === '"' ? '"' : '\'' + + const regex = new RegExp(JavascriptLexer.concatenatedSegmentPattern, 'gi') + while(matches = regex.exec(string)) { + const match = matches[0].trim() + if (match !== '+') { + parts.push(match) + } + } + + const result = parts.reduce( + (concatenatedString, x) => { + x = x && x.trim() + if (this.validateString(x)) { + concatenatedString += x.slice(1, -1) + } + else { + containsVariable = true + } + return concatenatedString + }, + '' + ) + if (!result || containsVariable) { + return string + } + else { + return quotationMark + result + quotationMark + } + } + + static get concatenatedSegmentPattern() { + return ( + [ + BaseLexer.singleQuotePattern, + BaseLexer.doubleQuotePattern, + BaseLexer.backQuotePattern, + BaseLexer.variablePattern, + '(?:\\s*\\+\\s*)' // support for concatenation via + + ].join('|') + ) + } + + static get concatenatedArgumentPattern() { + return ( + '(' + + '(?:' + + JavascriptLexer.concatenatedSegmentPattern + + ')+' + + ')' + ) + } + + static get hashPattern() { + return '(\\{[^}]*\\})' + } + + static get stringOrVariableOrHashPattern() { + return ( + '(' + + '(' + + '(?:' + + [ + JavascriptLexer.concatenatedArgumentPattern, + JavascriptLexer.hashPattern, + ].join('|') + + ')' + + '(?:\\s*,\\s*)?' + + ')+' + + ')' + ) + } + + createFunctionRegex() { + const pattern = ( + '(?:\\W|^)' + + this.functionPattern() + '\\s*\\(\\s*' + + JavascriptLexer.stringOrVariableOrHashPattern + + '\\s*\\)' + ) + this.functionRegex = new RegExp(pattern, 'gi') + return this.functionRegex + } + + createArgumentsRegex() { + const pattern = ( + '(' + + [ + JavascriptLexer.concatenatedArgumentPattern, + JavascriptLexer.hashPattern, + ].join('|') + + ')' + + '(?:\\s*,\\s*)?' + ) + this.argumentsRegex = new RegExp(pattern, 'gi') + return this.argumentsRegex + } + + createHashRegex() { + const pattern = ( + '(?:(\'|")?(' + + [ + 'context', + 'defaultValue' + ].join('|') + + ')\\1)' + + '(?:\\s*:\\s*)' + + '(' + BaseLexer.stringPattern + ')' + ) + this.hashRegex = new RegExp(pattern, 'gi') + return this.hashRegex + } +} diff --git a/test/lexers/base-lexer.test.js b/test/lexers/base-lexer.test.js new file mode 100644 index 00000000..9f319007 --- /dev/null +++ b/test/lexers/base-lexer.test.js @@ -0,0 +1,66 @@ +import { assert } from 'chai' +import BaseLexer from '../../src/lexers/base-lexer' + +describe('BaseLexer', function () { + it('functionPattern() return a regex pattern', function (done) { + const Lexer = new BaseLexer({functions: ['this.t', '__']}) + assert.equal( Lexer.functionPattern(), '(?:this\\.t|__)' ) + done() + }) + + describe('validateString()', function () { + it('matches double quote strings', function (done) { + const Lexer = new BaseLexer() + assert.equal( + Lexer.validateString('"args"'), + true + ) + done() + }) + + it('matches single quote strings', function (done) { + const Lexer = new BaseLexer() + assert.equal( + Lexer.validateString("'args'"), + true + ) + done() + }) + + it('does not match variables', function (done) { + const Lexer = new BaseLexer() + assert.equal( + Lexer.validateString('args'), + false + ) + done() + }) + + it('does not match null value', function (done) { + const Lexer = new BaseLexer() + assert.equal( + Lexer.validateString(null), + false + ) + done() + }) + + it('does not match undefined value', function (done) { + const Lexer = new BaseLexer() + assert.equal( + Lexer.validateString(undefined), + false + ) + done() + }) + + it('does not match empty string', function (done) { + const Lexer = new BaseLexer() + assert.equal( + Lexer.validateString(''), + false + ) + done() + }) + }) +}) diff --git a/test/lexers/handlebars-lexer.test.js b/test/lexers/handlebars-lexer.test.js new file mode 100644 index 00000000..14a1625d --- /dev/null +++ b/test/lexers/handlebars-lexer.test.js @@ -0,0 +1,169 @@ +import { assert } from 'chai' +import HandlebarsLexer from '../../src/lexers/handlebars-lexer' + +describe('HandlebarsLexer', function () { + it('extracts keys from translation components', function (done) { + const Lexer = new HandlebarsLexer() + const content = '

{{t "first"}}

' + assert.deepEqual( + Lexer.extract(content), + [ + { key: 'first' } + ] + ) + done() + }) + + it('extracts the second argument as defaultValue', function (done) { + const Lexer = new HandlebarsLexer() + const content = '

{{t "first" "bla"}}

' + assert.deepEqual( + Lexer.extract(content), + [ + { key: 'first', defaultValue: 'bla' } + ] + ) + done() + }) + + it('extracts the defaultValue arguments', function (done) { + const Lexer = new HandlebarsLexer() + const content = '

{{t "first" defaultValue="bla"}}

' + assert.deepEqual( + Lexer.extract(content), + [ + { key: 'first', defaultValue: 'bla' } + ] + ) + done() + }) + + it('extracts the context arguments', function (done) { + const Lexer = new HandlebarsLexer() + const content = '

{{t "first" context="bla"}}

' + assert.deepEqual( + Lexer.extract(content), + [ + { key: 'first', context: 'bla' } + ] + ) + done() + }) + + it('extracts keys from translation functions', function (done) { + const Lexer = new HandlebarsLexer() + const content = '

{{link-to (t "first") "foo"}}

' + assert.deepEqual( + Lexer.extract(content), + [ + { key: 'first' } + ] + ) + done() + }) + + it('supports a `functions` option', function (done) { + const Lexer = new HandlebarsLexer({functions: ['tt', '_e']}) + const content = '

{{link-to (tt "first") "foo"}}: {{_e "second"}}

' + assert.deepEqual( + Lexer.extract(content), + [ + { key: 'first' }, + { key: 'second' } + ] + ) + done() + }) + + describe('parseArguments()', function () { + it('matches string arguments', function (done) { + const Lexer = new HandlebarsLexer() + const args = '"first" "bla"' + assert.deepEqual( + Lexer.parseArguments(args), + { + arguments: [ + '"first"', + '"bla"' + ], + options: {} + } + ) + done() + }) + + it('matches variable arguments', function (done) { + const Lexer = new HandlebarsLexer() + const args = 'first bla' + assert.deepEqual( + Lexer.parseArguments(args), + { + arguments: [ + 'first', + 'bla' + ], + options: {} + } + ) + done() + }) + + it('matches key-value arguments', function (done) { + const Lexer = new HandlebarsLexer() + const args = 'first="bla"' + assert.deepEqual( + Lexer.parseArguments(args), + { + arguments: [ + 'first="bla"' + ], + options: { + first: 'bla' + } + } + ) + done() + }) + + it('skips key-value arguments that are variables', function (done) { + const Lexer = new HandlebarsLexer() + const args = 'second=bla' + assert.deepEqual( + Lexer.parseArguments(args), + { + arguments: [ + 'second=bla' + ], + options: { + // empty! + } + } + ) + done() + }) + + it('matches combinations', function (done) { + const Lexer = new HandlebarsLexer() + const args = '"first" second third-one="bla bla" fourth fifth=\'bla\' "sixth"' + assert.deepEqual( + Lexer.parseArguments(args), + { + arguments: [ + '"first"', + 'second', + 'third-one="bla bla"', + 'fourth', + 'fifth=\'bla\'', + '"sixth"' + ], + options: { + 'third-one': 'bla bla', + 'fifth': 'bla' + } + } + + ) + done() + }) + }) +}) diff --git a/test/lexers/html-lexer.test.js b/test/lexers/html-lexer.test.js new file mode 100644 index 00000000..5fd84b4d --- /dev/null +++ b/test/lexers/html-lexer.test.js @@ -0,0 +1,147 @@ +import { assert } from 'chai' +import HTMLLexer from '../../src/lexers/html-lexer' + +describe('HTMLLexer', function () { + it('extracts keys from html attributes', function (done) { + const Lexer = new HTMLLexer() + const content = '

' + assert.deepEqual( + Lexer.extract(content), + [ + { key: 'first' }, + { key: 'second' } + ] + ) + done() + }) + + it('ignores leading [] of the key', function (done) { + const Lexer = new HTMLLexer() + const content = '

' + assert.deepEqual( + Lexer.extract(content), + [ + { key: 'first' }, + { key: 'second' } + ] + ) + done() + }) + + it('supports the defaultValue option', function (done) { + const Lexer = new HTMLLexer() + const content = '

first

' + assert.deepEqual( + Lexer.extract(content), + [ + { key: 'first', defaultValue: 'bla' } + ] + ) + done() + }) + + it('grabs the default from innerHTML if missing', function (done) { + const Lexer = new HTMLLexer() + const content = '

first

' + assert.deepEqual( + Lexer.extract(content), + [ + { key: 'first' } + ] + ) + done() + }) + + it('supports multiline', function (done) { + const Lexer = new HTMLLexer() + const content = + '

Fourth

' + + '

' + assert.deepEqual( + Lexer.extract(content), + [ + { key: 'third' }, + { key: 'fourth' }, + { key: 'first', defaultValue: 'bar' } + ] + ) + done() + }) + + it('skip if no key is found', function (done) { + const Lexer = new HTMLLexer() + const content = '

' + assert.deepEqual( + Lexer.extract(content), + [] + ) + done() + }) + + it('supports a `attr` option', function (done) { + const Lexer = new HTMLLexer({attr: 'data-other'}) + const content = '

' + assert.deepEqual( + Lexer.extract(content), + [ + { key: 'first' }, + { key: 'second' } + ] + ) + done() + }) + + it('supports a `optionAttr` option', function (done) { + const Lexer = new HTMLLexer({optionAttr: 'data-other-options'}) + const content = '

' + assert.deepEqual( + Lexer.extract(content), + [ + { key: 'first', defaultValue: 'bar' } + ] + ) + done() + }) + + describe('parseAttributes()', function () { + it('extracts attribute value from string', function (done) { + const Lexer = new HTMLLexer() + assert.deepEqual( + Lexer.parseAttributes('title="" bla data-i18n="key1"', 'data-i18n'), + { + keys: 'key1', + options: {} + } + ) + done() + }) + + it('extracts json strings too', function (done) { + const Lexer = new HTMLLexer() + assert.deepEqual( + Lexer.parseAttributes('data-i18n="key1;key2" data-i18n-options=\'{"defaultValue": "bla"}\'', 'data-i18n-options'), + { + keys: 'key1;key2', + options: { + defaultValue: 'bla' + } + } + ) + done() + }) + + it('supports multiline', function (done) { + const Lexer = new HTMLLexer() + assert.deepEqual( + Lexer.parseAttributes('title=""\n bla\n data-i18n="first"\n data-i18n-options=\'{"defaultValue": "bar"}\''), + { + keys: 'first', + options: { + defaultValue: 'bar' + } + } + ) + done() + }) + }) +}) diff --git a/test/lexers/javascript-lexer.test.js b/test/lexers/javascript-lexer.test.js new file mode 100644 index 00000000..a925e32b --- /dev/null +++ b/test/lexers/javascript-lexer.test.js @@ -0,0 +1,181 @@ +import { assert } from 'chai' +import JavascriptLexer from '../../src/lexers/javascript-lexer' + +describe('JavascriptLexer', function () { + it('extracts keys from translation components', function (done) { + const Lexer = new JavascriptLexer() + const content = 'i18n.t("first")' + assert.deepEqual( + Lexer.extract(content), + [ + { key: 'first' } + ] + ) + done() + }) + + it('extracts the second argument as defaultValue', function (done) { + const Lexer = new JavascriptLexer() + const content = 'i18n.t("first" "bla")' + assert.deepEqual( + Lexer.extract(content), + [ + { key: 'first', defaultValue: 'bla' } + ] + ) + done() + }) + + it('extracts the defaultValue/context options', function (done) { + const Lexer = new JavascriptLexer() + const content = 'i18n.t("first", {defaultValue: "foo", context: \'bar\'})' + assert.deepEqual( + Lexer.extract(content), + [ + { key: 'first', defaultValue: 'foo', context: 'bar' } + ] + ) + done() + }) + + it('extracts the defaultValue/context options with quotation marks', function (done) { + const Lexer = new JavascriptLexer() + const content = 'i18n.t("first", {context: "foo", "defaultValue": \'bla\'})' + assert.deepEqual( + Lexer.extract(content), + [ + { key: 'first', defaultValue: 'bla', context: 'foo' } + ] + ) + done() + }) + + it('supports multiline and concatenation', function (done) { + const Lexer = new JavascriptLexer() + const content = 'i18n.t("foo" + \n "bar")' + assert.deepEqual( + Lexer.extract(content), + [ + { key: 'foobar' } + ] + ) + done() + }) + + it('does not parse text with `doesn\'t` or isolated `t` in it', function (done) { + const Lexer = new JavascriptLexer() + const js = "// FIX this doesn't work and this t is all alone\nt('first')\nt = function() {}" + assert.deepEqual( + Lexer.extract(js), + [ + { key: 'first' } + ] + ) + done() + }) + + it('ignores functions that ends with a t', function (done) { + const Lexer = new JavascriptLexer() + const js = 'import \'./yolo.js\' t(\'first\')' + assert.deepEqual( + Lexer.extract(js), + [ + { key: 'first' } + ] + ) + done() + }) + + it('supports a `functions` option', function (done) { + const Lexer = new JavascriptLexer({functions: ['tt', '_e']}) + const content = 'tt("first") + _e("second")' + assert.deepEqual( + Lexer.extract(content), + [ + { key: 'first' }, + { key: 'second' } + ] + ) + done() + }) + + describe('concatenateString()', function () { + it('concatenates strings', function (done) { + const Lexer = new JavascriptLexer() + assert.equal( + Lexer.concatenateString('"foo" + \'bar\''), + '"foobar"' + ) + done() + }) + + it('returns the original string if it contains variables', function (done) { + const Lexer = new JavascriptLexer() + assert.equal( + Lexer.concatenateString('"foo" + bar'), + '"foo" + bar' + ) + done() + }) + + it('returns the original string if it contains backquote string', function (done) { + const Lexer = new JavascriptLexer() + assert.equal( + Lexer.concatenateString('"foo" + `bar`'), + '"foo" + `bar`' + ) + done() + }) + }) + + describe('parseArguments()', function () { + it('matches string arguments', function (done) { + const Lexer = new JavascriptLexer() + const args = '"first", "bla"' + assert.deepEqual( + Lexer.parseArguments(args), + { + arguments: [ + '"first"', + '"bla"' + ], + options: {} + } + ) + done() + }) + + it('matches variable arguments', function (done) { + const Lexer = new JavascriptLexer() + const args = 'first bla' + assert.deepEqual( + Lexer.parseArguments(args), + { + arguments: [ + 'first', + 'bla' + ], + options: {} + } + ) + done() + }) + + it('matches concatenated arguments and concatenate when possible', function (done) { + const Lexer = new JavascriptLexer() + const args = "'first' + asd, 'bla' + 'asd', foo+bar+baz" + assert.deepEqual( + Lexer.parseArguments(args), + { + arguments: [ + "'first' + asd", + "'blaasd'", // string got concatenated! + "foo+bar+baz" + ], + options: {} + } + ) + done() + }) + }) +}) diff --git a/yarn.lock b/yarn.lock index f2e455ff..c403d3f8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,12 +2,6 @@ # yarn lockfile v1 -ansi-gray@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/ansi-gray/-/ansi-gray-0.1.1.tgz#2962cf54ec9792c48510a3deb524436861ef7251" - dependencies: - ansi-wrap "0.1.0" - ansi-regex@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" @@ -16,24 +10,12 @@ ansi-styles@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" -ansi-wrap@0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/ansi-wrap/-/ansi-wrap-0.1.0.tgz#a82250ddb0015e9a27ca82e82ea603bbfa45efaf" - append-buffer@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/append-buffer/-/append-buffer-1.0.2.tgz#d8220cf466081525efea50614f3de6514dfa58f1" dependencies: buffer-equal "^1.0.0" -array-differ@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/array-differ/-/array-differ-1.0.0.tgz#eff52e3758249d33be402b8bb8e564bb2b5d4031" - -array-uniq@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6" - assertion-error@^1.0.1: version "1.1.0" resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b" @@ -204,6 +186,10 @@ babel-plugin-syntax-exponentiation-operator@^6.8.0: version "6.13.0" resolved "https://registry.yarnpkg.com/babel-plugin-syntax-exponentiation-operator/-/babel-plugin-syntax-exponentiation-operator-6.13.0.tgz#9ee7e8337290da95288201a6a57f4170317830de" +babel-plugin-syntax-object-rest-spread@^6.8.0: + version "6.13.0" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz#fd6536f2bce13836ffa3a5458c4903a597bb3bf5" + babel-plugin-syntax-trailing-function-commas@^6.22.0: version "6.22.0" resolved "https://registry.yarnpkg.com/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-6.22.0.tgz#ba0360937f8d06e40180a43fe0d5616fff532cf3" @@ -392,6 +378,13 @@ babel-plugin-transform-exponentiation-operator@^6.22.0: babel-plugin-syntax-exponentiation-operator "^6.8.0" babel-runtime "^6.22.0" +babel-plugin-transform-object-rest-spread@^6.26.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-object-rest-spread/-/babel-plugin-transform-object-rest-spread-6.26.0.tgz#0f36692d50fef6b7e2d4b3ac1478137a963b7b06" + dependencies: + babel-plugin-syntax-object-rest-spread "^6.8.0" + babel-runtime "^6.26.0" + babel-plugin-transform-regenerator@^6.22.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.26.0.tgz#e0703696fbde27f0a3efcacf8b4dca2f7b3a8f2f" @@ -508,10 +501,6 @@ balanced-match@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" -beeper@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/beeper/-/beeper-1.1.1.tgz#e6d5ea8c5dad001304a70b22638447f69cb2f809" - brace-expansion@^1.1.7: version "1.1.8" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.8.tgz#c07b211c7c952ec1f8efd51a77ef0d1d3990a292" @@ -549,7 +538,7 @@ chai@^4.1.2: pathval "^1.0.0" type-detect "^4.0.0" -chalk@^1.0.0, chalk@^1.1.3: +chalk@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" dependencies: @@ -567,10 +556,6 @@ clone-buffer@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/clone-buffer/-/clone-buffer-1.0.0.tgz#e3e25b207ac4e701af721e2cb5a16792cac3dc58" -clone-stats@^0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/clone-stats/-/clone-stats-0.0.1.tgz#b88f94a82cf38b8791d58046ea4029ad88ca99d1" - clone-stats@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/clone-stats/-/clone-stats-1.0.0.tgz#b3782dff8bb5474e18b9b6bf0fdfe782f8777680" @@ -591,10 +576,6 @@ cloneable-readable@^1.0.0: process-nextick-args "^1.0.6" through2 "^2.0.1" -color-support@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2" - colors@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/colors/-/colors-1.1.2.tgz#168a4701756b6a7f51a12ce0c97bfa28c084ed63" @@ -633,10 +614,6 @@ core-util-is@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" -dateformat@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-2.2.0.tgz#4065e2013cf9fb916ddfd82efb506ad4c6769062" - debug@3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" @@ -672,12 +649,6 @@ diff@3.3.1: version "3.3.1" resolved "https://registry.yarnpkg.com/diff/-/diff-3.3.1.tgz#aa8567a6eed03c531fc89d3f711cd0e5259dec75" -duplexer2@0.0.2: - version "0.0.2" - resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.0.2.tgz#c614dcf67e2fb14995a91711e5a617e8a60a31db" - dependencies: - readable-stream "~1.1.9" - duplexify@^3.5.3: version "3.5.3" resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.5.3.tgz#8b5818800df92fd0125b27ab896491912858243e" @@ -713,14 +684,6 @@ extend@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.1.tgz#a755ea7bc1adfcc5a31ce7e762dbaadc5e636444" -fancy-log@^1.1.0: - version "1.3.2" - resolved "https://registry.yarnpkg.com/fancy-log/-/fancy-log-1.3.2.tgz#f41125e3d84f2e7d89a43d06d958c8f78be16be1" - dependencies: - ansi-gray "^0.1.1" - color-support "^1.1.3" - time-stamp "^1.0.0" - flush-write-stream@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.0.2.tgz#c81b90d8746766f1a609a46809946c45dd8ae417" @@ -788,12 +751,6 @@ globals@^9.18.0: version "9.18.0" resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a" -glogg@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/glogg/-/glogg-1.0.1.tgz#dcf758e44789cc3f3d32c1f3562a3676e6a34810" - dependencies: - sparkles "^1.0.0" - graceful-fs@^4.0.0, graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6: version "4.1.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" @@ -806,35 +763,6 @@ growl@1.10.3: version "1.10.3" resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.3.tgz#1926ba90cf3edfe2adb4927f5880bc22c66c790f" -gulp-util@~3.0.7: - version "3.0.8" - resolved "https://registry.yarnpkg.com/gulp-util/-/gulp-util-3.0.8.tgz#0054e1e744502e27c04c187c3ecc505dd54bbb4f" - dependencies: - array-differ "^1.0.0" - array-uniq "^1.0.2" - beeper "^1.0.0" - chalk "^1.0.0" - dateformat "^2.0.0" - fancy-log "^1.1.0" - gulplog "^1.0.0" - has-gulplog "^0.1.0" - lodash._reescape "^3.0.0" - lodash._reevaluate "^3.0.0" - lodash._reinterpolate "^3.0.0" - lodash.template "^3.0.0" - minimist "^1.1.0" - multipipe "^0.1.2" - object-assign "^3.0.0" - replace-ext "0.0.1" - through2 "^2.0.0" - vinyl "^0.5.0" - -gulplog@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/gulplog/-/gulplog-1.0.0.tgz#e28c4d45d05ecbbed818363ce8f9c5926229ffe5" - dependencies: - glogg "^1.0.0" - has-ansi@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" @@ -845,12 +773,6 @@ has-flag@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-2.0.0.tgz#e8207af1cc7b30d446cc70b734b5e8be18f88d51" -has-gulplog@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/has-gulplog/-/has-gulplog-0.1.0.tgz#6414c82913697da51590397dafb12f22967811ce" - dependencies: - sparkles "^1.0.0" - has-symbols@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.0.tgz#ba1a8f1af2a0fc39650f5c850367704122063b44" @@ -873,7 +795,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.1, inherits@~2.0.3: +inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" @@ -942,10 +864,6 @@ is-windows@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.1.tgz#310db70f742d259a16a369202b51af84233310d9" -isarray@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" - isarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" @@ -988,90 +906,7 @@ lead@^1.0.0: dependencies: flush-write-stream "^1.0.2" -lodash._basecopy@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz#8da0e6a876cf344c0ad8a54882111dd3c5c7ca36" - -lodash._basetostring@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/lodash._basetostring/-/lodash._basetostring-3.0.1.tgz#d1861d877f824a52f669832dcaf3ee15566a07d5" - -lodash._basevalues@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/lodash._basevalues/-/lodash._basevalues-3.0.0.tgz#5b775762802bde3d3297503e26300820fdf661b7" - -lodash._getnative@^3.0.0: - version "3.9.1" - resolved "https://registry.yarnpkg.com/lodash._getnative/-/lodash._getnative-3.9.1.tgz#570bc7dede46d61cdcde687d65d3eecbaa3aaff5" - -lodash._isiterateecall@^3.0.0: - version "3.0.9" - resolved "https://registry.yarnpkg.com/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz#5203ad7ba425fae842460e696db9cf3e6aac057c" - -lodash._reescape@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/lodash._reescape/-/lodash._reescape-3.0.0.tgz#2b1d6f5dfe07c8a355753e5f27fac7f1cde1616a" - -lodash._reevaluate@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/lodash._reevaluate/-/lodash._reevaluate-3.0.0.tgz#58bc74c40664953ae0b124d806996daca431e2ed" - -lodash._reinterpolate@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d" - -lodash._root@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/lodash._root/-/lodash._root-3.0.1.tgz#fba1c4524c19ee9a5f8136b4609f017cf4ded692" - -lodash.escape@^3.0.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/lodash.escape/-/lodash.escape-3.2.0.tgz#995ee0dc18c1b48cc92effae71a10aab5b487698" - dependencies: - lodash._root "^3.0.0" - -lodash.isarguments@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a" - -lodash.isarray@^3.0.0: - version "3.0.4" - resolved "https://registry.yarnpkg.com/lodash.isarray/-/lodash.isarray-3.0.4.tgz#79e4eb88c36a8122af86f844aa9bcd851b5fbb55" - -lodash.keys@^3.0.0: - version "3.1.2" - resolved "https://registry.yarnpkg.com/lodash.keys/-/lodash.keys-3.1.2.tgz#4dbc0472b156be50a0b286855d1bd0b0c656098a" - dependencies: - lodash._getnative "^3.0.0" - lodash.isarguments "^3.0.0" - lodash.isarray "^3.0.0" - -lodash.restparam@^3.0.0: - version "3.6.1" - resolved "https://registry.yarnpkg.com/lodash.restparam/-/lodash.restparam-3.6.1.tgz#936a4e309ef330a7645ed4145986c85ae5b20805" - -lodash.template@^3.0.0: - version "3.6.2" - resolved "https://registry.yarnpkg.com/lodash.template/-/lodash.template-3.6.2.tgz#f8cdecc6169a255be9098ae8b0c53d378931d14f" - dependencies: - lodash._basecopy "^3.0.0" - lodash._basetostring "^3.0.0" - lodash._basevalues "^3.0.0" - lodash._isiterateecall "^3.0.0" - lodash._reinterpolate "^3.0.0" - lodash.escape "^3.0.0" - lodash.keys "^3.0.0" - lodash.restparam "^3.0.0" - lodash.templatesettings "^3.0.0" - -lodash.templatesettings@^3.0.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/lodash.templatesettings/-/lodash.templatesettings-3.1.1.tgz#fb307844753b66b9f1afa54e262c745307dba8e5" - dependencies: - lodash._reinterpolate "^3.0.0" - lodash.escape "^3.0.0" - -lodash@^4.17.4, lodash@~4.17.3: +lodash@^4.17.4, lodash@~4.17.4: version "4.17.4" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" @@ -1091,10 +926,6 @@ minimist@0.0.8: version "0.0.8" resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" -minimist@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" - mkdirp@0.5.1, mkdirp@^0.5.1, mkdirp@~0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" @@ -1120,12 +951,6 @@ ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" -multipipe@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/multipipe/-/multipipe-0.1.2.tgz#2a8f2ddf70eed564dff2d57f1e1a137d9f05078b" - dependencies: - duplexer2 "0.0.2" - normalize-path@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" @@ -1142,10 +967,6 @@ number-is-nan@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" -object-assign@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-3.0.0.tgz#9bedd5ca0897949bca47e7ff408062d549f587f2" - object-keys@^1.0.11, object-keys@^1.0.8: version "1.0.11" resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.0.11.tgz#c54601778ad560f1142ce0e01bcca8b56d13426d" @@ -1226,15 +1047,6 @@ readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable string_decoder "~1.0.3" util-deprecate "~1.0.1" -readable-stream@~1.1.9: - version "1.1.14" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9" - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.1" - isarray "0.0.1" - string_decoder "~0.10.x" - readdirp@~2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.1.0.tgz#4ed0ad060df3073300c48440373f72d1cc642d78" @@ -1307,10 +1119,6 @@ repeating@^2.0.0: dependencies: is-finite "^1.0.0" -replace-ext@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-0.0.1.tgz#29bbd92078a739f0bcce2b4ee41e837953522924" - replace-ext@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-1.0.0.tgz#de63128373fcbf7c3ccfa4de5a480c45a67958eb" @@ -1347,18 +1155,10 @@ source-map@^0.5.6: version "0.5.7" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" -sparkles@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/sparkles/-/sparkles-1.0.0.tgz#1acbbfb592436d10bbe8f785b7cc6f82815012c3" - stream-shift@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.0.tgz#d5c752825e5367e786f78e18e445ea223a155952" -string_decoder@~0.10.x: - version "0.10.31" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" - string_decoder@~1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.0.3.tgz#0fc67d7c141825de94282dd536bec6b9bce860ab" @@ -1395,10 +1195,6 @@ through2@^2.0.0, through2@^2.0.1, through2@^2.0.3, through2@~2.0.0, through2@~2. readable-stream "^2.1.5" xtend "~4.0.1" -time-stamp@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/time-stamp/-/time-stamp-1.1.0.tgz#764a5a11af50561921b133f3b44e618687e0f5c3" - to-absolute-glob@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/to-absolute-glob/-/to-absolute-glob-2.0.2.tgz#1865f43d9e74b0822db9f145b78cff7d0f7c849b" @@ -1481,14 +1277,6 @@ vinyl-sourcemap@^1.1.0: remove-bom-buffer "^3.0.0" vinyl "^2.0.0" -vinyl@^0.5.0: - version "0.5.3" - resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-0.5.3.tgz#b0455b38fc5e0cf30d4325132e461970c2091cde" - dependencies: - clone "^1.0.0" - clone-stats "^0.0.1" - replace-ext "0.0.1" - vinyl@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-2.1.0.tgz#021f9c2cf951d6b939943c89eb5ee5add4fd924c" From cb199722923c3321cea50d101b5fd7d5c618c0f5 Mon Sep 17 00:00:00 2001 From: Karel Ledru-Mathe Date: Tue, 30 Jan 2018 21:45:13 -0500 Subject: [PATCH 3/8] Rewrite transform and parser --- index.js | 317 --------------- src/index.js | 199 ++++++++++ src/parser.js | 64 +++ test/locales/en/test_invalid.json | 3 + test/locales/en/test_leak.json | 4 + test/locales/fr/test_leak.json | 4 + test/parser.js | 542 ------------------------- test/parser.test.js | 637 ++++++++++++++++++++++++++++++ test/templating/handlebars.hbs | 16 +- test/templating/html.html | 19 +- test/templating/jade.jade | 2 - test/templating/javascript.js | 9 + 12 files changed, 949 insertions(+), 867 deletions(-) delete mode 100755 index.js create mode 100644 src/index.js create mode 100644 src/parser.js create mode 100644 test/locales/en/test_invalid.json create mode 100644 test/locales/en/test_leak.json create mode 100644 test/locales/fr/test_leak.json delete mode 100644 test/parser.js create mode 100644 test/parser.test.js delete mode 100644 test/templating/jade.jade create mode 100644 test/templating/javascript.js diff --git a/index.js b/index.js deleted file mode 100755 index b23ba747..00000000 --- a/index.js +++ /dev/null @@ -1,317 +0,0 @@ -var PluginError = require('plugin-error'); -var Transform = require('stream').Transform; -var util = require('util'); -var helpers = require('./src/helpers'); -var fs = require('fs'); -var File = require('vinyl'); -var path = require('path'); -var _ = require('lodash'); - - - -const PLUGIN_NAME = 'i18next-parser'; - - - -function Parser(options, transformConfig) { - - var self = this; - - options = options || {}; - transformConfig = transformConfig || {}; - - this.defaultNamespace = options.namespace || 'translation'; - this.functions = options.functions || ['t']; - this.locales = options.locales || ['en','fr']; - this.output = options.output || 'locales'; - this.regex = options.parser; - this.attributes = options.attributes || ['data-i18n']; - this.namespaceSeparator = options.namespaceSeparator || ':'; - this.keySeparator = options.keySeparator || '.'; - this.contextSeparator = options.contextSeparator || '_'; - this.translations = []; - this.extension = options.extension || '.json'; - this.suffix = options.suffix || ''; - this.prefix = options.prefix || ''; - this.writeOld = options.writeOld !== false; - this.keepRemoved = options.keepRemoved; - this.ignoreVariables = options.ignoreVariables || false; - this.defaultValues = options.defaultValues || false; - - ['functions', 'locales'].forEach(function( attr ) { - if ( (typeof self[ attr ] !== 'object') || ! self[ attr ].length ) { - throw new PluginError(PLUGIN_NAME, '`'+attr+'` must be an array'); - } - }); - - transformConfig.objectMode = true; - Transform.call(this, transformConfig); -} -util.inherits(Parser, Transform); - - - -Parser.prototype._transform = function(file, encoding, done) { - - var self = this; - this.base = this.base || file.base; - - - - // we do not handle streams - // ======================== - if (file.isStream()) { - this.emit( 'error', new PluginError( PLUGIN_NAME, 'Streams not supported' ) ); - return done(); - } - - - - // get the file from file path - // =========================== - if(file.isNull()) { - if ( file.stat.isDirectory() ) { - return done(); - } - else if ( file.path && fs.existsSync( file.path ) ) { - data = fs.readFileSync( file.path ); - } - else { - this.emit( 'error', new PluginError( PLUGIN_NAME, 'File has no content and is not readable' ) ); - return done(); - } - } - - - - // we handle buffers - // ================= - if(file.isBuffer()) { - data = file.contents; - } - - - - // create the parser regexes - // ========================= - var fileContent = data.toString(); - var keys = []; - var matches; - - this.emit( 'reading', file.path ); - - - // and we parse for functions... - // ============================= - var fnPattern = this.functions.join( '|' ).replace( '.', '\\.' ); - var singleQuotePattern = "'([^\'].*?[^\\\\])?'"; - var doubleQuotePattern = '"([^\"].*?[^\\\\])?"'; - var backQuotePattern = '`([^\`].*?[^\\\\])?`'; - var stringPattern = '(?:' + singleQuotePattern + '|' + doubleQuotePattern + '|' + backQuotePattern + ')'; - var pattern = '(?:\\W|^)(?:' + fnPattern + ')\\s*\\(?\\s*' + stringPattern + '(?:(?:[^).]*?)\\{(?:.*?)(?:(?:context|\'context\'|"context")\\s*:\\s*' + stringPattern + '(?:.*?)\\}))?'; - - if (this.defaultValues) { - var defaultValuePattern = '(,\\s*{[^}]*defaultValue\\s*:\\s*' + stringPattern + ')?'; - pattern += defaultValuePattern; - } - - var functionRegex = new RegExp( this.regex || pattern, 'g' ); - while (( matches = functionRegex.exec( fileContent ) )) { - var key = matches[1] || matches[2] || matches[3]; - if (key) { - var context = matches[4] || matches[5] || matches[6]; - if (context) { - key += this.contextSeparator + context; - } - var defaultValue = matches[8] || matches[9] || matches[10]; - keys.push( { key: key, defaultValue: defaultValue } ); - } - } - - // and we parse for functions with variables instead of string literals - // ==================================================================== - var noStringLiteralPattern = '[^a-zA-Z0-9_\'"`]((?:'+fnPattern+')(?:\\()\\s*(?:[^\'"`\)]+\\)))'; - var matches = new RegExp( noStringLiteralPattern, 'g' ).exec( fileContent ); - if (matches && matches.length) { - if (!this.ignoreVariables) { - this.emit( - 'error', - 'i18next-parser does not support variables in translation functions, use a string literal', - matches[1] - ); - } else { - console.log('[warning] '.yellow + 'i18next-parser does not support variables in translation functions, use a string literal ' + matches[1]); - } - } - - // and we parse for attributes in html - // =================================== - const attributes = '(?:' + this.attributes.join('|') + ')'; - var attributeWithValueRegex = new RegExp( '(?:\\s+' + attributes + '=")([^"]*)(?:")', 'gi' ); - var attributeWithoutValueRegex = new RegExp( '<([A-Z][A-Z0-9]*)(?:(?:\\s+[A-Z0-9-]+)(?:(?:=")(?:[^"]*)(?:"))?)*(?:(?:\\s+' + attributes + '))(?:(?:\\s+[A-Z0-9-]+)(?:(?:=")(?:[^"]*)(?:"))?)*\\s*(?:>(.*?)<\\/\\1>)', 'gi' ); - - while (( matches = attributeWithValueRegex.exec( fileContent ) )) { - matchKeys = matches[1].split(';'); - - for (var i in matchKeys) { - // remove any leading [] in the key - keys.push( { key: matchKeys[i].replace( /^\[[a-zA-Z0-9_-]*\]/ , '' ) } ); - } - } - - while (( matches = attributeWithoutValueRegex.exec( fileContent ) )) { - keys.push( { key: matches[2] } ); - } - - - // finally we add the parsed keys to the catalog - // ============================================= - for (var j in keys) { - var key = keys[j].key; - var defaultValue = keys[j].defaultValue; - // remove the backslash from escaped quotes - key = key.replace(/\\('|"|`)/g, '$1') - key = key.replace(/\\n/g, '\n'); - key = key.replace(/\\r/g, '\r'); - key = key.replace(/\\t/g, '\t'); - key = key.replace(/\\\\/g, '\\'); - - if ( key.indexOf( self.namespaceSeparator ) == -1 ) { - key = self.defaultNamespace + self.keySeparator + key; - } - else { - key = key.replace( self.namespaceSeparator, self.keySeparator ); - } - - self.translations.push( { key: key, defaultValue: defaultValue } ); - } - - done(); -}; - - - -Parser.prototype._flush = function(done) { - - var self = this; - var base = path.resolve( self.base, self.output ); - var translationsHash = {}; - - - - // remove duplicate keys - // ===================== - function getKey(translation) { - return translation.key; - } - function byKeyOrder(first, second) { - return first.key.localeCompare(second.key); - } - self.translations = _.uniqBy( self.translations, getKey ).sort(byKeyOrder); - - - - // turn the array of keys - // into an associative object - // ========================== - for (var index in self.translations) { - // simplify ${dot.separated.variables} into just their tails (${variables}) - var key = self.translations[index].key.replace( /\$\{(?:[^.}]+\.)*([^}]+)\}/g, '\${$1}' ); - var value = self.translations[index].defaultValue; - translationsHash = helpers.dotPathToHash( key, self.keySeparator, value, translationsHash ); - } - - - - // process each locale and namespace - // ================================= - for (var i in self.locales) { - var locale = self.locales[i]; - var localeBase = path.resolve( self.base, self.output, locale ); - - for (var namespace in translationsHash) { - - // get previous version of the files - var prefix = self.prefix.replace( '$LOCALE', locale ); - var suffix = self.suffix.replace( '$LOCALE', locale ); - var extension = self.extension.replace( '$LOCALE', locale ); - - var namespacePath = path.resolve( - localeBase, - prefix + namespace + suffix + extension - ); - var namespaceOldPath = path.resolve( - localeBase, - prefix + namespace + suffix + '_old' + extension - ); - - if ( fs.existsSync( namespacePath ) ) { - try { - currentTranslations = JSON.parse( fs.readFileSync( namespacePath ) ); - } - catch (error) { - this.emit( 'json_error', error.name, error.message ); - currentTranslations = {}; - } - } - else { - currentTranslations = {}; - } - - if ( fs.existsSync( namespaceOldPath ) ) { - try { - oldTranslations = JSON.parse( fs.readFileSync( namespaceOldPath ) ); - } - catch (error) { - this.emit( 'json_error', error.name, error.message ); - currentTranslations = {}; - } - } - else { - oldTranslations = {}; - } - - - - // merges existing translations with the new ones - mergedTranslations = helpers.mergeHashes( currentTranslations, translationsHash[namespace], null, this.keepRemoved ); - - // restore old translations if the key is empty - mergedTranslations.new = helpers.populateHash( oldTranslations, mergedTranslations.new ); - - // merges former old translations with the new ones - mergedTranslations.old = _.extend( oldTranslations, mergedTranslations.old ); - - - - // push files back to the stream - mergedTranslationsFile = new File({ - path: namespacePath, - base: base, - contents: new Buffer( JSON.stringify( mergedTranslations.new, null, 2 ) ) - }); - this.emit( 'writing', namespacePath ); - self.push( mergedTranslationsFile ); - - if ( self.writeOld ) { - mergedOldTranslationsFile = new File({ - path: namespaceOldPath, - base: base, - contents: new Buffer(JSON.stringify(mergedTranslations.old, null, 2)) - }); - this.emit( 'writing', namespaceOldPath ); - self.push( mergedOldTranslationsFile ); - } - - - } - } - - done(); -}; - - - -module.exports = function(options, transformConfig) { - return new Parser(options, transformConfig); -}; diff --git a/src/index.js b/src/index.js new file mode 100644 index 00000000..f6c756bd --- /dev/null +++ b/src/index.js @@ -0,0 +1,199 @@ +import { + dotPathToHash, + mergeHashes, + populateHash +} from './helpers' +import { Transform } from 'stream' +import _ from 'lodash' +import eol from 'eol' +import fs from 'fs' +import Parser from './parser' +import path from 'path' +import VirtualFile from 'vinyl' + +export default class i18nTransform extends Transform { + + constructor(options = {}) { + options.objectMode = true + super(options) + + this.defaults = { + contextSeparator: '_', // TODO + createOldLibraries: true, + defaultNamespace: 'translation', + defaultValue: '', + extension: '.json', + filename: '$NAMESPACE', + jsonIndentation: 2, + keepRemoved: false, + keySeparator: '.', + lexers: {}, + lineEnding: 'auto', + locales: ['en','fr'], + namespaceSeparator: ':', + output: 'locales', + sort: false + } + + this.options = {...this.defaults, ...options} + this.entries = [] + + this.parser = new Parser(this.options.lexers) + this.parser.on('error', error => this.emit('error', error)) + this.parser.on('warning', warning => this.emit('warning', warning)) + + this.localeRegex = /\$LOCALE/g + this.namespaceRegex = /\$NAMESPACE/g + } + + _transform(file, encoding, done) { + let content + if (file.isBuffer()) { + content = file.contents + } + else { + content = fs.readFileSync(file.path, enc) + } + + this.emit('reading', file) + + const extenstion = path.extname(file.path).substring(1) + const entries = this.parser.parse(content, extenstion) + + + entries.forEach(entry => { + let key = entry.key + const parts = key.split(this.options.namespaceSeparator) + + + if (parts.length > 1) { + entry.namespace = parts.shift() + } + else { + entry.namespace = this.options.defaultNamespace + } + + key = parts.join(this.options.namespaceSeparator) + key = key.replace(/\\('|"|`)/g, '$1') + key = key.replace(/\\n/g, '\n') + key = key.replace(/\\r/g, '\r') + key = key.replace(/\\t/g, '\t') + key = key.replace(/\\\\/g, '\\') + entry.key = entry.namespace + this.options.keySeparator + key + + this.addEntry(entry) + }) + + done() + } + + _flush(done) { + let catalog = {} + + if (this.options.sort) { + this.entries = this.entries.sort((a, b) => a.key.localeCompare(b.key)) + } + + this.entries.forEach(entry => { + catalog = dotPathToHash( + entry.key, + this.options.keySeparator, + entry.defaultValue || this.options.defaultValue, + catalog + ) + }) + + this.options.locales.forEach(locale => { + + const outputPath = path.resolve(this.options.output, locale) + + Object.keys(catalog).forEach((namespace) => { + let filename = this.options.filename + filename = filename.replace(this.localeRegex, locale) + filename = filename.replace(this.namespaceRegex, namespace) + + let extension = this.options.extension + extension = extension.replace(this.localeRegex, locale) + extension = extension.replace(this.namespaceRegex, namespace) + + const oldFilename = filename + '_old' + extension + filename += extension + + const namespacePath = path.resolve(outputPath, filename) + const namespaceOldPath = path.resolve(outputPath, oldFilename) + + let newCatalog + let existingCatalog = this.getCatalog(namespacePath) + let oldCatalog = this.getCatalog(namespaceOldPath) + + + // merges existing translations with the new ones + const {new: newKeys, old: oldKeys} = mergeHashes( + existingCatalog, + catalog[namespace], + null, + this.options.keepRemoved + ) + + // restore old translations if the key is empty + newCatalog = populateHash(oldCatalog, newKeys) + + // add keys from the current catalog that are no longer used + oldCatalog = _.extend(oldCatalog, oldKeys) + + // push files back to the stream + this.pushFile(namespacePath, newCatalog) + if ( this.options.createOldLibraries ) { + this.pushFile(namespaceOldPath, oldCatalog) + } + }) + }) + + done() + } + + addEntry(entry) { + let existing = this.entries.filter(x => x.key === entry.key)[0] + if (!existing) { + this.entries.push(entry) + } + else { + existing = {...existing, ...entry} + } + } + + getCatalog(path) { + let content + try { + content = JSON.parse( fs.readFileSync( path ) ) + } + catch (error) { + if (error.code !== 'ENOENT') { + this.emit('error', error) + } + content = {} + } + return content + } + + pushFile(path, contents) { + let text = JSON.stringify(contents, null, this.options.jsonIndentation) + '\n' + + if (this.options.lineEnding === 'auto') { + text = eol.auto(text) + } else if (lineEnding === '\r\n' || lineEnding === 'crlf') { + text = eol.crlf(text) + } else if (lineEnding === '\r' || lineEnding === 'cr') { + text = eol.cr(text) + } else { // Defaults to LF, aka \n + text = eol.lf(text) + } + + const file = new VirtualFile({ + path, + contents: Buffer.from(text) + }) + this.push(file) + } + +} diff --git a/src/parser.js b/src/parser.js new file mode 100644 index 00000000..82bae211 --- /dev/null +++ b/src/parser.js @@ -0,0 +1,64 @@ +import EventEmitter from 'events' +import HandlebarsLexer from './lexers/handlebars-lexer' +import HTMLLexer from './lexers/html-lexer' +import JavascriptLexer from './lexers/javascript-lexer' +import path from 'path' + +const lexers = { + hbs: ['HandlebarsLexer'], + handlebars: ['HandlebarsLexer'], + + htm: ['HTMLLexer'], + html: ['HTMLLexer'], + + js: ['JavascriptLexer'], + mjs: ['JavascriptLexer'], + + default: ['JavascriptLexer'] +} + +const lexersMap = { + 'HandlebarsLexer': HandlebarsLexer, + 'HTMLLexer': HTMLLexer, + 'JavascriptLexer': JavascriptLexer +} + +export default class Parser extends EventEmitter { + + constructor(options = {}) { + super(options) + this.lexers = {...lexers, ...options} + } + + parse(content, extension) { + let keys = [] + const lexers = this.lexers[extension] || this.lexers.default + + lexers.forEach(lexerConfig => { + let lexerName + let lexerOptions + + if (typeof lexerConfig === 'string') { + lexerName = lexerConfig + lexerOptions = {} + } + else { + lexerName = lexerConfig.lexer + delete lexerConfig.lexer + lexerOptions = lexerConfig + } + + + if (!lexersMap[lexerName]) { + this.emit('error', new Error(`Lexer '${lexerName}' does not exist`)) + } + + const Lexer = new lexersMap[lexerName](lexerOptions) + Lexer.on('warning', warning => this.emit('warning', warning)) + keys = keys.concat(Lexer.extract(content)) + }) + + return keys + } + +} diff --git a/test/locales/en/test_invalid.json b/test/locales/en/test_invalid.json new file mode 100644 index 00000000..7b360fef --- /dev/null +++ b/test/locales/en/test_invalid.json @@ -0,0 +1,3 @@ +// { + "invalid": "json", +} diff --git a/test/locales/en/test_leak.json b/test/locales/en/test_leak.json new file mode 100644 index 00000000..ade33c1a --- /dev/null +++ b/test/locales/en/test_leak.json @@ -0,0 +1,4 @@ +{ + "first": "first", + "second": "second" +} diff --git a/test/locales/fr/test_leak.json b/test/locales/fr/test_leak.json new file mode 100644 index 00000000..42c126cb --- /dev/null +++ b/test/locales/fr/test_leak.json @@ -0,0 +1,4 @@ +{ + "first": "premier", + "second": "" +} diff --git a/test/parser.js b/test/parser.js deleted file mode 100644 index 6f9ffbe4..00000000 --- a/test/parser.js +++ /dev/null @@ -1,542 +0,0 @@ -var fs = require('fs'); -var path = require('path'); -var assert = require('assert'); -var File = require('vinyl'); -var through = require('through2'); -var Parser = require('../index'); - -describe('parser', function () { - it('parses globally on multiple lines', function (done) { - var result; - var i18nextParser = Parser(); - var fakeFile = new File({ - contents: new Buffer("asd t('first') t('second') \n asd t('third') ad t('fourth')") - }); - - i18nextParser.on('data', function (file) { - if ( file.relative === 'en/translation.json' ) { - result = JSON.parse( file.contents ); - } - }); - i18nextParser.on('end', function () { - assert.deepEqual( result, { first: '', second: '', third: '', fourth: '' } ); - done(); - }); - - i18nextParser.end(fakeFile); - }); - - it('parses multiline function calls', function (done) { - var result; - var i18nextParser = Parser(); - var fakeFile = new File({ - contents: new Buffer("asd t(\n 'first'\n) t('second') \n asd t(\n\n'third')") - }); - - i18nextParser.on('data', function (file) { - if ( file.relative === 'en/translation.json' ) { - result = JSON.parse( file.contents ); - } - }); - i18nextParser.on('end', function () { - assert.deepEqual( result, { first: '', second: '', third: '' } ); - done(); - }); - - i18nextParser.end(fakeFile); - }); - - it('parses default js translations', function (done) { - var result; - var i18nextParser = Parser({ defaultValues: true }); - var fakeFile = new File({ - contents: new Buffer("asd t('first', { defaultValue: 'lol' }) t('second', {defaultValue:\"mdr\"}) \n asd t('third', { other: 'yolo', \ndefaultValue: `ptdr` }) ad t('fourth')") - }); - - i18nextParser.on('data', function (file) { - if ( file.relative === 'en/translation.json' ) { - result = JSON.parse( file.contents ); - } - }); - i18nextParser.on('end', function () { - assert.deepEqual( result, { first: 'lol', second: 'mdr', third: 'ptdr', fourth: '' } ); - done(); - }); - - i18nextParser.end(fakeFile); - }); - - it('parses attributes in html templates', function (done) { - var result; - var i18nextParser = Parser({ - attributes: ['data-i18n', 'translate', 't'] - }); - var fakeFile = new File({ - contents: new Buffer('

first

Second

Third

Fifth

') - }); - - i18nextParser.on('data', function (file) { - if ( file.relative === 'en/translation.json' ) { - result = JSON.parse( file.contents ); - } - }); - i18nextParser.on('end', function () { - assert.deepEqual( result, { first: '', second: '', third: '', fourth: '', fifth: '' } ); - done(); - }); - i18nextParser.end(fakeFile); - }); - - it('parses html templates', function (done) { - var result; - var i18nextParser = Parser(); - var fakeFile = new File({ - contents: fs.readFileSync( path.resolve(__dirname, 'templating/html.html') ) - }); - - i18nextParser.on('data', function (file) { - if ( file.relative === 'en/translation.json' ) { - result = JSON.parse( file.contents ); - } - }); - i18nextParser.on('end', function () { - assert.deepEqual( result, { first: '', second: '', third: '', fourth: '', fifth: '' } ); - done(); - }); - i18nextParser.end(fakeFile); - }); - - it('parses jade templates', function (done) { - var result; - var i18nextParser = Parser(); - var fakeFile = new File({ - contents: fs.readFileSync( path.resolve(__dirname, 'templating/jade.jade') ) - }); - - i18nextParser.on('data', function (file) { - if ( file.relative === 'en/translation.json' ) { - result = JSON.parse( file.contents ); - } - }); - i18nextParser.on('end', function () { - assert.deepEqual( result, { first: '' } ); - done(); - }); - - i18nextParser.end(fakeFile); - }); - - it('parses handlebars templates', function (done) { - var result; - var i18nextParser = Parser(); - var fakeFile = new File({ - contents: fs.readFileSync( path.resolve(__dirname, 'templating/handlebars.hbs') ) - }); - - i18nextParser.on('data', function (file) { - if ( file.relative === 'en/translation.json' ) { - result = JSON.parse( file.contents ); - } - }); - i18nextParser.on('end', function () { - assert.deepEqual( result, { first: '', second: '' } ); - done(); - }); - - i18nextParser.end(fakeFile); - }); - - it('creates two files per namespace and per locale', function (done) { - var results = []; - var i18nextParser = Parser({ - locales: ['en', 'de', 'fr'], - namespace: 'default' - }); - var fakeFile = new File({ - contents: new Buffer("asd t('ns1:first') t('second') \n asd t('ns2:third') ad t('fourth')") - }); - - i18nextParser.on('data', function (file) { - results.push(file.relative); - }); - i18nextParser.on('end', function () { - - var expectedFiles = [ - 'en/default.json', 'en/default_old.json', 'en/ns1.json', 'en/ns1_old.json', 'en/ns2.json', 'en/ns2_old.json', - 'de/default.json', 'de/default_old.json', 'de/ns1.json', 'de/ns1_old.json', 'de/ns2.json', 'de/ns2_old.json', - 'fr/default.json', 'fr/default_old.json', 'fr/ns1.json', 'fr/ns1_old.json', 'fr/ns2.json', 'fr/ns2_old.json' - ]; - var length = expectedFiles.length; - - expectedFiles.forEach(function (filename) { - assert( results.indexOf( filename ) !== -1 ); - if( ! --length ) done(); - }); - }); - - i18nextParser.end(fakeFile); - }); - - it('creates only one file per namespace and per locale with writeOld: false', function (done) { - var results = []; - var i18nextParser = Parser({ - locales: ['en', 'de', 'fr'], - namespace: 'default', - writeOld: false - }); - var fakeFile = new File({ - contents: new Buffer("asd t('ns1:first') t('second') \n asd t('ns2:third') ad t('fourth')") - }); - - i18nextParser.on('data', function (file) { - results.push(file.relative); - }); - i18nextParser.on('end', function () { - - var expectedFiles = [ - 'en/default.json', 'en/ns1.json', 'en/ns2.json', - 'de/default.json', 'de/ns1.json', 'de/ns2.json', - 'fr/default.json', 'fr/ns1.json', 'fr/ns2.json' - ]; - var length = expectedFiles.length; - - expectedFiles.forEach(function (filename) { - assert( results.indexOf( filename ) !== -1 ); - if( ! --length ) done(); - }); - }); - - i18nextParser.end(fakeFile); - }); - - it('handles prefix, suffix and extension with the current locale tag in each', function (done) { - var results = []; - var i18nextParser = Parser({ - locales: ['en'], - namespace: 'default', - prefix: 'p-$LOCALE-', - suffix: '-s-$LOCALE', - extension: '.$LOCALE.i18n' - }); - var fakeFile = new File({ - contents: new Buffer("asd t('fourth')") - }); - - i18nextParser.on('data', function (file) { - results.push(file.relative); - }); - i18nextParser.on('end', function () { - var expectedFiles = [ - 'en/p-en-default-s-en.en.i18n', 'en/p-en-default-s-en_old.en.i18n' - ]; - var length = expectedFiles.length; - - expectedFiles.forEach(function (filename) { - assert( results.indexOf( filename ) !== -1 ); - if( ! --length ) done(); - }); - }); - - i18nextParser.end(fakeFile); - }); - - it('handles custom namespace and key separators', function (done) { - var result; - var i18nextParser = Parser({ - namespaceSeparator: '?', - keySeparator: '-' - }); - var fakeFile = new File({ - base: __dirname, - contents: new Buffer("asd t('test_separators?first') t('test_separators?second-third')") - }); - - i18nextParser.on('data', function (file) { - if ( file.relative === 'en/test_separators.json' ) { - result = JSON.parse( file.contents ); - } - }); - i18nextParser.once('end', function () { - assert.deepEqual( result, { first: '', second: { third: '' } } ); - done(); - }); - - i18nextParser.end(fakeFile); - }); - - it('handles escaped single and double quotes', function (done) { - var result; - var i18nextParser = Parser(); - var fakeFile = new File({ - base: __dirname, - contents: new Buffer("asd t('escaped \\'single quotes\\'') t(\"escaped \\\"double quotes\\\"\")") - }); - - i18nextParser.on('data', function (file) { - if ( file.relative === 'en/translation.json' ) { - result = JSON.parse( file.contents ); - } - }); - i18nextParser.once('end', function () { - var keys = Object.keys(result); - assert.equal( keys[0], "escaped 'single quotes'" ); - assert.equal( keys[1], 'escaped "double quotes"' ); - done(); - }); - - i18nextParser.end(fakeFile); - }); - - it('handles escaped characters', function (done) { - var result; - var i18nextParser = Parser(); - var fakeFile = new File({ - base: __dirname, - contents: new Buffer("asd t('escaped backslash\\\\ newline\\n\\r tab\\t')") - }); - - i18nextParser.on('data', function (file) { - if ( file.relative === 'en/translation.json' ) { - result = JSON.parse( file.contents ); - } - }); - i18nextParser.once('end', function () { - var keys = Object.keys(result); - assert.equal( keys[0], 'escaped backslash\\ newline\n\r tab\t' ); - done(); - }); - - i18nextParser.end(fakeFile); - }); - - it('handles es6 template strings with expressions', function (done) { - var result; - var i18nextParser = Parser(); - var fakeFile = new File({ - base: __dirname, - contents: new Buffer("asd t(`root.plain`) t(`root.${expr}`) t(`root.${dotted.path}`)") - }); - - i18nextParser.on('data', function (file) { - if ( file.relative === 'en/translation.json' ) { - result = JSON.parse( file.contents ); - } - }); - i18nextParser.once('end', function () { - assert.deepEqual(Object.keys(result), ['root']); - assert.deepEqual(Object.keys(result.root), [ - '${path}', - '${expr}', - 'plain' - ]); - done(); - }); - - i18nextParser.end(fakeFile); - }); - - it('returns buffers', function (done) { - var i18nextParser = Parser(); - var fakeFile = new File({ - contents: new Buffer("asd t('first') t('second') \n asd t('third') ad t('fourth')") - }); - - i18nextParser.once('data', function (file) { - assert(file.isBuffer()); - done(); - }); - - i18nextParser.end(fakeFile); - }); - - it('retrieves values in existing file', function (done) { - var i18nextParser = Parser(); - var fakeFile = new File({ - base: __dirname, - contents: new Buffer("asd t('test_merge:first') t('test_merge:second')") - }); - - i18nextParser.on('data', function (file) { - if ( file.relative === 'en/test_merge.json' ) { - result = JSON.parse( file.contents ); - } - }); - i18nextParser.once('end', function () { - assert.deepEqual( result, { first: 'first', second: '' } ); - done(); - }); - - i18nextParser.end(fakeFile); - }); - - it('retrieves context values in existing file', function (done) { - var i18nextParser = Parser(); - var fakeFile = new File({ - base: __dirname, - contents: new Buffer("asd t('test_context:first')") - }); - - var expectedResult = { - first: 'first', - first_context1: 'first context1', - first_context2: '' - }; - - i18nextParser.on('data', function (file) { - if ( file.relative === 'en/test_context.json' ) { - result = JSON.parse( file.contents ); - } - }); - i18nextParser.once('end', function () { - assert.deepEqual( result, expectedResult ); - done(); - }); - - i18nextParser.end(fakeFile); - }); - - it('retrieves plural values in existing file', function (done) { - var i18nextParser = Parser(); - var fakeFile = new File({ - base: __dirname, - contents: new Buffer("asd t('test_plural:first') t('test_plural:second')") - }); - - var expectedResult = { - first: 'first', - first_plural: 'first plural', - second: 'second', - second_plural_0: 'second plural 0', - second_plural_12: 'second plural 12' - }; - - i18nextParser.on('data', function (file) { - if ( file.relative === 'en/test_plural.json' ) { - result = JSON.parse( file.contents ); - } - }); - i18nextParser.once('end', function () { - assert.deepEqual( result, expectedResult ); - done(); - }); - - i18nextParser.end(fakeFile); - }); - - it('retrieves plural and context values in existing file', function (done) { - var i18nextParser = Parser(); - var fakeFile = new File({ - base: __dirname, - contents: new Buffer("asd t('test_context_plural:first')") - }); - - var expectedResult = { - first: 'first', - first_context1_plural: 'first context1 plural', - first_context2_plural_2: 'first context2 plural 2' - }; - - i18nextParser.on('data', function (file) { - if ( file.relative === 'en/test_context_plural.json' ) { - result = JSON.parse( file.contents ); - } - }); - i18nextParser.once('end', function () { - assert.deepEqual( result, expectedResult ); - done(); - }); - - i18nextParser.end(fakeFile); - }); - - it('removes any trailing [bla] in the key', function (done) { - var result; - var i18nextParser = Parser(); - var fakeFile = new File({ - contents: new Buffer('

!first key!

') - }); - - i18nextParser.on('data', function (file) { - if ( file.relative === 'en/translation.json' ) { - result = JSON.parse( file.contents ); - } - }); - i18nextParser.on('end', function () { - assert.deepEqual( result, { first: '' } ); - done(); - }); - - i18nextParser.end(fakeFile); - }); - - it('fails to parse translation function with a variable', function (done) { - var i18nextParser = Parser(); - var fakeFile = new File({ - contents: new Buffer("asd t(firstVar)\n") - }); - - i18nextParser.on('error', function (message, region) { - assert.equal( message, 'i18next-parser does not support variables in translation functions, use a string literal' ); - assert.equal( region, 't(firstVar)' ); - done(); - }); - - i18nextParser.end(fakeFile); - }); - - it('does not parse text with `doesn\'t` or isolated `t` in it', function (done) { - var result; - var i18nextParser = Parser(); - var fakeFile = new File({ - contents: new Buffer("// FIX this doesn't work and this t is all alone\nt('first')\nt = function() {}") - }); - i18nextParser.on('data', function (file) { - if ( file.relative === 'en/translation.json' ) { - result = JSON.parse( file.contents ); - } - }); - i18nextParser.on('end', function () { - assert.deepEqual( result, {first: ''} ); - done(); - }); - i18nextParser.end(fakeFile); - }); - - it('parses context passed as object', function (done) { - var result; - var i18nextParser = Parser(); - var fakeFile = new File({ - contents: new Buffer('t("first", {context: \'date\'}) t("second", { "hello": "world", "context": \'form2\', "foo": "bar"}) t(`third`, { \'context\' : `context` }) t("fourth", { "context" : "pipo"})') - }); - - i18nextParser.on('data', function (file) { - if ( file.relative === 'en/translation.json' ) { - result = JSON.parse( file.contents ); - } - }); - i18nextParser.on('end', function () { - assert.deepEqual( result, { first_date: '', second_form2: '', third_context: '', fourth_pipo: '' } ); - done(); - }); - i18nextParser.end(fakeFile); - }); - - it('ignores functions that ends with a t', function (done) { - var result; - var i18nextParser = Parser(); - var fakeFile = new File({ - contents: new Buffer('import \'./yolo.js\'; t(\'first\');') - }); - - i18nextParser.on('data', function (file) { - if ( file.relative === 'en/translation.json' ) { - result = JSON.parse( file.contents ); - } - }); - i18nextParser.on('end', function () { - assert.deepEqual( result, { first: '' }); - done(); - }); - i18nextParser.end(fakeFile); - }); -}); diff --git a/test/parser.test.js b/test/parser.test.js new file mode 100644 index 00000000..a3c33141 --- /dev/null +++ b/test/parser.test.js @@ -0,0 +1,637 @@ +import { assert } from 'chai' +import Vinyl from 'vinyl' +import fs from 'fs' +import i18nTransform from '../src/index' +import path from 'path' + +describe('parser', function () { + it('parses globally on multiple lines', function (done) { + let result + const i18nextParser = new i18nTransform() + const fakeFile = new Vinyl({ + contents: Buffer.from("asd t('first') t('second') \n asd t('third') ad t('fourth')"), + path: 'file.js' + }) + i18nextParser.once('data', (file) => { + if (file.relative.endsWith('en/translation.json')) { + result = JSON.parse(file.contents) + } + }) + i18nextParser.on('end', function () { + assert.deepEqual( + result, + { first: '', second: '', third: '', fourth: '' } + ) + done() + }) + i18nextParser.end(fakeFile) + }) + + it('parses multiline function calls', function (done) { + let result + const i18nextParser = new i18nTransform() + const fakeFile = new Vinyl({ + contents: Buffer.from("asd t(\n 'first'\n) t('second') \n asd t(\n\n'third')"), + path: 'file.js' + }) + + i18nextParser.on('data', (file) => { + if (file.relative.endsWith('en/translation.json')) { + result = JSON.parse(file.contents) + } + }) + i18nextParser.on('end', function () { + assert.deepEqual( + result, + { first: '', second: '', third: '' } + ) + done() + }) + + i18nextParser.end(fakeFile) + }) + + it('parses html files', function (done) { + let result + const i18nextParser = new i18nTransform() + const fakeFile = new Vinyl({ + contents: fs.readFileSync( path.resolve(__dirname, 'templating/html.html') ), + path: 'file.html' + }) + const expected = { + first: '', + second: '', + third: '', + fourth: '', + fifth: 'bar', + sixth: '' + } + + i18nextParser.on('data', (file) => { + if (file.relative.endsWith('en/translation.json')) { + result = JSON.parse(file.contents) + } + }) + i18nextParser.on('end', function () { + assert.deepEqual(result, expected) + done() + }) + i18nextParser.end(fakeFile) + }) + + it('parses handlebars files', function (done) { + let result + const i18nextParser = new i18nTransform() + const fakeFile = new Vinyl({ + contents: fs.readFileSync( path.resolve(__dirname, 'templating/handlebars.hbs') ), + path: 'file.hbs' + }) + const expected = { + first: '', + second: 'defaultValue', + third: 'defaultValue', + fourth: 'defaultValue', + fifth: '', + sixth: '', + seventh: 'defaultValue' + } + + i18nextParser.on('data', (file) => { + if (file.relative.endsWith('en/translation.json')) { + result = JSON.parse(file.contents) + } + }) + i18nextParser.on('end', function () { + assert.deepEqual(result, expected) + done() + }) + + i18nextParser.end(fakeFile) + }) + + it('parses javascript files', function (done) { + let result + const i18nextParser = new i18nTransform() + const fakeFile = new Vinyl({ + contents: fs.readFileSync( path.resolve(__dirname, 'templating/javascript.js') ), + path: 'file.js' + }) + const expected = { + first: '', + second: 'defaultValue', + third: 'defaultValue', + fourth: '' + } + + i18nextParser.on('data', (file) => { + if (file.relative.endsWith('en/translation.json')) { + result = JSON.parse(file.contents) + } + }) + i18nextParser.on('end', function () { + assert.deepEqual(result, expected) + done() + }) + + i18nextParser.end(fakeFile) + }) + + it('creates two files per namespace and per locale', function (done) { + let results = [] + const i18nextParser = new i18nTransform({ + locales: ['en', 'de', 'fr'], + defaultNamespace: 'default' + }) + const fakeFile = new Vinyl({ + contents: Buffer.from("asd t('ns1:first') t('second') \n asd t('ns2:third') ad t('fourth')"), + path: 'file.js' + }) + + i18nextParser.on('data', (file) => { + results.push(file.relative.replace('locales/', '')) + }) + i18nextParser.on('end', function () { + + const expectedFiles = [ + 'en/default.json', 'en/default_old.json', 'en/ns1.json', 'en/ns1_old.json', 'en/ns2.json', 'en/ns2_old.json', + 'de/default.json', 'de/default_old.json', 'de/ns1.json', 'de/ns1_old.json', 'de/ns2.json', 'de/ns2_old.json', + 'fr/default.json', 'fr/default_old.json', 'fr/ns1.json', 'fr/ns1_old.json', 'fr/ns2.json', 'fr/ns2_old.json' + ] + let length = expectedFiles.length + + expectedFiles.forEach((filename) => { + assert.include(results, filename) + if( ! --length ) done() + }) + }) + + i18nextParser.end(fakeFile) + }) + + it('handles escaped single and double quotes', function (done) { + let result + const i18nextParser = new i18nTransform() + const fakeFile = new Vinyl({ + contents: Buffer.from("asd t('escaped \\'single quotes\\'') t(\"escaped \\\"double quotes\\\"\")"), + path: 'file.js' + }) + + i18nextParser.on('data', (file) => { + if (file.relative.endsWith('en/translation.json')) { + result = JSON.parse(file.contents) + } + }) + i18nextParser.once('end', function () { + const keys = Object.keys(result) + assert.equal( keys[0], "escaped 'single quotes'" ) + assert.equal( keys[1], 'escaped "double quotes"' ) + done() + }) + + i18nextParser.end(fakeFile) + }) + + it('handles escaped characters', function (done) { + let result + const i18nextParser = new i18nTransform() + const fakeFile = new Vinyl({ + contents: Buffer.from("asd t('escaped backslash\\\\ newline\\n\\r tab\\t')"), + path: 'file.js' + }) + + i18nextParser.on('data', (file) => { + if (file.relative.endsWith('en/translation.json')) { + result = JSON.parse(file.contents) + } + }) + i18nextParser.once('end', function () { + const keys = Object.keys(result) + assert.equal( keys[0], 'escaped backslash\\ newline\n\r tab\t' ) + done() + }) + + i18nextParser.end(fakeFile) + }) + + it('returns buffers', function (done) { + const i18nextParser = new i18nTransform() + const fakeFile = new Vinyl({ + contents: Buffer.from("asd t('first') t('second') \n asd t('third') ad t('fourth')"), + path: 'file.js' + }) + + i18nextParser.once('data', (file) => { + assert(file.isBuffer()) + done() + }) + + i18nextParser.end(fakeFile) + }) + + it('retrieves values in existing catalog', function (done) { + let result + const i18nextParser = new i18nTransform({output: 'test/locales'}) + const fakeFile = new Vinyl({ + contents: Buffer.from("asd t('test_merge:first') t('test_merge:second')"), + path: 'file.js' + }) + + i18nextParser.on('data', (file) => { + if (file.relative.endsWith('en/test_merge.json')) { + result = JSON.parse(file.contents) + } + }) + i18nextParser.once('end', function () { + assert.deepEqual( result, { first: 'first', second: '' } ) + done() + }) + + i18nextParser.end(fakeFile) + }) + + it('does not leak values between locales', function (done) { + let resultEN + let resultFR + const i18nextParser = new i18nTransform({output: 'test/locales'}) + const fakeFile = new Vinyl({ + contents: Buffer.from("asd t('test_leak:first') t('test_leak:second')"), + path: 'file.js' + }) + + i18nextParser.on('data', (file) => { + if (file.relative.endsWith('en/test_leak.json')) { + resultEN = JSON.parse(file.contents) + } + if (file.relative.endsWith('fr/test_leak.json')) { + resultFR = JSON.parse(file.contents) + } + }) + i18nextParser.once('end', function () { + assert.deepEqual( resultEN, { first: 'first', second: 'second' } ) + assert.deepEqual( resultFR, { first: 'premier', second: '' } ) + done() + }) + + i18nextParser.end(fakeFile) + }) + + it('retrieves context values in existing catalog', function (done) { + let result + const i18nextParser = new i18nTransform({output: 'test/locales'}) + const fakeFile = new Vinyl({ + contents: Buffer.from("asd t('test_context:first')"), + path: 'file.js' + }) + + const expectedResult = { + first: 'first', + first_context1: 'first context1', + first_context2: '' + } + + i18nextParser.on('data', (file) => { + if (file.relative.endsWith('en/test_context.json')) { + result = JSON.parse(file.contents) + } + }) + i18nextParser.once('end', function () { + assert.deepEqual( result, expectedResult ) + done() + }) + + i18nextParser.end(fakeFile) + }) + + it('retrieves plural values in existing catalog', function (done) { + let result + const i18nextParser = new i18nTransform({output: 'test/locales'}) + const fakeFile = new Vinyl({ + contents: Buffer.from("asd t('test_plural:first') t('test_plural:second')"), + path: 'file.js' + }) + + const expectedResult = { + first: 'first', + first_plural: 'first plural', + second: 'second', + second_plural_0: 'second plural 0', + second_plural_12: 'second plural 12' + } + + i18nextParser.on('data', (file) => { + if (file.relative.endsWith('en/test_plural.json')) { + result = JSON.parse(file.contents) + } + }) + i18nextParser.once('end', function () { + assert.deepEqual( result, expectedResult ) + done() + }) + + i18nextParser.end(fakeFile) + }) + + it('retrieves plural and context values in existing catalog', function (done) { + let result + const i18nextParser = new i18nTransform({output: 'test/locales'}) + const fakeFile = new Vinyl({ + contents: Buffer.from("asd t('test_context_plural:first')"), + path: 'file.js' + }) + + const expectedResult = { + first: 'first', + first_context1_plural: 'first context1 plural', + first_context2_plural_2: 'first context2 plural 2' + } + + i18nextParser.on('data', (file) => { + if (file.relative.endsWith('en/test_context_plural.json')) { + result = JSON.parse(file.contents) + } + }) + i18nextParser.once('end', function () { + assert.deepEqual( result, expectedResult ) + done() + }) + + i18nextParser.end(fakeFile) + }) + + describe('options', function () { + it('handles filename and extension with $LOCALE and $NAMESPACE var', function (done) { + let results = [] + const i18nextParser = new i18nTransform({ + locales: ['en'], + defaultNamespace: 'default', + filename: 'p-$LOCALE-$NAMESPACE', + extension: '.$LOCALE.i18n' + }) + const fakeFile = new Vinyl({ + contents: Buffer.from("asd t('fourth')"), + path: 'file.js' + }) + + i18nextParser.on('data', (file) => { + results.push(file.relative.replace('locales/', '')) + }) + i18nextParser.on('end', function () { + const expectedFiles = [ + 'en/p-en-default.en.i18n', 'en/p-en-default_old.en.i18n' + ] + let length = expectedFiles.length + + expectedFiles.forEach(function (filename) { + assert.include(results, filename) + if( ! --length ) done() + }) + }) + + i18nextParser.end(fakeFile) + }) + + it('handles custom namespace and key separators', function (done) { + let result + const i18nextParser = new i18nTransform({ + namespaceSeparator: '?', + keySeparator: '-' + }) + const fakeFile = new Vinyl({ + contents: Buffer.from("asd t('test_separators?first') t('test_separators?second-third')"), + path: 'file.js' + }) + + i18nextParser.on('data', (file) => { + if (file.relative.endsWith('en/test_separators.json')) { + result = JSON.parse(file.contents) + } + }) + i18nextParser.once('end', function () { + assert.deepEqual( result, { first: '', second: { third: '' } } ) + done() + }) + + i18nextParser.end(fakeFile) + }) + + it('supports a defaultValue', function(done) { + let result + const i18nextParser = new i18nTransform({ + defaultValue: 'NOT_TRANSLATED' + }) + const fakeFile = new Vinyl({ + contents: Buffer.from("asd t('first')"), + path: 'file.js' + }) + + i18nextParser.on('data', (file) => { + if (file.relative.endsWith('en/translation.json')) { + result = JSON.parse(file.contents) + } + }) + i18nextParser.once('end', function () { + assert.deepEqual( result, { first: 'NOT_TRANSLATED' } ) + done() + }) + + i18nextParser.end(fakeFile) + }) + + it('supports an jsonIndentation option', function(done) { + let result + const i18nextParser = new i18nTransform({ + jsonIndentation: 6 + }) + const fakeFile = new Vinyl({ + contents: Buffer.from("asd t('first')"), + path: 'file.js' + }) + + i18nextParser.on('data', (file) => { + if (file.relative.endsWith('en/translation.json')) { + result = file.contents.toString('utf8') + } + }) + i18nextParser.once('end', function () { + assert.deepEqual( result.split('\n')[1], ' "first": ""' ) + done() + }) + + i18nextParser.end(fakeFile) + }) + + it('handles skipping the old catalog with createOldLibraries=false', function (done) { + let results = [] + const i18nextParser = new i18nTransform({ + locales: ['en', 'de', 'fr'], + defaultNamespace: 'default', + createOldLibraries: false + }) + const fakeFile = new Vinyl({ + contents: Buffer.from("asd t('ns1:first') t('second') \n asd t('ns2:third') ad t('fourth')"), + path: 'file.js' + }) + + i18nextParser.on('data', (file) => { + results.push(file.relative.replace('locales/', '')) + }) + i18nextParser.on('end', function () { + + const expectedFiles = [ + 'en/default.json', 'en/ns1.json', 'en/ns2.json', + 'de/default.json', 'de/ns1.json', 'de/ns2.json', + 'fr/default.json', 'fr/ns1.json', 'fr/ns2.json' + ] + let length = expectedFiles.length + + expectedFiles.forEach(function (filename) { + assert.include(results, filename) + if( ! --length ) done() + }) + }) + + i18nextParser.end(fakeFile) + }) + + describe('lexers', function () { + it('support custom lexers options', function (done) { + let result + const i18nextParser = new i18nTransform({ + lexers: { + js: [{ + lexer: 'JavascriptLexer', + functions: ['bla', '_e'] + }] + } + }) + const fakeFile = new Vinyl({ + contents: Buffer.from("asd bla('first') _e('second')"), + path: 'file.js' + }) + + i18nextParser.on('data', (file) => { + if (file.relative.endsWith('en/translation.json')) { + result = JSON.parse(file.contents) + } + }) + i18nextParser.once('end', function () { + assert.deepEqual(result, {first: '', second: ''}) + done() + }) + + i18nextParser.end(fakeFile) + }) + }) + + describe('sort', function () { + it('does not sort by default', function (done) { + let result + const i18nextParser = new i18nTransform() + const fakeFile = new Vinyl({ + contents: Buffer.from("asd t('ccc') t('aaa') t('bbb.bbb') t('bbb.aaa')"), + path: 'file.js' + }) + + i18nextParser.on('data', (file) => { + if (file.relative.endsWith('en/translation.json')) { + result = JSON.parse(file.contents) + } + }) + i18nextParser.once('end', function () { + assert.sameOrderedMembers(Object.keys(result), ['ccc', 'aaa', 'bbb']) + assert.sameOrderedMembers(Object.keys(result.bbb), ['bbb', 'aaa']) + done() + }) + + i18nextParser.end(fakeFile) + }) + + it('supports sort as an option', function (done) { + let result + const i18nextParser = new i18nTransform({ + sort: true + }) + const fakeFile = new Vinyl({ + contents: Buffer.from("asd t('ccc') t('aaa') t('bbb.bbb') t('bbb.aaa')"), + path: 'file.js' + }) + + i18nextParser.on('data', (file) => { + if (file.relative.endsWith('en/translation.json')) { + result = JSON.parse(file.contents) + } + }) + i18nextParser.once('end', function () { + assert.sameOrderedMembers(Object.keys(result), ['aaa', 'bbb', 'ccc']) + assert.sameOrderedMembers(Object.keys(result.bbb), ['aaa', 'bbb']) + done() + }) + + i18nextParser.end(fakeFile) + }) + }) + }) + + describe('events', function () { + it('emits a `reading` event', function (done) { + let result + const i18nextParser = new i18nTransform() + const fakeFile = new Vinyl({ + contents: Buffer.from("content"), + path: 'file.js' + }) + + i18nextParser.on('reading', (file) => { + result = file.path + }) + i18nextParser.on('finish', function () { + assert.equal(result, 'file.js') + done() + }) + i18nextParser.end(fakeFile) + }) + + it('emits a `error` event if the catalog is not valid json', function (done) { + const i18nextParser = new i18nTransform({output: 'test/locales'}) + const fakeFile = new Vinyl({ + contents: Buffer.from("t('test_invalid:content')"), + path: 'file.js' + }) + + i18nextParser.on('error', (error) => { + assert.equal(error.message, 'Unexpected token / in JSON at position 0') + done() + }) + i18nextParser.end(fakeFile) + }) + + it('emits an `error` if a lexer does not exist', function (done) { + const results = [] + const i18nextParser = new i18nTransform({lexers: {js: ['fakeLexer']}}) + const fakeFile = new Vinyl({ + contents: Buffer.from("content"), + path: 'file.js' + }) + + i18nextParser.on('error', (error) => { + assert.equal(error.message, "Lexer 'fakeLexer' does not exist") + done() + }) + i18nextParser.end(fakeFile) + }) + + it('emits a `warning` event if a key contains a variable', function (done) { + const i18nextParser = new i18nTransform({output: 'test/locales'}) + const fakeFile = new Vinyl({ + contents: Buffer.from("t(variable)"), + path: 'file.js' + }) + + i18nextParser.on('warning', (message) => { + assert.equal(message, 'Key is not a string litteral: variable') + done() + }) + i18nextParser.end(fakeFile) + }) + }) +}) diff --git a/test/templating/handlebars.hbs b/test/templating/handlebars.hbs index ad6faac4..024d6e31 100644 --- a/test/templating/handlebars.hbs +++ b/test/templating/handlebars.hbs @@ -1,4 +1,16 @@
-

{{t "first"}}

- {{link-to (t "second") "foo"}} +

{{t "first"}}

+

{{t "second" "defaultValue" option="foo" context="male"}}

+

{{t "third" defaultValue="defaultValue" context="female"}}

+

{{t "fourth" context="male" bla='asd' defaultValue="defaultValue"}}

+

{{t + variable + "defaultValue" + option="foo" + context="male" + }}

+

{{t "fifth" variable option="foo" context="male"}}

+

{{t bljha bla-bla "second dsf" "defaultValue" option="foo" context="male"}}

+ {{link-to (t "sixth") "foo"}} + {{link-to (t "seventh" "defaultValue") "foo"}}
diff --git a/test/templating/html.html b/test/templating/html.html index 600da4b5..4a63bd2f 100644 --- a/test/templating/html.html +++ b/test/templating/html.html @@ -1,6 +1,17 @@
-

First

-

second

-

Fourth

-

{{ t "fifth" }}

+

First

+

second

+

Fourth

+

+

asd

+ diff --git a/test/templating/jade.jade b/test/templating/jade.jade deleted file mode 100644 index a4b8e6aa..00000000 --- a/test/templating/jade.jade +++ /dev/null @@ -1,2 +0,0 @@ -div.foobar - p= t('first') diff --git a/test/templating/javascript.js b/test/templating/javascript.js new file mode 100644 index 00000000..e43331a9 --- /dev/null +++ b/test/templating/javascript.js @@ -0,0 +1,9 @@ +i18n.t('first') +i18n.t('second', 'defaultValue') +i18n.t('third', {defaultValue: 'defaultValue'}) +i18n.t( + 'fou' + + 'rth' +) +i18n.t('not picked' + variable, {foo: bar}, 'bla' + 'asd', {}, foo+bar+baz ) +i18n.t(variable, {foo: bar}, 'bla' + 'asd', {}, foo+bar+baz ) From 169155ea4c7e5f753b36f5eb2fe7ab55af3998b1 Mon Sep 17 00:00:00 2001 From: Karel Ledru-Mathe Date: Wed, 21 Feb 2018 19:51:10 -0500 Subject: [PATCH 4/8] Update CLI code --- bin/cli.js | 225 ++++----- dist/helpers.js | 106 ++++ dist/index.js | 197 ++++++++ dist/lexers/base-lexer.js | 78 +++ dist/lexers/handlebars-lexer.js | 64 +++ dist/lexers/html-lexer.js | 75 +++ dist/lexers/javascript-lexer.js | 170 +++++++ dist/main.js | 85 ++++ dist/parser.js | 62 +++ package.json | 9 +- test/i18next-parser.config.js | 3 + yarn.lock | 823 +++++++++++++++++++++++++++++++- 12 files changed, 1730 insertions(+), 167 deletions(-) create mode 100644 dist/helpers.js create mode 100644 dist/index.js create mode 100644 dist/lexers/base-lexer.js create mode 100644 dist/lexers/handlebars-lexer.js create mode 100644 dist/lexers/html-lexer.js create mode 100644 dist/lexers/javascript-lexer.js create mode 100644 dist/main.js create mode 100644 dist/parser.js create mode 100644 test/i18next-parser.config.js diff --git a/bin/cli.js b/bin/cli.js index ed8f54cb..25b1bec4 100755 --- a/bin/cli.js +++ b/bin/cli.js @@ -1,162 +1,101 @@ #!/usr/bin/env node -var colors = require('colors'); -var program = require('commander'); -var fs = require('fs'); -var path = require('path'); -var Readable = require('stream').Readable; -var readdirp = require('readdirp'); -var through = require('through2'); -var File = require('vinyl'); -var mkdirp = require('mkdirp'); -var Parser = require('../index'); +var colors = require('colors') +var fs = require('fs') +var i18nTransform = require('../dist').default +var path = require('path') +var pkg = require('../package.json') +var program = require('commander') +var sort = require('gulp-sort') +var vfs = require('vinyl-fs') - -// Configure the command line -// ========================== program - .version('0.13.0') - .option( '-r, --recursive' , 'Parse sub directories' ) - .option( '-p, --parser ' , 'A custom regex to use to parse your code' ) - .option( '-a, --attributes ' , 'The html attributes to parse' ) - .option( '-o, --output ' , 'The directory to output parsed keys' ) - .option( '-f, --functions ' , 'The function names to parse in your code' ) - .option( '--prefix ' , 'Prefix filename for each locale, eg.: \'pre-$LOCALE-\' will yield \'pre-en-default.json\'') - .option( '--suffix ' , 'Suffix filename for each locale, eg.: \'-$suf-LOCALE\' will yield \'default-suf-en.json\'') - .option( '--extension ' , 'Specify extension for filename for each locale, eg.: \'.$LOCALE.i18n\' will yield \'default.en.i18n\'') - .option( '-n, --namespace ' , 'The default namespace (translation by default)' ) - .option( '-s, --namespace-separator ' , 'The default namespace separator (: by default)' ) - .option( '-k, --key-separator ' , 'The default key separator (. by default)' ) - .option( '-c, --context-separator ' , 'The default context separator (_ by default)' ) - .option( '-l, --locales ' , 'The locales in your application' ) - .option( '--directoryFilter ' , 'Filter directories' ) - .option( '--fileFilter ' , 'Filter files' ) - .option( '--keep-removed' , 'Prevent keys no longer found from being removed' ) - .option( '--write-old ' , 'Save (or don\'t if false) _old files' ) - .option( '--ignore-variables' , 'Don\'t fail when a variable is found' ) - .option( '--default-values' , 'Extract default values' ) - .parse( process.argv ); - - - -// Define the target directory -// =========================== -var firstArgument = process.argv[2]; -var input; -var output; - -if ( firstArgument && firstArgument.charAt(0) !== '-' ) { - var parts = firstArgument.split(':'); - - if ( parts.length > 1 ) { - input = path.resolve(process.cwd(), parts[0]); - output = path.resolve(process.cwd(), parts[1]); +.version(pkg.version) +.option('-c, --config ', 'Path to the config file (default: i18next-scanner.config.js)') +.option('-o, --output ', 'Path to the output directory (default: locales)') +.option('-s, --silent', 'Disable logging to stdout') + +program.on('--help', function() { + console.log(' Examples:') + console.log('') + console.log(' $ i18next "src/**/*.{js,jsx}"') + console.log(' $ i18next "/path/to/src/app.js" "/path/to/assets/index.html"') + console.log(' $ i18next --config i18next-parser.config.js --output /path/to/output \'src/**/*.{js,jsx}\'') + console.log('') +}) + +program.parse(process.argv) + +var args = program.args || [] + +var globs = args.map(function (s) { + s = s.trim() + if (s.match(/(^'.*'$|^".*"$)/)) { + s = s.slice(1, -1) } - else { - input = path.resolve(process.cwd(), firstArgument); + return s +}) + +var config = {} +if (program.config) { + try { + config = require(path.resolve(program.config)) + } catch (err) { + console.log(' [error] '.red + 'Config file does not exist: ' + program.config) + return } } -else { - input = process.cwd(); -} -output = output || program.output || 'locales'; +var output = config.output || program.output -if ( ! fs.existsSync(input) ) { - console.log( '\n' + 'Error: '.red + input + ' is not a file or directory\n' ); - process.exit( 1 ); +if (!output) { + console.log(' [error] '.red + 'an `output` is required via --config or --output option') + program.help() + program.exit(1) } - - -// Parse passed values -// =================== -program.locales = program.locales && program.locales.split(','); -program.attributes = program.attributes && program.attributes.split(','); -program.functions = program.functions && program.functions.split(','); -program.writeOld = program.writeOld !== 'false'; -program.directoryFilter = program.directoryFilter && program.directoryFilter.split(','); -program.fileFilter = program.fileFilter && program.fileFilter.split(','); -program.output = path.resolve(process.cwd(), output); - - +if (!globs.length) { + console.log(' [error] '.red + 'missing argument: ') + program.help() + return +} // Welcome message -// =============== -var intro = '\n'+ -'i18next Parser'.yellow + '\n' + -'--------------'.yellow + '\n' + -'Input: '.green + input + '\n' + -'Output: '.green + program.output + '\n\n'; - -console.log(intro); - - - -// Create a stream from the input -// ============================== -var stat = fs.statSync(input); -var stream; - -if ( stat.isDirectory() ) { - var args = { root: input }; - if( program.directoryFilter ) { - args.directoryFilter = program.directoryFilter; - } - if( program.fileFilter ) { - args.fileFilter = program.fileFilter; - } - if ( ! program.recursive) { - args.depth = 0; - } - - stream = readdirp( args ); +console.log() +console.log(' i18next Parser'.yellow) +console.log(' --------------'.yellow) +console.log(' Input: '.yellow + args.join(', ')) +console.log(' Output: '.yellow + program.output) +if (!program.silent) { + console.log() } -else { - stream = new Readable( { objectMode: true } ); - var file = new File({ - path: input, - stat: stat - }); - stream._read = function() { - stream.push( file ); - stream.push( null ); - }; -} - -// Parse the stream -// ================ -var parser = Parser(program); -parser.on('error', function (message, region) { - console.log('[error] '.red + message + ': ' + region.trim()); -}); +var count = 0 -stream - .pipe(through( { objectMode: true }, function (data, encoding, done) { - - if ( data instanceof File ) { - this.push( data ); +vfs.src(globs) +.pipe(sort()) +.pipe( + new i18nTransform(config) + .on('reading', function (file) { + if (!program.silent) { + console.log(' [read] '.green + file.path) } - else if ( data.fullPath ) { - var file = new File({ - path: data.fullPath, - stat: data.stat - }); - this.push( file ); + count++ + }) + .on('data', function (file) { + if (!program.silent) { + console.log(' [write] '.green + file.path) } - - done(); - })) - .pipe(parser.on('reading', function(path) { console.log('[parse] '.green + path) })) - .pipe(through( { objectMode: true }, function (file, encoding, done) { - - mkdirp.sync( path.dirname(file.path) ); - - fs.writeFileSync( file.path, file.contents + "\n" ); - - this.push( file ); - - done(); - })); + }) + .on('error', function (message, region) { + console.log(' [error] '.red + message + ': ' + region.trim()) + }) + .on('finish', function () { + if (!program.silent) { + console.log() + } + console.log(' Stats: '.yellow + count + ' files were parsed') + }) +) +.pipe(vfs.dest(output)) diff --git a/dist/helpers.js b/dist/helpers.js new file mode 100644 index 00000000..86013dad --- /dev/null +++ b/dist/helpers.js @@ -0,0 +1,106 @@ +'use strict';Object.defineProperty(exports, "__esModule", { value: true });exports.populateHash = exports.mergeHashes = exports.dotPathToHash = undefined;var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) {return typeof obj;} : function (obj) {return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;};var _lodash = require('lodash');var _lodash2 = _interopRequireDefault(_lodash);function _interopRequireDefault(obj) {return obj && obj.__esModule ? obj : { default: obj };} + +// Takes a `path` of the form 'foo.bar' and +// turn it into a hash {foo: {bar: ""}}. +// The generated hash can be attached to an +// optional `hash`. +function dotPathToHash(path) {var separator = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '.';var value = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : '';var target = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {}; + if (path.endsWith(separator)) { + path = path.slice(0, -separator.length); + } + + var result = {}; + var segments = path.split(separator); + + segments.reduce(function (hash, segment, index) { + if (index === segments.length - 1) { + hash[segment] = value; + } else + { + hash[segment] = {}; + } + return hash[segment]; + }, result); + + return _lodash2.default.merge(target, result); +} + + +// Takes a `source` hash and make sure its value +// are pasted in the `target` hash, if the target +// hash has the corresponding key (or if keepRemoved is true). +// If not, the value is added to an `old` hash. +function mergeHashes(source) {var target = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};var old = arguments[2];var keepRemoved = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false; + old = old || {}; + Object.keys(source).forEach(function (key) { + var hasNestedEntries = + _typeof(target[key]) === 'object' && + !Array.isArray(target[key]); + + + if (hasNestedEntries) { + var nested = mergeHashes(source[key], target[key], old[key], keepRemoved); + target[key] = nested.new; + old[key] = nested.old; + } else + if (target[key] !== undefined) { + if (typeof source[key] === 'string' || Array.isArray(source[key])) { + target[key] = source[key]; + } else + { + old[key] = source[key]; + } + } else + { + // support for plural in keys + var pluralMatch = /_plural(_\d+)?$/.test(key); + var singularKey = key.replace(/_plural(_\d+)?$/, ''); + + // support for context in keys + var contextMatch = /_([^_]+)?$/.test(singularKey); + var rawKey = singularKey.replace(/_([^_]+)?$/, ''); + + if ( + contextMatch && target[rawKey] !== undefined || + pluralMatch && target[singularKey] !== undefined) + { + target[key] = source[key]; + } else + if (keepRemoved) { + target[key] = source[key]; + old[key] = source[key]; + } else + { + old[key] = source[key]; + } + } + }); + + return { old: old, new: target }; +} + + +// Takes a `target` hash and replace its empty +// values with the `source` hash ones if they +// exist +function populateHash(source) {var target = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + Object.keys(source).forEach(function (key) { + if (target[key] !== undefined) { + if (_typeof(source[key]) === 'object') { + target[key] = populateHash(source[key], target[key]); + } else + if (target[key] === '') { + target[key] = source[key]; + } + } + }); + + return target; +}exports. + + + + +dotPathToHash = dotPathToHash;exports. +mergeHashes = mergeHashes;exports. +populateHash = populateHash; \ No newline at end of file diff --git a/dist/index.js b/dist/index.js new file mode 100644 index 00000000..45c54b97 --- /dev/null +++ b/dist/index.js @@ -0,0 +1,197 @@ +'use strict';Object.defineProperty(exports, "__esModule", { value: true });var _extends = Object.assign || function (target) {for (var i = 1; i < arguments.length; i++) {var source = arguments[i];for (var key in source) {if (Object.prototype.hasOwnProperty.call(source, key)) {target[key] = source[key];}}}return target;};var _createClass = function () {function defineProperties(target, props) {for (var i = 0; i < props.length; i++) {var descriptor = props[i];descriptor.enumerable = descriptor.enumerable || false;descriptor.configurable = true;if ("value" in descriptor) descriptor.writable = true;Object.defineProperty(target, descriptor.key, descriptor);}}return function (Constructor, protoProps, staticProps) {if (protoProps) defineProperties(Constructor.prototype, protoProps);if (staticProps) defineProperties(Constructor, staticProps);return Constructor;};}();var _helpers = require('./helpers'); + + + + +var _stream = require('stream'); +var _lodash = require('lodash');var _lodash2 = _interopRequireDefault(_lodash); +var _eol = require('eol');var _eol2 = _interopRequireDefault(_eol); +var _fs = require('fs');var _fs2 = _interopRequireDefault(_fs); +var _parser = require('./parser');var _parser2 = _interopRequireDefault(_parser); +var _path = require('path');var _path2 = _interopRequireDefault(_path); +var _vinyl = require('vinyl');var _vinyl2 = _interopRequireDefault(_vinyl);function _interopRequireDefault(obj) {return obj && obj.__esModule ? obj : { default: obj };}function _classCallCheck(instance, Constructor) {if (!(instance instanceof Constructor)) {throw new TypeError("Cannot call a class as a function");}}function _possibleConstructorReturn(self, call) {if (!self) {throw new ReferenceError("this hasn't been initialised - super() hasn't been called");}return call && (typeof call === "object" || typeof call === "function") ? call : self;}function _inherits(subClass, superClass) {if (typeof superClass !== "function" && superClass !== null) {throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);}subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } });if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;}var + +i18nTransform = function (_Transform) {_inherits(i18nTransform, _Transform); + + function i18nTransform() {var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};_classCallCheck(this, i18nTransform); + options.objectMode = true;var _this = _possibleConstructorReturn(this, (i18nTransform.__proto__ || Object.getPrototypeOf(i18nTransform)).call(this, + options)); + + _this.defaults = { + contextSeparator: '_', // TODO + createOldLibraries: true, + defaultNamespace: 'translation', + defaultValue: '', + extension: '.json', + filename: '$NAMESPACE', + jsonIndentation: 2, + keepRemoved: false, + keySeparator: '.', + lexers: {}, + lineEnding: 'auto', + locales: ['en', 'fr'], + namespaceSeparator: ':', + output: 'locales', + sort: false }; + + + _this.options = _extends({}, _this.defaults, options); + _this.entries = []; + + _this.parser = new _parser2.default(_this.options.lexers); + _this.parser.on('error', function (error) {return _this.emit('error', error);}); + _this.parser.on('warning', function (warning) {return _this.emit('warning', warning);}); + + _this.localeRegex = /\$LOCALE/g; + _this.namespaceRegex = /\$NAMESPACE/g;return _this; + }_createClass(i18nTransform, [{ key: '_transform', value: function _transform( + + file, encoding, done) {var _this2 = this; + var content = void 0; + if (file.isBuffer()) { + content = file.contents; + } else + { + content = _fs2.default.readFileSync(file.path, enc); + } + + this.emit('reading', file); + + var extenstion = _path2.default.extname(file.path).substring(1); + var entries = this.parser.parse(content, extenstion); + + + entries.forEach(function (entry) { + var key = entry.key; + var parts = key.split(_this2.options.namespaceSeparator); + + + if (parts.length > 1) { + entry.namespace = parts.shift(); + } else + { + entry.namespace = _this2.options.defaultNamespace; + } + + key = parts.join(_this2.options.namespaceSeparator); + key = key.replace(/\\('|"|`)/g, '$1'); + key = key.replace(/\\n/g, '\n'); + key = key.replace(/\\r/g, '\r'); + key = key.replace(/\\t/g, '\t'); + key = key.replace(/\\\\/g, '\\'); + entry.key = entry.namespace + _this2.options.keySeparator + key; + + _this2.addEntry(entry); + }); + + done(); + } }, { key: '_flush', value: function _flush( + + done) {var _this3 = this; + var catalog = {}; + + if (this.options.sort) { + this.entries = this.entries.sort(function (a, b) {return a.key.localeCompare(b.key);}); + } + + this.entries.forEach(function (entry) { + catalog = (0, _helpers.dotPathToHash)( + entry.key, + _this3.options.keySeparator, + entry.defaultValue || _this3.options.defaultValue, + catalog); + + }); + + this.options.locales.forEach(function (locale) { + + var outputPath = _path2.default.resolve(_this3.options.output, locale); + + Object.keys(catalog).forEach(function (namespace) { + var filename = _this3.options.filename; + filename = filename.replace(_this3.localeRegex, locale); + filename = filename.replace(_this3.namespaceRegex, namespace); + + var extension = _this3.options.extension; + extension = extension.replace(_this3.localeRegex, locale); + extension = extension.replace(_this3.namespaceRegex, namespace); + + var oldFilename = filename + '_old' + extension; + filename += extension; + + var namespacePath = _path2.default.resolve(outputPath, filename); + var namespaceOldPath = _path2.default.resolve(outputPath, oldFilename); + + var newCatalog = void 0; + var existingCatalog = _this3.getCatalog(namespacePath); + var oldCatalog = _this3.getCatalog(namespaceOldPath); + + + // merges existing translations with the new ones + var _mergeHashes = (0, _helpers.mergeHashes)( + existingCatalog, + catalog[namespace], + null, + _this3.options.keepRemoved),newKeys = _mergeHashes.new,oldKeys = _mergeHashes.old; + + + // restore old translations if the key is empty + newCatalog = (0, _helpers.populateHash)(oldCatalog, newKeys); + + // add keys from the current catalog that are no longer used + oldCatalog = _lodash2.default.extend(oldCatalog, oldKeys); + + // push files back to the stream + _this3.pushFile(namespacePath, newCatalog); + if (_this3.options.createOldLibraries) { + _this3.pushFile(namespaceOldPath, oldCatalog); + } + }); + }); + + done(); + } }, { key: 'addEntry', value: function addEntry( + + entry) { + var existing = this.entries.filter(function (x) {return x.key === entry.key;})[0]; + if (!existing) { + this.entries.push(entry); + } else + { + existing = _extends({}, existing, entry); + } + } }, { key: 'getCatalog', value: function getCatalog( + + path) { + var content = void 0; + try { + content = JSON.parse(_fs2.default.readFileSync(path)); + } + catch (error) { + if (error.code !== 'ENOENT') { + this.emit('error', error); + } + content = {}; + } + return content; + } }, { key: 'pushFile', value: function pushFile( + + path, contents) { + var text = JSON.stringify(contents, null, this.options.jsonIndentation) + '\n'; + + if (this.options.lineEnding === 'auto') { + text = _eol2.default.auto(text); + } else if (lineEnding === '\r\n' || lineEnding === 'crlf') { + text = _eol2.default.crlf(text); + } else if (lineEnding === '\r' || lineEnding === 'cr') { + text = _eol2.default.cr(text); + } else {// Defaults to LF, aka \n + text = _eol2.default.lf(text); + } + + var file = new _vinyl2.default({ + path: path, + contents: Buffer.from(text) }); + + this.push(file); + } }]);return i18nTransform;}(_stream.Transform);exports.default = i18nTransform; \ No newline at end of file diff --git a/dist/lexers/base-lexer.js b/dist/lexers/base-lexer.js new file mode 100644 index 00000000..567aa376 --- /dev/null +++ b/dist/lexers/base-lexer.js @@ -0,0 +1,78 @@ +'use strict';Object.defineProperty(exports, "__esModule", { value: true });var _extends = Object.assign || function (target) {for (var i = 1; i < arguments.length; i++) {var source = arguments[i];for (var key in source) {if (Object.prototype.hasOwnProperty.call(source, key)) {target[key] = source[key];}}}return target;};var _createClass = function () {function defineProperties(target, props) {for (var i = 0; i < props.length; i++) {var descriptor = props[i];descriptor.enumerable = descriptor.enumerable || false;descriptor.configurable = true;if ("value" in descriptor) descriptor.writable = true;Object.defineProperty(target, descriptor.key, descriptor);}}return function (Constructor, protoProps, staticProps) {if (protoProps) defineProperties(Constructor.prototype, protoProps);if (staticProps) defineProperties(Constructor, staticProps);return Constructor;};}();var _events = require('events');var _events2 = _interopRequireDefault(_events);function _interopRequireDefault(obj) {return obj && obj.__esModule ? obj : { default: obj };}function _classCallCheck(instance, Constructor) {if (!(instance instanceof Constructor)) {throw new TypeError("Cannot call a class as a function");}}function _possibleConstructorReturn(self, call) {if (!self) {throw new ReferenceError("this hasn't been initialised - super() hasn't been called");}return call && (typeof call === "object" || typeof call === "function") ? call : self;}function _inherits(subClass, superClass) {if (typeof superClass !== "function" && superClass !== null) {throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);}subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } });if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;}var + +BaseLexer = function (_EventEmitter) {_inherits(BaseLexer, _EventEmitter); + + function BaseLexer() {var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};_classCallCheck(this, BaseLexer);var _this = _possibleConstructorReturn(this, (BaseLexer.__proto__ || Object.getPrototypeOf(BaseLexer)).call(this)); + + _this.keys = []; + _this.functions = options.functions || ['t'];return _this; + }_createClass(BaseLexer, [{ key: 'populateKeysFromArguments', value: function populateKeysFromArguments( + + args) { + var firstArgument = args.arguments[0]; + var secondArgument = args.arguments[1]; + var isKeyString = this.validateString(firstArgument); + var isDefaultValueString = this.validateString(secondArgument); + + if (!isKeyString) { + this.emit('warning', 'Key is not a string litteral: ' + firstArgument); + } else + { + var result = _extends({}, + args.options, { + key: firstArgument.slice(1, -1) }); + + if (isDefaultValueString) { + result.defaultValue = secondArgument.slice(1, -1); + } + this.keys.push(result); + } + } }, { key: 'validateString', value: function validateString( + + string) { + var regex = new RegExp('^' + BaseLexer.stringPattern + '$', 'i'); + return regex.test(string); + } }, { key: 'functionPattern', value: function functionPattern() + + { + return '(?:' + this.functions.join('|').replace('.', '\\.') + ')'; + } }], [{ key: 'singleQuotePattern', get: function get() + + { + return "'(?:[^\'].*?[^\\\\])?'"; + } }, { key: 'doubleQuotePattern', get: function get() + + { + return '"(?:[^\"].*?[^\\\\])?"'; + } }, { key: 'backQuotePattern', get: function get() + + { + return '`(?:[^\`].*?[^\\\\])?`'; + } }, { key: 'variablePattern', get: function get() + + { + return '(?:[A-Z0-9_.-]+)'; + } }, { key: 'stringPattern', get: function get() + + { + return ( + '(?:' + + [ + BaseLexer.singleQuotePattern, + BaseLexer.doubleQuotePattern]. + join('|') + + ')'); + + } }, { key: 'stringOrVariablePattern', get: function get() + + { + return ( + '(?:' + + [ + BaseLexer.singleQuotePattern, + BaseLexer.doubleQuotePattern, + BaseLexer.variablePattern]. + join('|') + + ')'); + + } }]);return BaseLexer;}(_events2.default);exports.default = BaseLexer; \ No newline at end of file diff --git a/dist/lexers/handlebars-lexer.js b/dist/lexers/handlebars-lexer.js new file mode 100644 index 00000000..34fff28d --- /dev/null +++ b/dist/lexers/handlebars-lexer.js @@ -0,0 +1,64 @@ +'use strict';Object.defineProperty(exports, "__esModule", { value: true });var _createClass = function () {function defineProperties(target, props) {for (var i = 0; i < props.length; i++) {var descriptor = props[i];descriptor.enumerable = descriptor.enumerable || false;descriptor.configurable = true;if ("value" in descriptor) descriptor.writable = true;Object.defineProperty(target, descriptor.key, descriptor);}}return function (Constructor, protoProps, staticProps) {if (protoProps) defineProperties(Constructor.prototype, protoProps);if (staticProps) defineProperties(Constructor, staticProps);return Constructor;};}();var _baseLexer = require('./base-lexer');var _baseLexer2 = _interopRequireDefault(_baseLexer);function _interopRequireDefault(obj) {return obj && obj.__esModule ? obj : { default: obj };}function _classCallCheck(instance, Constructor) {if (!(instance instanceof Constructor)) {throw new TypeError("Cannot call a class as a function");}}function _possibleConstructorReturn(self, call) {if (!self) {throw new ReferenceError("this hasn't been initialised - super() hasn't been called");}return call && (typeof call === "object" || typeof call === "function") ? call : self;}function _inherits(subClass, superClass) {if (typeof superClass !== "function" && superClass !== null) {throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);}subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } });if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;}var + +HandlebarsLexer = function (_BaseLexer) {_inherits(HandlebarsLexer, _BaseLexer); + + function HandlebarsLexer() {var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};_classCallCheck(this, HandlebarsLexer);var _this = _possibleConstructorReturn(this, (HandlebarsLexer.__proto__ || Object.getPrototypeOf(HandlebarsLexer)).call(this, + options)); + + _this.functions = options.functions || ['t']; + + _this.createFunctionRegex(); + _this.createArgumentsRegex();return _this; + }_createClass(HandlebarsLexer, [{ key: 'extract', value: function extract( + + content) { + var matches = void 0; + + while (matches = this.functionRegex.exec(content)) { + var args = this.parseArguments(matches[1] || matches[2]); + this.populateKeysFromArguments(args); + } + + return this.keys; + } }, { key: 'parseArguments', value: function parseArguments( + + args) { + var matches = void 0; + var result = { + arguments: [], + options: {} }; + + while (matches = this.argumentsRegex.exec(args)) { + var arg = matches[1]; + var parts = arg.split('='); + result.arguments.push(arg); + if (parts.length === 2 && this.validateString(parts[1])) { + result.options[parts[0]] = parts[1].slice(1, -1); + } + } + return result; + } }, { key: 'createFunctionRegex', value: function createFunctionRegex() + + { + var functionPattern = this.functionPattern(); + var curlyPattern = '(?:{{)' + functionPattern + '\\s+(.*)(?:}})'; + var parenthesisPattern = '(?:\\()' + functionPattern + '\\s+(.*)(?:\\))'; + var pattern = curlyPattern + '|' + parenthesisPattern; + this.functionRegex = new RegExp(pattern, 'gi'); + return this.functionRegex; + } }, { key: 'createArgumentsRegex', value: function createArgumentsRegex() + + { + var pattern = + '(?:\\s+|^)' + + '(' + + '(?:' + + _baseLexer2.default.variablePattern + + '(?:=' + _baseLexer2.default.stringOrVariablePattern + ')?' + + ')' + + '|' + + _baseLexer2.default.stringPattern + + ')'; + this.argumentsRegex = new RegExp(pattern, 'gi'); + return this.argumentsRegex; + } }]);return HandlebarsLexer;}(_baseLexer2.default);exports.default = HandlebarsLexer; \ No newline at end of file diff --git a/dist/lexers/html-lexer.js b/dist/lexers/html-lexer.js new file mode 100644 index 00000000..41166d5f --- /dev/null +++ b/dist/lexers/html-lexer.js @@ -0,0 +1,75 @@ +'use strict';Object.defineProperty(exports, "__esModule", { value: true });var _extends = Object.assign || function (target) {for (var i = 1; i < arguments.length; i++) {var source = arguments[i];for (var key in source) {if (Object.prototype.hasOwnProperty.call(source, key)) {target[key] = source[key];}}}return target;};var _createClass = function () {function defineProperties(target, props) {for (var i = 0; i < props.length; i++) {var descriptor = props[i];descriptor.enumerable = descriptor.enumerable || false;descriptor.configurable = true;if ("value" in descriptor) descriptor.writable = true;Object.defineProperty(target, descriptor.key, descriptor);}}return function (Constructor, protoProps, staticProps) {if (protoProps) defineProperties(Constructor.prototype, protoProps);if (staticProps) defineProperties(Constructor, staticProps);return Constructor;};}();var _baseLexer = require('./base-lexer');var _baseLexer2 = _interopRequireDefault(_baseLexer);function _interopRequireDefault(obj) {return obj && obj.__esModule ? obj : { default: obj };}function _classCallCheck(instance, Constructor) {if (!(instance instanceof Constructor)) {throw new TypeError("Cannot call a class as a function");}}function _possibleConstructorReturn(self, call) {if (!self) {throw new ReferenceError("this hasn't been initialised - super() hasn't been called");}return call && (typeof call === "object" || typeof call === "function") ? call : self;}function _inherits(subClass, superClass) {if (typeof superClass !== "function" && superClass !== null) {throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);}subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } });if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;}var + +HTMLLexer = function (_BaseLexer) {_inherits(HTMLLexer, _BaseLexer); + + function HTMLLexer() {var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};_classCallCheck(this, HTMLLexer);var _this = _possibleConstructorReturn(this, (HTMLLexer.__proto__ || Object.getPrototypeOf(HTMLLexer)).call(this, + options)); + + _this.attr = options.attr || 'data-i18n'; + _this.optionAttr = options.optionAttr || 'data-i18n-options'; + + _this.createAttributeRegex(); + _this.createOptionAttributeRegex();return _this; + } + + // TODO rewrite to support the BaseLexer.extract() + _createClass(HTMLLexer, [{ key: 'extract', value: function extract(content) {var _this2 = this; + var matches = void 0; + var regex = new RegExp( + '<([A-Z][A-Z0-9]*)([^>]*\\s' + this.attr + '[^>]*)>(?:(.*?)<\\/\\1>)?', + 'gi');var _loop = function _loop() { + + + + var attrs = _this2.parseAttributes(matches[2]); + + // the attribute can hold multiple keys + var keys = attrs.keys.split(';'); + keys.forEach(function (key) { + // remove any leading [] in the key + key = key.replace(/^\[[a-zA-Z0-9_-]*\]/, ''); + + // if empty grab innerHTML from regex + key = key || matches[3]; + + if (key) { + _this2.keys.push(_extends({}, attrs.options, { key: key })); + } + });};while (matches = regex.exec(content)) {_loop(); + + } + + return this.keys; + } }, { key: 'createAttributeRegex', value: function createAttributeRegex() + + { + var pattern = '(?:' + this.attr + ')(?:\\s*=\\s*(' + _baseLexer2.default.stringPattern + ')|$|\\s)'; + this.attrRegex = new RegExp(pattern, 'i'); + return this.attrRegex; + } }, { key: 'createOptionAttributeRegex', value: function createOptionAttributeRegex() + + { + var pattern = '(?:' + this.optionAttr + ')(?:\\s*=\\s*(' + _baseLexer2.default.stringPattern + '))?'; + this.optionAttrRegex = new RegExp(pattern, 'i'); + return this.optionAttrRegex; + } }, { key: 'parseAttributes', value: function parseAttributes( + + args) { + var result = { keys: '', options: {} }; + this.attrRegex.lastIndex = 0; + var keysMatch = this.attrRegex.exec(args); + if (keysMatch && keysMatch[1]) { + result.keys = keysMatch[1].slice(1, -1); + } + + this.optionAttrRegex.lastIndex = 0; + var optionsMatch = this.optionAttrRegex.exec(args); + if (optionsMatch && optionsMatch[1]) { + try { + result.options = JSON.parse(optionsMatch[1].slice(1, -1)); + } finally + {} + } + + return result; + } }]);return HTMLLexer;}(_baseLexer2.default);exports.default = HTMLLexer; \ No newline at end of file diff --git a/dist/lexers/javascript-lexer.js b/dist/lexers/javascript-lexer.js new file mode 100644 index 00000000..029b35ac --- /dev/null +++ b/dist/lexers/javascript-lexer.js @@ -0,0 +1,170 @@ +'use strict';Object.defineProperty(exports, "__esModule", { value: true });var _createClass = function () {function defineProperties(target, props) {for (var i = 0; i < props.length; i++) {var descriptor = props[i];descriptor.enumerable = descriptor.enumerable || false;descriptor.configurable = true;if ("value" in descriptor) descriptor.writable = true;Object.defineProperty(target, descriptor.key, descriptor);}}return function (Constructor, protoProps, staticProps) {if (protoProps) defineProperties(Constructor.prototype, protoProps);if (staticProps) defineProperties(Constructor, staticProps);return Constructor;};}();var _baseLexer = require('./base-lexer');var _baseLexer2 = _interopRequireDefault(_baseLexer);function _interopRequireDefault(obj) {return obj && obj.__esModule ? obj : { default: obj };}function _classCallCheck(instance, Constructor) {if (!(instance instanceof Constructor)) {throw new TypeError("Cannot call a class as a function");}}function _possibleConstructorReturn(self, call) {if (!self) {throw new ReferenceError("this hasn't been initialised - super() hasn't been called");}return call && (typeof call === "object" || typeof call === "function") ? call : self;}function _inherits(subClass, superClass) {if (typeof superClass !== "function" && superClass !== null) {throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);}subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } });if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;}var + +JavascriptLexer = function (_BaseLexer) {_inherits(JavascriptLexer, _BaseLexer); + + function JavascriptLexer() {var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};_classCallCheck(this, JavascriptLexer);var _this = _possibleConstructorReturn(this, (JavascriptLexer.__proto__ || Object.getPrototypeOf(JavascriptLexer)).call(this, + options)); + + _this.functions = options.functions || ['t']; + + _this.createFunctionRegex(); + _this.createArgumentsRegex(); + _this.createHashRegex();return _this; + }_createClass(JavascriptLexer, [{ key: 'extract', value: function extract( + + content) { + var matches = void 0; + + while (matches = this.functionRegex.exec(content)) { + var args = this.parseArguments(matches[1] || matches[2]); + this.populateKeysFromArguments(args); + } + + return this.keys; + } }, { key: 'parseArguments', value: function parseArguments( + + args) { + var matches = void 0; + var result = { + arguments: [], + options: {} }; + + while (matches = this.argumentsRegex.exec(args)) { + var arg = matches[1]; + + if (arg.startsWith('{')) { + var optionMatches = void 0; + while (optionMatches = this.hashRegex.exec(args)) { + var key = optionMatches[2]; + var value = optionMatches[3]; + if (this.validateString(value)) { + result.options[key] = value.slice(1, -1); + } + } + } else + { + arg = this.concatenateString(arg); + } + result.arguments.push(arg); + } + return result; + } }, { key: 'concatenateString', value: function concatenateString( + + string) {var _this2 = this; + string = string.trim(); + var matches = void 0; + var containsVariable = false; + var parts = []; + var quotationMark = string.charAt(0) === '"' ? '"' : '\''; + + var regex = new RegExp(JavascriptLexer.concatenatedSegmentPattern, 'gi'); + while (matches = regex.exec(string)) { + var match = matches[0].trim(); + if (match !== '+') { + parts.push(match); + } + } + + var result = parts.reduce( + function (concatenatedString, x) { + x = x && x.trim(); + if (_this2.validateString(x)) { + concatenatedString += x.slice(1, -1); + } else + { + containsVariable = true; + } + return concatenatedString; + }, + ''); + + if (!result || containsVariable) { + return string; + } else + { + return quotationMark + result + quotationMark; + } + } }, { key: 'createFunctionRegex', value: function createFunctionRegex() + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + { + var pattern = + '(?:\\W|^)' + + this.functionPattern() + '\\s*\\(\\s*' + + JavascriptLexer.stringOrVariableOrHashPattern + + '\\s*\\)'; + + this.functionRegex = new RegExp(pattern, 'gi'); + return this.functionRegex; + } }, { key: 'createArgumentsRegex', value: function createArgumentsRegex() + + { + var pattern = + '(' + + [ + JavascriptLexer.concatenatedArgumentPattern, + JavascriptLexer.hashPattern]. + join('|') + + ')' + + '(?:\\s*,\\s*)?'; + + this.argumentsRegex = new RegExp(pattern, 'gi'); + return this.argumentsRegex; + } }, { key: 'createHashRegex', value: function createHashRegex() + + { + var pattern = + '(?:(\'|")?(' + + [ + 'context', + 'defaultValue']. + join('|') + + ')\\1)' + + '(?:\\s*:\\s*)' + + '(' + _baseLexer2.default.stringPattern + ')'; + + this.hashRegex = new RegExp(pattern, 'gi'); + return this.hashRegex; + } }], [{ key: 'concatenatedSegmentPattern', get: function get() {return [_baseLexer2.default.singleQuotePattern, _baseLexer2.default.doubleQuotePattern, _baseLexer2.default.backQuotePattern, _baseLexer2.default.variablePattern, '(?:\\s*\\+\\s*)' // support for concatenation via + + ].join('|');} }, { key: 'concatenatedArgumentPattern', get: function get() {return '(' + '(?:' + JavascriptLexer.concatenatedSegmentPattern + ')+' + ')';} }, { key: 'hashPattern', get: function get() {return '(\\{[^}]*\\})';} }, { key: 'stringOrVariableOrHashPattern', get: function get() {return '(' + '(' + '(?:' + [JavascriptLexer.concatenatedArgumentPattern, JavascriptLexer.hashPattern].join('|') + ')' + '(?:\\s*,\\s*)?' + ')+' + ')';} }]);return JavascriptLexer;}(_baseLexer2.default);exports.default = JavascriptLexer; \ No newline at end of file diff --git a/dist/main.js b/dist/main.js new file mode 100644 index 00000000..b158af67 --- /dev/null +++ b/dist/main.js @@ -0,0 +1,85 @@ +/******/ (function(modules) { // webpackBootstrap +/******/ // The module cache +/******/ var installedModules = {}; +/******/ +/******/ // The require function +/******/ function __webpack_require__(moduleId) { +/******/ +/******/ // Check if module is in cache +/******/ if(installedModules[moduleId]) { +/******/ return installedModules[moduleId].exports; +/******/ } +/******/ // Create a new module (and put it into the cache) +/******/ var module = installedModules[moduleId] = { +/******/ i: moduleId, +/******/ l: false, +/******/ exports: {} +/******/ }; +/******/ +/******/ // Execute the module function +/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); +/******/ +/******/ // Flag the module as loaded +/******/ module.l = true; +/******/ +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } +/******/ +/******/ +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = modules; +/******/ +/******/ // expose the module cache +/******/ __webpack_require__.c = installedModules; +/******/ +/******/ // define getter function for harmony exports +/******/ __webpack_require__.d = function(exports, name, getter) { +/******/ if(!__webpack_require__.o(exports, name)) { +/******/ Object.defineProperty(exports, name, { +/******/ configurable: false, +/******/ enumerable: true, +/******/ get: getter +/******/ }); +/******/ } +/******/ }; +/******/ +/******/ // define __esModule on exports +/******/ __webpack_require__.r = function(exports) { +/******/ Object.defineProperty(exports, '__esModule', { value: true }); +/******/ }; +/******/ +/******/ // getDefaultExport function for compatibility with non-harmony modules +/******/ __webpack_require__.n = function(module) { +/******/ var getter = module && module.__esModule ? +/******/ function getDefault() { return module['default']; } : +/******/ function getModuleExports() { return module; }; +/******/ __webpack_require__.d(getter, 'a', getter); +/******/ return getter; +/******/ }; +/******/ +/******/ // Object.prototype.hasOwnProperty.call +/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; +/******/ +/******/ // __webpack_public_path__ +/******/ __webpack_require__.p = ""; +/******/ +/******/ +/******/ // Load entry module and return exports +/******/ return __webpack_require__(__webpack_require__.s = "./src/index.js"); +/******/ }) +/************************************************************************/ +/******/ ({ + +/***/ "./src/index.js": +/*!**********************!*\ + !*** ./src/index.js ***! + \**********************/ +/*! no static exports found */ +/***/ (function(module, exports) { + +eval("throw new Error(\"Module parse failed: Unexpected token (38:20)\\nYou may need an appropriate loader to handle this file type.\\n| }\\n| \\n| this.options = {...this.defaults, ...options}\\n| this.parser = new Parser(this.options.lexers)\\n| this.entries = []\");\n\n//# sourceURL=webpack:///./src/index.js?"); + +/***/ }) + +/******/ }); \ No newline at end of file diff --git a/dist/parser.js b/dist/parser.js new file mode 100644 index 00000000..885a708d --- /dev/null +++ b/dist/parser.js @@ -0,0 +1,62 @@ +'use strict';Object.defineProperty(exports, "__esModule", { value: true });var _extends = Object.assign || function (target) {for (var i = 1; i < arguments.length; i++) {var source = arguments[i];for (var key in source) {if (Object.prototype.hasOwnProperty.call(source, key)) {target[key] = source[key];}}}return target;};var _createClass = function () {function defineProperties(target, props) {for (var i = 0; i < props.length; i++) {var descriptor = props[i];descriptor.enumerable = descriptor.enumerable || false;descriptor.configurable = true;if ("value" in descriptor) descriptor.writable = true;Object.defineProperty(target, descriptor.key, descriptor);}}return function (Constructor, protoProps, staticProps) {if (protoProps) defineProperties(Constructor.prototype, protoProps);if (staticProps) defineProperties(Constructor, staticProps);return Constructor;};}();var _events = require('events');var _events2 = _interopRequireDefault(_events); +var _handlebarsLexer = require('./lexers/handlebars-lexer');var _handlebarsLexer2 = _interopRequireDefault(_handlebarsLexer); +var _htmlLexer = require('./lexers/html-lexer');var _htmlLexer2 = _interopRequireDefault(_htmlLexer); +var _javascriptLexer = require('./lexers/javascript-lexer');var _javascriptLexer2 = _interopRequireDefault(_javascriptLexer); +var _path = require('path');var _path2 = _interopRequireDefault(_path);function _interopRequireDefault(obj) {return obj && obj.__esModule ? obj : { default: obj };}function _classCallCheck(instance, Constructor) {if (!(instance instanceof Constructor)) {throw new TypeError("Cannot call a class as a function");}}function _possibleConstructorReturn(self, call) {if (!self) {throw new ReferenceError("this hasn't been initialised - super() hasn't been called");}return call && (typeof call === "object" || typeof call === "function") ? call : self;}function _inherits(subClass, superClass) {if (typeof superClass !== "function" && superClass !== null) {throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);}subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } });if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;} + +var lexers = { + hbs: ['HandlebarsLexer'], + handlebars: ['HandlebarsLexer'], + + htm: ['HTMLLexer'], + html: ['HTMLLexer'], + + js: ['JavascriptLexer'], + mjs: ['JavascriptLexer'], + + default: ['JavascriptLexer'] }; + + +var lexersMap = { + 'HandlebarsLexer': _handlebarsLexer2.default, + 'HTMLLexer': _htmlLexer2.default, + 'JavascriptLexer': _javascriptLexer2.default };var + + +Parser = function (_EventEmitter) {_inherits(Parser, _EventEmitter); + + function Parser() {var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};_classCallCheck(this, Parser);var _this = _possibleConstructorReturn(this, (Parser.__proto__ || Object.getPrototypeOf(Parser)).call(this, + options)); + _this.lexers = _extends({}, lexers, options);return _this; + }_createClass(Parser, [{ key: 'parse', value: function parse( + + content, extension) {var _this2 = this; + var keys = []; + var lexers = this.lexers[extension] || this.lexers.default; + + lexers.forEach(function (lexerConfig) { + var lexerName = void 0; + var lexerOptions = void 0; + + if (typeof lexerConfig === 'string') { + lexerName = lexerConfig; + lexerOptions = {}; + } else + { + lexerName = lexerConfig.lexer; + delete lexerConfig.lexer; + lexerOptions = lexerConfig; + } + + + if (!lexersMap[lexerName]) { + _this2.emit('error', new Error('Lexer \'' + lexerName + '\' does not exist')); + } + + var Lexer = new lexersMap[lexerName](lexerOptions); + Lexer.on('warning', function (warning) {return _this2.emit('warning', warning);}); + keys = keys.concat(Lexer.extract(content)); + }); + + return keys; + } }]);return Parser;}(_events2.default);exports.default = Parser; \ No newline at end of file diff --git a/package.json b/package.json index 9e25847c..d131e49c 100644 --- a/package.json +++ b/package.json @@ -9,20 +9,20 @@ "i18next": "./bin/cli.js" }, "scripts": { - "test": "mocha --require babel-register --require babel-polyfill test/**/*.test.js" + "test": "mocha --require babel-register --require babel-polyfill test/**/*.test.js", + "watch": "babel src -d dist -w" }, "repository": { "type": "git", "url": "https://github.com/i18next/i18next-parser" }, "dependencies": { - "colors": "~1.1.2", + "colors": "~1.2.0-rc0", "commander": "~2.9.0", "concat-stream": "~1.6.0", "eol": "^0.9.1", + "gulp-sort": "^2.0.0", "lodash": "~4.17.4", - "mkdirp": "~0.5.1", - "readdirp": "~2.1.0", "through2": "~2.0.3", "vinyl": "~2.0.1", "vinyl-fs": "^3.0.2" @@ -31,6 +31,7 @@ "node": ">=0.10.22" }, "devDependencies": { + "babel-cli": "^6.26.0", "babel-plugin-transform-object-rest-spread": "^6.26.0", "babel-polyfill": "^6.26.0", "babel-preset-env": "^1.6.1", diff --git a/test/i18next-parser.config.js b/test/i18next-parser.config.js new file mode 100644 index 00000000..2a923664 --- /dev/null +++ b/test/i18next-parser.config.js @@ -0,0 +1,3 @@ +module.exports = { + output: 'manual' +}; diff --git a/yarn.lock b/yarn.lock index c403d3f8..4fc43da4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,6 +2,17 @@ # yarn lockfile v1 +abbrev@1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" + +ajv@^4.9.1: + version "4.11.8" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-4.11.8.tgz#82ffb02b29e662ae53bdc20af15947706739c536" + dependencies: + co "^4.6.0" + json-stable-stringify "^1.0.1" + ansi-regex@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" @@ -10,16 +21,97 @@ ansi-styles@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" +anymatch@^1.3.0: + version "1.3.2" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-1.3.2.tgz#553dcb8f91e3c889845dfdba34c77721b90b9d7a" + dependencies: + micromatch "^2.1.5" + normalize-path "^2.0.0" + append-buffer@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/append-buffer/-/append-buffer-1.0.2.tgz#d8220cf466081525efea50614f3de6514dfa58f1" dependencies: buffer-equal "^1.0.0" +aproba@^1.0.3: + version "1.2.0" + resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" + +are-we-there-yet@~1.1.2: + version "1.1.4" + resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz#bb5dca382bb94f05e15194373d16fd3ba1ca110d" + dependencies: + delegates "^1.0.0" + readable-stream "^2.0.6" + +arr-diff@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-2.0.0.tgz#8f3b827f955a8bd669697e4a4256ac3ceae356cf" + dependencies: + arr-flatten "^1.0.1" + +arr-flatten@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1" + +array-unique@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.2.1.tgz#a1d97ccafcbc2625cc70fadceb36a50c58b01a53" + +asn1@~0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.3.tgz#dac8787713c9966849fc8180777ebe9c1ddf3b86" + +assert-plus@1.0.0, assert-plus@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" + +assert-plus@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-0.2.0.tgz#d74e1b87e7affc0db8aadb7021f3fe48101ab234" + assertion-error@^1.0.1: version "1.1.0" resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b" +async-each@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.1.tgz#19d386a1d9edc6e7c1c85d388aedbcc56d33602d" + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + +aws-sign2@~0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.6.0.tgz#14342dd38dbcc94d0e5b87d763cd63612c0e794f" + +aws4@^1.2.1: + version "1.6.0" + resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.6.0.tgz#83ef5ca860b2b32e4a0deedee8c771b9db57471e" + +babel-cli@^6.26.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-cli/-/babel-cli-6.26.0.tgz#502ab54874d7db88ad00b887a06383ce03d002f1" + dependencies: + babel-core "^6.26.0" + babel-polyfill "^6.26.0" + babel-register "^6.26.0" + babel-runtime "^6.26.0" + commander "^2.11.0" + convert-source-map "^1.5.0" + fs-readdir-recursive "^1.0.0" + glob "^7.1.2" + lodash "^4.17.4" + output-file-sync "^1.1.2" + path-is-absolute "^1.0.1" + slash "^1.0.0" + source-map "^0.5.6" + v8flags "^2.1.1" + optionalDependencies: + chokidar "^1.6.1" + babel-code-frame@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b" @@ -501,6 +593,28 @@ balanced-match@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" +bcrypt-pbkdf@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz#63bc5dcb61331b92bc05fd528953c33462a06f8d" + dependencies: + tweetnacl "^0.14.3" + +binary-extensions@^1.0.0: + version "1.11.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.11.0.tgz#46aa1751fb6a2f93ee5e689bb1087d4b14c6c205" + +block-stream@*: + version "0.0.9" + resolved "https://registry.yarnpkg.com/block-stream/-/block-stream-0.0.9.tgz#13ebfe778a03205cfe03751481ebb4b3300c126a" + dependencies: + inherits "~2.0.0" + +boom@2.x.x: + version "2.10.1" + resolved "https://registry.yarnpkg.com/boom/-/boom-2.10.1.tgz#39c8918ceff5799f83f9492a848f625add0c766f" + dependencies: + hoek "2.x.x" + brace-expansion@^1.1.7: version "1.1.8" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.8.tgz#c07b211c7c952ec1f8efd51a77ef0d1d3990a292" @@ -508,6 +622,14 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" +braces@^1.8.2: + version "1.8.5" + resolved "https://registry.yarnpkg.com/braces/-/braces-1.8.5.tgz#ba77962e12dff969d6b76711e914b737857bf6a7" + dependencies: + expand-range "^1.8.1" + preserve "^0.2.0" + repeat-element "^1.1.2" + browser-stdout@1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.0.tgz#f351d32969d32fa5d7a5567154263d928ae3bd1f" @@ -527,6 +649,10 @@ caniuse-lite@^1.0.30000792: version "1.0.30000792" resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000792.tgz#d0cea981f8118f3961471afbb43c9a1e5bbf0332" +caseless@~0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" + chai@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/chai/-/chai-4.1.2.tgz#0f64584ba642f0f2ace2806279f4f06ca23ad73c" @@ -552,6 +678,21 @@ check-error@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82" +chokidar@^1.6.1: + version "1.7.0" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-1.7.0.tgz#798e689778151c8076b4b360e5edd28cda2bb468" + dependencies: + anymatch "^1.3.0" + async-each "^1.0.0" + glob-parent "^2.0.0" + inherits "^2.0.1" + is-binary-path "^1.0.0" + is-glob "^2.0.0" + path-is-absolute "^1.0.0" + readdirp "^2.0.0" + optionalDependencies: + fsevents "^1.0.0" + clone-buffer@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/clone-buffer/-/clone-buffer-1.0.0.tgz#e3e25b207ac4e701af721e2cb5a16792cac3dc58" @@ -576,14 +717,32 @@ cloneable-readable@^1.0.0: process-nextick-args "^1.0.6" through2 "^2.0.1" -colors@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/colors/-/colors-1.1.2.tgz#168a4701756b6a7f51a12ce0c97bfa28c084ed63" +co@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" + +code-point-at@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" + +colors@~1.2.0-rc0: + version "1.2.0-rc0" + resolved "https://registry.yarnpkg.com/colors/-/colors-1.2.0-rc0.tgz#55fa8c0c4454606756cd96ebcf520a9fcf743d11" + +combined-stream@^1.0.5, combined-stream@~1.0.5: + version "1.0.6" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.6.tgz#723e7df6e801ac5613113a7e445a9b69cb632818" + dependencies: + delayed-stream "~1.0.0" commander@2.11.0: version "2.11.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.11.0.tgz#157152fd1e7a6c8d98a5b715cf376df928004563" +commander@^2.11.0: + version "2.14.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.14.1.tgz#2235123e37af8ca3c65df45b026dbd357b01b9aa" + commander@~2.9.0: version "2.9.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.9.0.tgz#9c99094176e12240cb22d6c5146098400fe0f7d4" @@ -602,6 +761,10 @@ concat-stream@~1.6.0: readable-stream "^2.2.2" typedarray "^0.0.6" +console-control-strings@^1.0.0, console-control-strings@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" + convert-source-map@^1.5.0: version "1.5.1" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.5.1.tgz#b8278097b9bc229365de5c62cf5fcaed8b5599e5" @@ -610,17 +773,29 @@ core-js@^2.4.0, core-js@^2.5.0: version "2.5.3" resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.3.tgz#8acc38345824f16d8365b7c9b4259168e8ed603e" -core-util-is@~1.0.0: +core-util-is@1.0.2, core-util-is@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" +cryptiles@2.x.x: + version "2.0.5" + resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-2.0.5.tgz#3bdfecdc608147c1c67202fa291e7dca59eaa3b8" + dependencies: + boom "2.x.x" + +dashdash@^1.12.0: + version "1.14.1" + resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" + dependencies: + assert-plus "^1.0.0" + debug@3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" dependencies: ms "2.0.0" -debug@^2.6.8: +debug@^2.2.0, debug@^2.6.8: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" dependencies: @@ -632,6 +807,10 @@ deep-eql@^3.0.0: dependencies: type-detect "^4.0.0" +deep-extend@~0.4.0: + version "0.4.2" + resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.4.2.tgz#48b699c27e334bf89f10892be432f6e4c7d34a7f" + define-properties@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.2.tgz#83a73f2fea569898fb737193c8f873caf6d45c94" @@ -639,12 +818,24 @@ define-properties@^1.1.2: foreach "^2.0.5" object-keys "^1.0.8" +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + +delegates@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" + detect-indent@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-4.0.0.tgz#f76d064352cdf43a1cb6ce619c4ee3a9475de208" dependencies: repeating "^2.0.0" +detect-libc@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" + diff@3.3.1: version "3.3.1" resolved "https://registry.yarnpkg.com/diff/-/diff-3.3.1.tgz#aa8567a6eed03c531fc89d3f711cd0e5259dec75" @@ -658,6 +849,12 @@ duplexify@^3.5.3: readable-stream "^2.0.0" stream-shift "^1.0.0" +ecc-jsbn@~0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz#0fc73a9ed5f0d53c38193398523ef7e543777505" + dependencies: + jsbn "~0.1.0" + electron-to-chromium@^1.3.30: version "1.3.31" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.31.tgz#00d832cba9fe2358652b0c48a8816c8e3a037e9f" @@ -680,10 +877,50 @@ esutils@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b" -extend@^3.0.0: +expand-brackets@^0.1.4: + version "0.1.5" + resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-0.1.5.tgz#df07284e342a807cd733ac5af72411e581d1177b" + dependencies: + is-posix-bracket "^0.1.0" + +expand-range@^1.8.1: + version "1.8.2" + resolved "https://registry.yarnpkg.com/expand-range/-/expand-range-1.8.2.tgz#a299effd335fe2721ebae8e257ec79644fc85337" + dependencies: + fill-range "^2.1.0" + +extend@^3.0.0, extend@~3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.1.tgz#a755ea7bc1adfcc5a31ce7e762dbaadc5e636444" +extglob@^0.3.1: + version "0.3.2" + resolved "https://registry.yarnpkg.com/extglob/-/extglob-0.3.2.tgz#2e18ff3d2f49ab2765cec9023f011daa8d8349a1" + dependencies: + is-extglob "^1.0.0" + +extsprintf@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" + +extsprintf@^1.2.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" + +filename-regex@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/filename-regex/-/filename-regex-2.0.1.tgz#c1c4b9bee3e09725ddb106b75c1e301fe2f18b26" + +fill-range@^2.1.0: + version "2.2.3" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-2.2.3.tgz#50b77dfd7e469bc7492470963699fe7a8485a723" + dependencies: + is-number "^2.1.0" + isobject "^2.0.0" + randomatic "^1.1.3" + repeat-element "^1.1.2" + repeat-string "^1.5.2" + flush-write-stream@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.0.2.tgz#c81b90d8746766f1a609a46809946c45dd8ae417" @@ -691,10 +928,32 @@ flush-write-stream@^1.0.2: inherits "^2.0.1" readable-stream "^2.0.4" +for-in@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" + +for-own@^0.1.4: + version "0.1.5" + resolved "https://registry.yarnpkg.com/for-own/-/for-own-0.1.5.tgz#5265c681a4f294dabbf17c9509b6763aa84510ce" + dependencies: + for-in "^1.0.1" + foreach@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99" +forever-agent@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" + +form-data@~2.1.1: + version "2.1.4" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.1.4.tgz#33c183acf193276ecaa98143a69e94bfee1750d1" + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.5" + mime-types "^2.1.12" + fs-mkdirp-stream@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs-mkdirp-stream/-/fs-mkdirp-stream-1.0.0.tgz#0b7815fc3201c6a69e14db98ce098c16935259eb" @@ -702,18 +961,78 @@ fs-mkdirp-stream@^1.0.0: graceful-fs "^4.1.11" through2 "^2.0.3" +fs-readdir-recursive@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz#e32fc030a2ccee44a6b5371308da54be0b397d27" + fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" +fsevents@^1.0.0: + version "1.1.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.1.3.tgz#11f82318f5fe7bb2cd22965a108e9306208216d8" + dependencies: + nan "^2.3.0" + node-pre-gyp "^0.6.39" + +fstream-ignore@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/fstream-ignore/-/fstream-ignore-1.0.5.tgz#9c31dae34767018fe1d249b24dada67d092da105" + dependencies: + fstream "^1.0.0" + inherits "2" + minimatch "^3.0.0" + +fstream@^1.0.0, fstream@^1.0.10, fstream@^1.0.2: + version "1.0.11" + resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.11.tgz#5c1fb1f117477114f0632a0eb4b71b3cb0fd3171" + dependencies: + graceful-fs "^4.1.2" + inherits "~2.0.0" + mkdirp ">=0.5 0" + rimraf "2" + function-bind@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" +gauge@~2.7.3: + version "2.7.4" + resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" + dependencies: + aproba "^1.0.3" + console-control-strings "^1.0.0" + has-unicode "^2.0.0" + object-assign "^4.1.0" + signal-exit "^3.0.0" + string-width "^1.0.1" + strip-ansi "^3.0.1" + wide-align "^1.1.0" + get-func-name@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.0.tgz#ead774abee72e20409433a066366023dd6887a41" +getpass@^0.1.1: + version "0.1.7" + resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" + dependencies: + assert-plus "^1.0.0" + +glob-base@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/glob-base/-/glob-base-0.3.0.tgz#dbb164f6221b1c0b1ccf82aea328b497df0ea3c4" + dependencies: + glob-parent "^2.0.0" + is-glob "^2.0.0" + +glob-parent@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-2.0.0.tgz#81383d72db054fcccf5336daa902f182f6edbb28" + dependencies: + is-glob "^2.0.0" + glob-parent@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae" @@ -736,7 +1055,7 @@ glob-stream@^6.1.0: to-absolute-glob "^2.0.0" unique-stream "^2.0.2" -glob@7.1.2, glob@^7.1.1: +glob@7.1.2, glob@^7.0.5, glob@^7.1.1, glob@^7.1.2: version "7.1.2" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" dependencies: @@ -751,7 +1070,7 @@ globals@^9.18.0: version "9.18.0" resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a" -graceful-fs@^4.0.0, graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6: +graceful-fs@^4.0.0, graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.4, graceful-fs@^4.1.6: version "4.1.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" @@ -763,6 +1082,23 @@ growl@1.10.3: version "1.10.3" resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.3.tgz#1926ba90cf3edfe2adb4927f5880bc22c66c790f" +gulp-sort@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/gulp-sort/-/gulp-sort-2.0.0.tgz#c6762a2f1f0de0a3fc595a21599d3fac8dba1aca" + dependencies: + through2 "^2.0.1" + +har-schema@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-1.0.5.tgz#d263135f43307c02c602afc8fe95970c0151369e" + +har-validator@~4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-4.2.1.tgz#33481d0f1bbff600dd203d75812a6a5fba002e2a" + dependencies: + ajv "^4.9.1" + har-schema "^1.0.5" + has-ansi@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" @@ -777,10 +1113,27 @@ has-symbols@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.0.tgz#ba1a8f1af2a0fc39650f5c850367704122063b44" +has-unicode@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" + +hawk@3.1.3, hawk@~3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/hawk/-/hawk-3.1.3.tgz#078444bd7c1640b0fe540d2c9b73d59678e8e1c4" + dependencies: + boom "2.x.x" + cryptiles "2.x.x" + hoek "2.x.x" + sntp "1.x.x" + he@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd" +hoek@2.x.x: + version "2.16.3" + resolved "https://registry.yarnpkg.com/hoek/-/hoek-2.16.3.tgz#20bb7403d3cea398e91dc4710a8ff1b8274a25ed" + home-or-tmp@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/home-or-tmp/-/home-or-tmp-2.0.0.tgz#e36c3f2d2cae7d746a857e38d18d5f32a7882db8" @@ -788,6 +1141,14 @@ home-or-tmp@^2.0.0: os-homedir "^1.0.0" os-tmpdir "^1.0.1" +http-signature@~1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.1.1.tgz#df72e267066cd0ac67fb76adf8e134a8fbcf91bf" + dependencies: + assert-plus "^0.2.0" + jsprim "^1.2.2" + sshpk "^1.7.0" + inflight@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" @@ -795,10 +1156,14 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.3: +inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.0, inherits@~2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" +ini@~1.3.0: + version "1.3.5" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" + invariant@^2.2.2: version "2.2.2" resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.2.tgz#9e1f56ac0acdb6bf303306f338be3b204ae60360" @@ -812,10 +1177,34 @@ is-absolute@^1.0.0: is-relative "^1.0.0" is-windows "^1.0.1" +is-binary-path@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-1.0.1.tgz#75f16642b480f187a711c814161fd3a4a7655898" + dependencies: + binary-extensions "^1.0.0" + is-buffer@^1.1.5: version "1.1.6" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" +is-dotfile@^1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/is-dotfile/-/is-dotfile-1.0.3.tgz#a6a2f32ffd2dfb04f5ca25ecd0f6b83cf798a1e1" + +is-equal-shallow@^0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz#2238098fc221de0bcfa5d9eac4c45d638aa1c534" + dependencies: + is-primitive "^2.0.0" + +is-extendable@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" + +is-extglob@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-1.0.0.tgz#ac468177c4943405a092fc8f29760c6ffc6206c0" + is-extglob@^2.1.0: version "2.1.1" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" @@ -826,6 +1215,18 @@ is-finite@^1.0.0: dependencies: number-is-nan "^1.0.0" +is-fullwidth-code-point@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" + dependencies: + number-is-nan "^1.0.0" + +is-glob@^2.0.0, is-glob@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-2.0.1.tgz#d096f926a3ded5600f3fdfd91198cb0888c2d863" + dependencies: + is-extglob "^1.0.0" + is-glob@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-3.1.0.tgz#7ba5ae24217804ac70707b96922567486cc3e84a" @@ -836,6 +1237,26 @@ is-negated-glob@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-negated-glob/-/is-negated-glob-1.0.0.tgz#6910bca5da8c95e784b5751b976cf5a10fee36d2" +is-number@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-2.1.0.tgz#01fcbbb393463a548f2f466cce16dece49db908f" + dependencies: + kind-of "^3.0.2" + +is-number@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" + dependencies: + kind-of "^3.0.2" + +is-posix-bracket@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz#3334dc79774368e92f016e6fbc0a88f5cd6e6bc4" + +is-primitive@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-primitive/-/is-primitive-2.0.0.tgz#207bab91638499c07b2adf240a41a87210034575" + is-relative@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-relative/-/is-relative-1.0.0.tgz#a1bb6935ce8c5dba1e8b9754b9b2dcc020e2260d" @@ -846,6 +1267,10 @@ is-stream@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" +is-typedarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" + is-unc-path@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-unc-path/-/is-unc-path-1.0.0.tgz#d731e8898ed090a12c352ad2eaed5095ad322c9d" @@ -864,14 +1289,28 @@ is-windows@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.1.tgz#310db70f742d259a16a369202b51af84233310d9" -isarray@~1.0.0: +isarray@1.0.0, isarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" +isobject@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" + dependencies: + isarray "1.0.0" + +isstream@~0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" + js-tokens@^3.0.0, js-tokens@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" +jsbn@~0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" + jsesc@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-1.3.0.tgz#46c3fec8c1892b12b0833db9bc7622176dbab34b" @@ -880,12 +1319,20 @@ jsesc@~0.5.0: version "0.5.0" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" -json-stable-stringify@^1.0.0: +json-schema@0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" + +json-stable-stringify@^1.0.0, json-stable-stringify@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz#9a759d39c5f2ff503fd5300646ed445f88c4f9af" dependencies: jsonify "~0.0.0" +json-stringify-safe@~5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" + json5@^0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821" @@ -894,6 +1341,27 @@ jsonify@~0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" +jsprim@^1.2.2: + version "1.4.1" + resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" + dependencies: + assert-plus "1.0.0" + extsprintf "1.3.0" + json-schema "0.2.3" + verror "1.10.0" + +kind-of@^3.0.2: + version "3.2.2" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" + dependencies: + is-buffer "^1.1.5" + +kind-of@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-4.0.0.tgz#20813df3d712928b207378691a45066fae72dd57" + dependencies: + is-buffer "^1.1.5" + lazystream@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/lazystream/-/lazystream-1.0.0.tgz#f6995fe0f820392f61396be89462407bb77168e4" @@ -916,7 +1384,35 @@ loose-envify@^1.0.0: dependencies: js-tokens "^3.0.0" -minimatch@^3.0.2, minimatch@^3.0.4: +micromatch@^2.1.5: + version "2.3.11" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-2.3.11.tgz#86677c97d1720b363431d04d0d15293bd38c1565" + dependencies: + arr-diff "^2.0.0" + array-unique "^0.2.1" + braces "^1.8.2" + expand-brackets "^0.1.4" + extglob "^0.3.1" + filename-regex "^2.0.0" + is-extglob "^1.0.0" + is-glob "^2.0.1" + kind-of "^3.0.2" + normalize-path "^2.0.1" + object.omit "^2.0.0" + parse-glob "^3.0.4" + regex-cache "^0.4.2" + +mime-db@~1.33.0: + version "1.33.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.33.0.tgz#a3492050a5cb9b63450541e39d9788d2272783db" + +mime-types@^2.1.12, mime-types@~2.1.7: + version "2.1.18" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.18.tgz#6f323f60a83d11146f831ff11fd66e2fe5503bb8" + dependencies: + mime-db "~1.33.0" + +minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" dependencies: @@ -926,7 +1422,11 @@ minimist@0.0.8: version "0.0.8" resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" -mkdirp@0.5.1, mkdirp@^0.5.1, mkdirp@~0.5.1: +minimist@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" + +mkdirp@0.5.1, "mkdirp@>=0.5 0", mkdirp@^0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" dependencies: @@ -951,7 +1451,34 @@ ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" -normalize-path@^2.1.1: +nan@^2.3.0: + version "2.8.0" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.8.0.tgz#ed715f3fe9de02b57a5e6252d90a96675e1f085a" + +node-pre-gyp@^0.6.39: + version "0.6.39" + resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.6.39.tgz#c00e96860b23c0e1420ac7befc5044e1d78d8649" + dependencies: + detect-libc "^1.0.2" + hawk "3.1.3" + mkdirp "^0.5.1" + nopt "^4.0.1" + npmlog "^4.0.2" + rc "^1.1.7" + request "2.81.0" + rimraf "^2.6.1" + semver "^5.3.0" + tar "^2.2.1" + tar-pack "^3.4.0" + +nopt@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.1.tgz#d0d4685afd5415193c8c7505602d0d17cd64474d" + dependencies: + abbrev "1" + osenv "^0.1.4" + +normalize-path@^2.0.0, normalize-path@^2.0.1, normalize-path@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" dependencies: @@ -963,10 +1490,27 @@ now-and-later@^2.0.0: dependencies: once "^1.3.2" +npmlog@^4.0.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" + dependencies: + are-we-there-yet "~1.1.2" + console-control-strings "~1.1.0" + gauge "~2.7.3" + set-blocking "~2.0.0" + number-is-nan@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" +oauth-sign@~0.8.1: + version "0.8.2" + resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43" + +object-assign@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + object-keys@^1.0.11, object-keys@^1.0.8: version "1.0.11" resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.0.11.tgz#c54601778ad560f1142ce0e01bcca8b56d13426d" @@ -980,7 +1524,14 @@ object.assign@^4.0.4: has-symbols "^1.0.0" object-keys "^1.0.11" -once@^1.3.0, once@^1.3.1, once@^1.3.2, once@^1.4.0: +object.omit@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/object.omit/-/object.omit-2.0.1.tgz#1a9c744829f39dbb858c76ca3579ae2a54ebd1fa" + dependencies: + for-own "^0.1.4" + is-extendable "^0.1.1" + +once@^1.3.0, once@^1.3.1, once@^1.3.2, once@^1.3.3, once@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" dependencies: @@ -996,10 +1547,34 @@ os-homedir@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" -os-tmpdir@^1.0.1: +os-tmpdir@^1.0.0, os-tmpdir@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" +osenv@^0.1.4: + version "0.1.5" + resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.5.tgz#85cdfafaeb28e8677f416e287592b5f3f49ea410" + dependencies: + os-homedir "^1.0.0" + os-tmpdir "^1.0.0" + +output-file-sync@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/output-file-sync/-/output-file-sync-1.1.2.tgz#d0a33eefe61a205facb90092e826598d5245ce76" + dependencies: + graceful-fs "^4.1.4" + mkdirp "^0.5.1" + object-assign "^4.1.0" + +parse-glob@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/parse-glob/-/parse-glob-3.0.4.tgz#b2c376cfb11f35513badd173ef0bb6e3a388391c" + dependencies: + glob-base "^0.3.0" + is-dotfile "^1.0.0" + is-extglob "^1.0.0" + is-glob "^2.0.0" + path-dirname@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/path-dirname/-/path-dirname-1.0.2.tgz#cc33d24d525e099a5388c0336c6e32b9160609e0" @@ -1012,6 +1587,14 @@ pathval@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.0.tgz#b942e6d4bde653005ef6b71361def8727d0645e0" +performance-now@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-0.2.0.tgz#33ef30c5c77d4ea21c5a53869d91b56d8f2555e5" + +preserve@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b" + private@^0.1.6, private@^0.1.7: version "0.1.8" resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff" @@ -1020,6 +1603,10 @@ process-nextick-args@^1.0.6, process-nextick-args@~1.0.6: version "1.0.7" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3" +process-nextick-args@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa" + pump@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/pump/-/pump-2.0.1.tgz#12399add6e4cf7526d973cbc8b5ce2e2908b3909" @@ -1035,7 +1622,31 @@ pumpify@^1.3.5: inherits "^2.0.3" pump "^2.0.0" -readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.4, readable-stream@^2.0.5, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3: +punycode@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" + +qs@~6.4.0: + version "6.4.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233" + +randomatic@^1.1.3: + version "1.1.7" + resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-1.1.7.tgz#c7abe9cc8b87c0baa876b19fde83fd464797e38c" + dependencies: + is-number "^3.0.0" + kind-of "^4.0.0" + +rc@^1.1.7: + version "1.2.5" + resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.5.tgz#275cd687f6e3b36cc756baa26dfee80a790301fd" + dependencies: + deep-extend "~0.4.0" + ini "~1.3.0" + minimist "^1.2.0" + strip-json-comments "~2.0.1" + +readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.4, readable-stream@^2.0.5, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3: version "2.3.3" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.3.tgz#368f2512d79f9d46fdfc71349ae7878bbc1eb95c" dependencies: @@ -1047,7 +1658,19 @@ readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable string_decoder "~1.0.3" util-deprecate "~1.0.1" -readdirp@~2.1.0: +readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.1.4: + version "2.3.4" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.4.tgz#c946c3f47fa7d8eabc0b6150f4a12f69a4574071" + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~2.0.0" + safe-buffer "~5.1.1" + string_decoder "~1.0.3" + util-deprecate "~1.0.1" + +readdirp@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.1.0.tgz#4ed0ad060df3073300c48440373f72d1cc642d78" dependencies: @@ -1076,6 +1699,12 @@ regenerator-transform@^0.10.0: babel-types "^6.19.0" private "^0.1.6" +regex-cache@^0.4.2: + version "0.4.4" + resolved "https://registry.yarnpkg.com/regex-cache/-/regex-cache-0.4.4.tgz#75bdc58a2a1496cec48a12835bc54c8d562336dd" + dependencies: + is-equal-shallow "^0.1.3" + regexpu-core@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-2.0.0.tgz#49d038837b8dcf8bfa5b9a42139938e6ea2ae240" @@ -1113,6 +1742,14 @@ remove-trailing-separator@^1.0.1: version "1.1.0" resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" +repeat-element@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.2.tgz#ef089a178d1483baae4d93eb98b4f9e4e11d990a" + +repeat-string@^1.5.2: + version "1.6.1" + resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" + repeating@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/repeating/-/repeating-2.0.1.tgz#5214c53a926d3552707527fbab415dbc08d06dda" @@ -1123,13 +1760,46 @@ replace-ext@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-1.0.0.tgz#de63128373fcbf7c3ccfa4de5a480c45a67958eb" +request@2.81.0: + version "2.81.0" + resolved "https://registry.yarnpkg.com/request/-/request-2.81.0.tgz#c6928946a0e06c5f8d6f8a9333469ffda46298a0" + dependencies: + aws-sign2 "~0.6.0" + aws4 "^1.2.1" + caseless "~0.12.0" + combined-stream "~1.0.5" + extend "~3.0.0" + forever-agent "~0.6.1" + form-data "~2.1.1" + har-validator "~4.2.1" + hawk "~3.1.3" + http-signature "~1.1.0" + is-typedarray "~1.0.0" + isstream "~0.1.2" + json-stringify-safe "~5.0.1" + mime-types "~2.1.7" + oauth-sign "~0.8.1" + performance-now "^0.2.0" + qs "~6.4.0" + safe-buffer "^5.0.1" + stringstream "~0.0.4" + tough-cookie "~2.3.0" + tunnel-agent "^0.6.0" + uuid "^3.0.0" + resolve-options@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/resolve-options/-/resolve-options-1.1.0.tgz#32bb9e39c06d67338dc9378c0d6d6074566ad131" dependencies: value-or-function "^3.0.0" -safe-buffer@^5.1.0, safe-buffer@~5.1.0, safe-buffer@~5.1.1: +rimraf@2, rimraf@^2.5.1, rimraf@^2.6.1: + version "2.6.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.2.tgz#2ed8150d24a16ea8651e6d6ef0f47c4158ce7a36" + dependencies: + glob "^7.0.5" + +safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" @@ -1137,14 +1807,28 @@ semver@^5.3.0: version "5.5.0" resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab" +set-blocking@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" + set-immediate-shim@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz#4b2b1b27eb808a9f8dcc481a58e5e56f599f3f61" +signal-exit@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" + slash@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55" +sntp@1.x.x: + version "1.0.9" + resolved "https://registry.yarnpkg.com/sntp/-/sntp-1.0.9.tgz#6541184cc90aeea6c6e7b35e2659082443c66198" + dependencies: + hoek "2.x.x" + source-map-support@^0.4.15: version "0.4.18" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.18.tgz#0286a6de8be42641338594e97ccea75f0a2c585f" @@ -1155,22 +1839,52 @@ source-map@^0.5.6: version "0.5.7" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" +sshpk@^1.7.0: + version "1.13.1" + resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.13.1.tgz#512df6da6287144316dc4c18fe1cf1d940739be3" + dependencies: + asn1 "~0.2.3" + assert-plus "^1.0.0" + dashdash "^1.12.0" + getpass "^0.1.1" + optionalDependencies: + bcrypt-pbkdf "^1.0.0" + ecc-jsbn "~0.1.1" + jsbn "~0.1.0" + tweetnacl "~0.14.0" + stream-shift@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.0.tgz#d5c752825e5367e786f78e18e445ea223a155952" +string-width@^1.0.1, string-width@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" + dependencies: + code-point-at "^1.0.0" + is-fullwidth-code-point "^1.0.0" + strip-ansi "^3.0.0" + string_decoder@~1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.0.3.tgz#0fc67d7c141825de94282dd536bec6b9bce860ab" dependencies: safe-buffer "~5.1.0" -strip-ansi@^3.0.0: +stringstream@~0.0.4: + version "0.0.5" + resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.5.tgz#4e484cd4de5a0bbbee18e46307710a8a81621878" + +strip-ansi@^3.0.0, strip-ansi@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" dependencies: ansi-regex "^2.0.0" +strip-json-comments@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" + supports-color@4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-4.4.0.tgz#883f7ddabc165142b2a61427f3352ded195d1a3e" @@ -1181,6 +1895,27 @@ supports-color@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" +tar-pack@^3.4.0: + version "3.4.1" + resolved "https://registry.yarnpkg.com/tar-pack/-/tar-pack-3.4.1.tgz#e1dbc03a9b9d3ba07e896ad027317eb679a10a1f" + dependencies: + debug "^2.2.0" + fstream "^1.0.10" + fstream-ignore "^1.0.5" + once "^1.3.3" + readable-stream "^2.1.4" + rimraf "^2.5.1" + tar "^2.2.1" + uid-number "^0.0.6" + +tar@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/tar/-/tar-2.2.1.tgz#8e4d2a256c0e2185c6b18ad694aec968b83cb1d1" + dependencies: + block-stream "*" + fstream "^1.0.2" + inherits "2" + through2-filter@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/through2-filter/-/through2-filter-2.0.0.tgz#60bc55a0dacb76085db1f9dae99ab43f83d622ec" @@ -1212,10 +1947,26 @@ to-through@^2.0.0: dependencies: through2 "^2.0.3" +tough-cookie@~2.3.0: + version "2.3.3" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.3.tgz#0b618a5565b6dea90bf3425d04d55edc475a7561" + dependencies: + punycode "^1.4.1" + trim-right@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003" +tunnel-agent@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" + dependencies: + safe-buffer "^5.0.1" + +tweetnacl@^0.14.3, tweetnacl@~0.14.0: + version "0.14.5" + resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" + type-detect@^4.0.0: version "4.0.7" resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.7.tgz#862bd2cf6058ad92799ff5a5b8cf7b6cec726198" @@ -1224,6 +1975,10 @@ typedarray@^0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" +uid-number@^0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/uid-number/-/uid-number-0.0.6.tgz#0ea10e8035e8eb5b8e4449f06da1c730663baa81" + unc-path-regex@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/unc-path-regex/-/unc-path-regex-0.1.2.tgz#e73dd3d7b0d7c5ed86fbac6b0ae7d8c6a69d50fa" @@ -1235,14 +1990,36 @@ unique-stream@^2.0.2: json-stable-stringify "^1.0.0" through2-filter "^2.0.0" +user-home@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/user-home/-/user-home-1.1.1.tgz#2b5be23a32b63a7c9deb8d0f28d485724a3df190" + util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" +uuid@^3.0.0: + version "3.2.1" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.2.1.tgz#12c528bb9d58d0b9265d9a2f6f0fe8be17ff1f14" + +v8flags@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/v8flags/-/v8flags-2.1.1.tgz#aab1a1fa30d45f88dd321148875ac02c0b55e5b4" + dependencies: + user-home "^1.1.1" + value-or-function@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/value-or-function/-/value-or-function-3.0.0.tgz#1c243a50b595c1be54a754bfece8563b9ff8d813" +verror@1.10.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" + dependencies: + assert-plus "^1.0.0" + core-util-is "1.0.2" + extsprintf "^1.2.0" + vinyl-fs@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/vinyl-fs/-/vinyl-fs-3.0.2.tgz#1b86258844383f57581fcaac081fe09ef6d6d752" @@ -1300,6 +2077,12 @@ vinyl@~2.0.1: remove-trailing-separator "^1.0.1" replace-ext "^1.0.0" +wide-align@^1.1.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.2.tgz#571e0f1b0604636ebc0dfc21b0339bbe31341710" + dependencies: + string-width "^1.0.2" + wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" From 6a15131a74b6104e9bbc516305c5ad8e3d2c9e59 Mon Sep 17 00:00:00 2001 From: Karel Ledru-Mathe Date: Fri, 26 Jan 2018 00:39:39 -0500 Subject: [PATCH 5/8] Update documentation --- CHANGELOG.md | 4 + README.md | 357 +++++++++++++------------------------------- docs/development.md | 56 +++++++ docs/examples.md | 160 ++++++++++++++++++++ docs/migration.md | 39 +++++ package.json | 2 +- 6 files changed, 364 insertions(+), 254 deletions(-) create mode 100644 docs/development.md create mode 100644 docs/examples.md create mode 100644 docs/migration.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f08fe15..f3b6e591 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 1.0.0-beta1 + + + ## 0.13.0 - latest - Support `defaultValue` option along the translation key (#68) diff --git a/README.md b/README.md index c3f9728b..b7b8fbed 100644 --- a/README.md +++ b/README.md @@ -2,322 +2,173 @@ [![NPM](https://nodei.co/npm/i18next-parser.png?downloads=true&stars=true)](https://www.npmjs.com/package/i18next-parser) -A simple command line and gulp plugin that lets you parse your code and extract the translations keys in it. +When translating an application, maintaining the catalog by hand is painful. This package automate the process. Don't let the name fool you, it was originally built with i18next in mind but it works well with other i18n libraries. + -The idea is to parse code files to retrieve the translation keys and create a catalog. You can use the command line or run in the background with Gulp while coding. It removes the pain of maintaining your translation catalog. ## Features -- Parses a single file or a directory (recursively or not) -- Parses template files (support for jade, handlebars and data-i18n attribute in html) -- Creates one json file per locale and per namespace. -- Remove old keys your code doesn't use anymore and place them in a `namespace_old.json` file. It is useful to avoid losing translations you may want to reuse. -- Restore keys from the `_old` file if the one in the translation file is empty. -- Support most i18next features: - - Handles context keys of the form `key_context` - - Handles plural keys of the form `key_plural` and `key_plural_0` - - Handles multiline array in catalog -- Is a stream transform (so it works with gulp) +- Choose your weapon: A CLI, a standalone parser or a stream transform +- Three built in lexers: Javascript, HTML and Handlebars +- Creates one catalog file per locale and per namespace +- Backs up the old keys your code doesn't use anymore in `namespace_old.json` catalog. +- Restores keys from the `_old` file if the one in the translation file is empty. +- Supports i18next features: + - **Context**: keys of the form `key_context` + - **Plural**: keys of the form `key_plural` and `key_plural_0` +- Behind the hood, it's a stream transform (so it works with gulp) - Supports es6 template strings (in addition to single/double quoted strings) with ${expression} placeholders +## `1.x` ---- +`1.x` is currently in beta. It is a deep rewrite of this package that solves many issues, the main one being that it was slowly becoming unmaintainable. The [migration](docs/migration.md) contains all the breaking changes. If you rely on a `0.x.x` version, you can still find the old documentation on its dedicated [branch](https://github.com/i18next/i18next-parser/tree/0.x.x). -## Installation -``` -npm install i18next-parser -g -``` +## Usage -## Tests +### CLI + +You can use the CLI with the package installed locally but if you want to use it from anywhere, you better install it globally: ``` -mocha --require babel-register --require babel-polyfill test/**/*.js +yarn global add i18next-parser +npm install -g i18next-parser +i18next 'app/**/*.{js,hbs}' 'lib/**/*.{js,hbs}' [-oc] ``` ---- - -## Contribute - -Any contribution is welcome. Just follow those quick guidelines: - -1. Fork and clone the project -2. Create a branch `git checkout -b feature/my-feature` (use feature/ or hotfix/ branch prefixes accordingly) -3. Push to your fork -4. Write tests and documentation (I won't merge a PR without it) -5. Make a pull request from your repository `feature/my-feature` branch to this repository `master`. Do not create both an issue ticket and a Pull Request. -6. Wait, I am usually pretty fast to merge PRs :) - -Thanks a lot to all the previous [contributors](https://github.com/i18next/i18next-parser/graphs/contributors). - ---- +Multiple globbing patterns are supported to specify complex file selections. You can learn how to write globs [here](https://github.com/isaacs/node-glob). Note that glob must be wrapped with single quotes when passed as arguments. -## Lexers +- **-o, --output **: Where to write the locale files. +- **-c, --config **: The config file with all the options +- **-S, --silent**: The config file with all the options -- `HTMLLexer`: parse `data-i18n` and `data-i18n-options` in html. Useful mostly for users of `jquery-i18next`. +### Gulp ---- +Save the package to your devDependencies: -## CLI Usage - -`i18next /path/to/file/or/dir [-orapfnl]` - -- **-o, --output **: Where to write the locale files. -- **-r, --recursive**: Is --output is a directory, parses files in sub directories. -- **-f, --functions **: Function names to parse. Defaults to `t` -- **-a, --attributes **: HTML attributes to parse. Defaults to `data-i18n` -- **-p, --parser **: A custom regex for the parser to use. -- **-n, --namespace **: Default namespace used in your i18next config. Defaults to `translation` -- **-s, --namespace-separator **: Namespace separator used in your translation keys. Defaults to `:` -- **-k, --key-separator **: Key separator used in your translation keys. Defaults to `.` -- **-c, --context-separator **: Context separator used in your translation keys. Defaults to `_` -- **-l, --locales **: The locales in your applications. Defaults to `en,fr` -- **--directoryFilter**: Globs of folders to filter -- **--fileFilter**: Globs of files to filter -- **--keep-removed**: Prevent keys no longer found from being removed -- **--write-old false**: Avoid saving the \_old files -- **--ignore-variables**: Don't fail when a variable is found -- **--prefix **: Prefix filename for each locale, eg.: 'pre-$LOCALE-' will yield 'pre-en-default.json' -- **--suffix **: Suffix filename for each locale, eg.: '-$suf-LOCALE' will yield 'default-suf-en.json' -- **--extension **: Specify extension for filename for each locale, eg.: '.$LOCALE.i18n' will yield 'default.en.i18n' - ---- - -## Gulp Usage +``` +yarn add -D i18next-parser +npm install --save-dev i18next-parser +``` [Gulp](http://gulpjs.com/) defines itself as the streaming build system. Put simply, it is like Grunt, but performant and elegant. ```javascript -var i18next = require('i18next-parser'); +const i18next = require('i18next-parser'); gulp.task('i18next', function() { - gulp.src('app/**') - .pipe(i18next({ - locales: ['en', 'de'], - functions: ['__', '_e'], - output: '../locales' - })) - .pipe(gulp.dest('locales')); + gulp.src('app/**') + .pipe(i18next({ + locales: ['en', 'de'], + output: '../locales' + })) + .pipe(gulp.dest('locales')); }); ``` -- **output**: Where to write the locale files relative to the base (here `app/`). Defaults to `locales` -- **functions**: An array of functions names to parse. Defaults to `['t']` -- **attributes**: An array of html attributes to parse. Defaults to `['data-i18n']` -- **namespace**: Default namespace used in your i18next config. Defaults to `translation` -- **namespaceSeparator**: Namespace separator used in your translation keys. Defaults to `:` -- **keySeparator**: Key separator used in your translation keys. Defaults to `.` -- **locales**: An array of the locales in your applications. Defaults to `['en','fr']` -- **parser**: A custom regex for the parser to use. -- **writeOld**: Save the \_old files. Defaults to `true` -- **prefix**: Add a custom prefix in front of the file name. -- **suffix**: Add a custom suffix at the end of the file name. -- **extension**: Edit the extension of the files. Defaults to `.json` -- **keepRemoved**: Prevent keys no longer found from being removed -- **ignoreVariables**: Don't fail when a variable is found - -You can inject the locale tag in either the prefix, suffix or extension using the `$LOCALE` variable. - -### Note on paths (why your translations are not saved) - -The way gulp works, it take a `src()`, applies some transformations to the files matched and then render the transformation using the `dest()` command to a path of your choice. With `i18next-parser`, the `src()` takes the path to the files to parse and the `dest()` takes the path where you want the catalogs of translation keys. - -The problem is that the `i18next()` transform doesn't know about the path you specify in `dest()`. So it doesn't know where the catalogs are. So it can't merge the result of the parsing with the existing catalogs you may have there. - -``` -gulp.src('app/**') - .pipe(i18next()) - .pipe(gulp.dest('custom/path')); -``` - -If you consider the code above, any file that match the `app/**` pattern will have of base set to `app/`. *As per the vinyl-fs [documentation](https://github.com/wearefractal/vinyl-fs#srcglobs-opt) (which powers gulp), the base is the folder relative to the cwd and defaults is where the glob begins.* - -Bare with me, the `output` option isn't defined, it defaults to `locales`. So the `i18next()` transform will look for files in the `app/locales` directory (the base plus the output directory). But in reality they are located in `custom/path`. So for the `i18next-parser` to find your catalogs, you need the `output` option: - -``` -gulp.src('app/**') - .pipe(i18next({output: '../custom/path'})) - .pipe(gulp.dest('custom/path')); -``` - -The `output` option is relative to the base. In our case, we have `app/` as a base and we want `custom/path`. So the `output` option must be `../custom/path`. - - -### Events - -The transform emit a `reading` event for each file it parses: +**IMPORTANT**: `output` is required to know where to read the catalog from. You might think that `gulp.dest()` is enough though it does not inform the transform where to read the existing catalog from. -`.pipe( i18next().on('reading', function(path) { }) )` -The transform emit a `writing` event for each file it passes to the stream: +## Options -`.pipe( i18next().on('reading', function(path) { }) )` +Option | Description | Default +---------------------- | ----------------------------------------------------- | --- +**contextSeparator** | Key separator used in your translation keys | `_` +**createOldLibraries** | Save the \_old files | `true` +**defaultNamespace** | Default namespace used in your i18next config | `translation` +**defaultValue** | Default value to give to empty keys | `''` +**extension** | Edit the extension of the locale files | `.json` +**filename** | Edit the filename of the locale files | `'$NAMESPACE'` +**jsonIndentation** | Indentation of the catalog files | `2` +**keepRemoved** | Keep keys from the catalog that are no longer in code | `false` +**keySeparator** | Key separator used in your translation keys | `.` +**lexers** | See below for details | `{}` +**lineEnding** | Control the line ending. See options at [eol](https://github.com/ryanve/eol) | `auto` +**locales** | An array of the locales in your applications | `['en','fr']` +**namespaceSeparator** | Namespace separator used in your translation keys | `:` +**output** | Where to write the locale files relative to the base | `locales` +**sort** | Whether or not to sort the catalog | `false` -The transform emit a `json_error` event if the JSON.parse on json files fail. It is passed the error name (like `SyntaxError`) and the error message (like `Unexpected token }`): +### Catalog filenames -`.pipe( i18next().on('reading', function(name, message) { }) )` +Both `filename` and `extension` options support injection of `$LOCALE` and `$NAMESPACE` variables. -### Errors +### Lexers -- `i18next-parser does not support variables in translation functions, use a string literal`: i18next-parser can't parse keys if you use variables like `t(variable)`, you need to use strings like `t('string')`. - ---- - -## Examples - -**Change the output directory (cli and gulp)** - -Command line (the options are identical): - -`i18next /path/to/file/or/dir -o /output/directory` - -`i18next /path/to/file/or/dir:/output/directory` - -Gulp: - -`.pipe(i18next({output: 'translations'}))` - -It will create the file in the specified folder (in case of gulp it doesn't actually create the files until you call `dest()`): +The `lexers` option let you configure which Lexer to use for which extension. Here is the default: ``` -/output/directory/en/translation.json -... -``` - - - -**Change the locales (cli and gulp)** - -Command line: - -`i18next /path/to/file/or/dir -l en,de,sp` - -Gulp: +{ + lexers: { + hbs: ['HandlebarsLexer'], + handlebars: ['HandlebarsLexer'], -`.pipe(i18next({locales: ['en', 'de', 'sp']}))` + htm: ['HTMLLexer'], + html: ['HTMLLexer'], -This will create a directory per locale in the output folder: + js: ['JavascriptLexer'], + mjs: ['JavascriptLexer'], + default: ['JavascriptLexer'] + } +} ``` -locales/en/... -locales/de/... -locales/sp/... -``` - - - -**Change the default namespace (cli and gulp)** -Command line: +Note the presence of a `default` which will catch any extension that is not listed. There are 3 lexers available: `HandlebarsLexer`, `HTMLLexer` and `JavascriptLexer`. Each has configurations of its own. If you need to change the defaults, you can do it like so: -`i18next /path/to/file/or/dir -n my_default_namespace` - -Gulp: - -`.pipe(i18next({namespace: 'my_default_namespace'}))` - -This will add all the translation from the default namespace in the following file: - -``` -locales/en/my_default_namespace.json -... ``` - - - -**Change the namespace and key separators (cli and gulp)** - -Command line: - -`i18next /path/to/file/or/dir -s '?' -k '_'` - -Gulp: - -`.pipe(i18next({namespaceSeparator: '?', keySeparator: '_'}))` - -This parse the translation keys as follow: - -``` -namespace?key_subkey - -namespace.json { - key: { - subkey: '' - } + lexers: { + hbs: [ + { + lexer: 'HandlebarsLexer', + functions: ['translate', '__'] + } + ], + ... + } } -... ``` +**`HandlebarsLexer` options** +Option | Description | Default +------------- | --------------------------- | ------- +**functions** | Array of functions to match | `['t']` -**Change the translation functions (cli and gulp)** - -Command line: +**`HTMLLexer` options** -`i18next /path/to/file/or/dir -f __,_e` +Option | Description | Default +-------------- | --------------------------- | ------- +**attr** | Attribute for the keys | `'data-i18n'` +**optionAttr** | Attribute for the options | `'data-i18n-options'` -Gulp: - -`.pipe(i18next({functions: ['__', '_e']}))` - -This will parse any of the following function calls in your code and extract the key: - -``` -__('key' -__ 'key' -__("key" -__ "key" -_e('key' -_e 'key' -_e("key" -_e "key" -``` +**`JavscriptLexer` options** -Note1: we don't match the closing parenthesis as you may want to pass arguments to your translation function. +Option | Description | Default +------------- | --------------------------- | ------- +**functions** | Array of functions to match | `['t']` -Note2: the parser is smart about escaped single or double quotes you may have in your key. +## Events -**Change the regex (cli and gulp)** +The transform emits a `reading` event for each file it parses: -Command line: +`.pipe( i18next().on('reading', (file) => {}) )` -`i18next /path/to/file/or/dir -p "(.*)"` +The transform emits a `error:json` event if the JSON.parse on json files fail: -Gulp: +`.pipe( i18next().on('error:json', (path, error) => {}) )` -`.pipe(i18next({parser: '(.*)'}))` +The transform emits a `warning:variable` event if the file has a key that contains a variable: -If you use a custom regex, the `functions` option will be ignored. You need to write you regex to parse the functions you want parsed. +`.pipe( i18next().on('warning:variable', (path, key) => {}) )` -You must pass the regex as a string. That means that you will have to properly escape it. Also, the parser will consider the translation key to be the first truthy match of the regex; it means that you must use non capturing blocks `(?:)` for anything that is not the translation key. -The regex used by default is: -`[^a-zA-Z0-9_](?:t)(?:\\(|\\s)\\s*(?:(?:\'((?:(?:\\\\\')?[^\']+)+[^\\\\])\')|(?:"((?:(?:\\\\")?[^"]+)+[^\\\\])"))/g` - - - -**Filter files and folders (cli)** - -`i18next /path/to/file/or/dir --fileFilter '*.hbs,*.js' --directoryFilter '!.git'` - -In recursive mode, it will parse `*.hbs` and `*.js` files and skip `.git` folder. This options is passed to readdirp. To learn more, read [their documentation](https://github.com/thlorenz/readdirp#filters). - - - -**Work with Meteor TAP-i18N (gulp)** +## Contribute -`.pipe(i18next({ - output: "i18n", - locales: ['en', 'de', 'fr', 'es'], - functions: ['_'], - namespace: 'client', - suffix: '.$LOCALE', - extension: ".i18n.json", - writeOld: false -}))` +Any contribution is welcome. Please [read the guidelines](doc/development.md) first. -This will output your files in the format `$LOCALE/client.$LOCALE.i18n.json` in a `i18n/` directory. +Thanks a lot to all the previous [contributors](https://github.com/i18next/i18next-parser/graphs/contributors). diff --git a/docs/development.md b/docs/development.md new file mode 100644 index 00000000..82eef314 --- /dev/null +++ b/docs/development.md @@ -0,0 +1,56 @@ +# Contribute + +Any contribution is welcome. Just follow those guidelines: + +1. If you are unsure, open a ticket before working on anything +2. Fork and clone the project +3. Create a branch `git checkout -b feature/my-feature` (or `hotfix`) +4. Push the code to your fork +5. **Write tests and documentation. I won't merge a PR without it!** +6. Make a pull request from your new branch +7. Wait, I am usually pretty fast to merge PRs :) + +Thanks a lot to all the previous [contributors](https://github.com/i18next/i18next-parser/graphs/contributors). + +## Setup + +``` +git clone git@github.com:/i18next-parser.git +cd i18next-parser +yarn +``` + +## Development + +The code is written using the latest ES6 features. For the cli to run on older node version, it is compiled with Babel. You can run the compiler in watch mode and let it in the background: + +``` +yarn watch +``` + +Don't forget to commit the compiled files. + +## Tests + +Make sure the tests pass: + +``` +mocha --require babel-register --require babel-polyfill test/**/*.js +``` + +To test the CLI: + +``` +yarn link +cd test +i18next 'templating/**/*' -o manual +``` + +## `0.x` vs `1.x` + +`1.x` is a major release. It is not backward compatible. There are two separate branches: + +- `master` for `1.x` +- `0.x.x` for the old version + +I will not maintain the old version but will welcome bug fixes as PRs. diff --git a/docs/examples.md b/docs/examples.md new file mode 100644 index 00000000..d5440b75 --- /dev/null +++ b/docs/examples.md @@ -0,0 +1,160 @@ +# Examples + +**Change the output directory (cli and gulp)** + +Command line (the options are identical): + +`i18next /path/to/file/or/dir -o /output/directory` + +`i18next /path/to/file/or/dir:/output/directory` + +Gulp: + +`.pipe(i18next({output: 'translations'}))` + +It will create the file in the specified folder (in case of gulp it doesn't actually create the files until you call `dest()`): + +``` +/output/directory/en/translation.json +... +``` + + + +**Change the locales (cli and gulp)** + +Command line: + +`i18next /path/to/file/or/dir -l en,de,sp` + +Gulp: + +`.pipe(i18next({locales: ['en', 'de', 'sp']}))` + +This will create a directory per locale in the output folder: + +``` +locales/en/... +locales/de/... +locales/sp/... +``` + + + +**Change the default namespace (cli and gulp)** + +Command line: + +`i18next /path/to/file/or/dir -n my_default_namespace` + +Gulp: + +`.pipe(i18next({namespace: 'my_default_namespace'}))` + +This will add all the translation from the default namespace in the following file: + +``` +locales/en/my_default_namespace.json +... +``` + + + +**Change the namespace and key separators (cli and gulp)** + +Command line: + +`i18next /path/to/file/or/dir -s '?' -k '_'` + +Gulp: + +`.pipe(i18next({namespaceSeparator: '?', keySeparator: '_'}))` + +This parse the translation keys as follow: + +``` +namespace?key_subkey + +namespace.json +{ + key: { + subkey: '' + } +} +... +``` + + + +**Change the translation functions (cli and gulp)** + +Command line: + +`i18next /path/to/file/or/dir -f __,_e` + +Gulp: + +`.pipe(i18next({functions: ['__', '_e']}))` + +This will parse any of the following function calls in your code and extract the key: + +``` +__('key' +__ 'key' +__("key" +__ "key" +_e('key' +_e 'key' +_e("key" +_e "key" +``` + +Note1: we don't match the closing parenthesis as you may want to pass arguments to your translation function. + +Note2: the parser is smart about escaped single or double quotes you may have in your key. + + + +**Change the regex (cli and gulp)** + +Command line: + +`i18next /path/to/file/or/dir -p "(.*)"` + +Gulp: + +`.pipe(i18next({parser: '(.*)'}))` + +If you use a custom regex, the `functions` option will be ignored. You need to write you regex to parse the functions you want parsed. + +You must pass the regex as a string. That means that you will have to properly escape it. Also, the parser will consider the translation key to be the first truthy match of the regex; it means that you must use non capturing blocks `(?:)` for anything that is not the translation key. + +The regex used by default is: + +`[^a-zA-Z0-9_](?:t)(?:\\(|\\s)\\s*(?:(?:\'((?:(?:\\\\\')?[^\']+)+[^\\\\])\')|(?:"((?:(?:\\\\")?[^"]+)+[^\\\\])"))/g` + + + +**Filter files and folders (cli)** + + + +`i18next /path/to/file/or/dir --fileFilter '*.hbs,*.js' --directoryFilter '!.git'` + +In recursive mode, it will parse `*.hbs` and `*.js` files and skip `.git` folder. This options is passed to readdirp. To learn more, read [their documentation](https://github.com/thlorenz/readdirp#filters). + + + +**Work with Meteor TAP-i18N (gulp)** + +`.pipe(i18next({ + output: "i18n", + locales: ['en', 'de', 'fr', 'es'], + functions: ['_'], + namespace: 'client', + suffix: '.$LOCALE', + extension: ".i18n.json", + writeOld: false +}))` + +This will output your files in the format `$LOCALE/client.$LOCALE.i18n.json` in a `i18n/` directory. diff --git a/docs/migration.md b/docs/migration.md new file mode 100644 index 00000000..65d576d4 --- /dev/null +++ b/docs/migration.md @@ -0,0 +1,39 @@ +# Migrating from `0.x` to `1.x` + +## Breaking changes + +- Jade is not being tested anymore. If this is something you need, please make a PR with a Lexer for it +- `regex` option was deprecated. If you need to support a custom file format, please make a PR with a Lexer for it +- `ignoreVariables` was deprecated. Keys that are not string litterals now emit a warning +- `writeOld` was renamed `createOldLibraries`. It defaults to `true`. +- `namespace` was renamed `defaultNamespace`. It defaults to `translation`. +- `prefix` was deprecated. Use `filename` +- `suffix` was deprecated. Use `filename` +- catalogs are no longer sorted by default. Set `sort` to `true` to enable this. + +## Improvements + +- `defaultValue`: replace empty keys with the given value +- `filename` and `extension` support for `$NAMESPACE` and `$LOCALE` variables +- `jsonIndentation` let you control the indentation of the catalogs +- `lineEnding` let you control the line ending of the catalogs +- `sort` let you enable sorting. + +## Lexers + +Instead of writing a single regex to match all use cases or to run many regexes on all files, the new version introduce the concept of "Lexer". Each file format has its own Lexer. It adds some code but reduces complexity a lot and improves maintainability. + +## CLI + +- `i18next input:output` syntax was deprecated. Use the `--output` option +- `recursive` was deprecated. You can now pass a glob +- `directoryFilter` was deprecated. You can now pass a glob +- `fileFilter` was deprecated. You can now pass a glob + +### `0.x` + +`i18next src --recursive --fileFilter '*.hbs,*.js' --directoryFilter '!.git'` + +### `1.x` + +`i18next 'src/**/*.{js,hbs}' '!.git'` diff --git a/package.json b/package.json index d131e49c..0c750dd4 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "author": "Karel Ledru", "description": "Command Line tool for i18next", "name": "i18next-parser", - "version": "1.0.0-alpha1", + "version": "1.0.0-beta1", "license": "MIT", "main": "src/index.js", "bin": { From 3c6fd5ff5595cfc6fc2ca8064aad3077a697c9e3 Mon Sep 17 00:00:00 2001 From: Karel Ledru-Mathe Date: Sat, 24 Feb 2018 12:26:54 -0500 Subject: [PATCH 6/8] Support output to yml --- README.md | 2 +- dist/index.js | 16 +++++++++++----- docs/migration.md | 2 +- package.json | 3 ++- src/index.js | 14 ++++++++++---- test/parser.test.js | 27 +++++++++++++++++++++++++-- yarn.lock | 17 +++++++++++++++++ 7 files changed, 67 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index b7b8fbed..b6bd5b7e 100644 --- a/README.md +++ b/README.md @@ -79,7 +79,7 @@ Option | Description | **defaultValue** | Default value to give to empty keys | `''` **extension** | Edit the extension of the locale files | `.json` **filename** | Edit the filename of the locale files | `'$NAMESPACE'` -**jsonIndentation** | Indentation of the catalog files | `2` +**indentation** | Indentation of the catalog files | `2` **keepRemoved** | Keep keys from the catalog that are no longer in code | `false` **keySeparator** | Key separator used in your translation keys | `.` **lexers** | See below for details | `{}` diff --git a/dist/index.js b/dist/index.js index 45c54b97..d856e809 100644 --- a/dist/index.js +++ b/dist/index.js @@ -9,7 +9,8 @@ var _eol = require('eol');var _eol2 = _interopRequireDefault(_eol); var _fs = require('fs');var _fs2 = _interopRequireDefault(_fs); var _parser = require('./parser');var _parser2 = _interopRequireDefault(_parser); var _path = require('path');var _path2 = _interopRequireDefault(_path); -var _vinyl = require('vinyl');var _vinyl2 = _interopRequireDefault(_vinyl);function _interopRequireDefault(obj) {return obj && obj.__esModule ? obj : { default: obj };}function _classCallCheck(instance, Constructor) {if (!(instance instanceof Constructor)) {throw new TypeError("Cannot call a class as a function");}}function _possibleConstructorReturn(self, call) {if (!self) {throw new ReferenceError("this hasn't been initialised - super() hasn't been called");}return call && (typeof call === "object" || typeof call === "function") ? call : self;}function _inherits(subClass, superClass) {if (typeof superClass !== "function" && superClass !== null) {throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);}subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } });if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;}var +var _vinyl = require('vinyl');var _vinyl2 = _interopRequireDefault(_vinyl); +var _yamljs = require('yamljs');var _yamljs2 = _interopRequireDefault(_yamljs);function _interopRequireDefault(obj) {return obj && obj.__esModule ? obj : { default: obj };}function _classCallCheck(instance, Constructor) {if (!(instance instanceof Constructor)) {throw new TypeError("Cannot call a class as a function");}}function _possibleConstructorReturn(self, call) {if (!self) {throw new ReferenceError("this hasn't been initialised - super() hasn't been called");}return call && (typeof call === "object" || typeof call === "function") ? call : self;}function _inherits(subClass, superClass) {if (typeof superClass !== "function" && superClass !== null) {throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);}subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } });if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;}var i18nTransform = function (_Transform) {_inherits(i18nTransform, _Transform); @@ -24,7 +25,7 @@ i18nTransform = function (_Transform) {_inherits(i18nTransform, _Transform); defaultValue: '', extension: '.json', filename: '$NAMESPACE', - jsonIndentation: 2, + indentation: 2, keepRemoved: false, keySeparator: '.', lexers: {}, @@ -52,7 +53,7 @@ i18nTransform = function (_Transform) {_inherits(i18nTransform, _Transform); content = file.contents; } else { - content = _fs2.default.readFileSync(file.path, enc); + content = _fs2.default.readFileSync(file.path, encoding); } this.emit('reading', file); @@ -60,7 +61,6 @@ i18nTransform = function (_Transform) {_inherits(i18nTransform, _Transform); var extenstion = _path2.default.extname(file.path).substring(1); var entries = this.parser.parse(content, extenstion); - entries.forEach(function (entry) { var key = entry.key; var parts = key.split(_this2.options.namespaceSeparator); @@ -177,7 +177,13 @@ i18nTransform = function (_Transform) {_inherits(i18nTransform, _Transform); } }, { key: 'pushFile', value: function pushFile( path, contents) { - var text = JSON.stringify(contents, null, this.options.jsonIndentation) + '\n'; + var text = void 0; + if (path.endsWith('yml')) { + text = _yamljs2.default.stringify(contents, null, this.options.indentation); + } else + { + text = JSON.stringify(contents, null, this.options.indentation) + '\n'; + } if (this.options.lineEnding === 'auto') { text = _eol2.default.auto(text); diff --git a/docs/migration.md b/docs/migration.md index 65d576d4..b666fa17 100644 --- a/docs/migration.md +++ b/docs/migration.md @@ -15,7 +15,7 @@ - `defaultValue`: replace empty keys with the given value - `filename` and `extension` support for `$NAMESPACE` and `$LOCALE` variables -- `jsonIndentation` let you control the indentation of the catalogs +- `indentation` let you control the indentation of the catalogs - `lineEnding` let you control the line ending of the catalogs - `sort` let you enable sorting. diff --git a/package.json b/package.json index 0c750dd4..46c7db79 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,8 @@ "lodash": "~4.17.4", "through2": "~2.0.3", "vinyl": "~2.0.1", - "vinyl-fs": "^3.0.2" + "vinyl-fs": "^3.0.2", + "yamljs": "^0.3.0" }, "engines": { "node": ">=0.10.22" diff --git a/src/index.js b/src/index.js index f6c756bd..8c5d569e 100644 --- a/src/index.js +++ b/src/index.js @@ -10,6 +10,7 @@ import fs from 'fs' import Parser from './parser' import path from 'path' import VirtualFile from 'vinyl' +import YAML from 'yamljs' export default class i18nTransform extends Transform { @@ -24,7 +25,7 @@ export default class i18nTransform extends Transform { defaultValue: '', extension: '.json', filename: '$NAMESPACE', - jsonIndentation: 2, + indentation: 2, keepRemoved: false, keySeparator: '.', lexers: {}, @@ -52,7 +53,7 @@ export default class i18nTransform extends Transform { content = file.contents } else { - content = fs.readFileSync(file.path, enc) + content = fs.readFileSync(file.path, encoding) } this.emit('reading', file) @@ -60,7 +61,6 @@ export default class i18nTransform extends Transform { const extenstion = path.extname(file.path).substring(1) const entries = this.parser.parse(content, extenstion) - entries.forEach(entry => { let key = entry.key const parts = key.split(this.options.namespaceSeparator) @@ -177,7 +177,13 @@ export default class i18nTransform extends Transform { } pushFile(path, contents) { - let text = JSON.stringify(contents, null, this.options.jsonIndentation) + '\n' + let text + if (path.endsWith('yml')) { + text = YAML.stringify(contents, null, this.options.indentation) + } + else { + text = JSON.stringify(contents, null, this.options.indentation) + '\n' + } if (this.options.lineEnding === 'auto') { text = eol.auto(text) diff --git a/test/parser.test.js b/test/parser.test.js index a3c33141..146d1c80 100644 --- a/test/parser.test.js +++ b/test/parser.test.js @@ -437,10 +437,33 @@ describe('parser', function () { i18nextParser.end(fakeFile) }) - it('supports an jsonIndentation option', function(done) { + it('supports outputing to yml', function(done) { let result const i18nextParser = new i18nTransform({ - jsonIndentation: 6 + extension: '.yml' + }) + const fakeFile = new Vinyl({ + contents: Buffer.from("asd t('first')"), + path: 'file.js' + }) + + i18nextParser.on('data', (file) => { + if (file.relative.endsWith('en/translation.yml')) { + result = file.contents.toString('utf8') + } + }) + i18nextParser.once('end', function () { + assert.equal(result, 'first: ""\n' ) + done() + }) + + i18nextParser.end(fakeFile) + }) + + it('supports an indentation option', function(done) { + let result + const i18nextParser = new i18nTransform({ + indentation: 6 }) const fakeFile = new Vinyl({ contents: Buffer.from("asd t('first')"), diff --git a/yarn.lock b/yarn.lock index 4fc43da4..b3dfbd30 100644 --- a/yarn.lock +++ b/yarn.lock @@ -45,6 +45,12 @@ are-we-there-yet@~1.1.2: delegates "^1.0.0" readable-stream "^2.0.6" +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + dependencies: + sprintf-js "~1.0.2" + arr-diff@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-2.0.0.tgz#8f3b827f955a8bd669697e4a4256ac3ceae356cf" @@ -1839,6 +1845,10 @@ source-map@^0.5.6: version "0.5.7" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + sshpk@^1.7.0: version "1.13.1" resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.13.1.tgz#512df6da6287144316dc4c18fe1cf1d940739be3" @@ -2090,3 +2100,10 @@ wrappy@1: xtend@~4.0.0, xtend@~4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" + +yamljs@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/yamljs/-/yamljs-0.3.0.tgz#dc060bf267447b39f7304e9b2bfbe8b5a7ddb03b" + dependencies: + argparse "^1.0.7" + glob "^7.0.5" From b83109783bf1029d17ce2f687b08c8966f003ef0 Mon Sep 17 00:00:00 2001 From: Karel Ledru-Mathe Date: Sat, 24 Feb 2018 12:40:55 -0500 Subject: [PATCH 7/8] Create context keys --- dist/index.js | 9 +++++++- src/index.js | 9 +++++++- test/parser.test.js | 56 +++++++++++++++++++++++++++++++++------------ 3 files changed, 58 insertions(+), 16 deletions(-) diff --git a/dist/index.js b/dist/index.js index d856e809..8b701c57 100644 --- a/dist/index.js +++ b/dist/index.js @@ -19,7 +19,7 @@ i18nTransform = function (_Transform) {_inherits(i18nTransform, _Transform); options)); _this.defaults = { - contextSeparator: '_', // TODO + contextSeparator: '_', createOldLibraries: true, defaultNamespace: 'translation', defaultValue: '', @@ -160,6 +160,13 @@ i18nTransform = function (_Transform) {_inherits(i18nTransform, _Transform); { existing = _extends({}, existing, entry); } + + if (entry.context) { + var contextEntry = Object.assign({}, entry); + delete contextEntry.context; + contextEntry.key += this.options.contextSeparator + entry.context; + this.addEntry(contextEntry); + } } }, { key: 'getCatalog', value: function getCatalog( path) { diff --git a/src/index.js b/src/index.js index 8c5d569e..e07498b0 100644 --- a/src/index.js +++ b/src/index.js @@ -19,7 +19,7 @@ export default class i18nTransform extends Transform { super(options) this.defaults = { - contextSeparator: '_', // TODO + contextSeparator: '_', createOldLibraries: true, defaultNamespace: 'translation', defaultValue: '', @@ -160,6 +160,13 @@ export default class i18nTransform extends Transform { else { existing = {...existing, ...entry} } + + if (entry.context) { + const contextEntry = Object.assign({}, entry) + delete contextEntry.context + contextEntry.key += this.options.contextSeparator + entry.context + this.addEntry(contextEntry) + } } getCatalog(path) { diff --git a/test/parser.test.js b/test/parser.test.js index 146d1c80..88b093a6 100644 --- a/test/parser.test.js +++ b/test/parser.test.js @@ -51,6 +51,30 @@ describe('parser', function () { i18nextParser.end(fakeFile) }) + it('creates context keys', function (done) { + let result + const i18nextParser = new i18nTransform() + const fakeFile = new Vinyl({ + contents: Buffer.from("asd t('first', {context: 'female'})"), + path: 'file.js' + }) + + i18nextParser.on('data', (file) => { + if (file.relative.endsWith('en/translation.json')) { + result = JSON.parse(file.contents) + } + }) + i18nextParser.once('end', () => { + assert.deepEqual(result, { + first: '', + first_female: '' + }) + done() + }) + + i18nextParser.end(fakeFile) + }) + it('parses html files', function (done) { let result const i18nextParser = new i18nTransform() @@ -89,9 +113,13 @@ describe('parser', function () { const expected = { first: '', second: 'defaultValue', + second_male: 'defaultValue', third: 'defaultValue', + third_female: 'defaultValue', fourth: 'defaultValue', + fourth_male: 'defaultValue', fifth: '', + fifth_male: '', sixth: '', seventh: 'defaultValue' } @@ -181,7 +209,7 @@ describe('parser', function () { result = JSON.parse(file.contents) } }) - i18nextParser.once('end', function () { + i18nextParser.once('end', () => { const keys = Object.keys(result) assert.equal( keys[0], "escaped 'single quotes'" ) assert.equal( keys[1], 'escaped "double quotes"' ) @@ -204,7 +232,7 @@ describe('parser', function () { result = JSON.parse(file.contents) } }) - i18nextParser.once('end', function () { + i18nextParser.once('end', () => { const keys = Object.keys(result) assert.equal( keys[0], 'escaped backslash\\ newline\n\r tab\t' ) done() @@ -241,7 +269,7 @@ describe('parser', function () { result = JSON.parse(file.contents) } }) - i18nextParser.once('end', function () { + i18nextParser.once('end', () => { assert.deepEqual( result, { first: 'first', second: '' } ) done() }) @@ -266,7 +294,7 @@ describe('parser', function () { resultFR = JSON.parse(file.contents) } }) - i18nextParser.once('end', function () { + i18nextParser.once('end', () => { assert.deepEqual( resultEN, { first: 'first', second: 'second' } ) assert.deepEqual( resultFR, { first: 'premier', second: '' } ) done() @@ -294,7 +322,7 @@ describe('parser', function () { result = JSON.parse(file.contents) } }) - i18nextParser.once('end', function () { + i18nextParser.once('end', () => { assert.deepEqual( result, expectedResult ) done() }) @@ -323,7 +351,7 @@ describe('parser', function () { result = JSON.parse(file.contents) } }) - i18nextParser.once('end', function () { + i18nextParser.once('end', () => { assert.deepEqual( result, expectedResult ) done() }) @@ -350,7 +378,7 @@ describe('parser', function () { result = JSON.parse(file.contents) } }) - i18nextParser.once('end', function () { + i18nextParser.once('end', () => { assert.deepEqual( result, expectedResult ) done() }) @@ -406,7 +434,7 @@ describe('parser', function () { result = JSON.parse(file.contents) } }) - i18nextParser.once('end', function () { + i18nextParser.once('end', () => { assert.deepEqual( result, { first: '', second: { third: '' } } ) done() }) @@ -429,7 +457,7 @@ describe('parser', function () { result = JSON.parse(file.contents) } }) - i18nextParser.once('end', function () { + i18nextParser.once('end', () => { assert.deepEqual( result, { first: 'NOT_TRANSLATED' } ) done() }) @@ -452,7 +480,7 @@ describe('parser', function () { result = file.contents.toString('utf8') } }) - i18nextParser.once('end', function () { + i18nextParser.once('end', () => { assert.equal(result, 'first: ""\n' ) done() }) @@ -475,7 +503,7 @@ describe('parser', function () { result = file.contents.toString('utf8') } }) - i18nextParser.once('end', function () { + i18nextParser.once('end', () => { assert.deepEqual( result.split('\n')[1], ' "first": ""' ) done() }) @@ -537,7 +565,7 @@ describe('parser', function () { result = JSON.parse(file.contents) } }) - i18nextParser.once('end', function () { + i18nextParser.once('end', () => { assert.deepEqual(result, {first: '', second: ''}) done() }) @@ -560,7 +588,7 @@ describe('parser', function () { result = JSON.parse(file.contents) } }) - i18nextParser.once('end', function () { + i18nextParser.once('end', () => { assert.sameOrderedMembers(Object.keys(result), ['ccc', 'aaa', 'bbb']) assert.sameOrderedMembers(Object.keys(result.bbb), ['bbb', 'aaa']) done() @@ -584,7 +612,7 @@ describe('parser', function () { result = JSON.parse(file.contents) } }) - i18nextParser.once('end', function () { + i18nextParser.once('end', () => { assert.sameOrderedMembers(Object.keys(result), ['aaa', 'bbb', 'ccc']) assert.sameOrderedMembers(Object.keys(result.bbb), ['aaa', 'bbb']) done() From 2be2e734a7bd7501979153c6035acf10e0591652 Mon Sep 17 00:00:00 2001 From: Karel Ledru-Mathe Date: Sat, 24 Feb 2018 13:55:06 -0500 Subject: [PATCH 8/8] Code style --- dist/helpers.js | 14 +- dist/index.js | 18 +- dist/lexers/base-lexer.js | 7 +- dist/lexers/handlebars-lexer.js | 1 - dist/lexers/html-lexer.js | 2 - dist/lexers/javascript-lexer.js | 23 +- dist/parser.js | 8 +- src/helpers.js | 58 ++--- src/index.js | 33 ++- src/lexers/base-lexer.js | 9 +- src/lexers/handlebars-lexer.js | 1 - src/lexers/html-lexer.js | 6 +- src/lexers/javascript-lexer.js | 59 ++--- src/parser.js | 11 +- test/lexers/base-lexer.test.js | 52 ++--- test/lexers/handlebars-lexer.test.js | 169 +++++--------- test/lexers/html-lexer.test.js | 121 +++++----- test/lexers/javascript-lexer.test.js | 168 +++++--------- test/parser.test.js | 316 +++++++++++++++------------ 19 files changed, 463 insertions(+), 613 deletions(-) diff --git a/dist/helpers.js b/dist/helpers.js index 86013dad..5ec37956 100644 --- a/dist/helpers.js +++ b/dist/helpers.js @@ -25,7 +25,6 @@ function dotPathToHash(path) {var separator = arguments.length > 1 && arguments[ return _lodash2.default.merge(target, result); } - // Takes a `source` hash and make sure its value // are pasted in the `target` hash, if the target // hash has the corresponding key (or if keepRemoved is true). @@ -34,12 +33,15 @@ function mergeHashes(source) {var target = arguments.length > 1 && arguments[1] old = old || {}; Object.keys(source).forEach(function (key) { var hasNestedEntries = - _typeof(target[key]) === 'object' && - !Array.isArray(target[key]); - + _typeof(target[key]) === 'object' && !Array.isArray(target[key]); if (hasNestedEntries) { - var nested = mergeHashes(source[key], target[key], old[key], keepRemoved); + var nested = mergeHashes( + source[key], + target[key], + old[key], + keepRemoved); + target[key] = nested.new; old[key] = nested.old; } else @@ -79,7 +81,6 @@ function mergeHashes(source) {var target = arguments.length > 1 && arguments[1] return { old: old, new: target }; } - // Takes a `target` hash and replace its empty // values with the `source` hash ones if they // exist @@ -100,7 +101,6 @@ function populateHash(source) {var target = arguments.length > 1 && arguments[1] - dotPathToHash = dotPathToHash;exports. mergeHashes = mergeHashes;exports. populateHash = populateHash; \ No newline at end of file diff --git a/dist/index.js b/dist/index.js index 8b701c57..e43bcc19 100644 --- a/dist/index.js +++ b/dist/index.js @@ -1,8 +1,4 @@ 'use strict';Object.defineProperty(exports, "__esModule", { value: true });var _extends = Object.assign || function (target) {for (var i = 1; i < arguments.length; i++) {var source = arguments[i];for (var key in source) {if (Object.prototype.hasOwnProperty.call(source, key)) {target[key] = source[key];}}}return target;};var _createClass = function () {function defineProperties(target, props) {for (var i = 0; i < props.length; i++) {var descriptor = props[i];descriptor.enumerable = descriptor.enumerable || false;descriptor.configurable = true;if ("value" in descriptor) descriptor.writable = true;Object.defineProperty(target, descriptor.key, descriptor);}}return function (Constructor, protoProps, staticProps) {if (protoProps) defineProperties(Constructor.prototype, protoProps);if (staticProps) defineProperties(Constructor, staticProps);return Constructor;};}();var _helpers = require('./helpers'); - - - - var _stream = require('stream'); var _lodash = require('lodash');var _lodash2 = _interopRequireDefault(_lodash); var _eol = require('eol');var _eol2 = _interopRequireDefault(_eol); @@ -13,7 +9,6 @@ var _vinyl = require('vinyl');var _vinyl2 = _interopRequireDefault(_vinyl); var _yamljs = require('yamljs');var _yamljs2 = _interopRequireDefault(_yamljs);function _interopRequireDefault(obj) {return obj && obj.__esModule ? obj : { default: obj };}function _classCallCheck(instance, Constructor) {if (!(instance instanceof Constructor)) {throw new TypeError("Cannot call a class as a function");}}function _possibleConstructorReturn(self, call) {if (!self) {throw new ReferenceError("this hasn't been initialised - super() hasn't been called");}return call && (typeof call === "object" || typeof call === "function") ? call : self;}function _inherits(subClass, superClass) {if (typeof superClass !== "function" && superClass !== null) {throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);}subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } });if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;}var i18nTransform = function (_Transform) {_inherits(i18nTransform, _Transform); - function i18nTransform() {var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};_classCallCheck(this, i18nTransform); options.objectMode = true;var _this = _possibleConstructorReturn(this, (i18nTransform.__proto__ || Object.getPrototypeOf(i18nTransform)).call(this, options)); @@ -65,7 +60,6 @@ i18nTransform = function (_Transform) {_inherits(i18nTransform, _Transform); var key = entry.key; var parts = key.split(_this2.options.namespaceSeparator); - if (parts.length > 1) { entry.namespace = parts.shift(); } else @@ -104,7 +98,6 @@ i18nTransform = function (_Transform) {_inherits(i18nTransform, _Transform); }); this.options.locales.forEach(function (locale) { - var outputPath = _path2.default.resolve(_this3.options.output, locale); Object.keys(catalog).forEach(function (namespace) { @@ -126,7 +119,6 @@ i18nTransform = function (_Transform) {_inherits(i18nTransform, _Transform); var existingCatalog = _this3.getCatalog(namespacePath); var oldCatalog = _this3.getCatalog(namespaceOldPath); - // merges existing translations with the new ones var _mergeHashes = (0, _helpers.mergeHashes)( existingCatalog, @@ -194,11 +186,15 @@ i18nTransform = function (_Transform) {_inherits(i18nTransform, _Transform); if (this.options.lineEnding === 'auto') { text = _eol2.default.auto(text); - } else if (lineEnding === '\r\n' || lineEnding === 'crlf') { + } else + if (lineEnding === '\r\n' || lineEnding === 'crlf') { text = _eol2.default.crlf(text); - } else if (lineEnding === '\r' || lineEnding === 'cr') { + } else + if (lineEnding === '\r' || lineEnding === 'cr') { text = _eol2.default.cr(text); - } else {// Defaults to LF, aka \n + } else + { + // Defaults to LF, aka \n text = _eol2.default.lf(text); } diff --git a/dist/lexers/base-lexer.js b/dist/lexers/base-lexer.js index 567aa376..e9d1e89c 100644 --- a/dist/lexers/base-lexer.js +++ b/dist/lexers/base-lexer.js @@ -1,7 +1,6 @@ 'use strict';Object.defineProperty(exports, "__esModule", { value: true });var _extends = Object.assign || function (target) {for (var i = 1; i < arguments.length; i++) {var source = arguments[i];for (var key in source) {if (Object.prototype.hasOwnProperty.call(source, key)) {target[key] = source[key];}}}return target;};var _createClass = function () {function defineProperties(target, props) {for (var i = 0; i < props.length; i++) {var descriptor = props[i];descriptor.enumerable = descriptor.enumerable || false;descriptor.configurable = true;if ("value" in descriptor) descriptor.writable = true;Object.defineProperty(target, descriptor.key, descriptor);}}return function (Constructor, protoProps, staticProps) {if (protoProps) defineProperties(Constructor.prototype, protoProps);if (staticProps) defineProperties(Constructor, staticProps);return Constructor;};}();var _events = require('events');var _events2 = _interopRequireDefault(_events);function _interopRequireDefault(obj) {return obj && obj.__esModule ? obj : { default: obj };}function _classCallCheck(instance, Constructor) {if (!(instance instanceof Constructor)) {throw new TypeError("Cannot call a class as a function");}}function _possibleConstructorReturn(self, call) {if (!self) {throw new ReferenceError("this hasn't been initialised - super() hasn't been called");}return call && (typeof call === "object" || typeof call === "function") ? call : self;}function _inherits(subClass, superClass) {if (typeof superClass !== "function" && superClass !== null) {throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);}subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } });if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;}var BaseLexer = function (_EventEmitter) {_inherits(BaseLexer, _EventEmitter); - function BaseLexer() {var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};_classCallCheck(this, BaseLexer);var _this = _possibleConstructorReturn(this, (BaseLexer.__proto__ || Object.getPrototypeOf(BaseLexer)).call(this)); _this.keys = []; @@ -39,15 +38,15 @@ BaseLexer = function (_EventEmitter) {_inherits(BaseLexer, _EventEmitter); } }], [{ key: 'singleQuotePattern', get: function get() { - return "'(?:[^\'].*?[^\\\\])?'"; + return "'(?:[^'].*?[^\\\\])?'"; } }, { key: 'doubleQuotePattern', get: function get() { - return '"(?:[^\"].*?[^\\\\])?"'; + return '"(?:[^"].*?[^\\\\])?"'; } }, { key: 'backQuotePattern', get: function get() { - return '`(?:[^\`].*?[^\\\\])?`'; + return '`(?:[^`].*?[^\\\\])?`'; } }, { key: 'variablePattern', get: function get() { diff --git a/dist/lexers/handlebars-lexer.js b/dist/lexers/handlebars-lexer.js index 34fff28d..84f533cb 100644 --- a/dist/lexers/handlebars-lexer.js +++ b/dist/lexers/handlebars-lexer.js @@ -1,7 +1,6 @@ 'use strict';Object.defineProperty(exports, "__esModule", { value: true });var _createClass = function () {function defineProperties(target, props) {for (var i = 0; i < props.length; i++) {var descriptor = props[i];descriptor.enumerable = descriptor.enumerable || false;descriptor.configurable = true;if ("value" in descriptor) descriptor.writable = true;Object.defineProperty(target, descriptor.key, descriptor);}}return function (Constructor, protoProps, staticProps) {if (protoProps) defineProperties(Constructor.prototype, protoProps);if (staticProps) defineProperties(Constructor, staticProps);return Constructor;};}();var _baseLexer = require('./base-lexer');var _baseLexer2 = _interopRequireDefault(_baseLexer);function _interopRequireDefault(obj) {return obj && obj.__esModule ? obj : { default: obj };}function _classCallCheck(instance, Constructor) {if (!(instance instanceof Constructor)) {throw new TypeError("Cannot call a class as a function");}}function _possibleConstructorReturn(self, call) {if (!self) {throw new ReferenceError("this hasn't been initialised - super() hasn't been called");}return call && (typeof call === "object" || typeof call === "function") ? call : self;}function _inherits(subClass, superClass) {if (typeof superClass !== "function" && superClass !== null) {throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);}subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } });if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;}var HandlebarsLexer = function (_BaseLexer) {_inherits(HandlebarsLexer, _BaseLexer); - function HandlebarsLexer() {var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};_classCallCheck(this, HandlebarsLexer);var _this = _possibleConstructorReturn(this, (HandlebarsLexer.__proto__ || Object.getPrototypeOf(HandlebarsLexer)).call(this, options)); diff --git a/dist/lexers/html-lexer.js b/dist/lexers/html-lexer.js index 41166d5f..b2b9fef4 100644 --- a/dist/lexers/html-lexer.js +++ b/dist/lexers/html-lexer.js @@ -1,7 +1,6 @@ 'use strict';Object.defineProperty(exports, "__esModule", { value: true });var _extends = Object.assign || function (target) {for (var i = 1; i < arguments.length; i++) {var source = arguments[i];for (var key in source) {if (Object.prototype.hasOwnProperty.call(source, key)) {target[key] = source[key];}}}return target;};var _createClass = function () {function defineProperties(target, props) {for (var i = 0; i < props.length; i++) {var descriptor = props[i];descriptor.enumerable = descriptor.enumerable || false;descriptor.configurable = true;if ("value" in descriptor) descriptor.writable = true;Object.defineProperty(target, descriptor.key, descriptor);}}return function (Constructor, protoProps, staticProps) {if (protoProps) defineProperties(Constructor.prototype, protoProps);if (staticProps) defineProperties(Constructor, staticProps);return Constructor;};}();var _baseLexer = require('./base-lexer');var _baseLexer2 = _interopRequireDefault(_baseLexer);function _interopRequireDefault(obj) {return obj && obj.__esModule ? obj : { default: obj };}function _classCallCheck(instance, Constructor) {if (!(instance instanceof Constructor)) {throw new TypeError("Cannot call a class as a function");}}function _possibleConstructorReturn(self, call) {if (!self) {throw new ReferenceError("this hasn't been initialised - super() hasn't been called");}return call && (typeof call === "object" || typeof call === "function") ? call : self;}function _inherits(subClass, superClass) {if (typeof superClass !== "function" && superClass !== null) {throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);}subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } });if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;}var HTMLLexer = function (_BaseLexer) {_inherits(HTMLLexer, _BaseLexer); - function HTMLLexer() {var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};_classCallCheck(this, HTMLLexer);var _this = _possibleConstructorReturn(this, (HTMLLexer.__proto__ || Object.getPrototypeOf(HTMLLexer)).call(this, options)); @@ -36,7 +35,6 @@ HTMLLexer = function (_BaseLexer) {_inherits(HTMLLexer, _BaseLexer); _this2.keys.push(_extends({}, attrs.options, { key: key })); } });};while (matches = regex.exec(content)) {_loop(); - } return this.keys; diff --git a/dist/lexers/javascript-lexer.js b/dist/lexers/javascript-lexer.js index 029b35ac..99660a90 100644 --- a/dist/lexers/javascript-lexer.js +++ b/dist/lexers/javascript-lexer.js @@ -1,7 +1,6 @@ 'use strict';Object.defineProperty(exports, "__esModule", { value: true });var _createClass = function () {function defineProperties(target, props) {for (var i = 0; i < props.length; i++) {var descriptor = props[i];descriptor.enumerable = descriptor.enumerable || false;descriptor.configurable = true;if ("value" in descriptor) descriptor.writable = true;Object.defineProperty(target, descriptor.key, descriptor);}}return function (Constructor, protoProps, staticProps) {if (protoProps) defineProperties(Constructor.prototype, protoProps);if (staticProps) defineProperties(Constructor, staticProps);return Constructor;};}();var _baseLexer = require('./base-lexer');var _baseLexer2 = _interopRequireDefault(_baseLexer);function _interopRequireDefault(obj) {return obj && obj.__esModule ? obj : { default: obj };}function _classCallCheck(instance, Constructor) {if (!(instance instanceof Constructor)) {throw new TypeError("Cannot call a class as a function");}}function _possibleConstructorReturn(self, call) {if (!self) {throw new ReferenceError("this hasn't been initialised - super() hasn't been called");}return call && (typeof call === "object" || typeof call === "function") ? call : self;}function _inherits(subClass, superClass) {if (typeof superClass !== "function" && superClass !== null) {throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);}subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } });if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;}var JavascriptLexer = function (_BaseLexer) {_inherits(JavascriptLexer, _BaseLexer); - function JavascriptLexer() {var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};_classCallCheck(this, JavascriptLexer);var _this = _possibleConstructorReturn(this, (JavascriptLexer.__proto__ || Object.getPrototypeOf(JavascriptLexer)).call(this, options)); @@ -55,7 +54,7 @@ JavascriptLexer = function (_BaseLexer) {_inherits(JavascriptLexer, _BaseLexer); var matches = void 0; var containsVariable = false; var parts = []; - var quotationMark = string.charAt(0) === '"' ? '"' : '\''; + var quotationMark = string.charAt(0) === '"' ? '"' : "'"; var regex = new RegExp(JavascriptLexer.concatenatedSegmentPattern, 'gi'); while (matches = regex.exec(string)) { @@ -65,8 +64,7 @@ JavascriptLexer = function (_BaseLexer) {_inherits(JavascriptLexer, _BaseLexer); } } - var result = parts.reduce( - function (concatenatedString, x) { + var result = parts.reduce(function (concatenatedString, x) { x = x && x.trim(); if (_this2.validateString(x)) { concatenatedString += x.slice(1, -1); @@ -75,9 +73,7 @@ JavascriptLexer = function (_BaseLexer) {_inherits(JavascriptLexer, _BaseLexer); containsVariable = true; } return concatenatedString; - }, - ''); - + }, ''); if (!result || containsVariable) { return string; } else @@ -111,14 +107,6 @@ JavascriptLexer = function (_BaseLexer) {_inherits(JavascriptLexer, _BaseLexer); - - - - - - - - @@ -156,10 +144,7 @@ JavascriptLexer = function (_BaseLexer) {_inherits(JavascriptLexer, _BaseLexer); { var pattern = '(?:(\'|")?(' + - [ - 'context', - 'defaultValue']. - join('|') + + ['context', 'defaultValue'].join('|') + ')\\1)' + '(?:\\s*:\\s*)' + '(' + _baseLexer2.default.stringPattern + ')'; diff --git a/dist/parser.js b/dist/parser.js index 885a708d..ce4db201 100644 --- a/dist/parser.js +++ b/dist/parser.js @@ -18,13 +18,12 @@ var lexers = { var lexersMap = { - 'HandlebarsLexer': _handlebarsLexer2.default, - 'HTMLLexer': _htmlLexer2.default, - 'JavascriptLexer': _javascriptLexer2.default };var + HandlebarsLexer: _handlebarsLexer2.default, + HTMLLexer: _htmlLexer2.default, + JavascriptLexer: _javascriptLexer2.default };var Parser = function (_EventEmitter) {_inherits(Parser, _EventEmitter); - function Parser() {var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};_classCallCheck(this, Parser);var _this = _possibleConstructorReturn(this, (Parser.__proto__ || Object.getPrototypeOf(Parser)).call(this, options)); _this.lexers = _extends({}, lexers, options);return _this; @@ -48,7 +47,6 @@ Parser = function (_EventEmitter) {_inherits(Parser, _EventEmitter); lexerOptions = lexerConfig; } - if (!lexersMap[lexerName]) { _this2.emit('error', new Error('Lexer \'' + lexerName + '\' does not exist')); } diff --git a/src/helpers.js b/src/helpers.js index aab50686..9c43d04d 100644 --- a/src/helpers.js +++ b/src/helpers.js @@ -5,45 +5,47 @@ import _ from 'lodash' // The generated hash can be attached to an // optional `hash`. function dotPathToHash(path, separator = '.', value = '', target = {}) { - if (path.endsWith(separator)) { - path = path.slice( 0, -separator.length ) - } + if (path.endsWith(separator)) { + path = path.slice(0, -separator.length) + } - let result = {} - const segments = path.split(separator) + let result = {} + const segments = path.split(separator) - segments.reduce((hash, segment, index) => { - if (index === segments.length - 1) { - hash[segment] = value - } - else { - hash[segment] = {} - } - return hash[segment] - }, result) + segments.reduce((hash, segment, index) => { + if (index === segments.length - 1) { + hash[segment] = value + } + else { + hash[segment] = {} + } + return hash[segment] + }, result) - return _.merge(target, result) + return _.merge(target, result) } - // Takes a `source` hash and make sure its value // are pasted in the `target` hash, if the target // hash has the corresponding key (or if keepRemoved is true). // If not, the value is added to an `old` hash. function mergeHashes(source, target = {}, old, keepRemoved = false) { old = old || {} - Object.keys(source).forEach((key) => { - const hasNestedEntries = ( - typeof target[key] === 'object' && - !Array.isArray(target[key]) - ) + Object.keys(source).forEach(key => { + const hasNestedEntries = + typeof target[key] === 'object' && !Array.isArray(target[key]) if (hasNestedEntries) { - const nested = mergeHashes(source[key], target[key], old[key], keepRemoved) + const nested = mergeHashes( + source[key], + target[key], + old[key], + keepRemoved + ) target[key] = nested.new old[key] = nested.old } - else if ( target[key] !== undefined ) { + else if (target[key] !== undefined) { if (typeof source[key] === 'string' || Array.isArray(source[key])) { target[key] = source[key] } @@ -53,7 +55,7 @@ function mergeHashes(source, target = {}, old, keepRemoved = false) { } else { // support for plural in keys - const pluralMatch = /_plural(_\d+)?$/.test( key ) + const pluralMatch = /_plural(_\d+)?$/.test(key) const singularKey = key.replace(/_plural(_\d+)?$/, '') // support for context in keys @@ -76,16 +78,15 @@ function mergeHashes(source, target = {}, old, keepRemoved = false) { } }) - return {old, new: target} + return { old, new: target } } - // Takes a `target` hash and replace its empty // values with the `source` hash ones if they // exist function populateHash(source, target = {}) { - Object.keys(source).forEach((key) => { - if ( target[key] !== undefined ) { + Object.keys(source).forEach(key => { + if (target[key] !== undefined) { if (typeof source[key] === 'object') { target[key] = populateHash(source[key], target[key]) } @@ -99,7 +100,6 @@ function populateHash(source, target = {}) { } - export { dotPathToHash, mergeHashes, diff --git a/src/index.js b/src/index.js index e07498b0..f242f855 100644 --- a/src/index.js +++ b/src/index.js @@ -1,8 +1,4 @@ -import { - dotPathToHash, - mergeHashes, - populateHash -} from './helpers' +import { dotPathToHash, mergeHashes, populateHash } from './helpers' import { Transform } from 'stream' import _ from 'lodash' import eol from 'eol' @@ -13,7 +9,6 @@ import VirtualFile from 'vinyl' import YAML from 'yamljs' export default class i18nTransform extends Transform { - constructor(options = {}) { options.objectMode = true super(options) @@ -30,13 +25,13 @@ export default class i18nTransform extends Transform { keySeparator: '.', lexers: {}, lineEnding: 'auto', - locales: ['en','fr'], + locales: ['en', 'fr'], namespaceSeparator: ':', output: 'locales', sort: false } - this.options = {...this.defaults, ...options} + this.options = { ...this.defaults, ...options } this.entries = [] this.parser = new Parser(this.options.lexers) @@ -65,7 +60,6 @@ export default class i18nTransform extends Transform { let key = entry.key const parts = key.split(this.options.namespaceSeparator) - if (parts.length > 1) { entry.namespace = parts.shift() } @@ -104,10 +98,9 @@ export default class i18nTransform extends Transform { }) this.options.locales.forEach(locale => { - const outputPath = path.resolve(this.options.output, locale) - Object.keys(catalog).forEach((namespace) => { + Object.keys(catalog).forEach(namespace => { let filename = this.options.filename filename = filename.replace(this.localeRegex, locale) filename = filename.replace(this.namespaceRegex, namespace) @@ -126,9 +119,8 @@ export default class i18nTransform extends Transform { let existingCatalog = this.getCatalog(namespacePath) let oldCatalog = this.getCatalog(namespaceOldPath) - // merges existing translations with the new ones - const {new: newKeys, old: oldKeys} = mergeHashes( + const { new: newKeys, old: oldKeys } = mergeHashes( existingCatalog, catalog[namespace], null, @@ -143,7 +135,7 @@ export default class i18nTransform extends Transform { // push files back to the stream this.pushFile(namespacePath, newCatalog) - if ( this.options.createOldLibraries ) { + if (this.options.createOldLibraries) { this.pushFile(namespaceOldPath, oldCatalog) } }) @@ -158,7 +150,7 @@ export default class i18nTransform extends Transform { this.entries.push(entry) } else { - existing = {...existing, ...entry} + existing = { ...existing, ...entry } } if (entry.context) { @@ -194,11 +186,15 @@ export default class i18nTransform extends Transform { if (this.options.lineEnding === 'auto') { text = eol.auto(text) - } else if (lineEnding === '\r\n' || lineEnding === 'crlf') { + } + else if (lineEnding === '\r\n' || lineEnding === 'crlf') { text = eol.crlf(text) - } else if (lineEnding === '\r' || lineEnding === 'cr') { + } + else if (lineEnding === '\r' || lineEnding === 'cr') { text = eol.cr(text) - } else { // Defaults to LF, aka \n + } + else { + // Defaults to LF, aka \n text = eol.lf(text) } @@ -208,5 +204,4 @@ export default class i18nTransform extends Transform { }) this.push(file) } - } diff --git a/src/lexers/base-lexer.js b/src/lexers/base-lexer.js index 3efbfad0..42505e93 100644 --- a/src/lexers/base-lexer.js +++ b/src/lexers/base-lexer.js @@ -1,7 +1,6 @@ import EventEmitter from 'events' export default class BaseLexer extends EventEmitter { - constructor(options = {}) { super() this.keys = [] @@ -35,19 +34,19 @@ export default class BaseLexer extends EventEmitter { } functionPattern() { - return '(?:' + this.functions.join( '|' ).replace( '.', '\\.' ) + ')' + return '(?:' + this.functions.join('|').replace('.', '\\.') + ')' } static get singleQuotePattern() { - return "'(?:[^\'].*?[^\\\\])?'" + return "'(?:[^'].*?[^\\\\])?'" } static get doubleQuotePattern() { - return '"(?:[^\"].*?[^\\\\])?"' + return '"(?:[^"].*?[^\\\\])?"' } static get backQuotePattern() { - return '`(?:[^\`].*?[^\\\\])?`' + return '`(?:[^`].*?[^\\\\])?`' } static get variablePattern() { diff --git a/src/lexers/handlebars-lexer.js b/src/lexers/handlebars-lexer.js index 81545af0..1199b7b4 100644 --- a/src/lexers/handlebars-lexer.js +++ b/src/lexers/handlebars-lexer.js @@ -1,7 +1,6 @@ import BaseLexer from './base-lexer' export default class HandlebarsLexer extends BaseLexer { - constructor(options = {}) { super(options) diff --git a/src/lexers/html-lexer.js b/src/lexers/html-lexer.js index 42c6a3ff..57115e11 100644 --- a/src/lexers/html-lexer.js +++ b/src/lexers/html-lexer.js @@ -1,7 +1,6 @@ import BaseLexer from './base-lexer' export default class HTMLLexer extends BaseLexer { - constructor(options = {}) { super(options) @@ -27,7 +26,7 @@ export default class HTMLLexer extends BaseLexer { const keys = attrs.keys.split(';') keys.forEach(key => { // remove any leading [] in the key - key = key.replace(/^\[[a-zA-Z0-9_-]*\]/ , '') + key = key.replace(/^\[[a-zA-Z0-9_-]*\]/, '') // if empty grab innerHTML from regex key = key || matches[3] @@ -36,7 +35,6 @@ export default class HTMLLexer extends BaseLexer { this.keys.push({ ...attrs.options, key }) } }) - } return this.keys @@ -55,7 +53,7 @@ export default class HTMLLexer extends BaseLexer { } parseAttributes(args) { - const result = {keys: '', options: {}} + const result = { keys: '', options: {} } this.attrRegex.lastIndex = 0 let keysMatch = this.attrRegex.exec(args) if (keysMatch && keysMatch[1]) { diff --git a/src/lexers/javascript-lexer.js b/src/lexers/javascript-lexer.js index a369ea55..b5d544bb 100644 --- a/src/lexers/javascript-lexer.js +++ b/src/lexers/javascript-lexer.js @@ -1,7 +1,6 @@ import BaseLexer from './base-lexer' export default class JavascriptLexer extends BaseLexer { - constructor(options = {}) { super(options) @@ -55,7 +54,7 @@ export default class JavascriptLexer extends BaseLexer { let matches let containsVariable = false const parts = [] - const quotationMark = string.charAt(0) === '"' ? '"' : '\'' + const quotationMark = string.charAt(0) === '"' ? '"' : "'" const regex = new RegExp(JavascriptLexer.concatenatedSegmentPattern, 'gi') while(matches = regex.exec(string)) { @@ -65,19 +64,16 @@ export default class JavascriptLexer extends BaseLexer { } } - const result = parts.reduce( - (concatenatedString, x) => { - x = x && x.trim() - if (this.validateString(x)) { - concatenatedString += x.slice(1, -1) - } - else { - containsVariable = true - } - return concatenatedString - }, - '' - ) + const result = parts.reduce((concatenatedString, x) => { + x = x && x.trim() + if (this.validateString(x)) { + concatenatedString += x.slice(1, -1) + } + else { + containsVariable = true + } + return concatenatedString + }, '') if (!result || containsVariable) { return string } @@ -87,25 +83,17 @@ export default class JavascriptLexer extends BaseLexer { } static get concatenatedSegmentPattern() { - return ( - [ - BaseLexer.singleQuotePattern, - BaseLexer.doubleQuotePattern, - BaseLexer.backQuotePattern, - BaseLexer.variablePattern, - '(?:\\s*\\+\\s*)' // support for concatenation via + - ].join('|') - ) + return [ + BaseLexer.singleQuotePattern, + BaseLexer.doubleQuotePattern, + BaseLexer.backQuotePattern, + BaseLexer.variablePattern, + '(?:\\s*\\+\\s*)' // support for concatenation via + + ].join('|') } static get concatenatedArgumentPattern() { - return ( - '(' + - '(?:' + - JavascriptLexer.concatenatedSegmentPattern + - ')+' + - ')' - ) + return '(' + '(?:' + JavascriptLexer.concatenatedSegmentPattern + ')+' + ')' } static get hashPattern() { @@ -119,7 +107,7 @@ export default class JavascriptLexer extends BaseLexer { '(?:' + [ JavascriptLexer.concatenatedArgumentPattern, - JavascriptLexer.hashPattern, + JavascriptLexer.hashPattern ].join('|') + ')' + '(?:\\s*,\\s*)?' + @@ -144,7 +132,7 @@ export default class JavascriptLexer extends BaseLexer { '(' + [ JavascriptLexer.concatenatedArgumentPattern, - JavascriptLexer.hashPattern, + JavascriptLexer.hashPattern ].join('|') + ')' + '(?:\\s*,\\s*)?' @@ -156,10 +144,7 @@ export default class JavascriptLexer extends BaseLexer { createHashRegex() { const pattern = ( '(?:(\'|")?(' + - [ - 'context', - 'defaultValue' - ].join('|') + + ['context', 'defaultValue'].join('|') + ')\\1)' + '(?:\\s*:\\s*)' + '(' + BaseLexer.stringPattern + ')' diff --git a/src/parser.js b/src/parser.js index 82bae211..4ff1ce21 100644 --- a/src/parser.js +++ b/src/parser.js @@ -18,16 +18,15 @@ const lexers = { } const lexersMap = { - 'HandlebarsLexer': HandlebarsLexer, - 'HTMLLexer': HTMLLexer, - 'JavascriptLexer': JavascriptLexer + HandlebarsLexer, + HTMLLexer, + JavascriptLexer } export default class Parser extends EventEmitter { - constructor(options = {}) { super(options) - this.lexers = {...lexers, ...options} + this.lexers = { ...lexers, ...options } } parse(content, extension) { @@ -48,7 +47,6 @@ export default class Parser extends EventEmitter { lexerOptions = lexerConfig } - if (!lexersMap[lexerName]) { this.emit('error', new Error(`Lexer '${lexerName}' does not exist`)) } @@ -60,5 +58,4 @@ export default class Parser extends EventEmitter { return keys } - } diff --git a/test/lexers/base-lexer.test.js b/test/lexers/base-lexer.test.js index 9f319007..7476b6e0 100644 --- a/test/lexers/base-lexer.test.js +++ b/test/lexers/base-lexer.test.js @@ -1,65 +1,47 @@ import { assert } from 'chai' import BaseLexer from '../../src/lexers/base-lexer' -describe('BaseLexer', function () { - it('functionPattern() return a regex pattern', function (done) { - const Lexer = new BaseLexer({functions: ['this.t', '__']}) - assert.equal( Lexer.functionPattern(), '(?:this\\.t|__)' ) +describe('BaseLexer', () => { + it('functionPattern() return a regex pattern', (done) => { + const Lexer = new BaseLexer({ functions: ['this.t', '__'] }) + assert.equal(Lexer.functionPattern(), '(?:this\\.t|__)') done() }) - describe('validateString()', function () { - it('matches double quote strings', function (done) { + describe('validateString()', () => { + it('matches double quote strings', (done) => { const Lexer = new BaseLexer() - assert.equal( - Lexer.validateString('"args"'), - true - ) + assert.equal(Lexer.validateString('"args"'), true) done() }) - it('matches single quote strings', function (done) { + it('matches single quote strings', (done) => { const Lexer = new BaseLexer() - assert.equal( - Lexer.validateString("'args'"), - true - ) + assert.equal(Lexer.validateString("'args'"), true) done() }) - it('does not match variables', function (done) { + it('does not match variables', (done) => { const Lexer = new BaseLexer() - assert.equal( - Lexer.validateString('args'), - false - ) + assert.equal(Lexer.validateString('args'), false) done() }) - it('does not match null value', function (done) { + it('does not match null value', (done) => { const Lexer = new BaseLexer() - assert.equal( - Lexer.validateString(null), - false - ) + assert.equal(Lexer.validateString(null), false) done() }) - it('does not match undefined value', function (done) { + it('does not match undefined value', (done) => { const Lexer = new BaseLexer() - assert.equal( - Lexer.validateString(undefined), - false - ) + assert.equal(Lexer.validateString(undefined), false) done() }) - it('does not match empty string', function (done) { + it('does not match empty string', (done) => { const Lexer = new BaseLexer() - assert.equal( - Lexer.validateString(''), - false - ) + assert.equal(Lexer.validateString(''), false) done() }) }) diff --git a/test/lexers/handlebars-lexer.test.js b/test/lexers/handlebars-lexer.test.js index 14a1625d..14839f45 100644 --- a/test/lexers/handlebars-lexer.test.js +++ b/test/lexers/handlebars-lexer.test.js @@ -1,168 +1,119 @@ import { assert } from 'chai' import HandlebarsLexer from '../../src/lexers/handlebars-lexer' -describe('HandlebarsLexer', function () { - it('extracts keys from translation components', function (done) { +describe('HandlebarsLexer', () => { + it('extracts keys from translation components', (done) => { const Lexer = new HandlebarsLexer() const content = '

{{t "first"}}

' - assert.deepEqual( - Lexer.extract(content), - [ - { key: 'first' } - ] - ) + assert.deepEqual(Lexer.extract(content), [{ key: 'first' }]) done() }) - it('extracts the second argument as defaultValue', function (done) { + it('extracts the second argument as defaultValue', (done) => { const Lexer = new HandlebarsLexer() const content = '

{{t "first" "bla"}}

' - assert.deepEqual( - Lexer.extract(content), - [ - { key: 'first', defaultValue: 'bla' } - ] - ) + assert.deepEqual(Lexer.extract(content), [ + { key: 'first', defaultValue: 'bla' } + ]) done() }) - it('extracts the defaultValue arguments', function (done) { + it('extracts the defaultValue arguments', (done) => { const Lexer = new HandlebarsLexer() const content = '

{{t "first" defaultValue="bla"}}

' - assert.deepEqual( - Lexer.extract(content), - [ - { key: 'first', defaultValue: 'bla' } - ] - ) + assert.deepEqual(Lexer.extract(content), [ + { key: 'first', defaultValue: 'bla' } + ]) done() }) - it('extracts the context arguments', function (done) { + it('extracts the context arguments', (done) => { const Lexer = new HandlebarsLexer() const content = '

{{t "first" context="bla"}}

' - assert.deepEqual( - Lexer.extract(content), - [ - { key: 'first', context: 'bla' } - ] - ) + assert.deepEqual(Lexer.extract(content), [{ key: 'first', context: 'bla' }]) done() }) - it('extracts keys from translation functions', function (done) { + it('extracts keys from translation functions', (done) => { const Lexer = new HandlebarsLexer() const content = '

{{link-to (t "first") "foo"}}

' - assert.deepEqual( - Lexer.extract(content), - [ - { key: 'first' } - ] - ) + assert.deepEqual(Lexer.extract(content), [{ key: 'first' }]) done() }) - it('supports a `functions` option', function (done) { - const Lexer = new HandlebarsLexer({functions: ['tt', '_e']}) + it('supports a `functions` option', (done) => { + const Lexer = new HandlebarsLexer({ functions: ['tt', '_e'] }) const content = '

{{link-to (tt "first") "foo"}}: {{_e "second"}}

' - assert.deepEqual( - Lexer.extract(content), - [ - { key: 'first' }, - { key: 'second' } - ] - ) + assert.deepEqual(Lexer.extract(content), [ + { key: 'first' }, + { key: 'second' } + ]) done() }) - describe('parseArguments()', function () { - it('matches string arguments', function (done) { + describe('parseArguments()', () => { + it('matches string arguments', (done) => { const Lexer = new HandlebarsLexer() const args = '"first" "bla"' - assert.deepEqual( - Lexer.parseArguments(args), - { - arguments: [ - '"first"', - '"bla"' - ], - options: {} - } - ) + assert.deepEqual(Lexer.parseArguments(args), { + arguments: ['"first"', '"bla"'], + options: {} + }) done() }) - it('matches variable arguments', function (done) { + it('matches variable arguments', (done) => { const Lexer = new HandlebarsLexer() const args = 'first bla' - assert.deepEqual( - Lexer.parseArguments(args), - { - arguments: [ - 'first', - 'bla' - ], - options: {} - } - ) + assert.deepEqual(Lexer.parseArguments(args), { + arguments: ['first', 'bla'], + options: {} + }) done() }) - it('matches key-value arguments', function (done) { + it('matches key-value arguments', (done) => { const Lexer = new HandlebarsLexer() const args = 'first="bla"' - assert.deepEqual( - Lexer.parseArguments(args), - { - arguments: [ - 'first="bla"' - ], - options: { - first: 'bla' - } + assert.deepEqual(Lexer.parseArguments(args), { + arguments: ['first="bla"'], + options: { + first: 'bla' } - ) + }) done() }) - it('skips key-value arguments that are variables', function (done) { + it('skips key-value arguments that are variables', (done) => { const Lexer = new HandlebarsLexer() const args = 'second=bla' - assert.deepEqual( - Lexer.parseArguments(args), - { - arguments: [ - 'second=bla' - ], - options: { - // empty! - } + assert.deepEqual(Lexer.parseArguments(args), { + arguments: ['second=bla'], + options: { + // empty! } - ) + }) done() }) - it('matches combinations', function (done) { + it('matches combinations', (done) => { const Lexer = new HandlebarsLexer() - const args = '"first" second third-one="bla bla" fourth fifth=\'bla\' "sixth"' - assert.deepEqual( - Lexer.parseArguments(args), - { - arguments: [ - '"first"', - 'second', - 'third-one="bla bla"', - 'fourth', - 'fifth=\'bla\'', - '"sixth"' - ], - options: { - 'third-one': 'bla bla', - 'fifth': 'bla' - } + const args = + '"first" second third-one="bla bla" fourth fifth=\'bla\' "sixth"' + assert.deepEqual(Lexer.parseArguments(args), { + arguments: [ + '"first"', + 'second', + 'third-one="bla bla"', + 'fourth', + "fifth='bla'", + '"sixth"' + ], + options: { + 'third-one': 'bla bla', + fifth: 'bla' } - - ) + }) done() }) }) diff --git a/test/lexers/html-lexer.test.js b/test/lexers/html-lexer.test.js index 5fd84b4d..7c7ddbc8 100644 --- a/test/lexers/html-lexer.test.js +++ b/test/lexers/html-lexer.test.js @@ -1,110 +1,86 @@ import { assert } from 'chai' import HTMLLexer from '../../src/lexers/html-lexer' -describe('HTMLLexer', function () { - it('extracts keys from html attributes', function (done) { +describe('HTMLLexer', () => { + it('extracts keys from html attributes', (done) => { const Lexer = new HTMLLexer() const content = '

' - assert.deepEqual( - Lexer.extract(content), - [ - { key: 'first' }, - { key: 'second' } - ] - ) + assert.deepEqual(Lexer.extract(content), [ + { key: 'first' }, + { key: 'second' } + ]) done() }) - it('ignores leading [] of the key', function (done) { + it('ignores leading [] of the key', (done) => { const Lexer = new HTMLLexer() const content = '

' - assert.deepEqual( - Lexer.extract(content), - [ - { key: 'first' }, - { key: 'second' } - ] - ) + assert.deepEqual(Lexer.extract(content), [ + { key: 'first' }, + { key: 'second' } + ]) done() }) - it('supports the defaultValue option', function (done) { + it('supports the defaultValue option', (done) => { const Lexer = new HTMLLexer() - const content = '

first

' - assert.deepEqual( - Lexer.extract(content), - [ - { key: 'first', defaultValue: 'bla' } - ] - ) + const content = + '

first

' + assert.deepEqual(Lexer.extract(content), [ + { key: 'first', defaultValue: 'bla' } + ]) done() }) - it('grabs the default from innerHTML if missing', function (done) { + it('grabs the default from innerHTML if missing', (done) => { const Lexer = new HTMLLexer() const content = '

first

' - assert.deepEqual( - Lexer.extract(content), - [ - { key: 'first' } - ] - ) + assert.deepEqual(Lexer.extract(content), [{ key: 'first' }]) done() }) - it('supports multiline', function (done) { + it('supports multiline', (done) => { const Lexer = new HTMLLexer() const content = '

Fourth

' + '

' - assert.deepEqual( - Lexer.extract(content), - [ - { key: 'third' }, - { key: 'fourth' }, - { key: 'first', defaultValue: 'bar' } - ] - ) + assert.deepEqual(Lexer.extract(content), [ + { key: 'third' }, + { key: 'fourth' }, + { key: 'first', defaultValue: 'bar' } + ]) done() }) - it('skip if no key is found', function (done) { + it('skip if no key is found', (done) => { const Lexer = new HTMLLexer() const content = '

' - assert.deepEqual( - Lexer.extract(content), - [] - ) + assert.deepEqual(Lexer.extract(content), []) done() }) - it('supports a `attr` option', function (done) { - const Lexer = new HTMLLexer({attr: 'data-other'}) + it('supports a `attr` option', (done) => { + const Lexer = new HTMLLexer({ attr: 'data-other' }) const content = '

' - assert.deepEqual( - Lexer.extract(content), - [ - { key: 'first' }, - { key: 'second' } - ] - ) + assert.deepEqual(Lexer.extract(content), [ + { key: 'first' }, + { key: 'second' } + ]) done() }) - it('supports a `optionAttr` option', function (done) { - const Lexer = new HTMLLexer({optionAttr: 'data-other-options'}) - const content = '

' - assert.deepEqual( - Lexer.extract(content), - [ - { key: 'first', defaultValue: 'bar' } - ] - ) + it('supports a `optionAttr` option', (done) => { + const Lexer = new HTMLLexer({ optionAttr: 'data-other-options' }) + const content = + '

' + assert.deepEqual(Lexer.extract(content), [ + { key: 'first', defaultValue: 'bar' } + ]) done() }) - describe('parseAttributes()', function () { - it('extracts attribute value from string', function (done) { + describe('parseAttributes()', () => { + it('extracts attribute value from string', (done) => { const Lexer = new HTMLLexer() assert.deepEqual( Lexer.parseAttributes('title="" bla data-i18n="key1"', 'data-i18n'), @@ -116,10 +92,13 @@ describe('HTMLLexer', function () { done() }) - it('extracts json strings too', function (done) { + it('extracts json strings too', (done) => { const Lexer = new HTMLLexer() assert.deepEqual( - Lexer.parseAttributes('data-i18n="key1;key2" data-i18n-options=\'{"defaultValue": "bla"}\'', 'data-i18n-options'), + Lexer.parseAttributes( + 'data-i18n="key1;key2" data-i18n-options=\'{"defaultValue": "bla"}\'', + 'data-i18n-options' + ), { keys: 'key1;key2', options: { @@ -130,10 +109,12 @@ describe('HTMLLexer', function () { done() }) - it('supports multiline', function (done) { + it('supports multiline', (done) => { const Lexer = new HTMLLexer() assert.deepEqual( - Lexer.parseAttributes('title=""\n bla\n data-i18n="first"\n data-i18n-options=\'{"defaultValue": "bar"}\''), + Lexer.parseAttributes( + 'title=""\n bla\n data-i18n="first"\n data-i18n-options=\'{"defaultValue": "bar"}\'' + ), { keys: 'first', options: { diff --git a/test/lexers/javascript-lexer.test.js b/test/lexers/javascript-lexer.test.js index a925e32b..c6e6333c 100644 --- a/test/lexers/javascript-lexer.test.js +++ b/test/lexers/javascript-lexer.test.js @@ -1,180 +1,124 @@ import { assert } from 'chai' import JavascriptLexer from '../../src/lexers/javascript-lexer' -describe('JavascriptLexer', function () { - it('extracts keys from translation components', function (done) { +describe('JavascriptLexer', () => { + it('extracts keys from translation components', (done) => { const Lexer = new JavascriptLexer() const content = 'i18n.t("first")' - assert.deepEqual( - Lexer.extract(content), - [ - { key: 'first' } - ] - ) + assert.deepEqual(Lexer.extract(content), [{ key: 'first' }]) done() }) - it('extracts the second argument as defaultValue', function (done) { + it('extracts the second argument as defaultValue', (done) => { const Lexer = new JavascriptLexer() const content = 'i18n.t("first" "bla")' - assert.deepEqual( - Lexer.extract(content), - [ - { key: 'first', defaultValue: 'bla' } - ] - ) + assert.deepEqual(Lexer.extract(content), [ + { key: 'first', defaultValue: 'bla' } + ]) done() }) - it('extracts the defaultValue/context options', function (done) { + it('extracts the defaultValue/context options', (done) => { const Lexer = new JavascriptLexer() const content = 'i18n.t("first", {defaultValue: "foo", context: \'bar\'})' - assert.deepEqual( - Lexer.extract(content), - [ - { key: 'first', defaultValue: 'foo', context: 'bar' } - ] - ) + assert.deepEqual(Lexer.extract(content), [ + { key: 'first', defaultValue: 'foo', context: 'bar' } + ]) done() }) - it('extracts the defaultValue/context options with quotation marks', function (done) { + it('extracts the defaultValue/context options with quotation marks', (done) => { const Lexer = new JavascriptLexer() const content = 'i18n.t("first", {context: "foo", "defaultValue": \'bla\'})' - assert.deepEqual( - Lexer.extract(content), - [ - { key: 'first', defaultValue: 'bla', context: 'foo' } - ] - ) + assert.deepEqual(Lexer.extract(content), [ + { key: 'first', defaultValue: 'bla', context: 'foo' } + ]) done() }) - it('supports multiline and concatenation', function (done) { + it('supports multiline and concatenation', (done) => { const Lexer = new JavascriptLexer() const content = 'i18n.t("foo" + \n "bar")' - assert.deepEqual( - Lexer.extract(content), - [ - { key: 'foobar' } - ] - ) + assert.deepEqual(Lexer.extract(content), [{ key: 'foobar' }]) done() }) - it('does not parse text with `doesn\'t` or isolated `t` in it', function (done) { + it("does not parse text with `doesn't` or isolated `t` in it", (done) => { const Lexer = new JavascriptLexer() - const js = "// FIX this doesn't work and this t is all alone\nt('first')\nt = function() {}" - assert.deepEqual( - Lexer.extract(js), - [ - { key: 'first' } - ] - ) + const js = "// FIX this doesn't work and this t is all alone\nt('first')\nt = () => {}" + assert.deepEqual(Lexer.extract(js), [{ key: 'first' }]) done() }) - it('ignores functions that ends with a t', function (done) { + it('ignores functions that ends with a t', (done) => { const Lexer = new JavascriptLexer() - const js = 'import \'./yolo.js\' t(\'first\')' - assert.deepEqual( - Lexer.extract(js), - [ - { key: 'first' } - ] - ) + const js = "import './yolo.js' t('first')" + assert.deepEqual(Lexer.extract(js), [{ key: 'first' }]) done() }) - it('supports a `functions` option', function (done) { - const Lexer = new JavascriptLexer({functions: ['tt', '_e']}) + it('supports a `functions` option', (done) => { + const Lexer = new JavascriptLexer({ functions: ['tt', '_e'] }) const content = 'tt("first") + _e("second")' - assert.deepEqual( - Lexer.extract(content), - [ - { key: 'first' }, - { key: 'second' } - ] - ) + assert.deepEqual(Lexer.extract(content), [ + { key: 'first' }, + { key: 'second' } + ]) done() }) - describe('concatenateString()', function () { - it('concatenates strings', function (done) { + describe('concatenateString()', () => { + it('concatenates strings', (done) => { const Lexer = new JavascriptLexer() - assert.equal( - Lexer.concatenateString('"foo" + \'bar\''), - '"foobar"' - ) + assert.equal(Lexer.concatenateString('"foo" + \'bar\''), '"foobar"') done() }) - it('returns the original string if it contains variables', function (done) { + it('returns the original string if it contains variables', (done) => { const Lexer = new JavascriptLexer() - assert.equal( - Lexer.concatenateString('"foo" + bar'), - '"foo" + bar' - ) + assert.equal(Lexer.concatenateString('"foo" + bar'), '"foo" + bar') done() }) - it('returns the original string if it contains backquote string', function (done) { + it('returns the original string if it contains backquote string', (done) => { const Lexer = new JavascriptLexer() - assert.equal( - Lexer.concatenateString('"foo" + `bar`'), - '"foo" + `bar`' - ) + assert.equal(Lexer.concatenateString('"foo" + `bar`'), '"foo" + `bar`') done() }) }) - describe('parseArguments()', function () { - it('matches string arguments', function (done) { + describe('parseArguments()', () => { + it('matches string arguments', (done) => { const Lexer = new JavascriptLexer() const args = '"first", "bla"' - assert.deepEqual( - Lexer.parseArguments(args), - { - arguments: [ - '"first"', - '"bla"' - ], - options: {} - } - ) + assert.deepEqual(Lexer.parseArguments(args), { + arguments: ['"first"', '"bla"'], + options: {} + }) done() }) - it('matches variable arguments', function (done) { + it('matches variable arguments', (done) => { const Lexer = new JavascriptLexer() const args = 'first bla' - assert.deepEqual( - Lexer.parseArguments(args), - { - arguments: [ - 'first', - 'bla' - ], - options: {} - } - ) + assert.deepEqual(Lexer.parseArguments(args), { + arguments: ['first', 'bla'], + options: {} + }) done() }) - it('matches concatenated arguments and concatenate when possible', function (done) { + it('matches concatenated arguments and concatenate when possible', (done) => { const Lexer = new JavascriptLexer() const args = "'first' + asd, 'bla' + 'asd', foo+bar+baz" - assert.deepEqual( - Lexer.parseArguments(args), - { - arguments: [ - "'first' + asd", - "'blaasd'", // string got concatenated! - "foo+bar+baz" - ], - options: {} - } - ) + assert.deepEqual(Lexer.parseArguments(args), { + arguments: [ + "'first' + asd", + "'blaasd'", // string got concatenated! + 'foo+bar+baz' + ], + options: {} + }) done() }) }) diff --git a/test/parser.test.js b/test/parser.test.js index 88b093a6..7a17ef08 100644 --- a/test/parser.test.js +++ b/test/parser.test.js @@ -4,54 +4,52 @@ import fs from 'fs' import i18nTransform from '../src/index' import path from 'path' -describe('parser', function () { - it('parses globally on multiple lines', function (done) { +describe('parser', () => { + it('parses globally on multiple lines', done => { let result const i18nextParser = new i18nTransform() const fakeFile = new Vinyl({ - contents: Buffer.from("asd t('first') t('second') \n asd t('third') ad t('fourth')"), + contents: Buffer.from( + "asd t('first') t('second') \n asd t('third') ad t('fourth')" + ), path: 'file.js' }) - i18nextParser.once('data', (file) => { + i18nextParser.once('data', file => { if (file.relative.endsWith('en/translation.json')) { result = JSON.parse(file.contents) } }) - i18nextParser.on('end', function () { - assert.deepEqual( - result, - { first: '', second: '', third: '', fourth: '' } - ) + i18nextParser.on('end', () => { + assert.deepEqual(result, { first: '', second: '', third: '', fourth: '' }) done() }) i18nextParser.end(fakeFile) }) - it('parses multiline function calls', function (done) { + it('parses multiline function calls', done => { let result const i18nextParser = new i18nTransform() const fakeFile = new Vinyl({ - contents: Buffer.from("asd t(\n 'first'\n) t('second') \n asd t(\n\n'third')"), + contents: Buffer.from( + "asd t(\n 'first'\n) t('second') \n asd t(\n\n'third')" + ), path: 'file.js' }) - i18nextParser.on('data', (file) => { + i18nextParser.on('data', file => { if (file.relative.endsWith('en/translation.json')) { result = JSON.parse(file.contents) } }) - i18nextParser.on('end', function () { - assert.deepEqual( - result, - { first: '', second: '', third: '' } - ) + i18nextParser.on('end', () => { + assert.deepEqual(result, { first: '', second: '', third: '' }) done() }) i18nextParser.end(fakeFile) }) - it('creates context keys', function (done) { + it('creates context keys', done => { let result const i18nextParser = new i18nTransform() const fakeFile = new Vinyl({ @@ -59,7 +57,7 @@ describe('parser', function () { path: 'file.js' }) - i18nextParser.on('data', (file) => { + i18nextParser.on('data', file => { if (file.relative.endsWith('en/translation.json')) { result = JSON.parse(file.contents) } @@ -75,11 +73,13 @@ describe('parser', function () { i18nextParser.end(fakeFile) }) - it('parses html files', function (done) { + it('parses html files', done => { let result const i18nextParser = new i18nTransform() const fakeFile = new Vinyl({ - contents: fs.readFileSync( path.resolve(__dirname, 'templating/html.html') ), + contents: fs.readFileSync( + path.resolve(__dirname, 'templating/html.html') + ), path: 'file.html' }) const expected = { @@ -91,23 +91,25 @@ describe('parser', function () { sixth: '' } - i18nextParser.on('data', (file) => { + i18nextParser.on('data', file => { if (file.relative.endsWith('en/translation.json')) { result = JSON.parse(file.contents) } }) - i18nextParser.on('end', function () { + i18nextParser.on('end', () => { assert.deepEqual(result, expected) done() }) i18nextParser.end(fakeFile) }) - it('parses handlebars files', function (done) { + it('parses handlebars files', done => { let result const i18nextParser = new i18nTransform() const fakeFile = new Vinyl({ - contents: fs.readFileSync( path.resolve(__dirname, 'templating/handlebars.hbs') ), + contents: fs.readFileSync( + path.resolve(__dirname, 'templating/handlebars.hbs') + ), path: 'file.hbs' }) const expected = { @@ -124,12 +126,12 @@ describe('parser', function () { seventh: 'defaultValue' } - i18nextParser.on('data', (file) => { + i18nextParser.on('data', file => { if (file.relative.endsWith('en/translation.json')) { result = JSON.parse(file.contents) } }) - i18nextParser.on('end', function () { + i18nextParser.on('end', () => { assert.deepEqual(result, expected) done() }) @@ -137,11 +139,13 @@ describe('parser', function () { i18nextParser.end(fakeFile) }) - it('parses javascript files', function (done) { + it('parses javascript files', done => { let result const i18nextParser = new i18nTransform() const fakeFile = new Vinyl({ - contents: fs.readFileSync( path.resolve(__dirname, 'templating/javascript.js') ), + contents: fs.readFileSync( + path.resolve(__dirname, 'templating/javascript.js') + ), path: 'file.js' }) const expected = { @@ -151,12 +155,12 @@ describe('parser', function () { fourth: '' } - i18nextParser.on('data', (file) => { + i18nextParser.on('data', file => { if (file.relative.endsWith('en/translation.json')) { result = JSON.parse(file.contents) } }) - i18nextParser.on('end', function () { + i18nextParser.on('end', () => { assert.deepEqual(result, expected) done() }) @@ -164,91 +168,113 @@ describe('parser', function () { i18nextParser.end(fakeFile) }) - it('creates two files per namespace and per locale', function (done) { + it('creates two files per namespace and per locale', done => { let results = [] const i18nextParser = new i18nTransform({ locales: ['en', 'de', 'fr'], defaultNamespace: 'default' }) const fakeFile = new Vinyl({ - contents: Buffer.from("asd t('ns1:first') t('second') \n asd t('ns2:third') ad t('fourth')"), + contents: Buffer.from( + "asd t('ns1:first') t('second') \n asd t('ns2:third') ad t('fourth')" + ), path: 'file.js' }) - i18nextParser.on('data', (file) => { + i18nextParser.on('data', file => { results.push(file.relative.replace('locales/', '')) }) - i18nextParser.on('end', function () { - + i18nextParser.on('end', () => { const expectedFiles = [ - 'en/default.json', 'en/default_old.json', 'en/ns1.json', 'en/ns1_old.json', 'en/ns2.json', 'en/ns2_old.json', - 'de/default.json', 'de/default_old.json', 'de/ns1.json', 'de/ns1_old.json', 'de/ns2.json', 'de/ns2_old.json', - 'fr/default.json', 'fr/default_old.json', 'fr/ns1.json', 'fr/ns1_old.json', 'fr/ns2.json', 'fr/ns2_old.json' + 'en/default.json', + 'en/default_old.json', + 'en/ns1.json', + 'en/ns1_old.json', + 'en/ns2.json', + 'en/ns2_old.json', + 'de/default.json', + 'de/default_old.json', + 'de/ns1.json', + 'de/ns1_old.json', + 'de/ns2.json', + 'de/ns2_old.json', + 'fr/default.json', + 'fr/default_old.json', + 'fr/ns1.json', + 'fr/ns1_old.json', + 'fr/ns2.json', + 'fr/ns2_old.json' ] let length = expectedFiles.length - expectedFiles.forEach((filename) => { + expectedFiles.forEach(filename => { assert.include(results, filename) - if( ! --length ) done() + if (!--length) done() }) }) i18nextParser.end(fakeFile) }) - it('handles escaped single and double quotes', function (done) { + it('handles escaped single and double quotes', done => { let result const i18nextParser = new i18nTransform() const fakeFile = new Vinyl({ - contents: Buffer.from("asd t('escaped \\'single quotes\\'') t(\"escaped \\\"double quotes\\\"\")"), + contents: Buffer.from( + 'asd t(\'escaped \\\'single quotes\\\'\') t("escaped \\"double quotes\\"")' + ), path: 'file.js' }) - i18nextParser.on('data', (file) => { + i18nextParser.on('data', file => { if (file.relative.endsWith('en/translation.json')) { result = JSON.parse(file.contents) } }) i18nextParser.once('end', () => { const keys = Object.keys(result) - assert.equal( keys[0], "escaped 'single quotes'" ) - assert.equal( keys[1], 'escaped "double quotes"' ) + assert.equal(keys[0], "escaped 'single quotes'") + assert.equal(keys[1], 'escaped "double quotes"') done() }) i18nextParser.end(fakeFile) }) - it('handles escaped characters', function (done) { + it('handles escaped characters', done => { let result const i18nextParser = new i18nTransform() const fakeFile = new Vinyl({ - contents: Buffer.from("asd t('escaped backslash\\\\ newline\\n\\r tab\\t')"), + contents: Buffer.from( + "asd t('escaped backslash\\\\ newline\\n\\r tab\\t')" + ), path: 'file.js' }) - i18nextParser.on('data', (file) => { + i18nextParser.on('data', file => { if (file.relative.endsWith('en/translation.json')) { result = JSON.parse(file.contents) } }) i18nextParser.once('end', () => { const keys = Object.keys(result) - assert.equal( keys[0], 'escaped backslash\\ newline\n\r tab\t' ) + assert.equal(keys[0], 'escaped backslash\\ newline\n\r tab\t') done() }) i18nextParser.end(fakeFile) }) - it('returns buffers', function (done) { + it('returns buffers', done => { const i18nextParser = new i18nTransform() const fakeFile = new Vinyl({ - contents: Buffer.from("asd t('first') t('second') \n asd t('third') ad t('fourth')"), + contents: Buffer.from( + "asd t('first') t('second') \n asd t('third') ad t('fourth')" + ), path: 'file.js' }) - i18nextParser.once('data', (file) => { + i18nextParser.once('data', file => { assert(file.isBuffer()) done() }) @@ -256,37 +282,37 @@ describe('parser', function () { i18nextParser.end(fakeFile) }) - it('retrieves values in existing catalog', function (done) { + it('retrieves values in existing catalog', done => { let result - const i18nextParser = new i18nTransform({output: 'test/locales'}) + const i18nextParser = new i18nTransform({ output: 'test/locales' }) const fakeFile = new Vinyl({ contents: Buffer.from("asd t('test_merge:first') t('test_merge:second')"), path: 'file.js' }) - i18nextParser.on('data', (file) => { + i18nextParser.on('data', file => { if (file.relative.endsWith('en/test_merge.json')) { result = JSON.parse(file.contents) } }) i18nextParser.once('end', () => { - assert.deepEqual( result, { first: 'first', second: '' } ) + assert.deepEqual(result, { first: 'first', second: '' }) done() }) i18nextParser.end(fakeFile) }) - it('does not leak values between locales', function (done) { + it('does not leak values between locales', done => { let resultEN let resultFR - const i18nextParser = new i18nTransform({output: 'test/locales'}) + const i18nextParser = new i18nTransform({ output: 'test/locales' }) const fakeFile = new Vinyl({ contents: Buffer.from("asd t('test_leak:first') t('test_leak:second')"), path: 'file.js' }) - i18nextParser.on('data', (file) => { + i18nextParser.on('data', file => { if (file.relative.endsWith('en/test_leak.json')) { resultEN = JSON.parse(file.contents) } @@ -295,17 +321,17 @@ describe('parser', function () { } }) i18nextParser.once('end', () => { - assert.deepEqual( resultEN, { first: 'first', second: 'second' } ) - assert.deepEqual( resultFR, { first: 'premier', second: '' } ) + assert.deepEqual(resultEN, { first: 'first', second: 'second' }) + assert.deepEqual(resultFR, { first: 'premier', second: '' }) done() }) i18nextParser.end(fakeFile) }) - it('retrieves context values in existing catalog', function (done) { + it('retrieves context values in existing catalog', done => { let result - const i18nextParser = new i18nTransform({output: 'test/locales'}) + const i18nextParser = new i18nTransform({ output: 'test/locales' }) const fakeFile = new Vinyl({ contents: Buffer.from("asd t('test_context:first')"), path: 'file.js' @@ -317,24 +343,26 @@ describe('parser', function () { first_context2: '' } - i18nextParser.on('data', (file) => { + i18nextParser.on('data', file => { if (file.relative.endsWith('en/test_context.json')) { result = JSON.parse(file.contents) } }) i18nextParser.once('end', () => { - assert.deepEqual( result, expectedResult ) + assert.deepEqual(result, expectedResult) done() }) i18nextParser.end(fakeFile) }) - it('retrieves plural values in existing catalog', function (done) { + it('retrieves plural values in existing catalog', done => { let result - const i18nextParser = new i18nTransform({output: 'test/locales'}) + const i18nextParser = new i18nTransform({ output: 'test/locales' }) const fakeFile = new Vinyl({ - contents: Buffer.from("asd t('test_plural:first') t('test_plural:second')"), + contents: Buffer.from( + "asd t('test_plural:first') t('test_plural:second')" + ), path: 'file.js' }) @@ -346,22 +374,22 @@ describe('parser', function () { second_plural_12: 'second plural 12' } - i18nextParser.on('data', (file) => { + i18nextParser.on('data', file => { if (file.relative.endsWith('en/test_plural.json')) { result = JSON.parse(file.contents) } }) i18nextParser.once('end', () => { - assert.deepEqual( result, expectedResult ) + assert.deepEqual(result, expectedResult) done() }) i18nextParser.end(fakeFile) }) - it('retrieves plural and context values in existing catalog', function (done) { + it('retrieves plural and context values in existing catalog', done => { let result - const i18nextParser = new i18nTransform({output: 'test/locales'}) + const i18nextParser = new i18nTransform({ output: 'test/locales' }) const fakeFile = new Vinyl({ contents: Buffer.from("asd t('test_context_plural:first')"), path: 'file.js' @@ -373,21 +401,21 @@ describe('parser', function () { first_context2_plural_2: 'first context2 plural 2' } - i18nextParser.on('data', (file) => { + i18nextParser.on('data', file => { if (file.relative.endsWith('en/test_context_plural.json')) { result = JSON.parse(file.contents) } }) i18nextParser.once('end', () => { - assert.deepEqual( result, expectedResult ) + assert.deepEqual(result, expectedResult) done() }) i18nextParser.end(fakeFile) }) - describe('options', function () { - it('handles filename and extension with $LOCALE and $NAMESPACE var', function (done) { + describe('options', () => { + it('handles filename and extension with $LOCALE and $NAMESPACE var', done => { let results = [] const i18nextParser = new i18nTransform({ locales: ['en'], @@ -400,49 +428,52 @@ describe('parser', function () { path: 'file.js' }) - i18nextParser.on('data', (file) => { + i18nextParser.on('data', file => { results.push(file.relative.replace('locales/', '')) }) - i18nextParser.on('end', function () { + i18nextParser.on('end', () => { const expectedFiles = [ - 'en/p-en-default.en.i18n', 'en/p-en-default_old.en.i18n' + 'en/p-en-default.en.i18n', + 'en/p-en-default_old.en.i18n' ] let length = expectedFiles.length - expectedFiles.forEach(function (filename) { + expectedFiles.forEach(filename => { assert.include(results, filename) - if( ! --length ) done() + if (!--length) done() }) }) i18nextParser.end(fakeFile) }) - it('handles custom namespace and key separators', function (done) { + it('handles custom namespace and key separators', done => { let result const i18nextParser = new i18nTransform({ namespaceSeparator: '?', keySeparator: '-' }) const fakeFile = new Vinyl({ - contents: Buffer.from("asd t('test_separators?first') t('test_separators?second-third')"), + contents: Buffer.from( + "asd t('test_separators?first') t('test_separators?second-third')" + ), path: 'file.js' }) - i18nextParser.on('data', (file) => { + i18nextParser.on('data', file => { if (file.relative.endsWith('en/test_separators.json')) { result = JSON.parse(file.contents) } }) i18nextParser.once('end', () => { - assert.deepEqual( result, { first: '', second: { third: '' } } ) + assert.deepEqual(result, { first: '', second: { third: '' } }) done() }) i18nextParser.end(fakeFile) }) - it('supports a defaultValue', function(done) { + it('supports a defaultValue', done => { let result const i18nextParser = new i18nTransform({ defaultValue: 'NOT_TRANSLATED' @@ -452,20 +483,20 @@ describe('parser', function () { path: 'file.js' }) - i18nextParser.on('data', (file) => { + i18nextParser.on('data', file => { if (file.relative.endsWith('en/translation.json')) { result = JSON.parse(file.contents) } }) i18nextParser.once('end', () => { - assert.deepEqual( result, { first: 'NOT_TRANSLATED' } ) + assert.deepEqual(result, { first: 'NOT_TRANSLATED' }) done() }) i18nextParser.end(fakeFile) }) - it('supports outputing to yml', function(done) { + it('supports outputing to yml', done => { let result const i18nextParser = new i18nTransform({ extension: '.yml' @@ -475,20 +506,20 @@ describe('parser', function () { path: 'file.js' }) - i18nextParser.on('data', (file) => { + i18nextParser.on('data', file => { if (file.relative.endsWith('en/translation.yml')) { result = file.contents.toString('utf8') } }) i18nextParser.once('end', () => { - assert.equal(result, 'first: ""\n' ) + assert.equal(result, 'first: ""\n') done() }) i18nextParser.end(fakeFile) }) - it('supports an indentation option', function(done) { + it('supports an indentation option', done => { let result const i18nextParser = new i18nTransform({ indentation: 6 @@ -498,20 +529,20 @@ describe('parser', function () { path: 'file.js' }) - i18nextParser.on('data', (file) => { + i18nextParser.on('data', file => { if (file.relative.endsWith('en/translation.json')) { result = file.contents.toString('utf8') } }) i18nextParser.once('end', () => { - assert.deepEqual( result.split('\n')[1], ' "first": ""' ) + assert.deepEqual(result.split('\n')[1], ' "first": ""') done() }) i18nextParser.end(fakeFile) }) - it('handles skipping the old catalog with createOldLibraries=false', function (done) { + it('handles skipping the old catalog with createOldLibraries=false', done => { let results = [] const i18nextParser = new i18nTransform({ locales: ['en', 'de', 'fr'], @@ -519,40 +550,49 @@ describe('parser', function () { createOldLibraries: false }) const fakeFile = new Vinyl({ - contents: Buffer.from("asd t('ns1:first') t('second') \n asd t('ns2:third') ad t('fourth')"), + contents: Buffer.from( + "asd t('ns1:first') t('second') \n asd t('ns2:third') ad t('fourth')" + ), path: 'file.js' }) - i18nextParser.on('data', (file) => { + i18nextParser.on('data', file => { results.push(file.relative.replace('locales/', '')) }) - i18nextParser.on('end', function () { - + i18nextParser.on('end', () => { const expectedFiles = [ - 'en/default.json', 'en/ns1.json', 'en/ns2.json', - 'de/default.json', 'de/ns1.json', 'de/ns2.json', - 'fr/default.json', 'fr/ns1.json', 'fr/ns2.json' + 'en/default.json', + 'en/ns1.json', + 'en/ns2.json', + 'de/default.json', + 'de/ns1.json', + 'de/ns2.json', + 'fr/default.json', + 'fr/ns1.json', + 'fr/ns2.json' ] let length = expectedFiles.length - expectedFiles.forEach(function (filename) { + expectedFiles.forEach(filename => { assert.include(results, filename) - if( ! --length ) done() + if (!--length) done() }) }) i18nextParser.end(fakeFile) }) - describe('lexers', function () { - it('support custom lexers options', function (done) { + describe('lexers', () => { + it('support custom lexers options', done => { let result const i18nextParser = new i18nTransform({ lexers: { - js: [{ - lexer: 'JavascriptLexer', - functions: ['bla', '_e'] - }] + js: [ + { + lexer: 'JavascriptLexer', + functions: ['bla', '_e'] + } + ] } }) const fakeFile = new Vinyl({ @@ -560,13 +600,13 @@ describe('parser', function () { path: 'file.js' }) - i18nextParser.on('data', (file) => { + i18nextParser.on('data', file => { if (file.relative.endsWith('en/translation.json')) { result = JSON.parse(file.contents) } }) i18nextParser.once('end', () => { - assert.deepEqual(result, {first: '', second: ''}) + assert.deepEqual(result, { first: '', second: '' }) done() }) @@ -574,16 +614,18 @@ describe('parser', function () { }) }) - describe('sort', function () { - it('does not sort by default', function (done) { + describe('sort', () => { + it('does not sort by default', done => { let result const i18nextParser = new i18nTransform() const fakeFile = new Vinyl({ - contents: Buffer.from("asd t('ccc') t('aaa') t('bbb.bbb') t('bbb.aaa')"), + contents: Buffer.from( + "asd t('ccc') t('aaa') t('bbb.bbb') t('bbb.aaa')" + ), path: 'file.js' }) - i18nextParser.on('data', (file) => { + i18nextParser.on('data', file => { if (file.relative.endsWith('en/translation.json')) { result = JSON.parse(file.contents) } @@ -597,17 +639,19 @@ describe('parser', function () { i18nextParser.end(fakeFile) }) - it('supports sort as an option', function (done) { + it('supports sort as an option', done => { let result const i18nextParser = new i18nTransform({ sort: true }) const fakeFile = new Vinyl({ - contents: Buffer.from("asd t('ccc') t('aaa') t('bbb.bbb') t('bbb.aaa')"), + contents: Buffer.from( + "asd t('ccc') t('aaa') t('bbb.bbb') t('bbb.aaa')" + ), path: 'file.js' }) - i18nextParser.on('data', (file) => { + i18nextParser.on('data', file => { if (file.relative.endsWith('en/translation.json')) { result = JSON.parse(file.contents) } @@ -623,62 +667,62 @@ describe('parser', function () { }) }) - describe('events', function () { - it('emits a `reading` event', function (done) { + describe('events', () => { + it('emits a `reading` event', done => { let result const i18nextParser = new i18nTransform() const fakeFile = new Vinyl({ - contents: Buffer.from("content"), + contents: Buffer.from('content'), path: 'file.js' }) - i18nextParser.on('reading', (file) => { + i18nextParser.on('reading', file => { result = file.path }) - i18nextParser.on('finish', function () { + i18nextParser.on('finish', () => { assert.equal(result, 'file.js') done() }) i18nextParser.end(fakeFile) }) - it('emits a `error` event if the catalog is not valid json', function (done) { - const i18nextParser = new i18nTransform({output: 'test/locales'}) + it('emits a `error` event if the catalog is not valid json', done => { + const i18nextParser = new i18nTransform({ output: 'test/locales' }) const fakeFile = new Vinyl({ contents: Buffer.from("t('test_invalid:content')"), path: 'file.js' }) - i18nextParser.on('error', (error) => { + i18nextParser.on('error', error => { assert.equal(error.message, 'Unexpected token / in JSON at position 0') done() }) i18nextParser.end(fakeFile) }) - it('emits an `error` if a lexer does not exist', function (done) { + it('emits an `error` if a lexer does not exist', done => { const results = [] - const i18nextParser = new i18nTransform({lexers: {js: ['fakeLexer']}}) + const i18nextParser = new i18nTransform({ lexers: { js: ['fakeLexer'] } }) const fakeFile = new Vinyl({ - contents: Buffer.from("content"), + contents: Buffer.from('content'), path: 'file.js' }) - i18nextParser.on('error', (error) => { + i18nextParser.on('error', error => { assert.equal(error.message, "Lexer 'fakeLexer' does not exist") done() }) i18nextParser.end(fakeFile) }) - it('emits a `warning` event if a key contains a variable', function (done) { - const i18nextParser = new i18nTransform({output: 'test/locales'}) + it('emits a `warning` event if a key contains a variable', done => { + const i18nextParser = new i18nTransform({ output: 'test/locales' }) const fakeFile = new Vinyl({ - contents: Buffer.from("t(variable)"), + contents: Buffer.from('t(variable)'), path: 'file.js' }) - i18nextParser.on('warning', (message) => { + i18nextParser.on('warning', message => { assert.equal(message, 'Key is not a string litteral: variable') done() })