Esc

glosa

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 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.
import glosa from "glosa/x18n";

export default { presets: [], plugins: [glosa()] };
x18n.config.ts

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().

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

@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.

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()],
});
vite.config.ts

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.

pnpm add -D @x18n/translator-ai @ai-sdk/anthropic
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") })],
};
x18n.config.ts
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 covers the rest: context, budgets and reviewing what a model wrote.

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.

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);
ts
## `2edbea9ab7`

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

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

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

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.