@fluenti/vite-plugin
The core Vite plugin that powers Fluenti’s compile-time transforms, virtual module system, and code splitting. You never import @fluenti/vite-plugin directly — instead, use the framework-specific wrapper for your project.
Framework-specific usage
Section titled “Framework-specific usage”Each framework package re-exports the plugin factory with its own runtime generator and (optionally) framework-specific sub-plugins. The plugin ordering relative to the framework’s own Vite plugin matters.
import { defineConfig } from 'vite'import vue from '@vitejs/plugin-vue'import fluentiVue from '@fluenti/vue/vite-plugin'
export default defineConfig({ plugins: [ vue(), fluentiVue(), ],})fluentiVue() adds a fluenti:vue-template sub-plugin that pre-transforms v-t, <Trans>, and <Plural> usage in Vue SFC templates before the core script transform runs.
Place fluentiVue() after vue() so that Vue’s SFC compiler runs first.
import { defineConfig } from 'vite'import react from '@vitejs/plugin-react'import fluentiReact from '@fluenti/react/vite-plugin'
export default defineConfig({ plugins: [ fluentiReact(), react(), ],})Place fluentiReact() before react() so that Fluenti’s JSX transforms (t` `, <Trans>) run before React’s JSX compilation.
import { defineConfig } from 'vite'import solidPlugin from 'vite-plugin-solid'import fluentiSolid from '@fluenti/solid/vite-plugin'
export default defineConfig({ plugins: [ solidPlugin(), fluentiSolid(), ],})Place fluentiSolid() after solidPlugin(). The Solid wrapper passes the core plugins through with Solid’s reactive runtime generator.
Plugin options
Section titled “Plugin options”Every framework wrapper accepts a single optional FluentiPluginOptions object:
interface FluentiPluginOptions { config?: string | FluentiBuildConfig}The config field accepts three forms:
// 1. Zero-config — auto-discovers fluenti.config.ts in the project rootfluentiVue()
// 2. Path to a specific config filefluentiVue({ config: './i18n/fluenti.config.ts' })
// 3. Inline config object (merged with built-in defaults)fluentiVue({ config: { sourceLocale: 'en', locales: ['en', 'ja', 'zh-CN'], splitting: 'dynamic', },})Config fields reference
Section titled “Config fields reference”When passing an inline config object, the following FluentiBuildConfig fields are accepted. All are optional — defaults come from fluenti.config.ts or the built-in defaults shown below.
| Option | Type | Default | Description |
|---|---|---|---|
sourceLocale | string | 'en' | Source locale used for descriptor fallback and build transforms |
locales | LocaleDefinition[] | ['en'] | Locale codes or objects (string | { code, name?, iso?, dir?, domain? }) available to the build |
format | 'json' | 'po' | 'po' | Catalog file format |
compileOutDir | string | './src/locales/compiled' | Directory where compiled catalog files are read from |
splitting | 'dynamic' | 'static' | false | false | Build-time locale chunking strategy (see Code splitting below) |
defaultBuildLocale | string | sourceLocale | Locale inlined for 'static' builds |
include | string[] | ['./src/**/*.{vue,tsx,jsx,ts,js}'] | Source file patterns for dev watcher |
exclude | string[] | ['**/*.test.*', '**/*.spec.*', '**/__tests__/**', '**/*.d.ts'] | File patterns excluded from dev watcher |
devAutoCompile | boolean | true | Auto extract + compile on file change during dev |
buildAutoCompile | boolean | true | Auto extract + compile before production build |
devAutoCompileDelay | number | 300 | Debounce delay (ms) for dev auto-compile |
parallelCompile | boolean | false | Use worker threads for compilation (effective with 5+ locales) |
catalogExtension | string | '.js' | File extension for compiled catalog files (e.g. '.mjs', '.json') |
idGenerator | (message: string, context?: string) => string | built-in FNV-1a | Custom message ID generator. Replaces the default hash-based ID strategy |
onBeforeCompile | () => boolean | void | Promise<...> | — | Hook called before auto-compile. Return false to skip compilation |
onAfterCompile | () => void | Promise<void> | — | Hook called after auto-compile completes |
Sub-plugin pipeline
Section titled “Sub-plugin pipeline”The plugin factory produces six ordered sub-plugins. The order is significant — each plugin depends on the output of the previous stage.
| # | Plugin name | Enforce | Purpose |
|---|---|---|---|
| 1 | fluenti:virtual | — | Resolves virtual:fluenti/* module IDs. Must be first. |
| 2 | fluenti:vue-template | pre | (Vue only) Pre-transforms v-t, <Trans>, <Plural> in SFC templates |
| 3 | fluenti:script-transform | pre | AST scope-aware rewrite of t` ` tagged templates and <Trans> JSX optimization |
| 4 | fluenti:build-compile | — | Triggers extract + compile before production builds (buildStart hook) |
| 5 | fluenti:build-split | — | Rewrites t() calls to catalog references (dynamic/static) |
| 6 | fluenti:dev | — | File watcher + HMR for catalog changes during dev |
Code splitting strategies
Section titled “Code splitting strategies”The splitting option controls how translated messages are bundled for production.
false (default) — no splitting
Section titled “false (default) — no splitting”All compiled catalogs are bundled together. Every locale’s messages ship in the initial bundle. Simplest setup, suitable for apps with few locales or small catalogs.
fluentiVue({ config: { splitting: false },})No virtual modules are generated. Your app imports compiled catalogs directly from compileOutDir.
'dynamic' — lazy locale loading
Section titled “'dynamic' — lazy locale loading”Each locale is a separate chunk loaded on demand. Only the active locale is fetched; switching locales triggers a dynamic import().
fluentiVue({ config: { splitting: 'dynamic', locales: ['en', 'ja', 'zh-CN'], },})At build time, the plugin:
- Rewrites
t('message')calls to__catalog["<hash>"]references - Injects
import { __catalog } from 'virtual:fluenti/runtime'at the top of each transformed module - Generates
virtual:fluenti/runtime— a reactive module that exports__catalog,switchLocale(), andpreloadLocale() - Each locale’s catalog becomes a lazy chunk loaded via
import()
This is the recommended strategy for multi-locale SPAs.
'static' — single locale inlined
Section titled “'static' — single locale inlined”A single locale is inlined at build time. Other locales are stripped. Useful for pre-rendered or server-side builds that serve one locale per deployment.
fluentiVue({ config: { splitting: 'static', defaultBuildLocale: 'ja', // defaults to sourceLocale if omitted },})At build time, the plugin:
- Rewrites
t('message')calls to direct named imports (e.g._a1b2c3) - Injects
import { _a1b2c3, ... } from 'virtual:fluenti/messages' - Generates
virtual:fluenti/messages— a re-export from the compiled catalog ofdefaultBuildLocale
This produces the smallest bundle for a single locale but requires a separate build per locale.
Virtual module system
Section titled “Virtual module system”When splitting is enabled, the plugin serves generated code through Vite’s virtual module convention. These modules are implementation details — app code should use the framework runtime APIs (setLocale(), preloadLocale(), etc.) rather than importing virtual modules directly.
| Virtual module | Splitting mode | Contents |
|---|---|---|
virtual:fluenti/runtime | 'dynamic' | Reactive catalog object, switchLocale(), preloadLocale(), lazy import() loaders per locale |
virtual:fluenti/messages | 'static' | Re-export of all named exports from the defaultBuildLocale compiled catalog |
virtual:fluenti/messages/<locale> | any | Re-export of a specific locale’s compiled catalog |
Virtual modules are resolved with the \0 prefix (Vite convention) so they are excluded from the file system and from other plugins’ transforms.
TypeScript support
Section titled “TypeScript support”If your editor shows errors on virtual module imports, add a type declaration:
declare module 'virtual:fluenti/runtime' { export const __catalog: Record<string, (...args: unknown[]) => string> export function switchLocale(locale: string): Promise<void> export function preloadLocale(locale: string): Promise<void>}
declare module 'virtual:fluenti/messages' { const messages: Record<string, (...args: unknown[]) => string> export = messages}Script transforms
Section titled “Script transforms”The fluenti:script-transform plugin uses AST scope analysis to detect Fluenti’s t import and rewrite tagged templates at build time. Other variables named t are left untouched.
Compile-time t tagged template
Section titled “Compile-time t tagged template”// Source (your code)import { t } from '@fluenti/react'const greeting = t`Hello ${name}`// Build output (after transform)const { t } = useI18n()const greeting = t('Hello {name}', { name: name })Supported import sources:
import { t } from '@fluenti/vue'import { t } from '@fluenti/react'import { t } from '@fluenti/solid'
Supported call forms:
t`Hello ${name}`— tagged template with interpolationt({ message: 'Hello {name}', context: 'hero' }, { name })— descriptor object formt('literal string')— plain string (passed through for runtime lookup)
<Trans> compile-time optimization
Section titled “<Trans> compile-time optimization”For JSX files (.tsx, .jsx), the plugin statically extracts the message string from <Trans> children, pre-computes the message ID, and injects __id, __message, and __components props. This eliminates the runtime cost of extractMessage() and hashMessage() without changing the component API.
// Source<Trans>Hello <b>world</b></Trans>
// After transform — __id, __message, __components injected<Trans __id="a1b2c3" __message="Hello <0>world</0>" __components={[({ children }) => <b>{children}</b>]}> Hello <b>world</b></Trans>Dev mode vs build mode behavior
Section titled “Dev mode vs build mode behavior”The plugin adapts its behavior based on whether Vite is running serve (dev) or build (production).
Dev mode (vite dev)
Section titled “Dev mode (vite dev)”| Feature | Behavior |
|---|---|
| Auto extract + compile | On startup, runs extract then compile. On file changes matching include patterns, re-runs with debounce (devAutoCompileDelay ms). Disable with devAutoCompile: false. |
| HMR | When compiled catalog files change in compileOutDir, the plugin invalidates all virtual:fluenti/* modules, triggering a hot update. |
| Script transforms | t` ` and <Trans> are transformed on every request (standard Vite transform pipeline). |
| Code splitting | Virtual modules are served but splitting transforms are not applied — all catalogs are available for instant locale switching. |
Build mode (vite build)
Section titled “Build mode (vite build)”| Feature | Behavior |
|---|---|
| Auto compile | Before buildStart, runs compile (extract is skipped — source files have not changed). Disable with buildAutoCompile: false. |
| Compile hooks | onBeforeCompile runs before compilation. Return false to skip. onAfterCompile runs after successful compilation. |
| Script transforms | Same as dev. |
| Code splitting | fluenti:build-split rewrites t() calls to catalog references for dynamic/static splitting. |
| Parallel compile | When parallelCompile: true, compilation uses worker threads (effective with 5+ locales). |
Compile lifecycle hooks
Section titled “Compile lifecycle hooks”Use onBeforeCompile and onAfterCompile to integrate with external tooling:
fluentiReact({ config: { onBeforeCompile: async () => { // Pull latest translations from a TMS await fetchTranslationsFromCrowdin() // Return false to skip compilation // return false }, onAfterCompile: async () => { // Notify CI, run validation, etc. console.log('Catalogs compiled successfully') }, },})Custom ID generator
Section titled “Custom ID generator”By default, Fluenti generates message IDs using FNV-1a hashing. You can replace this with a custom function:
fluentiVue({ config: { idGenerator: (message, context) => { // Your custom ID logic const key = context ? `${context}::${message}` : message return myCustomHash(key) }, },})The idGenerator is used everywhere IDs are computed: extraction, compilation, and build-time code splitting transforms.
Troubleshooting
Section titled “Troubleshooting””Cannot find module ‘virtual:fluenti/runtime’”
Section titled “”Cannot find module ‘virtual:fluenti/runtime’””This error appears when splitting is false (default) but your code imports from the virtual module. Virtual modules are only generated when splitting is set to 'dynamic' or 'static'. Either enable splitting or import compiled catalogs directly.
HMR not picking up translation changes
Section titled “HMR not picking up translation changes”- Verify
devAutoCompileistrue(the default). - Check that the changed source file matches the
includepatterns and is not excluded. - Look for
[fluenti] Extracting and compiling... donein the terminal. If it does not appear, the watcher is not triggering. - Ensure
compileOutDirmatches the actual output directory of your compiled catalogs.
Build fails with “CLI not found”
Section titled “Build fails with “CLI not found””The auto-compile feature requires @fluenti/cli as a dev dependency. Install it:
pnpm add -D @fluenti/cliIf you manage compilation externally (e.g., in a CI step), disable auto-compile:
fluentiReact({ config: { devAutoCompile: false, buildAutoCompile: false, },})Plugin order causes transform issues
Section titled “Plugin order causes transform issues”Plugin ordering relative to the framework plugin matters:
- Vue:
vue()first, thenfluentiVue()— Vue’s SFC compiler must process.vuefiles before Fluenti’s template transform runs. - React:
fluentiReact()first, thenreact()— Fluenti’s JSX transforms must run before React’s JSX compilation. - Solid:
solidPlugin()first, thenfluentiSolid().
t variable name collisions
Section titled “t variable name collisions”The scope transform only rewrites t when it is imported from a @fluenti/* package or destructured from useI18n(). Other variables named t (e.g., from a test library) are left untouched. If you still see unexpected rewrites, check that you do not have a conflicting import { t } from '...' statement.
Large catalogs slow down dev startup
Section titled “Large catalogs slow down dev startup”Enable parallelCompile for projects with 5+ locales:
fluentiVue({ config: { parallelCompile: true, devAutoCompileDelay: 500, // increase debounce for heavy catalogs },})