При локализации веб-приложений внимание обычно сосредоточено на переводе видимого текста: заголовков, кнопок, сообщений и навигационных элементов. Однако значительная часть интерфейса доступна пользователям исключительно через вспомогательные технологии, такие как программы экранного доступа. Для таких сценариев используются атрибуты ARIA (Accessible Rich Internet Applications), содержимое которых также должно переводиться.
Библиотека I18next позволяет централизованно управлять переводами как видимого контента, так и значений ARIA-атрибутов.
Типичные ARIA-атрибуты, требующие локализации:
aria-labelaria-descriptionaria-placeholderaria-roledescriptionaria-valuetextaria-braillelabelaria-brailleroledescriptionНекоторые стандартные HTML-атрибуты также часто переводятся вместе с ARIA:
titleplaceholderaltПрограмма экранного доступа не анализирует визуальное оформление страницы. Она получает информацию из DOM-структуры и ARIA-атрибутов.
Например:
<button aria-label="Close">
✕
</button>
Пользователь увидит только символ крестика, а скринридер озвучит:
Close
Если интерфейс переключается на русский язык, а атрибут остается непереведенным, возникнет смешение языков:
<button aria-label="Close">
✕
</button>
Визуально:
Закрыть
Озвучивание:
Close
Такое поведение ухудшает доступность и нарушает целостность локализации.
Часто для ARIA создают отдельный раздел переводов.
Файл ru/common.json:
{
"buttons": {
"save": "Сохранить",
"cancel": "Отмена"
},
"aria": {
"closeDialog": "Закрыть диалог",
"openMenu": "Открыть меню",
"searchField": "Поле поиска"
}
}
Файл en/common.json:
{
"buttons": {
"save": "Save",
"cancel": "Cancel"
},
"aria": {
"closeDialog": "Close dialog",
"openMenu": "Open menu",
"searchField": "Search field"
}
}
Использование:
button.setAttribute(
'aria-label',
i18next.t('aria.closeDialog')
);
После смены языка значение будет автоматически браться из соответствующего словаря.
aria-label является наиболее часто локализуемым
атрибутом.
Пример:
<button id="menuButton">
☰
</button>
const button = document.getElementById('menuButton');
button.setAttribute(
'aria-label',
i18next.t('aria.openMenu')
);
Переводы:
{
"aria": {
"openMenu": "Открыть меню"
}
}
{
"aria": {
"openMenu": "Open menu"
}
}
Результат:
<button aria-label="Open menu">
☰
</button>
или
<button aria-label="Открыть меню">
☰
</button>
Для полей ввода часто используются оба атрибута.
Словарь:
{
"search": {
"placeholder": "Введите запрос",
"ariaLabel": "Поле поиска"
}
}
Код:
const input = document.querySelector('#search');
input.placeholder =
i18next.t('search.placeholder');
input.setAttribute(
'aria-label',
i18next.t('search.ariaLabel')
);
Получаем:
<input
placeholder="Введите запрос"
aria-label="Поле поиска"
/>
При большом количестве компонентов полезно группировать переводы по областям интерфейса.
{
"header": {
"aria": {
"logo": "Перейти на главную страницу",
"profile": "Открыть профиль"
}
},
"sidebar": {
"aria": {
"collapse": "Свернуть боковую панель",
"expand": "Развернуть боковую панель"
}
}
}
Использование:
i18next.t('header.aria.logo');
i18next.t('sidebar.aria.collapse');
Такая организация облегчает поддержку крупных проектов.
ARIA-текст нередко содержит динамические данные.
Словарь:
{
"aria": {
"deleteFile": "Удалить файл {{name}}"
}
}
Код:
button.setAttribute(
'aria-label',
i18next.t('aria.deleteFile', {
name: fileName
})
);
Если:
fileName = 'report.pdf';
Результат:
<button aria-label="Удалить файл report.pdf">
Для английского языка:
{
"aria": {
"deleteFile": "Delete file {{name}}"
}
}
Этот атрибут используется для представления значения элементов управления в понятной текстовой форме.
Пример с ползунком громкости:
<input
type="range"
id="volume"
/>
Словарь:
{
"aria": {
"volume": "Громкость {{value}} процентов"
}
}
Обновление:
slider.setAttribute(
'aria-valuetext',
i18next.t('aria.volume', {
value: slider.value
})
);
При значении:
75
Скринридер получит:
Громкость 75 процентов
Некоторые ARIA-сообщения содержат числовые значения и требуют корректного склонения.
Словарь для русского языка:
{
"notifications": {
"unread_one": "{{count}} непрочитанное сообщение",
"unread_few": "{{count}} непрочитанных сообщения",
"unread_many": "{{count}} непрочитанных сообщений",
"unread_other": "{{count}} непрочитанных сообщений"
}
}
Применение:
element.setAttribute(
'aria-label',
i18next.t('notifications.unread', {
count: unreadCount
})
);
Результаты:
1 непрочитанное сообщение
3 непрочитанных сообщения
12 непрочитанных сообщений
I18next автоматически выбирает нужную форму в зависимости от правил языка.
Компонент:
import { useTranslation } from 'react-i18next';
function CloseButton() {
const { t } = useTranslation();
return (
<button
aria-label={t('aria.closeDialog')}
>
✕
</button>
);
}
После переключения языка React выполнит повторный рендеринг и обновит значение атрибута.
Пример:
<input
aria-label={t('search.label')}
aria-description={t('search.description')}
placeholder={t('search.placeholder')}
/>
Словарь:
{
"search": {
"label": "Поле поиска",
"description": "Введите ключевые слова для поиска",
"placeholder": "Поиск..."
}
}
Такой подход обеспечивает полную локализацию компонента.
ARIA позволяет задавать пользовательское описание роли элемента.
Пример:
<div
role="button"
aria-roledescription="Карточка товара"
>
</div>
Через I18next:
element.setAttribute(
'aria-roledescription',
i18next.t('aria.productCard')
);
Словарь:
{
"aria": {
"productCard": "Карточка товара"
}
}
Английский вариант:
{
"aria": {
"productCard": "Product card"
}
}
Если приложение работает без реактивного фреймворка, ARIA-атрибуты необходимо обновлять вручную.
Пример:
i18next.on('languageChanged', () => {
document
.querySelectorAll('[data-i18n-aria]')
.forEach(element => {
const key =
element.dataset.i18nAria;
element.setAttribute(
'aria-label',
i18next.t(key)
);
});
});
HTML:
<button
data-i18n-aria="aria.closeDialog">
</button>
После переключения языка все соответствующие атрибуты будут обновлены.
Подход особенно полезен в крупных приложениях.
HTML:
<button
data-aria-label="aria.closeDialog">
</button>
<input
data-aria-label="aria.searchField">
Jav * aScript:
function localizeAria() {
document
.querySelectorAll('[data-aria-label]')
.forEach(element => {
element.setAttribute(
'aria-label',
i18next.t(
element.dataset.ariaLabel
)
);
});
}
Вызов:
localizeAria();
Иногда вместо aria-label используется ссылка на другой
элемент.
<label id="searchLabel">
Поиск
</label>
<input
aria-labelledby="searchLabel"
/>
Переводится содержимое самого элемента:
label.textContent =
i18next.t('search.label');
Скринридер автоматически получит локализованный текст.
Такой подход предпочтительнее, когда надпись уже присутствует на странице визуально.
При локализации доступности рекомендуется контролировать несколько аспектов:
Нежелательно:
Кнопка визуально: Удалить
aria-label: Уничтожить объект
Лучше:
Кнопка визуально: Удалить
aria-label: Удалить файл
Плохо:
btn del item
Хорошо:
Удалить элемент
Ключ:
{
"aria": {
"close": "Закрыть"
}
}
Может оказаться недостаточным.
Более информативно:
{
"aria": {
"closeDialog": "Закрыть диалог",
"closeMenu": "Закрыть меню",
"closeNotification": "Закрыть уведомление"
}
}
Скринридер будет озвучивать более понятные инструкции.
Пример организации ресурсов:
{
"aria": {
"buttons": {
"save": "Сохранить изменения",
"delete": "Удалить запись",
"close": "Закрыть окно"
},
"navigation": {
"openMenu": "Открыть меню",
"goHome": "Перейти на главную страницу"
},
"forms": {
"search": "Поле поиска",
"email": "Адрес электронной почты",
"password": "Пароль"
}
}
}
Такая структура позволяет хранить все тексты, связанные с доступностью, в одном логическом разделе, упрощая поддержку переводов, аудит доступности и сопровождение многоязычных интерфейсов на основе I18next.