Skip to content

Commit 21017fd

Browse files
authored
Change "settings['vue-i18n'].localeDir" to can specify object options. (#75)
1 parent cab4c01 commit 21017fd

File tree

31 files changed

+484
-104
lines changed

31 files changed

+484
-104
lines changed

docs/started.md

+15
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,28 @@ module.export = {
3535
settings: {
3636
'vue-i18n': {
3737
localeDir: './path/to/locales/*.json' // extention is glob formatting!
38+
// or
39+
// localeDir: {
40+
// pattern: './path/to/locales/*.json', // extention is glob formatting!
41+
// localeKey: 'file' // or 'key'
42+
// }
3843
}
3944
}
4045
}
4146
```
4247

4348
See [the rule list](../rules/)
4449

50+
### `settings['vue-i18n']`
51+
52+
- `localeDir` ... You can specify a string or an object.
53+
- String option ... A glob for specifying files that store localization messages of project.
54+
- Object option
55+
- `pattern` (`string`) ... A glob for specifying files that store localization messages of project.
56+
- `localeKey` (`'file' | 'key'`) ... Specifies how to determine the locale for localization messages.
57+
- `'file'` ... Determine the locale name from the filename. The resource file should only contain messages for that locale. Use this option if you use `vue-cli-plugin-i18n`. This option is also used when String option is specified.
58+
- `'key'` ... Determine the locale name from the root key name of the file contents. The value of that key should only contain messages for that locale. Used when the resource file is in the format given to the `messages` option of the `VueI18n` constructor option.
59+
4560
### Running ESLint from command line
4661

4762
If you want to run `eslint` from command line, make sure you include the `.vue` and `.json` extension using [the `--ext` option](https://eslint.org/docs/user-guide/configuring#specifying-file-extensions-to-lint) or a glob pattern because ESLint targets only `.js` files by default.

lib/rules/no-html-messages.js

+1-2
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ const { extname } = require('path')
77
const parse5 = require('parse5')
88
const {
99
UNEXPECTED_ERROR_LOCATION,
10-
findExistLocaleMessage,
1110
getLocaleMessages,
1211
extractJsonInfo,
1312
generateJsonAst
@@ -55,7 +54,7 @@ function create (context) {
5554
}
5655

5756
const localeMessages = getLocaleMessages(settings['vue-i18n'].localeDir)
58-
const targetLocaleMessage = findExistLocaleMessage(filename, localeMessages)
57+
const targetLocaleMessage = localeMessages.findExistLocaleMessage(filename)
5958
if (!targetLocaleMessage) {
6059
debug(`ignore ${filename} in no-html-messages`)
6160
return {}

lib/rules/no-missing-keys.js

+20-18
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,7 @@
66
const {
77
UNEXPECTED_ERROR_LOCATION,
88
defineTemplateBodyVisitor,
9-
getLocaleMessages,
10-
findMissingsFromLocaleMessages
9+
getLocaleMessages
1110
} = require('../utils/index')
1211

1312
function create (context) {
@@ -25,61 +24,61 @@ function create (context) {
2524

2625
return defineTemplateBodyVisitor(context, {
2726
"VAttribute[directive=true][key.name='t']" (node) {
28-
checkDirective(context, localeDir, localeMessages, node)
27+
checkDirective(context, localeMessages, node)
2928
},
3029

3130
"VAttribute[directive=true][key.name.name='t']" (node) {
32-
checkDirective(context, localeDir, localeMessages, node)
31+
checkDirective(context, localeMessages, node)
3332
},
3433

3534
"VElement[name=i18n] > VStartTag > VAttribute[key.name='path']" (node) {
36-
checkComponent(context, localeDir, localeMessages, node)
35+
checkComponent(context, localeMessages, node)
3736
},
3837

3938
"VElement[name=i18n] > VStartTag > VAttribute[key.name.name='path']" (node) {
40-
checkComponent(context, localeDir, localeMessages, node)
39+
checkComponent(context, localeMessages, node)
4140
},
4241

4342
CallExpression (node) {
44-
checkCallExpression(context, localeDir, localeMessages, node)
43+
checkCallExpression(context, localeMessages, node)
4544
}
4645
}, {
4746
CallExpression (node) {
48-
checkCallExpression(context, localeDir, localeMessages, node)
47+
checkCallExpression(context, localeMessages, node)
4948
}
5049
})
5150
}
5251

53-
function checkDirective (context, localeDir, localeMessages, node) {
52+
function checkDirective (context, localeMessages, node) {
5453
if ((node.value && node.value.type === 'VExpressionContainer') &&
5554
(node.value.expression && node.value.expression.type === 'Literal')) {
5655
const key = node.value.expression.value
5756
if (!key) {
5857
// TODO: should be error
5958
return
6059
}
61-
const missings = findMissingsFromLocaleMessages(localeMessages, key, localeDir)
60+
const missings = localeMessages.findMissingPaths(key)
6261
if (missings.length) {
63-
missings.forEach(missing => context.report({ node, ...missing }))
62+
missings.forEach((data) => context.report({ node, messageId: 'missing', data }))
6463
}
6564
}
6665
}
6766

68-
function checkComponent (context, localeDir, localeMessages, node) {
67+
function checkComponent (context, localeMessages, node) {
6968
if (node.value && node.value.type === 'VLiteral') {
7069
const key = node.value.value
7170
if (!key) {
7271
// TODO: should be error
7372
return
7473
}
75-
const missings = findMissingsFromLocaleMessages(localeMessages, key, localeDir)
74+
const missings = localeMessages.findMissingPaths(key)
7675
if (missings.length) {
77-
missings.forEach(missing => context.report({ node, ...missing }))
76+
missings.forEach((data) => context.report({ node, messageId: 'missing', data }))
7877
}
7978
}
8079
}
8180

82-
function checkCallExpression (context, localeDir, localeMessages, node) {
81+
function checkCallExpression (context, localeMessages, node) {
8382
const funcName = (node.callee.type === 'MemberExpression' && node.callee.property.name) || node.callee.name
8483

8584
if (!/^(\$t|t|\$tc|tc)$/.test(funcName) || !node.arguments || !node.arguments.length) {
@@ -95,9 +94,9 @@ function checkCallExpression (context, localeDir, localeMessages, node) {
9594
return
9695
}
9796

98-
const missings = findMissingsFromLocaleMessages(localeMessages, key, localeDir)
97+
const missings = localeMessages.findMissingPaths(key)
9998
if (missings.length) {
100-
missings.forEach(missing => context.report({ node, ...missing }))
99+
missings.forEach((data) => context.report({ node, messageId: 'missing', data }))
101100
}
102101
}
103102

@@ -110,7 +109,10 @@ module.exports = {
110109
recommended: true
111110
},
112111
fixable: null,
113-
schema: []
112+
schema: [],
113+
messages: {
114+
missing: "'{{path}}' does not exist in '{{locale}}'"
115+
}
114116
},
115117
create
116118
}

lib/rules/no-unused-keys.js

+21-5
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,38 @@ const flatten = require('flat')
99
const collectKeys = require('../utils/collect-keys')
1010
const {
1111
UNEXPECTED_ERROR_LOCATION,
12-
findExistLocaleMessage,
1312
getLocaleMessages,
1413
extractJsonInfo,
1514
generateJsonAst
1615
} = require('../utils/index')
1716
const debug = require('debug')('eslint-plugin-vue-i18n:no-unused-keys')
1817

18+
/**
19+
* @typedef {import('../utils/locale-messages').LocaleMessage} LocaleMessage
20+
*/
21+
1922
let usedLocaleMessageKeys = null // used locale message keys
2023

21-
function getUnusedKeys (context, json, usedkeys) {
24+
/**
25+
* @param {RuleContext} context
26+
* @param {LocaleMessage} targetLocaleMessage
27+
* @param {string} json
28+
* @param {object} usedkeys
29+
*/
30+
function getUnusedKeys (context, targetLocaleMessage, json, usedkeys) {
2231
let unusedKeys = []
2332

2433
try {
34+
let compareKeys = { ...usedkeys }
35+
if (targetLocaleMessage.localeKey === 'key') {
36+
compareKeys = targetLocaleMessage.locales.reduce((keys, locale) => {
37+
keys[locale] = usedkeys
38+
return keys
39+
}, {})
40+
}
2541
const jsonValue = JSON.parse(json)
2642
const diffValue = jsonDiffPatch.diff(
27-
flatten(usedkeys, { safe: true }),
43+
flatten(compareKeys, { safe: true }),
2844
flatten(jsonValue, { safe: true })
2945
)
3046
const diffLocaleMessage = flatten(diffValue, { safe: true })
@@ -89,7 +105,7 @@ function create (context) {
89105
}
90106

91107
const localeMessages = getLocaleMessages(settings['vue-i18n'].localeDir)
92-
const targetLocaleMessage = findExistLocaleMessage(filename, localeMessages)
108+
const targetLocaleMessage = localeMessages.findExistLocaleMessage(filename)
93109
if (!targetLocaleMessage) {
94110
debug(`ignore ${filename} in no-unused-keys`)
95111
return {}
@@ -111,7 +127,7 @@ function create (context) {
111127
const ast = generateJsonAst(context, jsonString, jsonFilename)
112128
if (!ast) { return }
113129

114-
const unusedKeys = getUnusedKeys(context, jsonString, usedLocaleMessageKeys)
130+
const unusedKeys = getUnusedKeys(context, targetLocaleMessage, jsonString, usedLocaleMessageKeys)
115131
if (!unusedKeys) { return }
116132

117133
traverseJsonAstWithUnusedKeys(unusedKeys, ast, (fullpath, node) => {

lib/utils/index.js

+40-35
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,31 @@
77
const glob = require('glob')
88
const { resolve } = require('path')
99
const jsonAstParse = require('json-to-ast')
10+
const { LocaleMessage, LocaleMessages } = require('./locale-messages')
1011

1112
const UNEXPECTED_ERROR_LOCATION = { line: 1, column: 0 }
1213

14+
/**
15+
* How to determine the locale for localization messages.
16+
* - `'file'` ... Determine the locale name from the filename. The resource file should only contain messages for that locale.
17+
* Use this option if you use `vue-cli-plugin-i18n`. This method is also used when String option is specified.
18+
* - `'key'` ... Determine the locale name from the root key name of the file contents. The value of that key should only contain messages for that locale.
19+
* Used when the resource file is in the format given to the `messages` option of the `VueI18n` constructor option.
20+
* @typedef {'file' | 'key'} LocaleKeyType
21+
*/
22+
/**
23+
* Type of `settings['vue-i18n'].localeDir`
24+
* @typedef {SettingsVueI18nLocaleDirGlob | SettingsVueI18nLocaleDirObject} SettingsVueI18nLocaleDir
25+
* @typedef {string} SettingsVueI18nLocaleDirGlob A glob for specifying files that store localization messages of project.
26+
* @typedef {object} SettingsVueI18nLocaleDirObject Specifies a glob and messages format type.
27+
* @property {string} pattern A glob for specifying files that store localization messages of project.
28+
* @property {LocaleKeyType} localeKey Specifies how to determine the locale for localization messages.
29+
*/
30+
/**
31+
* @typedef {import('./locale-messages').LocaleMessage} LocaleMessage
32+
* @typedef {import('./locale-messages').LocaleMessages} LocaleMessages
33+
*/
34+
1335
/**
1436
* Register the given visitor to parser services.
1537
* Borrow from GitHub `vuejs/eslint-plugin-vue` repo
@@ -26,23 +48,29 @@ function defineTemplateBodyVisitor (context, templateBodyVisitor, scriptVisitor)
2648
return context.parserServices.defineTemplateBodyVisitor(templateBodyVisitor, scriptVisitor)
2749
}
2850

29-
function findExistLocaleMessage (fullpath, localeMessages) {
30-
return localeMessages.find(message => message.fullpath === fullpath)
31-
}
32-
33-
function loadLocaleMessages (pattern) {
34-
const files = glob.sync(pattern)
35-
return files.map(file => {
36-
const path = resolve(process.cwd(), file)
37-
const filename = file.replace(/^.*(\\|\/|:)/, '')
38-
const messages = require(path)
39-
return { fullpath: path, path: file, filename, messages }
40-
})
51+
/**
52+
* @param {SettingsVueI18nLocaleDir} localeDir
53+
* @returns {LocaleMessages}
54+
*/
55+
function loadLocaleMessages (localeDir) {
56+
if (typeof localeDir === 'string') {
57+
return loadLocaleMessages({ pattern: localeDir, localeKey: 'file' })
58+
} else {
59+
const files = glob.sync(localeDir.pattern)
60+
return new LocaleMessages(files.map(file => {
61+
const fullpath = resolve(process.cwd(), file)
62+
return new LocaleMessage({ fullpath, path: file, localeKey: localeDir.localeKey || 'file' })
63+
}))
64+
}
4165
}
4266

4367
let localeMessages = null // locale messages
4468
let localeDir = null // locale dir
4569

70+
/**
71+
* @param {SettingsVueI18nLocaleDir} localeDirectory
72+
* @returns {LocaleMessages}
73+
*/
4674
function getLocaleMessages (localeDirectory) {
4775
if (localeDir !== localeDirectory) {
4876
localeDir = localeDirectory
@@ -53,27 +81,6 @@ function getLocaleMessages (localeDirectory) {
5381
return localeMessages
5482
}
5583

56-
function findMissingsFromLocaleMessages (localeMessages, key) {
57-
const missings = []
58-
const paths = key.split('.')
59-
localeMessages.forEach(localeMessage => {
60-
const length = paths.length
61-
let last = localeMessage.messages
62-
let i = 0
63-
while (i < length) {
64-
const value = last && last[paths[i]]
65-
if (value === undefined) {
66-
missings.push({
67-
message: `'${key}' does not exist`
68-
})
69-
}
70-
last = value
71-
i++
72-
}
73-
})
74-
return missings
75-
}
76-
7784
function extractJsonInfo (context, node) {
7885
try {
7986
const [str, filename] = node.comments
@@ -110,8 +117,6 @@ module.exports = {
110117
UNEXPECTED_ERROR_LOCATION,
111118
defineTemplateBodyVisitor,
112119
getLocaleMessages,
113-
findMissingsFromLocaleMessages,
114-
findExistLocaleMessage,
115120
extractJsonInfo,
116121
generateJsonAst
117122
}

0 commit comments

Comments
 (0)