Files
factorio-signal-exporter/web/lib/localization.ts
2026-06-04 11:44:20 +02:00

108 lines
2.8 KiB
TypeScript

export type LocaleMap = Map<string, string>;
export type ReverseMap = Map<string, string>;
/** Parses a 3-column CSV (section, item_key, localized_name). */
export function parseCsv(text: string): LocaleMap {
const map: LocaleMap = new Map();
const lines = text.split(/\r?\n/).slice(1);
for (const line of lines) {
if (!line.trim()) continue;
const cols = splitCsvLine(line);
if (cols.length >= 3) map.set(cols[1], cols[2]);
}
return map;
}
function splitCsvLine(line: string): string[] {
const cols: string[] = [];
let cur = '';
let inQuote = false;
for (let i = 0; i < line.length; i++) {
const ch = line[i];
if (ch === '"') {
if (inQuote && line[i + 1] === '"') {
cur += '"';
i++;
} else inQuote = !inQuote;
} else if (ch === ',' && !inQuote) {
cols.push(cur);
cur = '';
} else {
cur += ch;
}
}
cols.push(cur);
return cols;
}
declare global {
var __localeCache: LocaleMap | undefined;
}
async function loadCsv(path: string): Promise<LocaleMap> {
try {
const res = await fetch(path);
return res.ok ? parseCsv(await res.text()) : new Map();
} catch {
return new Map();
}
}
/**
* Fetches and merges DE + EN locale CSVs.
* Result is cached for the lifetime of the page.
*/
export async function getLocaleMap(): Promise<LocaleMap> {
if (globalThis.__localeCache) return globalThis.__localeCache;
const [en, de] = await Promise.all([
loadCsv('/factorio_english_items.csv'),
loadCsv('/factorio_german_items.csv'),
]);
const merged: LocaleMap = new Map([...en, ...de]);
globalThis.__localeCache = merged;
return merged;
}
/** Resolves an `item_key` to its localized display name, falling back to the key itself. */
export function resolveName(key: string, map: LocaleMap): string {
return map.get(key) ?? key;
}
/**
* Builds a reverse lookup: lowercased localized name → item_key.
* Used for normalizing user input back to raw keys.
*/
export function buildReverseMap(map: LocaleMap): ReverseMap {
const rev: ReverseMap = new Map();
for (const [key, name] of map) {
rev.set(name.toLowerCase(), key);
}
return rev;
}
/**
* Resolves a single user-typed token (localized name or raw key) to a raw item_key.
* Falls back to the input itself if no match is found.
*/
export function resolveKey(input: string, rev: ReverseMap): string {
return rev.get(input.toLowerCase()) ?? input;
}
/**
* Applies a regex pattern against both raw item_keys and localized names,
* returning the union of all matching raw item_keys.
*/
export function matchKeys(pattern: string, map: LocaleMap): string[] {
let re: RegExp;
try {
re = new RegExp(pattern, 'i');
} catch {
return [];
}
const result = new Set<string>();
for (const [key, name] of map) {
if (re.test(key) || re.test(name)) result.add(key);
}
return [...result];
}