Skip to content

fix: cannot resolve the ast messages which has json path #2157

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 5, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 123 additions & 0 deletions packages/core-base/src/ast.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import { NodeTypes } from '@intlify/message-compiler'
import { hasOwn, isObject } from '@intlify/shared'

import type {
LinkedModifierNode,
LinkedNode,
MessageNode,
Node,
PluralNode,
ResourceNode
} from '@intlify/message-compiler'
import type { MessageType } from './runtime'

export function isMessageAST(val: unknown): val is ResourceNode {
return (
isObject(val) &&
resolveType(val as Node) === 0 &&
(hasOwn(val, 'b') || hasOwn(val, 'body'))
)
}

const PROPS_BODY = ['b', 'body']

export function resolveBody(node: ResourceNode) {
return resolveProps<MessageNode | PluralNode>(node, PROPS_BODY)
}

const PROPS_CASES = ['c', 'cases']

export function resolveCases(node: PluralNode) {
return resolveProps<PluralNode['cases'], PluralNode['cases']>(
node,
PROPS_CASES,
[]
)
}

const PROPS_STATIC = ['s', 'static']

export function resolveStatic(node: MessageNode) {
return resolveProps(node, PROPS_STATIC)
}

const PROPS_ITEMS = ['i', 'items']

export function resolveItems(node: MessageNode) {
return resolveProps<MessageNode['items'], MessageNode['items']>(
node,
PROPS_ITEMS,
[]
)
}

const PROPS_TYPE = ['t', 'type']

export function resolveType(node: Node): ReturnType<typeof resolveProps> {
return resolveProps<NodeTypes>(node, PROPS_TYPE)
}

const PROPS_VALUE = ['v', 'value']

export function resolveValue<Message = string>(
node: { v?: MessageType<Message>; value?: MessageType<Message> },
type: NodeTypes
): MessageType<Message> {
const resolved = resolveProps<Message>(
node as Node,
PROPS_VALUE
) as MessageType<Message>
if (resolved != null) {
return resolved
} else {
throw createUnhandleNodeError(type)
}
}

const PROPS_MODIFIER = ['m', 'modifier']

export function resolveLinkedModifier(node: LinkedNode) {
return resolveProps<LinkedModifierNode>(node, PROPS_MODIFIER)
}

const PROPS_KEY = ['k', 'key']

export function resolveLinkedKey(node: LinkedNode) {
const resolved = resolveProps<LinkedNode['key']>(node, PROPS_KEY)
if (resolved) {
return resolved
} else {
throw createUnhandleNodeError(NodeTypes.Linked)
}
}

export function resolveProps<T = string, Default = undefined>(
node: Node,
props: string[],
defaultValue?: Default
): T | Default {
for (let i = 0; i < props.length; i++) {
const prop = props[i]
// eslint-disable-next-line @typescript-eslint/no-explicit-any
if (hasOwn(node, prop) && (node as any)[prop] != null) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return (node as any)[prop] as T
}
}
return defaultValue as Default
}

export const AST_NODE_PROPS_KEYS = [
...PROPS_BODY,
...PROPS_CASES,
...PROPS_STATIC,
...PROPS_ITEMS,
...PROPS_KEY,
...PROPS_MODIFIER,
...PROPS_VALUE,
...PROPS_TYPE
]

export function createUnhandleNodeError(type: NodeTypes) {
return new Error(`unhandled node type: ${type}`)
}
22 changes: 3 additions & 19 deletions packages/core-base/src/compilation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,14 @@ import {
mangle,
optimize
} from '@intlify/message-compiler'
import {
create,
format,
hasOwn,
isBoolean,
isObject,
isString,
warn
} from '@intlify/shared'
import { format as formatMessage, resolveType } from './format'
import { create, format, isBoolean, isString, warn } from '@intlify/shared'
import { isMessageAST } from './ast'
import { format as formatMessage } from './format'

import type {
CompileError,
CompileOptions,
CompilerResult,
Node,
ResourceNode
} from '@intlify/message-compiler'
import type { MessageCompilerContext } from './context'
Expand All @@ -41,14 +33,6 @@ export function clearCompileCache(): void {
compileCache = create()
}

export function isMessageAST(val: unknown): val is ResourceNode {
return (
isObject(val) &&
resolveType(val as Node) === 0 &&
(hasOwn(val, 'b') || hasOwn(val, 'body'))
)
}

function baseCompile(
message: string,
options: CompileOptions = {}
Expand Down
104 changes: 11 additions & 93 deletions packages/core-base/src/format.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,18 @@
import { NodeTypes } from '@intlify/message-compiler'
import { hasOwn, isNumber } from '@intlify/shared'
import {
createUnhandleNodeError,
resolveBody,
resolveCases,
resolveItems,
resolveLinkedKey,
resolveLinkedModifier,
resolveStatic,
resolveType,
resolveValue
} from './ast'

import type {
LinkedModifierNode,
LinkedNode,
ListNode,
MessageNode,
Expand Down Expand Up @@ -53,22 +63,6 @@ export function formatParts<Message = string>(
}
}

const PROPS_BODY = ['b', 'body']

function resolveBody(node: ResourceNode) {
return resolveProps<MessageNode | PluralNode>(node, PROPS_BODY)
}

const PROPS_CASES = ['c', 'cases']

function resolveCases(node: PluralNode) {
return resolveProps<PluralNode['cases'], PluralNode['cases']>(
node,
PROPS_CASES,
[]
)
}

export function formatMessageParts<Message = string>(
ctx: MessageContext<Message>,
node: MessageNode
Expand All @@ -87,22 +81,6 @@ export function formatMessageParts<Message = string>(
}
}

const PROPS_STATIC = ['s', 'static']

function resolveStatic(node: MessageNode) {
return resolveProps(node, PROPS_STATIC)
}

const PROPS_ITEMS = ['i', 'items']

function resolveItems(node: MessageNode) {
return resolveProps<MessageNode['items'], MessageNode['items']>(
node,
PROPS_ITEMS,
[]
)
}

type NodeValue<Message> = {
v?: MessageType<Message>
value?: MessageType<Message>
Expand Down Expand Up @@ -160,63 +138,3 @@ export function formatMessagePart<Message = string>(
throw new Error(`unhandled node on format message part: ${type}`)
}
}

const PROPS_TYPE = ['t', 'type']

export function resolveType(node: Node): ReturnType<typeof resolveProps> {
return resolveProps<NodeTypes>(node, PROPS_TYPE)
}

const PROPS_VALUE = ['v', 'value']

function resolveValue<Message = string>(
node: { v?: MessageType<Message>; value?: MessageType<Message> },
type: NodeTypes
): MessageType<Message> {
const resolved = resolveProps<Message>(
node as Node,
PROPS_VALUE
) as MessageType<Message>
if (resolved != null) {
return resolved
} else {
throw createUnhandleNodeError(type)
}
}

const PROPS_MODIFIER = ['m', 'modifier']

function resolveLinkedModifier(node: LinkedNode) {
return resolveProps<LinkedModifierNode>(node, PROPS_MODIFIER)
}

const PROPS_KEY = ['k', 'key']

function resolveLinkedKey(node: LinkedNode) {
const resolved = resolveProps<LinkedNode['key']>(node, PROPS_KEY)
if (resolved) {
return resolved
} else {
throw createUnhandleNodeError(NodeTypes.Linked)
}
}

function resolveProps<T = string, Default = undefined>(
node: Node,
props: string[],
defaultValue?: Default
): T | Default {
for (let i = 0; i < props.length; i++) {
const prop = props[i]
// eslint-disable-next-line @typescript-eslint/no-explicit-any
if (hasOwn(node, prop) && (node as any)[prop] != null) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return (node as any)[prop] as T
}
}
return defaultValue as Default
}

function createUnhandleNodeError(type: NodeTypes) {
return new Error(`unhandled node type: ${type}`)
}
1 change: 1 addition & 0 deletions packages/core-base/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export type {
CompileErrorCodes,
ResourceNode
} from '@intlify/message-compiler'
export { AST_NODE_PROPS_KEYS, isMessageAST } from './ast'
export * from './compilation'
export * from './context'
export * from './datetime'
Expand Down
12 changes: 11 additions & 1 deletion packages/core-base/src/resolver.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { isFunction, isObject } from '@intlify/shared'
import { AST_NODE_PROPS_KEYS, isMessageAST } from './ast'

/** @VueI18nGeneral */
export type Path = string
Expand Down Expand Up @@ -344,7 +345,16 @@ export function resolveValue(obj: unknown, path: Path): PathValue {
let last = obj
let i = 0
while (i < len) {
const val = last[hit[i]]
const key = hit[i]
/**
* NOTE:
* if `key` is intlify message format AST node key and `last` is intlify message format AST, skip it.
* because the AST node is not a key-value structure.
*/
if (AST_NODE_PROPS_KEYS.includes(key) && isMessageAST(last)) {
return null
}
const val = last[key]
if (val === undefined) {
return null
}
Expand Down
2 changes: 1 addition & 1 deletion packages/core-base/src/translate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {
measure,
warn
} from '@intlify/shared'
import { isMessageAST } from './compilation'
import { isMessageAST } from './ast'
import {
getAdditionalMeta,
handleMissing,
Expand Down
3 changes: 2 additions & 1 deletion packages/core-base/test/compilation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ vi.mock('@intlify/shared', async () => {
})

import { baseCompile } from '@intlify/message-compiler'
import { clearCompileCache, compile, isMessageAST } from '../src/compilation'
import { isMessageAST } from '../src/ast'
import { clearCompileCache, compile } from '../src/compilation'
import { createMessageContext as context } from '../src/runtime'

const DEFAULT_CONTEXT = { locale: 'en', key: 'key' }
Expand Down
Loading