fix(i18n): ein werfender Hörer hält die übrigen beim Sprachwechsel nicht auf
setLang rief die Hörer in einer Schleife ohne Schutz: warf einer (etwa beim Neuaufbau der Kopfzeile), blieben die folgenden ungerufen und der Aufrufer bekam den Fehler zu sehen, obwohl die Sprache schon umgestellt war. - i18n.ts: jeder Hörer läuft in try/catch; der Fehler geht mit console.error in die Konsole, der nächste Hörer folgt. - i18n.ts: der Parameter `browserLanguages` von initialLang hieß wie die Funktion darunter und verdeckte sie; jetzt `languages`. - Tests: ein werfender Hörer vor, zwischen und nach anderen: alle übrigen laufen, setLang wirft nicht, Sprache und <html lang> stehen, der Fehler steht einmal je Aufruf in der Konsole, der werfende Hörer bleibt angemeldet. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
0bcdfe258d
commit
1fd08d6373
2 files changed
+51
-4
No files matched your search
@@ -179,6 +179,45 @@ describe('onLangChange', () => {
|
||||
expect(calls).toEqual(['en']);
|
||||
});
|
||||
|
||||
it('ein Hörer, der wirft, hält die späteren nicht auf: die Sprache steht, der Fehler geht in die Konsole', () => {
|
||||
const error = vi.spyOn(console, 'error').mockImplementation(() => {});
|
||||
const boom = new Error('boom');
|
||||
const before = listen();
|
||||
stops.push(
|
||||
onLangChange(() => {
|
||||
throw boom;
|
||||
}),
|
||||
);
|
||||
const after = listen();
|
||||
expect(() => setLang('en')).not.toThrow();
|
||||
expect(currentLang()).toBe('en');
|
||||
expect(document.documentElement.lang).toBe('en');
|
||||
expect(before).toEqual(['en']);
|
||||
expect(after).toEqual(['en']);
|
||||
expect(error).toHaveBeenCalledTimes(1);
|
||||
expect(error).toHaveBeenCalledWith(expect.any(String), boom);
|
||||
// der werfende Hörer bleibt angemeldet und stört auch den nächsten Wechsel nicht
|
||||
expect(() => setLang('de')).not.toThrow();
|
||||
expect(after).toEqual(['en', 'de']);
|
||||
expect(error).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('mehrere werfende Hörer: jeder wird gemeldet, alle übrigen laufen', () => {
|
||||
const error = vi.spyOn(console, 'error').mockImplementation(() => {});
|
||||
const calls: string[] = [];
|
||||
for (const name of ['a', 'b', 'c']) {
|
||||
stops.push(
|
||||
onLangChange(() => {
|
||||
calls.push(name);
|
||||
if (name !== 'b') throw new Error(name);
|
||||
}),
|
||||
);
|
||||
}
|
||||
setLang('en');
|
||||
expect(calls).toEqual(['a', 'b', 'c']);
|
||||
expect(error).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('ein Hörer darf sich beim Melden abmelden, die übrigen werden trotzdem gerufen', () => {
|
||||
const calls: string[] = [];
|
||||
const stopA = onLangChange(() => {
|
||||
|
||||
+12
-4
@@ -11,9 +11,9 @@ export const LANG_KEY = 'aircard.lang';
|
||||
* Startsprache: die gespeicherte Wahl (`de` oder `en`), sonst Deutsch, wenn die erste Browsersprache mit `de` beginnt,
|
||||
* andernfalls Englisch. Dieselbe Regel steht im Skript in index.html, das <html lang> vor dem ersten Paint setzt.
|
||||
*/
|
||||
export function initialLang(stored: string | null, browserLanguages: readonly string[]): Lang {
|
||||
export function initialLang(stored: string | null, languages: readonly string[]): Lang {
|
||||
if (stored === 'de' || stored === 'en') return stored;
|
||||
return (browserLanguages[0] ?? '').toLowerCase().startsWith('de') ? 'de' : 'en';
|
||||
return (languages[0] ?? '').toLowerCase().startsWith('de') ? 'de' : 'en';
|
||||
}
|
||||
|
||||
function readStored(): string | null {
|
||||
@@ -42,7 +42,8 @@ export function currentLang(): Lang {
|
||||
|
||||
/**
|
||||
* Stellt die Sprache um: setzt <html lang>, merkt sie sich (außer mit `persist: false`) und meldet einen echten Wechsel an
|
||||
* die Hörer. Das Neuzeichnen von Kopfzeile und Seite ist Sache der Hörer.
|
||||
* die Hörer. Das Neuzeichnen von Kopfzeile und Seite ist Sache der Hörer; wirft einer, kommt der Fehler in die Konsole und die
|
||||
* übrigen werden trotzdem gerufen.
|
||||
*/
|
||||
export function setLang(next: Lang, opts: { persist?: boolean } = {}): void {
|
||||
const changed = next !== currentLang();
|
||||
@@ -55,8 +56,15 @@ export function setLang(next: Lang, opts: { persist?: boolean } = {}): void {
|
||||
// gilt dann nur für diese Sitzung
|
||||
}
|
||||
}
|
||||
if (!changed) return;
|
||||
// Kopie, damit sich ein Hörer beim Melden abmelden darf.
|
||||
if (changed) for (const fn of [...listeners]) fn(next);
|
||||
for (const fn of [...listeners]) {
|
||||
try {
|
||||
fn(next);
|
||||
} catch (err) {
|
||||
console.error('Ein Hörer des Sprachwechsels ist gescheitert', err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Meldet jeden Sprachwechsel (nicht den bloßen Aufruf mit der aktuellen Sprache). Gibt die Abmeldung zurück. */
|
||||
|
||||
Reference in new issue
Block a user