В английском языке все просто: 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, который обсуждался на Хабре и был обозван изобретанием велосипеда. Я, как заядлый велосипедист, не мог не изобрести свой :)
Этот код был переведен в метод класса HourFormatter, который у нас и занимается разбором человеко-лет.
Возвращает он, однако, не «писем нет», а числа. Потому как ChoiceFormat работает с числами, а не письмами.
Вот исходная проперть в language_ru.properties:
Она же в language_en.properties:
Теперь нужно как-то использовать метод plurals для вывода правильной формы так, чтобы английская форма не пострадала.
Идея состоит в том, чтобы передавать результаты вычисления plurals добавочными параметрами
А проперть для русского языка изменить следующим образом:
Вот, собственно, и все.
UPD. Как оказалось — не все.
Мне подсказали, что есть еще библиотека GNU gettext (реализованная в том числе и для Java), которая умеет обращаться с существительными во множественном числе. Еще есть Gettext Commons, где достаточно подробно расписано, как все прикрутить к имеющемуся коду.
Библиотека имеет интересные возможности:
Правда я так и не нашел, как же выполнить исходную задачу с форматированием времени.
«Русская языка» такая сложная, что в ней существительные после числительных иногда бывают в причудливых формах. Например, 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"));
Правда я так и не нашел, как же выполнить исходную задачу с форматированием времени.