# x18n

> Check, browse, edit and translate the catalogs with x18n's CLI, devtools and AI translation, and export them as Markdown.

glosa owns two things: the extraction and a small runtime. Everything else you do with messages, [x18n](https://github.com/x18n-dev/x18n) does. The catalogs are flat JSON locale files, which x18n reads as they are. `glosa/x18n` adds what x18n can't guess:

- IDs are hashes, so x18n finds where each message is used by running glosa's extraction over your components.
- Placeholders are `{name}`, `<0>…</0>` and `<0/>`, so x18n flags a translation that loses one.

## Set it up

```ts [x18n.config.ts]
import glosa from "glosa/x18n";

export default { presets: [], plugins: [glosa()] };
```

`presets: []` turns off x18n's framework detection, since the glosa plugin already says where the catalogs are and how to read them. If you changed the Vite plugin's `sourceLocale` or `dir`, pass the same to `glosa()`.

## Check from the terminal

```sh
pnpm add -D @x18n/cli
pnpm x18n check              # missing translations, broken placeholders, ready for CI
pnpm x18n usages 2edbea9ab7  # where a message is used
pnpm x18n ui                 # browse and edit every message in the browser
```

## Edit in the devtools

`@x18n/devtools` puts the same UI in Vite DevTools, next to your app. It lists every message with where it's used, and an edit there writes `locales/<locale>.json`. glosa sends the change to the page, which updates without a reload.

```ts [vite.config.ts]
import { DevTools } from "@vitejs/devtools";
import vue from "@vitejs/plugin-vue";
import { X18nDevtools } from "@x18n/devtools/vite";
import { glosa } from "glosa/vite";
import { defineConfig } from "vite";

export default defineConfig({
  plugins: [glosa(), vue(), DevTools(), X18nDevtools()],
});
```

## Translate with AI

x18n's translators fill in missing messages with a model. Each result is checked against glosa's placeholders before x18n writes it, so a translation that drops a tag never lands.

```sh
pnpm add -D @x18n/translator-ai @ai-sdk/anthropic
```

```ts [x18n.config.ts]
import { anthropic } from "@ai-sdk/anthropic";
import translatorAi from "@x18n/translator-ai";
import glosa from "glosa/x18n";

export default {
  presets: [],
  plugins: [glosa(), translatorAi({ model: anthropic("claude-sonnet-5-5") })],
};
```

```sh
pnpm x18n translate --to es --estimate  # what would be sent
pnpm x18n translate --to es             # translate and write locales/es.json
```

x18n's [machine translation guide](https://x18n.dev/guide/machine-translation) covers the rest: context, budgets and reviewing what a model wrote.

## Export as Markdown

`glosa/markdown` turns a catalog into one Markdown document, for a translator, a model or a review. Each message is a heading and a paragraph, and its tags and placeholders are inline code to move but not edit.

```ts
import { readFileSync } from "node:fs";
import { fromMarkdown, glosaSyntax, prepare, toMarkdown } from "glosa/markdown";

const source = JSON.parse(readFileSync("locales/en.json", "utf8"));
const messages = Object.entries(source).map(([id, text]) => prepare(id, text, glosaSyntax));
const markdown = toMarkdown(messages);
```

```md
## `2edbea9ab7`

Read the `<0>`guide`</0>` before you `<1>`deploy`</1>`.
```

To read a translated document back, pass the export it was made from as `basis`:

```ts
const { messages: results } = fromMarkdown(messages, translated, { locale: "es", basis: markdown });
```

Each result is `translated`, with its `value`; `missing`; `rejected`, when a code span was lost or changed; or `stale`, when the source changed since the export.

## Sitemap

See the full list of pages in [llms.txt](https://glosa.onmax.me/llms.txt).
