<p align="center">
  <a href="https://intlayer.org" rel="">
    <img src="https://raw.githubusercontent.com/aymericzip/intlayer/main/docs/assets/cover.webp" width="60%" alt="Intlayer Logo" />
  </a>
</p>

<h1 align="center">intlayer-swc-plugin</h1>

<p align="center">
  <a href="https://intlayer.org/doc/concept/content" rel="">Docs</a> •
  <a href="https://intlayer.org/doc/environment/nextjs" rel="">Next.js</a> •
  <a href="https://intlayer.org/doc/environment/vite-and-react" rel="">React + Vite</a> •
  <a href="https://intlayer.org/doc/concept/cms" rel="">CMS</a> •
  <a href="https://discord.gg/7uxamYVeCk" rel="noopener noreferrer nofollow">Discord</a>
</p>

<p align="center" style="margin-top:15px;">
  <a href="https://www.npmjs.com/package/@intlayer/swc" target="_blank" rel="noopener noreferrer nofollow"><img src="https://img.shields.io/npm/v/@intlayer/swc?style=for-the-badge&labelColor=FFFFFF&color=000000&logoColor=FFFFFF" alt="npm version" height="24"/></a>
  <a href="https://github.com/aymericzip/intlayer/stargazers" target="_blank" rel="noopener noreferrer nofollow"><img src="https://img.shields.io/github/stars/aymericzip/intlayer?style=for-the-badge&labelColor=000000&color=FFFFFF&logo=github&logoColor=FFD700" alt="GitHub Stars" height="24"/></a>
  <a href="https://www.npmjs.org/package/@intlayer/swc" target="_blank" rel="noopener noreferrer nofollow"><img src="https://img.shields.io/npm/dm/@intlayer/swc?style=for-the-badge&labelColor=000000&color=FFFFFF&logoColor=000000&cacheSeconds=86400" alt="monthly downloads" height="24"/></a>
</p>

An [SWC](https://swc.rs) transform plugin for [Intlayer](https://intlayer.org) — the open-source i18n framework for React, Next.js, Vue, Svelte, and more.

**Intlayer** is a **modern i18n solution** for web and mobile apps. It’s framework-agnostic, **AI-powered**, and includes a free **CMS & visual editor**. With **per-locale content files**, **TypeScript autocompletion**, **tree-shakable dictionaries**, and **CI/CD integration**, Intlayer makes internationalization **faster, cleaner, and smarter**.

## 🚀 Key Features

Intlayer provides a variety of features to help you scale your internationalization efforts.

- **Cross-Framework Support**: Compatible with all major frameworks and libraries, including Next.js, React, Vite, Vue.js, Nuxt, Preact, Express, and more.
- **JavaScript-Powered Content Management**: Harness the flexibility of JavaScript to define and manage your content efficiently.
- **Per-Locale Content Declaration File**: Speed up your development by declaring your content once, before auto generation.
- **Compiler**: The Intlayer Compiler extracts automatically the content from the components and generates the dictionary files.
- **Type-Safe Environment**: Leverage TypeScript to ensure your content definitions and code are error-free, while also benefiting from IDE autocompletion.
- **Simplified Setup**: Get up and running quickly with minimal configuration. Adjust settings for internationalization, routing, AI, build, and content handling with ease.
- **Simplified Content Retrieval**: No need to call your `t` function for each piece of content. Retrieve all your content directly using a single hook.
- **Consistent Server Component Implementation**: Perfectly suited for Next.js server components, use the same implementation for both client and server components.
- **Organized Codebase**: Keep your codebase more organized: 1 component = 1 dictionary in the same folder.
- **Enhanced Routing**: Full support of app routing, adapting seamlessly to complex application structures, for Next.js, React, Vite, Vue.js, etc.
- **Markdown Support**: Import and interpret locale files and remote Markdown for multilingual content like privacy policies, documentation, etc.
- **Free Visual Editor & CMS**: A free visual editor and CMS are available for content writers, removing the need for a localization platform.
- **Tree-shakable Content**: Tree-shakable content, reducing the size of the final bundle. Loads content per component, excluding any unused content from your bundle.
- **Static Rendering**: Doesn't block Static Rendering in Next.js.
- **AI-Powered Translation**: Transform your website into 231 languages with just one click using Intlayer's advanced AI-powered translation tools.
- **MCP Server Integration**: Provides an MCP server for IDE automation, enabling seamless content management and i18n workflows.
- **VSCode Extension**: Intlayer provides a VSCode extension to help you manage your content and translations.
- **Interoperability**: Allow interoperability with react-i18next, next-i18next, next-intl, react-intl, vue-i18n.
- **Performances & Benchmark**: Uses advanced tree-shaking and dynamic loading to boost performances and keep the solution as light as possible.

## 🛠️ How the plugin works

The plugin rewrites `useIntlayer` / `getIntlayer` / `useTranslations` call arguments at compile time, replacing string dictionary keys with pre-loaded dictionary imports. This eliminates runtime registry lookups and enables tree-shaking for per-locale bundles.

**Before** (source code):

```ts
import { useIntlayer } from "react-intlayer";
const t = useIntlayer("locale-switcher");
```

**After** (transformed output):

```ts
import _FsHhNfuhm85 from "../../.intlayer/dictionaries/locale-switcher.json" with { type: "json" };
import { useDictionary as useIntlayer } from "react-intlayer";
const t = useIntlayer(_FsHhNfuhm85);
```

Three import modes are supported:

| Mode      | Helper function        | Import type           |
| --------- | ---------------------- | --------------------- |
| `static`  | `useDictionary`        | JSON import assertion |
| `dynamic` | `useDictionaryDynamic` | Dynamic `.mjs` import |
| `fetch`   | `useDictionaryDynamic` | Fetch `.mjs` import   |

### Field renaming (`build.minify`)

When the compiled dictionaries have been minified — every user-defined content field renamed to a short alphabetic alias — the plugin rewrites the matching source accesses so both sides keep agreeing:

**Before**

```ts
const { title } = useIntlayer("about");
const content = useIntlayer("about");
content.section.subtitle;
```

**After**

```ts
const { d: title } = useIntlayer("about");
const content = useIntlayer("about");
content.b.a;
```

Destructuring (including nested patterns, local aliases and defaults), member chains, optional chaining, static computed accesses (`content["title"]`), array indexes (`content.list[0].title`) and signal-style accessors (`content().title`) are all handled. Dynamic accesses (`content[key]`) stop the rewrite, leaving the rest of the chain untouched.

The rename tables come from the `fieldRenameMap` option. Deciding _which_ fields are unused and _what_ alias each gets requires reading every component source file and rewriting the dictionary JSON — file I/O and cross-file state a per-file Wasm transform cannot do — so that analysis runs in Node (in `@intlayer/babel`, driven by `withIntlayer`) and only its result is handed to this plugin.

### Build reporting

The plugin transforms one file at a time with no cross-file state, so all it can report is a line per file. That is a tracing aid, not build output — the purge and minify summaries a build normally prints (which dictionaries were pruned, which fields were removed, what was partially minified) come from the Node-side pipeline and follow `log.mode` in `intlayer.config.*`, exactly as in a Vite build.

`logLevel` is therefore `"off"` unless you ask for tracing:

| Level     | Output                                                                      |
| --------- | --------------------------------------------------------------------------- |
| `"off"`   | Nothing (default)                                                           |
| `"info"`  | One line per transformed file: injected imports and renamed field count     |
| `"debug"` | Everything above, plus skipped files and the emitted code of each transform |

With `next-intlayer`, set `INTLAYER_SWC_LOG_LEVEL=info` (or `debug`) to turn it on; `log.mode: "disabled"` silences it regardless.

## Next.js compatibility

An SWC Wasm plugin can only be loaded by a host that speaks its `swc_ecma_ast`
schema. **Next.js 16.1.0 is the minimum**: it is the first release built on
SWC's forward-compatible plugin ABI, where the AST travels as self-describing
CBOR instead of rkyv, so one binary keeps working on later releases. Earlier
releases require an exact schema match and reject the plugin.

| Next.js | `swc_ecma_ast` | Plugin loads   |
| ------- | -------------- | -------------- |
| 14.2.x  | 0.112.7        | ❌ rkyv ABI    |
| 15.5.x  | 14.0.0         | ❌ rkyv ABI    |
| 16.0.x  | 16.0.0         | ❌ rkyv ABI    |
| 16.1.x  | 19.0.0         | ✅ exact match |
| 16.2.x  | 20.0.1         | ✅             |
| 16.3.x  | 25.0.0         | ✅             |

You do not have to check this yourself: `withIntlayer` from
[`next-intlayer`](https://www.npmjs.com/package/next-intlayer) reads the Next.js
version from your project and simply does not register the plugin below 16.1.0.
Those builds succeed, they just run without the bundle optimisation instead of
failing with `failed to invoke plugin`.

This is also why the crate pins `swc_core` to the `54.x` line rather than the
latest release: `54.x` is the newest `swc_core` still on `swc_ecma_ast` 19, the
schema Next.js 16.1 ships. Moving the pin forward would raise the minimum
supported Next.js version with it.

## Usage: Next.js / SWC Wasm plugin (recommended)

The Wasm binary is distributed via npm as [`@intlayer/swc`](https://www.npmjs.com/package/@intlayer/swc).
You do not need to add this Rust crate as a dependency for that use-case.

```bash
npm install @intlayer/swc
# or
bun add @intlayer/swc
```

Configure in `next.config.ts`:

```ts
import type { NextConfig } from "next";

const nextConfig: NextConfig = {
  experimental: {
    swcPlugins: [
      [
        "@intlayer/swc",
        {
          dictionariesDir: "/absolute/path/.intlayer/dictionaries",
          dictionariesEntryPath: "/absolute/path/.intlayer/dictionaries.mjs",
          dynamicDictionariesDir:
            "/absolute/path/.intlayer/dynamic_dictionaries",
          fetchDictionariesDir: "/absolute/path/.intlayer/fetch_dictionaries",
          importMode: "static", // "static" | "dynamic" | "fetch"
          replaceDictionaryEntry: false,
          filesList: [], // empty = transform all files
          dictionaryModeMap: {}, // per-key overrides, e.g. { "heavy-dict": "dynamic" }
          fieldRenameMap: {}, // minified field aliases, e.g. { about: { title: { shortName: "a", children: {} } } }
          logLevel: "off", // "off" | "info" | "debug"
        },
      ],
    ],
  },
};

export default nextConfig;
```

In practice you should use the [`@intlayer/webpack`](https://www.npmjs.com/package/@intlayer/webpack) or [`@intlayer/vite`](https://www.npmjs.com/package/@intlayer/vite) plugin, which configures the SWC plugin automatically based on your `intlayer.config.*` file.

## Usage: native Rust library

Add to `Cargo.toml`:

```toml
[dependencies]
intlayer-swc-plugin = "7"
```

Then call [`process_transform`] directly from your own SWC pipeline:

```rust
use intlayer_swc_plugin::{PluginConfig, process_transform};
use swc_core::ecma::ast::Program;

fn my_transform(program: Program, file_path: &str) -> Program {
    let config = PluginConfig {
        dictionaries_dir: "/project/.intlayer/dictionaries".into(),
        dictionaries_entry_path: "/project/.intlayer/dictionaries.mjs".into(),
        dynamic_dictionaries_dir: "/project/.intlayer/dynamic_dictionaries".into(),
        fetch_dictionaries_dir: "/project/.intlayer/fetch_dictionaries".into(),
        import_mode: Some("static".into()),
        replace_dictionary_entry: Some(false),
        ..PluginConfig::default()
    };
    process_transform(program, config, file_path.into())
}
```

### Building the Wasm plugin yourself

The `plugin` Cargo feature enables the `#[plugin_transform]` Wasm entry point. Without it the crate compiles as a standard native Rust library.

```bash
# Uses the alias defined in .cargo/config.toml
cargo build-wasip1 --release
# equivalent to:
cargo build --target wasm32-wasip1 --features plugin --release
```

Build through the alias, or at least through this crate's `.cargo/config.toml`:
it sets `--cfg=swc_ast_unknown` for `wasm32` targets, which is what opts the
binary into the forward-compatible ABI. Built without it, the plugin only loads
on hosts sharing its exact `swc_ecma_ast` version, and the first Next.js release
that adds an AST node breaks every build using it.

## Plugin configuration reference

All fields correspond to the JSON object passed as the second element of each `swcPlugins` tuple.

| Field                    | Type                               | Default    | Description                                          |
| ------------------------ | ---------------------------------- | ---------- | ---------------------------------------------------- |
| `dictionariesDir`        | `string`                           | required   | Absolute path to compiled `.json` dictionaries       |
| `dictionariesEntryPath`  | `string`                           | required   | Absolute path to the generated entry `.mjs` file     |
| `dynamicDictionariesDir` | `string`                           | required   | Absolute path for dynamic `.mjs` modules             |
| `fetchDictionariesDir`   | `string`                           | required   | Absolute path for fetch `.mjs` modules               |
| `importMode`             | `"static" \| "dynamic" \| "fetch"` | `"static"` | Global import strategy                               |
| `replaceDictionaryEntry` | `boolean`                          | `false`    | Replace entry file with empty stubs                  |
| `filesList`              | `string[]`                         | `[]`       | Allowlist of absolute file paths; empty = all files  |
| `dictionaryModeMap`      | `Record<string, string>`           | `{}`       | Per-dictionary import mode overrides                 |
| `nestingDictionaryKeys`  | `string[]`                         | `[]`       | Keys imported through their `nested/` companion      |
| `extraCallers`           | `ExtraCallerConfig[]`              | `[]`       | Compat-adapter callers to rewrite like `useIntlayer` |
| `fieldRenameMap`         | `Record<string, FieldRenameMap>`   | `{}`       | Minified field aliases, per dictionary key           |
| `logLevel`               | `"off" \| "info" \| "debug"`       | `"off"`    | Build-time reporting verbosity                       |

`FieldRenameMap` is a recursive object mapping each original field name to `{ shortName: string; children: FieldRenameMap }`.

## Public Rust API

The following symbols are exported by this crate:

- **`PluginConfig`** – configuration struct (mirrors the JSON options above). Implements `Default`, so `..PluginConfig::default()` keeps call sites stable as options are added.
- **`ExtraCallerConfig` / `NamespaceOptionConfig`** – compat-adapter caller descriptors.
- **`FieldRenameMap` / `FieldRenameNode`** – the minified field alias tables.
- **`LogLevel`** – build-time reporting verbosity.
- **`process_transform(program, cfg, filename) -> Program`** – core transform function; accepts and returns an SWC `Program` AST.
- **`normalize_path(path: &str) -> String`** – normalises Windows-style backslash paths to forward slashes for cross-platform path diffing.

### Crate layout

| Module             | Responsibility                                               |
| ------------------ | ------------------------------------------------------------ |
| `config`           | Plugin option types and their wire format                    |
| `ast`              | Small helpers for reading and building AST nodes             |
| `paths`            | Path normalisation and relative module specifiers            |
| `packages`         | Recognised package specifiers and generated-file conventions |
| `extra_caller`     | Namespace resolution for compat-adapter callers              |
| `field_rename`     | Source-side content field renaming (`build.minify`)          |
| `pre_pass`         | Caller discovery and the file-level dynamic/static decision  |
| `optimize`         | Call-site and import-specifier rewriting                     |
| `imports`          | Injection of the dictionary imports the rewrite created      |
| `dictionary_entry` | Emptying of the generated dictionaries entry module          |
| `logger`           | Build-time reporting                                         |

## Documentation

Explore our comprehensive documentation to get started with Intlayer and learn how to integrate it into your projects.

<details open>
<summary style="font-size:16px; font-weight:bold;">📘 Get Started</summary>
<ul>
  <li><a href="https://intlayer.org/doc/why" rel=''>Why Intlayer?</a></li>
  <li><a href="https://intlayer.org/doc" rel=''>Introduction</a></li>
</ul>
</details>

<details>
<summary style="font-size:16px; font-weight:bold;">⚙️ Concept</summary>
<ul>
  <li><a href="https://intlayer.org/doc/concept/how-works-intlayer" rel=''>How Intlayer Works</a></li>
  <li><a href="https://intlayer.org/doc/concept/configuration" rel=''>Configuration</a></li>
  <li><a href="https://intlayer.org/doc/concept/cli" rel=''>Intlayer CLI</a></li>
  <li><a href="https://intlayer.org/doc/compiler" rel=''>Compiler</a></li>
  <li><a href="https://intlayer.org/doc/concept/editor" rel=''>Intlayer Editor</a></li>
  <li><a href="https://intlayer.org/doc/concept/cms" rel=''>Intlayer CMS</a></li>
  <li><a href="https://intlayer.org/doc/concept/content" rel=''>Dictionary</a>
    <ul>
      <li><a href="https://intlayer.org/doc/concept/per-locale-file" rel=''>Per-Locale Content Declaration File</a></li>
      <li><a href="https://intlayer.org/doc/concept/content/translation" rel=''>Translation</a></li>
      <li><a href="https://intlayer.org/doc/concept/content/enumeration" rel=''>Enumeration</a></li>
      <li><a href="https://intlayer.org/doc/concept/content/condition" rel=''>Condition</a></li>
      <li><a href="https://intlayer.org/doc/concept/content/nesting" rel=''>Nesting</a></li>
      <li><a href="https://intlayer.org/doc/concept/content/markdown" rel=''>Markdown</a></li>
      <li><a href="https://intlayer.org/doc/concept/content/function-fetching" rel=''>Function Fetching</a></li>
      <li><a href="https://intlayer.org/doc/concept/content/insertion" rel=''>Insertion</a></li>
      <li><a href="https://intlayer.org/doc/concept/content/file" rel=''>File</a></li>
    </ul>
  </li>
</ul>
</details>

<details open>
<summary style="font-size:16px; font-weight:bold;">🌐 Environment</summary>
<ul>
  <li><a href="https://intlayer.org/doc/environment/nextjs" rel=''>Intlayer with Next.js 16</a>
    <ul>
      <li><a href="https://intlayer.org/doc/environment/nextjs/15" rel=''>Next.js 15</a></li>
      <li><a href="https://intlayer.org/doc/environment/nextjs/14" rel=''>Next.js 14 (App Router)</a></li>
      <li><a href="https://intlayer.org/doc/environment/nextjs/next-with-page-router" rel=''>Next.js Page Router</a></li>
      <li><a href="https://intlayer.org/doc/environment/nextjs/compiler" rel=''>Next.js using Compiler</a></li>
    </ul>
  </li>
  <li><a href="https://intlayer.org/doc/environment/create-react-app" rel=''>React CRA</a></li>
  <li><a href="https://intlayer.org/doc/environment/vite-and-react" rel=''>Vite + React</a></li>
  <li><a href="https://intlayer.org/doc/environment/vite-and-react" rel=''>Vite + React using Compiler</a></li>
  <li><a href="https://intlayer.org/doc/environment/vite-and-react/compiler" rel=''>React-router-v7</a></li>
  <li><a href="https://intlayer.org/doc/environment/tanstack-start" rel=''>Tanstack start</a>
    <ul>
      <li><a href="https://intlayer.org/doc/environment/tanstack-start/solid" rel=''>Solid</a></li>
    </ul>
  </li>
  <li><a href="https://intlayer.org/doc/environment/astro" rel=''>Astro</a>
    <ul>
      <li><a href="https://intlayer.org/doc/environment/astro/react" rel=''>React</a></li>
      <li><a href="https://intlayer.org/doc/environment/astro/vue" rel=''>Vue</a></li>
      <li><a href="https://intlayer.org/doc/environment/astro/svelte" rel=''>Svelte</a></li>
      <li><a href="https://intlayer.org/doc/environment/astro/solid" rel=''>Solid</a></li>
      <li><a href="https://intlayer.org/doc/environment/astro/lit" rel=''>Lit</a></li>
    </ul>
  </li>
  <li><a href="https://intlayer.org/doc/environment/react-native-and-expo" rel=''>React Native</a></li>
  <li><a href="https://intlayer.org/doc/environment/vite-and-svelte" rel=''>Vite + Svelte</a></li>
  <li><a href="https://intlayer.org/doc/environment/sveltekit" rel=''>SvelteKit</a></li>
  <li><a href="https://intlayer.org/doc/environment/vite-and-preact" rel=''>Vite + Preact</a></li>
  <li><a href="https://intlayer.org/doc/environment/vite-and-vue" rel=''>Vite + Vue</a></li>
  <li><a href="https://intlayer.org/doc/environment/nuxt-and-vue" rel=''>Vite + Nuxt</a></li>
  <li><a href="https://intlayer.org/doc/environment/vite-and-solid" rel=''>Vite + Solid</a></li>
  <li><a href="https://intlayer.org/doc/environment/solid-start" rel=''>Solid Start</a></li>
  <li><a href="https://intlayer.org/doc/environment/angular" rel=''>Angular</a></li>  <li>
     <a href="https://intlayer.org/doc/environment/express" rel=''>Backend</a>
     <ul>
      <li><a href="https://intlayer.org/doc/environment/express" rel=''>Express</a></li>
      <li><a href="https://intlayer.org/doc/environment/nest" rel=''>NestJS</a></li>
      <li><a href="https://intlayer.org/doc/environment/fastify" rel=''>Fastify</a></li>
      <li><a href="https://intlayer.org/doc/environment/adonisjs" rel=''>AdonisJS</a></li>
      <li><a href="https://intlayer.org/doc/environment/hono" rel=''>Hono</a></li>
      <li><a href="https://intlayer.org/doc/environment/elysia" rel=''>Elysia</a></li>
    </ul>
  </li>
</ul>
</details>

<details>
<summary style="font-size:16px; font-weight:bold;">📊 Benchmark</summary>
<ul>
  <li><a href="https://github.com/aymericzip/intlayer/blob/main/docs/docs/en/benchmark/nextjs.md" rel=''>Next.js</a></li>
  <li><a href="https://github.com/aymericzip/intlayer/blob/main/docs/docs/en/benchmark/tanstack.md" rel=''>TanStack Start</a></li>
  <li><a href="https://github.com/aymericzip/intlayer/blob/main/docs/docs/en/benchmark/vue.md" rel=''>Vue</a></li>
  <li><a href="https://github.com/aymericzip/intlayer/blob/main/docs/docs/en/benchmark/solid.md" rel=''>Solid</a></li>
  <li><a href="https://github.com/aymericzip/intlayer/blob/main/docs/docs/en/benchmark/svelte.md" rel=''>Svelte</a></li>
</ul>
</details>

<details>
<summary style="font-size:16px; font-weight:bold;">📰 Blog</summary>
<ul>
  <li><a href="https://github.com/aymericzip/intlayer/blob/main/docs/blog/en/what_is_internationalization.md" rel=''>What is i18n</a></li>
  <li><a href="https://intlayer.org/blog/SEO-and-i18n" rel=''>i18n and SEO</a></li>
  <li><a href="https://intlayer.org/blog/intlayer-with-next-i18next" rel=''>Intlayer and i18next</a></li>
  <li><a href="https://intlayer.org/blog/intlayer-with-react-i18next" rel=''>Intlayer and react-intl</a></li>
  <li><a href="https://intlayer.org/blog/intlayer-with-next-intl" rel=''>Intlayer and next-intl</a></li>
</ul>
</details>

## Related packages

| Package                                                                | Description                            |
| ---------------------------------------------------------------------- | -------------------------------------- |
| [`@intlayer/webpack`](https://www.npmjs.com/package/@intlayer/webpack) | Webpack plugin (Babel-based transform) |
| [`@intlayer/vite`](https://www.npmjs.com/package/@intlayer/vite)       | Vite plugin                            |
| [`react-intlayer`](https://www.npmjs.com/package/react-intlayer)       | React hooks                            |
| [`next-intlayer`](https://www.npmjs.com/package/next-intlayer)         | Next.js integration                    |

## License

Apache-2.0 © [Aymeric Pineau](https://github.com/aymericzip)
