Собственно, функция, принимающая 2 аргумента (первый из которых число, а второй - массив со словоформами), уже давно перестала быть диковинкой и есть в запаснике у многих. Например приблизительно в таком виде. Кстати, интересно, что эта задача почему-то традиционно называется "склонение числительных", хотя с числительным как раз ничего не делается, оно вообще выводится цифрами :)
Так вот, подход верный и работает, но беда в том, что таскать везде массив словоформ - это таки неудобно. Особенно если шаблонная система, где обычно и происходит вывод, не знает ничего про массивы. Ага, я про XSLT например :)
Кроме того, часто нужно строки доставать из какого-то хранилища данных для локализации ("словаря"), а механизм словарей в большинстве случаев рассчитывает, что в нем будут хранить строки, а не массивы.
Нижеследующий код умеет делать все то же самое, но вторым аргументом получает строку. Для наглядности, сначала пример работы.
$nums = array(1, 3, 5);
$strings = array(
'кенгуру', // не склоняется
'hour%s', // мн. ч. всегда одинаковое
'час%а%ов', // ед. ч. == основа
'комментари%й%я%ев', // ух, сколько вариантов!
'%ребенок%ребенка%детей' // начинается с %, берем "как есть"
);
foreach ( $strings as $str) {
foreach ($nums as $num) {
echo $num." ".decl($num, $str)."; ";
}
echo "<hr />";
}
Посмотреть вывод можно тут. Кстати, в этом примере видно, как функцию можно использовать для локализации - см. "часы" и "hours". Да, и массив вторым аргументом оно тоже поддерживает, если кому надо :)
А вот и сниппет со всей функцией:
function decl($num,$words) {
if (is_string($words)) {
$parts = explode("%", $words);
$r = array_shift($parts);
if ($r == '') {
$words = $parts;
if (!isset($words[2])) {
$words[2] = $parts[1];
}
} else {
$count_parts = count($parts);
// кенгуру
$words = array($r, $r, $r);
switch ($count_parts) {
case 1:
// hour%s
$words[1] .= $parts[0];
$words[2] .= $parts[0];
break;
case 2:
// час%а%ов
// глаз%а%
$words[1] .= $parts[0];
$words[2] .= $parts[1];
break;
case 3:
// комментари%й%я%ев
$words[0] .= $parts[0];
$words[1] .= $parts[1];
$words[2] .= $parts[2];
break;
}
}
}
$num=$num%100;
if ($num>19) {
$num=$num%10;
}
switch ($num) {
case 1:
return($words[0]);
case 2: case 3: case 4:
return($words[1]);
default:
return($words[2]);
}
}
Пользуйтесь невозбранно!
----
Заядлым игроманам (и просто любителям скоротать время, тупя в квестик) будет любопытно взглянуть на скриншоты final fantasy - весьма симпатичной игрушки.
function lexica($n,$a,$b,$c,$m=1)
{
$n=sprintf("%.0f",$n);
$x=abs($n>9?substr($n,-2):$n);
return ($m?($m==2?number_format($n,0,' ',' '):$n).' ':'').(($x%=100)>9&&$x<20||($x%=10)>4||$x==0?$c:($x==1?$a:$b));
}