diff --git a/packages/petite-vue-i18n/src/vue.d.ts b/packages/petite-vue-i18n/src/vue.d.ts index 13f279505..d700e7a08 100644 --- a/packages/petite-vue-i18n/src/vue.d.ts +++ b/packages/petite-vue-i18n/src/vue.d.ts @@ -864,7 +864,7 @@ declare module '@vue/runtime-core' { * @param key - A target locale message key * @param locale - A locale, optional, override locale that global scope or local scope * - * @returns If found locale message, `true`, else `false`, Note that `false` is returned even if the value present in the Key is not translatable. + * @returns If found locale message, `true`, else `false`, Note that `false` is returned even if the value present in the key is not translatable, yet if `translateExistCompatible` is set to `true`, it will return `true` if the key is available, even if the value is translatable. */ $te< Key extends string, diff --git a/packages/vue-i18n-bridge/src/vue.d.ts b/packages/vue-i18n-bridge/src/vue.d.ts index 0f118b414..41c289396 100644 --- a/packages/vue-i18n-bridge/src/vue.d.ts +++ b/packages/vue-i18n-bridge/src/vue.d.ts @@ -869,7 +869,7 @@ declare module '@vue/runtime-core' { * @param key - A target locale message key * @param locale - A locale, optional, override locale that global scope or local scope * - * @returns If found locale message, `true`, else `false`, Note that `false` is returned even if the value present in the Key is not translatable. + * @returns If found locale message, `true`, else `false`, Note that `false` is returned even if the value present in the key is not translatable, yet if `translateExistCompatible` is set to `true`, it will return `true` if the key is available, even if the value is translatable. */ $te< Key extends string, diff --git a/packages/vue-i18n-core/src/composer.ts b/packages/vue-i18n-core/src/composer.ts index c7bf189da..909432540 100644 --- a/packages/vue-i18n-core/src/composer.ts +++ b/packages/vue-i18n-core/src/composer.ts @@ -13,7 +13,8 @@ import { assign, inBrowser, deepCopy, - hasOwn + hasOwn, + warnOnce } from '@intlify/shared' import { isTranslateFallbackWarn, @@ -614,6 +615,24 @@ export interface ComposerOptions< * @defaultValue `undefined` */ messageCompiler?: MessageCompiler + /** + * @remarks + * An option to make `te` behavior specification before v9.6 + * + * @VueI18nTip + * :new: v9.10+ + * + * @VueI18nWarning + * This flag will be removed in v10. + * + * @VueI18nSee [GitHub Issue](https://github.com/intlify/vue-i18n-next/issues/1738) + * + * @VueI18nSee [`te`](composition#te-key-locale) + * + * @defaultValue `false` + * + */ + translateExistCompatible?: boolean } /** @@ -1383,7 +1402,7 @@ export interface Composer< * @param key - A target locale message key * @param locale - A locale, it will be used over than global scope or local scope * - * @returns If found locale message, `true`, else `false`, Note that `false` is returned even if the value present in the Key is not translatable. + * @returns If found locale message, `true`, else `false`, Note that `false` is returned even if the value present in the key is not translatable, yet if `translateExistCompatible` is set to `true`, it will return `true` if the key is available, even if the value is translatable. */ te< Str extends string, @@ -1860,6 +1879,17 @@ export function createComposer(options: any = {}, VueI18nLegacy?: any): any { const flatJson = options.flatJson const _ref = inBrowser ? ref : shallowRef + const translateExistCompatible = !!options.translateExistCompatible + if (__DEV__) { + if (translateExistCompatible && !__TEST__) { + warnOnce( + getWarnMessage( + I18nWarnCodes.NOTICE_DROP_TRANSLATE_EXIST_COMPATIBLE_FLAG + ) + ) + } + } + let _inheritLocale = isBoolean(options.inheritLocale) ? options.inheritLocale : true @@ -2357,11 +2387,11 @@ export function createComposer(options: any = {}, VueI18nLegacy?: any): any { const targetLocale = isString(locale) ? locale : _locale.value const message = getLocaleMessage(targetLocale) const resolved = _context.messageResolver(message, key) - return ( - isMessageAST(resolved) || - isMessageFunction(resolved) || - isString(resolved) - ) + return !translateExistCompatible + ? isMessageAST(resolved) || + isMessageFunction(resolved) || + isString(resolved) + : resolved != null }, () => [key], 'translate exists', diff --git a/packages/vue-i18n-core/src/i18n.ts b/packages/vue-i18n-core/src/i18n.ts index 7f395efa3..5750c2284 100644 --- a/packages/vue-i18n-core/src/i18n.ts +++ b/packages/vue-i18n-core/src/i18n.ts @@ -234,10 +234,10 @@ export interface I18n< * An instance of this property is **global scope***. */ readonly global: Legacy extends true - ? VueI18n - : Legacy extends false - ? Composer - : unknown + ? VueI18n + : Legacy extends false + ? Composer + : unknown /** * The property whether or not the Composition API is available * @@ -529,17 +529,17 @@ export function createI18n(options: any = {}, VueI18nLegacy?: any): any { // prettier-ignore const __legacyMode = __LITE__ ? false - : __FEATURE_LEGACY_API__ && isBoolean(options.legacy) + : __FEATURE_LEGACY_API__ && isBoolean(options.legacy) ? options.legacy : __FEATURE_LEGACY_API__ // prettier-ignore const __globalInjection = isBoolean(options.globalInjection) - ? options.globalInjection - : true + ? options.globalInjection + : true // prettier-ignore const __allowComposition = __LITE__ ? true - : __FEATURE_LEGACY_API__ && __legacyMode + : __FEATURE_LEGACY_API__ && __legacyMode ? !!options.allowComposition : true const __instances = new Map() @@ -1228,10 +1228,10 @@ function useI18nForLegacy( const _locale = ref( // prettier-ignore !isLocalScope || _inheritLocale - ? root.locale.value - : isString(options.locale) - ? options.locale - : DEFAULT_LOCALE + ? root.locale.value + : isString(options.locale) + ? options.locale + : DEFAULT_LOCALE ) const _fallbackLocale = ref( diff --git a/packages/vue-i18n-core/src/legacy.ts b/packages/vue-i18n-core/src/legacy.ts index 160d585c1..5b92e70b6 100644 --- a/packages/vue-i18n-core/src/legacy.ts +++ b/packages/vue-i18n-core/src/legacy.ts @@ -1376,6 +1376,8 @@ function convertComposerOptions< const datetimeFormats = options.datetimeFormats const numberFormats = options.numberFormats const flatJson = options.flatJson + const translateExistCompatible = (options as unknown as ComposerOptions) + .translateExistCompatible return { locale, @@ -1396,6 +1398,7 @@ function convertComposerOptions< escapeParameter, messageResolver: options.messageResolver, inheritLocale, + translateExistCompatible, __i18n, __root, __injectWithOption diff --git a/packages/vue-i18n-core/src/warnings.ts b/packages/vue-i18n-core/src/warnings.ts index 3e851202d..a9a954ad9 100644 --- a/packages/vue-i18n-core/src/warnings.ts +++ b/packages/vue-i18n-core/src/warnings.ts @@ -13,7 +13,8 @@ export const I18nWarnCodes = { COMPONENT_NAME_LEGACY_COMPATIBLE: inc(), // 14 NOT_FOUND_PARENT_SCOPE: inc(), // 15 IGNORE_OBJ_FLATTEN: inc(), // 16 - NOTICE_DROP_ALLOW_COMPOSITION: inc() // 17 + NOTICE_DROP_ALLOW_COMPOSITION: inc(), // 17 + NOTICE_DROP_TRANSLATE_EXIST_COMPATIBLE_FLAG: inc() // 18 } as const type I18nWarnCodes = (typeof I18nWarnCodes)[keyof typeof I18nWarnCodes] @@ -27,7 +28,8 @@ export const warnMessages: { [code: number]: string } = { [I18nWarnCodes.COMPONENT_NAME_LEGACY_COMPATIBLE]: `Component name legacy compatible: '{name}' -> 'i18n'`, [I18nWarnCodes.NOT_FOUND_PARENT_SCOPE]: `Not found parent scope. use the global scope.`, [I18nWarnCodes.IGNORE_OBJ_FLATTEN]: `Ignore object flatten: '{key}' key has an string value`, - [I18nWarnCodes.NOTICE_DROP_ALLOW_COMPOSITION]: `'allowComposition' option will be dropped in the next major version. For more information, please see 👉 https://tinyurl.com/2p97mcze` + [I18nWarnCodes.NOTICE_DROP_ALLOW_COMPOSITION]: `'allowComposition' option will be dropped in the next major version. For more information, please see 👉 https://tinyurl.com/2p97mcze`, + [I18nWarnCodes.NOTICE_DROP_TRANSLATE_EXIST_COMPATIBLE_FLAG]: `'translateExistCompatible' option will be dropped in the next major version.` } export function getWarnMessage( diff --git a/packages/vue-i18n-core/test/issues.test.ts b/packages/vue-i18n-core/test/issues.test.ts index 71d74c294..550e288cb 100644 --- a/packages/vue-i18n-core/test/issues.test.ts +++ b/packages/vue-i18n-core/test/issues.test.ts @@ -19,7 +19,8 @@ import { withDirectives, resolveDirective, nextTick, - getCurrentInstance + getCurrentInstance, + onMounted } from 'vue' import { setDevToolsHook, @@ -1229,7 +1230,6 @@ test('issue #1610', async () => { }) test('issue #1615', async () => { - console.log('----') const en = { hello: (() => { const fn = ctx => { @@ -1289,3 +1289,72 @@ test('issue #1717', async () => { 'a.b.c': 'Hello, Vue I18n' // should not be transformed to nested object like in issue }) }) + +test('issue #1738', async () => { + const resources = { + en: { + messages: { + common: { + actions: { + cancel: 'Cancel' + } + } + } + }, + nl: { + messages: { + common: { + actions: { + cancel: 'Cancel' + } + } + } + } + } + + function loadTranslations(): Promise { + return new Promise(resolve => resolve(resources)) + } + + function delay(ms: number) { + return new Promise(resolve => setTimeout(resolve, ms)) + } + + const i18n = createI18n({ + locale: 'nl', + legacy: false, + translateExistCompatible: true, + fallbackLocale: 'en', + missingWarn: false, + silentFallbackWarn: true + }) + + const App = defineComponent({ + setup() { + const { mergeLocaleMessage, te } = useI18n() + onMounted(() => { + setTimeout(async () => { + const data = await loadTranslations() + + for (const key in data) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const { messages } = (data as any)[key] + mergeLocaleMessage(key, messages) + } + }, 100) + }) + return { te } + }, + template: `
+

{{ te('common') }} - expected true

+

{{ te('common.actions') }} - expected true

+
` + }) + + const wrapper = await mount(App, i18n) + + await delay(110) + + expect(wrapper.find('#te1')?.textContent).toEqual(`true - expected true`) + expect(wrapper.find('#te2')?.textContent).toEqual(`true - expected true`) +}) diff --git a/packages/vue-i18n/src/vue.d.ts b/packages/vue-i18n/src/vue.d.ts index 0f118b414..41c289396 100644 --- a/packages/vue-i18n/src/vue.d.ts +++ b/packages/vue-i18n/src/vue.d.ts @@ -869,7 +869,7 @@ declare module '@vue/runtime-core' { * @param key - A target locale message key * @param locale - A locale, optional, override locale that global scope or local scope * - * @returns If found locale message, `true`, else `false`, Note that `false` is returned even if the value present in the Key is not translatable. + * @returns If found locale message, `true`, else `false`, Note that `false` is returned even if the value present in the key is not translatable, yet if `translateExistCompatible` is set to `true`, it will return `true` if the key is available, even if the value is translatable. */ $te< Key extends string,