Skip to content

Commit 1ec8978

Browse files
committed
feat: add support for vue-i18n custom blocks by default
1 parent da2e575 commit 1ec8978

File tree

12 files changed

+2698
-2956
lines changed

12 files changed

+2698
-2956
lines changed

e2e/__projects__/basic/__snapshots__/test.js.snap

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,37 @@
11
// Jest Snapshot v1, https://goo.gl/fbAQLP
22

3+
exports[`I18n Processor merges data blocks 1`] = `
4+
VueWrapper {
5+
"_emitted": Object {},
6+
"_emittedByOrder": Array [],
7+
"isFunctionalComponent": undefined,
8+
}
9+
`;
10+
11+
exports[`I18n Processor processes SFC with i18n JSON in external src attribute 1`] = `
12+
VueWrapper {
13+
"_emitted": Object {},
14+
"_emittedByOrder": Array [],
15+
"isFunctionalComponent": undefined,
16+
}
17+
`;
18+
19+
exports[`I18n Processor processes SFC with i18n JSON inline custom block 1`] = `
20+
VueWrapper {
21+
"_emitted": Object {},
22+
"_emittedByOrder": Array [],
23+
"isFunctionalComponent": undefined,
24+
}
25+
`;
26+
27+
exports[`I18n Processor processes SFC with i18n Yaml Inline 1`] = `
28+
VueWrapper {
29+
"_emitted": Object {},
30+
"_emittedByOrder": Array [],
31+
"isFunctionalComponent": undefined,
32+
}
33+
`;
34+
335
exports[`generates source maps for .vue files 1`] = `
436
"\\"use strict\\";
537
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<template>
2+
<p>{{ $t('hello') }}</p>
3+
</template>
4+
5+
<script>
6+
export default {
7+
name: 'I18nJSONFromSrc'
8+
}
9+
</script>
10+
11+
<i18n src="./locales.json" />
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<template>
2+
<p>{{ $t('hello') }}</p>
3+
</template>
4+
5+
<script>
6+
export default {
7+
name: 'I18nJSONInline'
8+
}
9+
</script>
10+
11+
<i18n>
12+
{
13+
"en": {
14+
"hello": "Hello i18n in SFC!"
15+
}
16+
}
17+
</i18n>
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<template>
2+
<p>{{ $t('hello') }}</p>
3+
</template>
4+
5+
<script>
6+
export default {
7+
name: 'I18nMergingMultipleBlocks'
8+
}
9+
</script>
10+
11+
<i18n src="./locales.json" />
12+
<i18n>
13+
{
14+
"en": {
15+
"hello": "Hello i18n in SFC!"
16+
}
17+
}
18+
</i18n>
19+
<i18n>
20+
en:
21+
hello: "hello world!"
22+
ja:
23+
hello: "こんにちは、世界!"
24+
</i18n>
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<template>
2+
<p>{{ $t('hello') }}</p>
3+
</template>
4+
5+
<script>
6+
export default {
7+
name: 'I18nYamlInline'
8+
}
9+
</script>
10+
11+
<i18n>
12+
en:
13+
hello: "hello world!"
14+
ja:
15+
hello: "こんにちは、世界!"
16+
</i18n>
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"en": {
3+
"hello": "Hello i18n in SFC!",
4+
"helloMultipleBlocks": "hello",
5+
"additionalKey": "This is an additional key"
6+
}
7+
}

e2e/__projects__/basic/test.js

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
import { mount } from '@vue/test-utils'
1+
import { mount, createLocalVue } from '@vue/test-utils'
22
import TypeScript from './components/TypeScript.vue'
33
import { resolve } from 'path'
44
import { readFileSync } from 'fs'
5+
import VueI18n from 'vue-i18n'
56
import jestVue from 'vue-jest'
67
import RenderFunction from './components/RenderFunction.vue'
78
import Jade from './components/Jade.vue'
@@ -12,6 +13,10 @@ import { randomExport } from './components/NamedExport.vue'
1213
import Coffee from './components/Coffee.vue'
1314
import CoffeeScript from './components/CoffeeScript.vue'
1415
import FunctionalSFCParent from './components/FunctionalSFCParent.vue'
16+
import I18nJSONInline from './components/I18nJSONInline.vue'
17+
import I18nJSONFromSrc from './components/I18nJSONFromSrc.vue'
18+
import I18nYamlInline from './components/I18nYamlInline.vue'
19+
import I18nMergingMultipleBlocks from './components/I18nMergingMultipleBlocks.vue'
1520
import NoScript from './components/NoScript.vue'
1621
import Pug from './components/Pug.vue'
1722
import PugRelative from './components/PugRelativeExtends.vue'
@@ -125,3 +130,42 @@ test('processes SFC with no template', () => {
125130
const wrapper = mount(RenderFunction)
126131
expect(wrapper.is('section')).toBe(true)
127132
})
133+
134+
describe('I18n Processor', () => {
135+
const setup = (opts = { locale: 'en' }) => {
136+
const localVue = createLocalVue()
137+
localVue.use(VueI18n)
138+
const i18n = new VueI18n(opts)
139+
return { i18n, localVue }
140+
}
141+
142+
test('processes SFC with i18n JSON inline custom block', () => {
143+
const { i18n, localVue } = setup()
144+
const wrapper = mount(I18nJSONInline, { i18n, localVue })
145+
expect(wrapper.text()).toBe('Hello i18n in SFC!')
146+
expect(wrapper).toMatchSnapshot()
147+
})
148+
149+
test('processes SFC with i18n JSON in external src attribute', () => {
150+
const { i18n, localVue } = setup()
151+
const wrapper = mount(I18nJSONFromSrc, { i18n, localVue })
152+
expect(wrapper.text()).toBe('Hello i18n in SFC!')
153+
expect(wrapper).toMatchSnapshot()
154+
})
155+
156+
test('processes SFC with i18n Yaml Inline', () => {
157+
const { i18n, localVue } = setup()
158+
const wrapper = mount(I18nYamlInline, { i18n, localVue })
159+
expect(wrapper.text()).toBe('hello world!')
160+
expect(wrapper).toMatchSnapshot()
161+
})
162+
163+
test('merges data blocks', () => {
164+
const { i18n, localVue } = setup()
165+
const wrapper = mount(I18nMergingMultipleBlocks, { i18n, localVue })
166+
expect(wrapper.text()).toBe('hello world!')
167+
expect(typeof wrapper.vm.$t('additionalKey')).toEqual('string')
168+
169+
expect(wrapper).toMatchSnapshot()
170+
})
171+
})

lib/generate-code.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ module.exports = function generateCode(
44
scriptResult,
55
templateResult,
66
stylesResult,
7+
i18nResult,
78
isFunctional
89
) {
910
let output = ''
@@ -75,6 +76,10 @@ module.exports = function generateCode(
7576
`})()\n`
7677
}
7778
}
79+
80+
if (i18nResult) {
81+
output += `;\n ${i18nResult}`
82+
}
7883
return {
7984
code: output,
8085
renderFnStartLine,

lib/process-i18n.js

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
const JSON5 = require('json5')
2+
const yaml = require('js-yaml')
3+
const fs = require('fs')
4+
const path = require('path')
5+
6+
const VUE_OPTIONS = '__options__'
7+
const VUE_I18N_OPTION = '__i18n'
8+
9+
function convert(source, lang) {
10+
switch (lang) {
11+
case 'yaml':
12+
case 'yml':
13+
return JSON.stringify(yaml.safeLoad(source), undefined, '\t')
14+
case 'json5':
15+
return JSON.stringify(JSON5.parse(source))
16+
default:
17+
return source
18+
}
19+
}
20+
21+
// Because the vue-i18n docs do not require a language
22+
// we have to try to parse the content as json first, then fall back to yaml
23+
function parseLanguageAndContent(block, filename) {
24+
const langKnown = block.attrs && block.attrs.lang
25+
let content = block.content
26+
27+
if (block.attrs && block.attrs.src) {
28+
content = fs.readFileSync(path.resolve(filename, '../', block.attrs.src))
29+
}
30+
31+
if (langKnown) {
32+
return JSON.stringify(JSON.parse(convert(content, langKnown)))
33+
}
34+
35+
try {
36+
return JSON.stringify(JSON.parse(convert(content, 'json')))
37+
} catch (_) {
38+
return JSON.stringify(JSON.parse(convert(content, 'yaml')))
39+
}
40+
}
41+
42+
module.exports = function(blocks, filename) {
43+
const base = `${VUE_OPTIONS}.${VUE_I18N_OPTION} = []`
44+
const codes = blocks.map(block => {
45+
if (block.type === 'i18n') {
46+
const value = parseLanguageAndContent(block, filename)
47+
.replace(/\u2028/g, '\\u2028')
48+
.replace(/\u2029/g, '\\u2029')
49+
.replace(/\\/g, '\\\\')
50+
return `${VUE_OPTIONS}.${VUE_I18N_OPTION}.push('${value.replace(
51+
/\u0027/g,
52+
'\\u0027'
53+
)}')`
54+
} else {
55+
return ''
56+
}
57+
})
58+
59+
return codes.length ? [base].concat(codes).join('\n') : ''
60+
}

lib/process.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const generateSourceMap = require('./generate-source-map')
55
const typescriptTransformer = require('./typescript-transformer')
66
const coffeescriptTransformer = require('./coffee-transformer')
77
const _processStyle = require('./process-style')
8+
const processI18n = require('./process-i18n')
89
const getVueJestConfig = require('./utils').getVueJestConfig
910
const logResultErrors = require('./utils').logResultErrors
1011
const stripInlineSourceMap = require('./utils').stripInlineSourceMap
@@ -99,6 +100,7 @@ module.exports = function(src, filename, config) {
99100
const templateResult = processTemplate(descriptor.template, filename, config)
100101
const scriptResult = processScript(descriptor.script, filename, config)
101102
const stylesResult = processStyle(descriptor.styles, filename, config)
103+
const i18nResult = processI18n(descriptor.customBlocks, filename, config)
102104

103105
const isFunctional =
104106
descriptor.template &&
@@ -112,6 +114,7 @@ module.exports = function(src, filename, config) {
112114
scriptResult,
113115
templateResult,
114116
stylesResult,
117+
i18nResult,
115118
isFunctional
116119
)
117120

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
"stylus": "^0.54.5",
5959
"typescript": "^3.2.2",
6060
"vue": "^2.4.2",
61+
"vue-i18n": "^8.15.5",
6162
"vue-template-compiler": "^2.4.2"
6263
},
6364
"peerDependencies": {
@@ -72,7 +73,8 @@
7273
"chalk": "^2.1.0",
7374
"convert-source-map": "^1.6.0",
7475
"extract-from-css": "^0.4.4",
75-
"source-map": "^0.5.6",
76+
"js-yaml": "^3.13.1",
77+
"json5": "^2.1.1",
7678
"ts-jest": "^24.0.0"
7779
},
7880
"repository": {

0 commit comments

Comments
 (0)