/* eslint-disable @typescript-eslint/no-explicit-any */ import { isArray, isObject, hasOwn, isPlainObject, isString } from '@intlify/shared' import { Text, createVNode } from 'vue' import { I18nErrorCodes, createI18nError } from './errors' import type { Locale, MessageResolver } from '@intlify/core-base' import type { Composer, ComposerOptions, CustomBlocks, VueMessageType } from './composer' import type { ComponentInternalInstance, RendererNode, RendererElement } from 'vue' type GetLocaleMessagesOptions<Messages = {}> = { messages?: { [K in keyof Messages]: Messages[K] } __i18n?: CustomBlocks<VueMessageType> messageResolver?: MessageResolver flatJson?: boolean } declare module 'vue' { // eslint-disable-next-line @typescript-eslint/no-unused-vars interface VNode<HostNode = RendererNode, HostElement = RendererElement> { toString: () => string // mark for vue-i18n message runtime } } // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types export function isLegacyVueI18n(VueI18n: any): boolean { if (VueI18n == null || VueI18n.version == null) { return false } return (Number(VueI18n.version.split('.')[0]) || -1) >= 8 } /** * Transform flat json in obj to normal json in obj */ export function handleFlatJson(obj: unknown): unknown { // check obj if (!isObject(obj)) { return obj } for (const key in obj as object) { // check key if (!hasOwn(obj, key)) { continue } // handle for normal json if (!key.includes('.')) { // recursive process value if value is also a object if (isObject(obj[key])) { handleFlatJson(obj[key]) } } // handle for flat json, transform to normal json else { // go to the last object const subKeys = key.split('.') const lastIndex = subKeys.length - 1 let currentObj = obj for (let i = 0; i < lastIndex; i++) { if (!(subKeys[i] in currentObj)) { currentObj[subKeys[i]] = {} } currentObj = currentObj[subKeys[i]] } // update last object value, delete old property currentObj[subKeys[lastIndex]] = obj[key] delete obj[key] // recursive process value if value is also a object if (isObject(currentObj[subKeys[lastIndex]])) { handleFlatJson(currentObj[subKeys[lastIndex]]) } } } return obj } export function getLocaleMessages<Messages = {}>( locale: Locale, options: GetLocaleMessagesOptions<Messages> ): { [K in keyof Messages]: Messages[K] } { const { messages, __i18n, messageResolver, flatJson } = options // prettier-ignore const ret = isPlainObject(messages) ? messages : isArray(__i18n) ? {} : { [locale]: {} } // merge locale messages of i18n custom block if (isArray(__i18n)) { __i18n.forEach(custom => { if ('locale' in custom && 'resource' in custom) { const { locale, resource } = custom if (locale) { ret[locale] = ret[locale] || {} deepCopy(resource, ret[locale]) } else { deepCopy(resource, ret) } } else { isString(custom) && deepCopy(JSON.parse(custom), ret) } }) } // handle messages for flat json if (messageResolver == null && flatJson) { for (const key in ret) { if (hasOwn(ret, key)) { handleFlatJson(ret[key]) } } } return ret as { [K in keyof Messages]: Messages[K] } } const isNotObjectOrIsArray = (val: unknown) => !isObject(val) || isArray(val) // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types export function deepCopy(src: any, des: any): void { // src and des should both be objects, and non of then can be a array if (isNotObjectOrIsArray(src) || isNotObjectOrIsArray(des)) { throw createI18nError(I18nErrorCodes.INVALID_VALUE) } for (const key in src) { if (hasOwn(src, key)) { if (isNotObjectOrIsArray(src[key]) || isNotObjectOrIsArray(des[key])) { // replace with src[key] when: // src[key] or des[key] is not a object, or // src[key] or des[key] is a array des[key] = src[key] } else { // src[key] and des[key] are both object, merge them deepCopy(src[key], des[key]) } } } } // eslint-disable-next-line @typescript-eslint/no-explicit-any export function getComponentOptions(instance: ComponentInternalInstance): any { return !__BRIDGE__ ? instance.type : instance.proxy!.$options } export function adjustI18nResources( global: Composer, options: ComposerOptions, componentOptions: any // eslint-disable-line @typescript-eslint/no-explicit-any ): void { let messages = isObject(options.messages) ? options.messages : {} if ('__i18nGlobal' in componentOptions) { messages = getLocaleMessages(global.locale.value as Locale, { messages, __i18n: componentOptions.__i18nGlobal }) } // merge locale messages const locales = Object.keys(messages) if (locales.length) { locales.forEach(locale => { global.mergeLocaleMessage(locale, messages[locale]) }) } if (!__LITE__) { // merge datetime formats if (isObject(options.datetimeFormats)) { const locales = Object.keys(options.datetimeFormats) if (locales.length) { locales.forEach(locale => { global.mergeDateTimeFormat(locale, options.datetimeFormats![locale]) }) } } // merge number formats if (isObject(options.numberFormats)) { const locales = Object.keys(options.numberFormats) if (locales.length) { locales.forEach(locale => { global.mergeNumberFormat(locale, options.numberFormats![locale]) }) } } } } export function createTextNode(key: string): any { return !__BRIDGE__ ? createVNode(Text, null, key, 0) : createVNodeVue2Compatible(key) } function createVNodeVue2Compatible(key: string): any { // shim Vue2 VNode interface // see the https://github.com/vuejs/vue/blob/dev/src/core/vdom/vnode.js const componentInstance = undefined return { tag: undefined, data: undefined, children: undefined, text: key, elm: undefined, ns: undefined, context: undefined, fnContext: undefined, fnOptions: undefined, fnScopeId: undefined, key: undefined, componentOptions: undefined, componentInstance, parent: undefined, raw: false, isStatic: false, isRootInsert: true, isComment: false, isCloned: false, isOnce: false, asyncFactory: undefined, asyncMeta: undefined, isAsyncPlaceholder: false, child: () => componentInstance } } /* eslint-enable @typescript-eslint/no-explicit-any */