/*
* plural word form chooser for i18n
*
* Copyright (c) 1998,2016 by Alexander V. Lukyanov (lav@yars.free.net)
*
* This file is in public domain.
*/
/*
* This file provides a function to transform a string with all plural forms
* of a word to a string with concrete form depending on a number.
* It uses a rule encoded in special string.
*/
/* TODO:
* allow to select number of argument
*/
#include <config.h>
#include <string.h>
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include "plural.h"
static int choose_plural_form(const char *rule,int num)
{
int res=0;
int match=1;
int n=num;
char c;
int arg,arg_len;
for(;;)
{
switch(c=*rule)
{
case '=':
case '!':
case '>':
case '<':
case '%':
if(sscanf(rule+1,"%d%n",&arg,&arg_len)<1)
return -1;
rule+=arg_len;
if(c=='%')
n%=arg;
if((c=='=' && !(n==arg))
|| (c=='!' && !(n!=arg))
|| (c=='>' && !(n> arg))
|| (c=='<' && !(n< arg)))
match=0;
break;
case '|':
case ' ':
case '\0':
if(match)
return res;
if(c=='\0')
return res+1;
if(c=='|')
match=1;
if(c==' ') /* next rule */
{
n=num;
res++;
match=1;
}
break;
}
rule++;
}
/* return res;*/
}
/*
* The function takes _untranslated_ string with $form1|form2|form3...$
* inserts, and a list of integer numbers. It uses gettext on the string.
* Using "translated" rule and the list of numbers it choose appropriate
* plural forms.
*
* If the string or the rule cannot be translated, it uses untranslated
* string and default (english) rule.
*
* Returns pointer to static storage, copy if needed.
*/
const char *plural(const char *format,...)
{
static char *res=0;
static size_t res_size=0;
/* This is the rule for plural form choice (last condition can be omitted) */
/* Operators: = > < ! | % */
const char *rule=N_("=1 =0|>1");
const char *s;
char *store;
int index,plural_index;
const char *new_format=gettext(format);
const char *new_rule=gettext(rule);
va_list va;
long arg;
va_start(va,format);
if(new_format!=format && new_rule!=rule) /* there is translation */
{
rule=new_rule; /* use "translated" rule */
format=new_format; /* and translated format */
}
if(res==0)
res=malloc(res_size=256);
if(res==0)
goto va_end_out;
store=res;
for(s=format; *s; s++)
{
#define ALLOCATE \
if(store-res+1>=res_size) \
{ \
int dif=store-res; \
res=realloc(res,res_size*=2); \
if(res==0) \
goto va_end_out; \
store=res+dif; \
} /* end ALLOCATE */
if(*s=='$' && s[1])
{
s++;
if(*s=='$')
goto plain;
/* check options */
if(*s=='#')
{
s++;
if(*s=='l') /* long */
{
s++;
if(*s=='l') /* long long */
{
s++;
arg=va_arg(va,long long)%1000000;
}
else
arg=va_arg(va,long);
}
else
{
arg=va_arg(va,int);
}
if(*s=='#') /* end of options */
s++;
}
else
{
arg=va_arg(va,int);
}
if(arg<0)
arg=-arg;
plural_index=choose_plural_form(rule,arg);
index=0;
while(index!=plural_index)
{
/* skip plural form */
while(*s!='$' && *s!='|' && *s)
s++;
if(*s==0)
goto out;
if(*s=='$')
break;
s++;
index++;
}
if(index==plural_index)
{
/* insert the form */
while(*s!='$' && *s!='|' && *s)
{
ALLOCATE;
*store++=*s++;
}
while(*s!='$' && *s)
s++;
if(*s==0)
goto out;
}
}
else
{
plain:
ALLOCATE;
*store++=*s;
}
}
out:
ALLOCATE;
*store=0;
va_end_out:
va_end(va);
return res;
}