Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for multiple locale directories #114

Merged
merged 2 commits into from
Aug 17, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion docs/started.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,17 @@ module.export = {
// pattern: './path/to/locales/*.{json,json5,yaml,yml}', // extension is glob formatting!
// localeKey: 'file' // or 'key'
// }
// or
// localeDir: [
// {
// pattern: './path/to/locales1/*.{json,json5,yaml,yml}',
// localeKey: 'file' // or 'key'
// },
// {
// pattern: './path/to/locales2/*.{json,json5,yaml,yml}',
// localeKey: 'file' // or 'key'
// },
// ]
}
}
}
Expand All @@ -53,13 +64,14 @@ See [the rule list](../rules/)

### `settings['vue-i18n']`

- `localeDir` ... You can specify a string or an object.
- `localeDir` ... You can specify a string or an object or an array.
- String option ... A glob for specifying files that store localization messages of project.
- Object option
- `pattern` (`string`) ... A glob for specifying files that store localization messages of project.
- `localeKey` (`'file' | 'key'`) ... Specifies how to determine the locale for localization messages.
- `'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.
- `'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.
- Array option ... An array of String option and Object option. Useful if you have multiple locale directories.

### Running ESLint from command line

Expand Down
1 change: 1 addition & 0 deletions lib/types/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export type LocaleKeyType = 'file' | 'key'
export type SettingsVueI18nLocaleDir =
| SettingsVueI18nLocaleDirGlob
| SettingsVueI18nLocaleDirObject
| (SettingsVueI18nLocaleDirGlob | SettingsVueI18nLocaleDirObject)[]
/**
* A glob for specifying files that store localization messages of project.
*/
Expand Down
75 changes: 52 additions & 23 deletions lib/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,15 @@ import type {
TemplateListener,
RuleListener,
LocaleKeyType,
SettingsVueI18nLocaleDir
SettingsVueI18nLocaleDir,
SettingsVueI18nLocaleDirObject,
SettingsVueI18nLocaleDirGlob
} from '../types'

interface LocaleFiles {
files: string[]
localeKey: LocaleKeyType
}
const UNEXPECTED_ERROR_LOCATION = { line: 1, column: 0 }
/**
* Register the given visitor to parser services.
Expand Down Expand Up @@ -48,16 +54,24 @@ export function defineTemplateBodyVisitor(
)
}

/**
* @param {string[]} files
* @param {LocaleKeyType} localeKey
* @returns {LocaleMessage[]}
*/
function loadLocaleMessages(files: string[], localeKey: LocaleKeyType) {
return files.map(file => {
const fullpath = resolve(process.cwd(), file)
return new FileLocaleMessage({ fullpath, localeKey })
})
function loadLocaleMessages(
localeFilesList: LocaleFiles[],
cwd: string
): FileLocaleMessage[] {
const results: FileLocaleMessage[] = []
const checkDupeMap: { [file: string]: LocaleKeyType[] } = {}
for (const { files, localeKey } of localeFilesList) {
for (const file of files) {
const localeKeys = checkDupeMap[file] || (checkDupeMap[file] = [])
if (localeKeys.includes(localeKey)) {
continue
}
localeKeys.push(localeKey)
const fullpath = resolve(cwd, file)
results.push(new FileLocaleMessage({ fullpath, localeKey }))
}
}
return results
}

/** @type {Set<RuleContext>} */
Expand Down Expand Up @@ -105,34 +119,49 @@ export function getLocaleMessages(context: RuleContext): LocaleMessages {
class LocaleDirLocaleMessagesCache {
private _targetFilesLoader: CacheLoader<[string, string], string[]>
private _loadLocaleMessages: (
files: string[],
localeKey: LocaleKeyType,
localeFilesList: LocaleFiles[],
cwd: string
) => FileLocaleMessage[]
constructor() {
this._targetFilesLoader = new CacheLoader(pattern => glob.sync(pattern))

this._loadLocaleMessages = defineCacheFunction((files, localeKey) => {
return loadLocaleMessages(files, localeKey)
})
this._loadLocaleMessages = defineCacheFunction(
(localeFilesList: LocaleFiles[], cwd) => {
return loadLocaleMessages(localeFilesList, cwd)
}
)
}
/**
* @param {SettingsVueI18nLocaleDir} localeDir
* @returns {LocaleMessage[]}
*/
getLocaleMessagesFromLocaleDir(localeDir: SettingsVueI18nLocaleDir) {
const cwd = process.cwd()
let localeFilesList: LocaleFiles[]
if (Array.isArray(localeDir)) {
localeFilesList = localeDir.map(dir => this._toLocaleFiles(dir, cwd))
} else {
localeFilesList = [this._toLocaleFiles(localeDir, cwd)]
}
return this._loadLocaleMessages(localeFilesList, cwd)
}

private _toLocaleFiles(
localeDir: SettingsVueI18nLocaleDirGlob | SettingsVueI18nLocaleDirObject,
cwd: string
): LocaleFiles {
const targetFilesLoader = this._targetFilesLoader
let files
let localeKey: LocaleKeyType
if (typeof localeDir === 'string') {
files = targetFilesLoader.get(localeDir, cwd)
localeKey = 'file'
return {
files: targetFilesLoader.get(localeDir, cwd),
localeKey: 'file'
}
} else {
files = targetFilesLoader.get(localeDir.pattern, cwd)
localeKey = String(localeDir.localeKey ?? 'file') as LocaleKeyType
return {
files: targetFilesLoader.get(localeDir.pattern, cwd),
localeKey: String(localeDir.localeKey ?? 'file') as LocaleKeyType
}
}
return this._loadLocaleMessages(files, localeKey, cwd)
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"hello": "hello",
"messages": {
"foo": "foo",
"bar": "bar",
"baz": "baz",
"dupe": "dupe"
},
"dupe": "dupe"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"good-bye": "good bye",
"messages": {
"qux": "qux",
"quux": "quux",
"corge": "corge",
"dupe": "dupe"
},
"dupe": "dupe"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"ja": {
"hello": "こんにちは",
"messages": {
"foo": "foo",
"bar": "bar",
"baz": "baz"
},
"dupe": "dupe"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
'ja':
'good-bye': 'さようなら'
'messages':
'qux': 'qux'
'quux': 'quux'
'corge': 'corge'
'dupe': 'dupe'
'dupe': 'dupe'
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<i18n lang="yaml">
en:
block: block
nest:
foo
ja:
block: block
nest:
foo: bar
</i18n>
<i18n lang="yaml">
en:
nest:
foo: bar
ja:
dupe: dupe
</i18n>
<i18n lang="json">
{
"en": {
"json-dupe": "dupe",
"nest": {
"json-dupe": "dupe"
},
"nest": {
"json-dupe": "dupe"
},
"json-dupe": "dupe"
}
}
</i18n>
<template></template>
<script></script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"hello": "hello",
"messages": {
"foo": "foo",
"bar": "bar",
"baz": "baz"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"good-bye": "good bye",
"messages": {
"qux": "qux",
"quux": "quux",
"corge": "corge"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"ja": {
"hello": "こんにちは",
"messages": {
"foo": "foo",
"bar": "bar",
"baz": "baz"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"ja": {
"good-bye": "さようなら",
"messages": {
"qux": "qux",
"quux": "quux",
"corge": "corge"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<i18n locale="en" lang="yaml">
block: block
</i18n>
<i18n locale="ja" lang="yaml">
block: block
</i18n>
<template></template>
<script></script>
11 changes: 11 additions & 0 deletions tests/fixtures/no-missing-keys/multiple-locales/locales1/en.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"hello": "hello world",
"messages": {
"hello": "hi DIO!",
"link": "@:message.hello",
"nested": {
"hello": "hi jojo!"
},
"en-only": "en-only"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"ja": {
"hello": "ハローワールド",
"messages": {
"hello": "こんにちは、DIO!",
"link": "@:message.hello",
"nested": {
"hello": "こんにちは、ジョジョ!"
}
},
"hello_dio": "こんにちは、アンダースコア DIO!",
"hello {name}": "こんにちは、{name}!",
"hello-dio": "こんにちは、ハイフン DIO!"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"hello_dio": "hello underscore DIO!",
"hello {name}": "hello {name}!",
"hello-dio": "hello hyphen DIO!"
}
14 changes: 14 additions & 0 deletions tests/fixtures/no-missing-keys/multiple-locales/src/App.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<template>
<div id="app">
<p v-t="'hello_dio'">{{ $t('messages.hello') }}</p>
</div>
</template>

<script>
export default {
name: 'App',
created() {
this.$i18n.t('hello {name}', { name: 'DIO' })
}
}
</script>
2 changes: 2 additions & 0 deletions tests/fixtures/no-missing-keys/multiple-locales/src/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
const $t = () => {}
$t('hello')
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"hello": "hello world",
"messages": {
"hello": "hi DIO!",
"link": "@:message.hello",
"nested": {
"hello": "hi jojo!"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"hello_dio": "hello underscore DIO!",
"hello {name}": "hello {name}!",
"hello-dio": "hello hyphen DIO!"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"ja": {
"hello": "ハローワールド",
"messages": {
"hello": "こんにちは、DIO!",
"link": "@:message.hello",
"nested": {
"hello": "こんにちは、ジョジョ!"
}
},
"hello_dio": "こんにちは、アンダースコア DIO!",
"hello {name}": "こんにちは、{name}!",
"hello-dio": "こんにちは、ハイフン DIO!"
}
}
14 changes: 14 additions & 0 deletions tests/fixtures/no-unused-keys/invalid/multiple-locales/src/App.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<template>
<div id="app">
<p v-t="'hello_dio'">{{ $t('messages.hello') }}</p>
</div>
</template>

<script>
export default {
name: 'App',
created() {
this.$i18n.t('hello {name}', { name: 'DIO' })
}
}
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
const $t = () => {}
$t('hello')
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"hello": "hello world",
"messages": {
"hello": "hi DIO!",
"link": "@:messages.hello"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"hello_dio": "hello underscore DIO!",
"hello {name}": "hello {name}!",
"term": "I accept xxx {0}."
}
Loading