Skip to content
Open
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
2 changes: 2 additions & 0 deletions packages/babel-plugin-lingui-macro/src/macro.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ function macro({ state, babel, config }: MacroParams) {
;[
JsMacroName.defineMessage,
JsMacroName.msg,
JsMacroName.arg,
JsMacroName.ph,
JsMacroName.t,
JsMacroName.useLingui,
JsMacroName.plural,
Expand Down
44 changes: 24 additions & 20 deletions packages/babel-plugin-lingui-macro/src/macroJsAst.ts
Original file line number Diff line number Diff line change
Expand Up @@ -257,20 +257,11 @@ export function tokenizeExpression(
if (t.isTSAsExpression(node)) {
return tokenizeExpression(node.expression, ctx)
}

if (t.isObjectExpression(node)) {
return tokenizeLabeledExpression(node, ctx)
} else if (
t.isCallExpression(node) &&
isLinguiIdentifier(node.callee, JsMacroName.ph, ctx) &&
node.arguments.length > 0
) {
if (!t.isObjectExpression(node.arguments[0])) {
throw new Error(
"Incorrect usage of `ph` macro. First argument should be an ObjectExpression"
)
}

return tokenizeLabeledExpression(node.arguments[0], ctx)
} else if (t.isCallExpression(node) && isPhDecorator(node, ctx)) {
return tokenizePhDecorator(node, ctx)
}

return {
Expand All @@ -280,18 +271,24 @@ export function tokenizeExpression(
}
}

function tokenizePhDecorator(node: CallExpression, ctx: MacroJsContext) {
if (node.arguments.length !== 1 || !t.isObjectExpression(node.arguments[0])) {
throw new Error(
"Incorrect usage of `ph` macro. Expected exactly one argument as `{variableName: variableValue}`"
)
}

return tokenizeLabeledExpression(node.arguments[0], ctx)
}

export function tokenizeArg(
node: CallExpression,
ctx: MacroJsContext
): ArgToken {
const arg = node.arguments[0] as Expression
const token = tokenizeExpression(node.arguments[0] as Expression, ctx)
token.raw = true

return {
type: "arg",
name: expressionToArgument(arg, ctx),
raw: true,
value: arg,
}
return token
}

export function expressionToArgument(
Expand All @@ -304,7 +301,14 @@ export function expressionToArgument(
return String(ctx.getExpressionIndex())
}

export function isArgDecorator(node: Node, ctx: MacroJsContext): boolean {
export function isPhDecorator(node: Node, ctx: MacroJsContext) {
return (
t.isCallExpression(node) &&
isLinguiIdentifier(node.callee, JsMacroName.ph, ctx)
)
}

export function isArgDecorator(node: Node, ctx: MacroJsContext) {
return (
t.isCallExpression(node) &&
isLinguiIdentifier(node.callee, JsMacroName.arg, ctx)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ const message =
`;

exports[`should expand macros in message property 1`] = `
import { defineMessage, plural, arg } from "@lingui/core/macro";
import { defineMessage, plural } from "@lingui/core/macro";
const message = defineMessage({
comment: "Description",
message: plural(value, { one: "book", other: "books" }),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,31 @@ _i18n._(

`;

exports[`Macro with utility arg macro usage in options 1`] = `
import { plural, arg } from "@lingui/core/macro";
plural(count, {
one: \`# book on {\${arg(today)}, date}\`,
other: \`# books on {\${arg(today)}, date}\`,
});

↓ ↓ ↓ ↓ ↓ ↓

import { i18n as _i18n } from "@lingui/core";
_i18n._(
/*i18n*/
{
id: "lEIbMo",
message:
"{count, plural, one {# book on {today, date}} other {# books on {today, date}}}",
values: {
count: count,
today: today,
},
}
);

`;

exports[`plural macro could be renamed 1`] = `
import { plural as plural2 } from "@lingui/core/macro";
const a = plural2(count, {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -514,6 +514,57 @@ _i18n._(

`;

exports[`Variables with arg macro is not wrapped in curly brackets 1`] = `
import { t, arg } from "@lingui/core/macro";
t\`Number {\${arg(num)}, number, myNumberStyle}\`;

↓ ↓ ↓ ↓ ↓ ↓

import { i18n as _i18n } from "@lingui/core";
_i18n._(
/*i18n*/
{
id: "6HvXd1",
message: "Number {num, number, myNumberStyle}",
values: {
num: num,
},
}
);

`;

exports[`Variables with arg macro supports named placeholders syntax 1`] = `
import { t, arg, ph } from "@lingui/core/macro";
t\`Number {\${arg({ num: getNum() })}, number, myNumberStyle}\`;
t\`Number {\${arg(ph({ num: getNum() }))}, number, myNumberStyle}\`;

↓ ↓ ↓ ↓ ↓ ↓

import { i18n as _i18n } from "@lingui/core";
_i18n._(
/*i18n*/
{
id: "6HvXd1",
message: "Number {num, number, myNumberStyle}",
values: {
num: getNum(),
},
}
);
_i18n._(
/*i18n*/
{
id: "6HvXd1",
message: "Number {num, number, myNumberStyle}",
values: {
num: getNum(),
},
}
);

`;

exports[`Variables with escaped double quotes are correctly formatted 1`] = `
import { t } from "@lingui/core/macro";
t\`Variable "name"\`;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ macroTester({
{
name: "should expand macros in message property",
code: `
import { defineMessage, plural, arg } from '@lingui/core/macro';
import { defineMessage, plural } from '@lingui/core/macro';
const message = defineMessage({
comment: "Description",
message: plural(value, { one: "book", other: "books" })
Expand Down
11 changes: 11 additions & 0 deletions packages/babel-plugin-lingui-macro/test/js-plural.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,17 @@ macroTester({
`,
},

{
name: "Macro with utility arg macro usage in options",
code: `
import { plural, arg } from '@lingui/core/macro';
plural(count, {
"one": \`# book on {\${arg(today)}, date}\`,
other: \`# books on {\${arg(today)}, date}\`
});
`,
},

{
useTypescriptPreset: true,
name: "Macro with labeled expression with `as` expression",
Expand Down
15 changes: 15 additions & 0 deletions packages/babel-plugin-lingui-macro/test/js-t.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,21 @@ macroTester({
t\`Variable \${{name} as any}\`;
`,
},
{
name: "Variables with arg macro is not wrapped in curly brackets",
code: `
import { t, arg } from '@lingui/core/macro';
t\`Number {\${arg(num)}, number, myNumberStyle}\`;
`,
},
{
name: "Variables with arg macro supports named placeholders syntax",
code: `
import { t, arg, ph } from '@lingui/core/macro';
t\`Number {\${arg({num: getNum()})}, number, myNumberStyle}\`;
t\`Number {\${arg(ph({num: getNum()}))}, number, myNumberStyle}\`;
`,
},
{
name: "Newlines are preserved",
code: `
Expand Down
14 changes: 14 additions & 0 deletions packages/core/macro/__typetests__/index.tst.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
selectOrdinal,
select,
ph,
arg,
} from "@lingui/core/macro"

const name = "Jack"
Expand Down Expand Up @@ -295,3 +296,16 @@ expect(

// should accept only strings
expect(select).type.not.toBeCallableWith("male", { male: 5, other: 5 })

///////////////////
//// Arg
///////////////////

// simple
expect(t`Hello ${arg(name)}`).type.toBe<string>()

// with labeled value
expect(t`Hello ${arg({ name: user.name })}`).type.toBe<string>()

// with ph labeled value
expect(t`Hello ${arg(ph({ name: user.name }))}`).type.toBe<string>()
12 changes: 12 additions & 0 deletions packages/core/macro/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -233,3 +233,15 @@ export const msg: typeof defineMessage
* Helps to define a name for a variable in the message
*/
export function ph(def: LabeledExpression<string | number>): string

/**
* Helps to inject a variable into the other macro usage without automatically wrapping it in curly brackets,
* so it can be used with custom ICU expressions.
*
* @example
* ```
* import { t, arg } from "@lingui/core/macro";
* t`Number {${arg(num)}, number, myNumberStyle}`;
* ```
*/
export function arg(def: MessagePlaceholder): string
29 changes: 29 additions & 0 deletions website/docs/ref/macro.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -499,6 +499,35 @@ const message = /*i18n*/ {

:::

### `arg`

The `arg` macro is a utility macro that helps to inject a message variables into the other macro usage without automatically wrapping them in curly brackets.

```ts
arg(value: string | number)
```

It can be used to apply custom formatting to a variable inside the other macro call. It is an escape hatch for writing message syntax using [ICU MessageFormat](/guides/message-format) on your own.

```js
import { t, arg } from "@lingui/core/macro";
const message = t`Number {${arg(num)}, number, myNumberStyle}`;

// ↓ ↓ ↓ ↓ ↓ ↓

import { i18n } from "@lingui/core";

const message = i18n._(
/*i18n*/ {
id: "6HvXd1",
message: "Number {num, number, myNumberStyle}",
values: { num },
}
);
```

:::

## React Macros

React (JSX) Macros are used in JSX elements and are transformed into the [`Trans`](/ref/react#trans) component imported from the [`@lingui/react`](/ref/react) package.
Expand Down
Loading