Война с роботами: склонение существительных после числительных

    Робот в кармашкеВ первой части Терминатора Рис Кайл рассказывает о том, как круто роботы научились маскироваться под людей. Что сейчас у них настоящие кожа и волосы, они потеют и т. п. Про предыдущие же модели он говорил, что их легко было отличить по резиновой коже.

    Думаю, что ещё более ранние модели отличались совсем просто — они говорили: «Солдат Крис Катарн, убил 10 враг, потратил 342 патрон, получил 0 ранение» и т. п., чем сразу палились.

    Ведь до сих пор, несмотря на развитие веба, на многих сайтах можно встретить «50 пользователи», «1 комментарии», «0 сообщения» и т. п. А ведь насколько приятней, когда сайт говорит с тобой на человеческом языке и правильно спрягает слова по числам.

    И ведь сделать это совсем несложно. Ниже приведены простые готовые функции, которые позволяют решить эту проблему на PHP и Javascript. Они настолько просты, что не составит труда перенести их на любой другой язык.

    В функцию передаётся число сущностей, для которого нужно подобрать окончания, и массив слов (или окончаний для слов) для чисел 1, 4 и 5. Например ['устрица', 'устрицы', 'устриц'].

    PHP



    1. /**
    2.  * Функция возвращает окончание для множественного числа слова на основании числа и массива окончаний
    3.  * param  $number Integer Число на основе которого нужно сформировать окончание
    4.  * param  $endingsArray  Array Массив слов или окончаний для чисел (1, 4, 5),
    5.  *         например array('яблоко', 'яблока', 'яблок')
    6.  * return String
    7.  */
    8. function getNumEnding($number, $endingArray)
    9. {
    10.     $number = $number % 100;
    11.     if ($number>=11 && $number<=19) {
    12.         $ending=$endingArray[2];
    13.     }
    14.     else {
    15.         $i = $number % 10;
    16.         switch ($i)
    17.         {
    18.             case (1): $ending = $endingArray[0]; break;
    19.             case (2):
    20.             case (3):
    21.             case (4): $ending = $endingArray[1]; break;
    22.             default: $ending=$endingArray[2];
    23.         }
    24.     }
    25.     return $ending;
    26. }


    JavaScript


    1. /**
    2.  * Функция возвращает окончание для множественного числа слова на основании числа и массива окончаний
    3.  * param  iNumber Integer Число на основе которого нужно сформировать окончание
    4.  * param  aEndings Array Массив слов или окончаний для чисел (1, 4, 5),
    5.  *         например ['яблоко', 'яблока', 'яблок']
    6.  * return String
    7.  */
    8. function getNumEnding(iNumber, aEndings)
    9. {
    10.     var sEnding, i;
    11.     iNumber = iNumber % 100;
    12.     if (iNumber>=11 && iNumber<=19) {
    13.         sEnding=aEndings[2];
    14.     }
    15.     else {
    16.         i = iNumber % 10;
    17.         switch (i)
    18.         {
    19.             case (1): sEnding = aEndings[0]; break;
    20.             case (2):
    21.             case (3):
    22.             case (4): sEnding = aEndings[1]; break;
    23.             default: sEnding = aEndings[2];
    24.         }
    25.     }
    26.     return sEnding;
    27. }


    Не забудьте отдельно обработать случай, для числа 0. Просто написать, например, «0 записей» не достаточно. Нужно как минимум написать «Записей нет» или изменить дизайн, скрыв вообще пустой блок с записями.

    UPD: Спасибо IGlukhovу за то, что поправил неграмотное название!
    Ads
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More

    Comments 122

      +2
      У себя в проектах использую этот код, не знаю автора, к сожалению, но работает отлично.

      /**
       * Возвращает единицу измерения с правильным окончанием
       * 
       * @param {Number} num      Число
       * @param {Object} cases    Варианты слова {nom: 'час', gen: 'часа', plu: 'часов'}
       * @return {String}            
       */
      function units(num, cases) {
          num = Math.abs(num);
          
          var word = '';
          
          if (num.toString().indexOf('.') > -1) {
              word = cases.gen;
          } else { 
              word = (
                  num % 10 == 1 && num % 100 != 11 
                      ? cases.nom
                      : num % 10 >= 2 && num % 10 <= 4 && (num % 100 < 10 || num % 100 >= 20) 
                          ? cases.gen
                          : cases.plu
              );
          }
          
          return word;
      }
      
        –1
        проще можно:
        /// /// по числу возврщает индекс из массива, чтоб было правильное окончание
        /// массив должен быть такой ['просмотр', 'просмотра', 'просмотров']
        ///
        ///
        ///
        public static int WorksEnd(int number)
        {
        if(Regex.Match(number.ToString(), «1\\d$»).Success)
        return 2;
        if(Regex.Match(number.ToString(), «1$»).Success)
        return 0;
        if(Regex.Match(number.ToString(), "(2|3|4)$").Success)
        return 1;
        return 2;
        }
          0
          На всякий случай, может кому пригодится:
          nom — Nominativ, именительный падеж;
          gen — Genetiv, родительный падеж;
          plu — Plural, множественное число.

        • UFO just landed and posted this here
            0
            Или даже «01 Января».
            +1
            А вот хотелось бы, чтобы не забивать базы всякими яблоками, одну функцию на все слова, которая определив склонение, сама высчитывала нужное окончание… Даже несмотря на великость и могучесть великого и могучего точность такой функции, на первый взгляд, можно довести до 70%
              0
              Для английского писал, причем поддерживала даже user's — users'.
              Для русского из-за того, что окончание зависит от рода существительного (пользователь, канитель), не стал писать что-то серьезное.
                0
                и ещё от одушевлённости, между прочим, тоже, так что нифига :)
                  0
                  Нет, от одушевленности зависит только окончания при склонении. Видеть кого? Пользователя. Видеть что? Бордель.
                  Но 5 пользователей, 5 борделей.
                  Может, я о чем-то забыл, но вроде как на окончание во множественном числе одушевленность не влияет.
                    0
                    5 человек, 5 веков
                      0
                      Главное не забывать о пользователях, и борделях!
                  +1
                  в pymorphy это есть, для питона, правда)
                    0
                    Поправьте меня, если я ошибаюсь, но там кажется задействованы словари, а хотелось как раз без этого
                      +3
                      Конечно, словари. Как иначе «человек-люди» во множественное число поставишь. А для слов, которых нет в словаре, прекрасно работает предсказатель, который склоняет слова по аналогии. Что imho работает лучше и проще каких-то вручную прописанных правил.

                      Прописывание правил в коде в пределе как раз и приводит к составлению словаря (только в довольно корявом и неудобным для поддержки виде).
                        0
                        Сейчас на сайте 10 человеков. Только что зашедшие человеки: Филипп, Туранга.
                          –1
                          Тогда уж, продолжая тему:
                          *Бендер заходит в чат
                          *Человеки покидает чат
                            –1
                            Сейчас на сайте 10 людей. Только что зашедшие люди: Филипп, Туранга.

                            один фиг
                              0
                              сейчас на сайте 1 людь

                              (извините, что отвечаю в старую тему)
                                0
                                (извините, что отвечаю в старую тему)

                                Да ничего страшного.
                                  0
                                  Так ведь все еще актуально =)
                        +1
                        Есть и PHPMorphy, но там словари по 7 Мб и ресурсов он кушает прилично. А в большинстве задач заранее известно чего будет спрягаться. Так что не вижу особой выгоды в универсальной функции.
                          +2
                          угу, морфология для этого — из пушки по воробьям. Разве что слова из базы обрабатывать.

                          введите название предмета: сепулька
                          у Вас 15 сепулек
                            +12
                            — Дайте мне пять шаурмей, ммм… 5 шаурменей, 5 шаурмов…
                            — Дайте мне две шаурмы и три шаурмы.
                              +1
                              И пять кочерег!
                      +9
                      Весьма рекомендую использовать тщательно собранные формулы для разных языков translate.sourceforge.net/wiki/l10n/pluralforms

                      PHP-шники аккуратно, там формулы даны для С, а в С немного другие приоритеты операторов, потому придётся расставить скобочки.
                        –3
                        Для тех, кто использует Smarty:
                        <?php
                        function smarty_function_ending($params, &$smarty)
                        {
                        $base = !isset($params['base']) ? '' : $params['base'];

                        $e0 = $params['e0'];
                        $e1 = $params['e1'];
                        $e2 = $params['e2'];
                        $number = $params['number'];

                        if ($number % 100 > 10 && $number % 100 < 20) return $base . $e0;
                        if ($number % 10 == 1) return $base . $e1;
                        if ($number % 10 >= 2 && $number % 10 <= 4) return $base . $e2;
                        return $base . $e0;
                        }


                        ложим в smarty/plugins/function.ending.php и используем
                        {$item.comments} {ending number=$item.comments base=«комментар» e0='иев' e1='ий' e2='ия'}
                          +15
                          Отлично, теперь мы будем ложить со склонениями!
                            +5
                            Кладём!
                              –1
                              Я с вас фигею, ребят. Человек дает вам работающее программное решение и получает минусы. Ему отвечают банальным занудством и огребают кучу плюсов. Ну-ну… а мы-то и не знали, как правильно: лОжить или ложИть.
                                0
                                Ну-ну… Раз вы не знали, то правильно: класть.
                                  0
                                  я думаю, это были минусы за smarty
                                  0
                                  как ты заверстаешь такие фразы?
                                  * к комментарию не добавлено плюсов
                                  * к комментарию добавлен плюс
                                  * к комментарию добавлены 2 плюса
                                  * к комментарию добавлены 5 плюсов

                                  попутно… как правильно?
                                  * к комментарию добавлен 1.1 плюс
                                  * к комментарию добавлен 1.1 плюса
                                    0
                                    Где то так:
                                    к комментарию
                                    {if $comments == 0}
                                    не добавлено плюсов
                                    {elseif $comments == 1}
                                    добавлен плюс
                                    {else}
                                    добавлены {$comments} {ending number=$comments base="плюс" e0="ов" e1="" e2="а"}
                                    {/if}


                                    попутно — думаю что правильнее «к комментарию добавлен 1.1 плюса» — «одна целая одна десятая плюса», но приведенный мной код не работает правильно с дробными числами.
                                      0
                                      вот видишь, пришлось добавлять пачку исключений… а ведь куда приятней было бы так:

                                      к комментарию {count $comments}не добавлено{/}добавлен{/}добавлены {$comments}{/count} {plural $comments}плюсов{/}плюс{/}плюса{/plural}

                                      «одна целая одна десятая плюса» — так не говорят, говорят: «один и один плюс»
                                    0
                                    уууу извращенец!
                                    громоздковатая конструкция, не находишь?
                                    для тех, кто использует смарти имхо проще сделать как то так:

                                    <?php
                                    function smarty_modifier_russify($i, $s1, $s4 = false, $s5 = false)
                                    {
                                        if (false == $s4) $s4 = $s1;
                                        if (false == $s5) $s5 = $s1;
                                        return Funcs::getRussianNumEnding($i, array($s1, $s4, $s5));
                                    }
                                    ?>

                                    суём это дело в \smarty\plugins\modifier.russify.php и используем:

                                    {$item.comments} комментар{$item.comments|russify:'иев':'ий':'ия'}

                                    ну или там

                                    Всего пользователей: {$usersTotal} {$usersTotal|russify:'человек':'человека'}
                                    0
                                    >массив слов (или окончаний для слов) для чисел 1, 4 и 5

                                    проще запомнить как «один», «два», «много»
                                      +7
                                      Вообще в такие функции обычно передаются окончания для чисел 0, 1 и 2 (согласитесь, это намного логичнее чем 1,4 и 5 или любой другой вариант).

                                      И, надо сказать, я удивлен таким постом, рассказывающем абсолютно банальные вещи — не надо пожалуйста опускать планку статей ниже плинтуса. А то такими темпами скоро появятся статью «Условные операторы в PHP» или «Как включить компьютер»
                                        +2
                                        Полезной информации в виде реализации в данном топике тоже не вижу — если уж программист не способен 1 раз в жизни написать для себя функцию в 10 строк, то гугл в помощь — на первых строках в выдаче есть реализации получше предложенной.
                                          0
                                          Можно было не приводить примеры функций, они и в самом деле элементарны. Тут скорее попытка привлечь внимание общественности к проблеме. Но выложить на Хабре статью и без примера — это как-то, по-моему, странно.
                                            0
                                            Я понял. Но в итоге просто у вас получилась статья на 2 экрана, вся суть которой умещается в 1 предложение.
                                              0
                                              Угу. Лучше бы раскрыли тему интеграции с gettext, в котором эти функции уже зашиты для кучи языков.
                                        +7
                                        Пользователь elfiki когда-то написал лаконичную функцию на PHP, пост с которой почему-то быстро поместил в черновики. К счастью, я успел зазвездить пост в ридере.

                                        /**
                                        * @param $number int число чего-либо
                                        * @param $titles array варинаты написания для количества 1, 2 и 5
                                        * @return string
                                        */
                                        function human_plural_form($number, $titles=array('комментарий','комментария','комментариев')){
                                        $cases = array (2, 0, 1, 1, 1, 2);
                                        return $number." ".$titles[ ($number%100>4 && $number%100<20)? 2: $cases[min($number%10, 5)] ];
                                        }
                                          0
                                          Мои вариант. Вроде как короче выглядит.

                                          //Для вывода статистики на екран.
                                          //firststr — пользователь
                                          //secondstr — пользователя
                                          //thirdstr — пользователей
                                          function get_name($count,$firststr,$secondstr,$thirdstr)
                                          {
                                          $ost = $count % 100;
                                          if ($ost > 9 && $ost < 20)
                                          {
                                          return $thirdstr;
                                          }
                                          else
                                          {
                                          $ost = $ost % 10;
                                          if ($ost == 1)
                                          return $firststr;
                                          elseif ($ost > 1 && $ost < 5)
                                          return $secondstr;
                                          else
                                          return $thirdstr;
                                          }
                                          }
                                            0
                                            Рад что перешел на Ruby on Rails и использую, сейчас уже встроенную, библиотеку i18n
                                              +1
                                              gettext тоже умеет plurals
                                              0
                                              $plural = $n%10==1&&$n%100!=11?'комментарий':($n%10>=2&&$n%10<=4&&($n%100<10||$n%100>=20)?'комментария':'комментариев');

                                              я думал, алгоритм подстановки слова в нужной форме уже ни для кого не секрет :-)
                                                +3
                                                Топик медленно перетекает в «Самые извращенские реализации функции окончания числительных» :)
                                                +10
                                                Это называется склонение существительных после числительных, а не «спряжение слов по числам».
                                                  0
                                                  а перед числительными их типа склонять не надо?
                                                  0
                                                  На php все гораздо проще:
                                                    0
                                                    сорри, вот:
                                                        function declOfNum($number, $titles)  
                                                        {  
                                                            $cases = array (2, 0, 1, 1, 1, 2);  
                                                            return $number." ".$titles[ ($number%100 > 4 && $number %100 < 20) ? 2 : $cases[min($number%10, 5)] ];  
                                                        } 
                                                    
                                                      0
                                                      переписывается на JS за 1 минуту, не менее просто :)
                                                      в этом коде нет ни одного выражения php, которое отсутствует в Javascript :)

                                                      зы: не могу утверждать, но мне кажется, что автор этого кода мой друг (не уверен, потому что он мог его где-то вычитать) — forum.woweb.ru/topic28561.html
                                                      • UFO just landed and posted this here
                                                      +1
                                                      Не будет бесполезным добавить функции 3ий параметр, отвечающий за обработку нулевого значения, ср.
                                                      В теме 0 комментариев
                                                      До события осталось нет часов

                                                      З.Ы. Это не просьба о помощи, а «хозяйке на заметку»
                                                        +1
                                                        А можно так:
                                                        Сообщения: 12
                                                        Устрицы: 76
                                                        и т.д. Ведь сайт это все-таки не человек а интерфейс, а в интерфейсах такое вполне допустимо.
                                                          +2
                                                          Rails (gem russian):

                                                          Russian::pluralize
                                                          Russian::p
                                                          
                                                          Russian.p(1, "вещь", "вещи", "вещей")
                                                          => "вещь"
                                                          Russian.p(2, "вещь", "вещи", "вещей")
                                                          => "вещи"
                                                          Russian.p(10, "вещь", "вещи", "вещей")
                                                          => "вещей"
                                                          Russian.p(3.14, "вещь", "вещи", "вещей", "вещи") # последний вариант используется для дробных величин
                                                          => "вещи"
                                                          
                                                            +1
                                                            кстати, частенько стоящее перед числительным слово тоже меняется.

                                                            например:

                                                            остался 1 час
                                                            осталось 2 часа

                                                            так что для полноценной работы функцию стоит дописать.

                                                            свою я выкладывать не буду, она страшная и уродливая.
                                                              +4
                                                              Можно использовать gettext.
                                                                0
                                                                а как там реализован универсальный (для любых языков) способ склонения?
                                                                  0
                                                                  Насколько я знаю, да.
                                                                    0
                                                                    Извините, но запятой после слова «как» не было, так что вопрос был в нём.
                                                                +3
                                                                Для XML/XSL.
                                                                Функция склонения
                                                                <!-- Склонение после числительных -->
                                                                <xsl:template name="declension">
                                                                	<!-- Число -->
                                                                	<xsl:param name="number" select="number"/>
                                                                	
                                                                	<!-- "Комментарий" -->
                                                                	<xsl:param name="f0" select="f0" />
                                                                
                                                                	<!-- "Комментария" -->
                                                                	<xsl:param name="f1" select="f1" />
                                                                
                                                                	<!-- "Комментариев" -->
                                                                	<xsl:param name="f2" select="f2" />
                                                                	<xsl:variable name="absnum">
                                                                		<xsl:choose>
                                                                			<xsl:when test="$number < 0"><xsl:value-of select="0 - $number" /></xsl:when>
                                                                			<xsl:otherwise><xsl:value-of select="$number" /></xsl:otherwise>
                                                                		</xsl:choose>
                                                                	</xsl:variable>
                                                                	
                                                                	<xsl:choose>
                                                                	   <xsl:when test="($absnum mod 10) = 1 and ($absnum mod 100) != 11">
                                                                		  <xsl:value-of select="$f0"/>
                                                                	   </xsl:when>
                                                                	   <xsl:when test="(($absnum mod 10) >= 2) and (($absnum mod 10) <= 4) and (($absnum mod 100 < 10) or ($absnum mod 100 >= 20))">
                                                                		  <xsl:value-of select="$f1"/>
                                                                	   </xsl:when>
                                                                	   <xsl:otherwise>
                                                                		  <xsl:value-of select="$f2"/>
                                                                	   </xsl:otherwise>
                                                                	</xsl:choose>
                                                                </xsl:template>

                                                                Вызов
                                                                <xsl:call-template name="declension">
                                                                	<xsl:with-param name="number" select="value-of-your-counter-or-number" />
                                                                	
                                                                	<xsl:with-param name="f0" select="'комментарий'" />
                                                                	<xsl:with-param name="f1" select="'комментария'" />
                                                                	<xsl:with-param name="f2" select="'комментариев'" />
                                                                </xsl:call-template>

                                                                Можно легко усовершенствовать — я подгружал функцией document() XML-словарь по коду языка (en.xml, ru.xml и т.д.), взятому из преобразуемого XML-файла (скажем в root-элементе тег lang с нужным значением) и модифицировал функцию declension для работы с разными языками, передавая не непосредственно слова в 3-х формах, а id фразы/слова из словаря.
                                                                  +1
                                                                  Хабрапарсер преобразовал символы… внутри всех параметров test вместо < и > должны быть &gt; и &lt;, т.е. <= и >= надо писать как как &gt;= и &lt;= соответственно.
                                                                    0
                                                                    мы используем похожий шаблон, только с 2 параметрами — первым передаем число, а вторым формы слова через запятую — мне кажется так удобнее чем делать 4 параметра :)
                                                                    –1
                                                                    Обалдеть) Буквально 15 минут назад на работе написал такое на C# для количества дней))) Жестоко совпало)

                                                                    private static string GetDaysWithCase(int daysCount)
                                                                    {
                                                                    if (daysCount < 0)
                                                                    throw new ArgumentException("daysCount");

                                                                    string days = daysCount.ToString();
                                                                    string daysCase = "дней";
                                                                    char lastChar = days[days.Length - 1];

                                                                    if (days.Length >= 2 && days[days.Length - 2] == '1')
                                                                    {
                                                                    // 10-19 дней
                                                                    }
                                                                    else if (lastChar == '1')
                                                                    {
                                                                    // 1, 21, 31 день
                                                                    daysCase = "день";
                                                                    }
                                                                    else if (lastChar == '2' || lastChar == '3' || lastChar == '4')
                                                                    {
                                                                    // 2, 3, 4, 22, 33, 44 дня
                                                                    daysCase = "дня";
                                                                    }

                                                                    return string.Format("{0} {1}", days, daysCase);
                                                                    }
                                                                      0
                                                                      а подсветка-то и не работает :(
                                                                      private static string GetDaysWithCase(int daysCount)
                                                                          {
                                                                            if (daysCount < 0)
                                                                              throw new ArgumentException("daysCount");

                                                                            string days = daysCount.ToString();
                                                                            string daysCase = "дней";
                                                                            char lastChar = days[days.Length - 1];

                                                                            if (days.Length >= 2 && days[days.Length - 2] == '1')
                                                                            {
                                                                              // 10-19 дней
                                                                            }
                                                                            else if (lastChar == '1')
                                                                            {
                                                                              // 1, 21, 31 день
                                                                              daysCase = "день";
                                                                            }
                                                                            else if (lastChar == '2' || lastChar == '3' || lastChar == '4')
                                                                            {
                                                                              // 2, 3, 4, 22, 33, 44 дня
                                                                              daysCase = "дня";
                                                                            }
                                                                            
                                                                            return string.Format("{0} {1}", days, daysCase);
                                                                          }


                                                                      * This source code was highlighted with Source Code Highlighter.
                                                                      +1
                                                                      function declOfNum( $number, $titles ) {
                                                                      $cases = array( 2, 0, 1, 1, 1, 2 );
                                                                      return $titles[ ( $number % 100 > 4 && $number % 100 < 20 ) ? 2 : $cases[ mi
                                                                      n( $number % 10, 5 ) ] ];
                                                                      }

                                                                        +13
                                                                        И это на главной Хабра?! :(
                                                                          +6
                                                                          И не в первый раз.
                                                                          +3
                                                                          Не забудьте отдельно обработать случай, для числа 0. Просто написать, например, «0 записей» не достаточно. Нужно как минимум написать «Записей нет» или изменить дизайн, скрыв вообще пустой блок с записями.


                                                                          Вот удивляет меня эта тяга к максимальному очеловечиванию интерфейса в ущерб удобству. Когда я ищу взглядом на странице количество записей (комментариев, еще чего-нибудь), я ищу число. Числа выделяются посреди текста, по ним можно ориентироваться. Заменяя число текстом, меня лишают этой возможности. Даже когда я привыкну к интерфейсу и буду знать, в каком именно месте отображается количество записей, я всё равно буду спотыкаться взглядом об это «Записей нет», ожидая увидеть цифру на месте количества.

                                                                          Скрывать пустой блок с записями — тоже не самое разумное решение. Есть возможность, что я буду долго блуждать по странице, пытаясь понять «где же, блин, тут записи должны отображаться». А когда запись появится, меня неприятно удивит блок, внезапно вылезший неизвестно откуда и нарушивший картину, к которой я только успел привыкнуть.
                                                                            0
                                                                            Простым пользователям, считающим компьютеры магией, проще понять, что «чего-то нет», чем понять «обнаружено 0 чего-то». Почитайте разговоры этих самых пользователей с техподдержкой (:
                                                                            Я за разделение интерфейсов для «крутых» и обычных пользователей. Крутым — «0 записей в базе», обычным — «ничего не найдено». По умолчанию интерфейс делать «обычным», в настройки кинуть галочку — «интерфейс для опытных пользователей» или «расширенный интерфейс».
                                                                              0
                                                                              где почитать? они прямо так и пишут «у вас написано что найдено 0 записей, но ни одной записи ведь не найдено?!?! а-а-а, у меня кипит мозг!»?

                                                                              ноль проходят в начальной школе. ЛЮБОЙ человек его понимает. даже совсем отсталый. а возмущает это лишь числоненавистников.
                                                                            0
                                                                            Давно пользуюсь вот этой функцией luchinsky.ru/coding/lexica/
                                                                            Написана для php и легко портируется на js
                                                                              0
                                                                              Неплохо бы в такие функции добавлять опциональный вариант на случай, если число — 0. Чтобы не писать «0 ответов», а «Ответов нет».
                                                                                0
                                                                                Ответы есть, и их 12 :)
                                                                                0
                                                                                Накатал за пару часиков. Правила конечно же не все заложил, да и это работа не одного дня. Но все же: dvx-dev.ru/plural.php
                                                                                Вводим слово в именительном падеже (в ед. числе, разумеется). Можно добавить прилагательное впереди.
                                                                                  0
                                                                                  forum.dklab.ru/viewtopic.php?t=6760 заметьте, теме уже 7 лет как…
                                                                                    +7
                                                                                    php-ника хлебом не корми, дай написать велосипед.

                                                                                    а ведь еще в двадцатом веке люди придумали gettext
                                                                                      0
                                                                                      на геттексте свет клином не сошёлся. тем более что ngettext не поддерживает русский язык.
                                                                                        0
                                                                                        >ngettext не поддерживает русский язык.

                                                                                        в php какой-то особый ngettext, в котором традиционно ничего не работает?
                                                                                          0
                                                                                          самый обычный. в качестве параметров она везде принимает лишь 2 формы. писать на английском языке приложение рассчитанное прежде всего на русскоязычную аудиторию, а потом отдавать переводчикам переводить обратно на русский — весьма глупо.
                                                                                            0
                                                                                            >писать на английском языке приложение рассчитанное прежде всего на русскоязычную аудиторию, а потом отдавать переводчикам переводить обратно на русский — весьма глупо.

                                                                                            это проблемы вашего техпроцесса, а не функции gettext.

                                                                                            можно задать произвольное количество plural forms и формулу их выбора. в русском языке их три и ngettext прекрасно это обрабатывает
                                                                                              0
                                                                                              у нас нет проблем с техпроцессом именно потому что геттекст не используется

                                                                                              char * ngettext (const char * msgid, const char * msgid_plural, unsigned long int n);

                                                                                              где ты тут узрел 3 формы? или предлагаешь писать в коде 2 формы, а третюю пусть приписывают переводчики?
                                                                                                0
                                                                                                >или предлагаешь писать в коде 2 формы, а третюю пусть приписывают переводчики?

                                                                                                это можно сделать дофигищей разных способов без оверхеда с двойным переводом. тот же .po файл для русского языка можно генерировать по исходному коду.

                                                                                                тем не менее, писать в коде русские сообщения и завязываться на фиксированное количество plural forms — моветон, ящитаю.
                                                                                                  0
                                                                                                  каким таким образом можно генерировать по исходному коду русские тексты, если в исходниках можно указать лишь 2 формы?

                                                                                                  то есть завязываться на английский и 2 формы — это нормально, а на русский и 3 формы — моветон? х)
                                                                                                    0
                                                                                                    >каким таким образом можно генерировать по исходному коду русские тексты, если в исходниках можно указать лишь 2 формы?

                                                                                                    напрягите фантазию

                                                                                                    >то есть завязываться на английский и 2 формы — это нормально, а на русский и 3 формы — моветон?

                                                                                                    у меня резист от троллинга на плюс пицот.

                                                                                                    вариант «английский в коде, русский в .po файле» — определенно лучше «русский в коде, что делать дальще — непонятно»
                                                                                                      0
                                                                                                      резист от интеллекта у тебя тоже зашкаливает…
                                                                                                  0
                                                                                                  ну то есть как локальное решение, чисто для русского языка — можно и свой велосипед, но это именно непортабельное локальное решение — по сути, грязный хак, мешающий нормальной локализации.
                                                                                        –4
                                                                                        Супер, спасибо автору! Давно мечтал о таком решении (перенес в свои проекты).
                                                                                          –4
                                                                                          А вообще, автор, НА ХУЙ С ХАБРА!
                                                                                            +2
                                                                                            «Вон из профессии!» =)
                                                                                              –5
                                                                                              Откуда у него такая карма, господи?! На чем он поднялся?
                                                                                                +3
                                                                                                залоговые аукционы
                                                                                              +1
                                                                                              за что вы его так?
                                                                                            +1
                                                                                            Наверное, англо-язычные роботы не палились.
                                                                                              0
                                                                                              Вот в одну строчку
                                                                                              public static function make($n, $form_1, $form_2, $form_3){
                                                                                              return ($n%10==1&&$n%100!=11? $form_1 :($n%10>=2&&$n%10<=4&&($n%100<10||$n%100>=20)? $form_2: $form_3));
                                                                                              }
                                                                                                +1
                                                                                                угу. А теперь попробуем просклонять:
                                                                                                1 год; 2,3,4 года; 5,6,7,8,9 лет

                                                                                                И таких слов-исключений попадается немало
                                                                                                  0
                                                                                                  напишите пожалуйста какие еще есть
                                                                                                    0
                                                                                                    Ну, к примеру, практически все существительные у которых есть множественная форма.
                                                                                                    ножницы (одни, двое, трое ножниц, ножницы, 4, 5 шт, шестеро, семеро, восемь, девять ножниц),
                                                                                                    торги
                                                                                                    хлопья (хлопья кукурузные, 3 шт, но не трое хлопьев или три хлопья)
                                                                                                  0
                                                                                                  И я добавлю :)
                                                                                                  
                                                                                                  Ext.util.Format.wformat = function(number,forms){
                                                                                                          var n = parseInt(number,10);
                                                                                                          var nplurals=3;
                                                                                                          var plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);
                                                                                                          plural = (n==0)?0:plural + 1;
                                                                                                          if(forms.length > plural) return forms[plural].replace("$",n);
                                                                                                          return "";
                                                                                                      }
                                                                                                  

                                                                                                  Собственно эта функция помещена в обьект Ext.util.Format для того чтобы ее можно было использовать в Шаблонах ExtJs вот так
                                                                                                  
                                                                                                  var tpl = new Ext.XTemplate('{year:(["нет","Один год","$ года","$ лет"])}');
                                                                                                  

                                                                                                  Ну и конечно не кто не заприщает ее использовать и без шаблонов :)
                                                                                                    +1
                                                                                                    И никто, никто не вспомнил про ngettext(), который старше большинства из нас. И прямо в википедии есть уже готовая реализация кода.
                                                                                                      0
                                                                                                      Кстати, а его всегда на хостингах включают?
                                                                                                      Что касаемо вообще gettext, останавливает от его использования формат библиотек, усложнённо как-то всё, с компилированием.
                                                                                                        0
                                                                                                        >останавливает от его использования формат библиотек, усложнённо как-то всё, с компилированием.

                                                                                                        вот он — php-way! проще навелосипедить, чем разобраться в стандартном решении.

                                                                                                        >с компилированием.

                                                                                                        конечно с компилированием, не парсить же каталог при каждом вызове, CPU time на шаред-хостинке халявный, да?
                                                                                                          0
                                                                                                          Стандартное решение не всем и не всегда подходит, зачастую велосипед лучше.
                                                                                                            0
                                                                                                            причины «объективно не подходит» и «проще навелосипедить» — немного разные.
                                                                                                              0
                                                                                                              Невозможность работы решения на хостинге, где PHP собран без поддержки gettext — для меня лично более чем объективная причина. Проще и, я думаю, правильнее, дать пользователю возможность работать с CMS (например) не заставляя его переписываться с хостером (а сперва он напишет разработчику, или просто плюнет на «не рабочее» решение).
                                                                                                      0
                                                                                                        0
                                                                                                        У Яндекса есть публичный сервис — "Склонятор". Возможно, в данном случае это как стрельба из пушки по воробьям, но, надеюсь, кому-нибудь прочитавшему это пригодится.
                                                                                                        Думается мне, можно написать под этот сервис универсальную обёртку на java.
                                                                                                          0
                                                                                                          Примерно такую? pipes.yahoo.com/pipes/pipe.info?_id=sBgMDDk93hGMjlvuPm7D0g
                                                                                                          Правда, на самом деле какое-то глупое использование «Склонятора».
                                                                                                            0
                                                                                                            Во-первых, это у меня не работает, во-вторых, я имел в виду вывод текста, связанного с числительными, автоматом через склонятор. То есть, например, брать в некую обёртку число и существительное, а выдавать и то и то в нужной форме — не через формы ввода, а прямо на генерируемой странице.
                                                                                                              0
                                                                                                              Я не проверял, просто вспомнил. Скорей всего Яндекс забанил доступ к своим сервисам от IP Yahoo Pipes, потому и сломалось. А там вроде как раз так и было сделано.
                                                                                                          0
                                                                                                          У меня так:

                                                                                                          /**
                                                                                                           * Функция склонения числительных в русском языке
                                                                                                           *
                                                                                                           * @param int    $number Число которое нужно просклонять
                                                                                                           * @param array  $titles Массив слов для склонения
                                                                                                           * @return string
                                                                                                           **/
                                                                                                          function DigitCase($number, $titles)
                                                                                                          {
                                                                                                          	//отсекаем минусовые значения
                                                                                                          	$number = str_replace("-","",$number);
                                                                                                                  $cases = array (2, 0, 1, 1, 1, 2);
                                                                                                                  return $titles[ ($number%100>4 && $number%100<20)? 2 : $cases[min($number%10, 5)] ];
                                                                                                          }
                                                                                                          
                                                                                                            0
                                                                                                            Use the ngettext, Luke!
                                                                                                              +1
                                                                                                              Вот вам чуть ли не для всех языков сразу: github.com/alien/ali-public/blob/master/samples/Ali/I18N/PluralForms.pm
                                                                                                                0
                                                                                                                На жабе для склонения дней:
                                                                                                                public class Misc {
                                                                                                                  public static String getDayText (int numberOfDays) {
                                                                                                                    int x;
                                                                                                                    x = numberOfDays % 100;
                                                                                                                    if (> 20) {
                                                                                                                       x = x % 10;
                                                                                                                    }
                                                                                                                    if (> 4) {
                                                                                                                       return "дней";
                                                                                                                    } else {
                                                                                                                       switch (x) {
                                                                                                                         case 1: return "день";
                                                                                                                         case 0: return "дней";
                                                                                                                         default: return "дня";
                                                                                                                       }
                                                                                                                    }
                                                                                                                }
                                                                                                                  0
                                                                                                                  Случайно наткнулся на тему, дополню на Obj-C:

                                                                                                                  -(NSString *)units:(int)num cases:(NSArray *)cases {
                                                                                                                  num = abs(num);
                                                                                                                  NSString *word = @"";
                                                                                                                  word = (
                                                                                                                  num % 10 == 1 && num % 100 != 11
                                                                                                                  ? [cases objectAtIndex:0]
                                                                                                                  : num % 10 >= 2 && num % 10 <= 4 && (num % 100 < 10 || num % 100 >= 20)
                                                                                                                  ? [cases objectAtIndex:1]
                                                                                                                  : [cases objectAtIndex:2]
                                                                                                                  );

                                                                                                                  return word;
                                                                                                                  }
                                                                                                                    0
                                                                                                                    Интересный подход. Теперь буду везде использовать это. Раньше в основном писал с ограничением до 10 обычным switch/case.
                                                                                                                      0
                                                                                                                      Пример под Python, если нужно кому-то, конечно.

                                                                                                                      number = 129
                                                                                                                      
                                                                                                                      word = ["комментарий", "комментария", "комментариев"]
                                                                                                                      
                                                                                                                      def get_com(x, y):
                                                                                                                          inumber = x % 100
                                                                                                                          if inumber >= 11 and inumber <=19:
                                                                                                                              y = y[2]
                                                                                                                          else:
                                                                                                                              iinumber = inumber % 10
                                                                                                                              if iinumber == 1:
                                                                                                                                  y = y[0]
                                                                                                                              elif iinumber == 2 or iinumber == 3 or iinumber == 4:
                                                                                                                                  y = y[1]
                                                                                                                              else:
                                                                                                                                  y = y[2]
                                                                                                                          print(x, y)
                                                                                                                      
                                                                                                                      get_com(number, word)
                                                                                                                      

                                                                                                                        0
                                                                                                                        С обработчиком числа 0 и выводом Числа, пробела, слова

                                                                                                                        def get_com(x, y):
                                                                                                                            if x == 0:
                                                                                                                                return "нет комментариев"
                                                                                                                            else:
                                                                                                                                inumber = x % 100
                                                                                                                                if inumber >= 11 and inumber <=19:
                                                                                                                                    y = y[2]
                                                                                                                                else:
                                                                                                                                    iinumber = inumber % 10
                                                                                                                                    if iinumber == 1:
                                                                                                                                        y = y[0]
                                                                                                                                    elif iinumber == 2 or iinumber == 3 or iinumber == 4:
                                                                                                                                        y = y[1]
                                                                                                                                    else:
                                                                                                                                        y = y[2]
                                                                                                                                return str(x) + " " + y
                                                                                                                        
                                                                                                                        0
                                                                                                                        http://www.unicode.org/cldr/charts/29/supplemental/language_plural_rules.html
                                                                                                                        сразу для всех-всех языков, с формулами

                                                                                                                        Only users with full accounts can post comments. Log in, please.