Skip to content

Commit 2bec149

Browse files
committed
🐛 fix: fix(a11y): show native language names in language selector for ORCA
- Display native language names as headings (e.g. "Português, Brazil" instead of "Portuguese - Brazil") so ORCA reads each language in its own tongue - Hide flag icons and English caption from screen readers using PRESENTATION accessible role - Ensure en_US appears first in the language list by using explicit favorites ordering instead of alphabetical sort - Pre-create factory widgets in setup phase to avoid unnecessary recreation on each bind
1 parent 1e6321c commit 2bec149

2 files changed

Lines changed: 87 additions & 41 deletions

File tree

biglinux-livecd/usr/share/biglinux/calamares/src/utils/constants.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
# Application Information
1010
APP_NAME = "BigLinux Calamares Config"
1111
APP_ID = "com.biglinux.calamares-config"
12-
APP_VERSION = "1.1.1"
12+
APP_VERSION = "1.1.2"
1313

1414
# Paths and Directories
1515
BASE_DIR = Path(__file__).parent.parent.parent

biglinux-livecd/usr/share/biglinux/livecd/ui/language_view.py

Lines changed: 86 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,42 @@
1414

1515
logger = get_logger()
1616

17+
# Clean native language names for screen reader pronunciation.
18+
# Maps the 2-letter lang prefix to a short, clear native name.
19+
_NATIVE_LANG_NAMES = {
20+
"be": "беларуская",
21+
"bg": "български",
22+
"cs": "čeština",
23+
"da": "dansk",
24+
"de": "Deutsch",
25+
"el": "ελληνικά",
26+
"en": "English",
27+
"es": "español",
28+
"et": "eesti",
29+
"fi": "suomi",
30+
"fr": "français",
31+
"he": "עברית",
32+
"hr": "hrvatski",
33+
"hu": "magyar",
34+
"is": "Íslenska",
35+
"it": "italiano",
36+
"ja": "日本語",
37+
"ko": "한국어",
38+
"nb": "norsk bokmål",
39+
"nl": "Nederlands",
40+
"nn": "norsk nynorsk",
41+
"pl": "polski",
42+
"pt": "Português",
43+
"ro": "română",
44+
"ru": "русский",
45+
"sk": "slovenčina",
46+
"sl": "slovenščina",
47+
"sv": "Svenska",
48+
"tr": "Türkçe",
49+
"uk": "українська",
50+
"zh": "中文",
51+
}
52+
1753

1854
def normalize_string(s: str) -> str:
1955
"""Normalizes a string by converting to lowercase and removing diacritics."""
@@ -122,8 +158,8 @@ def _load_languages(self):
122158
raw_data = json.load(f)
123159

124160
language_data = [LanguageListItem(**item) for item in raw_data]
125-
favorites = ["pt_BR", "en_US", "es_ES"]
126-
language_data.sort(key=lambda x: (x.code not in favorites, x.name))
161+
favorites_order = {"en_US": 0, "pt_BR": 1, "es_ES": 2}
162+
language_data.sort(key=lambda x: (x.code not in favorites_order, favorites_order.get(x.code, 999), x.name))
127163
self._store.splice(0, 0, language_data)
128164
GLib.idle_add(self._post_load_setup)
129165

@@ -200,70 +236,80 @@ def _on_factory_setup(self, factory, list_item):
200236
halign=Gtk.Align.START,
201237
valign=Gtk.Align.CENTER,
202238
)
203-
root_box.append(content_box)
204-
list_item.set_child(root_box)
205-
206-
try:
207-
cursor = Gdk.Cursor.new_from_name("pointer", None)
208-
root_box.set_cursor(cursor)
209-
except Exception:
210-
pass
211-
212-
motion_controller = Gtk.EventControllerMotion.new()
213-
motion_controller.connect("motion", self._on_mouse_motion_item, list_item)
214-
root_box.add_controller(motion_controller)
215-
216-
def _on_mouse_motion_item(self, controller, x, y, list_item):
217-
position = list_item.get_position()
218-
if position != Gtk.INVALID_LIST_POSITION:
219-
selection_model = self.grid_view.get_model()
220-
if selection_model and selection_model.get_selected() != position:
221-
selection_model.set_selected(position)
222-
223-
def _on_factory_bind(self, factory, list_item):
224-
item = list_item.get_item()
225-
root_box = list_item.get_child()
226-
content_box = root_box.get_first_child()
227-
228-
child = content_box.get_first_child()
229-
while child:
230-
content_box.remove(child)
231-
child = content_box.get_first_child()
232239

233-
# Accessible label for screen readers
234-
root_box.update_property(
235-
[Gtk.AccessibleProperty.LABEL],
236-
[f"{item.name} ({item.name_orig})"],
240+
flag_widget = Gtk.Image(
241+
pixel_size=36,
242+
accessible_role=Gtk.AccessibleRole.PRESENTATION,
237243
)
238244

239-
flag_widget = Gtk.Image.new_from_icon_name(item.flag_icon_name)
240-
flag_widget.set_pixel_size(36)
241-
242245
vbox = Gtk.Box(
243246
orientation=Gtk.Orientation.VERTICAL,
244247
spacing=2,
245248
halign=Gtk.Align.START,
246249
valign=Gtk.Align.CENTER,
247250
)
251+
# Heading: ORCA reads this (native language name)
248252
name_label = Gtk.Label(
249253
halign=Gtk.Align.START,
250-
label=item.name,
251254
wrap=True,
252255
justify=Gtk.Justification.LEFT,
253256
)
254257
name_label.add_css_class("heading")
258+
# Caption: hidden from ORCA (English name for visual reference only)
255259
orig_name_label = Gtk.Label(
256260
halign=Gtk.Align.START,
257-
label=item.name_orig,
258261
wrap=True,
259262
justify=Gtk.Justification.LEFT,
263+
accessible_role=Gtk.AccessibleRole.PRESENTATION,
260264
)
261265
orig_name_label.add_css_class("caption")
262266
vbox.append(name_label)
263267
vbox.append(orig_name_label)
264268

265269
content_box.append(flag_widget)
266270
content_box.append(vbox)
271+
root_box.append(content_box)
272+
273+
# Store references for _on_factory_bind
274+
root_box._flag = flag_widget
275+
root_box._name_label = name_label
276+
root_box._orig_label = orig_name_label
277+
278+
list_item.set_child(root_box)
279+
280+
try:
281+
cursor = Gdk.Cursor.new_from_name("pointer", None)
282+
root_box.set_cursor(cursor)
283+
except Exception:
284+
pass
285+
286+
motion_controller = Gtk.EventControllerMotion.new()
287+
motion_controller.connect("motion", self._on_mouse_motion_item, list_item)
288+
root_box.add_controller(motion_controller)
289+
290+
def _on_mouse_motion_item(self, controller, x, y, list_item):
291+
position = list_item.get_position()
292+
if position != Gtk.INVALID_LIST_POSITION:
293+
selection_model = self.grid_view.get_model()
294+
if selection_model and selection_model.get_selected() != position:
295+
selection_model.set_selected(position)
296+
297+
def _on_factory_bind(self, factory, list_item):
298+
item = list_item.get_item()
299+
root_box = list_item.get_child()
300+
301+
# Build native name heading: e.g. "Português, Brazil" or "English, United States"
302+
parts = item.name.split(" - ", 1)
303+
country = parts[1] if len(parts) > 1 else ""
304+
native_name = _NATIVE_LANG_NAMES.get(item.code[:2], item.name_orig)
305+
heading_text = f"{native_name}, {country}" if country else native_name
306+
307+
# Heading: native name (ORCA reads this)
308+
root_box._name_label.set_label(heading_text)
309+
# Caption: English name (hidden from ORCA via PRESENTATION role)
310+
root_box._orig_label.set_label(item.name)
311+
312+
root_box._flag.set_from_icon_name(item.flag_icon_name)
267313

268314
click_gesture = Gtk.GestureClick.new()
269315
click_gesture.connect("released", self._on_item_clicked, item)

0 commit comments

Comments
 (0)