Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

eslint-plugin-m6web-i18n

Package Overview
Dependencies
Maintainers
7
Versions
14
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

eslint-plugin-m6web-i18n - npm Package Compare versions

Comparing version 0.1.3 to 0.2.0

.editorconfig

11

lib/index.js
'use strict';
var noUnknownKey = require('./rules/no-unknown-key');
var noTextAsChildren = require('./rules/no-text-as-children');
var interpolationData = require('./rules/interpolation-data');
module.exports = {
rules: {
'no-unknown-key': require('./rules/no-unknown-key')('principalLangs'),
'no-unknown-key-secondary-langs': require('./rules/no-unknown-key')('secondaryLangs'),
'no-text-as-children': require('./rules/no-text-as-children')
'no-unknown-key': noUnknownKey('principalLangs'),
'no-unknown-key-secondary-langs': noUnknownKey('secondaryLangs'),
'no-text-as-children': noTextAsChildren,
'interpolation-data': interpolationData
}
};
'use strict';
module.exports = function (context) {
var config = context.settings.i18n;
var minimatch = require('minimatch');
if (!config || config.ignoreFiles && new RegExp(config.ignoreFiles).test(context.getFilename())) {
return {};
}
module.exports = {
meta: {
docs: {
description: 'ensures that no plain text is used in JSX components',
category: 'Possible errors'
},
schema: []
},
create: function create(context) {
var config = context.settings.i18n;
return {
JSXElement: function JSXElement(node) {
node.children.forEach(function (child) {
if (child.type === 'Literal') {
var text = child.raw.trim().replace('\\n', '');
if (text.length) {
context.report({ node: child, message: 'Untranslated text \'' + text + '\'' });
}
}
});
if (config && config.ignoreFiles && minimatch(context.getFilename(), config.ignoreFiles)) {
return {};
}
};
};
module.exports.schema = [];
return {
JSXElement: function JSXElement(node) {
node.children.forEach(function (child) {
if (child.type === 'Literal') {
var text = child.raw.trim().replace('\\n', '');
if (text.length) {
context.report({ node: child, message: 'Untranslated text \'' + text + '\'' });
}
}
});
}
};
}
};

@@ -5,79 +5,84 @@ 'use strict';

var _appRootPath = require('app-root-path');
var minimatch = require('minimatch');
var _appRootPath2 = _interopRequireDefault(_appRootPath);
var _require = require('../utils/utils'),
getKeyValue = _require.getKeyValue,
has = _require.has,
getLangConfig = _require.getLangConfig;
var _utils = require('../utils/utils');
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
module.exports = function (langsKey) {
return function (context) {
var langConfig = [];
var config = context.settings.i18n;
return {
meta: {
docs: {
description: 'ensures that used translate key is in translation file',
category: 'Possible errors'
},
schema: []
},
create: function create(context) {
var config = context.settings.i18n;
if (!config || config.ignoreFiles && new RegExp(config.ignoreFiles).test(context.getFilename())) {
return {};
}
if (!config || config.ignoreFiles && minimatch(context.getFilename(), config.ignoreFiles)) {
return {};
}
if (config && config[langsKey]) {
langConfig = config[langsKey].map(function (_ref) {
var name = _ref.name,
translationPath = _ref.translationPath;
return {
name: name,
translation: require(_appRootPath2.default + '/' + translationPath)
};
});
}
return {
CallExpression: function CallExpression(node) {
var funcName = node.callee.type === 'MemberExpression' && node.callee.property.name || node.callee.name;
return {
CallExpression: function CallExpression(node) {
var funcName = node.callee.type === 'MemberExpression' && node.callee.property.name || node.callee.name;
if (funcName !== config.functionName || !node.arguments || !node.arguments.length) {
return;
}
if (funcName !== config.functionName || !node.arguments || !node.arguments.length) {
return;
}
var _node$arguments = _slicedToArray(node.arguments, 3),
keyNode = _node$arguments[0],
countNode = _node$arguments[2];
var _node$arguments = _slicedToArray(node.arguments, 2),
keyNode = _node$arguments[0],
countNode = _node$arguments[1];
var key = getKeyValue(keyNode);
var key = (0, _utils.getKeyValue)(keyNode);
if (!key) {
return;
}
if (!key) {
return;
}
getLangConfig(config, langsKey).forEach(function (_ref) {
var name = _ref.name,
translation = _ref.translation;
if (typeof countNode === 'undefined') {
langConfig.forEach(function (_ref2) {
var name = _ref2.name,
translation = _ref2.translation;
if (!translation) {
context.report({
node: node,
severity: 2,
message: '\'' + name + '\' language is missing'
});
if (!(0, _utils.has)(translation, key)) {
context.report({ node: node, severity: 2, message: '\'' + key + '\' is missing from \'' + name + '\' language' });
return;
}
});
} else if (config.pluralizedKeys && config.pluralizedKeys.length) {
langConfig.forEach(function (_ref3) {
var name = _ref3.name,
translation = _ref3.translation;
var missingKeys = config.pluralizedKeys.reduce(function (accumulator, plural) {
return (0, _utils.has)(translation, key + '.' + plural) ? accumulator : accumulator.concat(plural);
}, []);
if (missingKeys.length) {
if (typeof countNode === 'undefined' && !has(translation, key)) {
context.report({
node: node,
message: '[' + missingKeys + '] keys are missing for key \'' + key + '\' in \'' + name + '\' language'
severity: 2,
message: '\'' + key + '\' is missing from \'' + name + '\' language'
});
return;
}
if (countNode && Array.isArray(config.pluralizedKeys)) {
var missingKeys = config.pluralizedKeys.filter(function (plural) {
return !has(translation, key + '.' + plural);
});
if (missingKeys.length) {
context.report({
node: node,
message: '[' + missingKeys + '] keys are missing for key \'' + key + '\' in \'' + name + '\' language'
});
}
}
});
}
}
};
};
}
};
};
module.exports.schema = [];
};
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _has = function _has(object, keys, index) {
var fs = require('fs');
var path = require('path');
var appRootPath = require('app-root-path');
var recursiveGet = function recursiveGet(object, keys, index) {
if (keys.length - index === 1) {
return !!object[keys[index]];
return object[keys[index]];
}
return object[keys[index]] ? _has(object[keys[index]], keys, index + 1) : false;
return object[keys[index]] ? recursiveGet(object[keys[index]], keys, index + 1) : undefined;
};
var has = exports.has = function has(object, key) {
return _has(object, key.split('.'), 0);
exports.has = function (object, key) {
return !!recursiveGet(object, key.split('.'), 0);
};
var getKeyValue = exports.getKeyValue = function getKeyValue(key) {
exports.get = function (object, key) {
return recursiveGet(object, key.split('.'), 0);
};
exports.getKeyValue = function (key) {
if (key.type === 'Literal') {

@@ -26,2 +31,30 @@ return key.value;

return null;
};
var langConfig = void 0;
var expireAt = 0;
exports.getLangConfig = function (config, languagesKey) {
if (expireAt <= Date.now() || config.disableCache) {
langConfig = config[languagesKey].map(function (_ref) {
var name = _ref.name,
translationPath = _ref.translationPath;
try {
var langFile = JSON.parse(fs.readFileSync(path.resolve(appRootPath + '/' + translationPath)).toString());
return {
name: name,
translation: langFile
};
} catch (e) {
return {
name: name,
translation: null
};
}
});
expireAt = Date.now() + (config.translationsCacheTTL || 500);
}
return langConfig;
};
{
"name": "eslint-plugin-m6web-i18n",
"version": "0.1.3",
"version": "0.2.0",
"description": "eslint plugin for generic i18n",

@@ -16,3 +16,5 @@ "author": "M6web",

"format": "prettier-eslint --write src/**/*.js",
"lint": "eslint src/**/*.js"
"lint": "eslint src/**/*.js",
"test": "mocha --recursive tests",
"prepack": "yarn lint && yarn test && yarn build"
},

@@ -33,9 +35,12 @@ "peerDependencies": {

"eslint-plugin-react": "^7.0.1",
"mocha": "^3.4.2",
"mversion": "^1.10.1",
"prettier": "^1.3.0",
"prettier-eslint-cli": "4.0.0"
"prettier-eslint-cli": "^4.0.0",
"sinon": "^2.3.7"
},
"dependencies": {
"app-root-path": "2.0.1"
"app-root-path": "2.0.1",
"minimatch": "3.0.4"
}
}

@@ -29,5 +29,8 @@ # eslint-plugin-m6web-i18n

* i18n/no-unknown-key: Verify that all translation keys you use are present in your primary translation files.
* i18n/no-unknown-key-secondary-langs: Same as the previous one. Allow you to have a different error level for secondary languages.
* i18n/no-text-as-children: Verify that you have no text children in your react code.
* **i18n/no-unknown-key**: Verify that all translation keys you use are present in your primary translation files.
* **i18n/no-unknown-key-secondary-langs**: Same as the previous one. Allow you to have a different error level for secondary languages.
* **i18n/no-text-as-children**: Verify that you have no text children in your react code.
* **i18n/interpolation-data**: Checks for usage of keys containing string interpolation, if translate function is called without
interpolation data it will show an error. Also if interpolation data is given and key doesn't contain interpolation it will also
show an error. `interpolationPattern` option is required to match interpolation in your translation file.

@@ -47,3 +50,4 @@ ## Config

"i18n/no-unknown-key-secondary-langs": "warn",
"i18n/no-text-as-children": "error"
"i18n/no-text-as-children": "error",
"i18n/interpolation-data": ["error", { "interpolationPattern": "\\{\\.+\\}" }]
},

@@ -76,7 +80,9 @@ // The plugin needs jsx feature to be on for 'no-text-as-children' rule

// If you want to ignore specific files
"ignoreFiles": "spec.js",
"ignoreFiles": "**/*.spec.js",
// If you have pluralization
"pluralizedKeys": ["one", "other"]
"pluralizedKeys": ["one", "other"],
// TTL of the translations file caching (defaults to 500ms)
"translationsCacheTTL": 300
}
}
```

@@ -0,7 +1,12 @@

const noUnknownKey = require('./rules/no-unknown-key');
const noTextAsChildren = require('./rules/no-text-as-children');
const interpolationData = require('./rules/interpolation-data');
module.exports = {
rules: {
'no-unknown-key': require('./rules/no-unknown-key')('principalLangs'),
'no-unknown-key-secondary-langs': require('./rules/no-unknown-key')('secondaryLangs'),
'no-text-as-children': require('./rules/no-text-as-children'),
}
'no-unknown-key': noUnknownKey('principalLangs'),
'no-unknown-key-secondary-langs': noUnknownKey('secondaryLangs'),
'no-text-as-children': noTextAsChildren,
'interpolation-data': interpolationData,
},
};

@@ -1,22 +0,31 @@

module.exports = context => {
const config = context.settings.i18n;
const minimatch = require('minimatch');
if (!config || (config.ignoreFiles && new RegExp(config.ignoreFiles).test(context.getFilename()))) {
return {};
}
module.exports = {
meta: {
docs: {
description: 'ensures that no plain text is used in JSX components',
category: 'Possible errors',
},
schema: [],
},
create(context) {
const config = context.settings.i18n;
return {
JSXElement(node) {
node.children.forEach(child => {
if (child.type === 'Literal') {
const text = child.raw.trim().replace('\\n', '');
if (text.length) {
context.report({ node: child, message: `Untranslated text '${text}'` });
if (config && config.ignoreFiles && minimatch(context.getFilename(), config.ignoreFiles)) {
return {};
}
return {
JSXElement(node) {
node.children.forEach(child => {
if (child.type === 'Literal') {
const text = child.raw.trim().replace('\\n', '');
if (text.length) {
context.report({ node: child, message: `Untranslated text '${text}'` });
}
}
}
});
},
};
});
},
};
},
};
module.exports.schema = [];

@@ -1,59 +0,69 @@

import appRootPath from 'app-root-path';
import { getKeyValue, has } from '../utils/utils';
const minimatch = require('minimatch');
const { getKeyValue, has, getLangConfig } = require('../utils/utils');
module.exports = langsKey => context => {
let langConfig = [];
const config = context.settings.i18n;
module.exports = langsKey => ({
meta: {
docs: {
description: 'ensures that used translate key is in translation file',
category: 'Possible errors',
},
schema: [],
},
create(context) {
const config = context.settings.i18n;
if (!config || (config.ignoreFiles && new RegExp(config.ignoreFiles).test(context.getFilename()))) {
return {};
}
if (!config || (config.ignoreFiles && minimatch(context.getFilename(), config.ignoreFiles))) {
return {};
}
if (config && config[langsKey]) {
langConfig = config[langsKey].map(({ name, translationPath }) => ({
name,
translation: require(`${appRootPath}/${translationPath}`),
}));
}
return {
CallExpression(node) {
const funcName = (node.callee.type === 'MemberExpression' && node.callee.property.name) || node.callee.name;
return {
CallExpression(node) {
const funcName = (node.callee.type === 'MemberExpression' && node.callee.property.name) || node.callee.name;
if (funcName !== config.functionName || !node.arguments || !node.arguments.length) {
return;
}
if (funcName !== config.functionName || !node.arguments || !node.arguments.length) {
return;
}
const [keyNode, , countNode] = node.arguments;
const key = getKeyValue(keyNode);
const [keyNode, countNode] = node.arguments;
const key = getKeyValue(keyNode);
if (!key) {
return;
}
if (!key) {
return;
}
getLangConfig(config, langsKey).forEach(({ name, translation }) => {
if (!translation) {
context.report({
node,
severity: 2,
message: `'${name}' language is missing`,
});
if (typeof countNode === 'undefined') {
langConfig.forEach(({ name, translation }) => {
if (!has(translation, key)) {
context.report({ node, severity: 2, message: `'${key}' is missing from '${name}' language` });
return;
}
});
} else if (config.pluralizedKeys && config.pluralizedKeys.length) {
langConfig.forEach(({ name, translation }) => {
const missingKeys = config.pluralizedKeys.reduce(
(accumulator, plural) => (has(translation, `${key}.${plural}`) ? accumulator : accumulator.concat(plural)),
[],
);
if (missingKeys.length) {
if (typeof countNode === 'undefined' && !has(translation, key)) {
context.report({
node,
message: `[${missingKeys}] keys are missing for key '${key}' in '${name}' language`,
severity: 2,
message: `'${key}' is missing from '${name}' language`,
});
return;
}
if (countNode && Array.isArray(config.pluralizedKeys)) {
const missingKeys = config.pluralizedKeys.filter(plural => !has(translation, `${key}.${plural}`));
if (missingKeys.length) {
context.report({
node,
message: `[${missingKeys}] keys are missing for key '${key}' in '${name}' language`,
});
}
}
});
}
},
};
};
module.exports.schema = [];
},
};
},
});

@@ -1,12 +0,18 @@

const _has = (object, keys, index) => {
const fs = require('fs');
const path = require('path');
const appRootPath = require('app-root-path');
const recursiveGet = (object, keys, index) => {
if (keys.length - index === 1) {
return !!object[keys[index]];
return object[keys[index]];
}
return object[keys[index]] ? _has(object[keys[index]], keys, index + 1) : false;
return object[keys[index]] ? recursiveGet(object[keys[index]], keys, index + 1) : undefined;
};
export const has = (object, key) => _has(object, key.split('.'), 0);
exports.has = (object, key) => !!recursiveGet(object, key.split('.'), 0);
export const getKeyValue = key => {
exports.get = (object, key) => recursiveGet(object, key.split('.'), 0);
exports.getKeyValue = key => {
if (key.type === 'Literal') {

@@ -20,1 +26,26 @@ return key.value;

};
let langConfig;
let expireAt = 0;
exports.getLangConfig = (config, languagesKey) => {
if (expireAt <= Date.now() || config.disableCache) {
langConfig = config[languagesKey].map(({ name, translationPath }) => {
try {
const langFile = JSON.parse(fs.readFileSync(path.resolve(`${appRootPath}/${translationPath}`)).toString());
return {
name,
translation: langFile,
};
} catch (e) {
return {
name,
translation: null,
};
}
});
expireAt = Date.now() + (config.translationsCacheTTL || 500);
}
return langConfig;
};

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc