<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:
| `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:
| `"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.
| 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.
| `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
| `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
| [`@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)