Работа с RTL-языками

Работа с RTL-языками в контексте дат в JavaScript требует учета не только локализации форматов, но и особенностей направления письма, которое влияет на визуальное представление строки, содержащей дату, время и текст. В экосистеме date-fns локализация реализована через подключаемые локали и функции форматирования, которые опираются на стандарты CLDR, но при использовании арабского, иврита и других RTL-культур появляется дополнительный слой проблем: порядок символов, смешение направлений текста и корректное отображение числовых и словесных компонентов даты.


RTL-языки (Right-to-Left), такие как арабский и иврит, меняют не только направление текста, но и визуальную интерпретацию строк, содержащих числа, знаки пунктуации и латиницу. Дата, представленная как комбинация чисел и слов, часто становится смешанным направлением текста (bidirectional text).

Типичный пример:

  • RTL: “15 مارس 2026”
  • LTR-вставка: “15 March 2026”

При неправильной обработке возможно визуальное «перемешивание» компонентов, например:

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

В JavaScript это особенно заметно при использовании format() без учета направления строки.


Локали RTL в date-fns

В date-fns локализация реализована через отдельные модули:

  • date-fns/locale/ar — арабский
  • date-fns/locale/he — иврит
  • date-fns/locale/fa — персидский (в некоторых сборках)

Каждая локаль содержит:

  • названия месяцев
  • названия дней недели
  • правила форматирования порядковых числительных
  • контекстные формы грамматики

Пример подключения локали:

import { format } from "date-fns";
import { ar } from "date-fns/locale";

format(new Date(2026, 2, 15), "d MMMM yyyy", { locale: ar });

Результат будет соответствовать арабской языковой модели, но не гарантирует корректного визуального RTL-выравнивания в DOM без дополнительных мер.


Визуальное направление текста и Unicode BiDi

Основная проблема RTL — не формат даты, а алгоритм двунаправленного текста Unicode (BiDi algorithm). Он автоматически перераспределяет порядок символов в строке.

Если строка содержит:

  • арабский текст (RTL)
  • латиницу (LTR)
  • цифры (нейтральные)

то итоговый порядок может отличаться от ожидаемого.

Пример проблемной строки:

15 مارس 2026 (Monday)

В некоторых контекстах может отображаться как:

(2026 Monday) 15 مارس

или частично инвертированно.


Использование format и контроль структуры строки

Функция format() в date-fns не управляет направлением текста, она лишь формирует строку:

format(date, "EEEE, d MMMM yyyy", { locale: ar });

Выход:

الأحد، 15 مارس 2026

Но при встраивании в UI важно учитывать контекст:

  • строка может быть внутри RTL-контейнера (dir="rtl")
  • или внутри LTR-интерфейса (например, админ-панель)

HTML-уровень управления направлением

Корректная работа с RTL часто решается не в JavaScript, а на уровне DOM:

<div dir="rtl">
  الأحد، 15 مارس 2026
</div>

или смешанный вариант:

<div dir="rtl">
  <span dir="ltr">15</span> مارس 2026
</div>

Это предотвращает перестановку чисел и символов.


Изоляция LTR-фрагментов внутри RTL

При работе с датами часто требуется изолировать числовые компоненты. Это достигается с помощью Unicode-меток:

  • LRM (Left-to-Right Mark)
  • RLM (Right-to-Left Mark)
  • LRE/PDF (embedding controls)

Пример ручной стабилизации строки:

const LRM = "\u200E";

const formatted = `15${LRM} March 2026`;

Однако при использовании date-fns предпочтительнее не вмешиваться вручную, а структурировать вывод через форматирование и DOM-изоляцию.


formatDistance и RTL-локализация

Функция formatDistance также зависит от локали:

import { formatDistance } from "date-fns";
import { ar } from "date-fns/locale";

formatDistance(new Date(2026, 2, 15), new Date(), {
  locale: ar,
  addSuffix: true
});

Результат будет грамматически адаптирован:

منذ 3 أيام

Особенность RTL здесь заключается в том, что числительное и слово могут менять порядок отображения в зависимости от окружения.


Форматы относительного времени

Функции:

  • formatRelative
  • formatDistanceToNow

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

Пример:

formatRelative(new Date(2026, 2, 15), new Date(), { locale: ar });

Результат:

الأحد الماضي عند الساعة 12:00

В этом случае важно учитывать:

  • слова с предлогами
  • позицию временной части
  • вложенность чисел и текста

Работа с календарными строками и смешанным направлением

При построении интерфейсов календарей возникает комбинация:

  • числа месяца (LTR)
  • названия дней (RTL)
  • служебные символы (нейтральные)

Пример компонента:

import { format } from "date-fns";
import { he } from "date-fns/locale";

const label = format(date, "EEEE, d MMMM", { locale: he });

Результат может быть визуально корректным, но при рендеринге в LTR-странице потребуется:

<div dir="rtl">
  <!-- date string -->
</div>

Проблемы смешанных локалей

Сложность возникает при сценариях:

  • интерфейс LTR (английский)
  • данные RTL (арабский)
  • дата содержит латинские элементы (например, “2026”)

В этом случае возможны:

  • разрыв числовых групп
  • инверсия скобок
  • неправильное позиционирование запятых

Роль формата tokens и локализованных шаблонов

date-fns использует токены форматирования:

  • d — день
  • MMMM — месяц
  • yyyy — год
  • EEEE — день недели

В RTL-контексте важно не менять токены, а менять только локаль:

format(date, "EEEE d MMMM yyyy", { locale: ar });

Стабилизация чисел в RTL

Частая проблема — визуальная инверсия чисел:

2026 → 6202 (визуально в некоторых UI)

Причина — BiDi алгоритм и отсутствие изоляции.

Практики стабилизации:

  • использование dir="ltr" для числовых блоков
  • разделение даты на части
  • отказ от монолитных строк в UI

Работа с кастомными форматами

При кастомизации форматов важно учитывать, что локаль влияет только на текстовые элементы:

format(date, "d MMM yyyy", { locale: ar });

Если требуется строгий контроль структуры:

  • числа можно форматировать отдельно
  • текст локали добавлять через словарь

Влияние шрифтов и рендеринга

Даже при корректной работе date-fns результат зависит от:

  • поддержки Arabic shaping
  • корректной реализации BiDi в браузере
  • шрифтов с RTL-глифами

Некоторые шрифты неправильно выравнивают цифры в арабском тексте, что усиливает визуальные ошибки.


Архитектура правильного вывода дат в RTL-интерфейсах

Стабильный подход включает разделение уровней:

  1. Логический слой (JavaScript)

    • формирование даты через date-fns
    • выбор локали
  2. Презентационный слой (DOM)

    • установка dir
    • изоляция чисел
  3. CSS слой

    • контроль выравнивания текста
    • использование unicode-bidi

Использование unicode-bidi в CSS

.date {
  direction: rtl;
  unicode-bidi: isolate;
}

Это снижает влияние соседнего LTR-контента.


Комбинированные сценарии

В реальных приложениях часто встречается:

  • календарь (RTL)
  • таймлайн событий (LTR числа)
  • уведомления (смешанный текст)

В таких условиях date-fns используется только для генерации локализованных строк, а финальная визуальная стабилизация выполняется на уровне интерфейса.


Особенности сериализации дат

При хранении и передаче дат не допускается использование локализованных строк. Даже в RTL-системах:

  • хранится ISO-формат (2026-03-15)
  • отображается локализованный формат

Иначе возникают ошибки:

  • сортировка
  • фильтрация
  • интернационализация API

Итоговые принципы работы в RTL-контексте

Работа с датами в RTL-средах требует разделения ответственности:

  • date-fns отвечает за локализацию
  • браузер отвечает за направление текста
  • разработчик интерфейса отвечает за изоляцию и структуру

Нарушение любого уровня приводит к неконтролируемому BiDi-эффекту, который невозможно исправить только средствами форматирования даты.