import { assign, create, escapeHtml, generateCodeFrame, generateFormatCacheKey, inBrowser, isArray, isBoolean, isEmptyObject, isFunction, isNumber, isObject, isPlainObject, isString, mark, measure, sanitizeTranslatedHtml, toDevtoolsGroupId, warn } from '@intlify/shared' import { isMessageAST } from './ast' import { CoreContext, getAdditionalMeta, handleMissing, isAlmostSameLocale, isImplicitFallback, isTranslateFallbackWarn, NOT_REOSLVED } from './context' import { translateDevTools } from './devtools' import { CoreErrorCodes, createCoreError } from './errors' import { getLocale } from './fallbacker' import { createMessageContext } from './runtime' import { CoreWarnCodes, getWarnMessage } from './warnings' import type { AdditionalPayloads } from '@intlify/devtools-types' import type { CompileError, ResourceNode } from '@intlify/message-compiler' import type { CoreInternalContext, DefineCoreLocaleMessage, LocaleMessages, LocaleMessageValue, MessageCompilerContext } from './context' import type { LocaleOptions } from './fallbacker' import type { Path, PathValue } from './resolver' import type { FallbackLocale, Locale, MessageContext, MessageContextOptions, MessageFunction, MessageFunctionInternal, MessageFunctionReturn, NamedValue } from './runtime' import type { IsEmptyObject, IsNever, PickupKeys, PickupPaths, RemovedIndexResources } from './types' const NOOP_MESSAGE_FUNCTION = () => '' export const isMessageFunction = (val: unknown): val is MessageFunction => isFunction(val) /** * # translate * * ## usages: * // for example, locale messages key * { 'foo.bar': 'hi {0} !' or 'hi {name} !' } * * // no argument, context & path only * translate(context, 'foo.bar') * * // list argument * translate(context, 'foo.bar', ['kazupon']) * * // named argument * translate(context, 'foo.bar', { name: 'kazupon' }) * * // plural choice number * translate(context, 'foo.bar', 2) * * // plural choice number with name argument * translate(context, 'foo.bar', { name: 'kazupon' }, 2) * * // default message argument * translate(context, 'foo.bar', 'this is default message') * * // default message with named argument * translate(context, 'foo.bar', { name: 'kazupon' }, 'Hello {name} !') * * // use key as default message * translate(context, 'hi {0} !', ['kazupon'], { default: true }) * * // locale option, override context.locale * translate(context, 'foo.bar', { name: 'kazupon' }, { locale: 'ja' }) * * // suppress localize miss warning option, override context.missingWarn * translate(context, 'foo.bar', { name: 'kazupon' }, { missingWarn: false }) * * // suppress localize fallback warning option, override context.fallbackWarn * translate(context, 'foo.bar', { name: 'kazupon' }, { fallbackWarn: false }) * * // escape parameter option, override context.escapeParameter * translate(context, 'foo.bar', { name: 'kazupon' }, { escapeParameter: true }) */ /** * Translate Options * * @remarks * Options for Translation API * * @VueI18nGeneral */ export interface TranslateOptions extends LocaleOptions { /** * @remarks * List interpolation */ list?: unknown[] /** * @remarks * Named interpolation */ named?: NamedValue /** * @remarks * Plulralzation choice number */ plural?: number /** * @remarks * Default message when is occurred translation missing */ default?: string | boolean /** * @remarks * Whether suppress warnings outputted when localization fails */ missingWarn?: boolean /** * @remarks * Whether do template interpolation on translation keys when your language lacks a translation for a key */ fallbackWarn?: boolean /** * @remarks * Whether to escape parameters for list or named interpolation values. * When enabled, this option: * - Escapes HTML special characters (`<`, `>`, `"`, `'`, `&`, `/`, `=`) in interpolation parameters * - Sanitizes the final translated HTML to prevent XSS attacks by: * - Escaping dangerous characters in HTML attribute values * - Neutralizing event handler attributes (onclick, onerror, etc.) * - Disabling javascript: URLs in href, src, action, formaction, and style attributes * * @defaultValue false * @see [HTML Message - Using the escapeParameter option](https://vue-i18n.intlify.dev/guide/essentials/syntax.html#using-the-escapeparameter-option) */ escapeParameter?: boolean /** * @remarks * Whether the message has been resolved */ resolvedMessage?: boolean } /** * TODO: * this type should be used (refactored) at `translate` type definition * (Unfortunately, using this type will result in key completion failure due to type mismatch...) */ /* type ResolveTranslateResourceKeys< Context extends CoreContext, DefinedLocaleMessage extends RemovedIndexResources = RemovedIndexResources, CoreMessages = IsEmptyObject extends false ? PickupPaths<{ [K in keyof DefinedLocaleMessage]: DefinedLocaleMessage[K] }> : never, ContextMessages = IsEmptyObject extends false ? PickupKeys : never, Result extends | CoreMessages | ContextMessages = IsNever extends false ? IsNever extends false ? CoreMessages | ContextMessages : CoreMessages : IsNever extends false ? ContextMessages : never > = Result */ /** * `translate` function overloads */ export function translate< Context extends CoreContext, Key extends string = string, DefinedLocaleMessage extends RemovedIndexResources = RemovedIndexResources, CoreMessages = IsEmptyObject extends false ? PickupPaths<{ [K in keyof DefinedLocaleMessage]: DefinedLocaleMessage[K] }> : never, ContextMessages = IsEmptyObject extends false ? PickupKeys : never, ResourceKeys extends | CoreMessages | ContextMessages = IsNever extends false ? IsNever extends false ? CoreMessages | ContextMessages : CoreMessages : IsNever extends false ? ContextMessages : never, Message = string >( context: Context, key: Key | ResourceKeys | number | MessageFunction, plural: number ): MessageFunctionReturn | number export function translate< Context extends CoreContext, Key extends string = string, DefinedLocaleMessage extends RemovedIndexResources = RemovedIndexResources, CoreMessages = IsEmptyObject extends false ? PickupPaths<{ [K in keyof DefinedLocaleMessage]: DefinedLocaleMessage[K] }> : never, ContextMessages = IsEmptyObject extends false ? PickupKeys : never, ResourceKeys extends | CoreMessages | ContextMessages = IsNever extends false ? IsNever extends false ? CoreMessages | ContextMessages : CoreMessages : IsNever extends false ? ContextMessages : never, Message = string >( context: Context, key: Key | ResourceKeys | number | MessageFunction ): MessageFunctionReturn | number export function translate< Context extends CoreContext, Key extends string = string, DefinedLocaleMessage extends RemovedIndexResources = RemovedIndexResources, CoreMessages = IsEmptyObject extends false ? PickupPaths<{ [K in keyof DefinedLocaleMessage]: DefinedLocaleMessage[K] }> : never, ContextMessages = IsEmptyObject extends false ? PickupKeys : never, ResourceKeys extends | CoreMessages | ContextMessages = IsNever extends false ? IsNever extends false ? CoreMessages | ContextMessages : CoreMessages : IsNever extends false ? ContextMessages : never, Message = string >( context: Context, key: Key | ResourceKeys | number | MessageFunction, plural: number ): MessageFunctionReturn | number export function translate< Context extends CoreContext, Key extends string = string, DefinedLocaleMessage extends RemovedIndexResources = RemovedIndexResources, CoreMessages = IsEmptyObject extends false ? PickupPaths<{ [K in keyof DefinedLocaleMessage]: DefinedLocaleMessage[K] }> : never, ContextMessages = IsEmptyObject extends false ? PickupKeys : never, ResourceKeys extends | CoreMessages | ContextMessages = IsNever extends false ? IsNever extends false ? CoreMessages | ContextMessages : CoreMessages : IsNever extends false ? ContextMessages : never, Message = string >( context: Context, key: Key | ResourceKeys | number | MessageFunction, plural: number, options: TranslateOptions ): MessageFunctionReturn | number export function translate< Context extends CoreContext, Key extends string = string, DefinedLocaleMessage extends RemovedIndexResources = RemovedIndexResources, CoreMessages = IsEmptyObject extends false ? PickupPaths<{ [K in keyof DefinedLocaleMessage]: DefinedLocaleMessage[K] }> : never, ContextMessages = IsEmptyObject extends false ? PickupKeys : never, ResourceKeys extends | CoreMessages | ContextMessages = IsNever extends false ? IsNever extends false ? CoreMessages | ContextMessages : CoreMessages : IsNever extends false ? ContextMessages : never, Message = string >( context: Context, key: Key | ResourceKeys | number | MessageFunction, defaultMsg: string ): MessageFunctionReturn | number export function translate< Context extends CoreContext, Key extends string = string, DefinedLocaleMessage extends RemovedIndexResources = RemovedIndexResources, CoreMessages = IsEmptyObject extends false ? PickupPaths<{ [K in keyof DefinedLocaleMessage]: DefinedLocaleMessage[K] }> : never, ContextMessages = IsEmptyObject extends false ? PickupKeys : never, ResourceKeys extends | CoreMessages | ContextMessages = IsNever extends false ? IsNever extends false ? CoreMessages | ContextMessages : CoreMessages : IsNever extends false ? ContextMessages : never, Message = string >( context: Context, key: Key | ResourceKeys | number | MessageFunction, defaultMsg: string, options: TranslateOptions ): MessageFunctionReturn | number export function translate< Context extends CoreContext, Key extends string = string, DefinedLocaleMessage extends RemovedIndexResources = RemovedIndexResources, CoreMessages = IsEmptyObject extends false ? PickupPaths<{ [K in keyof DefinedLocaleMessage]: DefinedLocaleMessage[K] }> : never, ContextMessages = IsEmptyObject extends false ? PickupKeys : never, ResourceKeys extends | CoreMessages | ContextMessages = IsNever extends false ? IsNever extends false ? CoreMessages | ContextMessages : CoreMessages : IsNever extends false ? ContextMessages : never, Message = string >( context: Context, key: Key | ResourceKeys | number | MessageFunction, list: unknown[] ): MessageFunctionReturn | number export function translate< Context extends CoreContext, Key extends string = string, DefinedLocaleMessage extends RemovedIndexResources = RemovedIndexResources, CoreMessages = IsEmptyObject extends false ? PickupPaths<{ [K in keyof DefinedLocaleMessage]: DefinedLocaleMessage[K] }> : never, ContextMessages = IsEmptyObject extends false ? PickupKeys : never, ResourceKeys extends | CoreMessages | ContextMessages = IsNever extends false ? IsNever extends false ? CoreMessages | ContextMessages : CoreMessages : IsNever extends false ? ContextMessages : never, Message = string >( context: Context, key: Key | ResourceKeys | number | MessageFunction, list: unknown[], plural: number ): MessageFunctionReturn | number export function translate< Context extends CoreContext, Key extends string = string, DefinedLocaleMessage extends RemovedIndexResources = RemovedIndexResources, CoreMessages = IsEmptyObject extends false ? PickupPaths<{ [K in keyof DefinedLocaleMessage]: DefinedLocaleMessage[K] }> : never, ContextMessages = IsEmptyObject extends false ? PickupKeys : never, ResourceKeys extends | CoreMessages | ContextMessages = IsNever extends false ? IsNever extends false ? CoreMessages | ContextMessages : CoreMessages : IsNever extends false ? ContextMessages : never, Message = string >( context: Context, key: Key | ResourceKeys | number | MessageFunction, list: unknown[], defaultMsg: string ): MessageFunctionReturn | number export function translate< Context extends CoreContext, Key extends string = string, DefinedLocaleMessage extends RemovedIndexResources = RemovedIndexResources, CoreMessages = IsEmptyObject extends false ? PickupPaths<{ [K in keyof DefinedLocaleMessage]: DefinedLocaleMessage[K] }> : never, ContextMessages = IsEmptyObject extends false ? PickupKeys : never, ResourceKeys extends | CoreMessages | ContextMessages = IsNever extends false ? IsNever extends false ? CoreMessages | ContextMessages : CoreMessages : IsNever extends false ? ContextMessages : never, Message = string >( context: Context, key: Key | ResourceKeys | number | MessageFunction, list: unknown[], options: TranslateOptions ): MessageFunctionReturn | number export function translate< Context extends CoreContext, Key extends string = string, DefinedLocaleMessage extends RemovedIndexResources = RemovedIndexResources, CoreMessages = IsEmptyObject extends false ? PickupPaths<{ [K in keyof DefinedLocaleMessage]: DefinedLocaleMessage[K] }> : never, ContextMessages = IsEmptyObject extends false ? PickupKeys : never, ResourceKeys extends | CoreMessages | ContextMessages = IsNever extends false ? IsNever extends false ? CoreMessages | ContextMessages : CoreMessages : IsNever extends false ? ContextMessages : never, Message = string >( context: Context, key: Key | ResourceKeys | number | MessageFunction, named: NamedValue ): MessageFunctionReturn | number export function translate< Context extends CoreContext, Key extends string = string, DefinedLocaleMessage extends RemovedIndexResources = RemovedIndexResources, CoreMessages = IsEmptyObject extends false ? PickupPaths<{ [K in keyof DefinedLocaleMessage]: DefinedLocaleMessage[K] }> : never, ContextMessages = IsEmptyObject extends false ? PickupKeys : never, ResourceKeys extends | CoreMessages | ContextMessages = IsNever extends false ? IsNever extends false ? CoreMessages | ContextMessages : CoreMessages : IsNever extends false ? ContextMessages : never, Message = string >( context: Context, key: Key | ResourceKeys | number | MessageFunction, named: NamedValue, plural: number ): MessageFunctionReturn | number export function translate< Context extends CoreContext, Key extends string = string, DefinedLocaleMessage extends RemovedIndexResources = RemovedIndexResources, CoreMessages = IsEmptyObject extends false ? PickupPaths<{ [K in keyof DefinedLocaleMessage]: DefinedLocaleMessage[K] }> : never, ContextMessages = IsEmptyObject extends false ? PickupKeys : never, ResourceKeys extends | CoreMessages | ContextMessages = IsNever extends false ? IsNever extends false ? CoreMessages | ContextMessages : CoreMessages : IsNever extends false ? ContextMessages : never, Message = string >( context: Context, key: Key | ResourceKeys | number | MessageFunction, named: NamedValue, defaultMsg: string ): MessageFunctionReturn | number export function translate< Context extends CoreContext, Key extends string = string, DefinedLocaleMessage extends RemovedIndexResources = RemovedIndexResources, CoreMessages = IsEmptyObject extends false ? PickupPaths<{ [K in keyof DefinedLocaleMessage]: DefinedLocaleMessage[K] }> : never, ContextMessages = IsEmptyObject extends false ? PickupKeys : never, ResourceKeys extends | CoreMessages | ContextMessages = IsNever extends false ? IsNever extends false ? CoreMessages | ContextMessages : CoreMessages : IsNever extends false ? ContextMessages : never, Message = string >( context: Context, key: Key | ResourceKeys | number | MessageFunction, named: NamedValue, options: TranslateOptions ): MessageFunctionReturn | number // implementation of `translate` function export function translate< Context extends CoreContext, Message = string >( context: Context, ...args: unknown[] ): MessageFunctionReturn | number { const { fallbackFormat, postTranslation, unresolving, messageCompiler, fallbackLocale, messages } = context const [key, options] = parseTranslateArgs(...args) const missingWarn = isBoolean(options.missingWarn) ? options.missingWarn : context.missingWarn const fallbackWarn = isBoolean(options.fallbackWarn) ? options.fallbackWarn : context.fallbackWarn const escapeParameter = isBoolean(options.escapeParameter) ? options.escapeParameter : context.escapeParameter const resolvedMessage = !!options.resolvedMessage // prettier-ignore const defaultMsgOrKey = isString(options.default) || isBoolean(options.default) // default by function option ? !isBoolean(options.default) ? options.default : (!messageCompiler ? () => key : key) : fallbackFormat // default by `fallbackFormat` option ? (!messageCompiler ? () => key : key) : null const enableDefaultMsg = fallbackFormat || (defaultMsgOrKey != null && (isString(defaultMsgOrKey) || isFunction(defaultMsgOrKey))) const locale = getLocale(context, options) // escape params escapeParameter && escapeParams(options) // resolve message format // eslint-disable-next-line prefer-const let [formatScope, targetLocale, message]: [ PathValue | MessageFunction | ResourceNode, Locale | undefined, LocaleMessageValue ] = !resolvedMessage ? resolveMessageFormat( context, key as string, locale, fallbackLocale as FallbackLocale, fallbackWarn, missingWarn ) : [ key, locale, (messages as unknown as LocaleMessages)[locale] || create() ] // NOTE: // Fix to work around `ssrTransfrom` bug in Vite. // https://github.com/vitejs/vite/issues/4306 // To get around this, use temporary variables. // https://github.com/nuxt/framework/issues/1461#issuecomment-954606243 let format = formatScope // if you use default message, set it as message format! let cacheBaseKey = key if ( !resolvedMessage && !( isString(format) || isMessageAST(format) || isMessageFunction(format) ) ) { if (enableDefaultMsg) { format = defaultMsgOrKey cacheBaseKey = format as Path | MessageFunction } } // checking message format and target locale if ( !resolvedMessage && (!( isString(format) || isMessageAST(format) || isMessageFunction(format) ) || !isString(targetLocale)) ) { return unresolving ? NOT_REOSLVED : (key as MessageFunctionReturn) } // TODO: refactor if (__DEV__ && isString(format) && context.messageCompiler == null) { warn( `The message format compilation is not supported in this build. ` + `Because message compiler isn't included. ` + `You need to pre-compilation all message format. ` + `So translate function return '${key}'.` ) return key as MessageFunctionReturn } // setup compile error detecting let occurred = false const onError = () => { occurred = true } // compile message format const msg = !isMessageFunction(format) ? compileMessageFormat( context, key as string, targetLocale!, format, cacheBaseKey as string, onError ) : format // if occurred compile error, return the message format if (occurred) { return format as MessageFunctionReturn } // evaluate message with context const ctxOptions = getMessageContextOptions( context, targetLocale!, message, options ) const msgContext = createMessageContext(ctxOptions) const messaged = evaluateMessage( context, msg as MessageFunction, msgContext ) // if use post translation option, proceed it with handler let ret = postTranslation ? postTranslation(messaged, key as string) : messaged // apply HTML sanitization for security if (escapeParameter && isString(ret)) { ret = sanitizeTranslatedHtml(ret) as MessageFunctionReturn } // NOTE: experimental !! if (__DEV__ || __FEATURE_PROD_INTLIFY_DEVTOOLS__) { // prettier-ignore const payloads = { timestamp: Date.now(), key: isString(key) ? key : isMessageFunction(format) ? (format as MessageFunctionInternal).key! : '', locale: targetLocale || (isMessageFunction(format) ? (format as MessageFunctionInternal).locale! : ''), format: isString(format) ? format : isMessageFunction(format) ? (format as MessageFunctionInternal).source! : '', message: ret as string } ;(payloads as AdditionalPayloads).meta = assign( {}, (context as unknown as CoreInternalContext).__meta, getAdditionalMeta() || {} ) translateDevTools(payloads) } return ret } function escapeParams(options: TranslateOptions) { if (isArray(options.list)) { options.list = options.list.map(item => isString(item) ? escapeHtml(item) : item ) } else if (isObject(options.named)) { Object.keys(options.named).forEach(key => { if (isString(options.named![key])) { options.named![key] = escapeHtml(options.named![key] as string) } }) } } function resolveMessageFormat( context: CoreContext, key: string, locale: Locale, fallbackLocale: FallbackLocale, fallbackWarn: boolean | RegExp, missingWarn: boolean | RegExp ): [PathValue, Locale | undefined, LocaleMessageValue] { const { messages, onWarn, messageResolver: resolveValue, localeFallbacker } = context const locales = localeFallbacker(context as any, fallbackLocale, locale) // eslint-disable-line @typescript-eslint/no-explicit-any let message: LocaleMessageValue = create() let targetLocale: Locale | undefined let format: PathValue = null let from: Locale = locale let to: Locale | null = null const type = 'translate' for (let i = 0; i < locales.length; i++) { targetLocale = to = locales[i] if ( __DEV__ && locale !== targetLocale && !isAlmostSameLocale(locale, targetLocale) && isTranslateFallbackWarn(fallbackWarn, key) ) { onWarn( getWarnMessage(CoreWarnCodes.FALLBACK_TO_TRANSLATE, { key, target: targetLocale }) ) } const emitter = __DEV__ ? (context as unknown as CoreInternalContext).__v_emitter : undefined // for vue-devtools timeline event if (__DEV__ && locale !== targetLocale) { if (emitter) { emitter.emit('fallback', { type, key, from, to, groupId: toDevtoolsGroupId(type, key) }) } } message = (messages as unknown as LocaleMessages)[targetLocale] || create() // for vue-devtools timeline event let start: number | null = null let startTag: string | undefined let endTag: string | undefined if (__DEV__ && inBrowser && emitter) { start = window.performance.now() startTag = 'intlify-message-resolve-start' endTag = 'intlify-message-resolve-end' mark && mark(startTag) } if ((format = resolveValue(message, key)) === null) { // if null, resolve with object key path format = (message as any)[key] // eslint-disable-line @typescript-eslint/no-explicit-any } // for vue-devtools timeline event if (__DEV__ && inBrowser && emitter) { const end = window.performance.now() if (emitter && start && format) { emitter.emit('message-resolve', { type: 'message-resolve', key, message: format, time: end - start, groupId: toDevtoolsGroupId(type, key) }) } if (startTag && endTag && mark && measure) { mark(endTag) measure('intlify message resolve', startTag, endTag) } } if (isString(format) || isMessageAST(format) || isMessageFunction(format)) { break } if (!isImplicitFallback(targetLocale, locales)) { const missingRet = handleMissing( context as any, // eslint-disable-line @typescript-eslint/no-explicit-any key, targetLocale, missingWarn, type ) if (missingRet !== key) { format = missingRet as PathValue } } from = to } return [format, targetLocale, message] } function compileMessageFormat( context: CoreContext, key: string, targetLocale: string, format: PathValue | ResourceNode | MessageFunction, cacheBaseKey: string, onError: () => void ): MessageFunctionInternal { const { messageCompiler, warnHtmlMessage } = context if (isMessageFunction(format)) { const msg = format as MessageFunctionInternal msg.locale = msg.locale || targetLocale msg.key = msg.key || key return msg } if (messageCompiler == null) { const msg = (() => format) as MessageFunctionInternal msg.locale = targetLocale msg.key = key return msg } // for vue-devtools timeline event const emitter = __DEV__ ? (context as unknown as CoreInternalContext).__v_emitter : undefined let start: number | null = null let startTag: string | undefined let endTag: string | undefined if (__DEV__ && inBrowser && emitter) { start = window.performance.now() startTag = 'intlify-message-compilation-start' endTag = 'intlify-message-compilation-end' mark && mark(startTag) } const msg = messageCompiler( format as string | ResourceNode, getCompileContext( context, targetLocale, cacheBaseKey, format as string | ResourceNode, warnHtmlMessage, onError ) ) as MessageFunctionInternal // for vue-devtools timeline event if (__DEV__ && inBrowser && emitter) { const end = window.performance.now() if (emitter && start) { emitter.emit('message-compilation', { type: 'message-compilation', message: format as string | ResourceNode | MessageFunction, time: end - start, groupId: toDevtoolsGroupId('translate', key) }) } if (startTag && endTag && mark && measure) { mark(endTag) measure('intlify message compilation', startTag, endTag) } } msg.locale = targetLocale msg.key = key msg.source = format as string return msg } function evaluateMessage( context: CoreContext, msg: MessageFunction, msgCtx: MessageContext ): MessageFunctionReturn { // for vue-devtools timeline event const emitter = __DEV__ ? (context as unknown as CoreInternalContext).__v_emitter : undefined let start: number | null = null let startTag: string | undefined let endTag: string | undefined if (__DEV__ && inBrowser && emitter) { start = window.performance.now() startTag = 'intlify-message-evaluation-start' endTag = 'intlify-message-evaluation-end' mark && mark(startTag) } const messaged = msg(msgCtx) // for vue-devtools timeline event if (__DEV__ && inBrowser && emitter) { const end = window.performance.now() if (emitter && start) { emitter.emit('message-evaluation', { type: 'message-evaluation', value: messaged, time: end - start, groupId: toDevtoolsGroupId( 'translate', (msg as MessageFunctionInternal).key ) }) } if (startTag && endTag && mark && measure) { mark(endTag) measure('intlify message evaluation', startTag, endTag) } } return messaged } /** @internal */ export function parseTranslateArgs( ...args: unknown[] ): [Path | MessageFunction | ResourceNode, TranslateOptions] { const [arg1, arg2, arg3] = args const options = create() as TranslateOptions if ( !isString(arg1) && !isNumber(arg1) && !isMessageFunction(arg1) && !isMessageAST(arg1) ) { throw createCoreError(CoreErrorCodes.INVALID_ARGUMENT) } // prettier-ignore const key = isNumber(arg1) ? String(arg1) : isMessageFunction(arg1) ? (arg1 as MessageFunction) : arg1 if (isNumber(arg2)) { options.plural = arg2 } else if (isString(arg2)) { options.default = arg2 } else if (isPlainObject(arg2) && !isEmptyObject(arg2)) { options.named = arg2 as NamedValue } else if (isArray(arg2)) { options.list = arg2 } if (isNumber(arg3)) { options.plural = arg3 } else if (isString(arg3)) { options.default = arg3 } else if (isPlainObject(arg3)) { assign(options, arg3) } return [key, options] } function getCompileContext( context: CoreContext, locale: Locale, key: string, source: string | ResourceNode, warnHtmlMessage: boolean, onError: (err: CompileError) => void ): MessageCompilerContext { return { locale, key, warnHtmlMessage, onError: (err: CompileError): void => { onError && onError(err) if (__DEV__) { const _source = getSourceForCodeFrame(source) const codeFrame = err.location && _source && generateCodeFrame( _source, err.location.start.offset, err.location.end.offset ) const emitter = (context as unknown as CoreInternalContext).__v_emitter if (emitter && _source) { emitter.emit('compile-error', { message: _source, error: err.message, start: err.location && err.location.start.offset, end: err.location && err.location.end.offset, groupId: toDevtoolsGroupId('translate', key) }) } const message = `Message compilation error: ${err.message}` throw new SyntaxError(codeFrame ? `${message}\n${codeFrame}` : message) } throw err }, onCacheKey: (source: string): string => generateFormatCacheKey(locale, key, source) } } function getSourceForCodeFrame( source: string | ResourceNode ): string | undefined { if (isString(source)) { return source } else { if (source.loc && source.loc.source) { return source.loc.source } } } function getMessageContextOptions( context: CoreContext, locale: Locale, message: LocaleMessageValue, options: TranslateOptions ): MessageContextOptions { const { modifiers, pluralRules, messageResolver: resolveValue, fallbackLocale, fallbackWarn, missingWarn, fallbackContext } = context const resolveMessage = ( key: string, useLinked: boolean ): MessageFunction => { let val = resolveValue(message, key) // fallback if (val == null && (fallbackContext || useLinked)) { const [format, , message] = resolveMessageFormat( fallbackContext || context, // NOTE: if has fallbackContext, fallback to root, else if use linked, fallback to local context key, locale, fallbackLocale as FallbackLocale, fallbackWarn, missingWarn ) val = format ?? resolveValue(message, key) } if (isString(val) || isMessageAST(val)) { let occurred = false const onError = () => { occurred = true } const msg = compileMessageFormat( context, key, locale, val, key, onError ) as unknown as MessageFunction return !occurred ? msg : (NOOP_MESSAGE_FUNCTION as MessageFunction) } else if (isMessageFunction(val)) { return val } else { // TODO: should be implemented warning message return NOOP_MESSAGE_FUNCTION as MessageFunction } } const ctxOptions: MessageContextOptions = { locale, modifiers, pluralRules, messages: resolveMessage } if (context.processor) { ctxOptions.processor = context.processor } if (options.list) { ctxOptions.list = options.list } if (options.named) { ctxOptions.named = options.named } if (isNumber(options.plural)) { ctxOptions.pluralIndex = options.plural } return ctxOptions }