🔥

Тред (@xnimorz)


Сейчас будет сложная история с техническими подробностями. История о сложностях работы с переводами на сайте. История сложная по многим причинам:

Переводов больше десятка мегабайт Не все переводы интерфейсные (интерфейсных только пара мегабайт), но тоже немало Переводы хочется менять в рантайме из панели админа В случае с SPA приложением хочется грузить переводы только для конкретной страницы.

Как решать такую проблему?

Мы пришли к такой модели: В реакт-компонентах появляется поле static trls поле, которое является обычной Map<String, String>. В ней value — это ключ перевода.

В контексте храним информацию, какой язык перевода нужен (потому что может быть часть сайта на русском, а резюме — на английском).

При сборке кода babel плагин выдирает поле trls, собирает большую мапу "страница: список переводов". Этот файл подключается как конфиг и по нему для каждой страницы присылаются переводы.

На бекендах переводы подгружаются из базы при старте и затем время от времени получают обновления. В рантайме храним переводы в marisa-trie дереве.

Кстати, плагин, который выдирает статические поля вот: github.com/hhru/babel-plu…

Казалось бы, хорошее решение, легко использовать. Все так, но в сентри начинают приходить баги, что перевода нет.

Добавляем логирование, начинаем трекать, в каких случаях происходит ошибка, что лежит в сторе, какие последние action происходили.

Делаем первый заход, понимаем, что страница например bla-bla, а данные в сторе от страницы foo-bar.

Разбираемся в проблеме, понимаем, что в некоторых редьюсерах данные применяются вот так: (state, action) => bla-bla return [...action.payload];

Quite a tricky thing in spread operator: you can spread undefined in object like {...undefined}, but you can't do it with arrays [...undefined]. It happens, as spread syntax can be applied only to iterable objects except for spread properties (e.g. for objects). pic.twitter.com/ItVvJRZyyj
В чем соль 1: Оператор ... очень хитрый оператор. Я писал об этом здесь: twitter.com/xnimorz/status… И поэтому он может падать. если action.payload будет undefined
notion image

В чем соль 2: мы используем batch actions и делаем батчевый редьюсер. Если падает применение данных в одно из полей в сторе, стор не применяется, остается старое значение.

Переводы хранятся в сторе. И по стечению обстоятельств — переводы первыми вызываются на странице. Вот они и падают.

Окей, почему мы не получали правильную ошибку? Оказалось, потому что случайно поставили на promise, который обрабатывал данные от асинхронного запроса reject ветку без репорта ошибки :facepalm:

Вот так один неверный reject привел к долгой истории и правкам редьюсеров.

Но на этом все не закончилось! Ошибка всплывает опять. Начинаем разбираться, находим проблему в фетчере и асинхронной записи данных (рассказывать подробно не буду, слишком много специфики). Правим, проходит время — ошибка снова всплывает.

Пять подобных итераций и полгода спустя мы из-за этой ошибки отрефакторили кучу мест, каждый раз получалось ее воспроизвести и пофиксить, но ошибка все еще с нами. Мы думаем о том, чтобы дать ей почетное звание помощника в поиске инфраструктурно-архитектурных недостатков.