Initial web
This commit is contained in:
103
web/lib/localization.ts
Normal file
103
web/lib/localization.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
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];
|
||||
}
|
||||
Reference in New Issue
Block a user