Skip to content

Commit

Permalink
⭐ new: locale messages split and match options
Browse files Browse the repository at this point in the history
closes #11
  • Loading branch information
kazupon committed Sep 23, 2019
1 parent b7e8bbf commit adb69fa
Show file tree
Hide file tree
Showing 7 changed files with 290 additions and 11 deletions.
34 changes: 29 additions & 5 deletions src/commands/infuse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import { resolve, parsePath, readSFC } from '../utils'
import infuse from '../infuser'
import squeeze from '../squeezer'
import fs from 'fs'
import path from 'path'
import { applyDiff } from 'deep-diff'
import glob from 'glob'
import { LocaleMessages, SFCFileInfo, MetaLocaleMessage, Locale } from '../../types'

import { debug as Debug } from 'debug'
Expand All @@ -13,6 +15,7 @@ const debug = Debug('vue-i18n-locale-message:commands:infuse')
type InfuseOptions = {
target: string
messages: string
match?: string
}

export const command = 'infuse'
Expand All @@ -33,23 +36,44 @@ export const builder = (args: Argv): Argv<InfuseOptions> => {
describe: 'locale messages path to be infused',
demandOption: true
})
.option('match', {
type: 'string',
alias: 'r',
describe: 'option should be accepted a regex filenames, must be specified together --messages'
})
}

export const handler = (args: Arguments<InfuseOptions>): void => {
const targetPath = resolve(args.target)
const messagesPath = resolve(args.messages)
const sources = readSFC(targetPath)
const messages = readLocaleMessages(messagesPath)
const messages = readLocaleMessages(messagesPath, args.match)
const meta = squeeze(targetPath, sources)
apply(messages, meta)
const newSources = infuse(targetPath, sources, meta)
writeSFC(newSources)
}

function readLocaleMessages (path: string): LocaleMessages {
// TODO: async implementation
const data = fs.readFileSync(path, { encoding: 'utf8' })
return JSON.parse(data) as LocaleMessages
function readLocaleMessages (targetPath: string, matchRegex?: string): LocaleMessages {
debug('readLocaleMessages', targetPath, matchRegex)
if (!matchRegex) {
const data = fs.readFileSync(targetPath, { encoding: 'utf8' })
return JSON.parse(data) as LocaleMessages
} else {
const globPath = path.normalize(`${targetPath}/*.json`)
const paths = glob.sync(globPath)
return paths.reduce((messages, p) => {
const re = new RegExp(matchRegex, 'ig')
const filename = path.basename(p)
const match = re.exec(filename)
debug('regex match', match)
if (match) {
const data = fs.readFileSync(p, { encoding: 'utf8' })
Object.assign(messages, { [match[1]]: JSON.parse(data) })
}
return messages
}, {} as LocaleMessages)
}
}

function removeItem<T> (item: T, items: T[]): boolean {
Expand Down
39 changes: 36 additions & 3 deletions src/commands/squeeze.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const debug = Debug('vue-i18n-locale-message:commands:squeeze')

type SqueezeOptions = {
target: string
split?: boolean
output: string
}

Expand All @@ -26,6 +27,12 @@ export const builder = (args: Argv): Argv<SqueezeOptions> => {
describe: 'target path that single-file components is stored',
demandOption: true
})
.option('split', {
type: 'boolean',
alias: 's',
default: false,
describe: 'split squeezed locale messages with locale'
})
.option('output', {
type: 'string',
alias: 'o',
Expand All @@ -38,7 +45,7 @@ export const handler = (args: Arguments<SqueezeOptions>): void => {
const targetPath = resolve(args.target)
const meta = squeeze(targetPath, readSFC(targetPath))
const messages = generate(meta)
writeLocaleMessages(resolve(args.output), messages)
writeLocaleMessages(messages, args)
}

function generate (meta: MetaLocaleMessage): LocaleMessages {
Expand Down Expand Up @@ -85,9 +92,35 @@ function generate (meta: MetaLocaleMessage): LocaleMessages {
return messages
}

function writeLocaleMessages (output: string, messages: LocaleMessages) {
function writeLocaleMessages (messages: LocaleMessages, args: Arguments<SqueezeOptions>) {
// TODO: async implementation
fs.writeFileSync(output, JSON.stringify(messages, null, 2))
const split = args.split
const output = resolve(args.output)
if (!split) {
fs.writeFileSync(output, JSON.stringify(messages, null, 2))
} else {
splitLocaleMessages(output, messages)
}
}

function splitLocaleMessages (path: string, messages: LocaleMessages) {
const locales: Locale[] = Object.keys(messages)
const write = () => {
locales.forEach(locale => {
fs.writeFileSync(`${path}/${locale}.json`, JSON.stringify(messages[locale], null, 2))
})
}
try {
fs.mkdirSync(path)
write()
} catch (err) {
if (err.code === 'EEXIST') {
write()
} else {
console.error(err.message)
throw err
}
}
}

export default {
Expand Down
6 changes: 5 additions & 1 deletion test/__snapshots__/cli.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ Options:
--help Show help [boolean]
--target, -t target path that single-file components is stored
[string] [required]
--messages, -m locale messages path to be infused [string] [required]"
--messages, -m locale messages path to be infused [string] [required]
--match, -r option should be accepted a regex filenames, must be specified
together --messages [string]"
`;

exports[`squeeze command output help 1`] = `
Expand All @@ -23,6 +25,8 @@ Options:
--help Show help [boolean]
--target, -t target path that single-file components is stored
[string] [required]
--split, -s split squeezed locale messages with locale
[boolean] [default: false]
--output, -o path to output squeezed locale messages
[string] [default: \\"/path/to/project1/messages.json\\"]"
`;
Expand Down
113 changes: 113 additions & 0 deletions test/commands/__snapshots__/infuse.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,119 @@ exports[`absolute path: /path/to/project1/src/pages/Login.vue 1`] = `
</i18n>
<i18n locale=\\"en\\">
{
\\"id\\": \\"User ID\\",
\\"password\\": \\"Password\\",
\\"confirm\\": \\"Confirm Password\\",
\\"button\\": \\"Login\\"
}
</i18n>"
`;
exports[`match option: /path/to/project1/src/App.vue 1`] = `
"
<template>
<p>template</p>
</template>
<script>
export default {}
</script>
<i18n>
{
\\"en\\": {
\\"title\\": \\"Application\\"
},
\\"ja\\": {
\\"title\\": \\"アプリケーション\\"
}
}
</i18n>
"
`;
exports[`match option: /path/to/project1/src/components/Modal.vue 1`] = `
"
<template>
<p>template</p>
</template>
<script>
export default {}
</script>
<i18n locale=\\"en\\">
{
\\"ok\\": \\"OK\\",
\\"cancel\\": \\"Cancel\\"
}
</i18n>
<i18n locale=\\"ja\\">
{
\\"ok\\": \\"OK\\",
\\"cancel\\": \\"キャンセル\\"
}
</i18n>
"
`;
exports[`match option: /path/to/project1/src/components/nest/RankingTable.vue 1`] = `
"
<template>
<p>template</p>
</template>
<script>
export default {}
</script>
<i18n locale=\\"en\\">
{
\\"headers\\": {
\\"rank\\": \\"Rank\\",
\\"name\\": \\"Name\\",
\\"score\\": \\"Score\\"
}
}
</i18n>
<i18n locale=\\"ja\\">
{
\\"headers\\": {
\\"rank\\": \\"ランク\\",
\\"name\\": \\"名前\\",
\\"score\\": \\"スコア\\"
}
}
</i18n>"
`;
exports[`match option: /path/to/project1/src/pages/Login.vue 1`] = `
"
<template>
<p>template</p>
</template>
<script>
export default {}
</script>
<i18n>
{
\\"ja\\": {
\\"id\\": \\"ユーザーID\\",
\\"passowrd\\": \\"パスワード\\",
\\"confirm\\": \\"パスワードの確認入力\\",
\\"button\\": \\"ログイン\\"
}
}
</i18n>
<i18n locale=\\"en\\">
{
\\"id\\": \\"User ID\\",
Expand Down
44 changes: 44 additions & 0 deletions test/commands/__snapshots__/squeeze.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -91,3 +91,47 @@ Object {
}",
}
`;

exports[`split option 1`] = `
Object {
"/path/to/project1/locales/en.json": "{
\\"App\\": {
\\"title\\": \\"Application\\"
},
\\"components\\": {
\\"Modal\\": {
\\"ok\\": \\"OK\\",
\\"cancel\\": \\"Cancel\\"
},
\\"nest\\": {
\\"RankingTable\\": {
\\"headers\\": {
\\"rank\\": \\"Rank\\",
\\"name\\": \\"Name\\",
\\"score\\": \\"Score\\"
}
}
}
}
}",
"/path/to/project1/locales/ja.json": "{
\\"App\\": {
\\"title\\": \\"アプリケーション\\"
},
\\"components\\": {
\\"Modal\\": {
\\"ok\\": \\"OK\\",
\\"cancel\\": \\"キャンセル\\"
}
},
\\"pages\\": {
\\"Login\\": {
\\"id\\": \\"ユーザーID\\",
\\"passowrd\\": \\"パスワード\\",
\\"confirm\\": \\"パスワードの確認入力\\",
\\"button\\": \\"ログイン\\"
}
}
}",
}
`;
47 changes: 46 additions & 1 deletion test/commands/infuse.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as yargs from 'yargs'
import jsonMetaInfo from '../fixtures/meta/json'
import json from '../fixtures/squeeze'
import path from 'path'

// -------
// mocking
Expand All @@ -12,6 +13,10 @@ const SFC_FILES = [
`${TARGET_PATH}/src/components/nest/RankingTable.vue`,
`${TARGET_PATH}/src/pages/Login.vue`
]
const LOCALE_FILES = [
`${TARGET_PATH}/src/locales/ja.json`,
`${TARGET_PATH}/src/locales/en.json`
]
const MOCK_FILES = SFC_FILES.reduce((files, file) => {
const meta = jsonMetaInfo.find(meta => meta.contentPath === file)
return Object.assign(files, { [file]: meta.raw })
Expand All @@ -36,7 +41,13 @@ jest.mock('fs', () => ({
import fs from 'fs'

// mock: glob
jest.mock('glob', () => ({ sync: jest.fn(() => SFC_FILES) }))
jest.mock('glob', () => ({ sync: jest.fn((pattern) => {
if (`${TARGET_PATH}/src/locales/*.json` === pattern) {
return LOCALE_FILES
} else {
return SFC_FILES
}
}) }))

// -------------------
// setup/teadown hooks
Expand Down Expand Up @@ -119,3 +130,37 @@ test('relative path', async () => {
expect(value).toMatchSnapshot(key)
}
})

test('match option', async () => {
// setup mocks
const mockUtils = utils as jest.Mocked<typeof utils>
mockUtils.resolve
.mockImplementationOnce(() => `${TARGET_PATH}/src`)
.mockImplementationOnce((...paths) => `${TARGET_PATH}/${paths[0]}`)
const writeFiles = {}
const mockFS = fs as jest.Mocked<typeof fs>
mockFS.readFileSync.mockImplementation(p => {
if (MOCK_FILES[p as string]) {
return MOCK_FILES[p as string]
} else {
return JSON.stringify(json[path.basename(p as string, '.json')])
}
})
mockFS.writeFileSync.mockImplementation((path, data) => {
writeFiles[path as string] = data.toString()
})

// run
const infuse = await import('../../src/commands/infuse')
const cmd = yargs.command(infuse)
const output = await new Promise(resolve => {
cmd.parse(`infuse --target=./src --messages=./src/locales --match=^([\\w-]*)\\.json`, () => {
resolve(writeFiles)
})
})

// check
for (const [key, value] of Object.entries(output)) {
expect(value).toMatchSnapshot(key)
}
})

0 comments on commit adb69fa

Please sign in to comment.