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