cjpais/Handy · info

Missing metadata for locale "${code}" in languages.ts

Error message

Missing metadata for locale "${code}" in languages.ts

What it means

At i18n bootstrap, import.meta.glob eagerly loads every src/i18n/locales/<code>/translation.json and turns each directory name into a supported language. This console.warn fires when such a discovered locale code has no matching entry in LANGUAGE_METADATA (src/i18n/languages.ts); the language still works but is listed in the UI with its raw code as both name and nativeName and sorts by that string.

Source

Thrown at src/i18n/index.ts:32

  "./locales/*/translation.json",
  { eager: true },
);

// Build resources from discovered locale files
const resources: Record<string, { translation: Record<string, unknown> }> = {};
for (const [path, module] of Object.entries(localeModules)) {
  const langCode = path.match(/\.\/locales\/(.+)\/translation\.json/)?.[1];
  if (langCode) {
    resources[langCode] = { translation: module.default };
  }
}

// Build supported languages list from discovered locales + metadata
export const SUPPORTED_LANGUAGES = Object.keys(resources)
  .map((code) => {
    const meta = LANGUAGE_METADATA[code];
    if (!meta) {
      console.warn(`Missing metadata for locale "${code}" in languages.ts`);
      return { code, name: code, nativeName: code, priority: undefined };
    }
    return {
      code,
      name: meta.name,
      nativeName: meta.nativeName,
      priority: meta.priority,
    };
  })
  .sort((a, b) => {
    // Sort by priority first (lower = higher), then alphabetically
    if (a.priority !== undefined && b.priority !== undefined) {
      return a.priority - b.priority;
    }
    if (a.priority !== undefined) return -1;
    if (b.priority !== undefined) return 1;
    return a.name.localeCompare(b.name);
  });

View on GitHub (pinned to c89b7bf389)

Solutions

  1. Add a metadata entry { name, nativeName, priority? } to LANGUAGE_METADATA in src/i18n/languages.ts keyed by the exact folder code
  2. Use canonical BCP-47 codes consistently between folder name and metadata key (case-sensitive match)
  3. Add a unit test asserting every SUPPORTED_LANGUAGES entry has name !== code, which catches any future occurrence
  4. Remove stray locale folders that are not intended to ship

Example fix

// before: locales/pt-BR/translation.json exists, languages.ts has no 'pt-BR' key
//   -> console.warn(`Missing metadata for locale "pt-BR" in languages.ts`)

// after: src/i18n/languages.ts
export const LANGUAGE_METADATA: Record<string, LanguageMetadata> = {
  // ...
  "pt-BR": { name: "Portuguese (Brazil)", nativeName: "Português (Brasil)", priority: 6 },
};
Defensive patterns

Strategy: validation

Validate before calling

// Fail the build/test when a locale folder lacks metadata
import localeModules from "virtual:i18n-glob"; // or replicate the glob in a vitest test
const discovered = Object.keys(localeModules).map((p) => p.match(/\.\/locales\/(.+)\/translation\.json/)?.[1]);
const missing = discovered.filter((c) => c && !(c in LANGUAGE_METADATA));
if (missing.length) throw new Error(`Missing LANGUAGE_METADATA for: ${missing.join(", ")}`);

Type guard

const isConfiguredLocale = (code: string): code is keyof typeof LANGUAGE_METADATA =>
  Object.prototype.hasOwnProperty.call(LANGUAGE_METADATA, code);

Prevention

When it happens

Trigger: Adding a new locale folder (e.g. src/i18n/locales/pt-BR/translation.json) without adding a pt-BR entry to LANGUAGE_METADATA; a folder whose code differs from the metadata key in case or hyphenation (zh-tw vs zh-TW); leftover experimental locale directories committed by accident.

Common situations: Translation contributions (the repo explicitly invites them via CONTRIBUTING_TRANSLATIONS.md) forgetting the metadata step; locale renames; copy-pasting a locale folder as a starting point.

Related errors


AI-assisted analysis of cjpais/Handy@c89b7bf389 (2026-08-17). Data as JSON: /api/errors/0a2526b6aa32278d. Report an issue: GitHub.