Translating Manifest Fields and Store Listings

Localise an extension's name, description, action title and command descriptions through _locales and __MSG_ placeholders, and keep store listings in step with the shipped translations.

Published September 18, 2026 Updated September 18, 2026 8 min read
Table of Contents

An extension can have perfectly translated UI and still greet a German user with an English name in the toolbar, an English tooltip, an English keyboard-shortcut description on chrome://extensions/shortcuts, and an English store listing. Those strings do not live in your popup’s code — they live in the manifest and in the store dashboard, and each is translated through a different mechanism. This guide is part of internationalisation and accessibility.

Where user-visible strings actually live

Four layers of translatable textStore listing text entered in the dashboard, manifest fields resolved from _locales at install, UI strings resolved by chrome.i18n at runtime, and strings in content your server sends.Store listingtitle, summary, description, screenshotsentered per language in each dashboardManifest fieldsname, description, action title, commands__MSG_key__ → _locales at installExtension UIpopup, options, notificationschrome.i18n.getMessage at runtimeServer contentanything your API returnsyour own localisation
Only the middle two are translated by the browser — the top layer is a separate, manual process per store.

Step-by-step

1. Declare a default locale and use placeholders

 1{
 2  "manifest_version": 3,
 3  "default_locale": "en",
 4  "name": "__MSG_extName__",
 5  "short_name": "__MSG_extShortName__",
 6  "description": "__MSG_extDescription__",
 7  "action": { "default_title": "__MSG_actionTitle__" },
 8  "commands": {
 9    "save-page": {
10      "suggested_key": { "default": "Alt+Shift+S" },
11      "description": "__MSG_cmdSavePage__"
12    }
13  }
14}

Execution context: parsed at install and on every browser locale change. default_locale is mandatory once _locales exists — without it the manifest fails to load. The browser substitutes __MSG_key__ in the fields it supports; placeholders in unsupported fields are shown literally, which is how you discover them.

2. Provide the messages

1// _locales/en/messages.json
2{
3  "extName":        { "message": "Reader — save and read later", "description": "Extension name in the store and toolbar. Max ~45 characters." },
4  "extShortName":   { "message": "Reader", "description": "Short name where space is tight. Max 12 characters." },
5  "extDescription": { "message": "Save articles, quotes and links to read later, on any device.", "description": "Shown on the extensions page. Max 132 characters." },
6  "actionTitle":    { "message": "Save this page to Reader", "description": "Toolbar button tooltip." },
7  "cmdSavePage":    { "message": "Save the current page", "description": "Shown on the keyboard shortcuts page." }
8}

Execution context: read by the browser for the user’s locale, falling back to default_locale per key. The description fields are for translators, not users — stating the length limit there prevents the most common translation bug, a name that is clipped mid-word in the toolbar.

3. Add locales, and let missing keys fall back

1// _locales/de/messages.json
2{
3  "extName":        { "message": "Reader – speichern und später lesen" },
4  "extDescription": { "message": "Artikel, Zitate und Links speichern und später lesen – auf jedem Gerät." },
5  "actionTitle":    { "message": "Diese Seite in Reader speichern" },
6  "cmdSavePage":    { "message": "Aktuelle Seite speichern" }
7}

Execution context: read by the browser for German-locale users. extShortName is absent here, so it falls back to English — a partial locale is valid, which means a new locale can ship incrementally. The directory name must be a supported locale code (de, pt_BR, zh_CN — underscores, not hyphens).

4. Respect the length limits per field

FieldPractical limitWhat happens past it
name45 charactersStore rejects; toolbar truncates earlier
short_name12 charactersTruncated in app launchers
description132 charactersStore rejects the package
command description~60 charactersWraps awkwardly on the shortcuts page

German and Finnish translations routinely run 30% longer than English, so a 40-character English name leaves too little room. Check lengths in CI rather than in review:

1import { readdirSync, readFileSync } from "node:fs";
2const LIMITS = { extName: 45, extShortName: 12, extDescription: 132 };
3for (const loc of readdirSync("_locales")) {
4  const msgs = JSON.parse(readFileSync(`_locales/${loc}/messages.json`, "utf8"));
5  for (const [key, max] of Object.entries(LIMITS)) {
6    const len = msgs[key]?.message?.length ?? 0;
7    if (len > max) throw new Error(`${loc}/${key} is ${len} chars (max ${max})`);
8  }
9}

Execution context: Node, in CI. A store rejection for an over-long description in one locale blocks the whole release, so this is worth failing the build over.

5. Translate the store listing separately — and keep it in step

The store listing is not generated from _locales. In the Chrome Web Store dashboard, add each language under Store listing and enter the title, summary and description; screenshots can also be localised. Firefox’s AMO has an equivalent per-locale listing editor.

A practical way to keep the two aligned is to keep the listing text in the repository next to the manifest strings and copy from there, so a translator updates both in one change:

1store/
2  en/listing.md      # title, summary, full description
3  de/listing.md
4_locales/
5  en/messages.json
6  de/messages.json

Execution context: the repository. Nothing automates the dashboard entry for you, but a single source file per language means the listing and the installed extension stop drifting apart — and drift here is visible to users before they install.

How a German user sees each stringThe browser resolves manifest placeholders from the de locale with fallback to en, the popup resolves UI strings the same way at runtime, and the store serves the German listing entered in the dashboard.Browser locale: deuser setting__MSG_extName__de → found__MSG_extShortName__de → missing → enat runtime and in the storei18n.getMessage()same fallbackStore listing (de)entered by handScreenshots (de)optional, per locale
The browser handles the fallback for the middle two; the store listing has no fallback beyond what you entered.

Choosing which locales to ship

Every added locale is a permanent maintenance cost: each new string needs translating before release, or it falls back to English and the UI becomes a mixture. That cost is worth paying where your users are, and the store dashboard’s installation statistics by language show exactly where that is.

A sensible progression is to ship the manifest strings and the store listing first for your top few languages — they are short, change rarely, and determine whether a user installs at all — and to translate the full UI only once a locale’s share justifies keeping it complete. A German listing and name with an English popup is a better experience than an English listing that a German user never clicks.

Right-to-left locales add layout work on top of translation, which is covered in supporting RTL locales in extension pages. Plan for it before adding Arabic or Hebrew rather than after.

Install conversion from a store listing, by listing languageRelative install conversion for non-English users viewing an English-only listing, a translated listing with English UI, and a fully translated listing and UI.English listing only100 relative …Translated listing + name164 relative …Listing + name + full UI181 relative …
Translating the listing and manifest strings captures most of the gain before the UI is touched.

Cross-browser variation

  • Chrome / Edge: __MSG_ substitution works in name, short_name, description, action.default_title, command descriptions and a few other fields. The store listing is localised in the developer dashboard per language.
  • Firefox: the same _locales format and placeholders. AMO listings are localised in the AMO developer hub; Firefox also shows the manifest description in about:addons.
  • Safari: _locales is supported for the extension. The App Store listing belongs to the containing app and is localised in App Store Connect, alongside the app’s own Localizable.strings.
  • All three: locale directory names use underscores (pt_BR), and an unrecognised directory name is silently ignored — a typo produces a locale that never loads.

Verification

  1. Switch the browser’s UI language to one of your locales, restart, and check the toolbar tooltip, the name on the extensions page and the shortcuts page.
  2. Confirm the resolved strings from any extension context:
1[chrome.i18n.getUILanguage(), chrome.i18n.getMessage("extName"), chrome.i18n.getMessage("extShortName")];
2// ["de", "Reader – speichern und später lesen", "Reader"]

Execution context: any extension page or the service worker. The short name falling back to English here confirms the fallback works as designed.

  1. Run the length check in CI across every locale.
  2. Search the extensions page and shortcuts page for a literal __MSG_ — any hit is a placeholder in an unsupported field or a missing key in the default locale.

FAQ

Can I change the language without changing the browser’s?

Not for manifest fields — the browser resolves them from its own UI language. Your own UI can offer a language switch by loading messages manually, but the name, tooltip and shortcut descriptions will follow the browser.

Why does my extension show __MSG_extName__?

The key is missing from the default_locale file, or default_locale is not set. The default locale must contain every key the manifest references.

Do I need to translate the store listing if the extension UI is translated?

The listing is what users see before installing, so it matters more than the UI for acquisition. Translate it first.

Other UI/UX Patterns & Interactive Components Resources