Skip to main content
Localization lets you author content once and deliver it in each user’s language. Every content type that shows text to end users can be translated — flows, checklists, launchers, banners, announcements, and resource centers. Key ideas, up front:
  • Translations belong to a content version. They are edited alongside the draft and go live through the normal publish flow — nothing changes for end users until you publish.
  • Language selection is driven by one attribute: locale_code. You tell Usertour which language each user should see. Usertour never guesses from the browser.
  • Untranslated text falls back to the source. A half-finished translation never breaks content: any text you haven’t translated is shown in the language the content was authored in.

How language selection works

When content is delivered to a user, Usertour picks the language like this:
  1. Read the user’s locale_code attribute (set via usertour.identify() — see Setting the user’s language).
  2. Find an enabled translation whose code matches exactly (zh-CN matches zh-CN).
  3. If there is no exact match, fall back to the primary language: a user with fr-CA gets a translation coded fr.
  4. If nothing matches — or the user has no locale_code at all — the user sees the content in its source language.
Usertour deliberately does not detect the browser language. The correct language for your product is the one your app is rendered in, and only your app knows that. If your app is already internationalized, passing its active language as locale_code is a one-line change and is always right — a separate guess by Usertour could disagree with what the user sees around it.

Set up your languages

Languages are defined once per project, then every content shares the list.
  1. Go to Settings → Localization.
  2. Click New localization.
  3. Pick a locale from the list — the Name and Code fields fill in automatically and stay editable.
  4. Click Create localization.
The Localization settings page Creating a new localization Two fields matter later:
  • Code is what gets matched against each user’s locale_code attribute, using the rules above. Letters, digits, - and _; unique within the project, and case doesn’t distinguish two codes (fr and FR would be the same language to a user, so they can’t both exist). It’s usually the locale tag, but it doesn’t have to be: fr-enterprise alongside fr gives one language two variants, each with its own translations, selected by the locale_code you set on the user.
  • Name is the language this localization stands for. End users never see it, but AI translate does — it’s the language the model is asked to translate into, so “French (France)” works better than a shorthand.

The default localization

The localization marked Default represents the language your content is authored in. It is not a translation target — there is nothing to translate into it — and users who don’t match any translation get this language. Use the ⋯ → Set as project default menu to change it.

Deleting a localization

Deleting a localization (⋯ → Delete) takes that language out of service everywhere at once: it disappears from every content’s Localization tab, and users who were getting it see the source language instead. The default localization can’t be deleted. The translations themselves are kept. To bring a deleted language back — with every translation it had, each with its enabled state — add a localization with the same code again, or restore it through the API or MCP. To stop delivering a single content’s translation without removing the language, turn off its Status switch instead.

Translate your content

Open any flow, checklist, banner or other content and switch to the Localization tab. You’ll see one row per language with how much is still missing, an enable switch, and when it was last updated. The Localization tab on a flow Click a language to open the translation editor. Source text is on the left, your translation goes on the right. Text is grouped the way the content is structured — one card per flow step, or per section for other content types. The translation editor While you work:
  • A red dot marks rows that are still untranslated. Check Only untranslated to hide everything you’ve already done.
  • A yellow dot marks rows whose source text changed after you translated them — see Keeping translations up to date.
  • Each card’s header counts what’s left in it; a green check means the card is done.
  • Everything autosaves as you type — the Saving/Saved indicator sits next to the header actions.
  • Leaving a translation blank means “fall back to the source language” for that text.
Rich text keeps its formatting automatically. A sentence with a bold phrase in the middle appears as separate rows (before / bold part / after) so the translation slots back into the same formatting. Translate the fragments so they read correctly when joined.
Editing translations never disturbs what’s live: if the version you’re translating is currently published, your first change automatically continues on a new draft, and end users keep seeing the published translation until you publish again.

AI translate

If machine translation is available on your instance, the editor offers it at two levels:
  • The AI translate button in the header translates everything that’s still untranslated, in batches, with a progress indicator. If it stops early (for example, a provider hiccup), click it again — it continues from what’s still missing.
  • Hovering a single row shows a magic-wand button that translates just that text.
AI translate in progress Machine translation fills blanks only — it never overwrites a translation you’ve already written or edited. Review the results before publishing; they land in the editor like any other edit.
On Usertour Cloud, machine translation is included with every paid plan. On self-hosted instances you bring your own AI provider — see Machine translation on self-hosted.

Export and import CSV

For external translators or a translation agency, use the Export CSV / Import CSV buttons in the editor header. The file has three columns — path, source, translation:
Rules on import:
  • Fill in the translation column and leave path and source untouched — rows are matched by path, and paths are internal identifiers, not meant to be edited.
  • A blank translation keeps whatever is currently in the editor; it never clears an existing translation.
  • Rows that don’t match the current content are skipped silently, so importing a slightly stale file is safe.
URLs are included in the export too, so a translator can point each language at its own assets and pages: media URLs (images, videos) and destinations — where a link, an image, a button, a checklist task or a resource center entry navigates to. They’re optional and don’t count as missing translations; leave one blank to keep the original.

Translate via the API or MCP

The same rows are available to programs and AI agents as JSON — no file involved. Through the v2 API:
Each row of the read carries the same path and source as the CSV, the current translation, plus kind (text to translate; destination for where a link or button navigates — any path your app can route, a relative one included; media for an image or embed URL, which must be absolute), optional (true for the two URL kinds) and outdated (the source text changed after the row was translated). Over MCP the same three calls are the list_localizations, get_version_localization and update_version_localization tools, so you can simply ask your agent to “translate this flow into French”. It behaves like the CSV import, with three differences worth knowing:
  • Unknown paths are rejected, not skipped. A path that isn’t part of the version fails the whole request and names the offender — read the rows again if the content changed. A blank translation still keeps the existing one.
  • Only a draft can be written. A version that is (or ever was) published is read-only: create a new version first — it carries every translation with it — then write to that one. Reading works on any version.
  • Send a language in one request. Saving re-bases the outdated tracking for the whole language on the current source text, so include every row you’re fixing in a single write.
Writes merge row by row: the paths you send are saved, every other row keeps what it has. The editor saves the same way, so an API write and someone translating the same language in the dashboard no longer overwrite each other’s rows — only the same row, written twice, ends with the later value.
The languages themselves can be managed the same way — POST, PATCH and DELETE on /v2/projects/{projectId}/localizations (MCP: create_localization, update_localization, delete_localization, restore_localization), so an agent asked to translate into a language the project doesn’t have yet can add it first. GET /v2/locales returns the same catalog the dashboard’s picker offers — a locale tag with the language name to file it under — so a language added by a program lands under the same name a person would have picked, which is also the name AI translate is given (over MCP the catalog is in get_authoring_guide, section localization). The catalog is a suggestion, not a fixed list: any well-formed tag is accepted. These use the localization:* scopes, while reading and writing a translation uses the content scopes — see Scopes. Changing the default localization stays in the dashboard.

Preview

The Preview button in the editor header renders the content with your working translations applied — no publish needed. Previewing a translation For a real end-to-end check in your own app, set locale_code on your test user and open the draft as usual.

Enable and publish

Two switches control what ships:
  1. Enable the language — the Status switch on the Localization tab. A disabled translation is never delivered, no matter how complete it is. This lets you translate incrementally and flip the switch when you’re happy.
  2. Publish the version — translations are part of the content version, so they go live exactly when the version does. If the content is already published, toggling or editing a translation creates a draft; publish it to ship the change.
Enabling a language

Keeping translations up to date

When you change source copy after it has been translated, the affected rows are flagged:
  • The Localization tab and each card show an outdated count.
  • In the editor, flagged rows carry a yellow dot and a Source changed hint, so a reviewer can see exactly what needs another look.
  • Editing the row (or translating it with the magic wand) clears the flag.
Outdated translations flagged after a source edit Newly added source text isn’t flagged as outdated — it simply shows up as untranslated, and the header AI translate button will pick it up. Programs and agents see the same flags: every row of a translation read has an outdated field, and GET …/versions/{id}?expand=localizations returns the missing / outdated counts for every language at once — the check to run after editing source text and before publishing, since publishing never blocks on stale translations.

Setting the user’s language

Set the locale_code attribute when you identify the user:
If your app is internationalized, pass its active language directly, for example:
When the user switches languages inside your app, update the attribute — content that’s currently showing is re-delivered in the new language:
locale_code is a standard user attribute, so you can also use it in start conditions and segments like any other attribute.

Built-in UI language

Besides your own copy, Usertour’s widgets contain built-in interface text — checklist dismiss prompts, resource center search and empty states, survey buttons, screen-reader labels, and date formatting. These follow the same signal: the user’s locale_code, falling back to your project’s default localization, then English. Built-in translations ship for: English, Simplified Chinese, Traditional Chinese, Japanese, Korean, French, German, Spanish, Portuguese, and Dutch. For other languages the built-in text stays in English while your translated content still shows correctly.

What’s translatable

Inside that content, three kinds of value can differ per language:
  • Text — every string a user reads, including an image’s alt text and the fallback text of a user attribute placeholder (shown when the attribute is empty).
  • Destinations — where a click goes: links in rich text, an image’s click-through link, and the Navigate to page action on buttons, survey questions, checklist tasks, launchers and resource center blocks, plus a resource center list entry’s own navigation. Point each language at its localized page; leave a destination blank to keep the original. A destination whose URL contains a user attribute is managed by the source content and doesn’t appear in the translation editor.
  • Media — image and video URLs, so each language can ship its own assets.
A few things intentionally never translate:
  • Content names (the name in your dashboard’s content list) stay stable — they’re used in analytics and by your team. For resource center lists, give each entry a translatable List item name in the block editor instead.
  • Event trackers have no end-user-visible text.

Machine translation on self-hosted

Self-hosted instances enable the AI translate buttons by configuring an AI provider through environment variables — any Anthropic key, an OpenAI-compatible gateway, or AWS Bedrock. See Environment Variables for the AI_* reference. Once configured, machine translation is available with no plan gating.