Skip to content

Commit f17fe9d

Browse files
authored
feat: i18n resource fully pre-compilation (#141)
* feat: i18n resource pre-compilation * WIP: json pre-compilation * fix: format errors * done * fix jest timeout
1 parent 24b4f38 commit f17fe9d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

55 files changed

+2371
-286
lines changed

.eslintrc.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ module.exports = {
2929
'object-curly-spacing': 'off',
3030
'@typescript-eslint/explicit-function-return-type': 'off',
3131
'@typescript-eslint/member-delimiter-style': 'off',
32-
'@typescript-eslint/no-use-before-define': 'off'
32+
'@typescript-eslint/no-use-before-define': 'off',
33+
'@typescript-eslint/no-non-null-assertion': 'off'
3334
}
3435
}

README.md

Lines changed: 108 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,13 @@
1616
<br/>
1717

1818
## :star: Features
19+
- i18n resource pre-compilation
1920
- `i18n` custom block
2021
- i18n resource definition
2122
- i18n resource importing
2223
- Locale of i18n resource definition
2324
- Locale of i18n resource definition for global scope
2425
- i18n resource formatting
25-
- i18n resource optimaization
2626

2727

2828
## :cd: Installation
@@ -39,9 +39,84 @@ $ npm i --save-dev @intlify/vue-i18n-loader@next
3939
$ yarn add -D @intlify/vue-i18n-loader@next
4040
```
4141

42+
## :rocket: i18n resource pre-compilation
43+
44+
### Why do we need to require the configuration?
45+
46+
Since [email protected], The locale messages are handled with message compiler, which converts them to javascript functions after compiling. After compiling, message compiler converts them into javascript functions, which can improve the performance of the application.
47+
48+
However, with the message compiler, the javascript function conversion will not work in some environments (e.g. CSP). For this reason, [email protected] and later offer a full version that includes compiler and runtime, and a runtime only version.
49+
50+
If you are using the runtime version, you will need to compile before importing locale messages by managing them in a file such as `.json`.
51+
52+
You can pre-commpile by configuring vue-i18n-loader as the webpack loader.
53+
54+
### Webpack configration
55+
56+
As an example, if your project has the locale messagess in `src/locales`, your webpack config will look like this:
57+
58+
```
59+
├── dist
60+
├── index.html
61+
├── package.json
62+
├── src
63+
│   ├── App.vue
64+
│   ├── locales
65+
│   │   ├── en.json
66+
│   │   └── ja.json
67+
│   └── main.js
68+
└── webpack.config.js
69+
```
70+
71+
```js
72+
import { createApp } from 'vue'
73+
import { createI18n } from 'vue-i18n' // import from runtime only
74+
import App from './App.vue'
75+
76+
// import i18n resources
77+
import en from './locale/en.json'
78+
import ja from './locale/ja.json'
79+
80+
const i18n = createI18n({
81+
locale: 'ja',
82+
messages: {
83+
en,
84+
ja
85+
}
86+
})
87+
88+
const app = createApp(App)
89+
app.use(i18n)
90+
app.mount('#app')
91+
```
92+
93+
In the case of the above project, you can use vue-i18n with webpack configuration to the following for runtime only:
94+
95+
```javascript
96+
module.exports = {
97+
module: {
98+
rules: [
99+
// ...
100+
{
101+
test: /\.(json5?|ya?ml)$/, // target json, json5, yaml and yml files
102+
type: 'javascript/auto',
103+
loader: '@intlify/vue-i18n-loader',
104+
include: [ // Use `Rule.include` to specify the files of locale messages to be pre-compiled
105+
path.resolve(__dirname, 'src/locales')
106+
]
107+
},
108+
// ...
109+
]
110+
}
111+
}
112+
```
113+
114+
The above uses webpack's `Rule.include` to specify the i18n resources to be precompiled. You can also use [`Rule.exclude`](https://webpack.js.org/configuration/module/#ruleexclude) to set the target.
115+
116+
42117
## :rocket: `i18n` custom block
43118

44-
the below example that`App.vue` have `i18n` custom block:
119+
The below example that`App.vue` have `i18n` custom block:
45120

46121
### i18n resource definition
47122

@@ -89,7 +164,7 @@ The locale messages defined at `i18n` custom blocks are **json format default**.
89164

90165
### i18n resource importing
91166

92-
you also can use `src` attribute:
167+
You also can use `src` attribute:
93168

94169
```vue
95170
<i18n src="./myLang.json"></i18n>
@@ -219,6 +294,36 @@ module.exports = {
219294
}
220295
```
221296

297+
## :rocket: loader options
298+
299+
### forceStringify
300+
301+
Whether pre-compile number and boolean values as message functions that return the string value, default `false`
302+
303+
```javascript
304+
module.exports = {
305+
module: {
306+
rules: [
307+
// ...
308+
{
309+
test: /\.(json5?|ya?ml)$/,
310+
type: 'javascript/auto',
311+
include: [path.resolve(__dirname, './src/locales')],
312+
use: [
313+
{
314+
loader: '@intlify/vue-i18n-loader',
315+
options: {
316+
forceStringify: true
317+
}
318+
}
319+
]
320+
},
321+
// ...
322+
]
323+
}
324+
}
325+
```
326+
222327
## :rocket: i18n resource optimization
223328

224329
You can optimize your localization performance with pre-compiling the i18n resources.

e2e/composition.test.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
describe('composition', () => {
2+
beforeAll(async () => {
3+
await page.goto(`http://localhost:8080/composition/`)
4+
})
5+
6+
test('initial rendering', async () => {
7+
await expect(page).toMatch('言語')
8+
await expect(page).toMatch('こんにちは、世界!')
9+
await expect(page).toMatch('バナナが欲しい?')
10+
await expect(page).toMatch('バナナ 0 個')
11+
})
12+
13+
test('change locale', async () => {
14+
await page.select('#app select', 'en')
15+
await expect(page).toMatch('Language')
16+
await expect(page).toMatch('hello, world!')
17+
})
18+
19+
test('change banana select', async () => {
20+
await page.select('#fruits select', '3')
21+
await expect(page).toMatch('バナナ 3 個')
22+
})
23+
})

e2e/example.test.js

Lines changed: 0 additions & 18 deletions
This file was deleted.

e2e/global.test.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
describe.skip(`global`, () => {
2+
beforeAll(async () => {
3+
await page.goto(`http://localhost:8080/global/`)
4+
})
5+
6+
test('initial rendering', async () => {
7+
await expect(page).toMatch('言語')
8+
await expect(page).toMatch('こんにちは、世界!')
9+
})
10+
11+
test('change locale', async () => {
12+
await page.select('#app select', 'en')
13+
await expect(page).toMatch('Language')
14+
await expect(page).toMatch('hello, world!')
15+
})
16+
})

e2e/legacy.test.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
describe('legacy', () => {
2+
beforeAll(async () => {
3+
await page.goto(`http://localhost:8080/legacy/`)
4+
})
5+
6+
test('initial rendering', async () => {
7+
await expect(page).toMatch('言語')
8+
await expect(page).toMatch('こんにちは、世界!')
9+
await expect(page).toMatch('バナナが欲しい?')
10+
await expect(page).toMatch('バナナ 0 個')
11+
})
12+
13+
test('change locale', async () => {
14+
await page.select('#app select', 'en')
15+
await expect(page).toMatch('Language')
16+
await expect(page).toMatch('hello, world!')
17+
await expect(page).toMatch('no bananas')
18+
})
19+
20+
test('change banana select', async () => {
21+
await page.select('#fruits select', '3')
22+
await expect(page).toMatch('3 bananas')
23+
await page.select('#app select', 'ja')
24+
await expect(page).toMatch('バナナ 3 個')
25+
})
26+
})

example/composable/App.vue renamed to example/composition/App.vue

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,22 @@
77
</select>
88
</form>
99
<p>{{ t('hello') }}</p>
10+
<Banana />
1011
</template>
1112

1213
<script>
1314
import { useI18n } from 'vue-i18n'
15+
import Banana from './Banana.vue'
1416
1517
export default {
1618
name: 'App',
19+
components: {
20+
Banana
21+
},
1722
setup() {
1823
const { t, locale } = useI18n({
19-
locale: 'ja'
24+
inheritLocale: true,
25+
useScope: 'local'
2026
})
2127
return { t, locale }
2228
}

example/composition/Banana.vue

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<template>
2+
<form id="fruits">
3+
<label>{{ t('select') }}</label>
4+
<select v-model.number="select">
5+
<option value="0">0</option>
6+
<option value="1">1</option>
7+
<option value="2">2</option>
8+
<option value="3">3</option>
9+
</select>
10+
</form>
11+
<p>{{ t('fruits.banana', select, { n: select }) }}</p>
12+
</template>
13+
14+
<script>
15+
import { ref } from 'vue'
16+
import { useI18n } from 'vue-i18n'
17+
18+
export default {
19+
name: 'Banana',
20+
setup() {
21+
const { t } = useI18n({ useScope: 'global' })
22+
const select = ref(0)
23+
return { t, select }
24+
}
25+
}
26+
</script>

example/composition/en.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
select: What do you want banana?
2+
fruits:
3+
banana: 'no bananas | {n} banana | {n} bananas'

example/composable/index.html renamed to example/composition/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,6 @@
88
<div id="app">
99
<App />
1010
</div>
11-
<script src="/dist/composable.js"></script>
11+
<script src="/dist/composition.js"></script>
1212
</body>
1313
</html>

0 commit comments

Comments
 (0)