Implement correct language markup in the rich full‑text view #55

Closed
opened 2026-07-16 08:20:16 -07:00 by serrebi · 3 comments
Owner

Currently, the rich full‑text view window displays content marked as English, regardless of the actual language of the article. This causes accessibility issues for users relying on assistive technologies — screen readers may attempt to read non‑English content using English speech synthesizers, apply English Braille translation tables, or use English character names. Additionally, text rendering may be affected (e.g., incorrect text direction for right‑to‑left languages).

Preconditions

  • The “Rich full‑text view” option is enabled in the settings (under the “Feeds & Articles” tab).

Steps to reproduce

  1. Open the rich full‑text view for an article whose content is extracted from a web page with a non‑English language setting.

Actual behaviour

  • The rich full‑text viewer window indicates that the content language is English. This can be verified using a screen reader with appropriate settings enabled.

Expected behaviour

The rich full‑text viewer must correctly identify and apply the appropriate language markup (lang attribute in HTML) based on the following priority order:

  1. If BlindRSS performs automatic translation of the content:

    • The language in the rich full‑text viewer should match the target language of the translation.
  2. If the original web page specifies a language:

    • Use the language defined in the lang attribute of the <html> tag of the source page.
  3. If full text extraction fails and the viewer displays feed content:

    • Use the language specified in the feed file:
      • First, check the xml:lang attribute on the article container (if available).
      • If the article’s language is not specified, use the language from the <language>.
  4. If no language is specified in the source page or feed:

    • Fall back to the currently active localization language of BlindRSS.

Technical notes

  • Language in HTML is set via the lang attribute (e.g., <html lang="ru"> for Russian).
  • For XML/RSS feeds, language can be specified:
    • Via the xml:lang attribute for description tag.
    • In the <language> element at the feed or article level.
  • The solution must ensure that the lang attribute is correctly applied to the root element (or relevant container) of the HTML rendered in the rich full‑text viewer.
  • This change will improve compatibility with screen readers, Braille displays, and other assistive technologies, as well as fix potential text rendering issues (e.g., bidirectional text).

Suggested solution

  1. Implement logic to detect and prioritize language sources according to the expected behaviour rules.
  2. Ensure the detected language is applied as the lang attribute in the HTML output of the rich full‑text viewer.
Currently, the rich full‑text view window displays content marked as English, regardless of the actual language of the article. This causes accessibility issues for users relying on assistive technologies — screen readers may attempt to read non‑English content using English speech synthesizers, apply English Braille translation tables, or use English character names. Additionally, text rendering may be affected (e.g., incorrect text direction for right‑to‑left languages). #### Preconditions * The “Rich full‑text view” option is enabled in the settings (under the “Feeds & Articles” tab). #### Steps to reproduce 1. Open the rich full‑text view for an article whose content is extracted from a web page with a non‑English language setting. #### Actual behaviour * The rich full‑text viewer window indicates that the content language is English. This can be verified using a screen reader with appropriate settings enabled. #### Expected behaviour The rich full‑text viewer must correctly identify and apply the appropriate language markup (`lang` attribute in HTML) based on the following priority order: 1. **If BlindRSS performs automatic translation of the content:** * The language in the rich full‑text viewer should match the target language of the translation. 2. **If the original web page specifies a language:** * Use the language defined in the `lang` attribute of the `<html>` tag of the source page. 3. **If full text extraction fails and the viewer displays feed content:** * Use the language specified in the feed file: * First, check the `xml:lang` attribute on the article container (if available). * If the article’s language is not specified, use the language from the `<language>`. 4. **If no language is specified in the source page or feed:** * Fall back to the currently active localization language of BlindRSS. #### Technical notes * Language in HTML is set via the `lang` attribute (e.g., `<html lang="ru">` for Russian). * For XML/RSS feeds, language can be specified: * Via the `xml:lang` attribute for description tag. * In the `<language>` element at the feed or article level. * The solution must ensure that the `lang` attribute is correctly applied to the root element (or relevant container) of the HTML rendered in the rich full‑text viewer. * This change will improve compatibility with screen readers, Braille displays, and other assistive technologies, as well as fix potential text rendering issues (e.g., bidirectional text). #### Suggested solution 1. Implement logic to detect and prioritize language sources according to the expected behaviour rules. 2. Ensure the detected language is applied as the `lang` attribute in the HTML output of the rich full‑text viewer. <!-- forgejo-github-sync: issue github=serrebidev/BlindRSS#72 -->
Author
Owner

Largely fixed in v1.107.0, but two of the four rules are not fully live and I want to be precise about which, rather than close this as done.

Root cause

Not a wxPython issue — ours. The rich view is built on wx_accessible_webview, whose constructor takes lang: str = "en". We never passed lang, so every article got the library's default. That's the hardcoded English you found.

What's fixed

Rule 4 (UI language fallback) — the document root now gets BlindRSS's active UI language instead of "en". Note this reports what's actually on screen: if you pick a language with no catalog, it resolves to en, because English source strings are what you'd be reading. Claiming otherwise would be lying to the screen reader.

Rule 2 (source page <html lang>) — read from the page before cleaning (the cleaner discards <html>), and applied. A page declaring lang="ru" now renders <article lang="ru">.

Rule 3b (feed <language>) — feeds now carry a language column, filled on refresh. feedparser surfaces both RSS <language> and Atom feed-level xml:lang as feed.language, so both work. NULL means "never declared", which is a real answer distinct from a default — it's what lets rule 4 fall through correctly. The write is COALESCE'd so a conditional GET can't erase a known language.

Where the lang goes: on <article>, not <html>. The webview skeleton is built once and reused for every article, so the root can't follow per-article language. Your issue explicitly allows "the root element or relevant container", and lang on an ancestor applies to its whole subtree, so content is announced correctly either way.

The resolver refuses to guess: an unusable value ("", "unknown", junk) falls through to the next source rather than becoming a bad lang. A wrong tag is worse than none — it actively points assistive tech at the wrong synthesizer.

What's not live, and why

Rule 1 (translation target) — implemented and tested, but currently unreachable: automatic translation never runs in the rich view at all. _translate_rendered_text_if_enabled is only called from the plain-text full-text worker, not from _rich_load_worker. So there's no translated content in the rich view to mark. Rule 1 presupposes behaviour that doesn't exist — that's arguably a separate bug (rich view + translation enabled = untranslated article), and probably deserves its own issue. Happy to file it.

Rule 3a (per-item xml:lang) — the resolver accepts and prioritizes it, but nothing populates it. Storing per-article language needs an articles.language column, and articles has ~46 SELECT sites with explicit column lists. The channel language covers the common case; per-item override only matters for genuinely multilingual feeds. Left as a follow-up rather than rushed.

Miniflux caveat — the Miniflux API exposes no feed language field, so rule 3 yields NULL on that backend and resolution falls through to rules 2 and 4. Rules 2 and 4 work on every backend, so the reported symptom is fixed regardless.

Worth a look with your screen reader to confirm it behaves as you expect.

Largely fixed in **v1.107.0**, but two of the four rules are not fully live and I want to be precise about which, rather than close this as done. ## Root cause Not a wxPython issue — ours. The rich view is built on `wx_accessible_webview`, whose constructor takes `lang: str = "en"`. We never passed `lang`, so every article got the library's default. That's the hardcoded English you found. ## What's fixed **Rule 4 (UI language fallback)** — the document root now gets BlindRSS's active UI language instead of `"en"`. Note this reports what's *actually on screen*: if you pick a language with no catalog, it resolves to `en`, because English source strings are what you'd be reading. Claiming otherwise would be lying to the screen reader. **Rule 2 (source page `<html lang>`)** — read from the page before cleaning (the cleaner discards `<html>`), and applied. A page declaring `lang="ru"` now renders `<article lang="ru">`. **Rule 3b (feed `<language>`)** — feeds now carry a `language` column, filled on refresh. `feedparser` surfaces both RSS `<language>` and Atom feed-level `xml:lang` as `feed.language`, so both work. NULL means "never declared", which is a real answer distinct from a default — it's what lets rule 4 fall through correctly. The write is `COALESCE`'d so a conditional GET can't erase a known language. **Where the `lang` goes:** on `<article>`, not `<html>`. The webview skeleton is built once and reused for every article, so the root can't follow per-article language. Your issue explicitly allows "the root element **or relevant container**", and `lang` on an ancestor applies to its whole subtree, so content is announced correctly either way. The resolver **refuses to guess**: an unusable value (`""`, `"unknown"`, junk) falls through to the next source rather than becoming a bad `lang`. A wrong tag is worse than none — it actively points assistive tech at the wrong synthesizer. ## What's not live, and why **Rule 1 (translation target)** — implemented and tested, but currently unreachable: **automatic translation never runs in the rich view at all.** `_translate_rendered_text_if_enabled` is only called from the plain-text full-text worker, not from `_rich_load_worker`. So there's no translated content in the rich view to mark. Rule 1 presupposes behaviour that doesn't exist — that's arguably a separate bug (rich view + translation enabled = untranslated article), and probably deserves its own issue. Happy to file it. **Rule 3a (per-item `xml:lang`)** — the resolver accepts and prioritizes it, but nothing populates it. Storing per-article language needs an `articles.language` column, and `articles` has ~46 SELECT sites with explicit column lists. The channel language covers the common case; per-item override only matters for genuinely multilingual feeds. Left as a follow-up rather than rushed. **Miniflux caveat** — the Miniflux API exposes no feed language field, so rule 3 yields NULL on that backend and resolution falls through to rules 2 and 4. Rules 2 and 4 work on every backend, so the reported symptom is fixed regardless. Worth a look with your screen reader to confirm it behaves as you expect. <!-- forgejo-github-sync: comment github=serrebidev/BlindRSS#72/4993954154 -->
Author
Owner

I tested it on version 1.107.0 and everything seems to work as expected.
Limitations of translation and multilingual feeds are understandable and not a high priority.
The current implementation covers the vast majority of scenarios.
Thank you!

I tested it on version 1.107.0 and everything seems to work as expected. Limitations of translation and multilingual feeds are understandable and not a high priority. The current implementation covers the vast majority of scenarios. Thank you! <!-- forgejo-github-sync: comment github=serrebidev/BlindRSS#72/4994758429 -->
Author
Owner

Thanks for confirming, and for the detailed original report — the lang placement and the feedparser language facts in it are what made the fix straightforward.

Agreed on the limitations. Per-element language markup for genuinely multilingual feeds would need language detection per block, which is a much bigger change for a much smaller set of cases; if a concrete feed ever makes it painful in practice, please open a new issue with the URL and we can revisit.

Closing as fixed in v1.107.0.

Thanks for confirming, and for the detailed original report — the `lang` placement and the feedparser language facts in it are what made the fix straightforward. Agreed on the limitations. Per-element language markup for genuinely multilingual feeds would need language detection per block, which is a much bigger change for a much smaller set of cases; if a concrete feed ever makes it painful in practice, please open a new issue with the URL and we can revisit. Closing as fixed in v1.107.0. <!-- forgejo-github-sync: comment github=serrebidev/BlindRSS#72/4994765438 -->
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
serrebi/BlindRSS#55
No description provided.