Pull to refresh

Склонение существительных с числительными

Reading time 3 min
Views 34K
В английском языке все просто: 1 year, 2 years ,… N years
«Русская языка» такая сложная, что в ней существительные после числительных иногда бывают в причудливых формах. Например, 0 лет, 1 год, 2 года, 3 года, 4 года, 5 лет, ..., 11 лет, 12 лет, .., 21 год.


Нам нужно было выводить бюджеты для задач в различных формах. Например, бюджет проекта может быть выражен как 1.5 года, а бюджет задачи как 12 часов или как 20 минут. С часами и минутами прокатывала, если зажмуриться, форма 50 минут(ы), но нельзя же записать 1.5 год(а, лет).

Для форматирования вывода в Java есть чудесный класс java.text.ChoiceFormat, который в строковом виде выглядит примерно так: "{0,choice,0#{0,number,integer} years|1#{0,number,integer} year|1<{0,number,integer} years}". Т.е. ChoiceFormat просто задает «лимиты» для параметра. В какой диапазон параметр попадает, тот формат и используется.
Для русского языка такой подход неприменим (см. пример выше). Что же делать? Писать свой MessageFormat и ChoiceFormat? Классы весьма сложные и содержат вызовы методов классов, доступных только в package java.text. Да и вообще делать специальную обработку отдельно для русского языка не хотелось бы. Мало ли на свете языков с аналогичными проблемами?

Методом проб, ошибок и тяжких дум получилось следующее решение:

На сайте Xpoint.ru был найдет нижеследующий код на PHP, который обсуждался на Хабре и был обозван изобретанием велосипеда. Я, как заядлый велосипедист, не мог не изобрести свой :)

<?
function pluralForm($n, $form1, $form2, $form5)
{
$n = abs($n) % 100;
$n1 = $n % 10;
if ($n > 10 && $n < 20) return $form5;
if ($n1 > 1 && $n1 < 5) return $form2;
if ($n1 == 1) return $form1;
return $form5;
}

// пример использования
echo "В Вашем почтовом ящике $n ".pluralForm($n, "письмо", "письма", "писем");
?>


Этот код был переведен в метод класса HourFormatter, который у нас и занимается разбором человеко-лет.

private Integer plurals(Long n){
if (n==0) return 0;
n = Math.abs(n) % 100;
Long n1 = n % 10;
if (n > 10 && n < 20) return 5;
if (n1 > 1 && n1 < 5) return 2;
if (n1 == 1) return 1;
return 5;
}


Возвращает он, однако, не «писем нет», а числа. Потому как ChoiceFormat работает с числами, а не письмами.

Вот исходная проперть в language_ru.properties:

BUDGET_FORMAT={0,choice,0#|0<{0,number,'#.###'} лет|1#{0,number,integer} год|1<{0,number,'#.###'} года|5#{0,number,'#.###'} лет} {1,choice,0#|0<{1,number,'#.###'} месяцев|1#{1,number,integer} месяц|1<{1,number,'#.###'} месяца|5#{1,number,'#.###'} месяцев}

Она же в language_en.properties:

BUDGET_FORMAT={0,choice,0#|0<{0,number,'#.###'} years|1#{0,number,integer} year|1<{0,number,'#.###'} years {1,choice,0#|0<{1,number,'#.###'} months|1#{1,number,integer} month|1<{1,number,'#.###'} months


Теперь нужно как-то использовать метод plurals для вывода правильной формы так, чтобы английская форма не пострадала.
Идея состоит в том, чтобы передавать результаты вычисления plurals добавочными параметрами

public String getString() {
// специально для русского языка:
Integer year = plurals(getYears().intValue());
Integer month = plurals(getMonths().intValue());
return I18n.getString(this.locale, "BUDGET_FORMAT", new Object[]{getYears(), getMonths(), year, month});
}


А проперть для русского языка изменить следующим образом:

BUDGET_FORMAT={ 2,choice,0#|0<{0,number,'#.###'} лет|1#{0,number,integer} год|1<{0,number,'#.###'} года|5#{0,number,'#.###'} лет} { 3,choice,0#|0<{1,number,'#.###'} месяцев|1#{1,number,integer} месяц|1<{1,number,'#.###'} месяца|5#{1,number,'#.###'} месяцев}


Вот, собственно, и все.

UPD. Как оказалось — не все.

Мне подсказали, что есть еще библиотека GNU gettext (реализованная в том числе и для Java), которая умеет обращаться с существительными во множественном числе. Еще есть Gettext Commons, где достаточно подробно расписано, как все прикрутить к имеющемуся коду.

Библиотека имеет интересные возможности:
  • есть инструмент для извлечения текста из кода (правда сначала рекомендуется все привести в MessageFormat).
  • ключами при этом становится первоначальный текст
  • Есть возможность разделять, например, глаголы и существительные, если они в языке имеют одинаковую форму.

System.out.println(i18n.trc("chat (verb)", "chat"));
System.out.println(i18n.trc("chat (noun)", "chat"));


Правда я так и не нашел, как же выполнить исходную задачу с форматированием времени.

Tags:
Hubs:
+8
Comments 20
Comments Comments 20

Articles