Skip to content

Commit ff236a8

Browse files
committed
⭐ new: i18n custom block updating
1 parent 00c1890 commit ff236a8

File tree

7 files changed

+414
-9
lines changed

7 files changed

+414
-9
lines changed

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
"debug": "^4.1.1",
1515
"js-yaml": "^3.13.1",
1616
"json5": "^2.1.0",
17+
"prettier": "^1.18.2",
1718
"vue-template-compiler": "^2.6.10"
1819
},
1920
"devDependencies": {
@@ -22,6 +23,7 @@
2223
"@types/js-yaml": "^3.12.1",
2324
"@types/json5": "^0.0.30",
2425
"@types/node": "^12.6.8",
26+
"@types/prettier": "^1.18.1",
2527
"@typescript-eslint/eslint-plugin": "^1.13.0",
2628
"@typescript-eslint/parser": "^1.13.0",
2729
"@typescript-eslint/typescript-estree": "^1.13.0",

src/infuser.ts

Lines changed: 125 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,127 @@
1-
import { LocaleMessages, LocaleMessageMeta } from '../types'
1+
import { SFCDescriptor, SFCBlock } from 'vue-template-compiler'
2+
import { Locale, LocaleMessages, SFCFileInfo } from '../types'
23

3-
export default function infuse (messages: LocaleMessages, meta: LocaleMessageMeta[]): void {
4+
import { reflectSFCDescriptor, parseContent, stringfyContent } from './utils'
5+
import prettier from 'prettier'
6+
7+
import { debug as Debug } from 'debug'
8+
const debug = Debug('vue-i18n-locale-message:infuser')
9+
10+
export default function infuse (basePath: string, sources: SFCFileInfo[], messages: LocaleMessages): SFCFileInfo[] {
11+
const descriptors = reflectSFCDescriptor(basePath, sources)
12+
const locales = Object.keys(messages)
13+
14+
return descriptors.map(descriptor => {
15+
return {
16+
content: generate(locales, messages, descriptor),
17+
path: descriptor.contentPath
18+
} as SFCFileInfo
19+
})
20+
}
21+
22+
function generate (locales: Locale[], messages: LocaleMessages, descriptor: SFCDescriptor): string {
23+
const target = getTargetLocaleMessages(locales, messages, descriptor)
24+
debug('target locale messages\n', target)
25+
26+
const blocks: SFCBlock[] = getBlocks(descriptor)
27+
blocks.forEach(b => debug(`block: type=${b.type}, start=${b.start}, end=${b.end}`))
28+
29+
const { raw } = descriptor
30+
const content = buildContent(target, raw, blocks)
31+
debug(`build content:\n${content}`)
32+
debug(`content size: raw=${raw.length}, content=${content.length}`)
33+
34+
return format(content, 'vue')
35+
}
36+
37+
function getTargetLocaleMessages (locales: Locale[], messages: LocaleMessages, descriptor: SFCDescriptor): LocaleMessages {
38+
return locales.reduce((target, locale) => {
39+
const obj = messages[locale]
40+
if (obj) {
41+
let o: any = obj
42+
const hierarchy = descriptor.hierarchy.concat()
43+
while (hierarchy.length > 0) {
44+
const key = hierarchy.shift()
45+
if (!key) { break }
46+
o = o[key]
47+
}
48+
return Object.assign(target, { [locale]: o }) as LocaleMessages
49+
} else {
50+
return target
51+
}
52+
}, {} as LocaleMessages)
53+
}
54+
55+
function getBlocks (descriptor: SFCDescriptor): SFCBlock[] {
56+
const { template, script, styles, customBlocks } = descriptor
57+
const blocks: SFCBlock[] = [...styles, ...customBlocks]
58+
template && blocks.push(template as SFCBlock)
59+
script && blocks.push(script as SFCBlock)
60+
blocks.sort((a, b) => { return (a.start as number) - (b.start as number) })
61+
return blocks
62+
}
63+
64+
function buildContent (target: LocaleMessages, raw: string, blocks: SFCBlock[]): string {
65+
let offset = 0
66+
let contents: string[] = []
67+
let targetLocales = Object.keys(target) as Locale[]
68+
69+
contents = blocks.reduce((contents, block) => {
70+
if (block.type === 'i18n') {
71+
let lang = block.attrs.lang
72+
lang = (!lang || typeof lang !== 'string') ? 'json' : lang
73+
74+
let messages: any = null
75+
const locale = block.attrs.locale as Locale
76+
if (!locale || typeof locale !== 'string') {
77+
const obj = parseContent(block.content, lang)
78+
const locales = Object.keys(obj) as Locale[]
79+
messages = locales.reduce((messages, locale) => {
80+
return Object.assign(messages, { [locale]: target[locale] })
81+
}, {} as LocaleMessages)
82+
locales.forEach(locale => {
83+
targetLocales = targetLocales.filter(l => l !== locale)
84+
})
85+
} else {
86+
messages = Object.assign({}, target[locale])
87+
targetLocales = targetLocales.filter(l => l !== locale)
88+
}
89+
90+
contents = contents.concat(raw.slice(offset, block.start))
91+
const serialized = `\n${format(stringfyContent(messages, lang), lang)}`
92+
contents = contents.concat(serialized)
93+
offset = block.end as number
94+
} else {
95+
contents = contents.concat(raw.slice(offset, block.end))
96+
offset = block.end as number
97+
}
98+
return contents
99+
}, contents)
100+
contents = contents.concat(raw.slice(offset, raw.length))
101+
102+
if (targetLocales.length > 0) {
103+
contents = targetLocales.reduce((contents, locale) => {
104+
contents.push(`\n
105+
<i18n locale="${locale}">
106+
${format(stringfyContent(target[locale], 'json'), 'json')}</i18n>`)
107+
return contents
108+
}, contents)
109+
}
110+
111+
return contents.join('')
112+
}
113+
114+
function format (source: string, lang: string): string {
115+
switch (lang) {
116+
case 'vue':
117+
return prettier.format(source, { parser: 'vue' })
118+
case 'yaml':
119+
case 'yml':
120+
return prettier.format(source, { parser: 'yaml', tabWidth: 2 })
121+
case 'json5':
122+
return prettier.format(source, { parser: 'json5', tabWidth: 2 })
123+
case 'json':
124+
default:
125+
return prettier.format(source, { parser: 'json-stringify', tabWidth: 2 })
126+
}
4127
}

src/utils.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,9 @@ export function stringfyContent (content: any, lang: string): string {
6060
switch (lang) {
6161
case 'yaml':
6262
case 'yml':
63-
return yaml.safeDump(content)
63+
const s = yaml.safeDump(content, { indent: 20 })
64+
debug('yaml:', s)
65+
return s
6466
case 'json5':
6567
return JSON5.stringify(content, null, 2)
6668
case 'json':
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`json: /path/to/project1/src/App.vue 1`] = `
4+
"<template>
5+
<p>template</p>
6+
</template>
7+
8+
<script>
9+
export default {};
10+
</script>
11+
12+
<i18n>
13+
{
14+
\\"en\\": {
15+
\\"title\\": \\"Application\\"
16+
},
17+
\\"ja\\": {
18+
\\"title\\": \\"アプリケーション\\"
19+
}
20+
}
21+
</i18n>
22+
"
23+
`;
24+
25+
exports[`json: /path/to/project1/src/components/Modal.vue 1`] = `
26+
"<template>
27+
<p>template</p>
28+
</template>
29+
30+
<script>
31+
export default {};
32+
</script>
33+
34+
<i18n locale=\\"en\\">
35+
{
36+
\\"ok\\": \\"OK\\",
37+
\\"cancel\\": \\"Cancel\\"
38+
}
39+
</i18n>
40+
41+
<i18n locale=\\"ja\\">
42+
{
43+
\\"ok\\": \\"OK\\",
44+
\\"cancel\\": \\"キャンセル\\"
45+
}
46+
</i18n>
47+
"
48+
`;
49+
50+
exports[`json: /path/to/project1/src/components/nest/RankingTable.vue 1`] = `
51+
"<template>
52+
<p>template</p>
53+
</template>
54+
55+
<script>
56+
export default {};
57+
</script>
58+
59+
<i18n locale=\\"en\\">
60+
{
61+
\\"headers\\": {
62+
\\"rank\\": \\"Rank\\",
63+
\\"name\\": \\"Name\\",
64+
\\"score\\": \\"Score\\"
65+
}
66+
}
67+
</i18n>
68+
69+
<i18n locale=\\"ja\\">
70+
{
71+
\\"headers\\": {
72+
\\"rank\\": \\"ランク\\",
73+
\\"name\\": \\"名前\\",
74+
\\"score\\": \\"スコア\\"
75+
}
76+
}
77+
</i18n>
78+
"
79+
`;
80+
81+
exports[`json: /path/to/project1/src/pages/Login.vue 1`] = `
82+
"<template>
83+
<p>template</p>
84+
</template>
85+
86+
<script>
87+
export default {};
88+
</script>
89+
90+
<i18n>
91+
{
92+
\\"ja\\": {
93+
\\"id\\": \\"ユーザーID\\",
94+
\\"passowrd\\": \\"パスワード\\",
95+
\\"confirm\\": \\"パスワードの確認入力\\",
96+
\\"button\\": \\"ログイン\\"
97+
}
98+
}
99+
</i18n>
100+
101+
<i18n locale=\\"en\\">
102+
{
103+
\\"id\\": \\"User ID\\",
104+
\\"password\\": \\"Password\\",
105+
\\"confirm\\": \\"Confirm Password\\",
106+
\\"button\\": \\"Login\\"
107+
}
108+
</i18n>
109+
"
110+
`;
111+
112+
exports[`json5: /path/to/project1/src/components/Modal.vue 1`] = `
113+
"<template>
114+
<p>template</p>
115+
</template>
116+
117+
<script>
118+
export default {};
119+
</script>
120+
121+
<i18n lang=\\"json5\\" locale=\\"en\\">
122+
{
123+
ok: \\"OK\\",
124+
cancel: \\"Cancel\\"
125+
}
126+
</i18n>
127+
128+
<i18n locale=\\"ja\\">
129+
{
130+
\\"ok\\": \\"OK\\",
131+
\\"cancel\\": \\"キャンセル\\"
132+
}
133+
</i18n>
134+
"
135+
`;
136+
137+
exports[`yaml: /path/to/project1/src/components/Modal.vue 1`] = `
138+
"<template>
139+
<p>template</p>
140+
</template>
141+
142+
<script>
143+
export default {};
144+
</script>
145+
146+
<i18n lang=\\"yaml\\" locale=\\"en\\">
147+
ok: OK
148+
cancel: Cancel
149+
</i18n>
150+
151+
<i18n lang=\\"yml\\" locale=\\"ja\\">
152+
ok: OK
153+
cancel: キャンセル
154+
</i18n>
155+
"
156+
`;

test/fixtures/file/yaml.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,12 @@ export default {}
99
</script>
1010
1111
<i18n lang="yaml" locale="en">
12-
ok: OK
13-
cancel: Cancel
12+
ok: OK
13+
cancel: Cancel
1414
</i18n>
1515
1616
<i18n lang="yml" locale="ja">
17-
ok: OK
18-
cancel: キャンセル
17+
ok: OK
18+
cancel: キャンセル
1919
</i18n>`
2020
}]

0 commit comments

Comments
 (0)