"use strict";
function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
Object.defineProperty(exports, "__esModule", {
value: true
});
exports["default"] = void 0;
var _fs = _interopRequireDefault(require("fs"));
var acorn = _interopRequireWildcard(require("acorn"));
var _acornJsx = _interopRequireDefault(require("acorn-jsx"));
var _acornStage = _interopRequireDefault(require("acorn-stage3"));
var _chalk = _interopRequireDefault(require("chalk"));
var _cloneDeep = _interopRequireDefault(require("clone-deep"));
var _deepmerge = _interopRequireDefault(require("deepmerge"));
var _ensureArray = _interopRequireDefault(require("ensure-array"));
var _esprima = require("esprima");
var _lodash = _interopRequireDefault(require("lodash"));
var _parse = _interopRequireDefault(require("parse5"));
var _sortobject = _interopRequireDefault(require("sortobject"));
var _i18next = _interopRequireDefault(require("i18next"));
var _acornJsxWalk = _interopRequireDefault(require("./acorn-jsx-walk"));
var _flattenObjectKeys = _interopRequireDefault(require("./flatten-object-keys"));
var _omitEmptyObject = _interopRequireDefault(require("./omit-empty-object"));
var _nodesToString = _interopRequireDefault(require("./nodes-to-string"));
function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function _getRequireWildcardCache() { return cache; }; return cache; }
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || _typeof(obj) !== "object" && typeof obj !== "function") { return { "default": obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj["default"] = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }
function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; }
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a 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); } }
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
var defaults = {
debug: false,
// verbose logging
sort: false,
// sort keys in alphabetical order
attr: {
// HTML attributes to parse
list: ['data-i18n'],
extensions: ['.html', '.htm']
},
func: {
// function names to parse
list: ['i18next.t', 'i18n.t'],
extensions: ['.js', '.jsx']
},
trans: {
// Trans component (https://github.com/i18next/react-i18next)
component: 'Trans',
i18nKey: 'i18nKey',
defaultsKey: 'defaults',
extensions: ['.js', '.jsx'],
fallbackKey: false,
acorn: {
ecmaVersion: 10,
// defaults to 10
sourceType: 'module' // defaults to 'module'
// Check out https://github.com/acornjs/acorn/tree/master/acorn#interface for additional options
}
},
lngs: ['en'],
// array of supported languages
fallbackLng: 'en',
// language to lookup key if not found while calling `parser.get(key, { lng: '' })`
ns: [],
// string or array of namespaces
defaultLng: 'en',
// default language used for checking default values
defaultNs: 'translation',
// default namespace used if not passed to translation function
defaultValue: '',
// default value used if not passed to `parser.set`
// resource
resource: {
// The path where resources get loaded from. Relative to current working directory.
loadPath: 'i18n/{{lng}}/{{ns}}.json',
// The path to store resources. Relative to the path specified by `gulp.dest(path)`.
savePath: 'i18n/{{lng}}/{{ns}}.json',
// Specify the number of space characters to use as white space to insert into the output JSON string for readability purpose.
jsonIndent: 2,
// Normalize line endings to '\r\n', '\r', '\n', or 'auto' for the current operating system. Defaults to '\n'.
// Aliases: 'CRLF', 'CR', 'LF', 'crlf', 'cr', 'lf'
lineEnding: '\n'
},
keySeparator: '.',
// char to separate keys
nsSeparator: ':',
// char to split namespace from key
// Context Form
context: true,
// whether to add context form key
contextFallback: true,
// whether to add a fallback key as well as the context form key
contextSeparator: '_',
// char to split context from key
contextDefaultValues: [],
// list of values for dynamic values
// Plural Form
plural: true,
// whether to add plural form key
pluralFallback: true,
// whether to add a fallback key as well as the plural form key
pluralSeparator: '_',
// char to split plural from key
// interpolation options
interpolation: {
prefix: '{{',
// prefix for interpolation
suffix: '}}' // suffix for interpolation
}
}; // 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;
var start = -1;
var i = 0;
str = '' + str; // ensure string
for (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 normalizeOptions = function normalizeOptions(options) {
// Attribute
if (_lodash["default"].isUndefined(_lodash["default"].get(options, 'attr.list'))) {
_lodash["default"].set(options, 'attr.list', defaults.attr.list);
}
if (_lodash["default"].isUndefined(_lodash["default"].get(options, 'attr.extensions'))) {
_lodash["default"].set(options, 'attr.extensions', defaults.attr.extensions);
} // Function
if (_lodash["default"].isUndefined(_lodash["default"].get(options, 'func.list'))) {
_lodash["default"].set(options, 'func.list', defaults.func.list);
}
if (_lodash["default"].isUndefined(_lodash["default"].get(options, 'func.extensions'))) {
_lodash["default"].set(options, 'func.extensions', defaults.func.extensions);
} // Trans
if (_lodash["default"].get(options, 'trans')) {
if (_lodash["default"].isUndefined(_lodash["default"].get(options, 'trans.component'))) {
_lodash["default"].set(options, 'trans.component', defaults.trans.component);
}
if (_lodash["default"].isUndefined(_lodash["default"].get(options, 'trans.i18nKey'))) {
_lodash["default"].set(options, 'trans.i18nKey', defaults.trans.i18nKey);
}
if (_lodash["default"].isUndefined(_lodash["default"].get(options, 'trans.defaultsKey'))) {
_lodash["default"].set(options, 'trans.defaultsKey', defaults.trans.defaultsKey);
}
if (_lodash["default"].isUndefined(_lodash["default"].get(options, 'trans.extensions'))) {
_lodash["default"].set(options, 'trans.extensions', defaults.trans.extensions);
}
if (_lodash["default"].isUndefined(_lodash["default"].get(options, 'trans.fallbackKey'))) {
_lodash["default"].set(options, 'trans.fallbackKey', defaults.trans.fallbackKey);
}
if (_lodash["default"].isUndefined(_lodash["default"].get(options, 'trans.acorn'))) {
_lodash["default"].set(options, 'trans.acorn', defaults.trans.acorn);
}
} // Resource
if (_lodash["default"].isUndefined(_lodash["default"].get(options, 'resource.loadPath'))) {
_lodash["default"].set(options, 'resource.loadPath', defaults.resource.loadPath);
}
if (_lodash["default"].isUndefined(_lodash["default"].get(options, 'resource.savePath'))) {
_lodash["default"].set(options, 'resource.savePath', defaults.resource.savePath);
}
if (_lodash["default"].isUndefined(_lodash["default"].get(options, 'resource.jsonIndent'))) {
_lodash["default"].set(options, 'resource.jsonIndent', defaults.resource.jsonIndent);
}
if (_lodash["default"].isUndefined(_lodash["default"].get(options, 'resource.lineEnding'))) {
_lodash["default"].set(options, 'resource.lineEnding', defaults.resource.lineEnding);
} // Accept both nsseparator or nsSeparator
if (!_lodash["default"].isUndefined(options.nsseparator)) {
options.nsSeparator = options.nsseparator;
delete options.nsseparator;
} // Allowed only string or false
if (!_lodash["default"].isString(options.nsSeparator)) {
options.nsSeparator = false;
} // Accept both keyseparator or keySeparator
if (!_lodash["default"].isUndefined(options.keyseparator)) {
options.keySeparator = options.keyseparator;
delete options.keyseparator;
} // Allowed only string or false
if (!_lodash["default"].isString(options.keySeparator)) {
options.keySeparator = false;
}
if (!_lodash["default"].isArray(options.ns)) {
options.ns = [options.ns];
}
options.ns = _lodash["default"].union(_lodash["default"].flatten(options.ns.concat(options.defaultNs)));
return options;
}; // Get an array of plural suffixes for a given language.
// @param {string} lng The language.
// @param {string} pluralSeparator pluralSeparator, default '_'.
// @return {array} An array of plural suffixes.
var getPluralSuffixes = function getPluralSuffixes(lng) {
var pluralSeparator = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '_';
var rule = _i18next["default"].services.pluralResolver.getRule(lng);
if (!(rule && rule.numbers)) {
return []; // Return an empty array if lng is not supported
}
if (rule.numbers.length === 1) {
return ["".concat(pluralSeparator, "0")];
}
if (rule.numbers.length === 2) {
return ['', "".concat(pluralSeparator, "plural")];
}
var suffixes = rule.numbers.reduce(function (acc, n, i) {
return acc.concat("".concat(pluralSeparator).concat(i));
}, []);
return suffixes;
};
/**
* Creates a new parser
* @constructor
*/
var Parser = /*#__PURE__*/function () {
// The resStore stores all translation keys including unused ones
// The resScan only stores translation keys parsed from code
// The all plurals suffixes for each of target languages.
function Parser(options) {
var _this = this;
_classCallCheck(this, Parser);
_defineProperty(this, "options", _objectSpread({}, defaults));
_defineProperty(this, "resStore", {});
_defineProperty(this, "resScan", {});
_defineProperty(this, "pluralSuffixes", {});
this.options = normalizeOptions(_objectSpread({}, this.options, {}, options));
var lngs = this.options.lngs;
var namespaces = this.options.ns;
lngs.forEach(function (lng) {
_this.resStore[lng] = _this.resStore[lng] || {};
_this.resScan[lng] = _this.resScan[lng] || {};
_this.pluralSuffixes[lng] = (0, _ensureArray["default"])(getPluralSuffixes(lng, _this.options.pluralSeparator));
if (_this.pluralSuffixes[lng].length === 0) {
_this.log("No plural rule found for: ".concat(lng));
}
namespaces.forEach(function (ns) {
var resPath = _this.formatResourceLoadPath(lng, ns);
_this.resStore[lng][ns] = {};
_this.resScan[lng][ns] = {};
try {
if (_fs["default"].existsSync(resPath)) {
_this.resStore[lng][ns] = JSON.parse(_fs["default"].readFileSync(resPath, 'utf-8'));
}
} catch (err) {
_this.error("Unable to load resource file ".concat(_chalk["default"].yellow(JSON.stringify(resPath)), ": lng=").concat(lng, ", ns=").concat(ns));
_this.error(err);
}
});
});
this.log("options=".concat(JSON.stringify(this.options, null, 2)));
}
_createClass(Parser, [{
key: "log",
value: function log() {
var debug = this.options.debug;
if (debug) {
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
console.log.apply(this, [_chalk["default"].cyan('i18next-scanner:')].concat(args));
}
}
}, {
key: "error",
value: function error() {
for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
args[_key2] = arguments[_key2];
}
console.error.apply(this, [_chalk["default"].red('i18next-scanner:')].concat(args));
}
}, {
key: "formatResourceLoadPath",
value: function formatResourceLoadPath(lng, ns) {
var options = this.options;
var regex = {
lng: new RegExp(_lodash["default"].escapeRegExp(options.interpolation.prefix + 'lng' + options.interpolation.suffix), 'g'),
ns: new RegExp(_lodash["default"].escapeRegExp(options.interpolation.prefix + 'ns' + options.interpolation.suffix), 'g')
};
return options.resource.loadPath.replace(regex.lng, lng).replace(regex.ns, ns);
}
}, {
key: "formatResourceSavePath",
value: function formatResourceSavePath(lng, ns) {
var options = this.options;
var regex = {
lng: new RegExp(_lodash["default"].escapeRegExp(options.interpolation.prefix + 'lng' + options.interpolation.suffix), 'g'),
ns: new RegExp(_lodash["default"].escapeRegExp(options.interpolation.prefix + 'ns' + options.interpolation.suffix), 'g')
};
return options.resource.savePath.replace(regex.lng, lng).replace(regex.ns, ns);
}
}, {
key: "fixStringAfterRegExp",
value: function fixStringAfterRegExp(strToFix) {
var fixedString = _lodash["default"].trim(strToFix); // Remove leading and trailing whitespace
var firstChar = fixedString[0]; // Ignore key with embedded expressions in string literals
if (firstChar === '`' && fixedString.match(/\${.*?}/)) {
return null;
}
if (_lodash["default"].includes(['\'', '"', '`'], firstChar)) {
// Remove first and last character
fixedString = fixedString.slice(1, -1);
} // restore multiline strings
fixedString = fixedString.replace(/(\\\n|\\\r\n)/g, ''); // JavaScript character escape sequences
// https://mathiasbynens.be/notes/javascript-escapes
// Single character escape sequences
// Note: IE < 9 treats '\v' as 'v' instead of a vertical tab ('\x0B'). If cross-browser compatibility is a concern, use \x0B instead of \v.
// Another thing to note is that the \v and \0 escapes are not allowed in JSON strings.
fixedString = fixedString.replace(/(\\b|\\f|\\n|\\r|\\t|\\v|\\0|\\\\|\\"|\\')/g, function (match) {
return eval("\"".concat(match, "\""));
}); // * Octal escapes have been deprecated in ES5.
// * Hexadecimal escape sequences: \\x[a-fA-F0-9]{2}
// * Unicode escape sequences: \\u[a-fA-F0-9]{4}
fixedString = fixedString.replace(/(\\x[a-fA-F0-9]{2}|\\u[a-fA-F0-9]{4})/g, function (match) {
return eval("\"".concat(match, "\""));
});
return fixedString;
} // i18next.t('ns:foo.bar') // matched
// i18next.t("ns:foo.bar") // matched
// i18next.t('ns:foo.bar') // matched
// i18next.t("ns:foo.bar", { count: 1 }); // matched
// i18next.t("ns:foo.bar" + str); // not matched
}, {
key: "parseFuncFromString",
value: function parseFuncFromString(content) {
var _this2 = this;
var opts = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
var customHandler = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
if (_lodash["default"].isFunction(opts)) {
customHandler = opts;
opts = {};
}
var funcs = opts.list !== undefined ? (0, _ensureArray["default"])(opts.list) : (0, _ensureArray["default"])(this.options.func.list);
if (funcs.length === 0) {
return this;
}
var matchFuncs = funcs.map(function (func) {
return '(?:' + func + ')';
}).join('|').replace(/\./g, '\\.'); // `\s` matches a single whitespace character, which includes spaces, tabs, form feeds, line feeds and other unicode spaces.
var matchSpecialCharacters = '[\\r\\n\\s]*';
var stringGroup = matchSpecialCharacters + '(' + // backtick (``)
'`(?:[^`\\\\]|\\\\(?:.|$))*`' + '|' + // double quotes ("")
'"(?:[^"\\\\]|\\\\(?:.|$))*"' + '|' + // single quote ('')
'\'(?:[^\'\\\\]|\\\\(?:.|$))*\'' + ')' + matchSpecialCharacters;
var pattern = '(?:(?:^\\s*)|[^a-zA-Z0-9_])' + '(?:' + matchFuncs + ')' + '\\(' + stringGroup + '(?:[\\,]' + stringGroup + ')?' + '[\\,\\)]';
var re = new RegExp(pattern, 'gim');
var r;
var _loop = function _loop() {
var options = {};
var full = r[0];
var key = _this2.fixStringAfterRegExp(r[1], true);
if (!key) {
return "continue";
}
if (r[2] !== undefined) {
var defaultValue = _this2.fixStringAfterRegExp(r[2], false);
if (!defaultValue) {
return "continue";
}
options.defaultValue = defaultValue;
}
var endsWithComma = full[full.length - 1] === ',';
if (endsWithComma) {
var _opts = _objectSpread({}, opts),
propsFilter = _opts.propsFilter;
var code = matchBalancedParentheses(content.substr(re.lastIndex));
if (typeof propsFilter === 'function') {
code = propsFilter(code);
}
try {
var syntax = code.trim() !== '' ? (0, _esprima.parse)('(' + code + ')') : {};
var props = _lodash["default"].get(syntax, 'body[0].expression.properties') || []; // http://i18next.com/docs/options/
var supportedOptions = ['defaultValue', 'defaultValue_plural', 'count', 'context', 'ns', 'keySeparator', 'nsSeparator'];
props.forEach(function (prop) {
if (_lodash["default"].includes(supportedOptions, prop.key.name)) {
if (prop.value.type === 'Literal') {
options[prop.key.name] = prop.value.value;
} else if (prop.value.type === 'TemplateLiteral') {
options[prop.key.name] = prop.value.quasis.map(function (element) {
return element.value.cooked;
}).join('');
} else {
// Unable to get value of the property
options[prop.key.name] = '';
}
}
});
} catch (err) {
_this2.error("Unable to parse code \"".concat(code, "\""));
_this2.error(err);
}
}
if (customHandler) {
customHandler(key, options);
return "continue";
}
_this2.set(key, options);
};
while (r = re.exec(content)) {
var _ret = _loop();
if (_ret === "continue") continue;
}
return this;
} // Parses translation keys from `Trans` components in JSX
//