Skip to content
better-i18n.com

Content entries can have translations in multiple languages. This guide covers how to add and manage those translations.

Adding a translation to an existing entry #

Via the dashboard #

  1. Open the content entry
  2. Click "Add translation" in the top-right language switcher
  3. Select the target language
  4. Enter the translated title and body
  5. Click Save

Via MCP (AI agents) #

Code
"Translate entry {id} to Turkish and French"

The agent calls updateContentEntry with a translations map:

JSON
{
  "entryId": "uuid",
  "translations": {
    "tr": { "title": "Başlık", "bodyMarkdown": "## Bölüm\n\nİçerik..." },
    "fr": { "title": "Titre", "bodyMarkdown": "## Section\n\nContenu..." }
  }
}

Writing translations goes through the dashboard or MCP. The Content API at content.better-i18n.com is read-only — it has no PATCH or POST route.

Bulk translating entries #

For multiple entries at once, use bulkUpdateEntries — up to 200 entries per call, with a failed array in the response so only the failures need retrying:

JSON
{
  "entries": [
    {
      "entryId": "uuid-1",
      "translations": {
        "tr": { "title": "...", "bodyMarkdown": "..." },
        "fr": { "title": "...", "bodyMarkdown": "..." }
      }
    },
    {
      "entryId": "uuid-2",
      "translations": {
        "tr": { "title": "..." }
      }
    }
  ]
}

One practical note from doing this at scale: a very large payload can be truncated in transit, and a truncated JSON body fails to parse rather than failing loudly per entry. If a bulk call errors out on a large batch, split it — fewer entries, or fewer languages per call — rather than retrying the same payload.

Translation status #

Each translation carries its own status:

StatusMeaning
draftSaved, not marked live
publishedMarked live, publishedAt stamped
archivedTaken out of circulation, content kept

The Content API does not filter by status on its own, so a request without ?status=published returns drafts and archived entries too. If your app renders whatever the API hands back, add the filter.

Publishing an entry publishes its translations — but not blindly. From the dashboard you can pick which languages go live, and a language whose body is still empty is skipped rather than being marked published over a blank editor. Over MCP there is no per-language choice: every language with content goes live. See How do I publish content entries?.

Finding untranslated entries #

Over MCP, use the missingLanguage filter:

Code
listContentEntries({ modelSlug: "help-article", missingLanguage: "tr" })

This returns entries that do NOT yet have a Turkish translation.

Important: missingLanguage=tr is the one you want, not language=tr. language=tr returns entries that already have Turkish — the exact opposite of what you are looking for when hunting down untranslated content. This filter is an MCP filter; the read API does not expose it.

Localized custom fields #

Custom fields can be marked as localized: true, which means each language has its own value.

Example: a slug field that stores the URL slug per language:

JSON
{
  "translations": {
    "en": { "customFields": { "slug": "getting-started" } },
    "tr": { "customFields": { "slug": "baslarken" } },
    "fr": { "customFields": { "slug": "premiers-pas" } }
  }
}

Note the difference between the two slugs on an entry: the top-level slug is one shared CMS identifier across every language, so per-language URLs need their own localized field like the one above.

Setting custom fields at the top level of an update touches the source language only. To set them for one language, pass them under that language — translations.tr.customFields — or use the languageCode single-language mode.

Publishing translations #

Use the Publish button in the dashboard, or ask an agent to publish the entry (publishContentEntry). There is no publish endpoint on the read API.