diff options
| author | Fuwn <[email protected]> | 2026-05-24 14:08:46 +0000 |
|---|---|---|
| committer | Fuwn <[email protected]> | 2026-05-24 14:08:46 +0000 |
| commit | c7abe99c138f9bdcf417808b12361d1da2e18e58 (patch) | |
| tree | 198f62a4428190dfc8e6f3a63bb0ed7619620453 /src | |
| parent | refactor(locale): move hardcoded UI strings into english locale (diff) | |
| download | due.moe-c7abe99c138f9bdcf417808b12361d1da2e18e58.tar.xz due.moe-c7abe99c138f9bdcf417808b12361d1da2e18e58.zip | |
fix(locale): deep-merge fallback into partial namespace returns
svelte-i18n's getJSON returns the active locale's sub-tree verbatim;
nested keys missing in that locale do not auto-fall-back to the
fallbackLocale. The locale store now deep-merges the English value
into the resolved object so JP users see English for any sub-key the
JA dictionary omits (instead of blanks).
Diffstat (limited to 'src')
| -rw-r--r-- | src/stores/locale.ts | 45 |
1 files changed, 38 insertions, 7 deletions
diff --git a/src/stores/locale.ts b/src/stores/locale.ts index 826d717c..1b94ea96 100644 --- a/src/stores/locale.ts +++ b/src/stores/locale.ts @@ -25,6 +25,22 @@ interface Options { values?: InterpolationValues; } +const FALLBACK_LOCALE = "en"; + +const isPlainObject = (value: unknown): value is Record<string, unknown> => + 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<string, unknown> = { ...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 = {}) => @@ -32,9 +48,24 @@ const createLocale = () => { {}, { get(_target, key) { - const localisation = $json(key.toString(), options.locale); + 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 (localisation === key.toString()) return undefined; + if (currentMissing && fallbackMissing) return undefined; + if (currentMissing) resolved = fallback; + else if (fallbackMissing) resolved = current; + else resolved = deepMerge(fallback, current); + } const replaceValues = ( localisation: InterpolationValues, @@ -45,14 +76,14 @@ const createLocale = () => { const updatedLocalisation: InterpolationValues = {}; - for (const [key, value] of Object.entries(localisation)) { + for (const [k, value] of Object.entries(localisation)) { if (typeof value === "string") { - updatedLocalisation[key] = value.replace( + updatedLocalisation[k] = value.replace( /\{(\w+)\}/g, (match, name) => (values ? values[name] : match) as string, ); } else { - updatedLocalisation[key] = replaceValues( + updatedLocalisation[k] = replaceValues( value as InterpolationValues, values, ) as InterpolationValue; @@ -64,11 +95,11 @@ const createLocale = () => { if (options.values) return replaceValues( - localisation as unknown as InterpolationValues, + resolved as unknown as InterpolationValues, options.values, ); - return localisation; + return resolved; }, }, ); |