Skip to content

Commit 04dee29

Browse files
committed
⚡ improvement: Support meta local message outputting for 3rd vendor tools
closes #13
1 parent b89268f commit 04dee29

File tree

11 files changed

+527
-222
lines changed

11 files changed

+527
-222
lines changed

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
"dependencies": {
1414
"@vue/component-compiler-utils": "^3.0.0",
1515
"debug": "^4.1.1",
16+
"deep-diff": "^1.0.2",
1617
"glob": "^7.1.4",
1718
"js-yaml": "^3.13.1",
1819
"json5": "^2.1.0",
@@ -22,6 +23,7 @@
2223
},
2324
"devDependencies": {
2425
"@types/debug": "^4.1.4",
26+
"@types/deep-diff": "^1.0.0",
2527
"@types/glob": "^7.1.1",
2628
"@types/jest": "^24.0.15",
2729
"@types/js-yaml": "^3.12.1",

src/commands/infuse.ts

Lines changed: 108 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
import { Arguments, Argv } from 'yargs'
22

3-
import { resolve, readSFC } from '../utils'
3+
import { resolve, parsePath, readSFC } from '../utils'
44
import infuse from '../infuser'
5+
import squeeze from '../squeezer'
56
import fs from 'fs'
6-
import { LocaleMessages, SFCFileInfo } from '../../types'
7+
import { applyDiff } from 'deep-diff'
8+
import { LocaleMessages, SFCFileInfo, MetaLocaleMessage, Locale } from '../../types'
9+
10+
import { debug as Debug } from 'debug'
11+
const debug = Debug('vue-i18n-locale-message:commands:infuse')
712

813
type InfuseOptions = {
914
target: string
@@ -33,7 +38,11 @@ export const builder = (args: Argv): Argv<InfuseOptions> => {
3338
export const handler = (args: Arguments<InfuseOptions>): void => {
3439
const targetPath = resolve(args.target)
3540
const messagesPath = resolve(args.messages)
36-
const newSources = infuse(targetPath, readSFC(targetPath), readLocaleMessages(messagesPath))
41+
const sources = readSFC(targetPath)
42+
const messages = readLocaleMessages(messagesPath)
43+
const meta = squeeze(targetPath, sources)
44+
apply(messages, meta)
45+
const newSources = infuse(targetPath, sources, meta)
3746
writeSFC(newSources)
3847
}
3948

@@ -43,6 +52,102 @@ function readLocaleMessages (path: string): LocaleMessages {
4352
return JSON.parse(data) as LocaleMessages
4453
}
4554

55+
function removeItem<T> (item: T, items: T[]): boolean {
56+
const index = items.indexOf(item)
57+
if (index === -1) { return false }
58+
items.splice(index, 1)
59+
return true
60+
}
61+
62+
function apply (messages: LocaleMessages, meta: MetaLocaleMessage): MetaLocaleMessage {
63+
const { target, components } = meta
64+
65+
for (const [component, blocks] of Object.entries(components)) {
66+
debug(`apply component = ${component}, blocks = ${JSON.stringify(blocks)}`)
67+
const { hierarchy } = parsePath(target, component)
68+
69+
const collectMessages = getTargetLocaleMessages(messages, hierarchy)
70+
debug('collect messages', JSON.stringify(collectMessages, null, 2))
71+
72+
const sourceLocales: Locale[] = Object.keys(collectMessages)
73+
const targetLocales = blocks.reduce((locales, block) => {
74+
if (block.locale) {
75+
locales.push(block.locale)
76+
} else {
77+
locales = Object.keys(block.messages).reduce((locales, locale) => {
78+
locales.push(locale)
79+
return locales
80+
}, locales)
81+
}
82+
return locales
83+
}, [] as Locale[])
84+
debug(`locales: source = ${sourceLocales}, target = ${targetLocales}`)
85+
86+
blocks.forEach(block => {
87+
const { locale } = block
88+
if (locale) {
89+
applyDiff(block.messages[locale], collectMessages[locale])
90+
removeItem(locale, sourceLocales)
91+
removeItem(locale, targetLocales)
92+
} else {
93+
const locales: Locale[] = Object.keys(block.messages)
94+
locales.forEach(locale => {
95+
applyDiff(block.messages[locale], collectMessages[locale])
96+
removeItem(locale, sourceLocales)
97+
removeItem(locale, targetLocales)
98+
})
99+
}
100+
})
101+
debug(`locales remain: source = ${sourceLocales}, target = ${targetLocales}`)
102+
103+
if (sourceLocales.length) {
104+
sourceLocales.forEach(locale => {
105+
blocks.push({
106+
lang: 'json',
107+
locale,
108+
messages: Object.assign({}, { [locale]: collectMessages[locale] })
109+
})
110+
})
111+
}
112+
113+
if (targetLocales.length) {
114+
debug('invalid target remain locales ...', targetLocales.length)
115+
}
116+
}
117+
118+
return meta
119+
}
120+
121+
function getTargetLocaleMessages (messages: LocaleMessages, hierarchy: string[]): LocaleMessages {
122+
return Object.keys(messages).reduce((target, locale) => {
123+
debug(`processing curernt: locale=${locale}, target=${JSON.stringify(target)}`)
124+
125+
const obj = messages[locale]
126+
if (obj) {
127+
let o: any = obj
128+
let prev: any = null
129+
const h = hierarchy.concat()
130+
while (h.length > 0) {
131+
const key = h.shift()
132+
debug('processing hierarchy key: ', key)
133+
134+
if (!key || !o) { break }
135+
o = o[key]
136+
prev = o
137+
debug(`processing o = ${JSON.stringify(o)}, prev = ${JSON.stringify(prev)}`)
138+
}
139+
140+
if (!o && !prev) {
141+
return target
142+
} else {
143+
return Object.assign(target, { [locale]: ((!o && prev) ? prev : o) }) as LocaleMessages
144+
}
145+
} else {
146+
return target
147+
}
148+
}, {} as LocaleMessages)
149+
}
150+
46151
function writeSFC (sources: SFCFileInfo[]) {
47152
// TODO: async implementation
48153
sources.forEach(({ path, content }) => {

src/commands/squeeze.ts

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
import { Arguments, Argv } from 'yargs'
2-
import { LocaleMessages } from '../../types'
2+
import { LocaleMessages, MetaLocaleMessage, Locale } from '../../types'
33

4-
import { resolve, readSFC } from '../utils'
4+
import { resolve, parsePath, readSFC } from '../utils'
55
import squeeze from '../squeezer'
66
import fs from 'fs'
77

8+
import { debug as Debug } from 'debug'
9+
const debug = Debug('vue-i18n-locale-message:commands:squeeze')
10+
811
type SqueezeOptions = {
912
target: string
1013
output: string
@@ -33,10 +36,55 @@ export const builder = (args: Argv): Argv<SqueezeOptions> => {
3336

3437
export const handler = (args: Arguments<SqueezeOptions>): void => {
3538
const targetPath = resolve(args.target)
36-
const messages = squeeze(targetPath, readSFC(targetPath))
39+
const meta = squeeze(targetPath, readSFC(targetPath))
40+
const messages = generate(meta)
3741
writeLocaleMessages(resolve(args.output), messages)
3842
}
3943

44+
function generate (meta: MetaLocaleMessage): LocaleMessages {
45+
const { target, components } = meta
46+
let messages: LocaleMessages = {}
47+
48+
const assignLocales = (locales: Locale[], messages: LocaleMessages): LocaleMessages => {
49+
return locales.reduce((messages, locale) => {
50+
!messages[locale] && Object.assign(messages, { [locale]: {}})
51+
return messages
52+
}, messages)
53+
}
54+
55+
for (const [component, blocks] of Object.entries(components)) {
56+
debug(`generate component = ${component}`)
57+
const parsed = parsePath(target, component)
58+
messages = blocks.reduce((messages, block) => {
59+
debug(`generate current messages = ${JSON.stringify(messages)}`)
60+
const locales = Object.keys(block.messages)
61+
messages = assignLocales(locales, messages)
62+
locales.reduce((messages, locale) => {
63+
if (block.messages[locale]) {
64+
const localeMessages = messages[locale]
65+
const localeBlockMessages = block.messages[locale]
66+
let target: any = localeMessages
67+
const hierarchy = parsed.hierarchy.concat()
68+
while (hierarchy.length >= 0) {
69+
const key = hierarchy.shift()
70+
if (!key) { break }
71+
if (!target[key]) {
72+
target[key] = {}
73+
}
74+
target = target[key]
75+
}
76+
Object.assign(target, localeBlockMessages)
77+
return messages
78+
}
79+
return messages
80+
}, messages)
81+
return messages
82+
}, messages)
83+
}
84+
85+
return messages
86+
}
87+
4088
function writeLocaleMessages (output: string, messages: LocaleMessages) {
4189
// TODO: async implementation
4290
fs.writeFileSync(output, JSON.stringify(messages, null, 2))

src/infuser.ts

Lines changed: 41 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,68 +1,38 @@
11
import { SFCDescriptor, SFCBlock } from 'vue-template-compiler'
2-
import { Locale, LocaleMessages, SFCFileInfo } from '../types'
2+
import { Locale, MetaLocaleMessage, SFCI18nBlock, SFCFileInfo } from '../types'
33

4-
import { reflectSFCDescriptor, parseContent, stringfyContent } from './utils'
4+
import { escape, reflectSFCDescriptor, parseContent, stringfyContent } from './utils'
55
import prettier from 'prettier'
66

77
import { debug as Debug } from 'debug'
88
const debug = Debug('vue-i18n-locale-message:infuser')
99

10-
export default function infuse (basePath: string, sources: SFCFileInfo[], messages: LocaleMessages): SFCFileInfo[] {
10+
export default function infuse (basePath: string, sources: SFCFileInfo[], meta: MetaLocaleMessage): SFCFileInfo[] {
1111
const descriptors = reflectSFCDescriptor(basePath, sources)
12-
const locales = Object.keys(messages)
1312

1413
return descriptors.map(descriptor => {
1514
return {
16-
content: generate(locales, messages, descriptor),
15+
content: generate(meta, descriptor),
1716
path: descriptor.contentPath
1817
} as SFCFileInfo
1918
})
2019
}
2120

22-
function generate (locales: Locale[], messages: LocaleMessages, descriptor: SFCDescriptor): string {
23-
const target = getTargetLocaleMessages(locales, messages, descriptor)
24-
debug('target locale messages\n', target)
21+
function generate (meta: MetaLocaleMessage, descriptor: SFCDescriptor): string {
22+
const i18nBlocks = meta.components[descriptor.contentPath]
23+
debug('target i18n blocks\n', i18nBlocks)
2524

2625
const blocks: SFCBlock[] = getBlocks(descriptor)
2726
blocks.forEach(b => debug(`block: type=${b.type}, start=${b.start}, end=${b.end}`))
2827

2928
const { raw } = descriptor
30-
const content = buildContent(target, raw, blocks)
29+
const content = buildContent(i18nBlocks, raw, blocks)
3130
debug(`build content:\n${content}`)
3231
debug(`content size: raw=${raw.length}, content=${content.length}`)
3332

3433
return format(content, 'vue')
3534
}
3635

37-
function getTargetLocaleMessages (locales: Locale[], messages: LocaleMessages, descriptor: SFCDescriptor): LocaleMessages {
38-
return locales.reduce((target, locale) => {
39-
debug(`processing curernt: locale=${locale}, target=${target}`)
40-
41-
const obj = messages[locale]
42-
if (obj) {
43-
let o: any = obj
44-
let prev: any = null
45-
const hierarchy = descriptor.hierarchy.concat()
46-
while (hierarchy.length > 0) {
47-
const key = hierarchy.shift()
48-
debug('processing hierarchy key: ', key)
49-
50-
if (!key || !o) { break }
51-
o = o[key]
52-
prev = o
53-
}
54-
55-
if (!o && !prev) {
56-
return target
57-
} else {
58-
return Object.assign(target, { [locale]: ((!o && prev) ? prev : o) }) as LocaleMessages
59-
}
60-
} else {
61-
return target
62-
}
63-
}, {} as LocaleMessages)
64-
}
65-
6636
function getBlocks (descriptor: SFCDescriptor): SFCBlock[] {
6737
const { template, script, styles, customBlocks } = descriptor
6838
const blocks: SFCBlock[] = [...styles, ...customBlocks]
@@ -72,36 +42,36 @@ function getBlocks (descriptor: SFCDescriptor): SFCBlock[] {
7242
return blocks
7343
}
7444

75-
function buildContent (target: LocaleMessages, raw: string, blocks: SFCBlock[]): string {
45+
function buildContent (i18nBlocks: SFCI18nBlock[], raw: string, blocks: SFCBlock[]): string {
7646
let offset = 0
47+
let i18nBlockCounter = 0
7748
let contents: string[] = []
78-
let targetLocales = Object.keys(target) as Locale[]
7949

8050
contents = blocks.reduce((contents, block) => {
8151
if (block.type === 'i18n') {
8252
let lang = block.attrs.lang
8353
lang = (!lang || typeof lang !== 'string') ? 'json' : lang
54+
const locale: Locale | undefined = block.attrs.locale
55+
const i18nBlock = i18nBlocks[i18nBlockCounter]
56+
debug(`meta.lang = ${i18nBlock.lang}, block.lang = ${lang}, meta.locale = ${i18nBlock.locale}, block.locale = ${locale}`)
8457

8558
let messages: any = null
86-
const locale = block.attrs.locale as Locale
87-
if (!locale || typeof locale !== 'string') {
88-
const obj = parseContent(block.content, lang)
89-
const locales = Object.keys(obj) as Locale[]
90-
messages = locales.reduce((messages, locale) => {
91-
return Object.assign(messages, { [locale]: target[locale] })
92-
}, {} as LocaleMessages)
93-
locales.forEach(locale => {
94-
targetLocales = targetLocales.filter(l => l !== locale)
95-
})
59+
if (lang === i18nBlock.lang && locale === i18nBlock.locale) {
60+
if (locale) {
61+
messages = i18nBlock.messages[locale]
62+
} else {
63+
messages = i18nBlock.messages
64+
}
9665
} else {
97-
messages = Object.assign({}, target[locale])
98-
targetLocales = targetLocales.filter(l => l !== locale)
66+
debug(`unmatch meta block and sfc block`)
67+
messages = parseContent(block.content, lang)
9968
}
10069

10170
contents = contents.concat(raw.slice(offset, block.start))
10271
const serialized = `\n${format(stringfyContent(messages, lang), lang)}`
10372
contents = contents.concat(serialized)
10473
offset = block.end as number
74+
i18nBlockCounter++
10575
} else {
10676
contents = contents.concat(raw.slice(offset, block.end))
10777
offset = block.end as number
@@ -110,18 +80,32 @@ function buildContent (target: LocaleMessages, raw: string, blocks: SFCBlock[]):
11080
}, contents)
11181
contents = contents.concat(raw.slice(offset, raw.length))
11282

113-
if (targetLocales.length > 0) {
114-
contents = targetLocales.reduce((contents, locale) => {
115-
contents.push(`\n
116-
<i18n locale="${locale}">
117-
${format(stringfyContent(target[locale], 'json'), 'json')}</i18n>`)
83+
if (i18nBlocks.length > i18nBlockCounter) {
84+
i18nBlocks.slice(i18nBlockCounter).reduce((contents, i18nBlock) => {
85+
contents.push(buildI18nTag(i18nBlock))
11886
return contents
11987
}, contents)
12088
}
12189

12290
return contents.join('')
12391
}
12492

93+
function buildI18nTag (i18nBlock: SFCI18nBlock): string {
94+
const { locale, lang, messages } = i18nBlock
95+
let tag = '<i18n'
96+
if (locale) {
97+
tag += ` locale="${escape(locale)}"`
98+
}
99+
if (lang !== 'json') {
100+
tag += ` lang="${escape(lang)}"`
101+
}
102+
tag += '>'
103+
104+
return `\n
105+
${tag}
106+
${format(stringfyContent(locale ? messages[locale] : messages, lang), lang)}</i18n>`
107+
}
108+
125109
function format (source: string, lang: string): string {
126110
debug(`format: lang=${lang}, source=${source}`)
127111

0 commit comments

Comments
 (0)