Automate Chrome Web Store listing translations with a console script
Chrome Web Store doesn’t offer a bulk upload for store descriptions. With 52 supported languages, manually switching and pasting each translation takes hours. This guide shows how to automate the entire process with a browser console script that fills every language in under a minute.
The problem
The Chrome Developer Dashboard has a “Store Listing” tab where you enter per-language marketing text. But there’s no bulk import — you have to:
- Select a language from the dropdown
- Wait for the form to load
- Paste the translated description
- Repeat for every language (up to 52 times)
The extension name and short description are controlled by manifest.json via __MSG_appName__ and __MSG_shortDesc__ — those are applied automatically from your _locales folder. But the detailed store description (up to 16,000 characters) must be entered manually in the dashboard for each language.
What the script does
The automation script runs in the browser console on the CWS Developer Dashboard. It:
Creates a file picker
A green button appears at the top of the page. You click it and select the _locales folder from the ZIP that LocalePack generated.
Reads all locale files
The script scans every messages.json inside _locales/ and extracts the storeDesc.message value for each locale.
Fills each language automatically
For each locale, it opens the CWS language dropdown, selects the matching language, waits for the form to load, and fills the description textarea.
Reports results
The console shows how many locales were filled and how many were skipped. You click Save draft to commit the changes.
iw, not he. The script maps these automatically.Prerequisites
Before running the script, you need:
- A translated
_localesfolder withmessages.jsonfiles containing astoreDesckey. You can generate this with LocalePack’s store listing translator. - Access to the Chrome Developer Dashboard for your extension
- The extension must already be published or in draft with at least one language
Step-by-step instructions
Open the Store Listing tab
Go to the Chrome Developer Dashboard, select your extension, and navigate to the Store Listing tab. You should see the language dropdown labeled “Current editing language”.
Open the browser console
Press F12 (or Cmd+Option+J on Mac) to open Chrome DevTools, then switch to the Console tab.
Allow pasting (if blocked)
Chrome may block pasting in the console. If you see a warning, type allow pasting and press Enter. This is a one-time action per tab.
Paste and run the script
Copy the script below (use the copy button), paste it into the console, and press Enter.
Select the _locales folder
A green file picker will appear at the top of the page. Click it and select the _locales folder from your downloaded ZIP. The script will begin processing automatically.
Save and verify
When the console shows “Done!”, click Save draft in the CWS dashboard. Then spot-check a few languages by switching the dropdown — verify the description was filled correctly.
The script
Copy the entire script and paste it into the Chrome DevTools console on the CWS Store Listing page. You can also download it as a file.
const ANIMATION_TIMEOUT = 500;
const fileInput = document.createElement("input");
fileInput.setAttribute("id", "filepicker");
fileInput.setAttribute("type", "file");
fileInput.setAttribute("webkitdirectory", "");
fileInput.setAttribute("multiple", "");
fileInput.setAttribute(
"style",
"position: absolute;top: 0;z-index: 999;padding: 1rem;background: green;",
);
document.documentElement.append(fileInput);
document.getElementById("filepicker").addEventListener(
"change",
async event => {
const files = event.target.files;
const locales = {};
const localeFiles = Object.values(files)
.filter(f => f.name == "messages.json")
.filter(f => f.type == "application/json");
for (const localeFile of localeFiles) {
const localeCode = localeFile.webkitRelativePath
.replace("_locales/", "")
.replace("/messages.json", "");
const fileText = await localeFile.text();
const localeJson = JSON.parse(fileText);
if (localeJson.storeDesc) {
locales[localeCode] = localeJson.storeDesc.message;
} else {
console.warn(`[${localeCode}] - no storeDesc key, skipping`);
}
}
console.log(`Found ${Object.keys(locales).length} locales with storeDesc`);
console.log("Locales:", locales);
await uploadLocales(locales);
},
false,
);
function sleep(time) {
return new Promise(resolve => setTimeout(resolve, time));
}
async function uploadLocales(locales) {
const dropdown = document.evaluate(
"//h3[text()='Current editing language']/../../div[2]//div[@jsshadow]/div/div",
document,
null,
XPathResult.FIRST_ORDERED_NODE_TYPE,
null,
).singleNodeValue;
if (!dropdown) {
console.error("Could not find the language dropdown. Make sure you are on the Store Listing tab.");
return;
}
let filled = 0;
let skipped = 0;
for (const [rawCode, description] of Object.entries(locales)) {
let code = rawCode.replace("_", "-");
if (code === "he") {
code = "iw";
}
dropdown.click();
await sleep(ANIMATION_TIMEOUT);
const langList = document.evaluate(
"//ul[@aria-label='Language']",
document,
null,
XPathResult.FIRST_ORDERED_NODE_TYPE,
null,
).singleNodeValue;
if (!langList) {
console.error("Could not find the language list. Try refreshing the page.");
return;
}
const langItem = [...langList.children]
.filter(e => e.tagName == "LI")
.find(e => e.getAttribute("data-value") == code);
if (!langItem) {
console.warn(`[${code}] not found in CWS language dropdown, skipping`);
skipped++;
document.body.click();
await sleep(ANIMATION_TIMEOUT);
continue;
}
langItem.click();
await sleep(ANIMATION_TIMEOUT);
const textarea = document.evaluate(
"//textarea[@maxlength='16000']",
document,
null,
XPathResult.FIRST_ORDERED_NODE_TYPE,
null,
).singleNodeValue;
if (!textarea) {
console.error(`[${code}] could not find description textarea`);
skipped++;
continue;
}
textarea.dispatchEvent(new Event("focus"));
textarea.value = description;
textarea.dispatchEvent(new Event("input", { bubbles: true }));
filled++;
console.log(`[${code}] ✓ description filled`);
await sleep(ANIMATION_TIMEOUT);
}
console.log(
`Done! Filled ${filled} locale(s), skipped ${skipped}.` +
` Click "Save draft" to save your changes.`,
);
}Expected file format
The script expects a standard _locales folder structure where each locale’s messages.json contains a storeDesc key:
_locales/
├── en/
│ └── messages.json
├── fr/
│ └── messages.json
├── de/
│ └── messages.json
└── ... (one folder per language)
// Example: _locales/de/messages.json
{
"appName": {
"message": "Dunkelmodus",
"description": "Extension name"
},
"shortDesc": {
"message": "Dunkelmodus für Chrome mit einem Klick.",
"description": "Short description (max 132 chars)"
},
"storeDesc": {
"message": "🔥 Erleben Sie ultimativen visuellen Komfort...",
"description": "Full store description (max 16000 chars)"
}
}The script only reads the storeDesc.message field. If a locale file doesn’t have a storeDesc key, it’s skipped with a console warning.
Locale code mapping
Chrome Web Store uses slightly different locale codes than the _locales folder convention. The script handles these mappings automatically:
_locales code | CWS code | Language |
|---|---|---|
| he | iw | Hebrew |
| zh_CN | zh-CN | Chinese (Simplified) |
| zh_TW | zh-TW | Chinese (Traditional) |
| pt_BR | pt-BR | Portuguese (Brazil) |
| pt_PT | pt-PT | Portuguese (Portugal) |
| es_419 | es-419 | Spanish (Latin America) |
All other locale codes are converted by replacing underscores with hyphens (e.g. pt_BR → pt-BR).
Troubleshooting
Console shows 'Could not find the language dropdown'
Make sure you're on the Store Listing tab (not Package, Privacy, or Distribution). The script looks for the heading 'Current editing language' — if CWS redesigned their UI, the XPath selectors may need updating.
Some locales show '[code] not found in CWS language dropdown, skipping'
This means the locale exists in your _locales folder but isn't available in CWS for your extension. You may need to add the language in the CWS dashboard first (under 'Add a language').
Script runs too fast and misses some languages
The script waits 500ms between each language switch. If your connection is slow, you can increase the ANIMATION_TIMEOUT constant at the top of the script to 1000 or higher.
Chrome blocks pasting in the console
Type allow pasting in the console and press Enter. This is a Chrome security feature that only needs to be done once per tab.
Generating the translated files
Before running the automation script, you need translated messages.json files with the storeDesc key. LocalePack generates these for you:
Go to the store listing translator
Open localepack.app/store-listing and click “Translate Store Listing”.
Enter your listing text
Fill in your extension name (75 chars), short description (132 chars), and full store description (16,000 chars). Character counters show remaining space.
Select languages and pay
Choose which languages to translate into (up to 51 target languages), review the price, and complete checkout.
Download the ZIP
Once translations are complete, download the ZIP. It contains a _locales folder with messages.json per language — ready for the automation script.
Translate your Chrome Web Store listing
LocalePack translates your store listing into up to 51 languages with marketing-quality AI translations. Download the _locales ZIP, run the automation script, and your CWS listing is fully localized in minutes.