i18next-scanner
Advanced tools
Comparing version 1.1.0 to 1.2.0
@@ -18,2 +18,6 @@ 'use strict'; | ||
var _esprima = require('esprima'); | ||
var _esprima2 = _interopRequireDefault(_esprima); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
@@ -56,2 +60,4 @@ | ||
nsSeparator: ':', // char to split namespace from key | ||
pluralSeparator: '_', // char to split plural from key | ||
contextSeparator: '_', // char to split context from key | ||
@@ -65,2 +71,36 @@ // interpolation options | ||
// http://codereview.stackexchange.com/questions/45991/balanced-parentheses | ||
var matchBalancedParentheses = function matchBalancedParentheses() { | ||
var str = arguments.length <= 0 || arguments[0] === undefined ? '' : arguments[0]; | ||
var parentheses = '[]{}()'; | ||
var stack = []; | ||
var bracePosition = void 0; | ||
var start = -1; | ||
str = '' + str; // ensure string | ||
for (var _i = 0; _i < str.length; ++_i) { | ||
if (start >= 0 && stack.length === 0) { | ||
return str.substring(start, _i); | ||
} | ||
bracePosition = parentheses.indexOf(str[_i]); | ||
if (bracePosition < 0) { | ||
continue; | ||
} | ||
if (bracePosition % 2 === 0) { | ||
if (start < 0) { | ||
start = _i; // remember the start position | ||
} | ||
stack.push(bracePosition + 1); // push next expected brace position | ||
continue; | ||
} | ||
if (stack.pop() !== bracePosition) { | ||
return str.substring(start, _i); | ||
} | ||
} | ||
return str.substring(start, i); | ||
}; | ||
var transformOptions = function transformOptions(options) { | ||
@@ -217,20 +257,42 @@ // Attribute | ||
}).value().join('|').replace(/\./g, '\\.'); | ||
var pattern = '(?:(?:^[\s]*)|[^a-zA-Z0-9_])(?:' + matchPattern + ')\\(("(?:[^"\\\\]|\\\\.)*"|\'(?:[^\'\\\\]|\\\\.)*\')\\s*[\\,\\)]'; | ||
var results = content.match(new RegExp(pattern, 'gim')) || []; | ||
results.forEach(function (result) { | ||
var r = result.match(new RegExp(pattern)); | ||
if (!r) { | ||
return; | ||
} | ||
var pattern = '(?:(?:^[\\s]*)|[^a-zA-Z0-9_])(?:' + matchPattern + ')\\(("(?:[^"\\\\]|\\\\.)*"|\'(?:[^\'\\\\]|\\\\.)*\')\\s*[\\,\\)]'; | ||
var re = new RegExp(pattern, 'gim'); | ||
var r = void 0; | ||
var _loop = function _loop() { | ||
var full = r[0]; | ||
var key = _lodash2.default.trim(r[1], '\'"'); | ||
var options = {}; | ||
var endsWithComma = full[full.length - 1] === ','; | ||
if (endsWithComma) { | ||
(function () { | ||
var code = matchBalancedParentheses(content.substr(re.lastIndex)); | ||
var syntax = _esprima2.default.parse('(' + code + ')'); | ||
var props = _lodash2.default.get(syntax, 'body[0].expression.properties') || []; | ||
// http://i18next.com/docs/options/ | ||
var supportedOptions = ['defaultValue', 'count', 'context']; | ||
props.forEach(function (prop) { | ||
if (_lodash2.default.includes(supportedOptions, prop.key.name)) { | ||
options[prop.key.name] = prop.value.value; | ||
} | ||
}); | ||
})(); | ||
} | ||
if (customHandler) { | ||
customHandler(key); | ||
return; | ||
customHandler(key, options); | ||
return 'continue'; | ||
} | ||
_this2.set(key); | ||
}); | ||
_this2.set(key, options); | ||
}; | ||
while (r = re.exec(content)) { | ||
var _ret = _loop(); | ||
if (_ret === 'continue') continue; | ||
} | ||
return this; | ||
@@ -259,10 +321,7 @@ } | ||
}).value().join('|').replace(/\./g, '\\.'); | ||
var pattern = '(?:(?:^[\s]*)|[^a-zA-Z0-9_])(?:' + matchPattern + ')=("[^"]*"|\'[^\']*\')'; | ||
var results = content.match(new RegExp(pattern, 'gim')) || []; | ||
results.forEach(function (result) { | ||
var r = result.match(new RegExp(pattern)); | ||
if (!r) { | ||
return; | ||
} | ||
var pattern = '(?:(?:^[\\s]*)|[^a-zA-Z0-9_])(?:' + matchPattern + ')=("[^"]*"|\'[^\']*\')'; | ||
var re = new RegExp(pattern, 'gim'); | ||
var r = void 0; | ||
while (r = re.exec(content)) { | ||
var attr = _lodash2.default.trim(r[1], '\'"'); | ||
@@ -292,3 +351,3 @@ var keys = attr.indexOf(';') >= 0 ? attr.split(';') : [attr]; | ||
}); | ||
}); | ||
} | ||
@@ -337,4 +396,3 @@ return this; | ||
if (!_lodash2.default.isUndefined(key)) { | ||
var options = this.options; | ||
var ns = options.defaultNs; | ||
var ns = this.options.defaultNs; | ||
@@ -349,4 +407,4 @@ // http://i18next.com/translate/keyBasedFallback/ | ||
if (_lodash2.default.isString(options.nsSeparator) && key.indexOf(options.nsSeparator) > -1) { | ||
var parts = key.split(options.nsSeparator); | ||
if (_lodash2.default.isString(this.options.nsSeparator) && key.indexOf(this.options.nsSeparator) > -1) { | ||
var parts = key.split(this.options.nsSeparator); | ||
ns = parts[0]; | ||
@@ -356,4 +414,4 @@ key = parts[1]; | ||
var keys = _lodash2.default.isString(options.keySeparator) ? key.split(options.keySeparator) : [key]; | ||
var lng = opts.lng ? opts.lng : options.fallbackLng; | ||
var keys = _lodash2.default.isString(this.options.keySeparator) ? key.split(this.options.keySeparator) : [key]; | ||
var lng = opts.lng ? opts.lng : this.options.fallbackLng; | ||
var namespaces = resStore[lng] || {}; | ||
@@ -375,12 +433,22 @@ | ||
// @param {string} key The translation key | ||
// @param {string} [defaultValue] The key's value | ||
// @param {object} [options] The options object | ||
// @param {string} [options.defaultValue] defaultValue to return if translation not found | ||
// @param {number} [options.count] count value used for plurals | ||
// @param {string} [options.context] used for contexts (eg. male) | ||
}, { | ||
key: 'set', | ||
value: function set(key, defaultValue) { | ||
value: function set(key) { | ||
var _this4 = this; | ||
var options = this.options; | ||
var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; | ||
var ns = options.defaultNs; | ||
// Backward compatibility | ||
if (_lodash2.default.isString(options)) { | ||
var defaultValue = options; | ||
options = {}; | ||
options.defaultValue = defaultValue; | ||
} | ||
var ns = this.options.defaultNs; | ||
console.assert(_lodash2.default.isString(ns) && !!ns.length, 'ns is not a valid string', ns); | ||
@@ -396,4 +464,4 @@ | ||
if (_lodash2.default.isString(options.nsSeparator) && key.indexOf(options.nsSeparator) > -1) { | ||
var parts = key.split(options.nsSeparator); | ||
if (_lodash2.default.isString(this.options.nsSeparator) && key.indexOf(this.options.nsSeparator) > -1) { | ||
var parts = key.split(this.options.nsSeparator); | ||
ns = parts[0]; | ||
@@ -403,40 +471,75 @@ key = parts[1]; | ||
var keys = _lodash2.default.isString(options.keySeparator) ? key.split(options.keySeparator) : [key]; | ||
options.lngs.forEach(function (lng) { | ||
var value = _this4.resStore[lng] && _this4.resStore[lng][ns]; | ||
var x = 0; | ||
var keys = _lodash2.default.isString(this.options.keySeparator) ? key.split(this.options.keySeparator) : [key]; | ||
this.options.lngs.forEach(function (lng) { | ||
var res = _this4.resStore[lng] && _this4.resStore[lng][ns]; | ||
while (keys[x]) { | ||
value = value && value[keys[x]]; | ||
x++; | ||
if (!_lodash2.default.isObject(res)) { | ||
// skip undefined namespace | ||
console.log('The namespace "' + ns + '" does not exist:', { key: key, options: options }); | ||
return; | ||
} | ||
if (!_lodash2.default.isUndefined(value)) { | ||
// Found a value associated with the key | ||
var lookupKey = '[' + lng + '][' + ns + '][' + keys.join('][') + ']'; | ||
_this4.debuglog('Found a value %s associated with the key %s in %s.', JSON.stringify(_lodash2.default.get(_this4.resStore, lookupKey)), JSON.stringify(keys.join(options.keySeparator || '')), JSON.stringify(_this4.formatResourceLoadPath(lng, ns))); | ||
} else if (_lodash2.default.isObject(_this4.resStore[lng][ns])) { | ||
(function () { | ||
// Adding a new entry | ||
var res = _this4.resStore[lng][ns]; | ||
Object.keys(keys).forEach(function (index) { | ||
var elem = keys[index]; | ||
if (index >= keys.length - 1) { | ||
if (_lodash2.default.isUndefined(defaultValue)) { | ||
res[elem] = _lodash2.default.isFunction(options.defaultValue) ? options.defaultValue(lng, ns, elem) : options.defaultValue; | ||
} else { | ||
res[elem] = defaultValue; | ||
} | ||
} else { | ||
res[elem] = res[elem] || {}; | ||
res = res[elem]; | ||
} | ||
}); | ||
var lookupKey = '[' + lng + '][' + ns + '][' + keys.join('][') + ']'; | ||
_this4.debuglog('Adding a new entry {%s:%s} to %s.', JSON.stringify(keys.join(options.keySeparator || '')), JSON.stringify(_lodash2.default.get(_this4.resStore, lookupKey)), JSON.stringify(_this4.formatResourceLoadPath(lng, ns))); | ||
})(); | ||
} else { | ||
// skip the namespace that is not defined in the i18next options | ||
console.log('The namespace "' + ns + '" does not exist:', { key: key, defaultValue: defaultValue }); | ||
} | ||
Object.keys(keys).forEach(function (index) { | ||
var key = keys[index]; | ||
if (index < keys.length - 1) { | ||
res[key] = res[key] || {}; | ||
res = res[key]; | ||
return; // continue | ||
} | ||
var hasContext = options.context !== undefined; | ||
var hasCount = options.count !== undefined; | ||
// Context & Plural | ||
// http://i18next.com/translate/context/ | ||
// http://i18next.com/translate/pluralSimple/ | ||
// | ||
// Format: | ||
// "<key>[[{{contextSeparator}}<context>]{{pluralSeparator}}<plural>]" | ||
// | ||
// Example: | ||
// { | ||
// "translation": { | ||
// "friend": "A friend", | ||
// "friend_male": "A boyfriend", | ||
// "friend_female": "A girlfriend", | ||
// "friend_male_plural": "{{count}} boyfriends", | ||
// "friend_female_plural": "{{count}} girlfriends" | ||
// } | ||
// } | ||
var formattedKey = key; | ||
// http://i18next.com/translate/context/ | ||
if (hasContext) { | ||
formattedKey = formattedKey + _this4.options.contextSeparator + options.context; | ||
} | ||
// http://i18next.com/translate/pluralSimple/ | ||
if (hasCount) { | ||
// TODO: multiple plural forms | ||
formattedKey = formattedKey + _this4.options.pluralSeparator + 'plural'; | ||
} | ||
if (options.defaultValue !== undefined) { | ||
// Use `options.defaultValue` if specified | ||
if (res[key] === undefined) { | ||
res[key] = options.defaultValue; | ||
_this4.debuglog('Added a new translation key { %s: %s } to %s', JSON.stringify(key), JSON.stringify(res[key]), JSON.stringify(_this4.formatResourceLoadPath(lng, ns))); | ||
} | ||
if (formattedKey !== key && res[formattedKey] === undefined) { | ||
res[formattedKey] = options.defaultValue; | ||
_this4.debuglog('Added a new translation key { %s: %s } to %s', JSON.stringify(formattedKey), JSON.stringify(res[formattedKey]), JSON.stringify(_this4.formatResourceLoadPath(lng, ns))); | ||
} | ||
} else { | ||
// Fallback to `this.options.defaultValue` | ||
if (res[key] === undefined) { | ||
res[key] = _lodash2.default.isFunction(_this4.options.defaultValue) ? _this4.options.defaultValue(lng, ns, key) : _this4.options.defaultValue; | ||
_this4.debuglog('Added a new translation key { %s: %s } to %s', JSON.stringify(key), JSON.stringify(res[key]), JSON.stringify(_this4.formatResourceLoadPath(lng, ns))); | ||
} | ||
if (formattedKey !== key && res[formattedKey] === undefined) { | ||
res[formattedKey] = _lodash2.default.isFunction(_this4.options.defaultValue) ? _this4.options.defaultValue(lng, ns, key) : _this4.options.defaultValue; | ||
_this4.debuglog('Added a new translation key { %s: %s } to %s', JSON.stringify(formattedKey), JSON.stringify(res[formattedKey]), JSON.stringify(_this4.formatResourceLoadPath(lng, ns))); | ||
} | ||
} | ||
}); | ||
}); | ||
@@ -443,0 +546,0 @@ } |
{ | ||
"name": "i18next-scanner", | ||
"version": "1.1.0", | ||
"version": "1.2.0", | ||
"description": "Scan your code, extract translation keys/values, and merge them into i18n resource files.", | ||
@@ -40,2 +40,3 @@ "homepage": "https://github.com/i18next/i18next-scanner", | ||
"dependencies": { | ||
"esprima": "^2.7.2", | ||
"lodash": "^4.6.1", | ||
@@ -55,4 +56,2 @@ "through2": "^2.0.0", | ||
"eslint": "^2.4.0", | ||
"eslint-config-airbnb": "^6.1.0", | ||
"eslint-plugin-react": "^4.2.1", | ||
"gulp": "^3.9.1", | ||
@@ -59,0 +58,0 @@ "gulp-tap": "^0.1.3", |
@@ -32,4 +32,3 @@ # i18next-scanner [![build status](https://travis-ci.org/i18next/i18next-scanner.svg?branch=master)](https://travis-ci.org/i18next/i18next-scanner) [![Coverage Status](https://coveralls.io/repos/i18next/i18next-scanner/badge.svg?branch=master&service=github)](https://coveralls.io/github/i18next/i18next-scanner?branch=master) | ||
var customHandler = function(key) { | ||
var defaultValue = '__TRANSLATION__'; // optional default value | ||
parser.set(key, defaultValue); | ||
parser.set(key, '__TRANSLATION__'); | ||
}; | ||
@@ -157,9 +156,9 @@ | ||
parser.parseFuncFromString(content, function(key) { | ||
var defaultValue = key; // use key as the value | ||
parser.set(key, defaultValue); | ||
parser.parseFuncFromString(content, function(key, options) { | ||
options.defaultValue = key; // use key as the value | ||
parser.set(key, options); | ||
}); | ||
parser.parseFuncFromString(content, { list: ['_t'] }, function(key) { | ||
parser.set(key); // use defaultValue | ||
parser.parseFuncFromString(content, { list: ['_t'] }, function(key, options) { | ||
parser.set(key, options); // use defaultValue | ||
}); | ||
@@ -204,7 +203,12 @@ ``` | ||
```js | ||
// Set translation key | ||
// Set a translation key | ||
parser.set(key); | ||
// Set translation key with its defaultValue | ||
// Set a translation key with default value | ||
parser.set(key, defaultValue); | ||
// Set a translation key with default value using options | ||
parser.set(key, { | ||
defaultValue: defaultValue | ||
}); | ||
``` | ||
@@ -325,2 +329,4 @@ | ||
keySeparator: '.', | ||
pluralSeparator: '_', | ||
contextSeparator: '_', | ||
interpolation: { | ||
@@ -419,8 +425,8 @@ pefix: '{{', | ||
Provides the default value with a function: | ||
Provides the default value as a callback function: | ||
```js | ||
{ | ||
// @param {string} lng The language currently used. | ||
// @param {ns} ns The namespace currently used. | ||
// @param {key} key The translation key. | ||
// @param {string} ns The namespace currently used. | ||
// @param {string} key The translation key. | ||
// @return {string} Returns a default value for the translation key. | ||
@@ -474,2 +480,14 @@ defaultValue: function(lng, ns, key) { | ||
#### pluralSeparator | ||
Type: `String` Default: `'_'` | ||
The character to split plural from key. | ||
#### contextSeparator | ||
Type: `String` Default: `'_'` | ||
The character to split context from key. | ||
#### interpolation | ||
@@ -476,0 +494,0 @@ |
/* eslint no-console: 0 */ | ||
import _ from 'lodash'; | ||
import fs from 'fs'; | ||
import esprima from 'esprima'; | ||
@@ -38,2 +39,4 @@ const defaults = { | ||
nsSeparator: ':', // char to split namespace from key | ||
pluralSeparator: '_', // char to split plural from key | ||
contextSeparator: '_', // char to split context from key | ||
@@ -47,2 +50,34 @@ // interpolation options | ||
// http://codereview.stackexchange.com/questions/45991/balanced-parentheses | ||
const matchBalancedParentheses = (str = '') => { | ||
const parentheses = '[]{}()'; | ||
const stack = []; | ||
let bracePosition; | ||
let start = -1; | ||
str = '' + str; // ensure string | ||
for (let i = 0; i < str.length; ++i) { | ||
if ((start >= 0) && (stack.length === 0)) { | ||
return str.substring(start, i); | ||
} | ||
bracePosition = parentheses.indexOf(str[i]); | ||
if (bracePosition < 0) { | ||
continue; | ||
} | ||
if ((bracePosition % 2) === 0) { | ||
if (start < 0) { | ||
start = i; // remember the start position | ||
} | ||
stack.push(bracePosition + 1); // push next expected brace position | ||
continue; | ||
} | ||
if (stack.pop() !== bracePosition) { | ||
return str.substring(start, i); | ||
} | ||
} | ||
return str.substring(start, i); | ||
}; | ||
const transformOptions = (options) => { | ||
@@ -183,19 +218,36 @@ // Attribute | ||
.replace(/\./g, '\\.'); | ||
const pattern = '(?:(?:^[\s]*)|[^a-zA-Z0-9_])(?:' + matchPattern + ')\\(("(?:[^"\\\\]|\\\\.)*"|\'(?:[^\'\\\\]|\\\\.)*\')\\s*[\\,\\)]'; | ||
const results = content.match(new RegExp(pattern, 'gim')) || []; | ||
results.forEach((result) => { | ||
const r = result.match(new RegExp(pattern)); | ||
if (!r) { | ||
return; | ||
} | ||
const pattern = '(?:(?:^[\\s]*)|[^a-zA-Z0-9_])(?:' + matchPattern + ')\\(("(?:[^"\\\\]|\\\\.)*"|\'(?:[^\'\\\\]|\\\\.)*\')\\s*[\\,\\)]'; | ||
const re = new RegExp(pattern, 'gim'); | ||
let r; | ||
while ((r = re.exec(content))) { | ||
const full = r[0]; | ||
const key = _.trim(r[1], '\'"'); | ||
const options = {}; | ||
const endsWithComma = (full[full.length - 1] === ','); | ||
if (endsWithComma) { | ||
const code = matchBalancedParentheses(content.substr(re.lastIndex)); | ||
const syntax = esprima.parse('(' + code + ')'); | ||
const props = _.get(syntax, 'body[0].expression.properties') || []; | ||
// http://i18next.com/docs/options/ | ||
const supportedOptions = [ | ||
'defaultValue', | ||
'count', | ||
'context' | ||
]; | ||
props.forEach((prop) => { | ||
if (_.includes(supportedOptions, prop.key.name)) { | ||
options[prop.key.name] = prop.value.value; | ||
} | ||
}); | ||
} | ||
if (customHandler) { | ||
customHandler(key); | ||
return; | ||
customHandler(key, options); | ||
continue; | ||
} | ||
this.set(key); | ||
}); | ||
this.set(key, options); | ||
} | ||
@@ -219,10 +271,7 @@ return this; | ||
.replace(/\./g, '\\.'); | ||
const pattern = '(?:(?:^[\s]*)|[^a-zA-Z0-9_])(?:' + matchPattern + ')=("[^"]*"|\'[^\']*\')'; | ||
const results = content.match(new RegExp(pattern, 'gim')) || []; | ||
results.forEach((result) => { | ||
const r = result.match(new RegExp(pattern)); | ||
if (!r) { | ||
return; | ||
} | ||
const pattern = '(?:(?:^[\\s]*)|[^a-zA-Z0-9_])(?:' + matchPattern + ')=("[^"]*"|\'[^\']*\')'; | ||
const re = new RegExp(pattern, 'gim'); | ||
let r; | ||
while ((r = re.exec(content))) { | ||
const attr = _.trim(r[1], '\'"'); | ||
@@ -252,3 +301,3 @@ const keys = (attr.indexOf(';') >= 0) ? attr.split(';') : [attr]; | ||
}); | ||
}); | ||
} | ||
@@ -292,4 +341,3 @@ return this; | ||
if (!_.isUndefined(key)) { | ||
const options = this.options; | ||
let ns = options.defaultNs; | ||
let ns = this.options.defaultNs; | ||
@@ -304,4 +352,4 @@ // http://i18next.com/translate/keyBasedFallback/ | ||
if (_.isString(options.nsSeparator) && (key.indexOf(options.nsSeparator) > -1)) { | ||
const parts = key.split(options.nsSeparator); | ||
if (_.isString(this.options.nsSeparator) && (key.indexOf(this.options.nsSeparator) > -1)) { | ||
const parts = key.split(this.options.nsSeparator); | ||
ns = parts[0]; | ||
@@ -311,4 +359,6 @@ key = parts[1]; | ||
const keys = _.isString(options.keySeparator) ? key.split(options.keySeparator) : [key]; | ||
const lng = opts.lng ? opts.lng : options.fallbackLng; | ||
const keys = _.isString(this.options.keySeparator) | ||
? key.split(this.options.keySeparator) | ||
: [key]; | ||
const lng = opts.lng ? opts.lng : this.options.fallbackLng; | ||
const namespaces = resStore[lng] || {}; | ||
@@ -330,7 +380,15 @@ | ||
// @param {string} key The translation key | ||
// @param {string} [defaultValue] The key's value | ||
set(key, defaultValue) { | ||
const options = this.options; | ||
// @param {object} [options] The options object | ||
// @param {string} [options.defaultValue] defaultValue to return if translation not found | ||
// @param {number} [options.count] count value used for plurals | ||
// @param {string} [options.context] used for contexts (eg. male) | ||
set(key, options = {}) { | ||
// Backward compatibility | ||
if (_.isString(options)) { | ||
let defaultValue = options; | ||
options = {}; | ||
options.defaultValue = defaultValue; | ||
} | ||
let ns = options.defaultNs; | ||
let ns = this.options.defaultNs; | ||
console.assert(_.isString(ns) && !!ns.length, 'ns is not a valid string', ns); | ||
@@ -346,4 +404,4 @@ | ||
if (_.isString(options.nsSeparator) && (key.indexOf(options.nsSeparator) > -1)) { | ||
const parts = key.split(options.nsSeparator); | ||
if (_.isString(this.options.nsSeparator) && (key.indexOf(this.options.nsSeparator) > -1)) { | ||
const parts = key.split(this.options.nsSeparator); | ||
ns = parts[0]; | ||
@@ -353,45 +411,93 @@ key = parts[1]; | ||
const keys = _.isString(options.keySeparator) ? key.split(options.keySeparator) : [key]; | ||
options.lngs.forEach((lng) => { | ||
let value = this.resStore[lng] && this.resStore[lng][ns]; | ||
let x = 0; | ||
const keys = _.isString(this.options.keySeparator) ? key.split(this.options.keySeparator) : [key]; | ||
this.options.lngs.forEach((lng) => { | ||
let res = this.resStore[lng] && this.resStore[lng][ns]; | ||
while (keys[x]) { | ||
value = value && value[keys[x]]; | ||
x++; | ||
if (!_.isObject(res)) { // skip undefined namespace | ||
console.log('The namespace "' + ns + '" does not exist:', { key, options }); | ||
return; | ||
} | ||
if (!_.isUndefined(value)) { | ||
// Found a value associated with the key | ||
let lookupKey = '[' + lng + '][' + ns + '][' + keys.join('][') + ']'; | ||
this.debuglog('Found a value %s associated with the key %s in %s.', | ||
JSON.stringify(_.get(this.resStore, lookupKey)), | ||
JSON.stringify(keys.join(options.keySeparator || '')), | ||
JSON.stringify(this.formatResourceLoadPath(lng, ns)) | ||
); | ||
} else if (_.isObject(this.resStore[lng][ns])) { | ||
// Adding a new entry | ||
let res = this.resStore[lng][ns]; | ||
Object.keys(keys).forEach((index) => { | ||
const elem = keys[index]; | ||
if (index >= (keys.length - 1)) { | ||
if (_.isUndefined(defaultValue)) { | ||
res[elem] = _.isFunction(options.defaultValue) ? options.defaultValue(lng, ns, elem) : options.defaultValue; | ||
} else { | ||
res[elem] = defaultValue; | ||
} | ||
} else { | ||
res[elem] = res[elem] || {}; | ||
res = res[elem]; | ||
Object.keys(keys).forEach((index) => { | ||
const key = keys[index]; | ||
if (index < (keys.length - 1)) { | ||
res[key] = res[key] || {}; | ||
res = res[key]; | ||
return; // continue | ||
} | ||
const hasContext = (options.context !== undefined); | ||
const hasCount = (options.count !== undefined); | ||
// Context & Plural | ||
// http://i18next.com/translate/context/ | ||
// http://i18next.com/translate/pluralSimple/ | ||
// | ||
// Format: | ||
// "<key>[[{{contextSeparator}}<context>]{{pluralSeparator}}<plural>]" | ||
// | ||
// Example: | ||
// { | ||
// "translation": { | ||
// "friend": "A friend", | ||
// "friend_male": "A boyfriend", | ||
// "friend_female": "A girlfriend", | ||
// "friend_male_plural": "{{count}} boyfriends", | ||
// "friend_female_plural": "{{count}} girlfriends" | ||
// } | ||
// } | ||
let formattedKey = key; | ||
// http://i18next.com/translate/context/ | ||
if (hasContext) { | ||
formattedKey = formattedKey + this.options.contextSeparator + options.context; | ||
} | ||
// http://i18next.com/translate/pluralSimple/ | ||
if (hasCount) { // TODO: multiple plural forms | ||
formattedKey = formattedKey + this.options.pluralSeparator + 'plural'; | ||
} | ||
if (options.defaultValue !== undefined) { | ||
// Use `options.defaultValue` if specified | ||
if (res[key] === undefined) { | ||
res[key] = options.defaultValue; | ||
this.debuglog('Added a new translation key { %s: %s } to %s', | ||
JSON.stringify(key), | ||
JSON.stringify(res[key]), | ||
JSON.stringify(this.formatResourceLoadPath(lng, ns)) | ||
); | ||
} | ||
}); | ||
let lookupKey = '[' + lng + '][' + ns + '][' + keys.join('][') + ']'; | ||
this.debuglog('Adding a new entry {%s:%s} to %s.', | ||
JSON.stringify(keys.join(options.keySeparator || '')), | ||
JSON.stringify(_.get(this.resStore, lookupKey)), | ||
JSON.stringify(this.formatResourceLoadPath(lng, ns)) | ||
); | ||
} else { // skip the namespace that is not defined in the i18next options | ||
console.log('The namespace "' + ns + '" does not exist:', { key, defaultValue }); | ||
} | ||
if ((formattedKey !== key) && (res[formattedKey] === undefined)) { | ||
res[formattedKey] = options.defaultValue; | ||
this.debuglog('Added a new translation key { %s: %s } to %s', | ||
JSON.stringify(formattedKey), | ||
JSON.stringify(res[formattedKey]), | ||
JSON.stringify(this.formatResourceLoadPath(lng, ns)) | ||
); | ||
} | ||
} else { | ||
// Fallback to `this.options.defaultValue` | ||
if (res[key] === undefined) { | ||
res[key] = _.isFunction(this.options.defaultValue) | ||
? this.options.defaultValue(lng, ns, key) | ||
: this.options.defaultValue; | ||
this.debuglog('Added a new translation key { %s: %s } to %s', | ||
JSON.stringify(key), | ||
JSON.stringify(res[key]), | ||
JSON.stringify(this.formatResourceLoadPath(lng, ns)) | ||
); | ||
} | ||
if ((formattedKey !== key) && (res[formattedKey] === undefined)) { | ||
res[formattedKey] = _.isFunction(this.options.defaultValue) | ||
? this.options.defaultValue(lng, ns, key) | ||
: this.options.defaultValue; | ||
this.debuglog('Added a new translation key { %s: %s } to %s', | ||
JSON.stringify(formattedKey), | ||
JSON.stringify(res[formattedKey]), | ||
JSON.stringify(this.formatResourceLoadPath(lng, ns)) | ||
); | ||
} | ||
} | ||
}); | ||
}); | ||
@@ -398,0 +504,0 @@ } |
@@ -10,5 +10,8 @@ var i18n = require('i18next'); | ||
_t('This value does not exist.'), | ||
_t('YouTube has more than __count__ billion users.', {count: 1}) | ||
_t('YouTube has more than {{count}} billion users.', {count: 1}), | ||
_t('You have {{count}} messages.', { | ||
count: 10 | ||
}); | ||
].join('\n'); | ||
console.log(msg); |
@@ -6,2 +6,3 @@ import _ from 'lodash'; | ||
import tap from 'gulp-tap'; | ||
import VirtualFile from 'vinyl'; | ||
import { test } from 'tap'; | ||
@@ -24,3 +25,3 @@ import scanner from '../src'; | ||
loadPath: '', | ||
savePath: 'i18n/__lng__/__ns__.json' | ||
savePath: 'i18n/{{lng}}/{{ns}}.json' | ||
}, | ||
@@ -30,7 +31,45 @@ nsSeparator: false, // namespace separator | ||
interpolation: { | ||
prefix: '__', | ||
suffix: '__' | ||
prefix: '{{', | ||
suffix: '}}' | ||
} | ||
}; | ||
test('Parse both .html and .js files', (t) => { | ||
const options = _.merge({}, defaults, {}); | ||
const list = [ | ||
'test/fixtures/app.html', | ||
'test/fixtures/modules/**/*.js' | ||
]; | ||
gulp.src(list) | ||
.pipe(scanner(options)) | ||
.pipe(tap(function(file) { | ||
const contents = file.contents.toString(); | ||
const list = [ | ||
'i18n/de/resource.json', | ||
'i18n/en/resource.json', | ||
]; | ||
if (_.includes(list, file.path)) { | ||
const found = JSON.parse(contents); | ||
const wanted = { | ||
"Loading...": "__STRING_NOT_TRANSLATED__", | ||
"This value does not exist.": "__STRING_NOT_TRANSLATED__", | ||
"You have {{count}} messages.": "__STRING_NOT_TRANSLATED__", | ||
"You have {{count}} messages._plural": "__STRING_NOT_TRANSLATED__", | ||
"YouTube has more than {{count}} billion users.": "__STRING_NOT_TRANSLATED__", | ||
"YouTube has more than {{count}} billion users._plural": "__STRING_NOT_TRANSLATED__", | ||
'key4': '__STRING_NOT_TRANSLATED__', | ||
'key3': '__STRING_NOT_TRANSLATED__', | ||
'key2': '__STRING_NOT_TRANSLATED__', | ||
'key1': '__STRING_NOT_TRANSLATED__' | ||
}; | ||
t.same(found, wanted); | ||
} | ||
})) | ||
.on('end', function() { | ||
t.end(); | ||
}); | ||
}); | ||
test('[Key Based Fallback] defaultValue as string', function(t) { | ||
@@ -57,3 +96,6 @@ const options = _.merge({}, defaults, { | ||
"This value does not exist.": "__STRING_NOT_TRANSLATED__", | ||
"YouTube has more than __count__ billion users.": "__STRING_NOT_TRANSLATED__" | ||
"YouTube has more than {{count}} billion users.": "__STRING_NOT_TRANSLATED__", | ||
"YouTube has more than {{count}} billion users._plural": "__STRING_NOT_TRANSLATED__", | ||
"You have {{count}} messages.": "__STRING_NOT_TRANSLATED__", | ||
"You have {{count}} messages._plural": "__STRING_NOT_TRANSLATED__" | ||
}; | ||
@@ -91,3 +133,6 @@ t.same(found, wanted); | ||
"This value does not exist.": "This value does not exist.", | ||
"YouTube has more than __count__ billion users.": "YouTube has more than __count__ billion users." | ||
"YouTube has more than {{count}} billion users.": "YouTube has more than {{count}} billion users.", | ||
"YouTube has more than {{count}} billion users._plural": "YouTube has more than {{count}} billion users.", | ||
"You have {{count}} messages.": "You have {{count}} messages.", | ||
"You have {{count}} messages._plural": "You have {{count}} messages." | ||
}; | ||
@@ -102,3 +147,6 @@ t.same(found, wanted); | ||
"This value does not exist.": "__STRING_NOT_TRANSLATED__", | ||
"YouTube has more than __count__ billion users.": "__STRING_NOT_TRANSLATED__" | ||
"YouTube has more than {{count}} billion users.": "__STRING_NOT_TRANSLATED__", | ||
"YouTube has more than {{count}} billion users._plural": "__STRING_NOT_TRANSLATED__", | ||
"You have {{count}} messages.": "__STRING_NOT_TRANSLATED__", | ||
"You have {{count}} messages._plural": "__STRING_NOT_TRANSLATED__" | ||
}; | ||
@@ -113,3 +161,3 @@ t.same(found, wanted); | ||
test('should get empty result', function(t) { | ||
test('Empty result', function(t) { | ||
const options = _.merge({}, defaults, { | ||
@@ -140,1 +188,64 @@ func: { | ||
}); | ||
test('Custom transform', function(t) { | ||
const options = _.merge({}, defaults, { | ||
}); | ||
const expectedKey = 'CUSTOM TRANSFORM'; | ||
const customTransform = function(file, enc, done) { | ||
this.parser.set(expectedKey); | ||
done(); | ||
}; | ||
gulp.src('test/fixtures/modules/**/*.js') | ||
.pipe(scanner(options, customTransform)) | ||
.pipe(tap(function(file) { | ||
const contents = file.contents.toString(); | ||
const list = [ | ||
'i18n/de/resource.json', | ||
'i18n/en/resource.json', | ||
]; | ||
if (_.includes(list, file.path)) { | ||
const found = JSON.parse(contents); | ||
const wanted = { | ||
"Loading...": "__STRING_NOT_TRANSLATED__", | ||
"This value does not exist.": "__STRING_NOT_TRANSLATED__", | ||
"YouTube has more than {{count}} billion users.": "__STRING_NOT_TRANSLATED__", | ||
"YouTube has more than {{count}} billion users._plural": "__STRING_NOT_TRANSLATED__", | ||
"You have {{count}} messages.": "__STRING_NOT_TRANSLATED__", | ||
"You have {{count}} messages._plural": "__STRING_NOT_TRANSLATED__", | ||
"CUSTOM TRANSFORM": "__STRING_NOT_TRANSLATED__" | ||
}; | ||
t.same(found, wanted); | ||
} | ||
})) | ||
.on('end', function() { | ||
t.end(); | ||
}); | ||
}); | ||
test('Custom flush', function(t) { | ||
const options = _.merge({}, defaults, { | ||
}); | ||
const expectedContents = 'CUSTOM FLUSH'; | ||
const customFlush = function(done) { | ||
this.push(new VirtualFile({ | ||
path: 'virtual-path', | ||
contents: new Buffer(expectedContents) | ||
})); | ||
done(); | ||
}; | ||
gulp.src('test/fixtures/modules/**/*.js') | ||
.pipe(scanner(options, null, customFlush)) | ||
.pipe(tap(function(file) { | ||
const contents = file.contents.toString(); | ||
t.same(contents, expectedContents); | ||
})) | ||
.on('end', function() { | ||
t.end(); | ||
}); | ||
}); |
@@ -73,5 +73,7 @@ import fs from 'fs'; | ||
translation: { | ||
"key4": "__TRANSLATION__", | ||
"key3": "__TRANSLATION__", | ||
"key2": "__TRANSLATION__", | ||
"key1": "__TRANSLATION__" | ||
} | ||
} | ||
} | ||
@@ -85,3 +87,5 @@ }); | ||
"key1": "__TRANSLATION__", | ||
"key2": "__TRANSLATION__" | ||
"key2": "__TRANSLATION__", | ||
"key3": "__TRANSLATION__", | ||
"key4": "__TRANSLATION__" | ||
} | ||
@@ -118,3 +122,6 @@ } | ||
"This value does not exist.": "This value does not exist.", | ||
"YouTube has more than __count__ billion users.": "YouTube has more than __count__ billion users." | ||
"YouTube has more than {{count}} billion users.": "YouTube has more than {{count}} billion users.", | ||
"YouTube has more than {{count}} billion users._plural": "YouTube has more than {{count}} billion users.", | ||
"You have {{count}} messages.": "You have {{count}} messages.", | ||
"You have {{count}} messages._plural": "You have {{count}} messages." | ||
} | ||
@@ -209,1 +216,53 @@ } | ||
}); | ||
test('Plural', (t) => { | ||
const parser = new Parser(); | ||
const content = fs.readFileSync(path.resolve(__dirname, 'fixtures/plural.js'), 'utf-8'); | ||
parser.parseFuncFromString(content); | ||
t.same(parser.get(), { | ||
en: { | ||
translation: { | ||
"key": "", | ||
"key_plural": "", | ||
"keyWithCount": "", | ||
"keyWithCount_plural": "" | ||
} | ||
} | ||
}); | ||
t.end(); | ||
}); | ||
test('Context', (t) => { | ||
const parser = new Parser(); | ||
const content = fs.readFileSync(path.resolve(__dirname, 'fixtures/context.js'), 'utf-8'); | ||
parser.parseFuncFromString(content); | ||
t.same(parser.get(), { | ||
en: { | ||
translation: { | ||
"friend": "", | ||
"friend_male": "", | ||
"friend_female": "" | ||
} | ||
} | ||
}); | ||
t.end(); | ||
}); | ||
test('Context with plural combined', (t) => { | ||
const parser = new Parser(); | ||
const content = fs.readFileSync(path.resolve(__dirname, 'fixtures/context-plural.js'), 'utf-8'); | ||
parser.parseFuncFromString(content); | ||
t.same(parser.get(), { | ||
en: { | ||
translation: { | ||
"friend": "", | ||
"friend_plural": "", | ||
"friend_male": "", | ||
"friend_male_plural": "", | ||
"friend_female": "", | ||
"friend_female_plural": "" | ||
} | ||
} | ||
}); | ||
t.end(); | ||
}); |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
96469
14
33
1824
512
5
+ Addedesprima@^2.7.2
+ Addedesprima@2.7.3(transitive)