---
title: Plugin Localization SDK
description: Permission-gated host locale, localized text, and scoped Domain Lexicon APIs for plugins
---

# Plugin Localization SDK

## Overview

The Localization SDK is the supported plugin contract for reading the host locale, resolving localized values, creating transport-safe i18n messages, and using the host Domain Lexicon.

It is available to plugins with `sdkapi >= 260713` through:

- Main/runtime context: `context.utils.i18n` and `context.utils.lexicon`
- Mirrored main/runtime context: `context.utils.plugin.i18n` and `context.utils.plugin.lexicon`
- Plugin renderer: `usePluginI18n()` and `usePluginLexicon()` from `@talex-touch/utils/plugin/sdk`

The current host locales are `en-US` and `zh-CN`.

## Manifest requirements

Declare only the permissions used by the plugin:

:::TuffCodeBlock{lang="json"}
---
code: |
  {
  "sdkapi": 260713,
  "permissions": {
  "required": ["i18n.read", "lexicon.read"],
  "optional": ["lexicon.register"]
  },
  "permissionReasons": {
  "i18n.read": "Resolve plugin labels using the host locale",
  "lexicon.read": "Search the host Domain Lexicon",
  "lexicon.register": "Register plugin-scoped aliases while the plugin is enabled"
  }
  }
---
:::

| Permission         | Required by                    |
| ------------------ | ------------------------------ |
| `i18n.read`        | `getLocale()`, `resolveText()` |
| `lexicon.read`     | `resolve()`, `search()`        |
| `lexicon.register` | `register()`                   |

The host checks the SDK marker, manifest declaration, current grant, loaded plugin, and verified plugin identity. Missing state fails closed before locale or lexicon services run.

`createMessage()` is a pure string constructor and does not read host state. It rejects an empty message key.

## Runtime usage

:::TuffCodeBlock{lang="typescript"}
---
code: |
  export default {
  async onInit(context) {
  const { i18n, lexicon } = context.utils

        const locale = await i18n.getLocale()
        const title = await i18n.resolveText(
          {
            default: 'Unit Converter',
            locales: { 'zh-CN': '单位换算' }
          },
          locale
        )
        const message = i18n.createMessage('plugin.ready', { title })

        const meter = await lexicon.resolve('unit.length.meter', {
          locale,
          domain: 'unit'
        })
        const matches = await lexicon.search('meter', {
          locale,
          domain: 'unit',
          limit: 5
        })

        context.utils.logger.info(
          `${message}:${meter?.label ?? 'missing'}:${matches.length}`
        )
      }

  ## }
---
:::

## Renderer usage

:::TuffCodeBlock{lang="typescript"}
---
code: |
  import {
  usePluginI18n,
  usePluginLexicon
  } from '@talex-touch/utils/plugin/sdk'

  const i18n = usePluginI18n()
  const lexicon = usePluginLexicon()

  const locale = await i18n.getLocale()
  const label = await i18n.resolveText({
  default: 'Ready',
  locales: { 'zh-CN': '就绪' }
  })
  const capabilities = await lexicon.search('ready', {
  locale,
  domain: 'capability'
  })
---
:::

Renderer hooks require an active plugin renderer channel. Do not call them from a normal application renderer or before the plugin channel is ready.

## API reference

### I18n

| Method                        | Result                        | Notes                                                                                                |
| ----------------------------- | ----------------------------- | ---------------------------------------------------------------------------------------------------- |
| `getLocale()`                 | `Promise<'en-US' \| 'zh-CN'>` | Reads the current host locale.                                                                       |
| `resolveText(value, locale?)` | `Promise<string>`             | Resolves a string or `{ default, locales }` value. The host locale is used when `locale` is omitted. |
| `createMessage(key, params?)` | `string`                      | Creates a `$i18n:` transport message without a host call.                                            |

### Domain Lexicon

| Method                        | Result                                        | Notes                                                                                             |
| ----------------------------- | --------------------------------------------- | ------------------------------------------------------------------------------------------------- |
| `resolve(id, options?)`       | `Promise<ResolvedDomainLexiconEntry \| null>` | Resolves an official entry or an entry owned by the calling plugin.                               |
| `search(query, options?)`     | `Promise<DomainLexiconMatch[]>`               | Searches official and caller-owned entries. Supports `locale`, `domain`, and `limit`.             |
| `register(entries, options?)` | `Promise<PluginLexiconRegisterResult>`        | Atomically registers plugin-local entries. `replace: true` replaces the caller's current overlay. |

Supported domains are `unit`, `currency`, `timezone`, `capability`, `fileType`, and `systemAction`.

## Register plugin-scoped entries

:::TuffCodeBlock{lang="typescript"}
---
code: |
  const result = await context.utils.lexicon.register(
  [
  {
  id: 'status.ready',
  domain: 'capability',
  version: '1',
  labels: {
  default: 'Ready',
  locales: { 'zh-CN': '就绪' }
  },
  aliases: {
  default: ['ready'],
  locales: { 'zh-CN': ['就绪'] }
  }
  }
  ],
  { replace: false }
  )

  // The host assigns plugin:<pluginId>:status.ready.
  console.log(result.ids[0])
---
:::

Registration boundaries:

- IDs supplied by a plugin are local IDs. A plugin cannot choose another plugin namespace.
- The host projects `status.ready` to `plugin:<pluginId>:status.ready` and sets `source=plugin:<pluginId>`.
- Official IDs cannot be overridden, and one plugin cannot resolve or search another plugin's overlay.
- Each plugin can hold at most 100 entries. One request can register at most 50 entries and 256 KiB.
- A batch is fully validated before it is committed.
- Plugin overlays are in memory only. They are removed when the plugin is disabled or unloaded and are not written to SQLite, Catalog, or sync payloads.

## Errors and recovery

Permission, SDK, identity, and payload failures are explicit transport errors. Handle them as unavailable capability states rather than returning a fake localized value or empty successful result.

Common codes include:

- `PLUGIN_I18N_PERMISSION_UNAVAILABLE`
- `PLUGIN_I18N_PERMISSION_DENIED`
- `PLUGIN_LEXICON_PERMISSION_UNAVAILABLE`
- `PLUGIN_LEXICON_PERMISSION_DENIED`
- `PLUGIN_LOCALIZATION_SDK_UNSUPPORTED`
- `PLUGIN_LOCALIZATION_INVALID_REQUEST`
- `PLUGIN_LOCALIZATION_PLUGIN_UNAVAILABLE`

## Do not use host internals as a plugin API

`i18nResolver.addMessages()` and direct imports from the host locale registry are application-internal mechanisms. They do not provide plugin identity, permission checks, namespace isolation, or lifecycle cleanup. Plugins must use the Localization SDK described above.

## Related documentation

- [Plugin Context](./plugin-context.en.mdc)
- [Permission SDK](./permission.en.mdc)
- [Manifest Reference](../reference/manifest.en.mdc)
- [Intelligence SDK](./intelligence.en.mdc)
