From 125c94be05e2293299bae0f35333e7ddc4cf9d58 Mon Sep 17 00:00:00 2001 From: kazuya kawaguchi Date: Sun, 20 Nov 2022 23:45:48 +0900 Subject: [PATCH] fix: exntending for Composer and VueI18n (#1237) * fix: exntending for Composer and VueI18n * fix: lint warnings --- packages/vue-i18n-core/src/i18n.ts | 22 +++++++++ packages/vue-i18n-core/src/mixins/next.ts | 5 ++ packages/vue-i18n-core/test/helper.ts | 21 ++++++++- packages/vue-i18n-core/test/i18n.test.ts | 57 +++++++++++++++++++++++ 4 files changed, 103 insertions(+), 2 deletions(-) diff --git a/packages/vue-i18n-core/src/i18n.ts b/packages/vue-i18n-core/src/i18n.ts index 0bffb775a..48632b345 100644 --- a/packages/vue-i18n-core/src/i18n.ts +++ b/packages/vue-i18n-core/src/i18n.ts @@ -240,6 +240,14 @@ export interface I18n< dispose(): void } +/** + * @internal + */ +type ExtendHooks = { + __composerExtend?: (composer: Composer) => void + __vueI18nExtend?: (vueI18n: VueI18n) => void +} + /** * I18n interface for internal usage * @@ -272,6 +280,8 @@ export interface I18nInternal< instance: Instance ): void __deleteInstance(component: ComponentInternalInstance): void + __composerExtend?: Required['__composerExtend'] + __vueI18nExtend?: Required['__vueI18nExtend'] } /** @@ -541,6 +551,15 @@ export function createI18n(options: any = {}, VueI18nLegacy?: any): any { app.__VUE_I18N_SYMBOL__ = symbol app.provide(app.__VUE_I18N_SYMBOL__, i18n as unknown as I18n) + // set composer & vuei18n extend hook options from plugin options + if (isPlainObject(options[0])) { + const opts = options[0] as ExtendHooks + ;(i18n as unknown as I18nInternal).__composerExtend = + opts.__composerExtend + ;(i18n as unknown as I18nInternal).__vueI18nExtend = + opts.__vueI18nExtend + } + // global method and properties injection for Composition API if (!__legacyMode && __globalInjection) { injectGlobalFields(app, i18n.global as Composer) @@ -838,6 +857,9 @@ export function useI18n< } composer = createComposer(composerOptions, _legacyVueI18n) as Composer + if (i18nInternal.__composerExtend) { + i18nInternal.__composerExtend(composer) + } setupLifeCycle(i18nInternal, instance, composer) i18nInternal.__setInstance(instance, composer) diff --git a/packages/vue-i18n-core/src/mixins/next.ts b/packages/vue-i18n-core/src/mixins/next.ts index 9e6e7541d..af800c011 100644 --- a/packages/vue-i18n-core/src/mixins/next.ts +++ b/packages/vue-i18n-core/src/mixins/next.ts @@ -97,6 +97,11 @@ export function defineMixin( this.$i18n.n(...args) this.$tm = (key: Path): LocaleMessageValue | {} => this.$i18n.tm(key) + + // extend vue-i18n legacy APIs + if (i18n.__vueI18nExtend) { + i18n.__vueI18nExtend(this.$i18n) + } }, mounted(): void { diff --git a/packages/vue-i18n-core/test/helper.ts b/packages/vue-i18n-core/test/helper.ts index 00bd7fda0..6b698fef9 100644 --- a/packages/vue-i18n-core/test/helper.ts +++ b/packages/vue-i18n-core/test/helper.ts @@ -15,7 +15,9 @@ import { import { compile } from '@vue/compiler-dom' import * as runtimeDom from 'vue' import { I18n } from '../src/i18n' -import { isBoolean, assign } from '@intlify/shared' +import { isBoolean, isPlainObject, assign } from '@intlify/shared' + +import type { I18nPluginOptions } from '../src/plugin/types' export interface MountOptions { propsData: Record // eslint-disable-line @typescript-eslint/no-explicit-any @@ -23,6 +25,7 @@ export interface MountOptions { components: ComponentOptions['components'] slots: Record installI18n: boolean + pluginOptions?: I18nPluginOptions } interface Wrapper { @@ -85,6 +88,20 @@ export function mount( const installI18n = isBoolean(options.installI18n) ? options.installI18n : true + + const pluginOptions: I18nPluginOptions = isPlainObject(options.pluginOptions) + ? options.pluginOptions + : { + globalInstall: true, + useI18nComponentName: false + } + if (pluginOptions.globalInstall == null) { + pluginOptions.globalInstall = true + } + if (pluginOptions.useI18nComponentName == null) { + pluginOptions.useI18nComponentName = false + } + return new Promise((resolve, reject) => { // NOTE: only supports props as an object const propsData = reactive( @@ -156,7 +173,7 @@ export function mount( } } - installI18n && app.use(i18n) + installI18n && app.use(i18n, pluginOptions) const rootEl = document.createElement('div') document.body.appendChild(rootEl) diff --git a/packages/vue-i18n-core/test/i18n.test.ts b/packages/vue-i18n-core/test/i18n.test.ts index a94b82afb..99a05003b 100644 --- a/packages/vue-i18n-core/test/i18n.test.ts +++ b/packages/vue-i18n-core/test/i18n.test.ts @@ -4,6 +4,7 @@ import { h, + ref, defineComponent, defineCustomElement, nextTick, @@ -32,6 +33,7 @@ import { } from '@intlify/devtools-if' import type { I18n } from '../src/i18n' +import type { VueI18n } from '../src/legacy' import type { App } from 'vue' const container = document.createElement('div') @@ -1360,3 +1362,58 @@ describe('release global scope', () => { expect(error).not.toEqual(errorMessages[I18nErrorCodes.UNEXPECTED_ERROR]) }) }) + +describe('Composer & VueI18n extend hooking', () => { + test('composition', async () => { + const composerExtendSpy = jest + .fn() + .mockImplementation((composer: Composer) => { + ;(composer as any).foo = ref('hello world') + }) + const vueI18nExtendSpy = jest.fn() + const i18n = createI18n({ + legacy: false + }) + + const App = defineComponent({ + setup() { + // @ts-ignore + const { foo } = useI18n({ + useScope: 'local' + }) + return { foo } + }, + template: '

{{ foo }}

' + }) + const { html } = await mount(App, i18n, { + pluginOptions: { + __composerExtend: composerExtendSpy, + __vueI18nExtend: vueI18nExtendSpy + } as any + }) + expect(composerExtendSpy).toHaveBeenCalled() + expect(html()).toBe('

hello world

') + expect(vueI18nExtendSpy).not.toHaveBeenCalled() + }) + + test('legacy', async () => { + const composerExtendSpy = jest.fn() + const vueI18nExtendSpy = jest + .fn() + .mockImplementation((vueI18n: VueI18n) => { + ;(vueI18n as any).foo = 'hello world' + }) + const i18n = createI18n({ legacy: true }) + + const App = defineComponent({ template: '

{{ $i18n.foo }}

' }) + const { html } = await mount(App, i18n, { + pluginOptions: { + __composerExtend: composerExtendSpy, + __vueI18nExtend: vueI18nExtendSpy + } as any + }) + expect(composerExtendSpy).not.toHaveBeenCalled() + expect(vueI18nExtendSpy).toHaveBeenCalled() + expect(html()).toBe('

hello world

') + }) +})