К содержанию

Сниппет 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
8 771
Реклама

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

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

            Ну и лично я люблю, когда понятно, а не коротко. PHP всё равно оптимизирует исходники при выполнении.
              Fi1osof
              Fi1osof Эксперт
              22 января 2015, 15:57
              Ясно. ОК.
            Павел Левин
            Павел Левин Проверенный
            22 января 2015, 22:32
            На 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
              Не уверен, что нужна его реализация в виде отдельного пакета, хотя я бы наверное собрал несколько таких мелочей в один пакет под названием Утилиты. Сюда можно добавить еще форматирование чисел, дат и тд, тогда будет иметь смысл делать пакет. В остальном, из того, что видел, реализация declension занимает 20 строчек кода максимум.
                Василий Наумкин
                Василий Наумкин Администратор
                23 января 2015, 07:25
                В остальном, из того, что видел, реализация declension занимает 20 строчек кода максимум.
                Обрати внимание — это код из LiveStreet, ты его, вероятно, не видел.

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

                Я вообще не понимаю экономии на строках. А есть еще люди, которые на скобках экономят, и на пробелах. Должно быть понятно, тем более, когда выкладываешь что-то для других.
                  Dmitry Rodionov
                  Dmitry Rodionov Проверенный
                  23 января 2015, 11:49
                  А еще на здоровье экономят :-P
                    Іван Клімчук
                    Іван Клімчук Администратор
                    23 января 2015, 12:02
                    Я не к формату придираюсь, и не считаю пробельные строки и отступы, это же понятно.
                    Я к тому, что функция нужная, но не такая важная, чтобы из нее пакет делать. Алгоритм элементарный, такие задачки на информатике в школе проходить нужно и пишется он за пару минут. Это сейчас не укор твоим способностям, они неоспоримы.
                Андрей
                Андрей Старожил
                03 марта 2015, 20:44
                Давно пользовался 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
                  И всё же, может быть оформить это решение как отдельный пакет для репозитория modstore.pro/ ?

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

                  Я понимаю, что решение для кого-то несложное, но зачем каждый раз его велосипедить, если быстрее скачать и использовать?
                    Илья Уткин
                    Илья Уткин Администратор
                    20 мая 2015, 10:37
                    Поправил пакет, чтобы его можно было найти в репозитории 2.3.x

                  Павел Романов
                  Павел Романов Проверенный
                  19 мая 2015, 17:47
                  На одном форуме народ соревновался в самом «компактном» решении этой задачи ))
                  Победило такое решение:
                  <?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
                    Интересное решение! В закладки!
                      Павел Романов
                      Павел Романов Проверенный
                      19 мая 2015, 18:26
                      Да, только "=" после 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
                        UDAV Старожил
                        04 июня 2016, 22:33
                        А как для корзины minishop2 прикрутить данное решение? Я так понимаю из-за аякса этот код не работает.
                    Andrew
                    Andrew Старожил
                    09 февраля 2020, 09:31
                    Подскажите пожалуйста, в документации про Парсер 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
                      В новых версиях pdoTools общее количество результатов не считается, если не указать это специально, для скорости.
                      Так что сейчас у тебя нет этой переменной, он пустая, и количество комментариев выставляется через javascript.

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

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

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

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