Plugin System
A plugin is a function that receives the i18n instance and wires extra behaviour into it — a loader, a locale detector, a translation post-processor, an event listener, or any combination. The official plugins (FetchLoader, LocaleDetector, InContextEditorPlugin) are built on the same contract you’d use for your own. If your translations live as local JSON files and you want TypeScript autocomplete without the TMS, see @comvi/vite-plugin (generates types at build/dev time from local JSON, no API key needed).
Quick Example
Section titled “Quick Example”import { createI18n } from "@comvi/core";import type { I18nPluginFactory } from "@comvi/core";
const Logger: I18nPluginFactory<{ prefix?: string }> = (options = {}) => function LoggerPlugin(i18n) { const prefix = options.prefix ?? "[i18n]"; const unsubscribe = i18n.on("localeChanged", ({ from, to }) => { console.log(`${prefix} ${from} → ${to}`); });
return unsubscribe; // cleanup runs on i18n.destroy() };
const i18n = createI18n({ locale: "en" }) .use(Logger({ prefix: "[app]" }));
await i18n.init();Most configurable plugins use two layers: a factory that takes options and returns a plugin function. The plugin function runs during init() and may return a cleanup function that runs on destroy().
Lifecycle
Section titled “Lifecycle”use() only queues the plugin — nothing runs until init().
- Plugin functions run in registration order (FIFO). Each gets its own timeout.
- After all plugins, the registered locale detector (if any) runs and may switch the locale.
- Initial namespaces are loaded last, using whatever loader the plugins registered.
- On
destroy(), cleanup functions run in reverse order (LIFO) and are awaited.
Registration Options
Section titled “Registration Options”i18n.use(plugin, options) accepts an optional second argument:
| Option | Default | Behaviour |
|---|---|---|
required | true | A throw or timeout from the plugin function aborts init(). Set to false to report the error and continue. |
timeout | 10000 ms | Per-plugin-function budget. Exceeding it rejects with a timeout error. |
onError | — | Called before the default error pipeline if the plugin function fails. Throws inside onError are swallowed. |
i18n.use(MyPlugin({ apiKey: "..." }), { required: false, timeout: 5000, onError: (err) => analytics.captureException(err),});A factory that throws synchronously (e.g. on invalid options) fails before use() even sees a plugin function — PluginOptions do not apply to that path.