LocalePack
ChromeFirefoxEdgeOperaSafariCWS Listing
Vue.jsReact
Next.jsi18nextReact Native
Guides
Home/Guides/Firefox WebExtensions i18n
February 21, 2026

Firefox WebExtensions i18n: messages.json reference

Firefox add-ons use the exact same _locales/ / messages.json format as Chrome. But the runtime API is browser.i18n instead of chrome.i18n, and AMO (addons.mozilla.org) has its own review and display behaviour. This article covers everything you need to know.

The format is identical to Chrome

Firefox implements the WebExtension standard, which uses the same messages.json format Chrome pioneered. The folder structure, key schema, placeholder syntax, and default_locale rule are all identical:

my-addon/
├── manifest.json           ← "default_locale": "en" required
└── _locales/
    ├── en/
    │   └── messages.json
    ├── de/
    │   └── messages.json
    └── fr/
        └── messages.json

A messages.json file that works in Chrome will work in Firefox without modification. The only differences are in the runtime API name and a handful of AMO-specific considerations.

browser.i18n vs chrome.i18n

Firefox exposes the i18n API as browser.i18n. Chrome exposes it as chrome.i18n. The method signatures are identical — only the namespace differs:

// Chrome / Edge / Opera
chrome.i18n.getMessage("appName");
chrome.i18n.getMessage("welcomeMessage", ["Alice"]);
chrome.i18n.getUILanguage();

// Firefox / Safari
browser.i18n.getMessage("appName");
browser.i18n.getMessage("welcomeMessage", ["Alice"]);
browser.i18n.getUILanguage();
Firefox also supports the chrome.* namespace as an alias for backwards compatibility. Code written for Chrome using chrome.i18n will run in Firefox without changes. The browser.i18n namespace is preferred in Firefox-first code because it returns Promises where applicable and is the canonical WebExtension API name.

If you want a single codebase that works across Chrome and Firefox, the safest approach is to use chrome.i18n everywhere — Firefox supports it, and it avoids a runtime polyfill. Alternatively, use a compatibility shim:

// i18n.js — cross-browser shim
const i18n = (typeof browser !== "undefined" ? browser : chrome).i18n;

export function t(key, substitutions) {
  return i18n.getMessage(key, substitutions);
}

messages.json format reference

Firefox supports all three fields of the WebExtension message schema:

{
  "appName": {
    "message": "My Add-on",
    "description": "Name of the add-on shown in Firefox toolbar and AMO listing."
  },
  "notificationText": {
    "message": "New message from $SENDER$",
    "description": "Push notification text shown when a new message arrives.",
    "placeholders": {
      "sender": {
        "content": "$1",
        "example": "Alice"
      }
    }
  },
  "itemCount": {
    "message": "$COUNT$ items saved",
    "description": "Status bar label showing how many items are saved.",
    "placeholders": {
      "count": {
        "content": "$1",
        "example": "12"
      }
    }
  }
}
messagerequired

The translated string. In Firefox, getMessage() returns this value. Placeholder tokens like $SENDER$ are replaced at runtime with substitution values.

descriptionoptional

Translator context. Firefox itself ignores this field at runtime, but AMO reviewer tools and AI translation services (including LocalePack) read it to improve accuracy.

placeholdersoptional

Named dynamic values. Each placeholder key maps a $NAME$ token in message to a positional substitution ($1–$9) or a literal string. Firefox preserves these identically to Chrome.

Using __MSG_key__ in manifest.json

The __MSG_key__ substitution syntax works the same way in Firefox as in Chrome. Firefox reads the active locale’s messages.json and substitutes the token before the manifest is evaluated:

{
  "manifest_version": 2,
  "name": "__MSG_appName__",
  "description": "__MSG_appDescription__",
  "default_locale": "en",
  "version": "1.0.0"
}
Firefox still supports Manifest V2, which is the most widely deployed format on AMO. Manifest V3 is supported from Firefox 109+. The i18n system is identical in both versions. If you target older Firefox releases, use MV2.

AMO localization behaviour

When you submit an add-on to AMO, the store uses your _locales/ folder in two ways:

Store listing display

AMO shows your add-on name and description in the visitor’s locale if a matching folder exists in _locales/. If not, it falls back to your default_locale strings. This is separate from the store listing text you enter in the AMO developer hub — more on that below.

Two distinct sources of text on AMO

AMO has two separate sources for add-on text:
  1. 1.In-extension strings — from _locales/*/messages.json. Displayed in the browser toolbar, popups, and options pages.
  2. 2.AMO listing text — entered manually in the AMO developer hub under “Manage Versions › Localize.” This is what appears on the public store page and search results. It is not read from messages.json.

Reviewer experience

AMO reviewers see your extension name and description in their own locale if available. Complete, accurate translations improve the reviewer experience and reduce back-and-forth during review. Extensions with broken placeholders or obviously machine-mangled strings are more likely to receive reviewer comments.

Firefox locale codes

Firefox uses the same locale code format as Chrome — lowercase language code, optional underscore-separated region code — but the approved list is slightly different. The most important differences:

Both Chrome and Firefox

endefresjakozh_CNzh_TWpt_BRruarplnl

Firefox accepts (Chrome may not)

nb_NOnn_NOhy_AMkaazbemksqbscyeugl

In practice, if you are targeting the major languages (top 20–30 locales), the codes are identical between Chrome and Firefox. Differences only appear for smaller regional languages.

Testing Firefox add-on i18n locally

The cleanest way to test a different locale in Firefox is web-ext, Mozilla’s official CLI tool for add-on development. Pass --start-url along with a Firefox profile set to the target locale, or use the --pref flag:

# Install web-ext globally
npm install -g web-ext

# Run with German locale
web-ext run --pref intl.locale.requested=de

# Run with Japanese locale
web-ext run --pref intl.locale.requested=ja

Alternatively, change the locale in Firefox directly:

# 1. Go to about:config in Firefox
# 2. Search for: intl.locale.requested
# 3. Set the value to your target locale code, e.g. "de" or "ja"
# 4. Restart Firefox
# 5. Reload your extension from about:debugging

Common Firefox-specific pitfalls

Using Promises with browser.i18n.getMessage()

Unlike most browser.* APIs, browser.i18n.getMessage() is synchronous and returns a plain string, not a Promise. Do not await it or chain .then() — it will silently return the Promise object rather than the translated string.

Forgetting default_locale causes AMO submission rejection

AMO runs the same manifest validation as Firefox itself. A _locales/ directory without default_locale in manifest.json will cause an automatic validation failure during upload. The error appears immediately in the AMO developer hub before human review even begins.

Confusing in-extension strings with AMO listing text

messages.json controls what users see inside the extension (toolbar badge, popup text, options page labels). The AMO store page description, screenshots, and summary are entered separately in the developer hub. Translating messages.json does not update your AMO listing — you must enter listing translations manually.

Invalid JSON in any locale file blocks the whole add-on

Firefox parses every messages.json file on install. A single syntax error in any locale — trailing comma, unescaped quote, missing brace — prevents the entire add-on from loading, even if that locale is never used. Always validate all locale files before packaging.

Placeholder name case mismatch

In the message string, $SENDER$ references the placeholder defined by the key sender (lowercase) in the placeholders object. The token in message is always uppercase with dollar signs; the object key is always lowercase. Firefox matches them case-insensitively, but Chrome does not — so always follow the convention to keep the add-on cross-browser.

Cross-browser compatibility summary

FeatureChromeFirefox
File formatmessages.jsonmessages.json (identical)
Folder structure_locales/{locale}/_locales/{locale}/ (identical)
Runtime APIchrome.i18nbrowser.i18n (chrome.i18n also works)
getMessage()Synchronous, returns stringSynchronous, returns string
Substitution argsArray of stringsArray of strings (identical)
$PLACEHOLDER$ syntaxSupportedSupported (identical)
__MSG_key__ in manifestSupportedSupported (identical)
default_locale requiredYes (when _locales/ exists)Yes (when _locales/ exists)
Manifest versionMV3 only (MV2 deprecated)MV2 and MV3 both supported
Per-key fallbackYes (falls back to default_locale)Yes (identical)

Translate your Firefox add-on into 52 languages

LocalePack generates a _locales ZIP compatible with both Firefox and Chrome — correct locale codes, preserved $PLACEHOLDER$ tokens, validated JSON in every file. Upload your source messages.json, pay once, and download.

Localize your Firefox add-on →
← Back to Guides
LocalePack
GuidesPrivacyTermsSupport

© 2025 LocalePack. All rights reserved.

This project was translated with LocalePack logoLocalePack