i18next-scanner
Advanced tools
Comparing version 1.2.1 to 1.3.0
@@ -168,2 +168,5 @@ 'use strict'; | ||
var Parser = function () { | ||
// The resStore stores all translation keys including unused ones | ||
function Parser(options) { | ||
@@ -176,2 +179,3 @@ var _this = this; | ||
this.resStore = {}; | ||
this.resScan = {}; | ||
@@ -185,2 +189,3 @@ this.options = transformOptions(_lodash2.default.extend({}, this.options, options)); | ||
_this.resStore[lng] = _this.resStore[lng] || {}; | ||
_this.resScan[lng] = _this.resScan[lng] || {}; | ||
namespaces.forEach(function (ns) { | ||
@@ -190,2 +195,3 @@ var resPath = _this.formatResourceLoadPath(lng, ns); | ||
_this.resStore[lng][ns] = {}; | ||
_this.resScan[lng][ns] = {}; | ||
try { | ||
@@ -206,2 +212,5 @@ var stat = _fs2.default.statSync(resPath); | ||
// The resScan only stores translation keys parsed from code | ||
_createClass(Parser, [{ | ||
@@ -391,3 +400,8 @@ key: 'debuglog', | ||
var resStore = _lodash2.default.assign({}, this.resStore); | ||
var resStore = {}; | ||
if (this.options.removeUnusedKeys) { | ||
resStore = this.resScan; | ||
} else { | ||
resStore = this.resStore; | ||
} | ||
@@ -495,8 +509,8 @@ if (opts.sort) { | ||
this.options.lngs.forEach(function (lng) { | ||
var res = _this4.resStore[lng] && _this4.resStore[lng][ns]; | ||
var resLoad = _this4.resStore[lng] && _this4.resStore[lng][ns]; | ||
var resScan = _this4.resScan[lng] && _this4.resScan[lng][ns]; | ||
if (!_lodash2.default.isObject(res)) { | ||
if (!_lodash2.default.isObject(resLoad)) { | ||
// skip undefined namespace | ||
console.log('The namespace "' + ns + '" does not exist:', { key: key, options: options }); | ||
return; | ||
@@ -509,4 +523,6 @@ } | ||
if (index < keys.length - 1) { | ||
res[key] = res[key] || {}; | ||
res = res[key]; | ||
resLoad[key] = resLoad[key] || {}; | ||
resLoad = resLoad[key]; | ||
resScan[key] = resScan[key] || {}; | ||
resScan = resScan[key]; | ||
@@ -551,20 +567,26 @@ return; // continue | ||
// 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 (resLoad[key] === undefined) { | ||
resLoad[key] = options.defaultValue; | ||
_this4.debuglog('Added a new translation key { %s: %s } to %s', JSON.stringify(key), JSON.stringify(resLoad[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))); | ||
resScan[key] = resLoad[key]; | ||
if (formattedKey !== key && resLoad[formattedKey] === undefined) { | ||
resLoad[formattedKey] = options.defaultValue; | ||
_this4.debuglog('Added a new translation key { %s: %s } to %s', JSON.stringify(formattedKey), JSON.stringify(resLoad[formattedKey]), JSON.stringify(_this4.formatResourceLoadPath(lng, ns))); | ||
} | ||
resScan[formattedKey] = resLoad[formattedKey]; | ||
} else { | ||
// Fallback to `this.options.defaultValue` | ||
if (res[key] === undefined) { | ||
res[key] = _lodash2.default.isFunction(_this4.options.defaultValue) ? _this4.options.defaultValue(lng, ns, key, options) : _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 (resLoad[key] === undefined) { | ||
resLoad[key] = _lodash2.default.isFunction(_this4.options.defaultValue) ? _this4.options.defaultValue(lng, ns, key, options) : _this4.options.defaultValue; | ||
_this4.debuglog('Added a new translation key { %s: %s } to %s', JSON.stringify(key), JSON.stringify(resLoad[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, options) : _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))); | ||
resScan[key] = resLoad[key]; | ||
if (formattedKey !== key && resLoad[formattedKey] === undefined) { | ||
resLoad[formattedKey] = _lodash2.default.isFunction(_this4.options.defaultValue) ? _this4.options.defaultValue(lng, ns, key, options) : _this4.options.defaultValue; | ||
_this4.debuglog('Added a new translation key { %s: %s } to %s', JSON.stringify(formattedKey), JSON.stringify(resLoad[formattedKey]), JSON.stringify(_this4.formatResourceLoadPath(lng, ns))); | ||
} | ||
resScan[formattedKey] = resLoad[formattedKey]; | ||
} | ||
@@ -571,0 +593,0 @@ }); |
{ | ||
"name": "i18next-scanner", | ||
"version": "1.2.1", | ||
"version": "1.3.0", | ||
"description": "Scan your code, extract translation keys/values, and merge them into i18n resource files.", | ||
@@ -41,16 +41,16 @@ "homepage": "https://github.com/i18next/i18next-scanner", | ||
"esprima": "^2.7.2", | ||
"lodash": "^4.6.1", | ||
"through2": "^2.0.0", | ||
"vinyl": "^1.1.0", | ||
"vinyl-fs": "^2.2.1" | ||
"lodash": "^4.12.0", | ||
"through2": "^2.0.1", | ||
"vinyl": "^1.1.1", | ||
"vinyl-fs": "^2.4.3" | ||
}, | ||
"devDependencies": { | ||
"babel-cli": "^6.6.5", | ||
"babel-core": "^6.7.2", | ||
"babel-eslint": "^6.0.0-beta.6", | ||
"babel-cli": "^6.8.0", | ||
"babel-core": "^6.8.0", | ||
"babel-eslint": "^6.0.4", | ||
"babel-loader": "^6.2.4", | ||
"babel-preset-es2015": "^6.6.0", | ||
"babel-preset-stage-0": "^6.5.0", | ||
"coveralls": "^2.11.8", | ||
"eslint": "^2.4.0", | ||
"coveralls": "^2.11.9", | ||
"eslint": "^2.10.1", | ||
"gulp": "^3.9.1", | ||
@@ -60,5 +60,5 @@ "gulp-tap": "^0.1.3", | ||
"sha1": "^1.1.1", | ||
"tap": "^5.7.0", | ||
"tap": "^5.7.1", | ||
"text-table": "^0.2.0" | ||
} | ||
} |
@@ -307,2 +307,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) | ||
debug: false, | ||
removeUnusedKeys: false, | ||
sort: false, | ||
@@ -342,2 +343,8 @@ attr: { | ||
#### removeUnusedKeys | ||
Type: `Boolean` Default: `false` | ||
Set to `true` to remove unused translation keys from i18n resource files. | ||
#### sort | ||
@@ -344,0 +351,0 @@ |
@@ -149,4 +149,9 @@ /* eslint no-console: 0 */ | ||
options = _.assign({}, defaults); | ||
// The resStore stores all translation keys including unused ones | ||
resStore = {}; | ||
// The resScan only stores translation keys parsed from code | ||
resScan = {}; | ||
constructor(options) { | ||
@@ -160,2 +165,3 @@ this.options = transformOptions(_.extend({}, this.options, options)); | ||
this.resStore[lng] = this.resStore[lng] || {}; | ||
this.resScan[lng] = this.resScan[lng] || {}; | ||
namespaces.forEach((ns) => { | ||
@@ -165,2 +171,3 @@ const resPath = this.formatResourceLoadPath(lng, ns); | ||
this.resStore[lng][ns] = {}; | ||
this.resScan[lng][ns] = {}; | ||
try { | ||
@@ -336,3 +343,8 @@ const stat = fs.statSync(resPath); | ||
const resStore = _.assign({}, this.resStore); | ||
let resStore = {}; | ||
if (this.options.removeUnusedKeys) { | ||
resStore = this.resScan; | ||
} else { | ||
resStore = this.resStore; | ||
} | ||
@@ -435,7 +447,7 @@ if (opts.sort) { // sort by key | ||
this.options.lngs.forEach((lng) => { | ||
let res = this.resStore[lng] && this.resStore[lng][ns]; | ||
let resLoad = this.resStore[lng] && this.resStore[lng][ns]; | ||
let resScan = this.resScan[lng] && this.resScan[lng][ns]; | ||
if (!_.isObject(res)) { // skip undefined namespace | ||
if (!_.isObject(resLoad)) { // skip undefined namespace | ||
console.log('The namespace "' + ns + '" does not exist:', { key, options }); | ||
return; | ||
@@ -448,4 +460,6 @@ } | ||
if (index < (keys.length - 1)) { | ||
res[key] = res[key] || {}; | ||
res = res[key]; | ||
resLoad[key] = resLoad[key] || {}; | ||
resLoad = resLoad[key]; | ||
resScan[key] = resScan[key] || {}; | ||
resScan = resScan[key]; | ||
@@ -489,22 +503,25 @@ return; // continue | ||
// Use `options.defaultValue` if specified | ||
if (res[key] === undefined) { | ||
res[key] = options.defaultValue; | ||
if (resLoad[key] === undefined) { | ||
resLoad[key] = options.defaultValue; | ||
this.debuglog('Added a new translation key { %s: %s } to %s', | ||
JSON.stringify(key), | ||
JSON.stringify(res[key]), | ||
JSON.stringify(resLoad[key]), | ||
JSON.stringify(this.formatResourceLoadPath(lng, ns)) | ||
); | ||
} | ||
if ((formattedKey !== key) && (res[formattedKey] === undefined)) { | ||
res[formattedKey] = options.defaultValue; | ||
resScan[key] = resLoad[key]; | ||
if ((formattedKey !== key) && (resLoad[formattedKey] === undefined)) { | ||
resLoad[formattedKey] = options.defaultValue; | ||
this.debuglog('Added a new translation key { %s: %s } to %s', | ||
JSON.stringify(formattedKey), | ||
JSON.stringify(res[formattedKey]), | ||
JSON.stringify(resLoad[formattedKey]), | ||
JSON.stringify(this.formatResourceLoadPath(lng, ns)) | ||
); | ||
} | ||
resScan[formattedKey] = resLoad[formattedKey]; | ||
} else { | ||
// Fallback to `this.options.defaultValue` | ||
if (res[key] === undefined) { | ||
res[key] = _.isFunction(this.options.defaultValue) | ||
if (resLoad[key] === undefined) { | ||
resLoad[key] = _.isFunction(this.options.defaultValue) | ||
? this.options.defaultValue(lng, ns, key, options) | ||
@@ -514,8 +531,10 @@ : this.options.defaultValue; | ||
JSON.stringify(key), | ||
JSON.stringify(res[key]), | ||
JSON.stringify(resLoad[key]), | ||
JSON.stringify(this.formatResourceLoadPath(lng, ns)) | ||
); | ||
} | ||
if ((formattedKey !== key) && (res[formattedKey] === undefined)) { | ||
res[formattedKey] = _.isFunction(this.options.defaultValue) | ||
resScan[key] = resLoad[key]; | ||
if ((formattedKey !== key) && (resLoad[formattedKey] === undefined)) { | ||
resLoad[formattedKey] = _.isFunction(this.options.defaultValue) | ||
? this.options.defaultValue(lng, ns, key, options) | ||
@@ -525,6 +544,7 @@ : this.options.defaultValue; | ||
JSON.stringify(formattedKey), | ||
JSON.stringify(res[formattedKey]), | ||
JSON.stringify(resLoad[formattedKey]), | ||
JSON.stringify(this.formatResourceLoadPath(lng, ns)) | ||
); | ||
} | ||
resScan[formattedKey] = resLoad[formattedKey]; | ||
} | ||
@@ -531,0 +551,0 @@ }); |
{ | ||
"language": { | ||
"en-US": "English" | ||
"de-DE": "German" | ||
} | ||
} |
@@ -5,3 +5,4 @@ { | ||
"8524de963f07201e5c086830d370797f": "Loading...", | ||
"b04ba49f848624bb97ab094a2631d2ad74913498": "Loading..." | ||
"b04ba49f848624bb97ab094a2631d2ad74913498": "Loading...", | ||
"Loading...": "Loading..." | ||
} |
@@ -244,1 +244,139 @@ import _ from 'lodash'; | ||
}); | ||
test('Keep old translations', function(t) { | ||
const options = _.merge({}, defaults, { | ||
resource: { | ||
loadPath: 'test/fixtures/i18n/{{lng}}/{{ns}}.json', | ||
savePath: 'i18n/{{lng}}/{{ns}}.json' | ||
} | ||
}); | ||
gulp.src('test/fixtures/modules/**/*.js') | ||
.pipe(scanner(options)) | ||
.pipe(tap(function(file) { | ||
const contents = file.contents.toString(); | ||
// English - locale.json | ||
if (file.path === 'i18n/en/locale.json') { | ||
const found = JSON.parse(contents); | ||
const wanted = { | ||
"language": { | ||
"en-US": "English" | ||
} | ||
}; | ||
t.same(found, wanted); | ||
} | ||
// English - resource.json | ||
if (file.path === 'i18n/en/resource.json') { | ||
const found = JSON.parse(contents); | ||
const wanted = { | ||
"loading": "Loading...", | ||
"cd643ef3": "Loading...", | ||
"8524de963f07201e5c086830d370797f": "Loading...", | ||
"b04ba49f848624bb97ab094a2631d2ad74913498": "Loading...", | ||
"Loading...": "Loading...", // Note. This is an existing translation key in English resource file. | ||
"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__" | ||
}; | ||
t.same(found, wanted); | ||
} | ||
// German - locale.json | ||
if (file.path === 'i18n/de/locale.json') { | ||
const found = JSON.parse(contents); | ||
const wanted = { | ||
"language": { | ||
"de-DE": "German" | ||
} | ||
}; | ||
t.same(found, wanted); | ||
} | ||
// German - resource.json | ||
if (file.path === 'i18n/de/resource.json') { | ||
const found = JSON.parse(contents); | ||
const wanted = { | ||
"loading": "Wird geladen...", | ||
"cd643ef3": "Wird geladen...", | ||
"8524de963f07201e5c086830d370797f": "Wird geladen...", | ||
"b04ba49f848624bb97ab094a2631d2ad74913498": "Wird geladen...", | ||
"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__" | ||
}; | ||
t.same(found, wanted); | ||
} | ||
})) | ||
.on('end', function() { | ||
t.end(); | ||
}); | ||
}); | ||
// https://github.com/i18next/i18next-scanner/issues/30 | ||
test('Remove old translation keys which are already removed from code', function(t) { | ||
const options = _.merge({}, defaults, { | ||
removeUnusedKeys: true, | ||
resource: { | ||
loadPath: 'test/fixtures/i18n/{{lng}}/{{ns}}.json', | ||
savePath: 'i18n/{{lng}}/{{ns}}.json' | ||
} | ||
}); | ||
gulp.src('test/fixtures/modules/**/*.js') | ||
.pipe(scanner(options)) | ||
.pipe(tap(function(file) { | ||
const contents = file.contents.toString(); | ||
// English - locale.json | ||
if (file.path === 'i18n/en/locale.json') { | ||
const found = JSON.parse(contents); | ||
const wanted = {}; | ||
t.same(found, wanted); | ||
} | ||
// English - resource.json | ||
if (file.path === 'i18n/en/resource.json') { | ||
const found = JSON.parse(contents); | ||
const wanted = { | ||
"Loading...": "Loading...", // Note. This is an existing translation key in English resource file. | ||
"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__" | ||
}; | ||
t.same(found, wanted); | ||
} | ||
// German - locale.json | ||
if (file.path === 'i18n/de/locale.json') { | ||
const found = JSON.parse(contents); | ||
const wanted = {}; | ||
t.same(found, wanted); | ||
} | ||
// German - resource.json | ||
if (file.path === 'i18n/de/resource.json') { | ||
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__" | ||
}; | ||
t.same(found, wanted); | ||
} | ||
})) | ||
.on('end', function() { | ||
t.end(); | ||
}); | ||
}); |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
101236
1894
519
Updatedlodash@^4.12.0
Updatedthrough2@^2.0.1
Updatedvinyl@^1.1.1
Updatedvinyl-fs@^2.4.3