diff --git a/tests/test_locale_manager.py b/tests/test_locale_manager.py index eb58810..8e38a5e 100644 --- a/tests/test_locale_manager.py +++ b/tests/test_locale_manager.py @@ -65,5 +65,51 @@ def test_update_install_messages_exist_in_all_locales(self): self.assertTrue(strings[key].strip()) +class AccessibilityLocaleTests(unittest.TestCase): + """The QML accessibility labels added in this batch reference these + keys. Missing them in a non-English locale silently regresses to a + KeyError-as-empty-string in the QML lookup ``s[...]``, which leaves + screen readers reading nothing for an interactive control. + """ + + REQUIRED_KEYS = frozenset({ + "dialog.close", + "scroll.ignore_trackpad", + "scroll.ignore_trackpad_desc", + "scroll.smart_shift", + }) + ENGLISH_VALUES = frozenset({ + "Close", + "Ignore trackpad", + "Only respond to mouse events, not trackpad or Magic Mouse", + }) + + def test_required_accessibility_keys_present_in_all_locales(self): + for locale, strings in _TRANSLATIONS.items(): + with self.subTest(locale=locale): + missing = self.REQUIRED_KEYS - strings.keys() + self.assertFalse(missing, f"{locale} missing keys: {missing}") + for key in self.REQUIRED_KEYS: + self.assertTrue( + strings[key].strip(), + f"{locale}.{key} is blank", + ) + + def test_chinese_locales_do_not_passthrough_english(self): + """Trackpad strings used to ship English text in the zh_CN and + zh_TW maps. Pin that they are now actually localized.""" + for locale in ("zh_CN", "zh_TW"): + with self.subTest(locale=locale): + for key in ( + "scroll.ignore_trackpad", + "scroll.ignore_trackpad_desc", + ): + self.assertNotIn( + _TRANSLATIONS[locale][key], + self.ENGLISH_VALUES, + f"{locale}.{key} still ships English", + ) + + if __name__ == "__main__": unittest.main() diff --git a/ui/locale_manager.py b/ui/locale_manager.py index d6d1f34..f1a9898 100644 --- a/ui/locale_manager.py +++ b/ui/locale_manager.py @@ -216,6 +216,9 @@ ), "accessibility.info": "System Settings -> Privacy & Security -> Accessibility", + # Common dialog chrome + "dialog.close": "Close", + # About dialog "about.title": "About Mouser", "about.subtitle": "Runtime and build details for support and debugging.", @@ -387,8 +390,8 @@ "scroll.scroll_direction_desc": "\u53cd\u8f6c\u6eda\u52a8\u65b9\u5411\uff08\u81ea\u7136\u6eda\u52a8\uff09", "scroll.invert_vertical": "\u53cd\u8f6c\u5782\u76f4\u6eda\u52a8", "scroll.invert_horizontal": "\u53cd\u8f6c\u6c34\u5e73\u6eda\u52a8", - "scroll.ignore_trackpad": "Ignore trackpad", - "scroll.ignore_trackpad_desc": "Only respond to mouse events, not trackpad or Magic Mouse", + "scroll.ignore_trackpad": "\u5ffd\u7565\u89e6\u6478\u677f", + "scroll.ignore_trackpad_desc": "\u4ec5\u54cd\u5e94\u9f20\u6807\u4e8b\u4ef6\uff0c\u4e0d\u54cd\u5e94\u89e6\u6478\u677f\u6216 Magic Mouse", "scroll.dpi_note": "DPI \u66f4\u6539\u9700\u8981\u901a\u8fc7 HID++ \u4e0e\u8bbe\u5907\u901a\u4fe1\uff0c\u5c06\u5728\u77ed\u6682\u5ef6\u8fdf\u540e\u751f\u6548\u3002", "scroll.language": "\u8bed\u8a00", "scroll.language_desc": "\u9009\u62e9\u5e94\u7528\u7a0b\u5e8f\u7684\u663e\u793a\u8bed\u8a00\u3002", @@ -426,6 +429,8 @@ ), "accessibility.info": "\u7cfb\u7edf\u8bbe\u7f6e -> \u9690\u79c1\u4e0e\u5b89\u5168\u6027 -> \u8f85\u52a9\u529f\u80fd", + "dialog.close": "\u5173\u95ed", + "about.title": "\u5173\u4e8e Mouser", "about.subtitle": "\u7528\u4e8e\u652f\u6301\u548c\u8c03\u8bd5\u7684\u8fd0\u884c\u65f6\u4e0e\u6784\u5efa\u4fe1\u606f\u3002", "about.version": "\u7248\u672c", @@ -595,8 +600,8 @@ "scroll.scroll_direction_desc": "\u53cd\u8f49\u6372\u52d5\u65b9\u5411\uff08\u81ea\u7136\u6372\u52d5\uff09", "scroll.invert_vertical": "\u53cd\u8f49\u5782\u76f4\u6372\u52d5", "scroll.invert_horizontal": "\u53cd\u8f49\u6c34\u5e73\u6372\u52d5", - "scroll.ignore_trackpad": "Ignore trackpad", - "scroll.ignore_trackpad_desc": "Only respond to mouse events, not trackpad or Magic Mouse", + "scroll.ignore_trackpad": "\u5ffd\u7565\u89f8\u63a7\u677f", + "scroll.ignore_trackpad_desc": "\u50c5\u56de\u61c9\u6ed1\u9f20\u4e8b\u4ef6\uff0c\u4e0d\u56de\u61c9\u89f8\u63a7\u677f\u6216 Magic Mouse", "scroll.dpi_note": "DPI \u66f4\u6539\u9700\u8981\u900f\u904e HID++ \u8207\u88dd\u7f6e\u901a\u8a0a\uff0c\u5c07\u5728\u77ed\u66ab\u5ef6\u9072\u5f8c\u751f\u6548\u3002", "scroll.language": "\u8a9e\u8a00", "scroll.language_desc": "\u9078\u64c7\u61c9\u7528\u7a0b\u5f0f\u7684\u986f\u793a\u8a9e\u8a00\u3002", @@ -634,6 +639,8 @@ ), "accessibility.info": "\u7cfb\u7d71\u8a2d\u5b9a -> \u96b1\u79c1\u6b0a\u8207\u5b89\u5168\u6027 -> \u8f14\u52a9\u4f7f\u7528", + "dialog.close": "\u95dc\u9589", + "about.title": "\u95dc\u65bc Mouser", "about.subtitle": "\u63d0\u4f9b\u652f\u63f4\u8207\u9664\u932f\u7528\u7684\u57f7\u884c\u6642\u8207\u5efa\u7f6e\u8cc7\u8a0a\u3002", "about.version": "\u7248\u672c", diff --git a/ui/qml/Main.qml b/ui/qml/Main.qml index cef0f22..3df5a26 100644 --- a/ui/qml/Main.qml +++ b/ui/qml/Main.qml @@ -377,6 +377,10 @@ ApplicationWindow { ? Qt.rgba(1, 1, 1, uiState.darkMode ? 0.08 : 0.65) : "transparent" + Accessible.role: Accessible.Button + Accessible.name: s["dialog.close"] + Accessible.onPressAction: aboutDialog.close() + AppIcon { anchors.centerIn: parent width: 14 diff --git a/ui/qml/ScrollPage.qml b/ui/qml/ScrollPage.qml index 160aa4d..2172dd2 100644 --- a/ui/qml/ScrollPage.qml +++ b/ui/qml/ScrollPage.qml @@ -350,7 +350,7 @@ Item { checked: backend.smartShiftEnabled focusPolicy: Qt.StrongFocus Material.accent: scrollPage.theme.accent - Accessible.name: "SmartShift" + Accessible.name: s["scroll.smart_shift"] onClicked: backend.setSmartShiftEnabled(checked) } } @@ -472,6 +472,12 @@ Item { ? scrollPage.theme.accent : scrollPage.theme.border + Accessible.role: Accessible.Button + Accessible.name: ssText.text + Accessible.checkable: true + Accessible.checked: backend.smartShiftMode === modelData.value + Accessible.onPressAction: backend.setSmartShift(modelData.value) + Text { id: ssText anchors.centerIn: parent @@ -658,6 +664,12 @@ Item { Behavior on color { ColorAnimation { duration: 120 } } + Accessible.role: Accessible.Button + Accessible.name: modelData.name + Accessible.checkable: true + Accessible.checked: lm.language === modelData.code + Accessible.onPressAction: lm.setLanguage(modelData.code) + Text { id: langText anchors.centerIn: parent