From ffa6b176d02fb820d28da9173ff7d68f9b8e8ae2 Mon Sep 17 00:00:00 2001 From: jelveh Date: Thu, 1 Oct 2026 22:03:03 -0700 Subject: [PATCH] Stop double-encoding translated toolbar labels i18n() already HTML-encodes its result, so wrapping it in escape() again turned accented labels into literal entities (e.g. "Schließen") for screen readers, and .text() did the same for the error message. --- .../src/UI/Dashboard/AppBrowser.browser.test.js | 11 ++++++++++- src/gui/src/toolbarEmbed.js | 14 +++++++------- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/gui/src/UI/Dashboard/AppBrowser.browser.test.js b/src/gui/src/UI/Dashboard/AppBrowser.browser.test.js index 9ef08e3f2..f80afcde5 100644 --- a/src/gui/src/UI/Dashboard/AppBrowser.browser.test.js +++ b/src/gui/src/UI/Dashboard/AppBrowser.browser.test.js @@ -202,9 +202,10 @@ describe('embedded app browser', () => { describe('embedded toolbar', () => { - async function openToolbar (viewport = { width: 1100, height: 720 }, token = sessionToken) { + async function openToolbar (viewport = { width: 1100, height: 720 }, token = sessionToken, storage = []) { const context = await browser.newContext({ viewport, storageState: { cookies: [], origins: [{ origin, localStorage: [ + ...storage, { name: 'auth_token_v2', value: token }, { name: 'logged_in_users', value: JSON.stringify([ { uuid: 'first', username: 'First', auth_token: sessionToken }, @@ -266,6 +267,14 @@ describe('embedded toolbar', () => { } finally { await context.close(); } }); + it('labels controls with translated text encoded once', async () => { + const { context, toolbar } = await openToolbar(undefined, sessionToken, [{ name: 'user_preferences', value: '{"language":"de"}' }]); + try { + await toolbar.getByRole('button', { name: 'Konto', exact: true }).click(); + await toolbar.getByRole('button', { name: 'Schließen', exact: true }).waitFor(); + } finally { await context.close(); } + }); + it('opens upgrade, settings, and add-account in new tabs', async () => { const { context, page, toolbar } = await openToolbar(); try { diff --git a/src/gui/src/toolbarEmbed.js b/src/gui/src/toolbarEmbed.js index ab6cff8fd..2a5ec6e2a 100644 --- a/src/gui/src/toolbarEmbed.js +++ b/src/gui/src/toolbarEmbed.js @@ -58,13 +58,13 @@ function showPanel (next) { closePanel(); panel = next; $root.find(`[data-panel="${next}"]`).attr('aria-expanded', 'true'); - $root.append(`
-
${i18n(next === 'apps' ? 'toolbar_apps' : 'account')}
+ $root.append(`
+
${i18n(next === 'apps' ? 'toolbar_apps' : 'account')}
`); $root.find('.toolbar-close').on('click', closePanel); const $content = $root.find('.toolbar-panel-content'); if ( next === 'apps' ) { - $content.html(``); + $content.html(``); $content.find('iframe').on('load', event => { const frameDocument = event.currentTarget.contentDocument; frameDocument?.addEventListener('keydown', keyEvent => { @@ -109,7 +109,7 @@ async function accountAction (action) { busy = true; $root.find('.toolbar-account, .toolbar-logout, .toolbar-confirm-logout').prop('disabled', true); try { await action(); } - catch { $root.find('.toolbar-error').text(i18n('toolbar_account_error')).prop('hidden', false); } + catch { $root.find('.toolbar-error').html(i18n('toolbar_account_error')).prop('hidden', false); } finally { busy = false; $root.find('.toolbar-account, .toolbar-logout, .toolbar-confirm-logout').prop('disabled', false); @@ -148,10 +148,10 @@ async function mount () { if ( !sessionToken ) throw new Error('Missing session'); user = await (await request('/whoami')).json(); if ( !user.uuid ) throw new Error('Missing account'); - $root.html(`