JS: Архитектура фронтенда
Теория: Организация текстов интерфейса
Интерфейс любого сайта включает в себя не только визуальные компоненты, но и текст. Это могут быть названия кнопок, пунктов меню, сообщения об ошибках в форме, различные тексты, разбросанные по всему сайту.
Важно, что они не хранятся в базе данных, а зашиты прямо в код в тех местах, где они используются.
Эти тексты со временем начинают причинять боль. Они расползаются по всем слоям приложения и засоряют его. Очень быстро появляется дублирование одних и тех же фраз. Становится сложно отслеживать их согласованность и адекватность. В конце концов, программисты становятся единственными людьми в компании, которые могут их поменять, потому что никто кроме них не понимает, как найти эти тексты.
При правильном подходе, подобные тексты хранятся в одном месте отдельно от кода. Такой способ значительно упрощает приложение:
- Текстами проще управлять, выполнять массовое обновление, отслеживать то, что устарело.
- Это могут делать не только программисты. Более того, тексты можно выгружать во внешние системы, которые дают возможность работать с ними множеству людей (об этом ниже).
- Упрощается интернационализация и локализация.
Так как организовать хранение текстов? Ответ для многих программистов может показаться неожиданным. Даже если ваш сайт не собирается быть мультиязычным, для работы с текстами все равно используют i18n-библиотеки. i18n расшифровывается как интернационализация (internationalization). Этим термином в программировании обозначают все, что связано с переводами. Как правило, речь идет про специальные библиотеки, которые позволяют переводить интерфейсы, оставляя код приложения простым.
Самое интересное, что эти библиотеки дают все те возможности, которые были перечислены ранее, и не требуют от программистов обязательно добавлять несколько языков. Считайте, что мультиязычность — это приятное дополнение, которое можно задействовать, если вдруг понадобится. Кроме базовых задач, эти библиотеки решают еще множество сопутствующих. Ниже мы их рассмотрим.
У Хекслета на GitHub открыто большое количество проектов на разных языках. Во всех этих проектах есть тексты, и все они подставляются в код через i18n-библиотеки. Большая часть этих библиотек интегрирована с фреймворками и поставляется из коробки. Вот несколько примеров:
В каждом из этих проектов свои способы организации переводов, это видно по разным форматам файлов. Одно остается неизменным: строки не разбросаны по коду. Они все собраны в одном месте и подставляются в нужных местах через i18n-библиотеки.
В мире JS наиболее популярной библиотекой для работы с текстами стала i18next. Это не просто библиотека, а целый фреймворк, имеющий интеграции со всеми популярными решениями, такими как Angular, React или Vue.js. Пример использования:
Единственное место, где появляется понятие "язык" — это инициализация. Нужно указать текущий язык (lng) и добавить тексты для этого языка. На этом все: дальше мы только управляем текстами. Если появляется новый текст, то для него придумывается ключ и добавляется в объект translation. Затем этот текст извлекается по указанному ключу. Из кода выше видно, что этот текст очень легко переиспользовать. Достаточно обратиться к этому же ключу в другом месте программы.
Когда текста становится больше, то его можно вынести в отдельный файл. В таком случае инициализация меняется на такую:
В принципе, выносить тексты лучше сразу. Их никогда не бывает мало.
i18next поддерживает такое понятие как "бэкенды". Она позволяет загружать тексты из внешних источников, например, через AJAX-запрос (именно поэтому инициализация библиотеки — асинхронная). Подробнее в официальной документации.
Со временем вы заметите, что плоская структура key-value не всегда удобна. Иногда захочется делать вложенность, группировать ключи. К счастью, с этим нет никаких проблем. I18next поддерживает такую возможность из коробки.
В некоторых ситуациях тексты зависят от различных динамических параметров, например, от имени пользователя. В таком случае используется встроенная интерполяция:
В более сложных ситуациях одной интерполяции недостаточно. Представьте себе, что нам надо выводить количество баллов, как на Хекслете. Слово "балл" будет меняться в зависимости от числа баллов: 1 балл, 2 балла, 10 баллов. Как это сделать? С помощью плюрализации!
Связь текстов с состоянием приложения
Типичная ошибка при работе с текстами — хранить их прямо в состоянии:
У такого подхода есть один очень серьезный недостаток. Он не сочетается с переключением языков. Представьте, что пользователь поменял язык интерфейса, а в состоянии в это время записаны тексты. Появляется проблема — как изменить тексты на правильный язык? В общем случае никак, потому что в строке текста нет информации, что это было. То есть невозможно сопоставить этот текст с ключом и найти соответствующий перевод в другом месте. Кроме того, сама задача очень непростая, текстов может быть много, они разбросаны по разным частям состояния. Придется писать специальную логику под каждую конкретную ситуацию (каждый конкретный кусок состояния).
Любые тексты, которые выводятся в зависимости от действий пользователя, не должны храниться в состоянии приложения. Эти тексты должны зависеть от состояния процессов:
Только в некоторых ситуациях, где нужно явно знать, какие тексты использовать — можно хранить ключи, например, для перевода ошибок.
В любом случае, готовые строки формируются только при выводе.
Инициализация
Вернемся к примеру из начала урока:
При инициализации глобальный объект i18next мутируется, и поэтому функция i18next.t может импортироваться напрямую из библиотеки. Это удобно с точки зрения использования, но добавляет проблем при необходимости многократной инициализации. Когда необходимо инициализировать приложение несколько раз? Например, в тестах, где на каждый тест происходит новый запуск приложения "с нуля", или при серверном рендеринге, когда для каждого пользователя на сервере создается свой экземпляр приложения. Для таких случаев библиотека содержит функцию createInstance, которая создает, как нетрудно догадаться, новый экземпляр i18next:
Такой подход с глобальным состоянием не уникален, например, библиотеку axios можно конфигурировать как глобально, так и создавать инстанс. В общем случае мутируемое глобальное состояние — зло и источник багов.




