В пятницу буду на Я люблю фронтенд (мы собрали осколки того то, что от нас осталось, склеили кое-как и дотащили конфу до продакшена) рассуждать о том, какой стек для бэкенда на node.js я бы выбрал в 2022. Будет онлайн, всё как всегда бесплатно, знания бесценны.
Ах да, наконец-то скажем, кто и как победил в ctf.
Ах да, наконец-то скажем, кто и как победил в ctf.
Я ❤ Фронтенд
В четвёртый раз соберём фронтенд-сообщество, чтобы обсудить новости веба, поделиться опытом и провести время в отличной компании. Будут доклады про Node.js, производительность, доступность и многое другое, а также подведём итоги CTF.
❤24👍6
Одна из самых больших проблем в запоминании принципов SOLID (если вы зачем-то хотите их запомнить) — это буква L, (LSP, Liskov substitution principle, или принцип подстановки Барбары Лисков). Каноническое определение его скучно до зевоты: Пусть q(x) является свойством, верным относительно объектов x некоторого типа T. Тогда q(y) также должно быть верным для объектов y типа S, где S является подтипом типа T.
Если говорить с точки зрения программирования, то это значит, что отнаследованные классы должы без ошибок вставать на место родителя в коде.
Наиболее простой способ понять этот принцип — это проблема квадрата и прямоугольника. Квадрат нельзя наследовать от прямоугольника, потому, что после этого мы теряем свойство родителя (независимое управление длинами сторон X и Y). У квадрата стороны равны, у прямоугольника нет. Сторонами прямоугольника можно управлять раздельно, и квадратом не заткнуть любую дырку, которую может занять прямоугольник.
Чтобы подружить квадрат и прямоугольник, мы должны исключить наличие в родительском классе методов, противоречащих методам потомка. Но тогда зачем нам вообще нужен такой убогий класс — родитель? И вот тут начинают смотреть в сторону композиции вместо наследования, чтобы не натягивать сову на глобус.
Итого, если вы отнаследовались от какого-то класса и переписали его методы так, что ваш новый класс ведёт себя иначе и не может заменить родителя в коде — вы нарушили LSP.
https://news.1rj.ru/str/xufostation/772
Если говорить с точки зрения программирования, то это значит, что отнаследованные классы должы без ошибок вставать на место родителя в коде.
Наиболее простой способ понять этот принцип — это проблема квадрата и прямоугольника. Квадрат нельзя наследовать от прямоугольника, потому, что после этого мы теряем свойство родителя (независимое управление длинами сторон X и Y). У квадрата стороны равны, у прямоугольника нет. Сторонами прямоугольника можно управлять раздельно, и квадратом не заткнуть любую дырку, которую может занять прямоугольник.
Чтобы подружить квадрат и прямоугольник, мы должны исключить наличие в родительском классе методов, противоречащих методам потомка. Но тогда зачем нам вообще нужен такой убогий класс — родитель? И вот тут начинают смотреть в сторону композиции вместо наследования, чтобы не натягивать сову на глобус.
Итого, если вы отнаследовались от какого-то класса и переписали его методы так, что ваш новый класс ведёт себя иначе и не может заменить родителя в коде — вы нарушили LSP.
https://news.1rj.ru/str/xufostation/772
Telegram
UfoStation
Сделать код понятным: как Барбара Лисков повлияла на современное программирование
Барбара Лисков родилась в то время, когда лучшим выбором для женщины было удачное замужество, но выбрала IT. Она стала одним из пионеров развития современных языков и принципов…
Барбара Лисков родилась в то время, когда лучшим выбором для женщины было удачное замужество, но выбрала IT. Она стала одним из пионеров развития современных языков и принципов…
🔥17👍6👎1
Цитата с Хабра
«Вообще, при разработке, наследование можно рассматривать с двух позиций: подклассы — это подтипы, со всеми ограничениями контрактного программирования и принципа Лисков, и подклассы — это способ переиспользовать код, со всеми своими потенциальным проблемами. Т.е. можно либо думать и проектировать ответственности классов и контракты и не переживать про клиентский код. Либо думать про то, какой может быть клиентский код, как классы будут использоваться, и быть готовым к потенциальным проблемам, но в меньшей степени заботится о соблюдении принципа подстановки. Решение как обычно за разработчиком, самое главное, чтобы выбор в конкретной ситуации был осознанный и было понимание какие плюсы, минусы и подводные камни сопровождают то или иное решение.»
«Вообще, при разработке, наследование можно рассматривать с двух позиций: подклассы — это подтипы, со всеми ограничениями контрактного программирования и принципа Лисков, и подклассы — это способ переиспользовать код, со всеми своими потенциальным проблемами. Т.е. можно либо думать и проектировать ответственности классов и контракты и не переживать про клиентский код. Либо думать про то, какой может быть клиентский код, как классы будут использоваться, и быть готовым к потенциальным проблемам, но в меньшей степени заботится о соблюдении принципа подстановки. Решение как обычно за разработчиком, самое главное, чтобы выбор в конкретной ситуации был осознанный и было понимание какие плюсы, минусы и подводные камни сопровождают то или иное решение.»
👍6
После сегодняшнего обсуждения лямбда-боли:
——
думаю, что мы придём к тому, что будет одна лямбда для всего http и отдельным опциональным лямбдам там, где нужен s2s
——
Предложил сразу встроить фастифай и позже отправить это в кубер :troll_mode_activated:
——
думаю, что мы придём к тому, что будет одна лямбда для всего http и отдельным опциональным лямбдам там, где нужен s2s
——
Предложил сразу встроить фастифай и позже отправить это в кубер :troll_mode_activated:
😁3
Кстати, если среди читающих есть опытные пользователи лямбд — поделитесь в комментах: пакуете ли вы все HTTP-лямбды в одно приложение с внутренним роутингом, слушающее корневой роут в API Gateway, или разделяете на множество мелких «контроллеров», обслуживающих отдельные роуты?
Первый раз за несколько месяцев написал
let. Но потом одумался и вынес код в функцию.
🔥24😁13👍3❤1👏1
После того как команда Платформы разделила монорепу фронтенда (неудобно же работать в монорепе!) на полирепу с микрофронтами, каждая бизнесовая команда тихонько начала пилить свои маленькие монорепы (ну удобно же работать в монорепе!).
😁24🔥9🤯3👏1
Forwarded from запуск завтра
«Простота — это великая добродетель, но она требует напряженной работы, чтобы её достичь и образование, чтобы оценить. И к сожалению, сложность продается легче.» — Дейкстра.
—
Простые решения дешевле в реализации и поддержке, в них меньше вероятности допустить ошибку, их легче передать другому и так далее и так далее.
Кажется, что раз решение сложное — то в него вложено больше труда и значит оно должно быть лучше.
Это, конечно, не так. Процитирую Паскаля: «если бы у меня было больше времени, я бы написал письмо покороче».
В программировании ровно то же самое: простые решения требуют гораздо больше интеллектуальных усилий, приходят с опытом.
Да, есть задачи, которые сами по себе сложные, с огромным числом ограничений и тд., например, биллинги обычно такие. При этом, почти все биллинги, которые я видел, были переусложнены сверх необходимого. 🙈
—
Отличная статья об этом феномене на примере научных статьей в области машинного обучения. Спасибо Игорю за наводку.
—
Простые решения дешевле в реализации и поддержке, в них меньше вероятности допустить ошибку, их легче передать другому и так далее и так далее.
Кажется, что раз решение сложное — то в него вложено больше труда и значит оно должно быть лучше.
Это, конечно, не так. Процитирую Паскаля: «если бы у меня было больше времени, я бы написал письмо покороче».
В программировании ровно то же самое: простые решения требуют гораздо больше интеллектуальных усилий, приходят с опытом.
Да, есть задачи, которые сами по себе сложные, с огромным числом ограничений и тд., например, биллинги обычно такие. При этом, почти все биллинги, которые я видел, были переусложнены сверх необходимого. 🙈
—
Отличная статья об этом феномене на примере научных статьей в области машинного обучения. Спасибо Игорю за наводку.
👍13❤10
Вот Виктор Хомяков на Субботнике подтверждает мнение, что JS не лучший выбор для FP с точки зрения перфоманса и лимитов памяти (ох уж эти цепочки map().filter().reduce() и прочие спреды). Да, Ramda может немного упростить жизнь
Мутируйте, господа, если работает с большими обьёмами. Мутировать не стыдно, мутировать — это нормально, когда мы втыкаемся в проблемы с производительностью.
Или, хотя бы, дробите на чанки.
но фундаментальные противоречия отсутствия оптимизации хвостовой рекурсии (кроме Сафари) и иммутабельных структур на уровне движка непреодолимы.Мутируйте, господа, если работает с большими обьёмами. Мутировать не стыдно, мутировать — это нормально, когда мы втыкаемся в проблемы с производительностью.
Или, хотя бы, дробите на чанки.
👍12❤2
Вот правда, дизайн Server Push ставил в тупик с первого дня, однако дотащили же до прода. Зачем, кто это форсил — непонятно.
https://news.1rj.ru/str/daily_geek_news/197
https://news.1rj.ru/str/daily_geek_news/197
Telegram
Daily Geek News
Начиная с хрома версии 106 в браузере будет выключен HTTP/2 server push, который продавали как важную причину для перехода на новую версию HTTP. При этом, конечно, ни браузеры ни серверы толком не поддерживали нормально эту идею, мы с Умпутуном ещё тогда…
По поводу того, как же мы будем жить без
Что тут важно: если вы можете ответить быстро, то ничего городить не нужно. Присылайте
Upd
Когда написал пост, внезапно осознал ещё один важный кейс: edge-прокси. Собственно, для чего Cloudflare одними из первых и внедрили экспериментальную поддержку. Работает это так, что на проксирующем сервере, расположенном близко к клиенту, доклеивается заголовок
Server Push (словно кто-то с ним жил, ха). Начиная с Chrome 103 (и только Chrome) на всех пользователей раскатали поддержку стандарта 103 Early Hints который заключается в такой простой идее: если ваш сервер очень тугодумный и не может мгновенно выплюнуть на запрос ответ 200 с HTML, то вы можете использовать время ожидания с пользой, ответить сначала статусом 103, в тело положить Link со ссылками на важные ресурсы, браузер побежит их качать, а тут и 200 долетит с основным телом ответа.
HTTP/1.1 103 Early Hints
Link: </style.css>; rel="preload"; as="style"
Link: <https://cdn.test>; rel="preconnect"
HTTP/1.1 200 OK
Link: </style.css>; rel="preload"; as="style"
Link: <https://cdn.test>; rel="preconnect"
Content-Type: text/html; charset=utf-8
<!DOCTYPE html>
...
Что тут важно: если вы можете ответить быстро, то ничего городить не нужно. Присылайте
200 и на важные ресурсы пропишите link rel=preload и link rel=preconnect. Браузер сам решит, как дальше оптимизировать загрузку. Т.е. Early Hints это такой костылик для случаев когда у вас удивительно медленный серверный рендер и за время тугодумия сервера браузер может что-то полезное скачать.Upd
Когда написал пост, внезапно осознал ещё один важный кейс: edge-прокси. Собственно, для чего Cloudflare одними из первых и внедрили экспериментальную поддержку. Работает это так, что на проксирующем сервере, расположенном близко к клиенту, доклеивается заголовок
103, выплёвывается ответ, а дальше запрос бежит на ваш далеко расположенный сервер.
👍12❤1
Где бы монорепа точно пригодилась
В webpack module federation есть такая штука как shared dependencies. Мы указываем в головном приложении, что у нас есть один экземпляр
Вот как-то так
Теперь каждый подключаемый модуль не будет тащить свою версию реакта, а использовать общую. Красиво, но есть проблема обновлений зависимостей.
Во-первых, само по себе обновление может стать болезненным, если в мажоре библиотеки сломают API (привет react-router) так, что старый код не сможет работать с новым API. Т.е. ставим условный react 19 и наш подключаемый модуль ломается, нам нужно переписать и релизнуть новые версии микрофронтов максимально быстро вслед за хостом (что для многих проектов просто недопустимо).
Во-вторых, весьма болезненно ходить по полирепе, останавливать релизы, вносить правки в зависимости и код каждого отдельного микрофронта, а после массово релизить.
В монорепозитории очевидно можно протащить это за один PR.
В webpack module federation есть такая штука как shared dependencies. Мы указываем в головном приложении, что у нас есть один экземпляр
react, один
react-dom, один
react-router-domи что эти синглтоны шарятся между всеми модулями.
Вот как-то так
new ModuleFederationPlugin({
name: 'host',
shared: {
react: { singleton: true, requiredVersion: dependencies.react, eager: true },
'react-dom': { singleton: true, requiredVersion: dependencies['react-dom'], eager: true },
'react-router-dom': {
singleton: true,
requiredVersion: dependencies['react-router-dom'],
eager: true,
}
},
Теперь каждый подключаемый модуль не будет тащить свою версию реакта, а использовать общую. Красиво, но есть проблема обновлений зависимостей.
Во-первых, само по себе обновление может стать болезненным, если в мажоре библиотеки сломают API (привет react-router) так, что старый код не сможет работать с новым API. Т.е. ставим условный react 19 и наш подключаемый модуль ломается, нам нужно переписать и релизнуть новые версии микрофронтов максимально быстро вслед за хостом (что для многих проектов просто недопустимо).
Во-вторых, весьма болезненно ходить по полирепе, останавливать релизы, вносить правки в зависимости и код каждого отдельного микрофронта, а после массово релизить.
В монорепозитории очевидно можно протащить это за один PR.
🤔3👍2
Где монорепа в целом бесполезна
Иногда возникает желание сложить микрофронт и BFF для него в одну монорепу фронт-бек. Кажется, что решение убийственно хорошо: разработчик скачивавает один репозиторий и одним скриптом стартует фронт и бэк. Но главной киллер-фичей кажется тут то, что можно переиспользовать тайпскриптовые типы как контракты. Поменял типы ответа на бэке — фронт тут же загорелся красным. Идеально!
Но мы забыли про деплой. Да, релизится такая монорепа одним скриптом, который катит бэк на сервера бэкенда, а фронт на фронтовые сервера (Next, или просто статика в S3, не важно). И вот тут в момент деплоя возникает рассинхрон. То, что там казалось монолитной сущностью во время разработки, оказывается не монолитом в проде. Пользователь заходит на обновлённый фронт, а бэк ещё не готов и отвечает ошибкой. Или наоборот, старый фронт стучится в новый бэк.
Что тут можно сделать? Ну, например, закрывать серверы парами и делать стики-сессии. Но если на канареечном сервере это приемлимо, то в бою стики это странно. Мы хотим распределять нагрузку, раскидывая запросы на разные бэки, а не делать вечные пары фронт-бек. Другая проблема, что пользователь может не обновить фронт в браузере, а бэк уже обновился. Тут и стики не помогут.
Вывод — монорепа здесь даёт ложную надежду на синхронность фронта и бэка. А вот лежал бы код в разных репозиториях, мы бы вынуждены были вынести контракты в третий репозиторий и шарить их уже по настоящему. А тут бы и версионирование API подъехало.
Итого, с моей точки зрения, для разнородных сущностей монорепа малополезна. Можно сложить в монорепу бэкенды. Можно отдельно сложить в другую монорепу фронтенды. В отдельный репозиторий положить контракты. В четвёртый и пятый репозитории лягут iOS и Android-фронты. Для UI-компонентов тоже можно завести свою монорепу. Сложить туда и компоненты и атомы дизайн-системы и что ещё пригодится. Но упаси вас господь паблишить компоненты UI-кита как отдельные пакеты, это дорога в ад.
Иногда возникает желание сложить микрофронт и BFF для него в одну монорепу фронт-бек. Кажется, что решение убийственно хорошо: разработчик скачивавает один репозиторий и одним скриптом стартует фронт и бэк. Но главной киллер-фичей кажется тут то, что можно переиспользовать тайпскриптовые типы как контракты. Поменял типы ответа на бэке — фронт тут же загорелся красным. Идеально!
Но мы забыли про деплой. Да, релизится такая монорепа одним скриптом, который катит бэк на сервера бэкенда, а фронт на фронтовые сервера (Next, или просто статика в S3, не важно). И вот тут в момент деплоя возникает рассинхрон. То, что там казалось монолитной сущностью во время разработки, оказывается не монолитом в проде. Пользователь заходит на обновлённый фронт, а бэк ещё не готов и отвечает ошибкой. Или наоборот, старый фронт стучится в новый бэк.
Что тут можно сделать? Ну, например, закрывать серверы парами и делать стики-сессии. Но если на канареечном сервере это приемлимо, то в бою стики это странно. Мы хотим распределять нагрузку, раскидывая запросы на разные бэки, а не делать вечные пары фронт-бек. Другая проблема, что пользователь может не обновить фронт в браузере, а бэк уже обновился. Тут и стики не помогут.
Вывод — монорепа здесь даёт ложную надежду на синхронность фронта и бэка. А вот лежал бы код в разных репозиториях, мы бы вынуждены были вынести контракты в третий репозиторий и шарить их уже по настоящему. А тут бы и версионирование API подъехало.
Итого, с моей точки зрения, для разнородных сущностей монорепа малополезна. Можно сложить в монорепу бэкенды. Можно отдельно сложить в другую монорепу фронтенды. В отдельный репозиторий положить контракты. В четвёртый и пятый репозитории лягут iOS и Android-фронты. Для UI-компонентов тоже можно завести свою монорепу. Сложить туда и компоненты и атомы дизайн-системы и что ещё пригодится. Но упаси вас господь паблишить компоненты UI-кита как отдельные пакеты, это дорога в ад.
❤7👍4🤔1
В предыдущем посте затронул тему ui-kit. Не раз встречал желение прикрутить к киту условную Лерну и паблишить компоненты как отдельные сущности. Мотивация понятная — мало кто хочет поднимать UI-kit во всём приложении, когда дизайнер принёс страничку с новым элементом. Гораздо проще было бы поставить непосредственно этот элемент (или обновить существующий). Однако, в проектах, где такой подход принят, я вижу сильную неоднородность фронта: на одной странице можно встретить 3-4 разные итерации развития кнопки. Что-то доехало из корня, что-то транзитивно из более сложных сущностей. В итоге, пользователь недоумевает почему дизайн такой лоскутный (тут кнопка круглая, а тут квадратная, тут галочки синие, а тут зелёные, а тут вообще градиент с тенями вылез), а бандл раздут излишними копиями одной и той же сущности разных мажорных и минорных её версий.
Да, обновлять kit во всём приложении сразу сложнее, но делать это необходимо и делать это нужно регулярно, чтобы не столкнуться с ситуацией, когда ради нового аттрибута кнопки нужно прыгнуть через три мажора. А такое я тоже встречал.
Да, обновлять kit во всём приложении сразу сложнее, но делать это необходимо и делать это нужно регулярно, чтобы не столкнуться с ситуацией, когда ради нового аттрибута кнопки нужно прыгнуть через три мажора. А такое я тоже встречал.
🔥7👍4
В воскресенье что-то будет
https://news.1rj.ru/str/backend_megdu_skobkah/7823
https://news.1rj.ru/str/backend_megdu_skobkah/7823
Telegram
{ между скобок } анонсы 📣 in { между скобок }
Всем привет 👋 Хочется напомнить, что сегодня в 19:00 по мск встречаемся в zoom на публичном собеседование по LeetCode. Так же хочется рассказать, что у нас будет в воскресенье.
🗓 28 августа, вс, 20:00 (мск) “CHAPTER 9: Design a web crawler” System Design…
🗓 28 августа, вс, 20:00 (мск) “CHAPTER 9: Design a web crawler” System Design…
🔥5❤3
Неделю назад жаловался на отсутствие иммутабельных структур в JS, а тут вдруг всплыл интересный пропозал по добавленю в JS иммутабельных Record и Tuple (кортежей).
Если кратко, то предлагается добавить в язык два новых «сложных» иммутабельных примитива, собственно
Описываются они так
Мутировать их нельзя, внутри могут лежать только примитивы, никаких ссылок на объекты и функции (но над этим работают, возможно ссылки разрешат). Так как это примитивы, то сравниваются они по значению, а не по ссылкам
Ещё раз — это примитивы
Так как кортежи выглядят как массивы, то очень хотелось бы к ним применять методы из Array.prototype, но проблема в том, что многие методы там мутирующие. Так что вместе с этим пропозалом едет второй пропозал с добавлением иммутабельных методов в Array и TypedArray
И он уже на Stage 3 и приедет к нам прежду всего с новым Safari
Ну и вдогонку доклад https://portal.gitnation.org/contents/record-and-tuple-immutable-data-structures-in-js
Если кратко, то предлагается добавить в язык два новых «сложных» иммутабельных примитива, собственно
Record и Tuple. Описываются они так
const record = #{ a: 1, b: 2 }
const tuple = #[1, 2]Мутировать их нельзя, внутри могут лежать только примитивы, никаких ссылок на объекты и функции (но над этим работают, возможно ссылки разрешат). Так как это примитивы, то сравниваются они по значению, а не по ссылкам
const record1 = #{ a: 1, b: 2 }
const record2 = #{ a: 1, b: 2 }
const record3 = #{ ...record1 }
const record4 = #{ b: 2, a: 1 }
record1 === record2 // true
record1 === record3 // true
record1 === record4 // true ключи сортируются лексическиЕщё раз — это примитивы
typeof #{ a: 1 } === "record"
typeof #[1, 2,3] === "tuple"Так как кортежи выглядят как массивы, то очень хотелось бы к ним применять методы из Array.prototype, но проблема в том, что многие методы там мутирующие. Так что вместе с этим пропозалом едет второй пропозал с добавлением иммутабельных методов в Array и TypedArray
• Array.prototype.toReversed() -> Array
• Array.prototype.toSorted(compareFn) -> Array
• Array.prototype.toSpliced(start, deleteCount, ...items) -> Array
• Array.prototype.with(index, value) -> ArrayИ он уже на Stage 3 и приедет к нам прежду всего с новым Safari
Ну и вдогонку доклад https://portal.gitnation.org/contents/record-and-tuple-immutable-data-structures-in-js
GitHub
GitHub - tc39/proposal-record-tuple: ECMAScript proposal for the Record and Tuple value types. | Stage 2: it will change!
ECMAScript proposal for the Record and Tuple value types. | Stage 2: it will change! - tc39/proposal-record-tuple
❤24🔥9🥰4👍3
Монументальный труд Виктора Хомякова про работу с памятью в JS. Утечки, оптимизации, отличия ноды и браузеров.
https://habr.com/ru/company/yandex/blog/666870/
https://habr.com/ru/company/yandex/blog/666870/
Хабр
Память в браузерах и в Node.js: ограничения, утечки и нестандартные оптимизации
Интро: почему я написал эту статью Меня зовут Виктор, я разрабатываю страницу результатов поиска Яндекса. Несмотря на внешнюю простоту, поисковая выдача — сложная штука: на каждый запрос генерируется...
👍24🔥8
Forwarded from artalog (artalar)
Давно жду этот пропосал и одно из самых интересных нововведений там это доп синтаксис для иммутабельного изменения глубоколежащего свойства.
👍10❤1
Как устроена локализация в крупных проектах
В маленьких проектах всё просто — пишем все тексты в интерфейсах на одном языке и в ус не дуем. Если же проект вырастает и начинает экспансию, то вместе с ней приходит боль. Все тексты нужно заменять на ключи, выносить в файлики и базы данных (такой кортеж из ключа и набора переводов на разные языки) и заменять ключ на перевод по выбору пользователя.
Если говорить о фронтенде, то тут сразу отпадает вариант с динамической подстановкой текстов из загруженной в бандл бд — наборы переводов для всех языков просто раздуют бандл. Это значит, что нам нужно подготовить переведённые бандлы в момент сборки, чтобы пользователь грузил только те тексты, которые нужны в его выбранной локали. А есть же ещё RTL-языки, ох.
Это всё решаемо и это только полбеды.
Вторая половина беды состоит в том, что процесс перевода человеком — это синхронный процесс. В крупном проекте не можете выкатить релиз не переведя его на хотя бы базовый набор языков. Особенно, если ваш основной язык не международный. Фоллбечить немецкий в русский это просто смешно 🙂 Значит, вам нужно чтобы к релизу были готовы два языка — локальный + английский. Очевидно, что за переводы не должен отвечать разработчик или дизайнер, для этого есть ребята, прокачанные в мультиязычном написании текстов.
Для работы с переводчиками используют так называемые translation management systems которые в чём-то похожи на content managment systems. Это специальный UI, где переводчики видят какие свежие ключи приехали, как они лежат в контексте (неплохо, когда вместе с ключами едет подсказка их применения), переводят и аппрувят их.
Процесс выглядит примерно так
1. Разработчик расставляет в коде интерфейса ключи, а не тексты. Рядом заводит файлик, в котором описывает связки ключ -> { дефолтный текст, множестенные формы, подсказки переводчику }
2. В момент билда все ключи загружаются в TMS, находятся изменения и переводчик получает задачу на переводы
3. Билд стоит в ожидании перевода на базовый минимум языков
4. Переводчик в интерфейсе TMS делает свою работу и жмёт аппрув
5. По готовности переводов CI выкачивает их и собирает локализованные бандлы, билд едет на прод. Что не перевели — фоллбечится в английский (скорее всего). Остатки доедут в следующий релиз.
Тут конечно большой минус, что бутылочным горлышком становится переводчик. Потому дальше можно придумать различные варианты ускорения работы, например, что переводчик получает задачу уже в момент тестирования ветки QA и при релизе ветка переводов мерджится в main. Т.е. одна задача не должна блочить своими переводами весь релизный процесс, только себя.
Для примера TMS систем рекомендую посмотреть на Weblate
В маленьких проектах всё просто — пишем все тексты в интерфейсах на одном языке и в ус не дуем. Если же проект вырастает и начинает экспансию, то вместе с ней приходит боль. Все тексты нужно заменять на ключи, выносить в файлики и базы данных (такой кортеж из ключа и набора переводов на разные языки) и заменять ключ на перевод по выбору пользователя.
Если говорить о фронтенде, то тут сразу отпадает вариант с динамической подстановкой текстов из загруженной в бандл бд — наборы переводов для всех языков просто раздуют бандл. Это значит, что нам нужно подготовить переведённые бандлы в момент сборки, чтобы пользователь грузил только те тексты, которые нужны в его выбранной локали. А есть же ещё RTL-языки, ох.
Это всё решаемо и это только полбеды.
Вторая половина беды состоит в том, что процесс перевода человеком — это синхронный процесс. В крупном проекте не можете выкатить релиз не переведя его на хотя бы базовый набор языков. Особенно, если ваш основной язык не международный. Фоллбечить немецкий в русский это просто смешно 🙂 Значит, вам нужно чтобы к релизу были готовы два языка — локальный + английский. Очевидно, что за переводы не должен отвечать разработчик или дизайнер, для этого есть ребята, прокачанные в мультиязычном написании текстов.
Для работы с переводчиками используют так называемые translation management systems которые в чём-то похожи на content managment systems. Это специальный UI, где переводчики видят какие свежие ключи приехали, как они лежат в контексте (неплохо, когда вместе с ключами едет подсказка их применения), переводят и аппрувят их.
Процесс выглядит примерно так
1. Разработчик расставляет в коде интерфейса ключи, а не тексты. Рядом заводит файлик, в котором описывает связки ключ -> { дефолтный текст, множестенные формы, подсказки переводчику }
2. В момент билда все ключи загружаются в TMS, находятся изменения и переводчик получает задачу на переводы
3. Билд стоит в ожидании перевода на базовый минимум языков
4. Переводчик в интерфейсе TMS делает свою работу и жмёт аппрув
5. По готовности переводов CI выкачивает их и собирает локализованные бандлы, билд едет на прод. Что не перевели — фоллбечится в английский (скорее всего). Остатки доедут в следующий релиз.
Тут конечно большой минус, что бутылочным горлышком становится переводчик. Потому дальше можно придумать различные варианты ускорения работы, например, что переводчик получает задачу уже в момент тестирования ветки QA и при релизе ветка переводов мерджится в main. Т.е. одна задача не должна блочить своими переводами весь релизный процесс, только себя.
Для примера TMS систем рекомендую посмотреть на Weblate
👍17🔥7