75 lines
2.3 KiB
TypeScript
75 lines
2.3 KiB
TypeScript
import lobbyCatalog from '../../../shared/i18n/lobby.json';
|
|
|
|
export type LobbyCatalog = typeof lobbyCatalog;
|
|
export type SupportedLocale = LobbyCatalog['locales']['supported'][number];
|
|
|
|
export const LOBBY_I18N_CATALOG = lobbyCatalog;
|
|
export const DEFAULT_LOCALE = lobbyCatalog.locales.default as SupportedLocale;
|
|
export const SUPPORTED_LOCALES = lobbyCatalog.locales.supported as readonly SupportedLocale[];
|
|
|
|
export function normalizeLocale(rawLocale?: string | null): SupportedLocale {
|
|
const locale = (rawLocale ?? '').trim().toLowerCase().replace(/_/g, '-');
|
|
if ((SUPPORTED_LOCALES as readonly string[]).includes(locale)) {
|
|
return locale as SupportedLocale;
|
|
}
|
|
|
|
const shortLocale = locale.split('-')[0] ?? '';
|
|
if ((SUPPORTED_LOCALES as readonly string[]).includes(shortLocale)) {
|
|
return shortLocale as SupportedLocale;
|
|
}
|
|
|
|
return DEFAULT_LOCALE;
|
|
}
|
|
|
|
export function translateCatalogPath(
|
|
root: Record<string, unknown>,
|
|
keyPath: string,
|
|
locale: string,
|
|
fallback = DEFAULT_LOCALE,
|
|
): string {
|
|
const normalizedLocale = normalizeLocale(locale);
|
|
const segments = keyPath.split('.');
|
|
|
|
let cursor: unknown = root;
|
|
for (const segment of segments) {
|
|
if (!cursor || typeof cursor !== 'object' || !(segment in (cursor as Record<string, unknown>))) {
|
|
return keyPath;
|
|
}
|
|
cursor = (cursor as Record<string, unknown>)[segment];
|
|
}
|
|
|
|
if (!cursor || typeof cursor !== 'object') {
|
|
return keyPath;
|
|
}
|
|
|
|
const translations = cursor as Record<string, string>;
|
|
return translations[normalizedLocale] ?? translations[fallback] ?? keyPath;
|
|
}
|
|
|
|
export function collectLocaleParityIssues(
|
|
node: unknown,
|
|
locales: readonly string[] = SUPPORTED_LOCALES,
|
|
path = '',
|
|
): string[] {
|
|
if (!node || typeof node !== 'object') {
|
|
return [];
|
|
}
|
|
|
|
const record = node as Record<string, unknown>;
|
|
const keys = Object.keys(record);
|
|
const isTranslationLeaf = keys.length > 0 && locales.every((locale) => locale in record);
|
|
|
|
if (isTranslationLeaf) {
|
|
const issues: string[] = [];
|
|
for (const locale of locales) {
|
|
const value = record[locale];
|
|
if (typeof value !== 'string' || !value.trim()) {
|
|
issues.push(`${path || '<root>'} missing non-empty '${locale}' translation`);
|
|
}
|
|
}
|
|
return issues;
|
|
}
|
|
|
|
return keys.flatMap((key) => collectLocaleParityIssues(record[key], locales, path ? `${path}.${key}` : key));
|
|
}
|