Skip to content

Commit 28a3098

Browse files
authored
Use @intlify/message-compiler and supports vue-i18n v9 message format (#145)
* Use `@intlify/message-compiler` and supports vue-i18n v9 format * update * Add `messageSyntaxVersion` to setting. If you specify `'^9.0.0'`, only `@intlify/message-compiler` will parse the message. * fix getMessageSyntaxVersions * fix getMessageSyntaxVersions * update
1 parent d5810c3 commit 28a3098

File tree

15 files changed

+1220
-69
lines changed

15 files changed

+1220
-69
lines changed

.eslintrc.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ module.exports = {
1717
ecmaVersion: 2015
1818
},
1919
rules: {
20-
'object-shorthand': 'error'
20+
'object-shorthand': 'error',
21+
'no-debugger': 'error'
2122
},
2223
overrides: [
2324
{

docs/started.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ module.export = {
3838
},
3939
settings: {
4040
'vue-i18n': {
41-
localeDir: './path/to/locales/*.{json,json5,yaml,yml}' // extension is glob formatting!
41+
localeDir: './path/to/locales/*.{json,json5,yaml,yml}', // extension is glob formatting!
4242
// or
4343
// localeDir: {
4444
// pattern: './path/to/locales/*.{json,json5,yaml,yml}', // extension is glob formatting!
@@ -55,6 +55,10 @@ module.export = {
5555
// localeKey: 'file' // or 'key'
5656
// },
5757
// ]
58+
59+
// Specify the version of `vue-i18n` you are using.
60+
// If not specified, the message will be parsed twice.
61+
messageSyntaxVersion: '^9.0.0'
5862
}
5963
}
6064
}
@@ -72,6 +76,7 @@ See [the rule list](../rules/)
7276
- `'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.
7377
- `'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.
7478
- Array option ... An array of String option and Object option. Useful if you have multiple locale directories.
79+
- `messageSyntaxVersion` (Optional) ... Specify the version of `vue-i18n` you are using. If not specified, the message will be parsed twice.
7580

7681
### Running ESLint from command line
7782

lib/rules/no-unused-keys.ts

Lines changed: 22 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ import type {
1717
RuleFixer,
1818
Fix,
1919
SourceCode,
20-
Range
20+
Range,
21+
CustomBlockVisitorFactory
2122
} from '../types'
2223
import { joinPath } from '../utils/key-path'
2324
const debug = debugBuilder('eslint-plugin-vue-i18n:no-unused-keys')
@@ -46,12 +47,13 @@ function isDef<V>(v: V | null | undefined): v is V {
4647
function getUsedKeysMap(
4748
targetLocaleMessage: LocaleMessage,
4849
values: I18nLocaleMessageDictionary,
49-
usedkeys: string[]
50+
usedkeys: string[],
51+
context: RuleContext
5052
): UsedKeys {
5153
/** @type {UsedKeys} */
5254
const usedKeysMap: UsedKeys = {}
5355

54-
for (const key of [...usedkeys, ...collectLinkedKeys(values)]) {
56+
for (const key of [...usedkeys, ...collectLinkedKeys(values, context)]) {
5557
const paths = key.split('.')
5658
let map = usedKeysMap
5759
while (paths.length) {
@@ -481,29 +483,13 @@ function create(context: RuleContext): RuleListener {
481483
}
482484

483485
if (extname(filename) === '.vue') {
484-
return defineCustomBlocksVisitor(
485-
context,
486-
ctx => {
487-
const localeMessages = getLocaleMessages(context)
488-
const usedLocaleMessageKeys = collectKeysFromAST(
489-
context.getSourceCode().ast as VAST.ESLintProgram,
490-
context.getSourceCode().visitorKeys
491-
)
492-
const targetLocaleMessage = localeMessages.findBlockLocaleMessage(
493-
ctx.parserServices.customBlock
494-
)
495-
if (!targetLocaleMessage) {
496-
return {}
497-
}
498-
const usedKeys = getUsedKeysMap(
499-
targetLocaleMessage,
500-
targetLocaleMessage.messages,
501-
usedLocaleMessageKeys
502-
)
503-
504-
return createVisitorForJson(ctx.getSourceCode(), usedKeys)
505-
},
506-
ctx => {
486+
const createCustomBlockRule = (
487+
createVisitor: (
488+
sourceCode: SourceCode,
489+
usedKeys: UsedKeys
490+
) => RuleListener
491+
): CustomBlockVisitorFactory => {
492+
return ctx => {
507493
const localeMessages = getLocaleMessages(context)
508494
const usedLocaleMessageKeys = collectKeysFromAST(
509495
context.getSourceCode().ast as VAST.ESLintProgram,
@@ -518,11 +504,17 @@ function create(context: RuleContext): RuleListener {
518504
const usedKeys = getUsedKeysMap(
519505
targetLocaleMessage,
520506
targetLocaleMessage.messages,
521-
usedLocaleMessageKeys
507+
usedLocaleMessageKeys,
508+
context
522509
)
523510

524-
return createVisitorForYaml(ctx.getSourceCode(), usedKeys)
511+
return createVisitor(ctx.getSourceCode(), usedKeys)
525512
}
513+
}
514+
return defineCustomBlocksVisitor(
515+
context,
516+
createCustomBlockRule(createVisitorForJson),
517+
createCustomBlockRule(createVisitorForYaml)
526518
)
527519
} else if (context.parserServices.isJSON || context.parserServices.isYAML) {
528520
const localeMessages = getLocaleMessages(context)
@@ -543,7 +535,8 @@ function create(context: RuleContext): RuleListener {
543535
const usedKeys = getUsedKeysMap(
544536
targetLocaleMessage,
545537
targetLocaleMessage.messages,
546-
usedLocaleMessageKeys
538+
usedLocaleMessageKeys,
539+
context
547540
)
548541
if (context.parserServices.isJSON) {
549542
return createVisitorForJson(sourceCode, usedKeys)

lib/types/eslint.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ export interface RuleContext {
3232
settings: {
3333
'vue-i18n'?: {
3434
localeDir?: SettingsVueI18nLocaleDir
35+
messageSyntaxVersion?: string
3536
}
3637
}
3738
parserPath: string

lib/utils/collect-linked-keys.ts

Lines changed: 39 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,43 +2,36 @@
22
* @fileoverview Collect the keys used by the linked messages.
33
* @author Yosuke Ota
44
*/
5-
// Note: If vue-i18n@next parser is separated from vue plugin, change it to use that.
6-
7-
import type { I18nLocaleMessageDictionary } from '../types'
8-
9-
const linkKeyMatcher = /(?:@(?:\.[a-z]+)?:(?:[\w\-_|.]+|\([\w\-_|.]+\)))/g
10-
const linkKeyPrefixMatcher = /^@(?:\.([a-z]+))?:/
11-
const bracketsMatcher = /[()]/g
5+
import type { ResourceNode } from '@intlify/message-compiler'
6+
import { NodeTypes } from '@intlify/message-compiler'
7+
import { traverseNode } from './message-compiler/traverser'
8+
import type { I18nLocaleMessageDictionary, RuleContext } from '../types'
9+
import { parse } from './message-compiler/parser'
10+
import { parse as parseForV8 } from './message-compiler/parser-v8'
11+
import type { MessageSyntaxVersions } from './message-compiler/utils'
12+
import { getMessageSyntaxVersions } from './message-compiler/utils'
1213

1314
/**
1415
* Extract the keys used by the linked messages.
1516
* @param {any} object
1617
* @returns {IterableIterator<string>}
1718
*/
1819
function* extractUsedKeysFromLinks(
19-
object: I18nLocaleMessageDictionary
20+
object: I18nLocaleMessageDictionary,
21+
messageSyntaxVersions: MessageSyntaxVersions
2022
): IterableIterator<string> {
2123
for (const value of Object.values(object)) {
2224
if (!value) {
2325
continue
2426
}
2527
if (typeof value === 'object') {
26-
yield* extractUsedKeysFromLinks(value)
28+
yield* extractUsedKeysFromLinks(value, messageSyntaxVersions)
2729
} else if (typeof value === 'string') {
28-
if (value.indexOf('@:') >= 0 || value.indexOf('@.') >= 0) {
29-
// see https://github.com/kazupon/vue-i18n/blob/c07d1914dcac186291b658a8b9627732010f6848/src/index.js#L435
30-
const matches = value.match(linkKeyMatcher)!
31-
for (const idx in matches) {
32-
const link = matches[idx]
33-
const linkKeyPrefixMatches = link.match(linkKeyPrefixMatcher)!
34-
const [linkPrefix] = linkKeyPrefixMatches
35-
36-
// Remove the leading @:, @.case: and the brackets
37-
const linkPlaceholder = link
38-
.replace(linkPrefix, '')
39-
.replace(bracketsMatcher, '')
40-
yield linkPlaceholder
41-
}
30+
if (messageSyntaxVersions.v9) {
31+
yield* extractUsedKeysFromAST(parse(value).ast)
32+
}
33+
if (messageSyntaxVersions.v8) {
34+
yield* extractUsedKeysFromAST(parseForV8(value).ast)
4235
}
4336
}
4437
}
@@ -50,7 +43,28 @@ function* extractUsedKeysFromLinks(
5043
* @returns {string[]}
5144
*/
5245
export function collectLinkedKeys(
53-
object: I18nLocaleMessageDictionary
46+
object: I18nLocaleMessageDictionary,
47+
context: RuleContext
5448
): string[] {
55-
return [...new Set<string>(extractUsedKeysFromLinks(object))]
49+
return [
50+
...new Set<string>(
51+
extractUsedKeysFromLinks(object, getMessageSyntaxVersions(context))
52+
)
53+
].filter(s => !!s)
54+
}
55+
56+
function extractUsedKeysFromAST(ast: ResourceNode): Set<string> {
57+
const keys = new Set<string>()
58+
traverseNode(ast, node => {
59+
if (node.type === NodeTypes.Linked) {
60+
if (node.key.type === NodeTypes.LinkedKey) {
61+
keys.add(node.key.value)
62+
} else if (node.key.type === NodeTypes.Literal) {
63+
keys.add(node.key.value)
64+
} else if (node.key.type === NodeTypes.List) {
65+
keys.add(String(node.key.index))
66+
}
67+
}
68+
})
69+
return keys
5670
}

0 commit comments

Comments
 (0)