diff --git a/packages/vue-i18n-routing/package.json b/packages/vue-i18n-routing/package.json index d6d8424a..e3db07c9 100644 --- a/packages/vue-i18n-routing/package.json +++ b/packages/vue-i18n-routing/package.json @@ -28,7 +28,7 @@ "vite-plugin-dts": "^3.5.1", "vitest": "^0.34.1", "vue": "^3.2.27", - "vue-i18n": "npm:vue-i18n@next", + "vue-i18n": "npm:vue-i18n@9.3.0-beta.26", "vue-i18n-bridge": "next", "vue-i18n-legacy": "npm:vue-i18n@8", "vue-router": "^4.1.5", diff --git a/packages/vue-i18n-routing/src/extends/__test__/i18n.test.ts b/packages/vue-i18n-routing/src/extends/__test__/i18n.test.ts index af901614..d4887d67 100644 --- a/packages/vue-i18n-routing/src/extends/__test__/i18n.test.ts +++ b/packages/vue-i18n-routing/src/extends/__test__/i18n.test.ts @@ -19,10 +19,29 @@ describe('extendI18n', () => { localeCodes: ['en', 'ja'] }) - useSetup(() => {}, [i18n]) + const vm = useSetup(() => {}, [i18n]) const composer = i18n.global as unknown as Composer - assert.deepEqual(composer.locales!.value, [{ code: 'en' }, { code: 'ja' }]) - assert.deepEqual(composer.localeCodes!.value, ['en', 'ja']) + assert.deepEqual(composer.locales.value, [{ code: 'en' }, { code: 'ja' }]) + assert.deepEqual(composer.localeCodes.value, ['en', 'ja']) + + vm.unmount() + }) + }) + + describe('vue-i18n v9: legacy mode', () => { + it('should be extended', () => { + const i18n = createI18n({ legacy: true, locale: 'en' }) + extendI18n(i18n, { + locales: [{ code: 'en' }, { code: 'ja' }], + localeCodes: ['en', 'ja'] + }) + + const vm = useSetup(() => {}, [i18n]) + const vueI18n = i18n.global as unknown as VueI18n + assert.deepEqual(vueI18n.locales, [{ code: 'en' }, { code: 'ja' }]) + assert.deepEqual(vueI18n.localeCodes, ['en', 'ja']) + + vm.unmount() }) }) @@ -52,8 +71,9 @@ describe('extendI18n', () => { }) const vm = useSetup(() => {}, [i18n]) const $i18n = (vm as any).$i18n - const composer = i18n.global as unknown as Composer + + // custom extending const foo = (composer as any).foo as Ref assert.equal(foo.value, 'foo') assert.equal($i18n.foo, 'foo') @@ -74,7 +94,7 @@ describe('extendI18n', () => { const vueI18nSpy = vi.fn() vueI18nSpy.mockImplementation(() => 'vue-i18n-foo') - const i18n = createI18n({ locale: 'en' }) + const i18n = createI18n({ legacy: true, locale: 'en' }) extendI18n(i18n, { locales: [{ code: 'en' }, { code: 'ja' }], localeCodes: ['en', 'ja'], @@ -101,8 +121,9 @@ describe('extendI18n', () => { }) const vm = useSetup(() => {}, [i18n]) const $i18n = (vm as any).$i18n - const vueI18n = i18n.global as unknown as VueI18n + + // custom extending const foo = (vueI18n as any).foo as string assert.equal(foo, 'vue-i18n-foo') assert.equal($i18n.foo, 'vue-i18n-foo') diff --git a/packages/vue-i18n-routing/src/extends/i18n.ts b/packages/vue-i18n-routing/src/extends/i18n.ts index cc20c83c..ac5a2d60 100644 --- a/packages/vue-i18n-routing/src/extends/i18n.ts +++ b/packages/vue-i18n-routing/src/extends/i18n.ts @@ -14,7 +14,7 @@ import { DEFAULT_BASE_URL } from '../constants' import { resolveBaseUrl, isVueI18n, getComposer, inBrowser } from '../utils' import type { I18nRoutingOptions, LocaleObject } from '../types' -import type { I18n, Composer, VueI18n } from '@intlify/vue-i18n-bridge' +import type { I18n, Composer, VueI18n, VueI18nExtender, ComposerExtender, Disposer } from '@intlify/vue-i18n-bridge' import type { App } from 'vue-demi' // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -57,11 +57,11 @@ export interface VueI18nRoutingPluginOptions { /** * @internal */ - __composerExtend?: (composer: Composer) => void + __composerExtend?: ComposerExtender /** * @internal */ - __vueI18nExtend?: (vueI18n: VueI18n) => void + __vueI18nExtend?: VueI18nExtender } export interface ExtendProperyDescripters { @@ -103,21 +103,29 @@ export function extendI18n( pluginOptions.inject = true } const orgComposerExtend = pluginOptions.__composerExtend - pluginOptions.__composerExtend = (c: Composer) => { - const g = getComposer(i18n) - c.locales = computed(() => g.locales.value) - c.localeCodes = computed(() => g.localeCodes.value) - c.baseUrl = computed(() => g.baseUrl.value) + pluginOptions.__composerExtend = (localComposer: Composer) => { + const globalComposer = getComposer(i18n) + localComposer.locales = computed(() => globalComposer.locales.value) + localComposer.localeCodes = computed(() => globalComposer.localeCodes.value) + localComposer.baseUrl = computed(() => globalComposer.baseUrl.value) + let orgComposerDispose: Disposer | undefined if (isFunction(orgComposerExtend)) { - Reflect.apply(orgComposerExtend, pluginOptions, [c]) + orgComposerDispose = Reflect.apply(orgComposerExtend, pluginOptions, [globalComposer]) + } + return () => { + orgComposerDispose && orgComposerDispose() } } - if (isVueI18n(i18n.global)) { + if (i18n.mode === 'legacy') { const orgVueI18nExtend = pluginOptions.__vueI18nExtend pluginOptions.__vueI18nExtend = (vueI18n: VueI18n) => { extendVueI18n(vueI18n, hooks.onExtendVueI18n) + let orgVueI18nDispose: Disposer | undefined if (isFunction(orgVueI18nExtend)) { - Reflect.apply(orgVueI18nExtend, pluginOptions, [vueI18n]) + orgVueI18nDispose = Reflect.apply(orgVueI18nExtend, pluginOptions, [vueI18n]) + } + return () => { + orgVueI18nDispose && orgVueI18nDispose() } } } @@ -125,13 +133,15 @@ export function extendI18n( options[0] = pluginOptions Reflect.apply(orgInstall, i18n, [vue, ...options]) - const composer = getComposer(i18n) + const globalComposer = getComposer(i18n) // extend global - scope.run(() => extendComposer(composer, { locales, localeCodes, baseUrl, hooks, context })) - if (isVueI18n(i18n.global)) { - extendVueI18n(i18n.global, hooks.onExtendVueI18n) - } + scope.run(() => { + extendComposer(globalComposer, { locales, localeCodes, baseUrl, hooks, context }) + if (i18n.mode === 'legacy' && isVueI18n(i18n.global)) { + extendVueI18n(i18n.global, hooks.onExtendVueI18n) + } + }) // extend vue component instance for Vue 3 const app = vue as App @@ -140,11 +150,12 @@ export function extendI18n( ? isVue3 ? app.config.globalProperties.$i18n : i18n + // for legacy mode : isVue2 ? i18n : null if (exported) { - extendExportedGlobal(exported, composer, hooks.onExtendExportedGlobal) + extendExportedGlobal(exported, globalComposer, hooks.onExtendExportedGlobal) } if (pluginOptions.inject) { @@ -162,7 +173,7 @@ export function extendI18n( }) } - // release scope on unmounting + // dispose when app will be unmounting if (app.unmount) { const unmountApp = app.unmount app.unmount = () => { @@ -203,37 +214,11 @@ function extendComposer(composer: Composer, options: VueI18nE } } -// eslint-disable-next-line @typescript-eslint/no-explicit-any -function extendExportedGlobal(exported: any, g: Composer, hook?: ExtendExportedGlobalHook) { - const properties: ExtendProperyDescripters[] = [ - { - locales: { - get() { - return g.locales.value - } - }, - localeCodes: { - get() { - return g.localeCodes.value - } - }, - baseUrl: { - get() { - return g.baseUrl.value - } - } - } - ] - hook && properties.push(hook(g)) - for (const property of properties) { - for (const [key, descriptor] of Object.entries(property)) { - Object.defineProperty(exported, key, descriptor) - } - } -} - -function extendVueI18n(vueI18n: VueI18n, hook?: ExtendVueI18nHook): void { - const composer = getComposer(vueI18n) +function extendProperyDescripters( + composer: Composer, + exported: any, // eslint-disable-line @typescript-eslint/no-explicit-any + hook?: ExtendVueI18nHook | ExtendExportedGlobalHook +): void { const properties: ExtendProperyDescripters[] = [ { locales: { @@ -256,11 +241,21 @@ function extendVueI18n(vueI18n: VueI18n, hook?: ExtendVueI18nHook): void { hook && properties.push(hook(composer)) for (const property of properties) { for (const [key, descriptor] of Object.entries(property)) { - Object.defineProperty(vueI18n, key, descriptor) + Object.defineProperty(exported, key, descriptor) } } } +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function extendExportedGlobal(exported: any, g: Composer, hook?: ExtendExportedGlobalHook) { + extendProperyDescripters(g, exported, hook) +} + +function extendVueI18n(vueI18n: VueI18n, hook?: ExtendVueI18nHook): void { + const c = getComposer(vueI18n) + extendProperyDescripters(c, vueI18n, hook) +} + // eslint-disable-next-line @typescript-eslint/no-explicit-any function isPluginOptions(options: any): options is VueI18nRoutingPluginOptions { return isObject(options) && ('inject' in options || '__composerExtend' in options || '__vueI18nExtend' in options) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0677268f..f8543940 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -94,10 +94,10 @@ importers: dependencies: '@intlify/shared': specifier: next - version: 9.3.0-beta.25 + version: 9.3.0-beta.26 '@intlify/vue-i18n-bridge': specifier: ^0.8.0 - version: 0.8.0(@vue/composition-api@1.7.1)(vue-i18n-bridge@9.3.0-beta.25)(vue-i18n@9.3.0-beta.25) + version: 0.8.0(@vue/composition-api@1.7.1)(vue-i18n-bridge@9.3.0-beta.25)(vue-i18n@9.3.0-beta.26) '@intlify/vue-router-bridge': specifier: ^0.8.0 version: 0.8.0(@vue/composition-api@1.7.1)(vue-router@4.2.4)(vue@3.3.4) @@ -133,8 +133,8 @@ importers: specifier: ^3.2.27 version: 3.3.4 vue-i18n: - specifier: npm:vue-i18n@next - version: 9.3.0-beta.25(vue@3.3.4) + specifier: npm:vue-i18n@9.3.0-beta.26 + version: 9.3.0-beta.26(vue@3.3.4) vue-i18n-bridge: specifier: next version: 9.3.0-beta.25(vue@3.3.4) @@ -499,6 +499,15 @@ packages: '@intlify/shared': 9.3.0-beta.25 '@intlify/vue-devtools': 9.3.0-beta.25 + /@intlify/core-base@9.3.0-beta.26: + resolution: {integrity: sha512-omhEuB6+uE3D36wAtBazTkjQxfMy+sHAdvPe51aPINVcIiL9RZiSMgecipbvkjqnL/5ogTcCc/UXKWlM3jb90g==} + engines: {node: '>= 16'} + dependencies: + '@intlify/devtools-if': 9.3.0-beta.26 + '@intlify/message-compiler': 9.3.0-beta.26 + '@intlify/shared': 9.3.0-beta.26 + '@intlify/vue-devtools': 9.3.0-beta.26 + /@intlify/devtools-if@9.2.2: resolution: {integrity: sha512-4ttr/FNO29w+kBbU7HZ/U0Lzuh2cRDhP8UlWOtV9ERcjHzuyXVZmjyleESK6eVP60tGC9QtQW9yZE+JeRhDHkg==} engines: {node: '>= 14'} @@ -512,6 +521,12 @@ packages: dependencies: '@intlify/shared': 9.3.0-beta.25 + /@intlify/devtools-if@9.3.0-beta.26: + resolution: {integrity: sha512-N13XwjkT/payWlr8DqBVylsPlwjm4WyaI+eaG+eVdBKPw0yI/9c4zJ3U+/ASRBRuzArVJxALE+LT/cI1NxWGvw==} + engines: {node: '>= 16'} + dependencies: + '@intlify/shared': 9.3.0-beta.26 + /@intlify/eslint-plugin-vue-i18n@2.0.0(eslint@8.47.0): resolution: {integrity: sha512-ECBD0TvQNa56XKyuM6FPIGAAl7MP6ODcgjBQJrzucNxcTb8fYTWmZ+xgBuvmvAtA0iE0D4Wp18UMild2N0bGyw==} engines: {node: ^14.17.0 || >=16.0.0} @@ -553,6 +568,13 @@ packages: '@intlify/shared': 9.3.0-beta.25 source-map-js: 1.0.2 + /@intlify/message-compiler@9.3.0-beta.26: + resolution: {integrity: sha512-qsfU6Lca7mI80ts1vgy+pfNvGm2gHy0nERpT/K1GYgnECmsKwud0e8SG1PPxKPEHKa5Mdngzs4pS7X1wH0SCGA==} + engines: {node: '>= 16'} + dependencies: + '@intlify/shared': 9.3.0-beta.26 + source-map-js: 1.0.2 + /@intlify/shared@9.2.2: resolution: {integrity: sha512-wRwTpsslgZS5HNyM7uDQYZtxnbI12aGiBZURX3BTR9RFIKKRWpllTsgzHWvj3HKm3Y2Sh5LPC1r0PDCKEhVn9Q==} engines: {node: '>= 14'} @@ -562,6 +584,10 @@ packages: resolution: {integrity: sha512-Zg+ECV9RPdp227tCJOgvPb+S3i651nf4kKHsMojSyWCppVK/4NFuDrBG2lIQSQL6Iq5LKVr5MkezHCW2NBTQRg==} engines: {node: '>= 16'} + /@intlify/shared@9.3.0-beta.26: + resolution: {integrity: sha512-RpCtfSYIg4tSskrazTr5+WCHyw6qpgwdIxC+x3nCnrPGxyk+en9FoSbadVfx/w7uDTdyhKslEw4d2+qhNc0s4Q==} + engines: {node: '>= 16'} + /@intlify/vue-devtools@9.2.2: resolution: {integrity: sha512-+dUyqyCHWHb/UcvY1MlIpO87munedm3Gn6E9WWYdWrMuYLcoIoOEVDWSS8xSwtlPU+kA+MEQTP6Q1iI/ocusJg==} engines: {node: '>= 14'} @@ -577,7 +603,14 @@ packages: '@intlify/core-base': 9.3.0-beta.25 '@intlify/shared': 9.3.0-beta.25 - /@intlify/vue-i18n-bridge@0.8.0(@vue/composition-api@1.7.1)(vue-i18n-bridge@9.3.0-beta.25)(vue-i18n@9.3.0-beta.25): + /@intlify/vue-devtools@9.3.0-beta.26: + resolution: {integrity: sha512-IwJM8jUCM+savGDvgcOQaVHNMPDbvRf9dVjB+skCemYA7DBqtJ0YypTlRDtt6PO9rDfkWU2Z0ueaY/o6OaGtRg==} + engines: {node: '>= 16'} + dependencies: + '@intlify/core-base': 9.3.0-beta.26 + '@intlify/shared': 9.3.0-beta.26 + + /@intlify/vue-i18n-bridge@0.8.0(@vue/composition-api@1.7.1)(vue-i18n-bridge@9.3.0-beta.25)(vue-i18n@9.3.0-beta.26): resolution: {integrity: sha512-wQ18fSccm9QaWpUW2vq2QHvojgKIog7s+UMj9LeY3pUV3yD9bU4YZI+1PTNoX3tOA+BE71gQyqVGox/TVQKP6Q==} engines: {node: '>= 12'} hasBin: true @@ -595,7 +628,7 @@ packages: optional: true dependencies: '@vue/composition-api': 1.7.1(vue@3.3.4) - vue-i18n: 9.3.0-beta.25(vue@3.3.4) + vue-i18n: 9.3.0-beta.26(vue@3.3.4) vue-i18n-bridge: 9.3.0-beta.25(vue@3.3.4) dev: false @@ -4642,15 +4675,15 @@ packages: vue: 3.3.4 dev: true - /vue-i18n@9.3.0-beta.25(vue@3.3.4): - resolution: {integrity: sha512-WfR5W3ql2fGFGyJfD6WsoyewqINra5NKxW50RIbLpiqw099PRNBZ4pPgMuO0J194OQ+VayOEMEGSnAimcolbQQ==} + /vue-i18n@9.3.0-beta.26(vue@3.3.4): + resolution: {integrity: sha512-RfyxO4wMJ3SiqjxKGydA1RnJ6rvl4y60Urzqab80x9weCooNdb7++fRce+ZtSqtvnt4NTSq4je/WWyU8EAR9cA==} engines: {node: '>= 16'} peerDependencies: vue: ^3.0.0 dependencies: - '@intlify/core-base': 9.3.0-beta.25 - '@intlify/shared': 9.3.0-beta.25 - '@intlify/vue-devtools': 9.3.0-beta.25 + '@intlify/core-base': 9.3.0-beta.26 + '@intlify/shared': 9.3.0-beta.26 + '@intlify/vue-devtools': 9.3.0-beta.26 '@vue/devtools-api': 6.5.0 vue: 3.3.4