intlayer-swc-plugin 9.5.9

SWC transform plugin for Intlayer. Replaces `useIntlayer` / `getIntlayer` string keys with pre-loaded dictionary imports at compile time, enabling tree-shaking and eliminating runtime registry lookups.
Documentation

An SWC transform plugin for Intlayer β€” 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):

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

After (transformed output):

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

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

After

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 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. You do not need to add this Rust crate as a dependency for that use-case.

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

Configure in next.config.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 or @intlayer/vite plugin, which configures the SWC plugin automatically based on your intlayer.config.* file.

Usage: native Rust library

Add to Cargo.toml:

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

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

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.

# 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.

Related packages

Package Description
@intlayer/webpack Webpack plugin (Babel-based transform)
@intlayer/vite Vite plugin
react-intlayer React hooks
next-intlayer Next.js integration

License

Apache-2.0 Β© Aymeric Pineau