Skip to content

Commit 3756bc9

Browse files
authored
feat(unplugin-vue-i18n): allow for a custom i18n block transform hook (#387)
* implement webpack custom block transform * remove debug code * remove json generate modifications * add vite example * remove vestigial debug code and adjust types * remove unused value from json.ts * add readme documentation for transformI18nBlock
1 parent b576904 commit 3756bc9

File tree

11 files changed

+182
-8
lines changed

11 files changed

+182
-8
lines changed

examples/vite/src/App.vue

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,18 @@
99
<p id="msg">{{ t('hello') }}</p>
1010
<p id="custom-directive" v-t="'hi'"></p>
1111
<Banana />
12+
<Apple />
1213
</template>
1314

1415
<script>
1516
import { useI18n } from 'vue-i18n'
17+
import Apple from './Apple.vue'
1618
import Banana from './Banana.vue'
1719
1820
export default {
1921
name: 'App',
2022
components: {
23+
Apple,
2124
Banana
2225
},
2326
setup() {

examples/vite/src/Apple.vue

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<template>
2+
<p>{{ t('language') }}</p>
3+
<p>{{ t('hello') }}</p>
4+
</template>
5+
6+
<script>
7+
import { useI18n } from 'vue-i18n'
8+
9+
export default {
10+
name: 'Apple',
11+
setup() {
12+
const { t } = useI18n({
13+
inheritLocale: true,
14+
useScope: 'local'
15+
})
16+
return { t }
17+
}
18+
}
19+
</script>
20+
21+
<i18n>
22+
[
23+
"language",
24+
"hello",
25+
]
26+
</i18n>

examples/vite/vite.config.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,27 @@ import { defineConfig } from 'vite'
33
import vue from '@vitejs/plugin-vue'
44
import vueI18n from '../../packages/unplugin-vue-i18n/src/vite'
55

6+
function transformI18nBlock(source) {
7+
const sourceCopy = source
8+
const block = JSON.parse(
9+
sourceCopy.replace(/[\n\s]/g, '').replace(/,\]$/, ']')
10+
)
11+
if (Array.isArray(block)) {
12+
const transformedBlock = JSON.stringify({
13+
en: {
14+
language: 'Language',
15+
hello: 'hello, world!'
16+
},
17+
ja: {
18+
language: '言語',
19+
hello: 'こんにちは、世界!'
20+
}
21+
})
22+
return transformedBlock
23+
}
24+
return source
25+
}
26+
627
export default defineConfig({
728
resolve: {
829
alias: {
@@ -21,7 +42,8 @@ export default defineConfig({
2142
vue(),
2243
vueI18n({
2344
include: path.resolve(__dirname, './src/locales/**'),
24-
optimizeTranslationDirective: true
45+
optimizeTranslationDirective: true,
46+
transformI18nBlock: transformI18nBlock
2547
})
2648
]
2749
})

examples/webpack/src/App.vue

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,18 @@
88
</form>
99
<p>{{ t('hello') }}</p>
1010
<Banana />
11+
<Apple />
1112
</template>
1213

1314
<script>
1415
import { useI18n } from 'vue-i18n'
16+
import Apple from './Apple.vue'
1517
import Banana from './Banana.vue'
1618
1719
export default {
1820
name: 'App',
1921
components: {
22+
Apple,
2023
Banana
2124
},
2225
setup() {

examples/webpack/src/Apple.vue

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<template>
2+
<p>{{ t('language') }}</p>
3+
<p>{{ t('hello') }}</p>
4+
</template>
5+
6+
<script>
7+
import { useI18n } from 'vue-i18n'
8+
9+
export default {
10+
name: 'Apple',
11+
setup() {
12+
const { t } = useI18n({
13+
inheritLocale: true,
14+
useScope: 'local'
15+
})
16+
return { t }
17+
}
18+
}
19+
</script>
20+
21+
<i18n>
22+
[
23+
"language",
24+
"hello",
25+
]
26+
</i18n>

examples/webpack/webpack.config.js

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,27 @@ const path = require('path')
22
const { VueLoaderPlugin } = require('vue-loader')
33
const VueI18nPlugin = require('../../packages/unplugin-vue-i18n/lib/webpack.cjs')
44

5+
function transformI18nBlock(source) {
6+
const sourceCopy = source
7+
const block = JSON.parse(
8+
sourceCopy.replace(/[\n\s]/g, '').replace(/,\]$/, ']')
9+
)
10+
if (Array.isArray(block)) {
11+
const transformedBlock = JSON.stringify({
12+
en: {
13+
language: 'Language',
14+
hello: 'hello, world!'
15+
},
16+
ja: {
17+
language: '言語',
18+
hello: 'こんにちは、世界!'
19+
}
20+
})
21+
return transformedBlock
22+
}
23+
return source
24+
}
25+
526
module.exports = {
627
mode: 'development',
728
devtool: 'source-map',
@@ -39,7 +60,8 @@ module.exports = {
3960
plugins: [
4061
new VueLoaderPlugin(),
4162
VueI18nPlugin({
42-
include: [path.resolve(__dirname, './src/locales/**')]
63+
include: [path.resolve(__dirname, './src/locales/**')],
64+
transformI18nBlock: transformI18nBlock
4365
})
4466
]
4567
}

packages/bundle-utils/src/codegen.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ export interface CodeGenOptions {
6161
escapeHtml?: boolean
6262
jit?: boolean
6363
minify?: boolean
64+
transformI18nBlock?: (source: string | Buffer) => string
6465
onWarn?: (msg: string) => void
6566
onError?: (
6667
msg: string,

packages/unplugin-vue-i18n/README.md

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -583,6 +583,53 @@ If do you will use this option, you need to enable `jitCompilation` option.
583583
> [!WARNING]
584584
About for manually signature, see the details [vue-i18n-extensions API docs](https://github.com/intlify/vue-i18n-extensions/blob/next/docs/%40intlify/vue-i18n-extensions-api.md#translationsignatures) and [usecase from vue-i18n-extensions PR](https://github.com/intlify/vue-i18n-extensions/pull/217/files#diff-3fb9543f91e011d4b0dc9beff44082fe1a99c9eab70c1afab23c3c34352b7c38R121-R200)
585585

586+
### `transformI18nBlock`
587+
588+
- **Type**: `function`
589+
- **Default:** `undefined`
590+
591+
This hook allows a user to modify the `<i18n>` block before the plugin generates the translations. The hook is passed the source of the `<ii8n>` block as a `string` after the SFC is read from disk.
592+
593+
**Plugin**
594+
```javascript
595+
function transformI18nBlock(source) {
596+
// transformation logic
597+
}
598+
599+
// Plugin
600+
vueI18n({
601+
transformI18nBlock
602+
})
603+
```
604+
605+
**Before**
606+
```html
607+
<i18n>
608+
[
609+
'slug-one',
610+
'slug-two'
611+
]
612+
<i18n>
613+
```
614+
615+
**After**
616+
```html
617+
<i18n>
618+
{
619+
'en': {
620+
'slug-one': 'foo',
621+
'slug-two': 'bar'
622+
},
623+
ja: {
624+
'slug-one': 'foo',
625+
'slug-two': 'bar'
626+
}
627+
}
628+
</i18n>
629+
```
630+
> [!IMPORTANT]
631+
The function **must** return a string or the build will fail.
632+
586633
## 📜 Changelog
587634

588635
Details changes for each release are documented in the [CHANGELOG.md](https://github.com/intlify/bundle-tools/blob/main/packages/unplugin-vue-i18n/CHANGELOG.md)

packages/unplugin-vue-i18n/src/core/options.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,11 @@ export function resolveOptions(
7777
TranslationDirectiveResolveIndetifier
7878
>()
7979

80+
const transformI18nBlock =
81+
typeof options.transformI18nBlock === 'function'
82+
? options.transformI18nBlock
83+
: null
84+
8085
return {
8186
include,
8287
exclude,
@@ -93,7 +98,8 @@ export function resolveOptions(
9398
strictMessage,
9499
escapeHtml,
95100
optimizeTranslationDirective,
96-
translationIdentifiers
101+
translationIdentifiers,
102+
transformI18nBlock
97103
}
98104
}
99105

packages/unplugin-vue-i18n/src/core/resource.ts

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,8 @@ export function resourcePlugin(
6060
ssrBuild,
6161
strictMessage,
6262
allowDynamic,
63-
escapeHtml
63+
escapeHtml,
64+
transformI18nBlock
6465
}: ResolvedOptions,
6566
meta: UnpluginContextMeta,
6667
installedPkgInfo: InstalledPackageInfo
@@ -437,14 +438,24 @@ export function resourcePlugin(
437438
) as CodeGenOptions
438439
debug('parseOptions', parseOptions)
439440

440-
const source = await getCode(
441+
let source = await getCode(
441442
code,
442443
filename,
443444
sourceMap,
444445
query,
445446
getSfcParser(),
446447
meta.framework
447448
)
449+
450+
if (typeof transformI18nBlock === 'function') {
451+
const modifiedSource = transformI18nBlock(source)
452+
if (modifiedSource && typeof modifiedSource === 'string') {
453+
source = modifiedSource
454+
} else {
455+
warn('transformI18nBlock should return a string')
456+
}
457+
}
458+
448459
const { code: generatedCode, map } = generate(source, parseOptions)
449460
debug('generated code', generatedCode)
450461
debug('sourcemap', map, sourceMap)
@@ -518,14 +529,16 @@ async function generateBundleResources(
518529
onlyLocales = [],
519530
strictMessage = true,
520531
escapeHtml = false,
521-
jit = true
532+
jit = true,
533+
transformI18nBlock = undefined
522534
}: {
523535
forceStringify?: boolean
524536
isGlobal?: boolean
525537
onlyLocales?: string[]
526538
strictMessage?: boolean
527539
escapeHtml?: boolean
528540
jit?: boolean
541+
transformI18nBlock?: PluginOptions['transformI18nBlock']
529542
}
530543
) {
531544
const codes = []
@@ -542,7 +555,8 @@ async function generateBundleResources(
542555
onlyLocales,
543556
strictMessage,
544557
escapeHtml,
545-
forceStringify
558+
forceStringify,
559+
transformI18nBlock
546560
}) as CodeGenOptions
547561
parseOptions.type = 'bare'
548562
const { code } = generate(source, parseOptions)
@@ -637,7 +651,8 @@ function getOptions(
637651
allowDynamic = false,
638652
strictMessage = true,
639653
escapeHtml = false,
640-
jit = true
654+
jit = true,
655+
transformI18nBlock = null
641656
}: {
642657
inSourceMap?: RawSourceMap
643658
forceStringify?: boolean
@@ -647,6 +662,7 @@ function getOptions(
647662
strictMessage?: boolean
648663
escapeHtml?: boolean
649664
jit?: boolean
665+
transformI18nBlock?: PluginOptions['transformI18nBlock'] | null
650666
}
651667
): Record<string, unknown> {
652668
const mode: DevEnv = isProduction ? 'production' : 'development'
@@ -662,6 +678,7 @@ function getOptions(
662678
jit,
663679
onlyLocales,
664680
env: mode,
681+
transformI18nBlock,
665682
onWarn: (msg: string): void => {
666683
warn(`${filename} ${msg}`)
667684
},

0 commit comments

Comments
 (0)