Русский spintax: почему ваши числа всегда выглядят криво (и что мы с этим сделали)
Если вы когда-нибудь писали русский spintax-шаблон с числом перед существительным — 3 языка, 15 слотов, 21 фриспин — есть высокая вероятность, что отрендеренный текст грамматически кривой на четверти казино из вашего каталога. До версии 1.5 этого движка любой spintax-инструмент имел тот же разрыв, и закрыть его аккуратно из шаблона было нечем.
Тест на 30 секунд
Три числа. Три русские формы язык. Подберите правильную для каждого:
| Количество | Правильная форма |
|---|---|
| 1 | язык |
| 2 | языка |
| 11 | языков (не языка!) |
| 21 | язык (обратно в единственное!) |
| 22 | языка |
| 112 | языков |
Правило зависит от n % 10 и n % 100 одновременно, с особым исключением для 11–14. То же самое в украинском и белорусском. У польского — четыре корзины. У чешского, словацкого и словенского — свои варианты. У арабского — шесть корзин. Ни одно из этих правил не выразимо тем набором примитивов, что был в spintax до сих пор.
Три workaround'а, все сломаны
1. Случайный выбор через перечисление
%LangCount% {язык|языка|языков}
Синоним {a|b|c} выбирает форму случайно — независимо от %LangCount%. Поэтому 12 может отрендериться как «12 язык», «12 языка» или «12 языков» — и только последнее правильно. Синтаксис шаблона выглядит так, будто учитывает число. Не учитывает.
Это самый коварный workaround, потому что он проходит беглый ревью. Текст читается грамматичным на том варианте, куда выпал случайный кубик с языков. Редактор смотрит превью, видит один правильный рендер, отгружает в прод — и баг ловится через месяцы, когда жалуется русскоговорящий читатель.
2. Закрытое inline-спаривание
{50|100|150|200} фриспинов
Автор вручную подобрал числа, попадающие в форму many (5+). Существительное жёстко вшито как фриспинов. Работает — для этих четырёх чисел — по случайности. Как только число становится переменной (%CasinoFreeSpinsAmount%), корреляция ломается, и вы возвращаетесь к Workaround 1.
3. Вложенные has-флаги через условия
%CasinoLanguagesCount% {?CasinoHasOneLang?язык|{?CasinoHasFewLangs?языка|языков}}
Ассемблер переменных считает три булевых флага на каждую считаемую сущность (CasinoHasOneLang, CasinoHasFewLangs, ...) и заполняет их "1" или "" по количеству. Шаблон ветвится на флагах.
Это можно сделать правильным — модульная математика живёт в ассемблере, не в шаблоне. Но:
- Три флага на каждую считаемую сущность, на каждый язык. У казино с языками, криптовалютами, провайдерами, бонусами, фриспинами, днями, часами — 7 сущностей × 3 флага × N языков.
- Редактор не может добавить новую конструкцию число + существительное, не попросив инженера сначала добавить тройку флагов, задеплоить новую версию воркера, и только потом писать шаблон. Каждый счётчик — engineer-in-the-loop.
- Литеральные числа в копи («за 30 дней», «5 крипт») вообще не закрываются — для произвольных литералов нет предзаготовленных флагов.
- Исключение «1, 21, 31… кроме 11» всё равно надо выражать в ассемблере.
{?Flag?…}— это truthy/falsy gate, он не умеетn % 10.
Почему до нас никто этого не сделал
Семейство spintax — GTW (Generating The Web), оригинальный русский SEO-инструмент, от которого синтаксис унаследован; nested-spintax-for-acf, ReCsBlue, любой WordPress-плагин в нише; десятки standalone «spintax-генераторов» в дикой природе — все используют один и тот же набор примитивов: перечисление {a|b|c}, иногда permutation [a|b|c], иногда переменные. Ни один не везёт примитив для плюрала. Русский веб обходит этот разрыв уже десятилетие.
Параллельно любой современный i18n-стэк считает это table-stakes:
- ICU MessageFormat:
{count, plural, one {1 язык} few {{count} языка} other {{count} языков}} - gettext:
ngettext("язык", "языка", count)с заголовкомPlural-Forms - FormatJS, Polyglot, i18next — везде та же форма
Эти инструменты живут в i18n, не в content generation. Адаптация любого из них означала бы миграцию каждого существующего {a|b|c} в платформе на ICU-синтаксис. Spintax оставался сломанным, потому что цена смены инструмента была выше цены расплывчатой копи.
Что мы выпустили
Новый spintax-нативный примитив, стоящий рядом с существующими, использующий те же фигурные скобки и pipe-разделители, и резолвящий число в форму на уровне движка:
поддерживает %CasinoLanguagesCount% {plural %CasinoLanguagesCount%: язык|языка|языков}
Три формы для русского/украинского/белорусского (one|few|many), две для EN-style локалей (one|many), с модульной математикой в хелпере движка — не в шаблоне, не в ассемблере.
Слот числа принимает либо ссылку %Var%, либо целое число литералом. Подстановка переменных идёт первой; к моменту работы плюрального прохода в слоте уже целое число литералом. Литеральный префикс «plural » — дискриминатор парсера, который не даёт спутать конструкцию с синонимом {a|b|c} даже после раскрытия. : однозначно отделяет число от форм.
Эффект на каталоге
Три реальных казино из публичного каталога:
| Казино | Криптовалюты | Провайдеры | Языки |
|---|---|---|---|
| 1xBet | 18 | 38 | 16 |
| 888starz | 1 | 12 | 8 |
| BC.Game | 17 | 24 | 12 |
Без примитива каждое казино рендерит одну и ту же расплывчатую фразу: «поддерживает множество криптовалют, среди которых Bitcoin, Ethereum, Tether». Фактическая разница между 18 и 1 криптовалютами невидима. С примитивом тот же шаблон рендерит три различных предложения:
- 1xBet: «поддерживает 18 криптовалют, среди которых Bitcoin, Ethereum, Tether»
- 888starz: «поддерживает 1 криптовалюту, среди которых Bitcoin»
- BC.Game: «поддерживает 17 криптовалют, среди которых Bitcoin, Ethereum, Tether»
SEO выигрывает от настоящей дифференциации: каждый обзор казино несёт уникальные для своего каталога факты. Читатель — от конкретных чисел вместо общих «много» / «различные». Редактор больше не тянется за расплывчатыми кванторами, потому что инструмент наконец-то даёт писать ровно то, что имеется в виду.
Где работает
- WordPress-плагин (этот проект, v1.5.0) — полный PHP-порт. Встроен в render-пайплайн между раскрытием условий и раскрытием перечислений. ~70 PHPUnit-тестов зеркалят канонический TS.
- TypeScript-движок — выпущен первым в casino-platform; вендорится в бандл spintax.net. Тот же алгоритм, те же краевые случаи.
- Песочница spintax.net — попробуйте вживую в браузере, без установки.
Движок под допустимой лицензией, и примитив документирован как часть spintax-синтаксиса, а не приватное расширение. Любой будущий spintax-инструмент может принять тот же синтаксис без координации — и мы надеемся, что примут.
Попробовать
В песочнице есть пример с плюралом. Поставьте %Count% на 0, 1, 2, 5, 11, 21, 22 по очереди и переключайте локаль между en и ru — логика корзин видна end-to-end без единой строки кода редактора.
Полное руководство — каждая форма, каждый краевой случай, точное место в пайплайне и боевые паттерны написания — в Согласование с числом: {plural <count>: form|…}.