feat(app): activeLocale-store mit persistence + initial-detection
This commit is contained in:
parent
f799223836
commit
8f513495e3
|
|
@ -0,0 +1,65 @@
|
|||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
import { detectInitialLocale } from './activeLocale';
|
||||
|
||||
describe('detectInitialLocale', () => {
|
||||
beforeEach(() => {
|
||||
globalThis.localStorage?.clear?.();
|
||||
});
|
||||
|
||||
it('nimmt wert aus localStorage, wenn vorhanden und gültig', () => {
|
||||
const storage = new Map<string, string>([['locale', 'en']]);
|
||||
expect(detectInitialLocale({
|
||||
storage: {
|
||||
getItem: (k) => storage.get(k) ?? null,
|
||||
setItem: () => {}
|
||||
},
|
||||
navigatorLanguage: 'de-DE',
|
||||
supported: ['de', 'en']
|
||||
})).toBe('en');
|
||||
});
|
||||
|
||||
it('fällt auf navigator.language zurück, wenn storage leer', () => {
|
||||
expect(detectInitialLocale({
|
||||
storage: {
|
||||
getItem: () => null,
|
||||
setItem: () => {}
|
||||
},
|
||||
navigatorLanguage: 'en-US',
|
||||
supported: ['de', 'en']
|
||||
})).toBe('en');
|
||||
});
|
||||
|
||||
it('normalisiert navigator.language (de-AT → de)', () => {
|
||||
expect(detectInitialLocale({
|
||||
storage: {
|
||||
getItem: () => null,
|
||||
setItem: () => {}
|
||||
},
|
||||
navigatorLanguage: 'de-AT',
|
||||
supported: ['de', 'en']
|
||||
})).toBe('de');
|
||||
});
|
||||
|
||||
it('fällt auf ersten supported eintrag, wenn navigator unbekannt', () => {
|
||||
expect(detectInitialLocale({
|
||||
storage: {
|
||||
getItem: () => null,
|
||||
setItem: () => {}
|
||||
},
|
||||
navigatorLanguage: 'fr-FR',
|
||||
supported: ['de', 'en']
|
||||
})).toBe('de');
|
||||
});
|
||||
|
||||
it('ignoriert ungültige werte im storage', () => {
|
||||
const storage = new Map<string, string>([['locale', 'fr']]);
|
||||
expect(detectInitialLocale({
|
||||
storage: {
|
||||
getItem: (k) => storage.get(k) ?? null,
|
||||
setItem: () => {}
|
||||
},
|
||||
navigatorLanguage: 'en-US',
|
||||
supported: ['de', 'en']
|
||||
})).toBe('en');
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
import { writable, type Writable } from 'svelte/store';
|
||||
|
||||
export type SupportedLocale = 'de' | 'en';
|
||||
export const SUPPORTED_LOCALES: readonly SupportedLocale[] = ['de', 'en'] as const;
|
||||
const STORAGE_KEY = 'locale';
|
||||
|
||||
interface Storage {
|
||||
getItem: (key: string) => string | null;
|
||||
setItem: (key: string, value: string) => void;
|
||||
}
|
||||
|
||||
export interface DetectArgs {
|
||||
storage: Storage;
|
||||
navigatorLanguage: string | undefined;
|
||||
supported: readonly string[];
|
||||
}
|
||||
|
||||
export function detectInitialLocale(args: DetectArgs): SupportedLocale {
|
||||
const stored = args.storage.getItem(STORAGE_KEY);
|
||||
if (stored && (args.supported as readonly string[]).includes(stored)) {
|
||||
return stored as SupportedLocale;
|
||||
}
|
||||
const nav = (args.navigatorLanguage ?? '').slice(0, 2).toLowerCase();
|
||||
if ((args.supported as readonly string[]).includes(nav)) {
|
||||
return nav as SupportedLocale;
|
||||
}
|
||||
return args.supported[0] as SupportedLocale;
|
||||
}
|
||||
|
||||
function createActiveLocale(): Writable<SupportedLocale> & { bootstrap: () => void } {
|
||||
const store = writable<SupportedLocale>('de');
|
||||
let bootstrapped = false;
|
||||
|
||||
function bootstrap() {
|
||||
if (bootstrapped) return;
|
||||
bootstrapped = true;
|
||||
if (typeof window === 'undefined') return;
|
||||
const initial = detectInitialLocale({
|
||||
storage: window.localStorage,
|
||||
navigatorLanguage: window.navigator.language,
|
||||
supported: SUPPORTED_LOCALES
|
||||
});
|
||||
store.set(initial);
|
||||
store.subscribe((v) => {
|
||||
try {
|
||||
window.localStorage.setItem(STORAGE_KEY, v);
|
||||
} catch {
|
||||
// private-mode / quota — ignorieren
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
subscribe: store.subscribe,
|
||||
set: store.set,
|
||||
update: store.update,
|
||||
bootstrap
|
||||
};
|
||||
}
|
||||
|
||||
export const activeLocale = createActiveLocale();
|
||||
Loading…
Reference in New Issue