Files
biosiasandClaude Sonnet 5 9e8af56069 Make config.js truly optional, falling back to config-default.js (AI)
A plain static `import ... from './config.js'` can't gracefully
degrade if that file doesn't exist -- a missing module breaks the
whole graph before any application code can catch it. So the four
consumers (discovery.js, i18n.js, templates.js, router.js) now import
BASE_URL/MARKED_URL/ARTICLES_URL from config-default.js instead, which
tries a dynamic import() of the optional config.js (catchable, unlike
a static import) and merges in whatever it exports, falling back to
its own shipped defaults for anything config.js doesn't override --
or doesn't exist at all. Top-level await makes every importing module
wait for that resolution first, same as a plain synchronous export.

config.js itself is now just an optional, empty-by-default file: only
export the specific constant(s) you want to change. Updated the
README's "Local configuration" section (and its three cross-references
in Running locally / Embedding in an existing page / Pointing
articles/ somewhere else) to drop the now-unnecessary "copy this file
before first use" step entirely.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AXSVPS3UmmASiiprPfcnUd
2026-09-12 16:51:18 +02:00

54 lines
2.2 KiB
JavaScript

// UI string lookup, so every user-facing message lives in a translatable
// JSON file (strings/{locale}.json) instead of being hardcoded in the
// JS. English (strings/en.json) is always fetched first as the
// fallback set; if the page's declared language (<html lang>) names another
// locale, that file is fetched too and merged on top — a translation file
// only needs to cover the keys it actually translates, missing ones still
// render in English rather than breaking.
import { BASE_URL } from './config-default.js';
const FALLBACK_LOCALE = 'en';
let strings = null;
let locale = FALLBACK_LOCALE;
async function fetchStrings(code) {
try {
const response = await fetch(`${BASE_URL}strings/${code}.json`);
if (!response.ok) return null;
return await response.json();
} catch {
return null;
}
}
// Must be awaited before the first render — see router.js's `ready` chain —
// since t()/getLocale() below assume this has already resolved.
export async function loadStrings() {
const fallback = await fetchStrings(FALLBACK_LOCALE);
const requested = (document.documentElement.lang || FALLBACK_LOCALE).trim().toLowerCase().split('-')[0];
if (requested === FALLBACK_LOCALE || !requested) {
strings = fallback;
locale = FALLBACK_LOCALE;
return;
}
const translated = await fetchStrings(requested);
strings = translated ? { ...fallback, ...translated } : fallback;
locale = translated ? requested : FALLBACK_LOCALE;
}
// The resolved locale code (e.g. "en", "cs") — used for Intl.* calls
// elsewhere so dates/sorting/search match whatever language is displayed.
export function getLocale() {
return locale;
}
// Looks up `key` and substitutes any `{name}` placeholders from `params`.
// Falls back to the raw key if strings never loaded (e.g. en.json itself
// failed to fetch) — a visible-but-not-crashing degradation, same spirit as
// the rest of this codebase's silent-skip fallbacks.
export function t(key, params = {}) {
const template = strings?.[key] ?? key;
return template.replace(/\{(\w+)\}/g, (match, name) => (params[name] != null ? String(params[name]) : match));
}