# Locale & Localization

> Several locales in one site, with a primary locale, machine translation that costs no credits, locale-aware delivery and SEO, and the same contract for hosted and headless front ends.

> Markdown version of https://kanorio.com/en/developers/products/localization. Append `.md` to any developer docs URL for this format. Index: https://kanorio.com/llms.txt · MCP: https://app.kanorio.com/api/mcp

Status: **Available**

A site has one primary locale and can add more. Every text field keeps its primary-locale value where it is; translations live in a table beside it, one row per record and locale. Adding a locale translates the whole site in the background and publishes it when done. The hosted site and any front end you build read the same data through the same `locale` parameter.

## Core objects

- **Locale set.** The site's locales with a primary locale, a status (`draft`, `translating`, `published`, `disabled`), a URL prefix for each secondary locale (`en`, `ja`, or a prefix the owner chose) and an auto-translate switch. `GET /api/v1/site` lists them in `locales[]`.
- **Translations.** One row per record and locale, with draft and published field data, its own URL slug (one segment) and a `sourceHash` of the source record at the time it was translated. The hash is how a stale translation is detected and how an external tool avoids overwriting newer source text.
- **Translatable fields.** A registry derived from each content type's schema says which paths can be translated: titles, rich text, section text, SEO fields, product descriptions, long-form details, FAQs and purchase notes, product options and variant descriptions, course units including text lessons and quiz questions (never the answer key), store-wide processing time and purchase notes, forms and legal pages. Collection fields opt in with `localized: true`. Prices, stock, orders, customers and reviews are never translated.
- **URL slugs.** Each locale can have its own slug for pages, posts, categories, tags, authors, products, product categories and collection items. The first machine translation into a locale generates it from the translated title (Latin-script languages get an ASCII slug; other scripts keep the primary slug when it is ASCII), and it never changes on its own after that. Pages translate only their own segment, so a page's path in a locale is built from each level's slug in that locale. Fixed permalink segments such as `/blog` and `/product` are not translated.
- **Translation jobs.** A full-site job runs when a locale is added or a missed batch is retried; later edits to the primary locale are retranslated incrementally. Jobs and single-record translation cost no AI credits; a daily call cap queues work to the next day instead of failing.

## How settings relate

Locale, currency, business market and time zone are four independent settings. None is derived from another, so a site can write in English, sell in euros and run on Tokyo time. A customer's locale (`Contact.locale`) is recorded at checkout, login and form submission and drives the locale of their emails, customer account and certificates.

## How the APIs fit

- **Read with the Content Delivery API.** Every read accepts `locale` (the code or the URL prefix). Untranslated fields fall back to the primary text, and the response says what it served in `resolvedLocale` and which locales have a translation in `availableLocales`. A read by slug accepts any locale's slug and returns `slug` in the requested locale; pages and products add `localePaths` (their path in every live locale) and collection items add `localeSlugs`. A locale the site does not have returns `400 UNSUPPORTED_LOCALE`. `/seo/*`, `/feed` and search follow the same parameter. See [Read content](https://kanorio.com/en/developers/guides/read-content).
- **Manage with the Management API.** `localization.locales.*` adds, updates, reorders, removes and sets the primary locale; `localization.translations.*` lists, reads, writes (`upsert` with `sourceHash`, and `slug` to set or clear a locale's URL slug), publishes and deletes translations and runs one on demand with `translate`; `localization.jobs.*` reads progress and retries. Adding or removing a locale changes every URL of that locale and asks for `confirm: true`. The locale code is the natural key: adding a locale that is already in the set returns `ALREADY_ADDED` and removing one that is gone returns `NOT_FOUND`, so a retried call never creates a second locale and no idempotency key is needed.
- **Listen for webhooks.** `content.translation.published` and `deleted` fire per record and locale; `localization.locale.published`, `unpublished` and `updated` when the locale set changes; `localization.job.completed` and `failed` for full-site jobs. The `cacheTags` on every content event carry `@<locale>`, so a translation going live refreshes only that locale. See [Webhooks](https://kanorio.com/en/developers/webhooks).
- **Route and tag in your front end.** `createKanorioMiddleware({ features: { locales: true } })` redirects the primary locale's prefix to the root, serves `/<prefix>/llms.txt` and `/<prefix>/rss.xml`, matches shared redirects without the prefix and locale redirects (`locale` set) against the full path. `requestLocaleOf`, `buildAlternates` and `languageSwitcher` (pass `localePaths`) give you the locale, canonical, hreflang and a language menu with the same rules as the hosted site. See [Next.js](https://kanorio.com/en/developers/guides/nextjs) and the [SDK reference](https://kanorio.com/en/developers/sdk).
- **Customer-facing text.** The Customer API and `@kanorio/elements` accept `locale`; without it they use the customer's recorded locale. Course and unit titles, order line names and completion certificates follow it. See [Courses and members](https://kanorio.com/en/developers/guides/courses-and-members).
- **Automated emails.** Order, payment, shipping, refund, sign-in and form confirmation emails go out in the customer's recorded locale when the site serves it, otherwise in the primary locale. An email the owner never customized uses the built-in wording, which exists for every site language. A customized email is translated like other content: its translation is the `message_template` record type (the customized template's id), corrected per locale with `localization.translations.*` and live on write. Until a locale has a translation of a customized email, that email goes out in the primary locale. Template variables (the triple-brace placeholders such as the order number) must stay exactly as in the source; a translation that adds a variable is rejected.
- **Export.** The site export includes `localization/locales.json` and `localization/translations.json` next to the primary-locale content.

## Behaviour to rely on

- The primary locale is served at the root; every other locale under its prefix. The primary locale's own code as a prefix redirects to the root with `301`.
- A locale that is still translating does not exist for visitors or the Delivery API. When the job finishes, the locale is published in one step.
- Removing a locale that was live redirects its prefix to the primary locale's pages. Its translations are kept for at least 30 days; adding the locale back reuses them and translates only what is missing. Give it the same prefix to bring the old URLs back.
- A page served in fallback (the locale has no translation of it yet) gets the primary locale's canonical and no hreflang, so search engines never index duplicate text.
- Records with a draft and publish lifecycle (pages, global sections, site settings, posts and collection items) keep edited translations as drafts. Publishing the record puts every locale's draft live; `localization.translations.publish` puts one locale live on its own and needs the record's publish permission (`site:publish`, `blog:publish` or `collections:publish`). Editing a translation never marks the primary locale as changed.
- A URL that uses another locale's slug, or the primary slug where the locale has its own, redirects to the locale's slug. Changing a live slug adds a 301 from the old URL in that locale; clearing it makes the locale follow the primary slug again.
- The primary locale can be changed while the site has one locale. Changing it on a multilingual site rewrites every URL and is not offered yet.
- The same rules apply to the hosted site and to a headless front end; `npx @kanorio/cli doctor` checks that your front end serves each locale under its prefix with the translated title, lists hreflang for every live locale and serves the per-locale `llms.txt`.

## What the APIs expose

| Capability | Status | API | Elements | Hosted site | Webhooks |
| --- | --- | --- | --- | --- | --- |
| Locale set & translations (add a locale, full-site machine translation, review) | Available | Management API `localization.locales.*` (`list`, `add`, `update`, `reorder`, `remove`, `set_primary`), `localization.translations.*` (`list`, `get`, `upsert` with `sourceHash`, `publish`, `delete`, `translate`), `localization.jobs.*`; REST `/sites/:id/locales`, `/sites/:id/translations` | — | `/settings` locale list (status, progress, URL prefix, auto-translate) and the editor's locale switcher for corrections | `content.translation.published` / `deleted`, `localization.locale.published` / `unpublished` / `updated`, `localization.job.completed` / `failed` |
| Locale-aware delivery, SEO files and customer-facing text | Available | Delivery `?locale=` (code or URL prefix) on `/site`, `/pages`, `/globals`, `/collections`, `/catalog`, `/forms`, `/search`, `/seo/*` and `/feed` with `resolvedLocale` / `availableLocales` / `localePaths` (collection items: `localeSlugs`), any locale's slug resolves to the record; `/redirects` rules carry `locale`; Customer API `locale` on deliverables, orders and unit open; SDK `createKanorioMiddleware({ features: { locales } })`, `buildAlternates`, `requestLocaleOf`, `languageSwitcher`, locale-scoped `revalidate`; CLI `doctor` i18n rules | `locale` on `<KanorioProvider>` (forms, login, curriculum and unit load the visitor's locale) | Primary locale at the root, other locales under their prefix (`/en/…`), header language menu, per-locale `llms.txt` / `rss.xml`, one sitemap with `xhtml:link`, hreflang and `x-default` | `cacheTags` on every content event carry `@<locale>` so a translation going live refreshes only that locale |

## API reference

- Management API: [Localization](https://kanorio.com/en/developers/management-api/localization) (16 operations)

## Webhook events

- `content.translation.published`: Fired when one record's translation in one locale goes live on its own (editor correction, Management API write, incremental or on-demand machine translation). cacheTags carry only that locale's tags. Not fired per record during a full-site translation job; see localization.job.completed.
- `content.translation.deleted`: Fired when a live translation of one record in one locale is deleted; visitors see the primary-locale text until it is translated again.
- `localization.locale.published`: Fired when a locale goes live for visitors and the Content Delivery API (a full-site translation job finished, or the owner published it). cacheTags is site:<id>: rebuild the language switcher and hreflang on every page.
- `localization.locale.unpublished`: Fired when a live locale is taken offline or removed (removed: true); its URL prefix now 301-redirects to the primary locale.
- `localization.locale.updated`: Fired when a live locale's URL prefix (change: path_prefix, previous holds the old prefix), its position in the language switcher (order) or the site's primary locale (primary) changes.
- `localization.job.completed`: Fired when a full-site translation job finishes. cacheTags is locale:<code>: every response of that locale may have changed. A locale going live for the first time also fires localization.locale.published.
- `localization.job.failed`: Fired when a full-site translation job fails (the model provider stayed unavailable or retries ran out; the daily limit only pauses a job). Retry it with localization.jobs.retry.

Payloads: https://kanorio.com/en/developers/webhooks/events

## Recent changes

- 2026-11-04: Localization: automated emails follow the customer's language
- 2026-11-04: Localization: publishing one locale needs the record's publish permission
- 2026-11-04: Localization: more translatable fields (product details, variants, lessons, quizzes, store notes)
- 2026-11-04: Localization: each locale can have its own URL slug
- 2026-11-03: Localization is generally available; site export includes locales and translations

Full changelog: https://kanorio.com/en/developers/changelog

## For site owners

Dashboard help: https://kanorio.com/en/help/multilingual-site
