Skip to content

Commit e4947e8

Browse files
authored
list command feature (#74)
* list command feature * fix type errors
1 parent fd25954 commit e4947e8

File tree

6 files changed

+453
-6
lines changed

6 files changed

+453
-6
lines changed

README.md

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ yarn global vue-i18n-locale-message
6767
- status: indicate translation status from localization service
6868
- import: import locale messages to localization service
6969
- export: export locale messages from localization service
70+
- list: list undefined fields in locale messages
7071

7172
## :rocket: Usages
7273

@@ -173,13 +174,22 @@ $ vue-i18n-locale-message export --provider=l10n-service-provider \
173174
--output=./src/locales
174175
```
175176

177+
#### list
178+
179+
```sh
180+
vue-i18n-locale-message list --locale=en \
181+
--target-paths=./src/locales/*.json \
182+
--filename-match=^([\\w]*)\\.json
183+
```
184+
176185

177186
## :red_car: Exit codes
178187

179-
| Codes | Description |
180-
|-------|----------------------------------------------------|
181-
| 4 | Not completed translation |
182-
| 64 | difference between local and localization services |
188+
| Codes | Description |
189+
|-------|----------------------------------------------------------------------|
190+
| 4 | Not completed localization |
191+
| 5 | There are undefined fields in locale messages |
192+
| 64 | difference between local and localization services |
183193

184194

185195
## :book: API: Specifications

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,16 +28,18 @@
2828
"@vue/component-compiler-utils": "^3.0.0",
2929
"debug": "^4.1.1",
3030
"deep-diff": "^1.0.2",
31+
"flat": "^5.0.0",
3132
"glob": "^7.1.4",
3233
"js-yaml": "^3.13.1",
33-
"json5": "^2.1.0",
3434
"json-diff": "^0.5.4",
35+
"json5": "^2.1.0",
3536
"vue-template-compiler": "^2.6.10",
3637
"yargs": "^15.0.0"
3738
},
3839
"devDependencies": {
3940
"@types/debug": "^4.1.4",
4041
"@types/deep-diff": "^1.0.0",
42+
"@types/flat": "^0.0.28",
4143
"@types/glob": "^7.1.1",
4244
"@types/jest": "^24.0.24",
4345
"@types/js-yaml": "^3.12.1",

src/commands/list.ts

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
import { flatten, unflatten } from 'flat'
2+
import glob from 'glob'
3+
import path from 'path'
4+
import fs from 'fs'
5+
import { promisify } from 'util'
6+
import { Arguments, Argv } from 'yargs'
7+
8+
import {
9+
resolve,
10+
getLocaleMessages
11+
} from '../utils'
12+
13+
import {
14+
Locale,
15+
LocaleMessage,
16+
LocaleMessages
17+
} from '../../types'
18+
19+
type ListOptions = {
20+
locale: string
21+
target?: string
22+
targetPaths?: string
23+
filenameMatch?: string
24+
indent: number
25+
define: boolean
26+
}
27+
28+
type ListFileInfo = {
29+
path: string
30+
locale: Locale
31+
message: LocaleMessage
32+
}
33+
34+
import { debug as Debug } from 'debug'
35+
const debug = Debug('vue-i18n-locale-message:commands:list')
36+
37+
const writeFile = promisify(fs.writeFile)
38+
39+
class LocaleMessageUndefindError extends Error {}
40+
41+
export const command = 'list'
42+
export const aliases = 'lt'
43+
export const describe = 'List undefined fields in locale messages'
44+
45+
export const builder = (args: Argv): Argv<ListOptions> => {
46+
return args
47+
.option('locale', {
48+
type: 'string',
49+
alias: 'l',
50+
describe: `the main locale of locale messages`,
51+
demandOption: true
52+
})
53+
.option('target', {
54+
type: 'string',
55+
alias: 't',
56+
describe: 'target path that locale messages file is stored, default list with the filename of target path as locale'
57+
})
58+
.option('targetPaths', {
59+
type: 'string',
60+
alias: 'T',
61+
describe: 'target directory paths that locale messages files is stored, Can also be specified multi paths with comma delimiter'
62+
})
63+
.option('filenameMatch', {
64+
type: 'string',
65+
alias: 'm',
66+
describe: `option should be accepted a regex filenames, must be specified together --targetPaths if it's directory path of locale messages`
67+
})
68+
.option('define', {
69+
type: 'boolean',
70+
alias: 'd',
71+
default: false,
72+
describe: `if there are undefined fields in the target locale messages, define them with empty string and save them`
73+
})
74+
.option('indent', {
75+
type: 'number',
76+
alias: 'i',
77+
default: 2,
78+
describe: `option for indent of locale message, if you need to adjust with --define option`
79+
})
80+
.fail((msg, err) => {
81+
if (msg) {
82+
// TODO: should refactor console message
83+
console.error(msg)
84+
process.exit(1)
85+
} else {
86+
if (err instanceof LocaleMessageUndefindError) {
87+
// TODO: should refactor console message
88+
console.warn(err.message)
89+
process.exit(5)
90+
} else {
91+
if (err) throw err
92+
}
93+
}
94+
})
95+
}
96+
97+
export const handler = async (args: Arguments<ListOptions>): Promise<unknown> => {
98+
const { locale, define } = args
99+
if (!args.target && !args.targetPaths) {
100+
// TODO: should refactor console message
101+
console.error('You need to specify either --target or --target-paths')
102+
return
103+
}
104+
105+
const localeMessages = getLocaleMessages(args)
106+
const flattedLocaleMessages = {} as LocaleMessages
107+
Object.keys(localeMessages).forEach(locale => {
108+
flattedLocaleMessages[locale] = flatten(localeMessages[locale])
109+
})
110+
111+
const mainLocaleMessage = flattedLocaleMessages[locale]
112+
if (!mainLocaleMessage) {
113+
console.error(`Not found main '${locale}' locale message`)
114+
return
115+
}
116+
117+
let valid = true
118+
Object.keys(flattedLocaleMessages).forEach(l => {
119+
const message = flattedLocaleMessages[l] as { [prop: string]: LocaleMessage }
120+
if (!message) {
121+
console.log(`Not found '${l}' locale messages`)
122+
valid = false
123+
} else {
124+
Object.keys(mainLocaleMessage).forEach(key => {
125+
if (!message[key]) {
126+
console.log(`${l}: '${key}' undefined`)
127+
valid = false
128+
if (define) {
129+
message[key] = ''
130+
}
131+
}
132+
})
133+
}
134+
})
135+
136+
if (!define && !valid) {
137+
return Promise.reject(new LocaleMessageUndefindError('there are undefined fields in the target locale messages, you can define with --define option'))
138+
}
139+
140+
const unflattedLocaleMessages = {} as LocaleMessages
141+
Object.keys(flattedLocaleMessages).forEach(locale => {
142+
unflattedLocaleMessages[locale] = unflatten(flattedLocaleMessages[locale])
143+
})
144+
145+
await tweakLocaleMessages(unflattedLocaleMessages, args)
146+
147+
return Promise.resolve()
148+
}
149+
150+
async function tweakLocaleMessages (messages: LocaleMessages, args: Arguments<ListOptions>) {
151+
const targets = [] as ListFileInfo[]
152+
153+
if (args.target) {
154+
const targetPath = resolve(args.target)
155+
const parsed = path.parse(targetPath)
156+
const locale = parsed.name
157+
targets.push({
158+
path: targetPath,
159+
locale,
160+
message: messages[locale]
161+
})
162+
} else if (args.targetPaths) {
163+
const filenameMatch = args.filenameMatch
164+
if (!filenameMatch) {
165+
// TODO: should refactor console message
166+
throw new Error('You need to specify together --filename-match')
167+
}
168+
const targetPaths = args.targetPaths.split(',').filter(p => p)
169+
targetPaths.forEach(targetPath => {
170+
const globedPaths = glob.sync(targetPath).map(p => resolve(p))
171+
globedPaths.forEach(fullPath => {
172+
const parsed = path.parse(fullPath)
173+
const re = new RegExp(filenameMatch, 'ig')
174+
const match = re.exec(parsed.base)
175+
debug('regex match', match, fullPath)
176+
if (match && match[1]) {
177+
const locale = match[1]
178+
if (args.locale !== locale) {
179+
targets.push({
180+
path: fullPath,
181+
locale,
182+
message: messages[locale]
183+
})
184+
}
185+
} else {
186+
// TODO: should refactor console message
187+
console.log(`${fullPath} is not matched with ${filenameMatch}`)
188+
}
189+
})
190+
})
191+
}
192+
193+
if (args.define) {
194+
for (const fileInfo of targets) {
195+
await writeFile(fileInfo.path, JSON.stringify(fileInfo.message, null, args.indent))
196+
}
197+
}
198+
}
199+
200+
export default {
201+
command,
202+
aliases,
203+
describe,
204+
builder,
205+
handler
206+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`--indent option 1`] = `
4+
Object {
5+
"ja.json": "{
6+
\\"foo\\": \\"\\",
7+
\\"bar\\": {
8+
\\"buz\\": \\"\\"
9+
}
10+
}",
11+
}
12+
`;

0 commit comments

Comments
 (0)