среда, 20 августа 2008 г.

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

В английском языке все просто: 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 1 && $n1

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

private Integer plurals(Long n){
if (n==0) return 0;
n = Math.abs(n) % 100;
Long n1 = n % 10;
if (n > 10 && n 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={0 2,choice,0#|0<{0,number,'#.###'} лет|1#{0,number,integer} год|1<{0,number,'#.###'} года|5#{0,number,'#.###'} лет} {1 3,choice,0#|0<{1,number,'#.###'} месяцев|1#{1,number,integer} месяц|1<{1,number,'#.###'} месяца|5#{1,number,'#.###'} месяцев}

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

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

Отправить комментарий