Skip to content

a11y: localize aria-label on form-control elements (<select>, <input>, <textarea>)#7713

Merged
JohnMcLear merged 1 commit intodevelopfrom
a11y/html10n-form-controls-aria
May 10, 2026
Merged

a11y: localize aria-label on form-control elements (<select>, <input>, <textarea>)#7713
JohnMcLear merged 1 commit intodevelopfrom
a11y/html10n-form-controls-aria

Conversation

@JohnMcLear
Copy link
Copy Markdown
Member

Summary

#7584 introduced auto-population of aria-label from a translation when an element has data-l10n-id and no author-supplied aria-label. That branch in html10n.ts:translateNode only fires for elements with no children (or with a data-l10n-id ending in a recognized attribute suffix like .title).

Form-control elements break the assumption: a <select> always has <option> children, an <input> or <textarea> may have implicit value content. The textContent branch handles them, but the aria-label fallback wasn't called from there. Plugins like ep_font_size, ep_headings2, and ep_hljs end up with a localized translation applied to a <select> but no accessible name on the element itself.

This PR factors the aria-label population out into a closure and calls it from the textContent branch when the node is a form control (<select>, <input>, <textarea>). Same "author-supplied aria-label wins on first pass; the data-l10n-aria-label marker lets us refresh values we wrote" semantics from #7584.

Test plan

New file: src/tests/frontend-new/specs/html10n_form_controls_aria.spec.ts

  • aria-label is populated on a <select> with data-l10n-id
  • aria-label is populated on a <textarea> with data-l10n-id
  • an author-supplied aria-label is preserved on first pass (no data-l10n-aria-label marker)

Background

Surfaced during the fleet-wide plugin a11y/i18n sweep — when we removed redundant hardcoded English aria-labels from <select> toolbar controls in plugins (ep_font_*, ep_hljs, ep_headings2), they ended up with no accessible name because of this gap. Stopgap was to restore the hardcoded English aria-labels; this PR removes the need for that workaround.

…, <textarea>)

#7584 introduced auto-population of aria-label from a translation
when an element has data-l10n-id and no author-supplied aria-label.
That branch only fires for elements with no children (or with
data-l10n-id ending in a recognized attribute suffix like .title).

Form-control elements break the assumption: a <select> always has
<option> children, an <input>/<textarea> may have implicit value
content. The textContent branch handles them, but the aria-label
fallback wasn't called from there. Plugins like ep_font_size,
ep_headings2, and ep_hljs end up with a localized translation
applied to a <select> but no accessible name on the element itself.

Calls populateAriaLabel() from the textContent branch when the node
is a <select>, <input>, or <textarea>. Keeps the same
"author-supplied aria-label wins on first pass; the
data-l10n-aria-label marker lets us refresh values we wrote"
semantics from #7584.

Adds Playwright coverage in
src/tests/frontend-new/specs/html10n_form_controls_aria.spec.ts:
- aria-label is populated on <select> with data-l10n-id
- aria-label is populated on <textarea> with data-l10n-id
- author-supplied aria-label is preserved on first pass
@qodo-code-review
Copy link
Copy Markdown

ⓘ You've reached your Qodo monthly free-tier limit. Reviews pause until next month — upgrade your plan to continue now, or link your paid account if you already have one.

@qodo-free-for-open-source-projects
Copy link
Copy Markdown

Review Summary by Qodo

Auto-populate aria-label on form-control elements in html10n

✨ Enhancement 🧪 Tests

Grey Divider

Walkthroughs

Description
• Refactor aria-label population into reusable closure in html10n
• Call populateAriaLabel() for form controls in textContent branch
• Add Playwright tests for aria-label auto-population on form controls
• Ensure <select>, <input>, <textarea> get localized accessible names
Diagram
flowchart LR
  A["translateNode() called"] --> B{"Element has children?"}
  B -->|No or attribute suffix| C["Apply translation"]
  C --> D["populateAriaLabel()"]
  B -->|Yes, textContent| E["Update child nodes"]
  E --> F{"Form control?"}
  F -->|SELECT/INPUT/TEXTAREA| D
  F -->|Other| G["Done"]
  D --> H["Set aria-label from translation"]
  H --> G
Loading

Grey Divider

File Changes

1. src/static/js/vendors/html10n.ts ✨ Enhancement +28/-13

Refactor aria-label population for form controls

• Extract aria-label population logic into populateAriaLabel() closure
• Call closure from both no-children and textContent branches
• Add form-control detection (SELECT, INPUT, TEXTAREA) in textContent branch
• Preserve author-supplied aria-labels using data-l10n-aria-label marker

src/static/js/vendors/html10n.ts


2. src/tests/frontend-new/specs/html10n_form_controls_aria.spec.ts 🧪 Tests +85/-0

Add regression tests for form control aria-label localization

• Test aria-label auto-population on <select> with data-l10n-id
• Test aria-label auto-population on <textarea> with data-l10n-id
• Test preservation of author-supplied aria-label without marker
• Verify data-l10n-aria-label marker is set only for html10n-generated values

src/tests/frontend-new/specs/html10n_form_controls_aria.spec.ts


Grey Divider

Qodo Logo

@qodo-free-for-open-source-projects
Copy link
Copy Markdown

qodo-free-for-open-source-projects Bot commented May 10, 2026

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (0)

Grey Divider


Remediation recommended

1. Misleading <select> warning 🐞 Bug ◔ Observability
Description
Html10n.translateNode() will still log "Unexpected error: could not translate element content" for
<select data-l10n-id> because it searches only for direct text child nodes, even though this PR now
successfully localizes <select> via aria-label. This will spam confusing warnings for the exact new
supported use-case (localized toolbar <select>s).
Code

src/static/js/vendors/html10n.ts[R697-710]

      if (!found) {
        console.warn('Unexpected error: could not translate element content for key '+str.id, node)
      }
+      // Form-controllable elements (<select>, <input>, <textarea>) carry their
+      // accessible name on aria-label rather than as text content (a <select>'s
+      // text is its <option> labels, not its own name). The textContent branch
+      // above doesn't fall through to populateAriaLabel(), so plugins that put
+      // data-l10n-id on a <select> and rely on the auto-population (introduced
+      // in #7584) end up with no accessible name. Populate aria-label here so
+      // those controls stay localized too. See ether/ep_align#182 review.
+      const tag = node.tagName;
+      if (tag === 'SELECT' || tag === 'INPUT' || tag === 'TEXTAREA') {
+        populateAriaLabel()
+      }
Evidence
translateElement() translates every element matching *[data-l10n-id] by calling translateNode(). In
translateNode()’s textContent branch, the code only considers direct text child nodes
(nodeType===3); for a <select> whose direct children are <option> elements, found remains false
and the warning is emitted, and only afterwards does the new code populate aria-label for
SELECT—making the warning misleading for the now-intended <select> localization path.

src/static/js/vendors/html10n.ts[498-508]
src/static/js/vendors/html10n.ts[681-710]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`translateNode()` warns about failing to translate element *content* even for `<select data-l10n-id>` elements where the new/desired behavior is to localize the control via `aria-label` (not via text content). This produces misleading console warnings for successfully-handled nodes.

### Issue Context
In the `textContent` branch, `<select>` typically has no direct text nodes (only `<option>` elements), so `found` stays false and the warning fires. The new code then correctly populates `aria-label` for form controls, so the warning no longer indicates a real problem.

### Fix Focus Areas
- src/static/js/vendors/html10n.ts[697-710]

### Suggested change
Adjust the warning condition to account for form controls. For example:
- Detect `SELECT` (and optionally `INPUT`/`TEXTAREA`) before logging, and suppress or downgrade the warning when the form-control aria-label path is taken.
- Alternatively, move the warning after the form-control handling and only warn if neither text content nor aria-label handling applied.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

@JohnMcLear JohnMcLear merged commit df0ecec into develop May 10, 2026
33 of 43 checks passed
@JohnMcLear JohnMcLear deleted the a11y/html10n-form-controls-aria branch May 10, 2026 11:23
JohnMcLear added a commit to ether/ep_font_color that referenced this pull request May 10, 2026
#156)

ether/etherpad#7713 extends html10n's aria-label auto-population to
cover form-control elements (<select>, <input>, <textarea>). On
newer etherpad with that fix, html10n overwrites aria-label with the
localized translation when the data-l10n-aria-label="true" marker
is present.

Adds the marker (and keeps the existing hardcoded aria-label) so:
  - older etherpad without the html10n fix: aria-label remains the
    hardcoded English fallback (no regression);
  - newer etherpad with the html10n fix: aria-label gets the localized
    translation, refreshed on pad.applyLanguage().

Also drops the no-op data-l10n-attr="aria-label" attribute (etherpad's
html10n doesn't read it; that's a convention from a different
localization library).
JohnMcLear added a commit to ether/ep_font_family that referenced this pull request May 10, 2026
#149)

ether/etherpad#7713 extends html10n's aria-label auto-population to
cover form-control elements (<select>, <input>, <textarea>). On
newer etherpad with that fix, html10n overwrites aria-label with the
localized translation when the data-l10n-aria-label="true" marker
is present.

Adds the marker (and keeps the existing hardcoded aria-label) so:
  - older etherpad without the html10n fix: aria-label remains the
    hardcoded English fallback (no regression);
  - newer etherpad with the html10n fix: aria-label gets the localized
    translation, refreshed on pad.applyLanguage().

Also drops the no-op data-l10n-attr="aria-label" attribute (etherpad's
html10n doesn't read it; that's a convention from a different
localization library).
JohnMcLear added a commit to ether/ep_font_size that referenced this pull request May 10, 2026
#138)

ether/etherpad#7713 extends html10n's aria-label auto-population to
cover form-control elements (<select>, <input>, <textarea>). On
newer etherpad with that fix, html10n overwrites aria-label with the
localized translation when the data-l10n-aria-label="true" marker
is present.

Adds the marker (and keeps the existing hardcoded aria-label) so:
  - older etherpad without the html10n fix: aria-label remains the
    hardcoded English fallback (no regression);
  - newer etherpad with the html10n fix: aria-label gets the localized
    translation, refreshed on pad.applyLanguage().

Also drops the no-op data-l10n-attr="aria-label" attribute (etherpad's
html10n doesn't read it; that's a convention from a different
localization library).
JohnMcLear added a commit to ether/ep_headings2 that referenced this pull request May 10, 2026
#178)

ether/etherpad#7713 extends html10n's aria-label auto-population to
cover form-control elements (<select>, <input>, <textarea>). On
newer etherpad with that fix, html10n overwrites aria-label with the
localized translation when the data-l10n-aria-label="true" marker
is present.

Adds the marker (and keeps the existing hardcoded aria-label) so:
  - older etherpad without the html10n fix: aria-label remains the
    hardcoded English fallback (no regression);
  - newer etherpad with the html10n fix: aria-label gets the localized
    translation, refreshed on pad.applyLanguage().

Also drops the no-op data-l10n-attr="aria-label" attribute (etherpad's
html10n doesn't read it; that's a convention from a different
localization library).
JohnMcLear added a commit to ether/ep_hljs that referenced this pull request May 10, 2026
#10)

ether/etherpad#7713 extends html10n's aria-label auto-population to
cover form-control elements (<select>, <input>, <textarea>). On
newer etherpad with that fix, html10n overwrites aria-label with the
localized translation when the data-l10n-aria-label="true" marker
is present.

Adds the marker (and keeps the existing hardcoded aria-label) so:
  - older etherpad without the html10n fix: aria-label remains the
    hardcoded English fallback (no regression);
  - newer etherpad with the html10n fix: aria-label gets the localized
    translation, refreshed on pad.applyLanguage().

Also drops the no-op data-l10n-attr="aria-label" attribute (etherpad's
html10n doesn't read it; that's a convention from a different
localization library).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant