export type LocaleMap = Map; export type ReverseMap = Map; /** 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 { 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 { 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(); for (const [key, name] of map) { if (re.test(key) || re.test(name)) result.add(key); } return [...result]; }