Skip to content

Commit

Permalink
feat: collect and print placeholders in Po files for better translati…
Browse files Browse the repository at this point in the history
…on context (#1965)

* feat: collect and print placeholders in Po files for better translation context

* support many placeholders values

* add limit for placeholder print and add more tests

* update readme and jsdoc
  • Loading branch information
timofei-iatsenko authored Jun 28, 2024
1 parent 8b35acf commit 6230100
Show file tree
Hide file tree
Showing 19 changed files with 533 additions and 31 deletions.
101 changes: 83 additions & 18 deletions packages/babel-plugin-extract-messages/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
Node,
ObjectExpression,
ObjectProperty,
isObjectExpression,
} from "@babel/types"
import type { PluginObj, PluginPass, NodePath } from "@babel/core"
import type { Hub } from "@babel/traverse"
Expand All @@ -19,6 +20,7 @@ export type ExtractedMessage = {
origin?: Origin

comment?: string
placeholders?: Record<string, string>
}

export type ExtractPluginOpts = {
Expand All @@ -30,6 +32,7 @@ type RawMessage = {
message?: string
comment?: string
context?: string
placeholders?: Record<string, string>
}

export type Origin = [filename: string, line: number, column?: number]
Expand All @@ -52,6 +55,7 @@ function collectMessage(
message: props.message,
context: props.context,
comment: props.comment,
placeholders: props.placeholders || {},
origin: [ctx.file.opts.filename, line, column],
})
}
Expand Down Expand Up @@ -109,19 +113,67 @@ function getTextFromExpression(
}
}

function extractFromObjectExpression(
function getNodeSource(fileContents: string, node: Node) {
return fileContents.slice(node.start, node.end)
}

function valuesObjectExpressionToPlaceholdersRecord(
t: BabelTypes,
exp: ObjectExpression,
hub: Hub,
keys: readonly string[]
hub: Hub
) {
const props: Record<string, string> = {}

;(exp.properties as ObjectProperty[]).forEach(({ key, value }, i) => {
let name: string

if (t.isStringLiteral(key) || t.isNumericLiteral(key)) {
name = key.value.toString()
} else if (t.isIdentifier(key)) {
name = key.name
} else {
console.warn(
hub.buildError(
exp,
`Could not extract values to placeholders. The key #${i} has unsupported syntax`,
SyntaxError
).message
)
}

if (name) {
props[name] = getNodeSource(hub.getCode(), value)
}
})

return props
}

function extractFromObjectExpression(
t: BabelTypes,
exp: ObjectExpression,
hub: Hub
) {
const props: RawMessage = {}

const textKeys = ["id", "message", "comment", "context"] as const

;(exp.properties as ObjectProperty[]).forEach(({ key, value }, i) => {
const name = (key as Identifier).name

if (!keys.includes(name as any)) return
props[name] = getTextFromExpression(t, value as Expression, hub)
if (name === "values" && isObjectExpression(value)) {
props.placeholders = valuesObjectExpressionToPlaceholdersRecord(
t,
value,
hub
)
} else if (textKeys.includes(name as any)) {
props[name as (typeof textKeys)[number]] = getTextFromExpression(
t,
value as Expression,
hub
)
}
})

return props
Expand Down Expand Up @@ -167,12 +219,7 @@ export default function ({ types: t }: { types: BabelTypes }): PluginObj {
path: NodePath<ObjectExpression>,
ctx: PluginPass
) => {
const props = extractFromObjectExpression(t, path.node, ctx.file.hub, [
"id",
"message",
"comment",
"context",
])
const props = extractFromObjectExpression(t, path.node, ctx.file.hub)

if (!props.id) {
console.warn(
Expand Down Expand Up @@ -200,7 +247,7 @@ export default function ({ types: t }: { types: BabelTypes }): PluginObj {
return
}

const props = attrs.reduce<Record<string, unknown>>((acc, item) => {
const props = attrs.reduce<RawMessage>((acc, item) => {
if (t.isJSXSpreadAttribute(item)) {
return acc
}
Expand All @@ -221,6 +268,19 @@ export default function ({ types: t }: { types: BabelTypes }): PluginObj {
acc[key] = item.value.expression.value
}
}

if (
key === "values" &&
t.isJSXExpressionContainer(item.value) &&
isObjectExpression(item.value.expression)
) {
acc.placeholders = valuesObjectExpressionToPlaceholdersRecord(
t,
item.value.expression,
ctx.file.hub
)
}

return acc
}, {})

Expand Down Expand Up @@ -266,7 +326,7 @@ export default function ({ types: t }: { types: BabelTypes }): PluginObj {
return
} else {
// i18n._(id, variables, descriptor)
let props = {
let props: RawMessage = {
id: getTextFromExpression(
t,
firstArgument.node as Expression,
Expand All @@ -279,16 +339,21 @@ export default function ({ types: t }: { types: BabelTypes }): PluginObj {
return
}

const secondArgument = path.node.arguments[1]
if (secondArgument && t.isObjectExpression(secondArgument)) {
props.placeholders = valuesObjectExpressionToPlaceholdersRecord(
t,
secondArgument,
ctx.file.hub
)
}

const msgDescArg = path.node.arguments[2]

if (t.isObjectExpression(msgDescArg)) {
props = {
...props,
...extractFromObjectExpression(t, msgDescArg, ctx.file.hub, [
"message",
"comment",
"context",
]),
...extractFromObjectExpression(t, msgDescArg, ctx.file.hub),
}
}

Expand Down
Loading

0 comments on commit 6230100

Please sign in to comment.