GoogleChrome/lighthouse · error · Error

could not fetch data for locale: ${locale}

Error message

could not fetch data for locale: ${locale}

What it means

Thrown by the viewer's _swapLocale when the freshly fetched locale-messages value is falsy. _fetchLocaleMessages does fetch('./locales/<locale>.json').json(); if that resolves to null/empty (missing or malformed locale file, failed fetch that still parsed), _swapLocale aborts with the offending locale in the message. It runs only after locale data registration is attempted.

Source

Thrown at viewer/app/src/viewer-ui-features.js:90

    }
  }

  /**
   * @param {LH.Locale} locale
   * @return {Promise<LhlMessages>}
   */
  async _fetchLocaleMessages(locale) {
    const response = await fetch(`./locales/${locale}.json`);
    return response.json();
  }

  /**
   * @param {LH.Locale} locale
   */
  async _swapLocale(locale) {
    const lhlMessages = await this._fetchLocaleMessages(locale);
    const i18nModule = await this._getI18nModule();
    if (!lhlMessages) throw new Error(`could not fetch data for locale: ${locale}`);

    i18nModule.format.registerLocaleData(locale, lhlMessages);
    const newLhr = i18nModule.swapLocale(this.json, locale).lhr;
    this._refreshCallback(newLhr);
  }

  /**
   * The i18n module is only need for swap-locale-feature.js, and is ~30KB,
   * so it is lazily loaded.
   * TODO: reduce the size of the formatting code and include it always (remove lazy load),
   *       possibly moving into base ReportUIFeatures.
   */
  _getI18nModule() {
    return import('../../../shared/localization/i18n-module.js');
  }
}

View on GitHub (pinned to 9515cd4e58)

Solutions

  1. Ensure locales/<locale>.json is present and non-empty in the deployed viewer build for every locale offered in the picker.
  2. Gate the locale picker on availability: verify the locale file is bundled before allowing selection.
  3. On this error, fall back to the current/default locale instead of leaving the UI broken.

Example fix

// before
async _fetchLocaleMessages(locale) {
  const r = await fetch(`./locales/${locale}.json`);
  return r.json(); // may be null/empty -> throws in _swapLocale
}

// after
async _fetchLocaleMessages(locale) {
  const r = await fetch(`./locales/${locale}.json`);
  if (!r.ok) return null;
  const msgs = await r.json();
  return msgs && Object.keys(msgs).length ? msgs : null;
}
Defensive patterns

Strategy: validation

Validate before calling

async _fetchLocaleMessages(locale) {
  const r = await fetch(`./locales/${locale}.json`);
  if (!r.ok) return null;
  const msgs = await r.json();
  return msgs && Object.keys(msgs).length ? msgs : null;
}
// Then only proceed in _swapLocale when the result is non-null.

Type guard

/** @param {unknown} m */
function hasLocaleMessages(m) {
  return m != null && typeof m === 'object' && Object.keys(/** @type {object} */ (m)).length > 0;
}

Try / catch

try {
  await this._swapLocale(locale);
} catch (err) {
  if (err.message.startsWith('could not fetch data for locale')) {
    showMessage(`Locale '${locale}' is unavailable; staying on the current language.`);
  } else throw err;
}

Prevention

When it happens

Trigger: Selecting a locale in the viewer whose locales/<locale>.json file is missing from the build, returns an empty body, or resolves to null/empty — then _swapLocale receives no usable messages.

Common situations: Viewer build that did not bundle the requested locale file; locale filename mismatch (case/region code); served locales directory incomplete; CDN/path misconfiguration so the file 404s but parsing yields a falsy result.

Related errors


AI-assisted analysis of GoogleChrome/lighthouse@9515cd4e58 (2026-08-13). Data as JSON: /api/errors/bae8156b83d21222. Report an issue: GitHub.