Вирішив написати в блог про те, як Kasta отримала українську мову. Враз і написання нею потренувати, а то я і книжки українською давно вже не читав, і не розмовляю майже, сумно. :( Пост буде складатися з двох частин — частина з історією і частина з технологіями.
Частина історична
Вперше ми заговорили, що було б непогано якось зробити переклад одразу як я прийшов працювати у тоді ще modnaKasta, у 2015 році. Напевне і до мене обговорювали, підтверджень не маю. :) Але в той момент навіть думати не було часу, бо проблемою було зібрати все до купи. Щось по ділу ми зробили на початку 16-го року: зібралися у Львові з продактом і розробниками PP (це в нас окремий сайт/сервіс для підготовки контенту) обговорювати роадмап. І тоді мені здавалося, що якось потроху, на бекграунді, можна буде перекласти сайт, а тому треба підготувати PP — щоб він міг робити багатомовний контент.
PP, на щастя, зробили свою частину роботи, і переклали всі характеристики, а от сайт — ні. :) Так воно й зависло, РР має переклади, але сайт оперує їх російськими назвами і все. Це, насправді, завжди було найбільшою перепоною — сайт для показу (і фільтрації!) використовував не id’шники характеристик, а їх значення. Вони навіть стирчали з API своїми неприємними urlencode’нутими писками. :) І всі клієнти (веб та аппи) навіть не вміли показувати людям не те саме, що відправляли в API для фільтраціїї товарів. Дуже неприємно і дуже складно змінити — треба переробити зберігання в ElasticSearch, API, інтерфейси всіх клієнтів. Така собі задача для бекграунду, виглядає не дуже реально. :)
Так ми й жили ще десь з рік, аж допоки мені не прийшло до голови, що сайт не тільки не вміє використовувати дані з PP, він їх ще й не має! :) (кидайте у мене каміння, хто без гріха, ггг) Все тому, що російський варіант цих значень приходив разом з товаром, іншого механізму передачі характеристик не було. Тому, якщо якесь значення змінили (виправлення помилки, наприклад) — то треба переопублікувати всі товари з цим значенням, щоб оновити все на сайті. Ми домовились про формат передачі даних та зробили так, щоб ці дані на кожну зміну відправлялись на сайт (та зберігалися там, ха-ха).
Ну добре, в нас вже є структуровані дані, товари, ми вже зберігаємо не тільки російські назви характеристик, а й їхні айдішки. Тож на кожному таунхолі компанії я, коли презентував потенційні великі зміни на сайті на наступний квартал, додавав слайд “Українська?” :) Зі знаком питання… Є така система оцінки, на дві шкали — важливо/не важливо та терміново/не терміново. І українська мова це дуже важливо, але, об’єктивно кажучи, зовсім не терміново. Вона не добавить різко продажів, вона не виправить якісь недоліки нашого сервісу, і так далі. Це як гігієна. Важливо, але ти на неї витрачаєш тим більше уваги, чим менше хижаків тебе намагається з’їсти. :)
Електроніка
А взимку цього року в наш дім прийшла електроніка. Зі всім своїм різноманіттям характеристик. Традиційні категорії Касти вибирають по бренду та зовнішньому вигляду — одяг, взуття, косметику, дрібничкі, домашній стафф. Електроніку ж приходять вибирати по характеристиках. Тому проект по додаванню в продаж на Касті електроніки викликав просто-таки вибух кількості характеристик.
А в нас додавання нової характеристики, як наслідок частини, яку не дуже добре спроектували (насправді в той час нормально, на базі того, що було, зробили досить непогано — ми з цим пережили 3 роки, але часи змінюються :)) — це був окремий біль. Прийшов час переробити те, як ми зберігаємо дані у еластіку (див. альманах) — і з точки зору форми, і з точки зору вмісту. Під “вмістом” я маю на увазі російські назви vs айдішніки, а під “формою”… читайте альманах, як вони зберігають, а нижче я коротко про це розповідаю.
Ураз і код інтеграції з еластіком вже дуже просив оновлення. Дописування фіч було складним і дуже помилкоємним (як щє перекласти error prone?) процесом. По-перше, і написаний код був у часи, коли розуміння і Кложі, і Еластіка було на іншому левелі, і розрахований на значно меншу складність. Та й API для клієнтів (у сенсі веб чи аппів) теж вже пережив 5 версій, тому ми точно знали, які помилки не треба робити. :)
Почали у лютому, а перший реліз сайта з новими фільтрами вийшов аж у травні. Мені здавалося, що “довго” — це ми будемо з місяця півтора працювати над цим, а у результаті… Авжеж, зачепило багато чого, деякі речі (особливо у адмінці сайту) навіть досі не переробили!
Розв’язок
Прекрасна новина була в тому, що API нових фільтрів одразу підтримував українську мову. :) Тож в мене почали свербіти руки — відчув, що ще небагато і ми можемо зробити українську мову, йуху. На бекграунді, як я й мріяв. :)
Що таке бекграунд взагалі? Виявилося, що це коли в жодному спринті немає ні слова про переклади, а ваш CTO намагається оминути більшість зустрічей і сидить пилить лібу для перекладу сайту на ClojureScript’і, бо існуючі йому не подобаються. За червень воно навіть починає працювати, і я, у вільний для роботи час, намагаюсь завести збірку двох js-файлів: по одному на кожну мову. Я розраховував, що за липень-серпень перекладемо, як раз до сезону.
Але коли я розповів, що ми готові перекладати сайт, раптом це стало головним проектом і замість спокійних двох місяців переклали все за буремні 3 тижні. Доробили підтримку української мови в інших апі, помітили всі строки в інтерфейсах, переклали їх, додали українську версію банерів, і т.д. Досі помилки виправляємо, розплачуємося за швидкість. :) Це, авжеж, не тільки розробники працювали, з усієї компанії люди приймали участь, а товари взагалі гугл транслейтом спочатку переклали — ніяка кількість людей не може швидко півмільйони описів перекласти. :)
Частина технічна
А навіщо взагалі писати свою бібліотеку для перекладів, невже не можна взяти щось готове? В нас увесь фронтенд написаний на ClojureScript, і виглядає ось таким чином:
(defc Component [arg1]
[:div "Сколько уже, " [:b arg1] ", ждать можно?"])
;; десь нижче у іншому компоненті:
(Component "блин")
Тобто вся верстка — структурована, а не просто рядок символів, як це звичайно буває в HTML-шаблонах. І ці теги (:div
, :b
) — вони прекомпілюються у виклики реактівських React.createElement
. І мені дуже хотілося це зберігти у результаті, щоб не прийшлося у рантаймі розбирати якусь верстку для того, щоб відмалювати користувачу потрібний елемент. Інакше кажучи, я хотів зробити так, щоб в мене перед компіляцією в українську мову було таке:
(defc Component [arg1]
[:div "Скільки вже, " [:b arg1] ", чекати можна?"])
;; десь нижче у іншому компоненті:
(Component "блін")
То це ми маємо через використання data reader’a і макроса і взагалі там код дещо страшний. :) Треба зізнатися, що ось ця частина — (defn reader-t [input] `(t ~input))
— написана не знаннями, а годиною-двома експериментів. :)
Тим не менш, все це дуже добре запрацювало, .po-файли генеруються, і можна перекладати зовсім складні речі, ось кілька прикладів із нашого uk.po
:
msgid "~(utils/plural term \"платеж\" \"платежа\" \"платежей\")"
msgstr "~(utils/plural term \"платіж\" \"платежи\" \"платежів\")"
msgid "~[:div.auth_title \"На номер \" [:strong id] [:br] \"отправлено SMS с кодом\"]"
msgstr "~[:div.auth_title \"На номер \" [:strong id] [:br] \"відправлено SMS з кодом\"]"
У файлі воно виглядає не дуже через екранізацію лапок, але всі редактори (чи то po-mode у Емаксі, чи то PoEdit, та й інші) при редагуванні дають писати без екранізації, а потім самі ці строки екранують. І так, за синтаксисом треба слідкувати, бо якщо забути десь лапки чи може дужку — то компіляція помре, тож цей файл мають правити або тільки програмісти, або хтось інший, але ну дуже акуратний. :)
Ну а фільтри, які ми так довго вимучували, віддаються з різною мовою в залежності від хедера, який приходить від клієнта. Окрім першого запиту з веба — там ми на nginx’ах знаходимо куку, бо ніяких кастомних хедерів на перший запит не вміємо. Але я мрію, що у воркері зробимо це — досі не зробили тільки тому, що воркер не має доступу ні до чого, окрім IndexedDB
, який є гемор з геморів.
ElasticSearch
Хочу щє витратити кілька слів на те, щоб описати, що змінилося у нас в Еластіку. Раніше в нас була доволі проста, але працююча схема. Товар там виглядав приблизно так:
{"id": "123456",
"properties": {"Kind": "Платье",
"Affiliation": "Женщинам"}}
Що це нам дає? По-перше, дуже просто з Еластіка отримати документ та побачити, що в ньому все нормально. :) По-друге, придумати, як по цьому будувати агрегації (для фільтрів на сайті) — легко. По-третє, дуже зручно для пошуку задавати різний скорінг для різних полей, щоб колір був важливіший за бренд, скажімо.
Але та стаття пропонує інший спосіб зберігання документів. Окрім того, що ми починаємо використовувати айдішники замість слів, ми ще й розкладуємо все на список невеликих словничків:
{"id": "123456",
"facets": [{"type_id": 21, "value_id": 290},
{"type_id": 6, "value_id": 804}]}
Чим цей варіант кращий? Найбільший плюс у тому, що ми можемо однією агрегацією в запиті отримати всі доступні нам характеристики (з властивостями) для фільтрації. До цього моменту у нас в конструкторі меню людина повинна була руками обрати фільтри, які покажуться користувачам при виборі цього пункту меню. А у пошуку взагалі просто був набір фільтрів за замовчуванням. Зараз ми можемо виводити користувачам всі доступні фільтри. Щє є над чим працювати, вочевидь, та все одно це була велика перемога. :)
Мораль
Ура! :)