fix(a11y): add accessible names to GUI controls (#7049)

Give icon-only back buttons, the header local-routing switch, the project
switcher popover, the Claude JSON editors, and the existing-Skills import
checkboxes programmatic names so screen readers announce their purpose.
Reuses existing i18n strings where possible and adds two short localized
labels across zh/en/ja/zh-TW.

Fixes #7048
This commit is contained in:
jintonglu6688 authored and GitHub committed 2026-09-02 12:57:56 +08:00
1 parent 460aa8c73d
commit c58a25b2ae
11 files changed
+26 -1

No files matched your search

+1
View File
@@ -1283,6 +1283,7 @@ function App() {
variant="outline"
size="icon"
disabled={managementBusy}
aria-label={t("common.back")}
onClick={() =>
setCurrentView(
currentView === "skillsDiscovery"
@@ -2,6 +2,7 @@ import React from "react";
import { createPortal } from "react-dom";
import { motion, AnimatePresence, useReducedMotion } from "framer-motion";
import { ArrowLeft } from "lucide-react";
import { useTranslation } from "react-i18next";
import { Button } from "@/components/ui/button";
import {
isWindows,
@@ -63,6 +64,7 @@ export const FullScreenPanel: React.FC<FullScreenPanelProps> = ({
contentClassName,
motionPreset = "fade",
}) => {
const { t } = useTranslation();
const prefersReducedMotion = useReducedMotion();
const shouldSlideFromRight =
motionPreset === "slide-from-right" && !prefersReducedMotion;
@@ -164,6 +166,7 @@ export const FullScreenPanel: React.FC<FullScreenPanelProps> = ({
variant="outline"
size="icon"
onClick={onClose}
aria-label={t("common.back")}
className="rounded-lg select-none"
style={{ WebkitAppRegion: "no-drag" } as React.CSSProperties}
>
@@ -110,6 +110,9 @@ export function ProfileSwitcher({ activeApp }: ProfileSwitcherProps) {
type="button"
role="combobox"
aria-expanded={open}
aria-label={t("profiles.switcherAriaLabel", {
name: currentProfile?.name ?? t("profiles.none"),
})}
title={t(`profiles.switcherTooltip.${scope}`)}
className={cn(
"inline-flex h-8 items-center gap-1.5 rounded-lg px-2.5 text-sm font-medium transition-colors",
@@ -128,6 +131,7 @@ export function ProfileSwitcher({ activeApp }: ProfileSwitcherProps) {
side="bottom"
align="start"
sideOffset={6}
aria-label={t(`profiles.switcherTooltip.${scope}`)}
className="z-[100] w-64 p-0"
>
<Command label={t("profiles.searchPlaceholder")}>
@@ -86,7 +86,12 @@ export function BasicFormFields({
<div className="flex-shrink-0 py-4 border-b border-border-default bg-muted/40">
<div className="px-6 flex items-center gap-4">
<DialogClose asChild>
<Button type="button" variant="outline" size="icon">
<Button
type="button"
variant="outline"
size="icon"
aria-label={t("common.back")}
>
<ArrowLeft className="h-4 w-4" />
</Button>
</DialogClose>
@@ -250,6 +250,7 @@ export function CommonConfigEditor({
<JsonEditor
value={localValue}
onChange={handleLocalChange}
ariaLabel={t("provider.configJson")}
placeholder={`{
"env": {
"ANTHROPIC_BASE_URL": "https://your-api-endpoint.com",
@@ -331,6 +332,7 @@ export function CommonConfigEditor({
<JsonEditor
value={commonConfigSnippet}
onChange={onCommonConfigSnippetChange}
ariaLabel={t("claudeConfig.editCommonConfigTitle")}
placeholder={`{
"env": {
"ANTHROPIC_BASE_URL": "https://your-api-endpoint.com"
+1
View File
@@ -81,6 +81,7 @@ export function ProxyToggle({ className, activeApp }: ProxyToggleProps) {
checked={takeoverEnabled}
onCheckedChange={handleToggle}
disabled={isPending || isInitialStatusPending}
aria-label={t("proxy.takeover.ariaLabel", { appLabel })}
/>
</div>
);
@@ -1101,6 +1101,7 @@ const ImportSkillsDialog: React.FC<ImportSkillsDialogProps> = ({
type="checkbox"
checked={selected.has(skill.directory)}
onChange={() => toggleSelect(skill.directory)}
aria-label={skill.name}
className="mt-1"
/>
<div className="flex-1 min-w-0">
+2
View File
@@ -2200,6 +2200,7 @@
}
},
"profiles": {
"switcherAriaLabel": "Project: {{name}}",
"switcherTooltip": {
"claude": "Projects: switch the Claude Code provider / MCP / Skills / memory files in one click",
"claude-desktop": "Projects: switch the Claude Desktop provider in one click",
@@ -2883,6 +2884,7 @@
},
"switchFailed": "Switch failed: {{error}}",
"takeover": {
"ariaLabel": "{{appLabel}} local routing",
"hint": "Select apps to route — once enabled, requests from that app will go through local routing",
"enabled": "{{app}} routing enabled",
"disabled": "{{app}} routing disabled",
+2
View File
@@ -2200,6 +2200,7 @@
}
},
"profiles": {
"switcherAriaLabel": "プロジェクト:{{name}}",
"switcherTooltip": {
"claude": "プロジェクト:Claude Code のプロバイダー / MCP / Skills / メモリファイルをワンクリックで切り替え",
"claude-desktop": "プロジェクト:Claude Desktop のプロバイダーをワンクリックで切り替え",
@@ -2883,6 +2884,7 @@
},
"switchFailed": "切り替えに失敗しました: {{error}}",
"takeover": {
"ariaLabel": "{{appLabel}} ローカルルーティング",
"hint": "ルーティングするアプリを選択します。有効にすると、そのアプリのリクエストはローカルルーティング経由で転送されます",
"enabled": "{{app}} ルーティング有効",
"disabled": "{{app}} ルーティング無効",
+2
View File
@@ -2201,6 +2201,7 @@
}
},
"profiles": {
"switcherAriaLabel": "專案:{{name}}",
"switcherTooltip": {
"claude": "專案:一鍵切換 Claude Code 的供應商 / MCP / Skills / 記憶檔案",
"claude-desktop": "專案:一鍵切換 Claude Desktop 的供應商",
@@ -2884,6 +2885,7 @@
},
"switchFailed": "切換失敗:{{error}}",
"takeover": {
"ariaLabel": "{{appLabel}} 本地路由",
"hint": "選擇要路由的應用程式,啟用後該應用程式的請求將透過本地路由轉發",
"enabled": "{{app}} 路由已啟用",
"disabled": "{{app}} 路由已關閉",
+2
View File
@@ -2200,6 +2200,7 @@
}
},
"profiles": {
"switcherAriaLabel": "项目:{{name}}",
"switcherTooltip": {
"claude": "项目:一键切换 Claude Code 的供应商 / MCP / Skills / 记忆文件",
"claude-desktop": "项目:一键切换 Claude Desktop 的供应商",
@@ -2883,6 +2884,7 @@
},
"switchFailed": "切换失败: {{error}}",
"takeover": {
"ariaLabel": "{{appLabel}} 本地路由",
"hint": "选择要路由的应用,启用后该应用的请求将通过本地路由转发",
"enabled": "{{app}} 路由已启用",
"disabled": "{{app}} 路由已关闭",