diff --git a/locales/rn/de.json b/locales/rn/de.json
index 234ed8ae..3046e3c4 100644
--- a/locales/rn/de.json
+++ b/locales/rn/de.json
@@ -278,6 +278,9 @@
"login_changed": "Sie haben das Konto gewechselt, daher wurde die Regel nicht gespeichert"
}
},
+ "delegates": {
+ "remove_for": "Zugriff für {name} entfernen"
+ },
"device_sync": {
"auth_problem_text": "Der Server akzeptiert die Anmeldung von {account} nicht mehr, daher werden die Kontakte und Kalender dieses Kontos auf diesem Gerät nicht mehr synchronisiert. Melden Sie sich erneut an, um fortzufahren. Auf diesem Gerät vorgenommene Änderungen bleiben erhalten.",
"auth_problem_title": "Erneut anmelden, um weiter zu synchronisieren",
@@ -533,6 +536,7 @@
"share_no_users": "Keine anderen Benutzer gefunden",
"share_remove": "Zugriff entfernen",
"share_remove_confirm": "Zugriff entfernen?",
+ "share_remove_for": "Zugriff für {name} entfernen",
"share_role_custom": "Benutzerdefiniert",
"share_search_placeholder": "Nach Name oder E-Mail suchen",
"share_unavailable": "Teilen ist auf diesem Gerät nicht verfügbar",
diff --git a/locales/rn/en.json b/locales/rn/en.json
index 730e7571..b9ff4a2b 100644
--- a/locales/rn/en.json
+++ b/locales/rn/en.json
@@ -325,6 +325,9 @@
"deep_link": {
"open_failed": "Couldn't open this"
},
+ "delegates": {
+ "remove_for": "Remove access for {name}"
+ },
"device_sync": {
"auth_problem_text": "The server no longer accepts the sign-in of {account}, so its contacts and calendars on this device stopped syncing. Sign in again to resume. Changes made on this device are kept.",
"auth_problem_title": "Sign in again to keep syncing",
@@ -730,6 +733,7 @@
"share_no_users": "No other users found",
"share_remove": "Remove access",
"share_remove_confirm": "Remove access?",
+ "share_remove_for": "Remove access for {name}",
"share_role_custom": "Custom",
"share_search_placeholder": "Search by name or email",
"share_unavailable": "Sharing is not available on this device",
diff --git a/src/components/__tests__/row-button-names.test.ts b/src/components/__tests__/row-button-names.test.ts
new file mode 100644
index 00000000..6eaaaec8
--- /dev/null
+++ b/src/components/__tests__/row-button-names.test.ts
@@ -0,0 +1,31 @@
+// Buttons repeated on every row of a list read the same to a screen reader:
+// TalkBack heard "Delete tag" on each tag in Settings > Tags and "Remove
+// access" on each person a file is shared with, without saying whose. They
+// carry the row's name now (webmail keyword-settings.tsx: "Delete {name}").
+import { describe, expect, it } from 'vitest';
+import fs from 'node:fs';
+import path from 'node:path';
+
+const SRC = path.resolve(__dirname, '../..');
+const read = (rel: string) => fs.readFileSync(path.join(SRC, rel), 'utf8').split('\r\n').join('\n');
+
+describe('per-row buttons', () => {
+ it('name the tag they edit or delete', () => {
+ const source = read('components/settings/KeywordSettings.tsx');
+ expect(source).toContain("t('settings.keywords.delete_tag', 'Delete {name}', { name: kw.label })");
+ expect(source).toContain("t('settings.keywords.edit_tag', 'Edit {name}', { name: kw.label })");
+ expect(source).not.toMatch(/accessibilityLabel=\{t\('settings\.keywords\.(delete|edit)',/);
+ });
+
+ it('name the person whose file access they remove', () => {
+ const source = read('components/files/ShareSheet.tsx');
+ expect(source).toContain("t('files.share_remove_for', 'Remove access for {name}', { name: principalName(principalId) })");
+ expect(source).not.toMatch(/accessibilityLabel=\{t\('files\.share_remove', 'Remove access'\)\}/);
+ });
+
+ it('name the delegate they change or remove', () => {
+ const source = read('components/settings/DelegateSettings.tsx');
+ expect(source).toContain("accessibilityLabel={t('delegates.remove_for', 'Remove access for {name}', { name: display.name })}");
+ expect(source).toContain("accessibilityLabel={t('delegates.dialog_title_change', 'Change access for {name}', { name: display.name })}");
+ });
+});
diff --git a/src/components/files/ShareSheet.tsx b/src/components/files/ShareSheet.tsx
index 7e06f5a3..3fee704d 100644
--- a/src/components/files/ShareSheet.tsx
+++ b/src/components/files/ShareSheet.tsx
@@ -167,15 +167,21 @@ export default function ShareSheet({ node, onClose, onChanged }: ShareSheetProps
? t('sharing.directory_not_browsable', "Your server doesn't let you browse its user directory.")
: null;
- const renderPrincipalLabel = (principalId: string) => {
+ const principalName = (principalId: string) => {
const p = principalsById.get(principalId);
// Stalwart 1.0 with directory queries off hides every principal the user
// has no relationship with, so a sharee may have no name.
const unresolved = !p && !loadingPrincipals;
+ return p?.description || p?.name || (unresolved ? t('sharing.unknown_user', 'Unknown user') : principalId);
+ };
+
+ const renderPrincipalLabel = (principalId: string) => {
+ const p = principalsById.get(principalId);
+ const unresolved = !p && !loadingPrincipals;
return (
- {p?.description || p?.name || (unresolved ? t('sharing.unknown_user', 'Unknown user') : principalId)}
+ {principalName(principalId)}
{p?.email || unresolved ? (
@@ -257,7 +263,8 @@ export default function ShareSheet({ node, onClose, onChanged }: ShareSheetProps
)}
hitSlop={8}
accessibilityRole="button"
- accessibilityLabel={t('files.share_remove', 'Remove access')}
+ // One per person: the name tells the buttons apart.
+ accessibilityLabel={t('files.share_remove_for', 'Remove access for {name}', { name: principalName(principalId) })}
>
diff --git a/src/components/settings/DelegateSettings.tsx b/src/components/settings/DelegateSettings.tsx
index c349b279..e13a88d0 100644
--- a/src/components/settings/DelegateSettings.tsx
+++ b/src/components/settings/DelegateSettings.tsx
@@ -241,10 +241,24 @@ export function DelegateSettings() {
-
diff --git a/src/components/settings/KeywordSettings.tsx b/src/components/settings/KeywordSettings.tsx
index 83d867fb..c3f6ca23 100644
--- a/src/components/settings/KeywordSettings.tsx
+++ b/src/components/settings/KeywordSettings.tsx
@@ -120,10 +120,10 @@ export function KeywordSettings() {
{kw.label}$label:{kw.id}
- setEditingId(kw.id)} accessibilityRole="button" accessibilityLabel={t('settings.keywords.edit', "Edit tag")}>
+ setEditingId(kw.id)} accessibilityRole="button" accessibilityLabel={t('settings.keywords.edit_tag', 'Edit {name}', { name: kw.label })}>
- deleteKeyword(kw.id)} accessibilityRole="button" accessibilityLabel={t('settings.keywords.delete', "Delete tag")}>
+ deleteKeyword(kw.id)} accessibilityRole="button" accessibilityLabel={t('settings.keywords.delete_tag', 'Delete {name}', { name: kw.label })}>