Skip to content

Commit

Permalink
Add support for multiple locale directories (#114)
Browse files Browse the repository at this point in the history
* WIP: Add support for multiple locale directories

* Add testcases
  • Loading branch information
ota-meshi committed Aug 17, 2020
1 parent d54fc72 commit 24eca59
Show file tree
Hide file tree
Showing 31 changed files with 785 additions and 25 deletions.
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

0 comments on commit 24eca59

Please sign in to comment.