Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,12 @@
"pad.importExport.exportword": "Microsoft Word",
"pad.importExport.exportpdf": "PDF",
"pad.importExport.exportopen": "ODF (Open Document Format)",
"pad.importExport.exportetherpada.title": "Export as Etherpad",
"pad.importExport.exporthtmla.title": "Export as HTML",
"pad.importExport.exportplaina.title": "Export as plain text",
"pad.importExport.exportworda.title": "Export as Microsoft Word",
"pad.importExport.exportpdfa.title": "Export as PDF",
"pad.importExport.exportopena.title": "Export as ODF (Open Document Format)",
"pad.importExport.noConverter.innerHTML": "You can only import from plain text or HTML formats. For more advanced import features, please <a href=\"https://github.com/ether/etherpad-lite/wiki/How-to-enable-importing-and-exporting-different-file-formats-with-AbiWord\">install LibreOffice</a>.",

"pad.modals.connected": "Connected.",
Expand Down
24 changes: 12 additions & 12 deletions src/templates/pad.html
Original file line number Diff line number Diff line change
Expand Up @@ -323,23 +323,23 @@ <h2 data-l10n-id="pad.importExport.import"></h2>
<div id="exportColumn">
<h2 data-l10n-id="pad.importExport.export"></h2>
<% e.begin_block("exportColumn"); %>
<a id="exportetherpada" target="_blank" class="exportlink">
<span class="exporttype buttonicon buttonicon-file-powerpoint" id="exportetherpad" data-l10n-id="pad.importExport.exportetherpad"></span>
<a id="exportetherpada" data-l10n-id="pad.importExport.exportetherpada.title" target="_blank" rel="noopener" class="exportlink">
<span class="exporttype buttonicon buttonicon-file-powerpoint" id="exportetherpad" data-l10n-id="pad.importExport.exportetherpad" aria-hidden="true"></span>
</a>
<a id="exporthtmla" target="_blank" class="exportlink">
<span class="exporttype buttonicon buttonicon-file-code" id="exporthtml" data-l10n-id="pad.importExport.exporthtml"></span>
<a id="exporthtmla" data-l10n-id="pad.importExport.exporthtmla.title" target="_blank" rel="noopener" class="exportlink">
<span class="exporttype buttonicon buttonicon-file-code" id="exporthtml" data-l10n-id="pad.importExport.exporthtml" aria-hidden="true"></span>
</a>
<a id="exportplaina" target="_blank" class="exportlink">
<span class="exporttype buttonicon buttonicon-file" id="exportplain" data-l10n-id="pad.importExport.exportplain"></span>
<a id="exportplaina" data-l10n-id="pad.importExport.exportplaina.title" target="_blank" rel="noopener" class="exportlink">
<span class="exporttype buttonicon buttonicon-file" id="exportplain" data-l10n-id="pad.importExport.exportplain" aria-hidden="true"></span>
</a>
<a id="exportworda" target="_blank" class="exportlink">
<span class="exporttype buttonicon buttonicon-file-word" id="exportword" data-l10n-id="pad.importExport.exportword"></span>
<a id="exportworda" data-l10n-id="pad.importExport.exportworda.title" target="_blank" rel="noopener" class="exportlink">
<span class="exporttype buttonicon buttonicon-file-word" id="exportword" data-l10n-id="pad.importExport.exportword" aria-hidden="true"></span>
</a>
<a id="exportpdfa" target="_blank" class="exportlink">
<span class="exporttype buttonicon buttonicon-file-pdf" id="exportpdf" data-l10n-id="pad.importExport.exportpdf"></span>
<a id="exportpdfa" data-l10n-id="pad.importExport.exportpdfa.title" target="_blank" rel="noopener" class="exportlink">
<span class="exporttype buttonicon buttonicon-file-pdf" id="exportpdf" data-l10n-id="pad.importExport.exportpdf" aria-hidden="true"></span>
</a>
<a id="exportopena" target="_blank" class="exportlink">
<span class="exporttype buttonicon buttonicon-file-alt" id="exportopen" data-l10n-id="pad.importExport.exportopen"></span>
<a id="exportopena" data-l10n-id="pad.importExport.exportopena.title" target="_blank" rel="noopener" class="exportlink">
<span class="exporttype buttonicon buttonicon-file-alt" id="exportopen" data-l10n-id="pad.importExport.exportopen" aria-hidden="true"></span>
</a>
<% e.end_block(); %>
</div>
Expand Down
41 changes: 25 additions & 16 deletions src/tests/frontend-new/specs/a11y_dialogs.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import {expect, test} from '@playwright/test';
import {goToNewPad} from '../helper/padHelper';

// Pin browser locale so html10n picks the English bundle. Several
// assertions in this file compare against specific English strings
// (e.g. "Close chat", "Export as Etherpad"); without this, translatewiki
// updates would localise those strings and break the suite.
test.use({locale: 'en-US'});

test.beforeEach(async ({page}) => {
await goToNewPad(page);
});
Expand Down Expand Up @@ -65,28 +71,31 @@ test('users popup closes on Escape even when focus is outside the popup', async
await expect(dialog).not.toHaveClass(/popup-show/);
});

test('export links have an accessible name from their localized content', async ({page}) => {
test('export links have a localized aria-label and matching title', async ({page}) => {
await page.locator('button[data-l10n-id="pad.toolbar.import_export.title"]').click();
// The Word/PDF/ODF export links are removed client-side by pad_impexp.ts
// when soffice is not configured, so only assert on links that the
// environment actually renders. For the ones that are present, their
// accessible name comes from the localized child span (data-l10n-id
// pad.importExport.exportetherpad etc.), not a hard-coded English
// aria-label. Assert the visible text is non-empty, which is what a
// screen reader will announce.
const ids = [
'#exportetherpada',
'#exporthtmla',
'#exportplaina',
'#exportworda',
'#exportpdfa',
'#exportopena',
// environment actually renders. Each anchor carries
// data-l10n-id="pad.importExport.export<format>a.title", which html10n
// expands into both `title` and `aria-label` from the same translation
// (e.g. "Export as Etherpad"). The inner icon span is aria-hidden so a
// screen reader announces the anchor's label once, not twice.
const cases: Array<[string, string]> = [
['#exportetherpada', 'Export as Etherpad'],
['#exporthtmla', 'Export as HTML'],
['#exportplaina', 'Export as plain text'],
['#exportworda', 'Export as Microsoft Word'],
['#exportpdfa', 'Export as PDF'],
['#exportopena', 'Export as ODF (Open Document Format)'],
];
for (const id of ids) {
for (const [id, expected] of cases) {
const locator = page.locator(id);
if ((await locator.count()) === 0) continue;
const text = (await locator.innerText()).trim();
expect(text.length).toBeGreaterThan(0);
await expect(locator).toHaveAttribute('aria-label', expected);
await expect(locator).toHaveAttribute('title', expected);
await expect(locator).toHaveAttribute('rel', 'noopener');
const innerSpan = locator.locator('span.exporttype');
await expect(innerSpan).toHaveAttribute('aria-hidden', 'true');
}
});

Expand Down
Loading