vite-plugin-build-time-i18n
v0.1.1
Published
Vite plugin for build-time i18n with static translation replacement and ICU message precompilation.
Downloads
205
Maintainers
Readme
vite-plugin-build-time-i18n
Build-time i18n for Vite. This plugin replaces string-literal translation calls during build so your app ships translated output instead of doing key lookup at runtime.
It is designed for projects that want:
- static replacement for simple messages
- precompiled formatting for plural, select, number, date, and time messages
- build-time diagnostics for missing, unused, or non-precompilable translation keys
- zero runtime translation catalog lookup in application code
License: LICENCE
Why use it
Instead of shipping a message catalog and resolving keys in the browser, this plugin rewrites calls such as:
const title = t("app.page.title");
const countLabel = t("app.page.priorityCount", { count: 2 });into either:
- a plain string literal for static messages
- a generated formatter call for messages that need interpolation or ICU-style branching
That keeps translated output close to the final bundle and catches catalog problems during the build.
Requirements
- Node.js 25+
- Vite 8+
Install
npm install vite-plugin-build-time-i18nvite is a peer dependency and must already exist in the consuming project.
Quick start
// vite.config.ts
import { defineConfig } from "vite";
import { buildTimeI18nPlugin } from "vite-plugin-build-time-i18n";
export default defineConfig({
plugins: [
...buildTimeI18nPlugin({
locale: "de",
localesDir: "src/i18n/locales",
}),
],
});// src/i18n/locales/de.json
{
"app": {
"page": {
"title": "Startseite",
"priorityCount": "{count, plural, one {# Prioritaet} other {# Prioritaeten}}"
}
}
}// application code
function t(key: string, values?: Record<string, unknown>) {
return key;
}
const title = t("app.page.title");
const countLabel = t("app.page.priorityCount", { count: 2 });Build output shape:
const title = "Startseite";
import { __i18nFormat } from "virtual:build-time-i18n-helper";
const countLabel = __i18nFormat(
{
type: "message",
parts: [
/* compiled parts */
],
},
{ count: 2 },
"de",
);How it works
During build, the plugin:
- reads
<localesDir>/<locale>.json - flattens nested message objects into dotted keys
- precompiles supported message syntax
- scans matching source files for direct calls to the configured translation function
- rewrites supported calls in the final bundle
Locale file format
Locale files must be top-level JSON objects. Nested objects are flattened into dotted keys.
{
"app": {
"route": {
"modeLabel": "Routenmodus"
},
"stats": {
"participants": "Teilnehmende: {count, number, compact}"
}
}
}This becomes:
app.route.modeLabelapp.stats.participants
Message values must be either strings or nested objects.
Options
type BuildTimeI18nPluginOptions = {
locale: string;
localesDir?: string;
include?: RegExp;
functionName?: string;
strictMissing?: boolean;
failOnDynamicKeys?: boolean;
includeEnvironmentLabelInWarnings?: boolean;
};locale
Active locale code. The plugin reads <localesDir>/<locale>.json.
localesDir
Directory containing locale JSON files.
Default candidates (first existing wins, resolved from process.cwd()):
<projectRoot>/locales<projectRoot>/i18n/locales
include
Regular expression used to choose which files run through the transform hook.
Default:
/\.[cm]?[jt]sx?$/;functionName
Identifier name to rewrite.
Default: "t"
Only direct identifier calls are rewritten:
t("app.page.title");These are not rewritten:
i18n.t("app.page.title");
translations[fn]("app.page.title");strictMissing
Controls how missing keys are handled.
true(default): fail the buildfalse: warn and replace with the key string
failOnDynamicKeys
Controls how non-literal translation keys are handled.
true(default): fail the buildfalse: warn and leave the call non-precompiled
includeEnvironmentLabelInWarnings
Controls whether diagnostics include the current Vite environment label.
true(default): include labels such as[client],[ssr], or[unknown]false: use plain[build-time-i18n]warnings without environment labels
Supported message syntax
This plugin supports a focused subset of ICU-style message formatting.
Variables
{name}Numbers
{amount, number}
{amount, number, integer}
{amount, number, percent}
{amount, number, compact}
{amount, number, currency:EUR}Dates and times
{when, date}
{when, date, short}
{when, date, medium}
{when, date, long}
{when, date, full}
{when, time}
{when, time, short}
{when, time, medium}
{when, time, long}
{when, time, full}Select
{status, select, open {Open} closed {Closed} other {Unknown}}Plural
{count, plural, =0 {No items} one {# item} other {# items}}Rules:
pluralandselectmust includeother#is only meaningful inside plural branches- invalid styles fail during catalog precompile
Diagnostics
The plugin reports diagnostics with the prefix [build-time-i18n].
By default, warnings include an environment label, for example:
[build-time-i18n] [client] ...[build-time-i18n] [ssr] ...
Unused-key diagnostics are environment-scoped. A key may be used in one environment and unused in another.
When locale JSON cannot be parsed, errors include the file path and parser details.
It can report:
- missing translation keys
- unused translation keys
- dynamic translation calls that cannot be precompiled
- invalid message syntax or unsupported formatting styles
Caveats
- This is not a full ICU MessageFormat implementation.
- Only direct calls to the configured function name are rewritten.
- The first argument must be a string literal to be precompiled.
- The plugin applies only to Vite build mode.
- Locale files must be valid JSON and must contain a top-level object.
Authoring Guardrails For AI Agents
This repository includes agent guidance and a validator to keep translation usage compatible with build-time precompilation.
- Skill:
.github/skills/build-time-i18n-authoring/SKILL.md - Instruction profile:
.github/instructions/build-time-i18n-authoring.instructions.md - Validator command:
npm run validate:i18n(runs with--allow-missing-localesin this repository)
The validator checks:
- locale JSON parse and shape issues
- precompile compatibility using plugin build-time checks
- key parity across locale files
- placeholder mismatch warnings across locales
- dynamic translation key calls in non-test source files
- member/computed translation calls in non-test source files that are not rewritten
Compared to established runtime translators, this plugin intentionally trades runtime flexibility for build-time replacement and diagnostics. In practice, dynamic key resolution and runtime locale behavior are more limited.
Advanced
When a message needs runtime formatting, the plugin injects a virtual helper import:
import { __i18nFormat } from "virtual:build-time-i18n-helper";That helper uses native Intl.PluralRules, Intl.NumberFormat, and
Intl.DateTimeFormat under the hood.
Development
npm install
npm run typecheck
npm run validate:i18n
npm test