Сниппет Declension для склонения слов

Предлагаю вашему вниманию небольшой сниппет для вывода склонения. Работает как фильтр вывода, использовать нужно примерно так:
[[+balls]] [[+balls:declension=`балл,балла,баллов`]]

Как видите, он применяется к любым тегам с числами, а в options нужно указать три формы слова (1 яблоко, 2 яблока и 10 яблок) для русского, или 2 (1 ticket, 2 tickets) для английского.

Для экспериментов можно повызывать сниппет с произвольными числами:
15 [[*id:input=`15`:declension=`яблоко,яблока,яблок`]]
Полный код под катом.

<?php
/**
 * Declension of words
 * This algorithm taken from https://github.com/livestreet/livestreet/blob/eca10c0186c8174b774a2125d8af3760e1c34825/engine/modules/viewer/plugs/modifier.declension.php
 */

/** @var array $scriptProperties */
$number = $modx->getOption('input', $scriptProperties, 0);
$forms = $modx->getOption('options', $scriptProperties, '');
if (is_string($forms)) {
	if ($forms[0] == '[') {
		$forms = $modx->fromJSON($forms);
	}
	else {
		$forms = explode(',', $forms);
	}
}
if (!is_numeric($number)) {
	return;
}
if (!is_array($forms) || empty($forms)) {
	return 'Вы должны указать формы слов для склонения. Например [[*id:declension=`яблоко,яблока,яблок`]].';
}

if (count($forms) == 3) {
	$mod100 = $number % 100;
	switch ($number % 10) {
		case 1:
			if ($mod100 == 11) {
				$text = $forms[2];
			}
			else {
				$text = $forms[0];
			}
			break;
		case 2:
		case 3:
		case 4:
			if (($mod100 > 10) && ($mod100 < 20)) {
				$text = $forms[2];
			}
			else {
				$text = $forms[1];
			}
			break;
		case 5:
		case 6:
		case 7:
		case 8:
		case 9:
		case 0:
		default:
			$text = $forms[2];
	}
}
elseif (count($forms) == 2) {
	if ($number == 1) {
		$text = $forms[0];
	}
	else {
		$text = $forms[1];
	}
}
else {
	$text = 'Недостаточно форм слов для склонения';
}

return $text;

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

Если кто-то оформит его в готовый пакет и выложит в репозиторий — не возражаю. Уже давно есть.
Василий Наумкин
22 января 2015, 06:44
modx.pro
28
6 414
+15

Комментарии: 30

Максим Кузнецов
22 января 2015, 10:30
0
Спасибо, забираю.)
    Николай Загумённов
    22 января 2015, 10:31
    0
    а он через аякс не обрабатывает {товара, товаров, товар} в чанке корзины? )
      Илья Уткин
      22 января 2015, 11:03
      1
      +1
      Столкнулся однажды с такой же проблемой, так что в оф. репозитории похожее дополнение есть)
      Пашок
      Пашок
      22 января 2015, 12:49
      0
      Грамотно! От души, друг! Ещё бы осуществить его работу под минишоп2. :)
        Иван Климчук
        22 января 2015, 23:54
        0
        а в чем проблема? он же как фильтр работает
        Fi1osof
        22 января 2015, 15:34
        0
        Василий, а на сколько оправдано так много строк кода? У нас в смарти-плагине всего несколько строчек. Все тоже самое, ниразу не было замечено, чтобы склонения как-то не так были.
        {$count|spell:«яблоко»:«яблока»:«яблок»}
          Василий Наумкин
          22 января 2015, 15:52
          0
          Это адаптация чужого кода, плюс здесь можно указывать 2 формы для английских слов.

          Ну и лично я люблю, когда понятно, а не коротко. PHP всё равно оптимизирует исходники при выполнении.
            Fi1osof
            22 января 2015, 15:57
            0
            Ясно. ОК.
          Павел Левин
          22 января 2015, 22:32
          1
          +1
          На JS тоже так можно =)

          function declineWord(number, titles)  {  
              cases = [2, 0, 1, 1, 1, 2];  
              return titles[ (number%100>4 && number%100<20)? 2 : cases[(number%10<5)?number%10:5] ];  
          }

          declineWord(2,['кнопка','кнопки','кнопок']);//2 кнопки
            Иван Климчук
            22 января 2015, 23:57
            +1
            Не уверен, что нужна его реализация в виде отдельного пакета, хотя я бы наверное собрал несколько таких мелочей в один пакет под названием Утилиты. Сюда можно добавить еще форматирование чисел, дат и тд, тогда будет иметь смысл делать пакет. В остальном, из того, что видел, реализация declension занимает 20 строчек кода максимум.
              Василий Наумкин
              23 января 2015, 07:25
              0
              В остальном, из того, что видел, реализация declension занимает 20 строчек кода максимум.
              Обрати внимание — это код из LiveStreet, ты его, вероятно, не видел.

              Можно и в одну строку сжать — не проблема. От этого кому лучше будет?

              Я вообще не понимаю экономии на строках. А есть еще люди, которые на скобках экономят, и на пробелах. Должно быть понятно, тем более, когда выкладываешь что-то для других.
                Dmitry Rodionov
                23 января 2015, 11:49
                0
                А еще на здоровье экономят :-P
                  Иван Климчук
                  23 января 2015, 12:02
                  0
                  Я не к формату придираюсь, и не считаю пробельные строки и отступы, это же понятно.
                  Я к тому, что функция нужная, но не такая важная, чтобы из нее пакет делать. Алгоритм элементарный, такие задачки на информатике в школе проходить нужно и пишется он за пару минут. Это сейчас не укор твоим способностям, они неоспоримы.
              Андрей
              Андрей
              03 марта 2015, 20:44
              0
              Давно пользовался modx.com/extras/package/numeralstring
              Но поскольку для новых версий MODX его из репозитория не скачать, можно использовать обычный сниппет:
              <?php
              /**
               *
               * @package numeralString
               * @author Kirill A. <kirusanov@gmail.com>
               *
               * Числительное по-русски.
               * На выходе число и три варианта написания: 1 - "*уй", 2 - "*уя", 5 - "*уев".
               * Никаких чанков для вывода - просто возвращает один из вариантов числительного.
               *
               * @property count число
               * @property one числительное для 1
               * @property two числительное для 2
               * @property five числительное для 5
               *
               */
              
              $count = !empty($scriptProperties['count']) ? abs((int)$scriptProperties['count']) : 0;
              $one = !empty($scriptProperties['one']) ? $scriptProperties['one'] : 'голос';
              $two = !empty($scriptProperties['two']) ? $scriptProperties['two'] : 'голоса';
              $five = !empty($scriptProperties['five']) ? $scriptProperties['five'] : 'голосов';
              $showNumber = !empty($scriptProperties['showNumber']) ? $scriptProperties['showNumber'] : 0;
              
              if ($showNumber == 1) {
              if (preg_match('/^1$|\d*[02-9]1$/', $count)) { return $count.' '.$one; }
              if (preg_match('/^[2-4]$|\d*[02-9][2-4]$/', $count)) { return $count.' '.$two; }
              return $count.' '.$five;
              }
              else {
              if (preg_match('/^1$|\d*[02-9]1$/', $count)) { return $one; }
              if (preg_match('/^[2-4]$|\d*[02-9][2-4]$/', $count)) { return $two; }
              return $five;
              }
              А дальше вызывать:
              [[!numeralString? &count=`123` &one=`голос`&two=`голоса`&five=`голосов`]]
                Василий Столейков
                19 мая 2015, 17:18
                0
                И всё же, может быть оформить это решение как отдельный пакет для репозитория modstore.pro/ ?

                Почему я поднял этот вопрос:
                потому что сейчас я решил «как обычно» не заморачиваясь скачать пакет «Units» из офф-репозитория, но его там не оказалось (не знаю, конфликт версий с MODX наверное). Дальше я вспомнил про этот сниппет и решил вбить его здесь на сообществе в поиск. Но подобрать подходящее слово у меня не получилось, и по «Units» тоже эта статья не нашлась. И пришлось мне кликать по пагинации раз 20 пока я не нашел эту статью (в закладках в профиле тоже куча статей и также долго пришлось её искать).

                Я понимаю, что решение для кого-то несложное, но зачем каждый раз его велосипедить, если быстрее скачать и использовать?
                Павел Романов
                19 мая 2015, 17:47
                7
                +3
                На одном форуме народ соревновался в самом «компактном» решении этой задачи ))
                Победило такое решение:
                <?php
                if($n!=''){
                return= $n%10==1&&$n%100!=11?$w1:($n%10>=2&&$n%10<=4&&($n%100<10||$n%100>=20)?$w2:$w3);
                }
                Ну и вызываем:
                [[Snippet? &n=`123` &w1=`год` &w2=`года` &w3=`лет` ]]
                  Василий Столейков
                  19 мая 2015, 18:21
                  0
                  Интересное решение! В закладки!
                    Павел Романов
                    19 мая 2015, 18:26
                    0
                    Да, только "=" после return долой, естессно ))):
                    <?php
                    if($n!=''){
                    return $n%10==1&&$n%100!=11?$w1:($n%10>=2&&$n%10<=4&&($n%100<10||$n%100>=20)?$w2:$w3);
                    }
                      UDAV
                      04 июня 2016, 22:33
                      0
                      А как для корзины minishop2 прикрутить данное решение? Я так понимаю из-за аякса этот код не работает.
                  Andrew
                  09 февраля 2020, 09:31
                  0
                  Подскажите пожалуйста, в документации про Парсер pdoTools есть такой модификатор:
                  declension (decl) — склоняет слово, следующее за числом по правилам русского языка. Например: 1 яблоко, 2 яблока, 10 яблок.
                  и пример:
                  {6 | declension : 'яблоко|яблока|яблок'} // яблок
                  {3 | declension : 'яблоко|яблока|яблок' : true} // 3 яблока
                  {101 | decl : 'яблоко,яблока,яблок' : false : ','} // яблоко
                  У меня для статей сделаны комментарии при помощи компонента Tickets, теперь к сути:
                  чанк обёртки комментариев содержит переменную {$total} — количество комментариев. Хочу добавить склонение, но оно не хочет работать, хм… вот чанк tpl_commentsWrapper
                  <section class="inner-bottom-xs comments">
                      <h3 class="title">(<span id="comment-total">{$total}</span>) {$total | declension:'Комментарий|Комментария|Комментариев'}- </h3>
                      <div id="comments-wrapper">
                          <div class="comment-list" id="comments">
                              {$comments}
                          </div>
                      </div>
                      <div id="comments-tpanel">
                          <div id="tpanel-refresh"></div>
                          <div id="tpanel-new"></div>
                      </div>
                  </section>
                  Может подскажете что не так делаю, ибо не склоняет переменную. Если подставлять цифры то склоняет, а переменную не хочет.
                    Василий Наумкин
                    09 февраля 2020, 14:40
                    0
                    В новых версиях pdoTools общее количество результатов не считается, если не указать это специально, для скорости.
                    Так что сейчас у тебя нет этой переменной, он пустая, и количество комментариев выставляется через javascript.

                    Укажи &setTotal=`1` в вызове TicketsComments, и всё должно заработать.
                      Andrew
                      09 февраля 2020, 15:37
                      0
                      Спасибо. Заработало. А сильно это на скорость влияет?
                        Василий Наумкин
                        09 февраля 2020, 17:13
                        0
                        Скорость надо проверять, зависит от многих факторов, но на большом количестве записей подсчёт результатов может украсть полсекунды или больше.

                        Да и дело даже не в скорости, а в том, что при добавлении нового комментария их общее количество изменится, а склонение — нет.

                        Поэтому лучше или оставить как есть, без склонения, или делать его на js — и тогда total вообще не нужен.
                    Andrew
                    09 февраля 2020, 17:28
                    0
                    Меня вот что сбивает с толку, переменная total без модификатора выводит количество, а с модификатором не хочет… получается что количество без модификатора считается без настройки 'set total'=>1, а с модификатором надо включать эту настройку… буду знать, спасибо.
                      Василий Наумкин
                      09 февраля 2020, 17:52
                      0
                      Еще раз — нет, не выводит она ничего.

                      Это после загрузки страницы javascript подставляет количество комментов. Открой исходник страницы и увидишь на месте переменной, что там пусто.
                      Авторизуйтесь или зарегистрируйтесь, чтобы оставлять комментарии.
                      30