Экранные читалки

Экранные читалки (screen readers) — вспомогательные технологии, преобразующие содержимое интерфейса в голосовой вывод или текст для устройств Брайля. При разработке многоязычных приложений недостаточно просто перевести текст. Интерфейс должен корректно озвучиваться, учитывать локаль пользователя, особенности форматирования чисел, дат, валют и структуру документа.

Библиотека FormatJS используется для интернационализации JavaScript-приложений и тесно связана с доступностью. Неправильное использование API форматирования способно ухудшить восприятие интерфейса экранными читалками даже при наличии корректного перевода.


Как экранные читалки интерпретируют интерфейс

Экранная читалка анализирует:

  • DOM-структуру;
  • семантические HTML-элементы;
  • ARIA-атрибуты;
  • текстовые значения;
  • язык документа;
  • изменения контента в реальном времени.

Для пользователя критичны:

  • корректное произношение текста;
  • правильное чтение дат и чисел;
  • понятные уведомления;
  • предсказуемая структура;
  • отсутствие визуально-зависимых элементов.

Пример проблемы:

<div>12/05/2026</div>

Без информации о локали и формате экранная читалка может интерпретировать дату неоднозначно:

  • 12 мая;
  • 5 декабря;
  • либо произнести набор чисел.

FormatJS решает подобные проблемы через локализованное форматирование.


Связь FormatJS и доступности

Основные пакеты экосистемы:

  • react-intl
  • intl-messageformat
  • @formatjs/intl
  • @formatjs/cli

Ключевая задача — предоставление семантически корректного локализованного текста.

Пример:

<IntlProvider locale="ru">
  <FormattedDate
    value={new Date()}
    year="numeric"
    month="long"
    day="numeric"
  />
</IntlProvider>

Экранная читалка получит уже локализованную строку:

29 мая 2026 г.

Вместо:

05/29/2026

Атрибут lang

Экранные читалки используют атрибут lang для выбора правил произношения.

Без него текст может озвучиваться неверным голосовым движком.

Неправильно

<html>

Правильно

<html lang="ru">

Для переключения локали:

<IntlProvider locale="fr">
  <App />
</IntlProvider>

и:

<html lang="fr">

Если приложение меняет язык динамически, атрибут должен обновляться синхронно.

Пример:

document.documentElement.lang = currentLocale;

Локализация чисел и доступность

Числа читаются экранными читалками согласно региональным правилам.

Форматирование через FormatJS

<FormattedNumber value={1000000.5} />

Русская локаль:

1 000 000,5

Американская:

1,000,000.5

Экранная читалка корректно распознает:

  • разделители тысяч;
  • десятичные знаки;
  • денежные единицы;
  • проценты.

Проблемы ручного форматирования

Ошибка

<span>{price} RUB</span>

Читалка может озвучить:

руб
эр ю би

или:

R U B

Правильный подход

<FormattedNumber
  value={price}
  style="currency"
  currency="RUB"
/>

Результат:

1 500,00 ₽

Озвучивание становится естественным.


Форматирование дат для экранных читалок

Дата должна быть понятной в голосовом интерфейсе.

Плохой вариант

<div>2026-05-29</div>

Озвучивание может быть неудобным:

два ноль два шесть тире ноль пять тире два девять

Использование FormattedDate

<FormattedDate
  value={date}
  weekday="long"
  year="numeric"
  month="long"
  day="numeric"
/>

Результат:

пятница, 29 мая 2026 г.

Форматирование времени

Временные значения особенно важны:

  • таймеры;
  • дедлайны;
  • уведомления;
  • расписания;
  • события.

Пример

<FormattedTime
  value={date}
  hour="numeric"
  minute="numeric"
/>

Локализованный вывод:

14:30

или:

2:30 PM

в зависимости от региона.

Экранная читалка использует региональные правила чтения времени.


Использование aria-label

Иногда визуальный текст недостаточен для доступности.

Пример иконки

<button>
  ?
</button>

Экранная читалка может озвучить:

кнопка

или:

эмодзи лупа

Локализованный aria-label

<button
  aria-label={intl.formatMessage({
    id: 'search',
    defaultMessage: 'Поиск'
  })}
>
  ?
</button>

Локализация ARIA-атрибутов

FormatJS часто используется именно внутри ARIA-атрибутов:

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

Пример

<input
  aria-label={intl.formatMessage({
    id: 'emailField',
    defaultMessage: 'Адрес электронной почты'
  })}
/>

Использование formatMessage

Основной механизм локализации:

intl.formatMessage()

Пример:

const message = intl.formatMessage({
  id: 'profile.edit',
  defaultMessage: 'Редактировать профиль'
});

Контекстно-зависимые сообщения

Экранным читалкам нужен полный контекст.

Плохо

<button>Подробнее</button>

При навигации по кнопкам пользователь услышит множество одинаковых элементов.

Лучше

<button>
  {intl.formatMessage({
    id: 'article.more',
    defaultMessage: 'Подробнее о статье'
  })}
</button>

Динамические уведомления и aria-live

Screen reader должен узнавать об изменениях интерфейса.

Использование live region

<div aria-live="polite">
  {message}
</div>

Локализованное сообщение

const message = intl.formatMessage({
  id: 'cart.added',
  defaultMessage: 'Товар добавлен в корзину'
});

Режимы aria-live

polite

Сообщение озвучивается после завершения текущей речи.

<div aria-live="polite">

Подходит для:

  • уведомлений;
  • обновлений статуса;
  • сообщений об успехе.

assertive

Прерывает текущую речь.

<div aria-live="assertive">

Используется осторожно:

  • ошибки;
  • критические предупреждения;
  • проблемы безопасности.

Интерполяция значений

FormatJS поддерживает безопасную подстановку параметров.

Пример

intl.formatMessage(
  {
    id: 'notifications',
    defaultMessage:
      'У вас {count} уведомлений'
  },
  {
    count: 5
  }
)

Экранная читалка озвучит фразу естественно.


Плюрализация и доступность

Русский язык имеет сложные правила склонения.

Без ICU MessageFormat

`${count} сообщений`

Ошибки:

  • 1 сообщений
  • 2 сообщений

С ICU

defaultMessage: `
{count, plural,
  one {# сообщение}
  few {# сообщения}
  many {# сообщений}
}`

Это важно для корректного восприятия голосом.


Гендерные формы

Некоторые языки требуют гендерных вариантов.

defaultMessage: `
{gender, select,
  male {Он вошёл}
  female {Она вошла}
  other {Вход выполнен}
}`

Экранная читалка получает грамматически корректную фразу.


Форматирование списков

Экранные читалки воспринимают списки иначе, чем визуальные пользователи.

Плохо

<span>Apple, Banana, Orange</span>

Использование FormattedList

<FormattedList
  type="conjunction"
  value={['Apple', 'Banana', 'Orange']}
/>

Результат:

Apple, Banana и Orange

Читалка произносит список естественно.


Семантический HTML

FormatJS не заменяет правильную HTML-структуру.

Ошибка

<div onCl ick={submit}>
  Отправить
</div>

Правильно

<button>
  {intl.formatMessage({
    id: 'submit',
    defaultMessage: 'Отправить'
  })}
</button>

Заголовки и навигация

Пользователи screen reader часто перемещаются по заголовкам.

Неправильно

<div className="title">
  Настройки
</div>

Правильно

<h2>
  {intl.formatMessage({
    id: 'settings',
    defaultMessage: 'Настройки'
  })}
</h2>

Скрытый текст для screen reader

Иногда нужен дополнительный контекст.

Пример

<button>
  ×
  <span className="sr-only">
    {intl.formatMessage({
      id: 'closeDialog',
      defaultMessage: 'Закрыть окно'
    })}
  </span>
</button>

CSS:

.sr-only {
  position: absolute;
  left: -9999px;
}

Проблемы сокращений

Аббревиатуры могут читаться неверно.

Пример

<span>JS</span>

Читалка может произнести:

джей эс

или:

жс

Решение

<abbr title="JavaScript">
  JS
</abbr>

Озвучивание валют

FormatJS умеет локализовать валюты.

<FormattedNumber
  value={1999}
  style="currency"
  currency="KZT"
/>

Для Казахстана:

1 999 ₸

Экранная читалка корректно распознает тенге.


Использование FormattedRelativeTime

Относительное время удобнее воспринимается голосом.

Пример

<FormattedRelativeTime
  value={-3}
  unit="day"
/>

Результат:

3 дня назад

Вместо:

26 мая 2026

Ошибки при локализации доступности

Перевод только видимого текста

Ошибка:

<button aria-label="Search">
  Поиск
</button>

Визуально — русский интерфейс, голосом — английский.


Использование изображений вместо текста

<img src="button.png" />

Screen reader не получает контент.


Отсутствие alt

<img src="logo.png" />

Правильно:

<img
  src="logo.png"
  alt={intl.formatMessage({
    id: 'companyLogo',
    defaultMessage: 'Логотип компании'
  })}
/>

RTL-языки и экранные читалки

Для арабского и иврита важны:

  • направление текста;
  • порядок чтения;
  • корректная локаль.

Пример

<html lang="ar" dir="rtl">

Автоматическое определение локали

Часто используется:

navigator.language

Пример:

const locale = navigator.language;

Однако screen reader может использовать отдельные настройки языка, поэтому предпочтительно предоставлять ручной выбор локали.


Проверка доступности

Инструменты тестирования:

  • NVDA
  • JAWS
  • VoiceOver
  • TalkBack

Автоматические проверки:

  • axe
  • Lighthouse
  • eslint-plugin-jsx-a11y

Тестирование локализованных сообщений

Проверяется:

  • корректность склонений;
  • чтение чисел;
  • озвучивание валют;
  • произношение дат;
  • поведение live region;
  • работа ARIA-атрибутов;
  • переключение языков.

Использование defaultMessage

defaultMessage служит резервным текстом.

intl.formatMessage({
  id: 'save',
  defaultMessage: 'Сохранить'
})

Если перевод отсутствует, интерфейс не останется пустым.

Для screen reader это особенно важно: пустой aria-label делает элемент практически недоступным.


Форматирование единиц измерения

<FormattedNumber
  value={25}
  style="unit"
  unit="kilobyte"
/>

Результат:

25 килобайт

Вместо:

25 KB

Доступность форм

Пример корректной локализации

<label htmlFor="email">
  {intl.formatMessage({
    id: 'email',
    defaultMessage: 'Электронная почта'
  })}
</label>

<input id="email" />

Сообщения об ошибках

Ошибка должна:

  • быть связана с полем;
  • озвучиваться автоматически;
  • локализоваться.

Пример

<input
  aria-describedby="email-error"
/>

<div
  id="email-error"
  aria-live="assertive"
>
  {intl.formatMessage({
    id: 'emailError',
    defaultMessage:
      'Введите корректный адрес электронной почты'
  })}
</div>

Производительность и screen reader

Частые обновления DOM могут:

  • сбивать фокус;
  • вызывать повторное чтение;
  • создавать шум.

FormatJS помогает централизовать локализацию и уменьшить количество лишних ререндеров при правильной архитектуре.


Использование useIntl

Хук:

const intl = useIntl();

Позволяет локализовать:

  • уведомления;
  • ARIA-атрибуты;
  • динамические сообщения;
  • ошибки форм.

Пример:

const label = intl.formatMessage({
  id: 'menu.open',
  defaultMessage: 'Открыть меню'
});

Локализация кнопок состояния

Плохо

<button>
  ON
</button>

Лучше

<button
  aria-pressed={enabled}
>
  {enabled
    ? intl.formatMessage({
        id: 'enabled',
        defaultMessage: 'Включено'
      })
    : intl.formatMessage({
        id: 'disabled',
        defaultMessage: 'Выключено'
      })}
</button>

Использование FormattedMessage

Базовый компонент:

<FormattedMessage
  id="welcome"
  defaultMessage="Добро пожаловать"
/>

Подходит для большинства текстовых элементов интерфейса.


Вложенная разметка в сообщениях

FormatJS поддерживает rich text formatting.

Пример

<FormattedMessage
  id="terms"
  defaultMessage="
    Прочитайте <link>условия использования</link>
  "
  values={{
    link: chunks => (
      <a href="/terms">
        {chunks}
      </a>
    )
  }}
/>

Screen reader корректно воспринимает ссылку внутри предложения.


Типичные ошибки accessibility при i18n

Жёстко закодированный язык

aria-label="Close"

Использование флагов вместо названий языков

Флаг не является языком.


Отсутствие уведомления о смене языка

При динамическом переключении интерфейса screen reader может не сообщить пользователю об изменениях.


Стратегия построения доступного i18n-интерфейса

Основные принципы:

  1. Семантический HTML.
  2. Полная локализация ARIA.
  3. Корректный lang.
  4. ICU MessageFormat.
  5. Локализованные даты и числа.
  6. Поддержка screen reader.
  7. Тестирование на разных языках.
  8. Использование live regions.
  9. Минимизация визуально-зависимых элементов.
  10. Предсказуемая структура интерфейса.