From 1c95f68dbd9095f4b92e50e638824e9de68bf3b5 Mon Sep 17 00:00:00 2001 From: kazuya kawaguchi Date: Wed, 13 Dec 2023 12:26:51 +0900 Subject: [PATCH] feat(unplugin-vue-i18n): support compatibility i18n custom blocks for legacy vue-i18n v8.x (#332) * feat(unplugin-vue-i18n): support compatibility i18n custom blocks for legacy vue-i18n v8.x * fix: remove debug console --- packages/unplugin-vue-i18n/README.md | 38 ++++++++++++++----- packages/unplugin-vue-i18n/src/index.ts | 17 +++++++++ packages/unplugin-vue-i18n/src/types.ts | 4 ++ packages/unplugin-vue-i18n/test/utils.ts | 4 ++ .../__snapshots__/custom-block.test.ts.snap | 20 ++++++++++ .../test/vite/custom-block.test.ts | 17 +++++++++ 6 files changed, 90 insertions(+), 10 deletions(-) diff --git a/packages/unplugin-vue-i18n/README.md b/packages/unplugin-vue-i18n/README.md index 47cb64e..2ba6d7a 100644 --- a/packages/unplugin-vue-i18n/README.md +++ b/packages/unplugin-vue-i18n/README.md @@ -281,13 +281,13 @@ This plugin will automatically select and bundle `petite-vue-i18n` build accordi If nothing is specified for this option, i.e. `undefined`, nothing is done to the resource in the above format. - > ⚠️ NOTE: + > ⚠️ NOTE: `json` resources matches this option, it will be handled **before the internal json plugin of bundler, and will not be processed afterwards**, else the option doesn't match, the bundler side will handle. - > ⚠️ NOTE: + > ⚠️ NOTE: `yaml` resources don't support multi documentation with `|`, alias with `&` and `*`, tags with `! `, `@`, etc. Only simple data structures. - > ⚠️ NOTE: + > ⚠️ NOTE: `js` and `ts` resources are set **simple export (`export default`) as locale messages object, as default**. ```js @@ -299,7 +299,7 @@ This plugin will automatically select and bundle `petite-vue-i18n` build accordi If you need to use programmatically dynamic resource construction, you would be enable `allowDynamic` option. about details, see the section. - > ⚠️ NOTE: + > ⚠️ NOTE: If you use the `js` and `ts` resources formats, set the paths, so your application code is not targeted. We recommend that resources be isolated from the application code. @@ -354,12 +354,12 @@ This plugin will automatically select and bundle `petite-vue-i18n` build accordi Whether locale mesages should be compiled by JIT (Just in Time) compilation with vue-i18n's message compiler. - > ⚠️ NOTE: + > ⚠️ NOTE: This option works with vue-i18n v9.3 and later. JIT compilation has been supported since vue-i18n v9.3. This means that since v9 was released until now, the message compiler compiles to executable JavaScript code, however it did not work in the CSP environment. Also, since this was an AOT (Ahead of Time) compilation, it was not possible to dynamically retrieve locale messages from the back-end Database and compose locale mesages with programatic. - > ⚠️ NOTE: + > ⚠️ NOTE: Enabling JIT compilation causes the message compiler to generate AST objects for locale mesages instead of JavaScript code. If you pre-compile locale messages with a tool such as the [Intlify CLI](https://github.com/intlify/cli) and import them dynamically, you need to rebuild that resource. About JIT compilation, See [here](https://vue-i18n.intlify.dev/guide/advanced/optimization.html#jit-compilation) @@ -373,10 +373,10 @@ Whether to tree-shake message compiler when we will be bundling. If do you will use this option, you need to enable `jitCompilation` option. -> ⚠️ NOTE: +> ⚠️ NOTE: This option works with vue-i18n v9.3 and later. -> ⚠️ NOTE: +> ⚠️ NOTE: If you enable this option, **you should check resources in your application are pre-compiled with this plugin.** If you will be loading resources dynamically from the back-end via the API, enabling this option do not work because there is not message compiler. ### `ssr` @@ -386,7 +386,7 @@ If you enable this option, **you should check resources in your application are Whether to bundle vue-i18n module for SSR at build time - > ⚠️ NOTE: + > ⚠️ NOTE: This option works with vue-i18n v9.4 and later. ### `runtimeOnly` @@ -395,7 +395,7 @@ If you enable this option, **you should check resources in your application are - **Default:** `true` Whether or not to automatically use Vue I18n **runtime-only** in production build, set `vue-i18n.runtime.esm-bundler.js` in the `vue-i18n` field of bundler config, the below: - + ``` - vite config: `resolve.alias` - webpack config: `resolve.alias` @@ -547,6 +547,24 @@ If you enable this option, **you should check resources in your application are > ⚠️ Note that if you set `bridge: true`, the bundle size will increase. It is recommended to disable this mode after the migration from vue-i18n@v8.26 to vue-i18n@v9.x is completed. +### `legacy` + +- **Type:** `boolean` +- **Default:** `false` + + This option supports Vue I18n v8.x compatibility for resources in i18n custom blocks. + + > ⚠️ To work for Vue 2.7, the value of `vueVersion` must be set to `'v2.7'`. + +### `vueVersion` + +- **Type:** `string` +- **Default:** `undefined` + + The version of Vue that will be used by Vue I18n. This option is enabled when the `legacy` option is `true`. + + Available values are `'v2.6'` and `'v2.7'`. + ### `esm` - **Type:** `boolean` diff --git a/packages/unplugin-vue-i18n/src/index.ts b/packages/unplugin-vue-i18n/src/index.ts index 29ff8d0..59ba61c 100644 --- a/packages/unplugin-vue-i18n/src/index.ts +++ b/packages/unplugin-vue-i18n/src/index.ts @@ -86,6 +86,13 @@ export const unplugin = createUnplugin((options = {}, meta) => { const bridge = !!options.bridge debug('bridge', bridge) + const legacy = !!options.legacy + debug('legacy', legacy) + + const vueVersion = isString(options.vueVersion) + ? options.vueVersion + : undefined + const runtimeOnly = isBoolean(options.runtimeOnly) ? options.runtimeOnly : true @@ -354,6 +361,8 @@ export const unplugin = createUnplugin((options = {}, meta) => { strictMessage, escapeHtml, bridge, + legacy, + vueVersion, jit: jitCompilation, onlyLocales, exportESM: esm, @@ -636,6 +645,8 @@ export const unplugin = createUnplugin((options = {}, meta) => { isGlobal: globalSFCScope, useClassComponent, bridge, + legacy, + vueVersion, jit: jitCompilation, strictMessage, escapeHtml, @@ -863,6 +874,8 @@ function getOptions( forceStringify = false, isGlobal = false, bridge = false, + legacy = false, + vueVersion = 'v2.6', onlyLocales = [], exportESM = true, useClassComponent = false, @@ -875,6 +888,8 @@ function getOptions( forceStringify?: boolean isGlobal?: boolean bridge?: boolean + legacy?: boolean + vueVersion?: CodeGenOptions['vueVersion'] onlyLocales?: string[] exportESM?: boolean useClassComponent?: boolean @@ -896,6 +911,8 @@ function getOptions( strictMessage, escapeHtml, bridge, + legacy, + vueVersion, jit, onlyLocales, exportESM, diff --git a/packages/unplugin-vue-i18n/src/types.ts b/packages/unplugin-vue-i18n/src/types.ts index 2bf72bd..1c91176 100644 --- a/packages/unplugin-vue-i18n/src/types.ts +++ b/packages/unplugin-vue-i18n/src/types.ts @@ -1,3 +1,5 @@ +import { CodeGenOptions } from '@intlify/bundle-utils' + export type SFCLangFormat = 'json' | 'json5' | 'yml' | 'yaml' export interface PluginOptions { include?: string | string[] @@ -14,6 +16,8 @@ export interface PluginOptions { defaultSFCLang?: SFCLangFormat globalSFCScope?: boolean bridge?: boolean + legacy?: boolean + vueVersion?: CodeGenOptions['vueVersion'] useClassComponent?: boolean useVueI18nImportName?: boolean strictMessage?: boolean diff --git a/packages/unplugin-vue-i18n/test/utils.ts b/packages/unplugin-vue-i18n/test/utils.ts index 074805f..4086aa3 100644 --- a/packages/unplugin-vue-i18n/test/utils.ts +++ b/packages/unplugin-vue-i18n/test/utils.ts @@ -158,6 +158,10 @@ export async function bundleAndRun( options.sourcemap = isBoolean(options.sourcemap) || false options.useClassComponent = isBoolean(options.useClassComponent) || false options.bridge = isBoolean(options.bridge) || false + options.legacy = isBoolean(options.legacy) || false + options.vueVersion = isString(options.vueVersion) + ? options.vueVersion + : 'v2.6' options.allowDynamic = isBoolean(options.allowDynamic) || false options.strictMessage = isBoolean(options.strictMessage) ? options.strictMessage diff --git a/packages/unplugin-vue-i18n/test/vite/__snapshots__/custom-block.test.ts.snap b/packages/unplugin-vue-i18n/test/vite/__snapshots__/custom-block.test.ts.snap index 73d3d94..34b0fa7 100644 --- a/packages/unplugin-vue-i18n/test/vite/__snapshots__/custom-block.test.ts.snap +++ b/packages/unplugin-vue-i18n/test/vite/__snapshots__/custom-block.test.ts.snap @@ -205,6 +205,26 @@ exports[`json5: exclude locales 1`] = ` ] `; +exports[`legacy for Vue 2.6 and before 1`] = ` +"function block0(Component) { + Component.options.__i18n = Component.options.__i18n || []; + Component.options.__i18n.push('{\\"en\\":{\\"hello\\":\\"hello world!\\"}}'); + delete Component.options._Ctor; +} +const _sfc_main = {}; +if (typeof block0 === \\"function\\") + block0(_sfc_main); +const exports = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ + __proto__: null, + default: _sfc_main +}, Symbol.toStringTag, { value: \\"Module\\" })); +if (typeof window !== \\"undefined\\") { + window.module = _sfc_main; + window.exports = exports; +} +" +`; + exports[`locale attr 1`] = ` [ { diff --git a/packages/unplugin-vue-i18n/test/vite/custom-block.test.ts b/packages/unplugin-vue-i18n/test/vite/custom-block.test.ts index 74eba6c..1e2db90 100644 --- a/packages/unplugin-vue-i18n/test/vite/custom-block.test.ts +++ b/packages/unplugin-vue-i18n/test/vite/custom-block.test.ts @@ -164,6 +164,23 @@ test('global scope and import', async () => { expect(g.resource.en.hello(createMessageContext())).toEqual('hello world!') }) +test('legacy for Vue 2.6 and before', async () => { + const { code } = await bundleAndRun('basic.vue', bundleVite, { + legacy: true + }) + expect(code).toMatchSnapshot() + expect(code).toMatch('Component.options.__i18n =') +}) + +test('legacy for Vue 2.7', async () => { + const { module } = await bundleAndRun('basic.vue', bundleVite, { + legacy: true, + vueVersion: 'v2.7' + }) + const i18n = JSON.parse(module.__i18n.pop()) + expect(i18n.en.hello).toEqual('hello world!') +}) + test('array', async () => { const { module } = await bundleAndRun('array.vue', bundleVite) expect(module.__i18n).toMatchSnapshot()