feat: show the accessibility and help links on the login page, in the settings sidebar and in the account menu

This commit is contained in:
Linus Rath
2026-10-29 18:28:06 +01:00
parent 623c9e1792
commit 315e292706
36 changed files with 526 additions and 0 deletions
@@ -31,6 +31,8 @@ vi.mock('@/hooks/use-config', () => ({
loginShowVersion: false,
loginShowTotp: true,
loginShowRememberMe: true,
accessibilityStatementUrl: 'https://example.org/accessibility',
accessibilityFeedbackUrl: 'mailto:barriers@example.org',
}),
}));
@@ -55,6 +57,13 @@ describe('login form accessibility', () => {
expect(document.title).toBe('sign_in - Webmail');
}, 30_000);
it('links the operator\'s accessibility statement and barrier report in the footer (OP-01)', async () => {
await renderForm();
const nav = screen.getByRole('navigation', { name: 'label' });
expect(nav.querySelector('a[href="https://example.org/accessibility"]')).not.toBeNull();
expect(nav.querySelector('a[href="mailto:barriers@example.org"]')).not.toBeNull();
}, 30_000);
it('puts the show-password toggle in the tab order with its state', async () => {
await renderForm();
const toggle = screen.getByRole('button', { name: 'show_password' });
+3
View File
@@ -35,6 +35,7 @@ import { toAsciiDomain } from "@/lib/idn";
import { usePrefetchMailView } from "@/hooks/use-prefetch-mail-view";
import { buildMobileHandoffUrl, mobileHandoffTarget, signedInLoginAction } from "@/lib/auth/mobile-handoff";
import { MobileHandoffConsent } from "@/components/auth/mobile-handoff-consent";
import { HelpLinks } from "@/components/layout/help-links";
import { consumeSessionEnd, type SessionEndReason } from "@/lib/session-policy/sign-out-reason";
/** The domain of a complete address (`user@example.com`), or '' while it is still being typed. */
@@ -1222,6 +1223,7 @@ function LoginPageContent() {
)}
</div>
)}
<HelpLinks listClassName="justify-center" />
{loginShowVersion && <VersionBadge />}
</div>
</div>
@@ -1845,6 +1847,7 @@ function LoginPageContent() {
)}
</div>
)}
<HelpLinks listClassName="justify-center" />
{loginShowVersion && <VersionBadge />}
</div>
</div>
@@ -0,0 +1,109 @@
import { fireEvent, render, screen, within } from '@testing-library/react';
import { beforeEach, describe, expect, it, vi } from 'vitest';
// OP-01: the accessibility and help links the operator configures show in
// the same order wherever they appear (WCAG 3.2.6), and not at all when
// none is set.
const links = vi.hoisted(() => ({
accessibilityStatementUrl: '',
accessibilityFeedbackUrl: '',
helpUrl: '',
easyReadUrl: '',
signLanguageUrl: '',
}));
vi.mock('@/hooks/use-config', async (importOriginal) => {
const actual = await importOriginal<typeof import('@/hooks/use-config')>();
return { ...actual, useConfig: () => ({ ...links }) };
});
import { HelpLinks } from '../help-links';
import { helpLinksFrom } from '@/lib/help-links';
const { AccountSwitcher } = await import('../account-switcher');
const { useAccountStore } = await import('@/stores/account-store');
const { useAuthStore } = await import('@/stores/auth-store');
const { useSettingsStore } = await import('@/stores/settings-store');
const account = {
id: 'a', label: 'a@example.com', email: 'a@example.com', username: 'a@example.com', displayName: 'A',
serverUrl: 'https://mail.example.com', avatarColor: '#336699', cookieSlot: 0, isConnected: true, isDefault: true,
};
beforeEach(() => {
Object.assign(links, {
accessibilityStatementUrl: 'https://example.com/accessibility',
accessibilityFeedbackUrl: 'mailto:barriers@example.com',
helpUrl: '',
easyReadUrl: 'https://example.com/leichte-sprache',
signLanguageUrl: '',
});
useAccountStore.setState({ accounts: [account] } as never);
useAuthStore.setState({ activeAccountId: 'a' } as never);
useSettingsStore.setState({ interfaceLayout: 'default' } as never);
});
describe('helpLinksFrom', () => {
it('keeps the fixed order, leaves out unset links and refuses script URLs', () => {
expect(helpLinksFrom({
signLanguageUrl: 'https://example.com/dgs',
helpUrl: 'javascript:alert(1)',
accessibilityStatementUrl: 'https://example.com/a11y',
accessibilityFeedbackUrl: 'mailto:barriers@example.com',
easyReadUrl: '',
})).toEqual([
{ id: 'statement', href: 'https://example.com/a11y', external: true },
{ id: 'feedback', href: 'mailto:barriers@example.com', external: false },
{ id: 'sign_language', href: 'https://example.com/dgs', external: true },
]);
});
});
describe('help links on a page', () => {
it('lists the configured links in a named navigation', () => {
render(<HelpLinks />);
const nav = screen.getByRole('navigation', { name: 'label' });
const items = within(nav).getAllByRole('link');
expect(items.map((a) => a.getAttribute('href'))).toEqual([
'https://example.com/accessibility',
'mailto:barriers@example.com',
'https://example.com/leichte-sprache',
]);
// A web page opens in a new tab and says so; the mail link doesn't.
expect(items[0].getAttribute('target')).toBe('_blank');
expect(items[0].textContent).toBe('statement (new_tab)');
expect(items[1].getAttribute('target')).toBeNull();
expect(items[1].textContent).toBe('feedback');
});
it('renders nothing when the operator set no link', () => {
Object.assign(links, { accessibilityStatementUrl: '', accessibilityFeedbackUrl: '', easyReadUrl: '' });
const { container } = render(<HelpLinks />);
expect(container.innerHTML).toBe('');
});
});
describe('help links in the account menu', () => {
it('offers them as menu items and closes the menu on choosing one', () => {
render(<AccountSwitcher variant="rail" />);
fireEvent.click(screen.getByTestId('account-switcher'));
const group = screen.getByRole('group', { name: 'label' });
const items = within(group).getAllByRole('menuitem');
expect(items.map((a) => a.textContent)).toEqual(['statement (new_tab)', 'feedback', 'easy_read (new_tab)']);
fireEvent.click(items[0]);
expect(screen.queryByRole('menu')).toBeNull();
});
it('offers them under Mountain View too', () => {
useSettingsStore.setState({ interfaceLayout: 'mountain-view' } as never);
render(<AccountSwitcher variant="header" />);
fireEvent.click(screen.getByTestId('account-switcher'));
expect(within(screen.getByRole('group', { name: 'label' })).getAllByRole('menuitem')).toHaveLength(3);
});
it('leaves the group out when no link is set', () => {
Object.assign(links, { accessibilityStatementUrl: '', accessibilityFeedbackUrl: '', easyReadUrl: '' });
render(<AccountSwitcher variant="rail" />);
fireEvent.click(screen.getByTestId('account-switcher'));
expect(screen.queryByRole('group', { name: 'label' })).toBeNull();
});
});
+15
View File
@@ -9,6 +9,7 @@ import { useAccountStore, type AccountEntry } from "@/stores/account-store";
import { useAuthStore } from "@/stores/auth-store";
import { useEmailStore } from "@/stores/email-store";
import { useSettingsStore } from "@/stores/settings-store";
import { HelpMenuItems } from "@/components/layout/help-links";
import { getMaxAccounts, sortDefaultFirst, reorderNonDefaultIds } from "@/lib/account-utils";
import { inboxUnread, totalInboxUnread } from "@/lib/account-inbox-unread";
import { isDocumentRTL } from "@/i18n/direction";
@@ -564,6 +565,11 @@ export function AccountSwitcher({
})}
</div>
)}
<HelpMenuItems
onSelect={() => setOpen(false)}
className="mt-2 rounded-3xl bg-background py-1"
itemClassName="w-full flex items-center px-5 py-3 text-sm text-start hover:bg-muted transition-colors"
/>
</>
)}
@@ -582,6 +588,15 @@ export function AccountSwitcher({
</div>
)}
{/* Accessibility and help links the operator set (lib/help-links.ts). */}
{!mvPopover && (
<HelpMenuItems
onSelect={() => setOpen(false)}
className="border-t border-border py-1"
itemClassName="w-full flex items-center px-3 py-2 pointer-coarse:min-h-11 text-sm text-start text-foreground hover:bg-muted transition-colors"
/>
)}
{/* Separator + Actions. Under Mountain View a single login signs out
from the pill above, so these rows are only for picking among several. */}
<div className={cn("border-t border-border", mvPopover && "hidden")}>
+81
View File
@@ -0,0 +1,81 @@
'use client';
import { useTranslations } from 'next-intl';
import { useConfig } from '@/hooks/use-config';
import { helpLinksFrom, type HelpLink } from '@/lib/help-links';
import { cn } from '@/lib/utils';
/** The configured accessibility and help links, in their fixed order. */
export function useHelpLinks(): HelpLink[] {
const { accessibilityStatementUrl, accessibilityFeedbackUrl, helpUrl, easyReadUrl, signLanguageUrl } = useConfig();
return helpLinksFrom({ accessibilityStatementUrl, accessibilityFeedbackUrl, helpUrl, easyReadUrl, signLanguageUrl });
}
function LinkText({ link }: { link: HelpLink }) {
const t = useTranslations('help_links');
return (
<>
{t(link.id)}
{link.external && <span className="sr-only"> ({t('new_tab')})</span>}
</>
);
}
/**
* The links as a small navigation list, for the login page footer and the
* settings sidebar. Renders nothing when the operator set none.
*/
export function HelpLinks({ className, listClassName, linkClassName }: { className?: string; listClassName?: string; linkClassName?: string }) {
const t = useTranslations('help_links');
const links = useHelpLinks();
if (links.length === 0) return null;
return (
<nav aria-label={t('label')} className={className} data-testid="help-links">
<ul className={cn('flex flex-wrap items-center gap-x-3 gap-y-1', listClassName)}>
{links.map((link) => (
<li key={link.id}>
<a
href={link.href}
target={link.external ? '_blank' : undefined}
rel={link.external ? 'noopener noreferrer' : undefined}
className={cn('text-xs text-muted-foreground underline-offset-2 hover:text-foreground hover:underline', linkClassName)}
>
<LinkText link={link} />
</a>
</li>
))}
</ul>
</nav>
);
}
/**
* The links as menu items, for the account menu. `onSelect` closes the
* menu. Renders nothing when the operator set none.
*/
export function HelpMenuItems({ onSelect, className, itemClassName }: {
onSelect?: () => void;
className?: string;
itemClassName?: string;
}) {
const t = useTranslations('help_links');
const links = useHelpLinks();
if (links.length === 0) return null;
return (
<div role="group" aria-label={t('label')} className={className} data-testid="help-menu-items">
{links.map((link) => (
<a
key={link.id}
role="menuitem"
href={link.href}
target={link.external ? '_blank' : undefined}
rel={link.external ? 'noopener noreferrer' : undefined}
onClick={() => onSelect?.()}
className={itemClassName}
>
<LinkText link={link} />
</a>
))}
</div>
);
}
+3
View File
@@ -47,6 +47,7 @@ import { CalendarManagementSettings } from '@/components/settings/calendar-manag
import { AddressBookManagementSettings } from '@/components/settings/address-book-management-settings';
import { FilterSettings } from '@/components/settings/filter-settings';
import { AboutDataSettings, AboutFooter } from '@/components/settings/about-data-settings';
import { HelpLinks } from '@/components/layout/help-links';
import { OrganizeSettings } from '@/components/settings/organize-settings';
import { AccountSecuritySettings } from '@/components/settings/account-security-settings';
import { FilesSettingsComponent } from '@/components/settings/files-settings';
@@ -1032,6 +1033,7 @@ export function SettingsApp({ linkSegments: routeSegments }: SettingsAppProps =
</div>
<div className="border-t border-border px-2 py-2">
<HelpLinks className="px-3 py-2" />
{showAdvanced && <AboutFooter />}
<AdvancedSettingsSwitch />
</div>
@@ -1188,6 +1190,7 @@ export function SettingsApp({ linkSegments: routeSegments }: SettingsAppProps =
</div>
</nav>
<div className="border-t border-border px-1 py-1">
<HelpLinks className="px-3 py-2" />
{showAdvanced && <AboutFooter />}
<AdvancedSettingsSwitch />
</div>
+45
View File
@@ -0,0 +1,45 @@
import type { ConfigData } from '@/hooks/use-config';
/**
* The accessibility and help links an operator configures (OP-01): the
* accessibility statement, where to report a barrier, help, Easy Read
* (Leichte Sprache) and sign language pages. They show in the same order
* in every place that lists them (WCAG 3.2.6): the login page footer, the
* settings sidebar and the account menu. Links that aren't set are left out.
*/
export type HelpLinkId = 'statement' | 'feedback' | 'help' | 'easy_read' | 'sign_language';
export interface HelpLink {
id: HelpLinkId;
href: string;
/** mailto: links open the mail app, not a new tab. */
external: boolean;
}
export type HelpLinkConfig = Pick<
ConfigData,
'accessibilityStatementUrl' | 'accessibilityFeedbackUrl' | 'helpUrl' | 'easyReadUrl' | 'signLanguageUrl'
>;
const ORDER: ReadonlyArray<[HelpLinkId, keyof HelpLinkConfig]> = [
['statement', 'accessibilityStatementUrl'],
['feedback', 'accessibilityFeedbackUrl'],
['help', 'helpUrl'],
['easy_read', 'easyReadUrl'],
['sign_language', 'signLanguageUrl'],
];
/**
* The configured links, in their fixed order. The values were checked when
* the config was served (http(s) or mailto: only); this checks again so a
* config from elsewhere can't put a script URL in a link.
*/
export function helpLinksFrom(config: Partial<HelpLinkConfig>): HelpLink[] {
const out: HelpLink[] = [];
for (const [id, key] of ORDER) {
const href = typeof config[key] === 'string' ? config[key]!.trim() : '';
if (!/^(https?:\/\/|mailto:)/i.test(href)) continue;
out.push({ id, href, external: !/^mailto:/i.test(href) });
}
return out;
}
+9
View File
@@ -5911,5 +5911,14 @@
"text": "CORS قاعدة في المتصفح. يجب أن يسمح الخادم لموقع الويب أولًا، وبعدها يسمح المتصفح لهذا الموقع بالتواصل مع الخادم."
}
}
},
"help_links": {
"label": "إمكانية الوصول والمساعدة",
"statement": "بيان إمكانية الوصول",
"feedback": "الإبلاغ عن عائق",
"help": "المساعدة",
"easy_read": "قراءة سهلة",
"sign_language": "لغة الإشارة",
"new_tab": "يُفتح في علامة تبويب جديدة"
}
}
+9
View File
@@ -5911,5 +5911,14 @@
"text": "CORS és una regla del navegador. Un servidor ha de permetre primer un lloc web perquè el navegador deixi que aquest lloc parli amb el servidor."
}
}
},
"help_links": {
"label": "Accessibilitat i ajuda",
"statement": "Declaració d'accessibilitat",
"feedback": "Informa d'una barrera",
"help": "Ajuda",
"easy_read": "Lectura fàcil",
"sign_language": "Llengua de signes",
"new_tab": "s'obre en una pestanya nova"
}
}
+9
View File
@@ -5911,5 +5911,14 @@
"text": "CORS je pravidlo prohlížeče. Server musí webovou stránku nejdřív povolit, teprve pak ji prohlížeč nechá se serverem komunikovat."
}
}
},
"help_links": {
"label": "Přístupnost a nápověda",
"statement": "Prohlášení o přístupnosti",
"feedback": "Nahlásit bariéru",
"help": "Nápověda",
"easy_read": "Snadné čtení",
"sign_language": "Znakový jazyk",
"new_tab": "otevře se na nové kartě"
}
}
+9
View File
@@ -5911,5 +5911,14 @@
"text": "CORS er en regel i browseren. En server skal først tillade en hjemmeside, før browseren lader den hjemmeside tale med serveren."
}
}
},
"help_links": {
"label": "Tilgængelighed og hjælp",
"statement": "Tilgængelighedserklæring",
"feedback": "Rapportér en barriere",
"help": "Hjælp",
"easy_read": "Let læst",
"sign_language": "Tegnsprog",
"new_tab": "åbner i en ny fane"
}
}
+9
View File
@@ -5911,5 +5911,14 @@
"text": "CORS ist eine Regel im Browser. Ein Server muss eine Website erst erlauben, bevor der Browser diese Website mit dem Server sprechen lässt."
}
}
},
"help_links": {
"label": "Barrierefreiheit und Hilfe",
"statement": "Erklärung zur Barrierefreiheit",
"feedback": "Barriere melden",
"help": "Hilfe",
"easy_read": "Leichte Sprache",
"sign_language": "Gebärdensprache",
"new_tab": "öffnet in einem neuen Tab"
}
}
+9
View File
@@ -5911,5 +5911,14 @@
"text": "CORS is a browser rule. A server has to allow a website before the browser lets that website talk to the server."
}
}
},
"help_links": {
"label": "Accessibility and help",
"statement": "Accessibility statement",
"feedback": "Report a barrier",
"help": "Help",
"easy_read": "Easy Read",
"sign_language": "Sign language",
"new_tab": "opens in a new tab"
}
}
+9
View File
@@ -5911,5 +5911,14 @@
"text": "CORS es una regla del navegador. Un servidor tiene que permitir antes un sitio web para que el navegador deje que ese sitio hable con el servidor."
}
}
},
"help_links": {
"label": "Accesibilidad y ayuda",
"statement": "Declaración de accesibilidad",
"feedback": "Informar de una barrera",
"help": "Ayuda",
"easy_read": "Lectura fácil",
"sign_language": "Lengua de signos",
"new_tab": "se abre en una pestaña nueva"
}
}
+9
View File
@@ -5911,5 +5911,14 @@
"text": "CORS on brauseri reegel. Server peab veebisaidi esmalt lubama, alles siis laseb brauser sellel veebisaidil serveriga suhelda."
}
}
},
"help_links": {
"label": "Juurdepääsetavus ja abi",
"statement": "Juurdepääsetavuse teatis",
"feedback": "Teata takistusest",
"help": "Abi",
"easy_read": "Lihtne keel",
"sign_language": "Viipekeel",
"new_tab": "avaneb uuel vahekaardil"
}
}
+9
View File
@@ -5911,5 +5911,14 @@
"text": "CORS یک قانون مرورگر است. سرور باید اول به یک وب‌سایت اجازه بدهد تا مرورگر بگذارد آن وب‌سایت با سرور ارتباط بگیرد."
}
}
},
"help_links": {
"label": "دسترس‌پذیری و راهنما",
"statement": "بیانیه دسترس‌پذیری",
"feedback": "گزارش یک مانع",
"help": "راهنما",
"easy_read": "متن آسان‌خوان",
"sign_language": "زبان اشاره",
"new_tab": "در زبانه‌ای تازه باز می‌شود"
}
}
+9
View File
@@ -5911,5 +5911,14 @@
"text": "CORS est une règle du navigateur. Un serveur doit d'abord autoriser un site web avant que le navigateur laisse ce site communiquer avec le serveur."
}
}
},
"help_links": {
"label": "Accessibilité et aide",
"statement": "Déclaration d'accessibilité",
"feedback": "Signaler un obstacle",
"help": "Aide",
"easy_read": "Facile à lire",
"sign_language": "Langue des signes",
"new_tab": "s'ouvre dans un nouvel onglet"
}
}
+9
View File
@@ -5912,5 +5912,14 @@
"text": "CORS הוא כלל של הדפדפן. שרת צריך לאשר אתר קודם, ורק אז הדפדפן מאפשר לאתר הזה לדבר עם השרת."
}
}
},
"help_links": {
"label": "נגישות ועזרה",
"statement": "הצהרת נגישות",
"feedback": "דיווח על מכשול",
"help": "עזרה",
"easy_read": "שפה פשוטה",
"sign_language": "שפת סימנים",
"new_tab": "נפתח בכרטיסייה חדשה"
}
}
+9
View File
@@ -5911,5 +5911,14 @@
"text": "A CORS egy böngészőszabály. A szervernek először engedélyeznie kell egy weboldalt, csak utána engedi a böngésző, hogy az a weboldal a szerverrel kommunikáljon."
}
}
},
"help_links": {
"label": "Kisegítő lehetőségek és súgó",
"statement": "Akadálymentességi nyilatkozat",
"feedback": "Akadály bejelentése",
"help": "Súgó",
"easy_read": "Könnyen érthető",
"sign_language": "Jelnyelv",
"new_tab": "új lapon nyílik meg"
}
}
+9
View File
@@ -5911,5 +5911,14 @@
"text": "CORS è una regola del browser. Un server deve autorizzare un sito web prima che il browser permetta a quel sito di comunicare con il server."
}
}
},
"help_links": {
"label": "Accessibilità e aiuto",
"statement": "Dichiarazione di accessibilità",
"feedback": "Segnala una barriera",
"help": "Aiuto",
"easy_read": "Facile da leggere",
"sign_language": "Lingua dei segni",
"new_tab": "si apre in una nuova scheda"
}
}
+9
View File
@@ -5911,5 +5911,14 @@
"text": "CORS はブラウザーのルールです。サーバーがウェブサイトを許可しないと、ブラウザーはそのサイトがサーバーとやり取りすることを認めません。"
}
}
},
"help_links": {
"label": "アクセシビリティとヘルプ",
"statement": "アクセシビリティ方針",
"feedback": "バリアを報告",
"help": "ヘルプ",
"easy_read": "わかりやすい版",
"sign_language": "手話",
"new_tab": "新しいタブで開きます"
}
}
+9
View File
@@ -5911,5 +5911,14 @@
"text": "CORS는 브라우저 규칙이에요. 서버가 먼저 웹사이트를 허용해야 브라우저가 그 웹사이트와 서버의 통신을 허락해요."
}
}
},
"help_links": {
"label": "접근성 및 도움말",
"statement": "접근성 선언문",
"feedback": "장벽 신고",
"help": "도움말",
"easy_read": "쉬운 글",
"sign_language": "수어",
"new_tab": "새 탭에서 열림"
}
}
+9
View File
@@ -5911,5 +5911,14 @@
"text": "CORS ir pārlūka noteikums. Serverim vispirms ir jāatļauj vietne, un tikai tad pārlūks ļauj šai vietnei sazināties ar serveri."
}
}
},
"help_links": {
"label": "Pieejamība un palīdzība",
"statement": "Pieejamības paziņojums",
"feedback": "Ziņot par šķērsli",
"help": "Palīdzība",
"easy_read": "Vieglā valoda",
"sign_language": "Zīmju valoda",
"new_tab": "atveras jaunā cilnē"
}
}
+9
View File
@@ -5911,5 +5911,14 @@
"text": "CORS бол хөтчийн дүрэм юм. Сервер эхлээд вэбсайтыг зөвшөөрөх ёстой, тэгж байж хөтөч тэр вэбсайтыг сервертэй харилцахыг зөвшөөрнө."
}
}
},
"help_links": {
"label": "Хүртээмж ба тусламж",
"statement": "Хүртээмжийн мэдэгдэл",
"feedback": "Саад бэрхшээл мэдээлэх",
"help": "Тусламж",
"easy_read": "Хялбар унших",
"sign_language": "Дохионы хэл",
"new_tab": "шинэ табад нээгдэнэ"
}
}
+9
View File
@@ -5911,5 +5911,14 @@
"text": "CORS er en regel i nettleseren. En server må tillate et nettsted før nettleseren lar det nettstedet snakke med serveren."
}
}
},
"help_links": {
"label": "Tilgjengelighet og hjelp",
"statement": "Tilgjengelighetserklæring",
"feedback": "Meld fra om en barriere",
"help": "Hjelp",
"easy_read": "Lettlest",
"sign_language": "Tegnspråk",
"new_tab": "åpnes i en ny fane"
}
}
+9
View File
@@ -5911,5 +5911,14 @@
"text": "CORS is een regel in de browser. Een server moet een website eerst toestaan voordat de browser die website met de server laat praten."
}
}
},
"help_links": {
"label": "Toegankelijkheid en help",
"statement": "Toegankelijkheidsverklaring",
"feedback": "Meld een drempel",
"help": "Help",
"easy_read": "Makkelijk lezen",
"sign_language": "Gebarentaal",
"new_tab": "opent in een nieuw tabblad"
}
}
+9
View File
@@ -5911,5 +5911,14 @@
"text": "CORS to reguła przeglądarki. Serwer musi najpierw zezwolić na stronę internetową, zanim przeglądarka pozwoli tej stronie rozmawiać z serwerem."
}
}
},
"help_links": {
"label": "Dostępność i pomoc",
"statement": "Deklaracja dostępności",
"feedback": "Zgłoś barierę",
"help": "Pomoc",
"easy_read": "Tekst łatwy do czytania",
"sign_language": "Język migowy",
"new_tab": "otwiera się w nowej karcie"
}
}
+9
View File
@@ -5911,5 +5911,14 @@
"text": "CORS é uma regra do navegador. Um servidor precisa permitir um site antes que o navegador deixe esse site conversar com o servidor."
}
}
},
"help_links": {
"label": "Acessibilidade e ajuda",
"statement": "Declaração de acessibilidade",
"feedback": "Relatar uma barreira",
"help": "Ajuda",
"easy_read": "Leitura fácil",
"sign_language": "Língua de sinais",
"new_tab": "abre em uma nova aba"
}
}
+9
View File
@@ -5911,5 +5911,14 @@
"text": "CORS este o regulă a browserului. Un server trebuie mai întâi să permită un site, apoi browserul lasă acel site să comunice cu serverul."
}
}
},
"help_links": {
"label": "Accesibilitate și ajutor",
"statement": "Declarație de accesibilitate",
"feedback": "Raportează o barieră",
"help": "Ajutor",
"easy_read": "Ușor de citit",
"sign_language": "Limbaj mimico-gestual",
"new_tab": "se deschide într-o filă nouă"
}
}
+9
View File
@@ -5911,5 +5911,14 @@
"text": "CORS — это правило браузера. Сервер должен сначала разрешить сайт, и только тогда браузер позволит этому сайту общаться с сервером."
}
}
},
"help_links": {
"label": "Специальные возможности и помощь",
"statement": "Заявление о доступности",
"feedback": "Сообщить о барьере",
"help": "Помощь",
"easy_read": "Простой язык",
"sign_language": "Жестовый язык",
"new_tab": "открывается в новой вкладке"
}
}
+9
View File
@@ -5911,5 +5911,14 @@
"text": "CORS je pravidlo prehliadača. Server musí webovú stránku najprv povoliť, až potom ju prehliadač nechá komunikovať so serverom."
}
}
},
"help_links": {
"label": "Prístupnosť a nápoveda",
"statement": "Vyhlásenie o prístupnosti",
"feedback": "Nahlásiť bariéru",
"help": "Nápoveda",
"easy_read": "Ľahké čítanie",
"sign_language": "Posunkový jazyk",
"new_tab": "otvorí sa na novej karte"
}
}
+9
View File
@@ -5919,5 +5919,14 @@
"text": "CORS คือกฎของเบราว์เซอร์ เซิร์ฟเวอร์ต้องอนุญาตเว็บไซต์ก่อน เบราว์เซอร์จึงจะยอมให้เว็บไซต์นั้นคุยกับเซิร์ฟเวอร์"
}
}
},
"help_links": {
"label": "การเข้าถึงและความช่วยเหลือ",
"statement": "คำแถลงการเข้าถึง",
"feedback": "แจ้งอุปสรรค",
"help": "ความช่วยเหลือ",
"easy_read": "ภาษาที่อ่านง่าย",
"sign_language": "ภาษามือ",
"new_tab": "เปิดในแท็บใหม่"
}
}
+9
View File
@@ -5911,5 +5911,14 @@
"text": "CORS bir tarayıcı kuralıdır. Tarayıcının bir web sitesinin sunucuyla konuşmasına izin vermesi için önce sunucunun o siteye izin vermesi gerekir."
}
}
},
"help_links": {
"label": "Erişilebilirlik ve yardım",
"statement": "Erişilebilirlik beyanı",
"feedback": "Engel bildir",
"help": "Yardım",
"easy_read": "Kolay okunur",
"sign_language": "İşaret dili",
"new_tab": "yeni sekmede açılır"
}
}
+9
View File
@@ -5911,5 +5911,14 @@
"text": "CORS — це правило браузера. Сервер має спершу дозволити сайт, і лише тоді браузер дозволить цьому сайту спілкуватися із сервером."
}
}
},
"help_links": {
"label": "Доступність і довідка",
"statement": "Заява про доступність",
"feedback": "Повідомити про бар'єр",
"help": "Довідка",
"easy_read": "Легке читання",
"sign_language": "Жестова мова",
"new_tab": "відкривається в новій вкладці"
}
}
+9
View File
@@ -5911,5 +5911,14 @@
"text": "CORS 是瀏覽器的一條規則。伺服器必須先允許某個網站,瀏覽器才會讓這個網站與伺服器溝通。"
}
}
},
"help_links": {
"label": "無障礙與說明",
"statement": "無障礙聲明",
"feedback": "回報障礙",
"help": "說明",
"easy_read": "易讀版",
"sign_language": "手語",
"new_tab": "在新分頁中開啟"
}
}
+9
View File
@@ -5911,5 +5911,14 @@
"text": "CORS 是浏览器的一条规则。服务器必须先允许某个网站,浏览器才会让这个网站与服务器通信。"
}
}
},
"help_links": {
"label": "无障碍与帮助",
"statement": "无障碍声明",
"feedback": "报告障碍",
"help": "帮助",
"easy_read": "易读版",
"sign_language": "手语",
"new_tab": "在新标签页中打开"
}
}