LocalePack
ChromeFirefoxEdgeOperaSafariรายการ CWS
Vue.jsReact
Next.jsi18nextReact Native
คู่มือนักพัฒนากรณีความสำเร็จ
Home/Guides/Improve AI translation quality
July 3, 2026

Improve AI translation quality in messages.json

AI translation of UI strings fails in a very specific way: the output is fluent, grammatical — and wrong for the button it appears on. The fix is not a better model. It is better input. The messages.json format already has two fields designed to carry translation context — description and placeholders — and most extensions leave them empty. This guide shows how to use them so a machine (or a human) translates your extension correctly on the first pass.

Why UI strings are the hardest text to translate

A novel gives a translator pages of surrounding context. A UI string gives them one or two words. Consider a key whose message is just Save. Is it a verb on a button ("save the file") or a noun in a banner ("save 20%")? In English the same word works for both. In German the button is Speichern and the discount is Sparen — two unrelated words. The model has to guess, and with no context, a wrong guess is fluent enough that you will not catch it until a user does.

Short strings, missing part-of-speech information, and invisible placement are the three gaps. Everything in this guide is a way of closing them inside the file format you already ship.

The description field is your context channel

Every entry in messages.json accepts an optional description field. Chrome ignores it at runtime — it exists purely to carry context to whoever (or whatever) translates the string. An AI translator reads it the same way a human would:

{
  "save": {
    "message": "Save",
    "description": "Button label. Verb: saves the user's current settings. Keep short."
  },
  "saveBanner": {
    "message": "Save 20% with a yearly plan",
    "description": "Marketing banner on the upgrade page. 'Save' means spend less money."
  }
}

A good description answers three questions in one or two sentences: where does the string appear (button, tooltip, error toast, menu item), what grammatical role does it play (verb, noun, complete sentence), and are there constraints (character limit, must match a term used elsewhere, do not translate the product name)?

// Weak — restates the string, adds nothing:
"description": "The save text"

// Strong — placement, role, constraint:
"description": "Context-menu item, verb. Appears next to 'Copy' and
'Delete'. Max ~20 chars or the menu truncates."

Placeholders: name them, describe them, give an example

Raw $1, $2 substitutions are opaque — the translator cannot tell what will be substituted, so they cannot pick correct grammar around it. Named placeholders with an example field solve both problems:

{
  "itemsSelected": {
    "message": "$COUNT$ items selected in $FOLDER$",
    "description": "Status bar text after multi-select.",
    "placeholders": {
      "count": {
        "content": "$1",
        "example": "3"
      },
      "folder": {
        "content": "$2",
        "example": "Downloads"
      }
    }
  }
}

The example value tells the translation model what shape the runtime value takes — a number, a folder name, an email address — which changes the grammar it wraps around the token. Languages with case systems (German, Russian, Finnish) may need different prepositions or word order depending on what the placeholder actually is.

Just as important: the $PLACEHOLDER$ token itself must survive translation byte-for-byte. A translator that "helpfully" localizes $COUNT$ to $ANZAHL$ breaks the substitution silently — the literal token renders in your UI. This is the placeholder-preservation problem covered in depth in messages.json format explained.

Ship full sentences, not fragments

Concatenating translated fragments in code is the single most reliable way to get broken output, because word order is language-specific:

// Broken by design — word order will not survive translation:
getMessage("you_have") + count + getMessage("unread_messages")

// Correct — one key, one complete sentence, placeholder inside:
{
  "unreadCount": {
    "message": "You have $COUNT$ unread messages",
    "placeholders": {
      "count": { "content": "$1", "example": "7" }
    }
  }
}

In Japanese the verb comes last; in German the count may move; in Arabic the whole sentence flows right-to-left. A complete sentence with an inline placeholder lets each language rearrange freely. Fragments lock every language into English word order.

Three quality traps the format will not catch for you

Plurals. chrome.i18n has no plural rules — one message per key, full stop. "1 items selected" is the classic symptom. Either write count-neutral copy ("Selected: $COUNT$"), or define separate keys (itemsOne, itemsMany) and pick in code. Say which strategy you use in the description so the translation stays consistent.

Casing conventions. Title Case Is An English Habit. German capitalizes nouns everywhere; French and Spanish use sentence case for UI labels. If your description says "Title case button label," a good translator will correctly ignore the English casing convention in languages where it looks wrong — that is a feature, not a bug.

Length expansion. German and Russian run 30–40% longer than English; Finnish compounds can be dramatic. If a string lives in a fixed-width popup, put the constraint in the description ("max ~24 chars"). Without it, the first report you get will be a screenshot of clipped text.

Pre-translation checklist

✓

Every user-visible key has a description with placement + grammatical role

✓

Strings with character limits state them in the description

✓

All placeholders are named, with content and a realistic example

✓

No sentence is assembled by concatenating separate keys in code

✓

Count-bearing strings have an explicit plural strategy

✓

Product names and trademarks are marked 'do not translate'

✓

The file validates before you send it for translation

The last point has its own guide: validate messages.json before shipping — context improves translation quality, but only a structurally valid file translates at all.

LocalePack reads your context fields

When LocalePack translates your messages.json, the description of every entry and the example of every placeholder are passed to the model as translation context — so the work you put into them pays off directly in output quality. Placeholder tokens are preserved byte-for-byte across all 52 languages, and the result downloads as a ready-to-ship _locales ZIP. Upload once, pay once.

Translate my messages.json →
← Back to Guides
LocalePack
คู่มือความเป็นส่วนตัวข้อกำหนดฝ่ายสนับสนุน

© 2025 LocalePack. สงวนลิขสิทธิ์

โปรเจกต์นี้แปลด้วย LocalePack logoLocalePack