Skip to content

Commit

Permalink
refactor(macro): use message descriptor for Trans (#1909)
Browse files Browse the repository at this point in the history
* refactor(macro): use message descriptor for Trans

* update snapshots

* introduce MsgDescriptorPropKey enum
  • Loading branch information
timofei-iatsenko authored May 21, 2024
1 parent 592889a commit 4821317
Show file tree
Hide file tree
Showing 16 changed files with 901 additions and 553 deletions.
18 changes: 15 additions & 3 deletions packages/babel-plugin-extract-messages/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import type * as BabelTypesNamespace from "@babel/types"
import {
Expression,
Identifier,
JSXAttribute,
Node,
ObjectExpression,
ObjectProperty,
Expand Down Expand Up @@ -192,9 +191,20 @@ export default function ({ types: t }: { types: BabelTypes }): PluginObj {
const { node } = path
if (!isTransComponent(path)) return

const attrs = (node.openingElement.attributes as JSXAttribute[]) || []
const attrs = node.openingElement.attributes || []

if (
t.isJSXSpreadAttribute(attrs[0]) &&
hasI18nComment(attrs[0].argument)
) {
return
}

const props = attrs.reduce<Record<string, unknown>>((acc, item) => {
if (t.isJSXSpreadAttribute(item)) {
return acc
}

const key = item.name.name
if (
key === "id" ||
Expand All @@ -216,7 +226,9 @@ export default function ({ types: t }: { types: BabelTypes }): PluginObj {

if (!props.id) {
// <Trans id={message} /> is valid, don't raise warning
const idProp = attrs.filter((item) => item.name.name === "id")[0]
const idProp = attrs.filter(
(item) => t.isJSXAttribute(item) && item.name.name === "id"
)[0]
if (idProp === undefined || t.isLiteral(props.id as any)) {
console.warn(
path.buildCodeFrameError("Missing message ID, skipping.").message
Expand Down
13 changes: 9 additions & 4 deletions packages/babel-plugin-lingui-macro/src/constants.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
export const ID = "id"
export const MESSAGE = "message"
export const COMMENT = "comment"
export const EXTRACT_MARK = "i18n"
export const CONTEXT = "context"
export const MACRO_LEGACY_PACKAGE = "@lingui/macro"
export const MACRO_CORE_PACKAGE = "@lingui/core/macro"
export const MACRO_REACT_PACKAGE = "@lingui/react/macro"

export enum MsgDescriptorPropKey {
id = "id",
message = "message",
comment = "comment",
values = "values",
components = "components",
context = "context",
}

export enum JsMacroName {
t = "t",
plural = "plural",
Expand Down
191 changes: 52 additions & 139 deletions packages/babel-plugin-lingui-macro/src/macroJs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,39 +6,21 @@ import {
Node,
ObjectExpression,
ObjectProperty,
SourceLocation,
StringLiteral,
TemplateLiteral,
} from "@babel/types"
import { NodePath } from "@babel/traverse"

import ICUMessageFormat, {
ArgToken,
ParsedResult,
TextToken,
Token,
Tokens,
} from "./icu"
import { ArgToken, TextToken, Token, Tokens } from "./icu"
import { makeCounter } from "./utils"
import {
COMMENT,
CONTEXT,
EXTRACT_MARK,
ID,
MESSAGE,
MACRO_CORE_PACKAGE,
JsMacroName,
MACRO_REACT_PACKAGE,
MACRO_LEGACY_PACKAGE,
MsgDescriptorPropKey,
} from "./constants"
import { generateMessageId } from "@lingui/message-utils/generateMessageId"

function buildICUFromTokens(tokens: Tokens) {
const messageFormat = new ICUMessageFormat()
const { message, values } = messageFormat.fromTokens(tokens)

return { message, values }
}
import { createMessageDescriptorFromTokens } from "./messageDescriptorUtils"

export type MacroJsOpts = {
i18nImportName: string
Expand Down Expand Up @@ -76,7 +58,11 @@ export class MacroJs {
linguiInstance?: babelTypes.Expression
) => {
return this.createI18nCall(
this.createMessageDescriptorFromTokens(tokens, path.node.loc),
createMessageDescriptorFromTokens(
tokens,
path.node.loc,
this.stripNonEssentialProps
),
linguiInstance
)
}
Expand All @@ -102,7 +88,11 @@ export class MacroJs {
this.isDefineMessage(path.get("tag"))
) {
const tokens = this.tokenizeTemplateLiteral(path.get("quasi"))
return this.createMessageDescriptorFromTokens(tokens, path.node.loc)
return createMessageDescriptorFromTokens(
tokens,
path.node.loc,
this.stripNonEssentialProps
)
}

if (path.isTaggedTemplateExpression()) {
Expand Down Expand Up @@ -255,9 +245,10 @@ export class MacroJs {
if (currentPath.isTaggedTemplateExpression()) {
const tokens = this.tokenizeTemplateLiteral(currentPath)

const descriptor = this.createMessageDescriptorFromTokens(
const descriptor = createMessageDescriptorFromTokens(
tokens,
currentPath.node.loc
currentPath.node.loc,
this.stripNonEssentialProps
)

const callExpr = this.types.callExpression(
Expand Down Expand Up @@ -319,81 +310,45 @@ export class MacroJs {
*
*/
processDescriptor = (descriptor: NodePath<ObjectExpression>) => {
const messageProperty = this.getObjectPropertyByKey(descriptor, MESSAGE)
const idProperty = this.getObjectPropertyByKey(descriptor, ID)
const contextProperty = this.getObjectPropertyByKey(descriptor, CONTEXT)
const commentProperty = this.getObjectPropertyByKey(descriptor, COMMENT)

const properties: ObjectProperty[] = []

if (idProperty) {
properties.push(idProperty.node)
}
const messageProperty = this.getObjectPropertyByKey(
descriptor,
MsgDescriptorPropKey.message
)
const idProperty = this.getObjectPropertyByKey(
descriptor,
MsgDescriptorPropKey.id
)
const contextProperty = this.getObjectPropertyByKey(
descriptor,
MsgDescriptorPropKey.context
)
const commentProperty = this.getObjectPropertyByKey(
descriptor,
MsgDescriptorPropKey.comment
)

if (!this.stripNonEssentialProps && contextProperty) {
properties.push(contextProperty.node)
}
let tokens: Token[] = []

// if there's `message` property, replace macros with formatted message
if (messageProperty) {
// Inside message descriptor the `t` macro in `message` prop is optional.
// Template strings are always processed as if they were wrapped by `t`.
const messageValue = messageProperty.get("value")

const tokens = messageValue.isTemplateLiteral()
tokens = messageValue.isTemplateLiteral()
? this.tokenizeTemplateLiteral(messageValue)
: this.tokenizeNode(messageValue, true)

let messageNode = messageValue.node as StringLiteral

if (tokens) {
const { message, values } = buildICUFromTokens(tokens)
messageNode = this.types.stringLiteral(message)

properties.push(this.createValuesProperty(values))
}

if (!this.stripNonEssentialProps) {
properties.push(
this.createObjectProperty(MESSAGE, messageNode as Expression)
)
}

if (!idProperty && this.types.isStringLiteral(messageNode)) {
const context =
contextProperty &&
this.getTextFromExpression(
contextProperty.get("value").node as Expression
)

properties.push(this.createIdProperty(messageNode.value, context))
}
}

if (!this.stripNonEssentialProps && commentProperty) {
properties.push(commentProperty.node)
}

return this.createMessageDescriptor(properties, descriptor.node.loc)
}

createIdProperty(message: string, context?: string) {
return this.createObjectProperty(
ID,
this.types.stringLiteral(generateMessageId(message, context))
)
}

createValuesProperty(values: ParsedResult["values"]) {
const valuesObject = Object.keys(values).map((key) =>
this.types.objectProperty(this.types.identifier(key), values[key])
)

if (!valuesObject.length) return

return this.types.objectProperty(
this.types.identifier("values"),
this.types.objectExpression(valuesObject)
return createMessageDescriptorFromTokens(
tokens,
descriptor.node.loc,
this.stripNonEssentialProps,
{
id: idProperty?.node,
context: contextProperty?.node,
comment: commentProperty?.node,
}
)
}

Expand All @@ -415,6 +370,15 @@ export class MacroJs {
),
]
}

if (path.isStringLiteral()) {
return [
{
type: "text",
value: path.node.value,
} satisfies TextToken,
]
}
// if (isFormatMethod(node.callee)) {
// // date, number
// return transformFormatMethod(node, file, props, root)
Expand Down Expand Up @@ -543,45 +507,6 @@ export class MacroJs {
)
}

createMessageDescriptorFromTokens(tokens: Tokens, oldLoc?: SourceLocation) {
const { message, values } = buildICUFromTokens(tokens)

const properties: ObjectProperty[] = [
this.createIdProperty(message),

!this.stripNonEssentialProps
? this.createObjectProperty(MESSAGE, this.types.stringLiteral(message))
: null,

this.createValuesProperty(values),
]

return this.createMessageDescriptor(
properties,
// preserve line numbers for extractor
oldLoc
)
}

createMessageDescriptor(
properties: ObjectProperty[],
oldLoc?: SourceLocation
): ObjectExpression {
const newDescriptor = this.types.objectExpression(
properties.filter(Boolean)
)
this.types.addComment(newDescriptor, "leading", EXTRACT_MARK)
if (oldLoc) {
newDescriptor.loc = oldLoc
}

return newDescriptor
}

createObjectProperty(key: string, value: Expression) {
return this.types.objectProperty(this.types.identifier(key), value)
}

getObjectPropertyByKey(
objectExp: NodePath<ObjectExpression>,
key: string
Expand Down Expand Up @@ -656,16 +581,4 @@ export class MacroJs {
return JsMacroName.selectOrdinal
}
}

getTextFromExpression(exp: Expression): string {
if (this.types.isStringLiteral(exp)) {
return exp.value
}

if (this.types.isTemplateLiteral(exp)) {
if (exp?.quasis.length === 1) {
return exp.quasis[0]?.value?.cooked
}
}
}
}
Loading

0 comments on commit 4821317

Please sign in to comment.