Skip to content

Commit bab14a4

Browse files
authored
feat: bundle and namespace features (#78)
* refactor fail * add utils tests * bundle & namespace feature implementation
1 parent 7cbdbe0 commit bab14a4

35 files changed

+1630
-612
lines changed

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,17 +28,19 @@
2828
"@vue/component-compiler-utils": "^3.0.0",
2929
"debug": "^4.1.1",
3030
"deep-diff": "^1.0.2",
31+
"deepmerge": "^4.2.2",
3132
"flat": "^5.0.0",
3233
"glob": "^7.1.4",
3334
"js-yaml": "^3.13.1",
3435
"json-diff": "^0.5.4",
3536
"json5": "^2.1.0",
3637
"vue-template-compiler": "^2.6.10",
37-
"yargs": "^15.0.0"
38+
"yargs": "^15.1.0"
3839
},
3940
"devDependencies": {
4041
"@types/debug": "^4.1.4",
4142
"@types/deep-diff": "^1.0.0",
43+
"@types/deepmerge": "^2.2.0",
4244
"@types/flat": "^0.0.28",
4345
"@types/glob": "^7.1.1",
4446
"@types/jest": "^24.0.24",

src/commands/diff.ts

Lines changed: 6 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Arguments, Argv } from 'yargs'
22
const { diffString } = require('json-diff') // NOTE: not provided type definition ...
3+
import { DiffError, fail } from './fails/diff'
34

45
import {
56
resolveProviderConf,
@@ -22,8 +23,6 @@ export const command = 'diff'
2223
export const aliases = 'df'
2324
export const describe = 'Diff locale messages between local and localization service'
2425

25-
class DiffError extends Error {}
26-
2726
export const builder = (args: Argv): Argv<DiffOptions> => {
2827
return args
2928
.option('provider', {
@@ -62,37 +61,22 @@ export const builder = (args: Argv): Argv<DiffOptions> => {
6261
alias: 'n',
6362
describe: 'option for the locale messages structure, you can specify the option, if you hope to normalize for the provider.'
6463
})
65-
.fail((msg, err) => {
66-
if (msg) {
67-
// TODO: should refactor console message
68-
console.error(msg)
69-
process.exit(1)
70-
} else {
71-
if (err instanceof DiffError) {
72-
// TODO: should refactor console message
73-
console.warn(err.message)
74-
process.exit(64)
75-
} else {
76-
// TODO: should refactor console message
77-
console.error(err.message)
78-
process.exit(1)
79-
}
80-
}
81-
})
64+
.fail(fail)
8265
}
8366

8467
export const handler = async (args: Arguments<DiffOptions>): Promise<unknown> => {
8568
const { normalize } = args
8669
const format = 'json'
70+
8771
const ProviderFactory = loadProvider(args.provider)
8872

8973
if (ProviderFactory === null) {
90-
throw new Error(`Not found ${args.provider} provider`)
74+
return Promise.reject(new Error(`Not found ${args.provider} provider`))
9175
}
9276

9377
if (!args.target && !args.targetPaths) {
9478
// TODO: should refactor console message
95-
throw new Error('You need to specify either --target or --target-paths')
79+
return Promise.reject(new Error('You need to specify either --target or --target-paths'))
9680
}
9781

9882
const confPath = resolveProviderConf(args.provider, args.conf)
@@ -108,7 +92,7 @@ export const handler = async (args: Arguments<DiffOptions>): Promise<unknown> =>
10892
console.log(ret)
10993

11094
if (ret) {
111-
throw new DiffError('There are differences!')
95+
return Promise.reject(new DiffError('There are differences!'))
11296
}
11397

11498
return Promise.resolve()

src/commands/fails/diff.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { ExitCode } from './exitcode'
2+
import defineFail from './fail'
3+
4+
export class DiffError extends Error {}
5+
export const fail = defineFail(DiffError, ExitCode.Difference)
6+
7+
export default {
8+
DiffError,
9+
fail
10+
}

src/commands/fails/exitcode.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export const enum ExitCode {
2+
NotYetTranslation = 4,
3+
UndefinedLocaleMessage = 5,
4+
Difference = 64
5+
}
6+

src/commands/fails/fail.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { ExitCode } from './exitcode'
2+
3+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
4+
type Constructor<T> = { new (...args: any[]): T }
5+
6+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
7+
function typeGuard<T> (o: any, className: Constructor<T>): o is T {
8+
return o instanceof className
9+
}
10+
11+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
12+
export default function defineFail (UserError: any, code: ExitCode) {
13+
return (msg: string, err: Error) => {
14+
if (msg) {
15+
// TODO: should refactor console message
16+
console.error(msg)
17+
process.exit(1)
18+
} else {
19+
if (typeGuard(err, UserError)) {
20+
// TODO: should refactor console message
21+
console.warn(err.message)
22+
process.exit(code)
23+
} else {
24+
// preserve statck! see the https://github.com/yargs/yargs/blob/master/docs/api.md#failfn
25+
throw err
26+
}
27+
}
28+
}
29+
}

src/commands/fails/list.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { ExitCode } from './exitcode'
2+
import defineFail from './fail'
3+
4+
export class LocaleMessageUndefindError extends Error {}
5+
export const fail = defineFail(LocaleMessageUndefindError, ExitCode.UndefinedLocaleMessage)
6+
7+
export default {
8+
LocaleMessageUndefindError,
9+
fail
10+
}

src/commands/fails/status.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { ExitCode } from './exitcode'
2+
import defineFail from './fail'
3+
4+
export class TranslationStatusError extends Error {}
5+
export const fail = defineFail(TranslationStatusError, ExitCode.NotYetTranslation)
6+
7+
export default {
8+
TranslationStatusError,
9+
fail
10+
}

src/commands/infuse.ts

Lines changed: 67 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,27 @@
11
import { Arguments, Argv } from 'yargs'
22

3-
import { resolve, parsePath, readSFC } from '../utils'
3+
import {
4+
resolve,
5+
parsePath,
6+
readSFC,
7+
NamespaceDictionary,
8+
loadNamespaceDictionary,
9+
splitLocaleMessages
10+
} from '../utils'
11+
412
import infuse from '../infuser'
513
import squeeze from '../squeezer'
614
import fs from 'fs'
715
import path from 'path'
816
import { applyDiff } from 'deep-diff'
917
import glob from 'glob'
10-
import { LocaleMessages, SFCFileInfo, MetaLocaleMessage, Locale } from '../../types'
18+
import {
19+
Locale,
20+
LocaleMessages,
21+
SFCFileInfo,
22+
MetaLocaleMessage,
23+
MetaExternalLocaleMessages
24+
} from '../../types'
1125

1226
import { debug as Debug } from 'debug'
1327
const debug = Debug('vue-i18n-locale-message:commands:infuse')
@@ -16,6 +30,9 @@ type InfuseOptions = {
1630
target: string
1731
locales: string
1832
match?: string
33+
unbundleTo?: string
34+
unbundleMatch?: string
35+
namespace?: string
1936
dryRun: boolean
2037
}
2138

@@ -42,6 +59,21 @@ export const builder = (args: Argv): Argv<InfuseOptions> => {
4259
alias: 'm',
4360
describe: 'option should be accepted a regex filenames, must be specified together --messages'
4461
})
62+
.option('unbundleTo', {
63+
type: 'string',
64+
alias: 'u',
65+
describe: `target path of external locale messages bundled with 'squeeze' command, can also be specified multi paths with comma delimiter`
66+
})
67+
.option('unbundleMatch', {
68+
type: 'string',
69+
alias: 'M',
70+
describe: `option should be accepted regex filename of external locale messages, must be specified if it's directory path of external locale messages with --unbundle-to`
71+
})
72+
.option('namespace', {
73+
type: 'string',
74+
alias: 'n',
75+
describe: 'file path that defines the namespace for external locale messages bundled together'
76+
})
4577
.option('dryRun', {
4678
type: 'boolean',
4779
alias: 'd',
@@ -50,15 +82,38 @@ export const builder = (args: Argv): Argv<InfuseOptions> => {
5082
})
5183
}
5284

53-
export const handler = (args: Arguments<InfuseOptions>): void => {
85+
export const handler = async (args: Arguments<InfuseOptions>) => {
5486
const targetPath = resolve(args.target)
5587
const messagesPath = resolve(args.locales)
88+
89+
let nsDictionary = {} as NamespaceDictionary
90+
try {
91+
if (args.namespace) {
92+
nsDictionary = await loadNamespaceDictionary(args.namespace)
93+
}
94+
} catch (e) {
95+
console.warn('cannot load namespace dictionary')
96+
}
97+
debug('namespace dictionary:', nsDictionary)
98+
5699
const sources = readSFC(targetPath)
57100
const messages = readLocaleMessages(messagesPath, args.match)
101+
102+
const { sfc, external } = splitLocaleMessages(messages, nsDictionary, args.unbundleTo, args.unbundleMatch)
103+
debug('sfc', sfc)
104+
debug('external', external)
105+
58106
const meta = squeeze(targetPath, sources)
59-
apply(messages, meta)
107+
apply(sfc, meta)
60108
const newSources = infuse(targetPath, sources, meta)
61-
if (!args.dryRun) writeSFC(newSources)
109+
110+
if (!args.dryRun) {
111+
writeSFC(newSources)
112+
}
113+
114+
if (!args.dryRun && external) {
115+
writeExternalLocaleMessages(external)
116+
}
62117
}
63118

64119
function readLocaleMessages (targetPath: string, matchRegex?: string): LocaleMessages {
@@ -186,6 +241,13 @@ function writeSFC (sources: SFCFileInfo[]) {
186241
})
187242
}
188243

244+
function writeExternalLocaleMessages (meta: MetaExternalLocaleMessages[]) {
245+
// TODO: async implementation
246+
meta.forEach(({ path, messages }) => {
247+
fs.writeFileSync(path, JSON.stringify(messages, null, 2))
248+
})
249+
}
250+
189251
export default {
190252
command,
191253
aliases,

src/commands/list.ts

Lines changed: 5 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import fs from 'fs'
55
import { promisify } from 'util'
66
import { Arguments, Argv } from 'yargs'
77

8+
import { LocaleMessageUndefindError, fail } from './fails/list'
9+
810
import {
911
resolve,
1012
getLocaleMessages
@@ -36,8 +38,6 @@ const debug = Debug('vue-i18n-locale-message:commands:list')
3638

3739
const writeFile = promisify(fs.writeFile)
3840

39-
class LocaleMessageUndefindError extends Error {}
40-
4141
export const command = 'list'
4242
export const aliases = 'lt'
4343
export const describe = 'List undefined fields in locale messages'
@@ -77,29 +77,14 @@ export const builder = (args: Argv): Argv<ListOptions> => {
7777
default: 2,
7878
describe: `option for indent of locale message, if you need to adjust with --define option`
7979
})
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-
})
80+
.fail(fail)
9581
}
9682

9783
export const handler = async (args: Arguments<ListOptions>): Promise<unknown> => {
9884
const { locale, define } = args
9985
if (!args.target && !args.targetPaths) {
10086
// TODO: should refactor console message
101-
console.error('You need to specify either --target or --target-paths')
102-
return
87+
return Promise.reject(new Error('You need to specify either --target or --target-paths'))
10388
}
10489

10590
const localeMessages = getLocaleMessages(args)
@@ -134,7 +119,7 @@ export const handler = async (args: Arguments<ListOptions>): Promise<unknown> =>
134119
})
135120

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

140125
const unflattedLocaleMessages = {} as LocaleMessages

0 commit comments

Comments
 (0)