import { derived, type Readable } from "svelte/store"; import { json } from "svelte-i18n"; import type { Locale } from "$lib/Locale/layout"; type FormatXMLElementFn = ( parts: Array, ) => R; type InterpolationValue = | string | number | boolean | Date | FormatXMLElementFn | null | undefined; type InterpolationValues = Record | undefined; interface Options { id?: string; locale?: string; format?: string; default?: string; values?: InterpolationValues; } const FALLBACK_LOCALE = "en"; const isPlainObject = (value: unknown): value is Record => typeof value === "object" && value !== null && !Array.isArray(value); const deepMerge = (fallback: unknown, current: unknown): unknown => { if (current === undefined || current === null) return fallback; if (!isPlainObject(current) || !isPlainObject(fallback)) return current; const merged: Record = { ...fallback }; for (const [key, value] of Object.entries(current)) { merged[key] = deepMerge(fallback[key], value); } return merged; }; const createLocale = () => { return derived(json, ($json) => { return (options: Options = {}) => new Proxy( {}, { get(_target, key) { const keyStr = key.toString(); const current = $json(keyStr, options.locale); const currentMissing = current === keyStr || current == null; let resolved: unknown; if (options.locale === FALLBACK_LOCALE) { if (currentMissing) return undefined; resolved = current; } else { const fallback = $json(keyStr, FALLBACK_LOCALE); const fallbackMissing = fallback === keyStr || fallback == null; if (currentMissing && fallbackMissing) return undefined; if (currentMissing) resolved = fallback; else if (fallbackMissing) resolved = current; else resolved = deepMerge(fallback, current); } const replaceValues = ( localisation: InterpolationValues, values: InterpolationValues, ) => { if (typeof localisation !== "object" || localisation === null) return localisation; const updatedLocalisation: InterpolationValues = {}; for (const [k, value] of Object.entries(localisation)) { if (typeof value === "string") { updatedLocalisation[k] = value.replace( /\{(\w+)\}/g, (match, name) => (values ? values[name] : match) as string, ); } else { updatedLocalisation[k] = replaceValues( value as InterpolationValues, values, ) as InterpolationValue; } } return updatedLocalisation; }; if (options.values) return replaceValues( resolved as unknown as InterpolationValues, options.values, ); return resolved; }, }, ); }) as Readable<(options?: Options) => Locale>; }; const locale = createLocale(); export default locale;