# Vite plugin

Generate a TranslationKeys declaration from local translation JSON during vite dev and vite build, with parameter types inferred from ICU syntax.

Canonical: https://comvi.io/docs/i18n/plugins/vite-plugin

With a Vite project, an i18n instance from the [Quick start](/docs/i18n/quick-start) and
translation JSON on disk, let `@comvi/vite-plugin` watch that JSON and write the declaration
that types every `t()` call. It runs in-process with no API key and no network. For catalogs
that live on the Comvi platform, use [comvi typegen](/docs/cli/typegen) instead; both emit the
same file.

## Install
**npm**

```bash
npm install -D @comvi/vite-plugin
```

**pnpm**

```bash
pnpm add -D @comvi/vite-plugin
```

**yarn**

```bash
yarn add -D @comvi/vite-plugin
```

**bun**

```bash
bun add -d @comvi/vite-plugin
```

The peer dependency is `vite` 5, 6, 7 or 8.

## Configure

`vite.config.ts`

```ts

export default defineConfig({
plugins: [
    comviTypes({
      translations: './src/locales',
      output: './src/types/i18n.d.ts',
      defaultNs: 'common',
    }),
],
});
```

The plugin generates once on `buildStart` and again whenever a `.json` file under
`translations` is added, changed or removed. Both paths resolve against the Vite root. Put
`output` inside your `tsconfig.json` `include`; most `src/**/*` setups already cover it.

This is a Vite plugin, not a Comvi plugin: it goes in `vite.config.ts`, never in `.use()`,
and it never touches the i18n instance. Its output is a `.d.ts` that augments the
`TranslationKeys` interface, so the runtime bundle is unchanged and the compiler is what sees
the result.

## Options

| Option | Default | Behaviour |
| --- | --- | --- |
| `translations` | required | Directory holding the translation JSON. |
| `output` | `'./src/types/i18n.d.ts'` | Where the declaration is written; parent directories are created. |
| `fileTemplate` | `'{namespace}/{languageTag}.json'` | Maps a file path to a language and a namespace. A file the template does not match is read as the default namespace, so a root `en.json` works with no configuration. |
| `defaultNs` | `'default'` | Keys from this namespace lose their prefix. Set it to what your runtime uses. |
| `strictParams` | `true` | Parameters required; `false` emits them as optional. |
| `augment` | from `package.json` | The module the declaration augments. |

Keep colons out of key names. A root key such as `"errors:NOT_FOUND"` next to an
`errors/en.json` holding `"NOT_FOUND"` becomes the same emitted key once the default prefix
is stripped, and the emitter throws `Translation key collision after stripping default
namespace …` instead of dropping one.

## What gets generated

`src/locales/en.json`

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

`src/locales/errors/en.json`

```json
{
"NOT_FOUND": "Not found"
}
```

`src/types/i18n.d.ts`

```ts
/**
 * Auto-generated translation keys, augmenting '@comvi/core'
 * DO NOT EDIT — this file is generated from your translation schema
 */

// Import to ensure module is resolved before augmentation

declare module '@comvi/core' {
interface TranslationKeys {
    "errors:NOT_FOUND": never;
    "greeting": never;
    "items": { "count": number };
    "welcome": { "name": string };
}
}

export {};
```

`src/usage.ts`

```ts

i18n.t('welcome', { name: 'Alice' });
i18n.t('greeting');
i18n.t('NOT_FOUND', { ns: 'errors' });
```

`src/usage-wrong.ts`

```ts

i18n.t('welcome'); // 'name' is required
i18n.t('welcome', { name: 42 }); // number is not assignable to string
i18n.t('typo'); // unknown key
```

Keys come out in code-unit order and double-quoted, `never` means no parameters, and nested
JSON flattens to dot keys. Parameter types come from ICU syntax: a bare `{name}` is `string`,
a `plural` or `selectordinal` count is `number`, a `select` variable is `string`. When two
locales disagree, the union is emitted and `number` wins. A message that uses an argument
type `@comvi/core` does not implement, such as `{v, number}`, gets no parameters at all, and
the plugin logs a warning naming the key, the file and the argument type. The declaration
augments `@comvi/core` when `package.json` declares it, otherwise the first framework binding
it declares, and it carries no timestamp, so an unchanged catalog produces no diff.

> **Types and the compiler are two decisions**
>
> A key with a `plural`, such as `items`, needs `compiler: icuCompiler` from `@comvi/core/icu`
> on the i18n instance, or it throws at ingestion in development and renders its braces in
> production. The types say nothing about which compiler you composed. And `{v, number}`,
> `{v, date}` and `{v, time}` are not implemented by any Comvi compiler: each is an
> `E_ICU_SYNTAX` at render, and the plugin warns about them at generation time. Format the value
> in code and interpolate a plain `{v}`. See
> [What ICU does not implement](/docs/i18n/icu-reference#what-icu-does-not-implement).

## Dev and build

| Mode | Behaviour |
| --- | --- |
| `vite dev` | Generates on startup, then on every JSON add, change or delete. No restart needed. |
| `vite build` | Generates once before bundling. A generation error fails the build; in dev it is only logged. |

Unchanged output is not written, so TypeScript does not re-check the project for nothing. The
comparison is against the last content this process emitted, so a deleted or hand-edited file
stays that way until a catalog change or a dev-server restart. Overlapping saves collapse into
one queued re-run. Every failure logs `[@comvi/vite-plugin] Failed to generate types: …`: a
missing `translations` directory (`ENOENT`), a malformed JSON file, a key collision, or a
parameter type outside `string` and `number`. In dev the previous declaration stays on disk,
so the editor keeps the last good types until the JSON parses again.

## Read next

- [Type-check translation keys](/docs/i18n/type-safety): how the augmentation reaches `useI18n()`, and `InferKeys`.
- [comvi typegen](/docs/cli/typegen): the same declarations from the platform schema.
- [Pluralize with ICU](/docs/i18n/pluralization): the compiler that renders what these types describe.
