Skip to content

Commit

Permalink
list command feature (#74)
Browse files Browse the repository at this point in the history
* list command feature

* fix type errors
  • Loading branch information
kazupon committed Jan 21, 2020
1 parent fd25954 commit e4947e8
Show file tree
Hide file tree
Showing 6 changed files with 453 additions and 6 deletions.
18 changes: 14 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ yarn global vue-i18n-locale-message
- status: indicate translation status from localization service
- import: import locale messages to localization service
- export: export locale messages from localization service
- list: list undefined fields in locale messages

## :rocket: Usages

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

#### list

```sh
vue-i18n-locale-message list --locale=en \
--target-paths=./src/locales/*.json \
--filename-match=^([\\w]*)\\.json
```


## :red_car: Exit codes

| Codes | Description |
|-------|----------------------------------------------------|
| 4 | Not completed translation |
| 64 | difference between local and localization services |
| Codes | Description |
|-------|----------------------------------------------------------------------|
| 4 | Not completed localization |
| 5 | There are undefined fields in locale messages |
| 64 | difference between local and localization services |


## :book: API: Specifications
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,18 @@
"@vue/component-compiler-utils": "^3.0.0",
"debug": "^4.1.1",
"deep-diff": "^1.0.2",
"flat": "^5.0.0",
"glob": "^7.1.4",
"js-yaml": "^3.13.1",
"json5": "^2.1.0",
"json-diff": "^0.5.4",
"json5": "^2.1.0",
"vue-template-compiler": "^2.6.10",
"yargs": "^15.0.0"
},
"devDependencies": {
"@types/debug": "^4.1.4",
"@types/deep-diff": "^1.0.0",
"@types/flat": "^0.0.28",
"@types/glob": "^7.1.1",
"@types/jest": "^24.0.24",
"@types/js-yaml": "^3.12.1",
Expand Down
206 changes: 206 additions & 0 deletions src/commands/list.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
import { flatten, unflatten } from 'flat'
import glob from 'glob'
import path from 'path'
import fs from 'fs'
import { promisify } from 'util'
import { Arguments, Argv } from 'yargs'

import {
resolve,
getLocaleMessages
} from '../utils'

import {
Locale,
LocaleMessage,
LocaleMessages
} from '../../types'

type ListOptions = {
locale: string
target?: string
targetPaths?: string
filenameMatch?: string
indent: number
define: boolean
}

type ListFileInfo = {
path: string
locale: Locale
message: LocaleMessage
}

import { debug as Debug } from 'debug'
const debug = Debug('vue-i18n-locale-message:commands:list')

const writeFile = promisify(fs.writeFile)

class LocaleMessageUndefindError extends Error {}

export const command = 'list'
export const aliases = 'lt'
export const describe = 'List undefined fields in locale messages'

export const builder = (args: Argv): Argv<ListOptions> => {
return args
.option('locale', {
type: 'string',
alias: 'l',
describe: `the main locale of locale messages`,
demandOption: true
})
.option('target', {
type: 'string',
alias: 't',
describe: 'target path that locale messages file is stored, default list with the filename of target path as locale'
})
.option('targetPaths', {
type: 'string',
alias: 'T',
describe: 'target directory paths that locale messages files is stored, Can also be specified multi paths with comma delimiter'
})
.option('filenameMatch', {
type: 'string',
alias: 'm',
describe: `option should be accepted a regex filenames, must be specified together --targetPaths if it's directory path of locale messages`
})
.option('define', {
type: 'boolean',
alias: 'd',
default: false,
describe: `if there are undefined fields in the target locale messages, define them with empty string and save them`
})
.option('indent', {
type: 'number',
alias: 'i',
default: 2,
describe: `option for indent of locale message, if you need to adjust with --define option`
})
.fail((msg, err) => {
if (msg) {
// TODO: should refactor console message
console.error(msg)
process.exit(1)
} else {
if (err instanceof LocaleMessageUndefindError) {
// TODO: should refactor console message
console.warn(err.message)
process.exit(5)
} else {
if (err) throw err
}
}
})
}

export const handler = async (args: Arguments<ListOptions>): Promise<unknown> => {
const { locale, define } = args
if (!args.target && !args.targetPaths) {
// TODO: should refactor console message
console.error('You need to specify either --target or --target-paths')
return
}

const localeMessages = getLocaleMessages(args)
const flattedLocaleMessages = {} as LocaleMessages
Object.keys(localeMessages).forEach(locale => {
flattedLocaleMessages[locale] = flatten(localeMessages[locale])
})

const mainLocaleMessage = flattedLocaleMessages[locale]
if (!mainLocaleMessage) {
console.error(`Not found main '${locale}' locale message`)
return
}

let valid = true
Object.keys(flattedLocaleMessages).forEach(l => {
const message = flattedLocaleMessages[l] as { [prop: string]: LocaleMessage }
if (!message) {
console.log(`Not found '${l}' locale messages`)
valid = false
} else {
Object.keys(mainLocaleMessage).forEach(key => {
if (!message[key]) {
console.log(`${l}: '${key}' undefined`)
valid = false
if (define) {
message[key] = ''
}
}
})
}
})

if (!define && !valid) {
return Promise.reject(new LocaleMessageUndefindError('there are undefined fields in the target locale messages, you can define with --define option'))
}

const unflattedLocaleMessages = {} as LocaleMessages
Object.keys(flattedLocaleMessages).forEach(locale => {
unflattedLocaleMessages[locale] = unflatten(flattedLocaleMessages[locale])
})

await tweakLocaleMessages(unflattedLocaleMessages, args)

return Promise.resolve()
}

async function tweakLocaleMessages (messages: LocaleMessages, args: Arguments<ListOptions>) {
const targets = [] as ListFileInfo[]

if (args.target) {
const targetPath = resolve(args.target)
const parsed = path.parse(targetPath)
const locale = parsed.name
targets.push({
path: targetPath,
locale,
message: messages[locale]
})
} else if (args.targetPaths) {
const filenameMatch = args.filenameMatch
if (!filenameMatch) {
// TODO: should refactor console message
throw new Error('You need to specify together --filename-match')
}
const targetPaths = args.targetPaths.split(',').filter(p => p)
targetPaths.forEach(targetPath => {
const globedPaths = glob.sync(targetPath).map(p => resolve(p))
globedPaths.forEach(fullPath => {
const parsed = path.parse(fullPath)
const re = new RegExp(filenameMatch, 'ig')
const match = re.exec(parsed.base)
debug('regex match', match, fullPath)
if (match && match[1]) {
const locale = match[1]
if (args.locale !== locale) {
targets.push({
path: fullPath,
locale,
message: messages[locale]
})
}
} else {
// TODO: should refactor console message
console.log(`${fullPath} is not matched with ${filenameMatch}`)
}
})
})
}

if (args.define) {
for (const fileInfo of targets) {
await writeFile(fileInfo.path, JSON.stringify(fileInfo.message, null, args.indent))
}
}
}

export default {
command,
aliases,
describe,
builder,
handler
}
12 changes: 12 additions & 0 deletions test/commands/__snapshots__/list.test.ts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`--indent option 1`] = `
Object {
"ja.json": "{
\\"foo\\": \\"\\",
\\"bar\\": {
\\"buz\\": \\"\\"
}
}",
}
`;
Loading

0 comments on commit e4947e8

Please sign in to comment.