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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ node_modules
.yarn
.swc
.new
dist
153 changes: 98 additions & 55 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,73 @@ yarn add @lingui/macro

## Usage

`.swcrc`
https://swc.rs/docs/configuration/swcrc
If your build tool uses a JS config (Next.js, Vite, etc.), use the `linguiMacroSwcPlugin` helper — it reads your Lingui config and prepares all plugin options automatically.

If you configure SWC directly via `.swcrc` (e.g. the SWC CLI), pass options manually as described in the [Options](#options) section below.

### JS config (recommended)

#### `next.config.js`

```js
const { linguiMacroSwcPlugin } = require("@lingui/swc-plugin/options")

/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
experimental: {
swcPlugins: [
linguiMacroSwcPlugin(),
],
},
};

module.exports = nextConfig;
```

> **Note**
> Consult with full working example for NextJS in the `/examples` folder in this repo.

#### `vite.config.ts`

```ts
import { defineConfig } from "vite"
import react from "@vitejs/plugin-react-swc"
import { lingui } from "@lingui/vite-plugin"
import { linguiMacroSwcPlugin } from "@lingui/swc-plugin/options"

export default defineConfig({
plugins: [
react({
plugins: [linguiMacroSwcPlugin()],
}),
lingui(),
],
})
```

#### `linguiMacroSwcPlugin(overrides?, configOptions?)`

`linguiMacroSwcPlugin` reads your Lingui config and maps relevant options to the SWC plugin format. It returns a `["@lingui/swc-plugin", options]` tuple ready to use in plugin arrays.

```js
import { linguiMacroSwcPlugin } from "@lingui/swc-plugin/options"

// Recommended — reads lingui.config.{js,ts} automatically
linguiMacroSwcPlugin()

// Override specific options
linguiMacroSwcPlugin({
useLinguiV5IdGeneration: true,
})

// Specify which lingui config to use
linguiMacroSwcPlugin({}, { configPath: '../lingui.config.js' })
```

### `.swcrc`

When using SWC directly via CLI or a JSON-only configuration, pass options manually. All options are optional — if your have a standard setup, an empty object `{}` is sufficient:

```json5
{
Expand All @@ -43,35 +108,16 @@ https://swc.rs/docs/configuration/swcrc
[
"@lingui/swc-plugin",
{
// Optional
// Unlike the JS version this option must be passed as object only.
// Docs https://lingui.dev/ref/conf#runtimeconfigmodule
// "runtimeModules": {
// "i18n": ["@lingui/core", "i18n"],
// "trans": ["@lingui/react", "Trans"]
// }
//
// Optional. Controls which descriptor fields are preserved in output.
// "descriptorFields": "auto" (default) | "all" | "id-only" | "message"
//
// Compatibility option allows to use v6.* SWC Plugin release channel with @lingui/cli@5.*
// Controls the BASE64 alphabet used for generating message IDs.
// - false (default): Uses URL-safe BASE64 alphabet (Lingui v6 behavior)
// - true: Uses standard BASE64 alphabet (Lingui v5 behavior for compatibility)
//
// IMPORTANT: This option is temporal and will be removed in the next major release.
// "useLinguiV5IdGeneration": true
//
// Optional. Restricts directive-based idPrefix application to explicit ids
// starting with this leader string, while keeping the leader in the final id.
// "idPrefixLeader": "."
//
// To configure custom JSX placeholder attribute and its defaults:
// "jsxPlaceholderAttribute": "_t",
// "jsxPlaceholderDefaults": {
// "a": "link",
// "em": "em"
// }
"runtimeModules": {
"i18n": ["@lingui/core", "i18n"],
"trans": ["@lingui/react", "Trans"],
"useLingui": ["@lingui/react", "useLingui"]
},
"descriptorFields": "auto",
"jsxPlaceholderAttribute": "_t",
"jsxPlaceholderDefaults": {
"a": "link"
}
},
],
],
Expand All @@ -80,6 +126,8 @@ https://swc.rs/docs/configuration/swcrc
}
```

## Options

### `descriptorFields`

Controls which fields are preserved in the transformed message descriptors. Accepts one of:
Expand All @@ -89,40 +137,35 @@ Controls which fields are preserved in the transformed message descriptors. Acce
- **`"id-only"`** — Keeps only the `id`. Most optimized for production bundles.
- **`"message"`** — Keeps `id`, `message`, and `context` (but not `comment`). Useful when you need message content at runtime.

Check [this article](https://lingui.dev/guides/optimizing-bundle-size) for more info about this configuration.
See [Optimizing bundle size](https://lingui.dev/guides/optimizing-bundle-size) for more info about this configuration.

### `idPrefixLeader`

Controls how directive-based `idPrefix` values are applied to explicit message ids.
The SWC plugin matches the Babel macro behavior

- When omitted, `idPrefix` is prepended to explicit static ids.
- When set, `idPrefix` is prepended only when the explicit static id starts with the configured leader string.
- Auto-generated hash ids are never prefixed.
See [Configuration Doc](https://lingui.dev/ref/conf#macroidprefixleader) and [`lingui-set` / `lingui-reset` Comment Directives Doc](https://lingui.dev/ref/macro#lingui-directive)

See [Lingui macro docs](https://lingui.dev/ref/macro) for comment directive syntax and semantics. The SWC plugin matches the Babel macro behavior.
### `jsxPlaceholderAttribute`

Or Next JS Usage:
Sets the JSX attribute name used to provide explicit placeholder names inside `<Trans>` content.

`next.config.js`
```js
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
experimental: {
swcPlugins: [
['@lingui/swc-plugin', {
// the same options as in .swcrc
}],
],
},
};
### `jsxPlaceholderDefaults`

module.exports = nextConfig;
```
Defines default placeholder names for JSX tags when no explicit placeholder attribute is present.

> **Note**
> Consult with full working example for NextJS in the `/examples` folder in this repo.
### `runtimeModules`

Overrides the runtime imports used by the plugin. Unlike [the Babel macro configuration](https://lingui.dev/ref/conf#runtimeconfigmodule), this option must be passed as an object.

### `useLinguiV5IdGeneration`

Compatibility option for using the v6 SWC plugin release channel with `@lingui/cli@5.*`.

- **`false`** (default) — Uses the URL-safe Base64 alphabet used by Lingui v6.
- **`true`** — Uses the standard Base64 alphabet used by Lingui v5.

> **Note**
> This option is temporary and will be removed in the next major release.

## Compatibility
SWC Plugin support is still experimental. They do not guarantee a semver backwards compatibility between different `swc-core` versions.
Expand Down
16 changes: 16 additions & 0 deletions e2e/fixtures/lingui-options/custom.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export default {
locales: ["en"],
sourceLocale: "en",
runtimeConfigModule: {
i18n: ["@custom/core", "customI18n"],
Trans: ["@custom/react", "CustomTrans"],
useLingui: ["@custom/react", "useCustomLingui"],
},
macro: {
jsxPlaceholderAttribute: "data-i18n",
jsxPlaceholderDefaults: {
a: "anchor",
strong: "bold",
},
},
}
15 changes: 15 additions & 0 deletions e2e/fixtures/lingui-options/lingui.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export default {
locales: ["en"],
sourceLocale: "en",
runtimeConfigModule: {
i18n: ["@acme/core", "i18n"],
Trans: ["@acme/react", "Trans"],
useLingui: ["@acme/react", "useLingui"],
},
macro: {
jsxPlaceholderAttribute: "_t",
jsxPlaceholderDefaults: {
a: "link",
},
},
}
114 changes: 114 additions & 0 deletions e2e/options.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import {describe, expect, it} from "vitest"
import {resolve} from "node:path"

import {linguiMacroSwcPlugin} from "../src-js/options"

const fixturesDir = resolve(import.meta.dirname, "fixtures/lingui-options")

describe("linguiMacroSwcPlugin", () => {
it("discovers the default Lingui config from cwd", () => {
const previousCwd = process.cwd()

try {
process.chdir(fixturesDir)

expect(linguiMacroSwcPlugin()).toMatchInlineSnapshot(`
[
"@lingui/swc-plugin",
{
"jsxPlaceholderAttribute": "_t",
"jsxPlaceholderDefaults": {
"a": "link",
},
"runtimeModules": {
"i18n": [
"@acme/core",
"i18n",
],
"trans": [
"@acme/react",
"Trans",
],
"useLingui": [
"@acme/react",
"useLingui",
],
},
},
]
`)
} finally {
process.chdir(previousCwd)
}
})

it("maps shared options from an explicit config path", () => {
expect(linguiMacroSwcPlugin({}, {configPath: resolve(fixturesDir, "custom.config.js")})).toMatchInlineSnapshot(

`
[
"@lingui/swc-plugin",
{
"jsxPlaceholderAttribute": "data-i18n",
"jsxPlaceholderDefaults": {
"a": "anchor",
"strong": "bold",
},
"runtimeModules": {
"i18n": [
"@custom/core",
"customI18n",
],
"trans": [
"@custom/react",
"CustomTrans",
],
"useLingui": [
"@custom/react",
"useCustomLingui",
],
},
},
]
`)
})

it("merges overrides over mapped config", () => {
expect(
linguiMacroSwcPlugin(
{
jsxPlaceholderAttribute: "data-test",
runtimeModules: {
trans: ["@override/react", "OverrideTrans"],
},
},
{configPath: resolve(fixturesDir, "custom.config.js")},
),
).toMatchInlineSnapshot(`
[
"@lingui/swc-plugin",
{
"jsxPlaceholderAttribute": "data-test",
"jsxPlaceholderDefaults": {
"a": "anchor",
"strong": "bold",
},
"runtimeModules": {
"i18n": [
"@custom/core",
"customI18n",
],
"trans": [
"@override/react",
"OverrideTrans",
],
"useLingui": [
"@custom/react",
"useCustomLingui",
],
},
},
]
`)
})
})
26 changes: 20 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,27 @@
],
"main": "target/wasm32-wasip1/release/lingui_macro_plugin.wasm",
"exports": {
".": "./target/wasm32-wasip1/release/lingui_macro_plugin.wasm"
".": "./target/wasm32-wasip1/release/lingui_macro_plugin.wasm",
"./options": {
"types": "./dist/options.d.ts",
"default": "./dist/options.js"
}
},
"scripts": {
"prepublishOnly": "cargo build-wasi --release",
"prepublishOnly": "yarn build:ts && yarn build:wasm",
"build:ts": "tsc -p tsconfig.build.json",
"build:wasm": "cargo build-wasi --release",
"test:e2e": "cargo build-wasi --release && vitest run"
},
"files": [],
"files": [
"LICENSE",
"README.md",
"dist/",
"target/wasm32-wasip1/release/lingui_macro_plugin.wasm"
],
"dependencies": {
"@lingui/conf": "5 || 6"
},
"peerDependencies": {
"@lingui/core": "5 || 6"
},
Expand All @@ -42,10 +56,10 @@
}
},
"devDependencies": {
"@swc/core": "^1.15.11",
"@swc/core": "^1.15.33",
"@types/node": "22.13.14",
"typescript": "^5.9.3",
"vitest": "^4.0.18"
"typescript": "^6.0.3",
"vitest": "^4.1.7"
},
"packageManager": "yarn@4.12.0+sha512.f45ab632439a67f8bc759bf32ead036a1f413287b9042726b7cc4818b7b49e14e9423ba49b18f9e06ea4941c1ad062385b1d8760a8d5091a1a31e5f6219afca8"
}
Loading
Loading