ARIA атрибуты и переводы

При локализации веб-приложений внимание обычно сосредоточено на переводе видимого текста: заголовков, кнопок, сообщений и навигационных элементов. Однако значительная часть интерфейса доступна пользователям исключительно через вспомогательные технологии, такие как программы экранного доступа. Для таких сценариев используются атрибуты ARIA (Accessible Rich Internet Applications), содержимое которых также должно переводиться.

Библиотека I18next позволяет централизованно управлять переводами как видимого контента, так и значений ARIA-атрибутов.

Типичные ARIA-атрибуты, требующие локализации:

  • aria-label
  • aria-description
  • aria-placeholder
  • aria-roledescription
  • aria-valuetext
  • aria-braillelabel
  • aria-brailleroledescription

Некоторые стандартные HTML-атрибуты также часто переводятся вместе с ARIA:

  • title
  • placeholder
  • alt

Почему ARIA-переводы важны

Программа экранного доступа не анализирует визуальное оформление страницы. Она получает информацию из DOM-структуры и ARIA-атрибутов.

Например:

<button aria-label="Close">
  ✕
</button>

Пользователь увидит только символ крестика, а скринридер озвучит:

Close

Если интерфейс переключается на русский язык, а атрибут остается непереведенным, возникнет смешение языков:

<button aria-label="Close">
  ✕
</button>

Визуально:

Закрыть

Озвучивание:

Close

Такое поведение ухудшает доступность и нарушает целостность локализации.


Хранение ARIA-переводов в ресурсах I18next

Часто для 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

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>

Перевод placeholder и aria-label одновременно

Для полей ввода часто используются оба атрибута.

Словарь:

{
  "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-текст нередко содержит динамические данные.

Словарь:

{
  "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}}"
  }
}

Перевод aria-valuetext

Этот атрибут используется для представления значения элементов управления в понятной текстовой форме.

Пример с ползунком громкости:

<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 автоматически выбирает нужную форму в зависимости от правил языка.


ARIA-переводы в React через react-i18next

Компонент:

import { useTranslation } from 'react-i18next';

function CloseButton() {
  const { t } = useTranslation();

  return (
    <button
      aria-label={t('aria.closeDialog')}
    >
      ✕
    </button>
  );
}

После переключения языка React выполнит повторный рендеринг и обновит значение атрибута.


Использование нескольких ARIA-атрибутов

Пример:

<input
  aria-label={t('search.label')}
  aria-description={t('search.description')}
  placeholder={t('search.placeholder')}
/>

Словарь:

{
  "search": {
    "label": "Поле поиска",
    "description": "Введите ключевые слова для поиска",
    "placeholder": "Поиск..."
  }
}

Такой подход обеспечивает полную локализацию компонента.


Перевод aria-roledescription

ARIA позволяет задавать пользовательское описание роли элемента.

Пример:

<div
  role="button"
  aria-roledescription="Карточка товара"
>
</div>

Через I18next:

element.setAttribute(
  'aria-roledescription',
  i18next.t('aria.productCard')
);

Словарь:

{
  "aria": {
    "productCard": "Карточка товара"
  }
}

Английский вариант:

{
  "aria": {
    "productCard": "Product card"
  }
}

Обновление ARIA после смены языка

Если приложение работает без реактивного фреймворка, 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>

После переключения языка все соответствующие атрибуты будут обновлены.


Использование data-атрибутов для массовой локализации

Подход особенно полезен в крупных приложениях.

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-labelledby и переводов

Иногда вместо aria-label используется ссылка на другой элемент.

<label id="searchLabel">
  Поиск
</label>

<input
  aria-labelledby="searchLabel"
/>

Переводится содержимое самого элемента:

label.textContent =
  i18next.t('search.label');

Скринридер автоматически получит локализованный текст.

Такой подход предпочтительнее, когда надпись уже присутствует на странице визуально.


Проверка качества ARIA-переводов

При локализации доступности рекомендуется контролировать несколько аспектов:

Соответствие видимому интерфейсу

Нежелательно:

Кнопка визуально: Удалить
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.