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
}