Chrome Extension Internationalization — Manifest V3 Guide

3 min read

Internationalization in Chrome Extensions (MV3)

Chrome’s i18n system supports multiple languages in extensions.

Directory Structure

my-extension/
├── _locales/
│   ├── en/messages.json
│   ├── es/messages.json
│   └── fr/messages.json
├── manifest.json
└── ...

messages.json Format

{
  "extension_name": {
    "message": "My Extension",
    "description": "The extension name"
  },
  "greeting_message": {
    "message": "Hello, $NAME$!",
    "placeholders": {
      "NAME": { "content": "$1", "example": "John" }
    }
  }
}

Use $NAME$ for named placeholders and $1, $2 for positional arguments.

Manifest Configuration

{
  "name": "__MSG_extension_name__",
  "description": "__MSG_extension_description__",
  "default_locale": "en",
  "manifest_version": 3
}

Using Messages in JavaScript

chrome.i18n.getMessage()

const name = chrome.i18n.getMessage('greeting_message', ['World']);
// Returns: "Hello, World!"

// You can also pass a single string instead of an array:
const greeting = chrome.i18n.getMessage('greeting_message', 'Alice');
// Returns: "Hello, Alice!"

Note: The substitutions parameter accepts a string or an array of up to 9 strings. It does NOT accept an object. Named placeholders like $NAME$ are defined in messages.json and map to positional substitutions ($1, $2, etc.).

Detecting User Language

const uiLang = chrome.i18n.getUILanguage();

chrome.i18n.getAcceptLanguages((languages) => {
  console.log('Accepted languages:', languages);
});

Predefined Messages

Key Description
@@extension_id Extension’s unique ID
@@ui_locale Current UI locale
@@bidi_dir “ltr” or “rtl”
@@bidi_reversed_dir Opposite of @@bidi_dir
@@bidi_start_edge “left” for ltr, “right” for rtl
@@bidi_end_edge “right” for ltr, “left” for rtl
const extId = chrome.i18n.getMessage('@@extension_id');
const direction = chrome.i18n.getMessage('@@bidi_dir');

Using Messages in CSS

For RTL support:

body {
  direction: __MSG_@@bidi_dir__;
}

Storing User Language Preference

Use @theluckystrike/webext-storage:

import { Storage } from '@theluckystrike/webext-storage';

const storage = new Storage();

async function setUserLanguage(lang) {
  await storage.set('userLanguage', lang);
}

async function getUserLanguage() {
  const { userLanguage } = await storage.get('userLanguage');
  return userLanguage || chrome.i18n.getUILanguage();
}

Dynamic Locale Switching

// background.js
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
  if (request.action === 'getTranslations') {
    const translations = {};
    request.keys.forEach(key => {
      translations[key] = chrome.i18n.getMessage(key, request.params);
    });
    sendResponse(translations);
  }
});

Best Practices

See Internationalization Guide.

References

Part of the Chrome Extension Guide by theluckystrike. Built at zovo.one.