Skip to content

Vite Plugin

Verified against @comvi/vite-plugin@0.3.0

@comvi/vite-plugin watches your local translation JSON files during vite dev and generates a TypeScript declaration that types every t() call in your project. It runs fully in-process — no API key, no network, no external service.

Use @comvi/vite-plugin when your translations live as local JSON files and you want autocomplete and parameter validation in your editor.

Use @comvi/cli typegen when your translations live in the Comvi TMS and you want to sync them to your repo or generate types from the live platform schema. See the CLI typegen docs.

Terminal window
npm install -D @comvi/vite-plugin
# pnpm
pnpm add -D @comvi/vite-plugin

Peer dependency: vite ^5 || ^6 || ^7 || ^8.

vite.config.ts
import { defineConfig } from "vite";
import { comviTypes } from "@comvi/vite-plugin";
export default defineConfig({
plugins: [
comviTypes({
translations: "./src/locales",
output: "./src/types/i18n.d.ts",
}),
],
});

The plugin runs once on buildStart and re-runs whenever you save a JSON file inside ./src/locales. The generated declaration is written to ./src/types/i18n.d.ts — make sure that path is covered by tsconfig.json’s include (most src/**/* setups already cover it).

interface ComviTypesOptions {
translations: string;
output?: string;
fileTemplate?: string;
defaultNs?: string;
strictParams?: boolean;
}

Required. Path to the directory containing your translation JSON files, relative to the project root.

comviTypes({ translations: "./src/locales" })

Path where the generated .d.ts file is written. Defaults to "./src/types/i18n.d.ts".

Pattern for matching translation files. Use {languageTag} and {namespace} placeholders.

// Default: one file per namespace per language
// matches: common/en.json, admin/de.json, etc.
comviTypes({
translations: "./src/locales",
fileTemplate: "{namespace}/{languageTag}.json",
})

For flat single-file-per-language layouts (en.json, de.json), omit fileTemplate — unmatched files are treated as the default namespace.

The namespace whose keys appear without a prefix in generated types. Defaults to "default". Set this to match your createI18n({ defaultNs }) value.

comviTypes({
translations: "./src/locales",
defaultNs: "common", // keys from common.json → 'welcome', not 'common:welcome'
})

Whether interpolation parameters are required (true) or optional (false). Defaults to true.

Given this file layout:

src/locales/
├── en/
│ ├── common.json
│ └── errors.json
└── de/
├── common.json
└── errors.json

With en/common.json:

{
"welcome": "Hello, {name}!",
"items": "{count, plural, one {# item} other {# items}}",
"greeting": "Hi"
}

And vite.config.ts:

comviTypes({
translations: "./src/locales",
defaultNs: "common",
})

The plugin writes:

// src/types/i18n.d.ts — DO NOT EDIT
import '@comvi/core';
declare module '@comvi/core' {
interface TranslationKeys {
'welcome': { name: string };
'items': { count: number };
'greeting': never;
'errors:NOT_FOUND': never;
}
}
export {};

Every t() call is now strictly typed:

t("welcome", { name: "Alice" }); // ✓ required param, type-checked
t("greeting"); // ✓ no params needed
t("NOT_FOUND", { ns: "errors" }); // ✓ namespaced key
t("welcome"); // ✗ Error: name is required
t("welcome", { name: 42 }); // ✗ Error: number not assignable to string
t("typo"); // ✗ Error: unknown key

The generated file augments @comvi/core’s TranslationKeys interface via TypeScript declaration merging. No runtime code is shipped — the plugin only writes a .d.ts file. For a full explanation of how type safety works across all methods and frameworks, see Type Safety.

ModeBehaviour
vite devGenerates on startup, then re-generates on every JSON add/change/delete inside translations. No server restart needed.
vite buildGenerates once before bundling. A generation error fails the build (unlike dev, which only logs).
  • Type Safety — how TranslationKeys augmentation works, InferKeys, and manual type approaches
  • CLI typegen — generate types from the Comvi TMS (remote schema, requires API key)