# @comvi/core/plugins

API reference for @comvi/core/plugins: plugins(), attachPlugins, definePlugin(), ensureInstallable, acquirePluginsApi and the members the plugin capability adds.

Canonical: https://comvi.io/docs/i18n/api/core-plugins

The plugin capability queues plugins for `init()` to run. Compose it with `.use(plugins())` on a
[`@comvi/core`](/docs/i18n/api/core) instance.

## Exports

- Values: `plugins`, `attachPlugins`, `definePlugin`, `ensureInstallable`, `acquirePluginsApi`.
- Types: `AcquiredPluginsApi`, `I18nPluginApi`, `PluginI18nInstance`, `I18nPlugin`,
`I18nPluginFactory`, `PluginOptions`.

## `plugins()`

```ts
function plugins(): <T extends I18n<any>>(i18n: T) => T & I18nPluginApi
function attachPlugins<T extends I18n<any>>(i18n: T): T & I18nPluginApi
```

`plugins()` takes no configuration, so it is `attachPlugins` in installer form. Attaching is
idempotent and keeps every registered plugin.

`src/plugins-instance.ts`

```ts

export const i18n = createI18n({ locale: 'en' }).use(plugins());

i18n.registerPlugin(inContextEditorPlugin(), { required: false, timeout: 5000 });
```

## `definePlugin(fn, options?)`

```ts
function definePlugin(fn: I18nPlugin, options?: PluginOptions): <T extends I18n<any>>(instance: T) => T & I18nPluginApi
```

`attachPlugins(i18n).registerPlugin(fn, options)` as an installer, so an inline plugin fits in a
`.use()` chain. A plugin package's own installer (`fetchLoader()`, `localeDetector()`) goes
straight into `.use()` without it.

`src/define-plugin.ts`

```ts

export const i18n = createI18n({ locale: 'en' }).use(
definePlugin((i18n) => {
    const off = i18n.on('localeChanged', ({ to }) => console.log(to));
    return off;
}),
);
```

## `ensureInstallable(i18n, installer)`

```ts
function ensureInstallable<T>(i18n: T, installer: string): T
```

The guard an installer calls first. During plugin initialization it throws
`E_INSTALLER_AS_PLUGIN`, naming `installer`, so `registerPlugin(fetchLoader())` fails before
anything attaches. Outside plugin initialization it returns the instance untouched.

## `acquirePluginsApi(i18n)`

```ts
function acquirePluginsApi(instance: WrapperI18nInstance): AcquiredPluginsApi

interface AcquiredPluginsApi {
onMissingKey(cb: (key: string, locale: string, namespace: string) => TranslationResult | void): () => void;
}
```

The component-facing slice behind every binding's `useI18nPlugins()`. It throws
`missingCapability('plugins')`, in development and production, when the instance has no plugin
capability. Cached per instance.

## `I18nPluginApi`

The members the capability adds to the i18n instance.

```ts
registerPlugin(plugin: I18nPlugin, options?: PluginOptions): this
registerLocaleDetector(detector: () => string | Promise<string>): void
getLanguageDetector(): (() => string | Promise<string>) | undefined
onMissingKey(cb: (key: string, locale: string, namespace: string) => TranslationResult | void): () => void
registerPostProcessor(fn: PostProcessFn): void
setPluginData(key: string, data: unknown): void
getPluginData<T = unknown>(key: string): T | undefined
```

| Member | Meaning |
| --- | --- |
| `registerPlugin(plugin, options?)` | Queues a plugin; chainable. Call before `init()`, which drains the queue once. An installer passed here fails at `init()` with `E_INSTALLER_AS_PLUGIN` |
| `registerLocaleDetector(fn)` | Consulted by `init()` after the plugins ran; a non-function throws |
| `getLanguageDetector()` | The registered detector, or `undefined` |
| `onMissingKey(cb)` | Runtime miss callback; the first defined return wins. Returns an unsubscribe. Separate from the constructor option |
| `registerPostProcessor(fn)` | Post-processors chain in registration order |
| `setPluginData(key, data)` | Instance-lifetime scratch space for plugins |
| `getPluginData<T>(key)` | The stored data, or `undefined` |

### `PluginOptions`

| Option | Type | Default | Meaning |
| --- | --- | --- | --- |
| `required` | `boolean` | `true` | A required plugin's failure throws out of `init()`; an optional one's is reported |
| `timeout` | `number` | `10000` | Milliseconds allowed for the plugin's initialization |
| `onError` | `(error: Error) => void` | none | Called for this plugin's error, which also reaches the instance's `onError` with source `'plugin'` |

Plugins run in order during `init()`, each in a timeout race. A plugin returning anything other
than `undefined` or a cleanup function fails with `E_PLUGIN_INIT_RETURN`. Cleanups run in
reverse order on `destroy()`. The `E_*` strings are the production messages; development builds
throw a longer one.

### Plugin types

```ts
type I18nPlugin = (i18n: PluginI18nInstance) => void | Promise<void> | PluginCleanup | Promise<PluginCleanup>
type I18nPluginFactory<T = unknown> = (options?: T) => I18nPlugin
type PluginI18nInstance<D> = WrapperI18nInstance<D> & I18nLoaderApi & I18nPluginApi
```

`PluginCleanup` (`() => void | Promise<void>`) is not exported. A plugin that registers a
loader needs `loader()` or `attachLoader` composed on the instance as well as `plugins()`.

## Read next

- [`@comvi/core`](/docs/i18n/api/core): the i18n instance this capability composes onto.
- [Write a plugin](/docs/i18n/plugins/custom-plugins): writing an installer, and what a plugin may do at `init()`.
- [Plugins](/docs/i18n/plugins): every shipped plugin and the installer each one exports.
