fix: normalize i18n registry lookup keys

Issue: #62
User-Visible: no
This commit is contained in:
Matysh
2026-08-28 02:55:22 +03:00
parent d0d6cf57a7
commit c755a0efc4
5 changed files with 36 additions and 19 deletions
File diff suppressed because one or more lines are too long
+2 -2
View File
File diff suppressed because one or more lines are too long
+11 -11
View File
@@ -2,7 +2,7 @@
"version": 1,
"fixture": "synthetic-only",
"chromium": "151.0.7922.34",
"sourceFingerprint": "63fd6b17b9c24cb2f35753e6632c7be08208a5c279978306f7c6d101a0c779d1",
"sourceFingerprint": "3e3153d6bbd75a97ba780b58b76a7231df7d1734a35bbcdce7d456cc9d7cc5e0",
"captureScriptSha256": "ce2e9542fed9dade3085be87d16f69adb2ac8262893ad78ad966b1b9673f2983",
"command": "npm run build && node demo/docs/capture.mjs",
"scenarios": {
@@ -14,7 +14,7 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "63fd6b17b9c24cb2f35753e6632c7be08208a5c279978306f7c6d101a0c779d1",
"sourceSha256": "3e3153d6bbd75a97ba780b58b76a7231df7d1734a35bbcdce7d456cc9d7cc5e0",
"imageSha256": "36223106c073f07d8cc3ecf8eaab37192ebb2687daba65c5c21047d0b7890de0"
},
"view-touch": {
@@ -25,7 +25,7 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "63fd6b17b9c24cb2f35753e6632c7be08208a5c279978306f7c6d101a0c779d1",
"sourceSha256": "3e3153d6bbd75a97ba780b58b76a7231df7d1734a35bbcdce7d456cc9d7cc5e0",
"imageSha256": "41e3ba67f8db0e98f26f484293af83ef937c369ca5ca6a59a3350d8954c906f4"
},
"space-create": {
@@ -36,7 +36,7 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "63fd6b17b9c24cb2f35753e6632c7be08208a5c279978306f7c6d101a0c779d1",
"sourceSha256": "3e3153d6bbd75a97ba780b58b76a7231df7d1734a35bbcdce7d456cc9d7cc5e0",
"imageSha256": "0dc024726327f60f4a9ceaf3044381691f81f1090af81d1812e870f22d9343ba"
},
"room-contour-close": {
@@ -47,7 +47,7 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "63fd6b17b9c24cb2f35753e6632c7be08208a5c279978306f7c6d101a0c779d1",
"sourceSha256": "3e3153d6bbd75a97ba780b58b76a7231df7d1734a35bbcdce7d456cc9d7cc5e0",
"imageSha256": "44cfaa95bd51e2cd628400e28db0ad8b2f0cd904385845bd402494f3f5c0d93c"
},
"plan-context-tray": {
@@ -58,7 +58,7 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "63fd6b17b9c24cb2f35753e6632c7be08208a5c279978306f7c6d101a0c779d1",
"sourceSha256": "3e3153d6bbd75a97ba780b58b76a7231df7d1734a35bbcdce7d456cc9d7cc5e0",
"imageSha256": "e0662429b423fb74151b583dcc2c8635b001b637d03bbf7a6b16aec46399c3f8"
},
"device-editor": {
@@ -69,7 +69,7 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "63fd6b17b9c24cb2f35753e6632c7be08208a5c279978306f7c6d101a0c779d1",
"sourceSha256": "3e3153d6bbd75a97ba780b58b76a7231df7d1734a35bbcdce7d456cc9d7cc5e0",
"imageSha256": "d0ffd31ce80bfde21ab75da356a5fc1af38246f2b301030880320620c228d89d"
},
"device-display-preview": {
@@ -80,7 +80,7 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "63fd6b17b9c24cb2f35753e6632c7be08208a5c279978306f7c6d101a0c779d1",
"sourceSha256": "3e3153d6bbd75a97ba780b58b76a7231df7d1734a35bbcdce7d456cc9d7cc5e0",
"imageSha256": "2cdabae1f89c3286e4fac0ce30f757ee1690b707ab8a5488748b7cd420626160"
},
"background-editor": {
@@ -91,7 +91,7 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "63fd6b17b9c24cb2f35753e6632c7be08208a5c279978306f7c6d101a0c779d1",
"sourceSha256": "3e3153d6bbd75a97ba780b58b76a7231df7d1734a35bbcdce7d456cc9d7cc5e0",
"imageSha256": "30147bb00a90eea7136b4cee30995f6e6a9217b5132f3e8d3ad7471413b1af8a"
},
"room-card": {
@@ -102,7 +102,7 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "63fd6b17b9c24cb2f35753e6632c7be08208a5c279978306f7c6d101a0c779d1",
"sourceSha256": "3e3153d6bbd75a97ba780b58b76a7231df7d1734a35bbcdce7d456cc9d7cc5e0",
"imageSha256": "029a3e69ec647a8a370d99e6bb7f9225833c526739076022f6b52ba54bff30ea"
},
"device-info": {
@@ -113,7 +113,7 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "63fd6b17b9c24cb2f35753e6632c7be08208a5c279978306f7c6d101a0c779d1",
"sourceSha256": "3e3153d6bbd75a97ba780b58b76a7231df7d1734a35bbcdce7d456cc9d7cc5e0",
"imageSha256": "dd492f53150b7149085daada5cce9eeae9bde9e7ea1d86679a54b3041f72f517"
}
}
+9 -4
View File
@@ -23,10 +23,6 @@ export type Lang = (typeof LANGUAGE_REGISTRY)[number]['code'];
export const FALLBACK_LANGUAGE_CODE: Lang = 'en';
export const FALLBACK_DICTIONARY = en;
const LANGUAGE_BY_CODE = new Map<string, LanguageEntry>(
LANGUAGE_REGISTRY.map((entry) => [entry.code, entry]),
);
/** Normalize HA locale tags for registry lookup. */
export function normalizeLanguageTag(value: unknown): string {
return typeof value === 'string'
@@ -34,6 +30,15 @@ export function normalizeLanguageTag(value: unknown): string {
: '';
}
/** Build a case-insensitive BCP 47 lookup while retaining canonical codes. */
export function buildLanguageLookup<T extends { code: string }>(
entries: readonly T[],
): ReadonlyMap<string, T> {
return new Map(entries.map((entry) => [normalizeLanguageTag(entry.code), entry]));
}
const LANGUAGE_BY_CODE = buildLanguageLookup(LANGUAGE_REGISTRY);
/** Return the canonical registry entry for a locale code, if it is shipped. */
export function languageEntry(value: unknown): LanguageEntry | undefined {
return LANGUAGE_BY_CODE.get(normalizeLanguageTag(value));
+12
View File
@@ -5,6 +5,8 @@ import { readdirSync, readFileSync } from 'node:fs';
import {
FALLBACK_LANGUAGE_CODE,
LANGUAGE_REGISTRY,
buildLanguageLookup,
languageEntry,
languageOptions,
normalizeLanguageTag,
resolveLanguageCode,
@@ -38,6 +40,16 @@ test('i18n: registry codes and English fallback are valid', () => {
}
});
test('i18n: canonical regional codes use normalized lookup keys', () => {
const regional = { code: 'pt-BR', nativeLabel: 'Português (Brasil)', dictionary: {} };
const lookup = buildLanguageLookup([regional]);
assert.equal(lookup.get('pt-br'), regional);
assert.equal(lookup.get(normalizeLanguageTag('PT_br')), regional);
for (const entry of LANGUAGE_REGISTRY) {
assert.equal(languageEntry(entry.code.toUpperCase()), entry);
}
});
test('i18n: registry matches frontend and backend locale files', () => {
const registryCodes = LANGUAGE_REGISTRY.map(({ code }) => code).sort();
const localeCodes = (url) => readdirSync(url)