

Created by Peter Kankowski
Last changed
Filed under Algorithms

Share on social sitesReddit Digg Delicious Buzz Facebook Twitter

Plural forms



    0 plików
    1 plik
  2-4 pliki
 5-21 plików
22-24 pliki
25-31 plików


The gettext library extracts a rule for plural form selection from the localization file. The rule is a C language expression, which is evaluated for each message. It's a universal solution, but an expression evaluator is probably an overkill for this task.

Simpler solution

Here are some observations about the languages mentioned on gettext page:

  • The pattern is often repeated for each 10 or 100 items. In Russian, it sounds like "twenty-one file", not "twenty-one files", because the noun is agreed with the last figure, "one". The same pattern repeats for 30, 40, etc.


range_start  range_end  modulo_for_repetition  skip_teens_flag

Here are some examples:

singular:  range_start = 1, range_end = 1
plural:    all other numbers

singular:  range_start = 1, range_end = 1
plural1:   range_start = 2, range_end = 4, modulo = 10, skip_teens = true
plural2:   all other numbers

singular:  range_start = 1, range_end = 1
plural1:   range_start = 2, range_end = 2
plural2:   all other numbers

singular:  range_start = 1, range_end = 1, modulo = 10, skip_teens = true
plural1:   range_start = 2, range_end = 9, modulo = 10, skip_teens = true
plural2:   all other numbers (from 10 to 19)


Using the Code

Include plurals.h and plurals.c in your project. The interface consists of two functions. First, you call PluralsReadCfg to read rules from the string. Next, you pass a number to PluralsGetForm. It returns the index of correct plural form for this number, which you use to read the string from your language file:

PLURAL_INFO plurals;

char lng_str_name[16], message[128];
sprintf(message, ReadFromLngFile(lng_str_name), number);


PluralRules = "1"
FilesFound0 = "%d file found"
FilesFound1 = "%d files found"


Even better solution is implementing a custom formatting function, so you could write something like "%d %(file|files) found" in the language file. Scott Rippey devised this technique and implemented it in VB .NET.


Two functions, PluralsReadCfg and PluralsGetForm, take 500 bytes in your executable file when compiled with MSVC++. A small price to pay for spelling your messages correctly in any language.

Download the source code (25 KB, MSVC++)

Peter Kankowski
Peter Kankowski

About the author


Created by Peter Kankowski
Last changed


Ten recent comments are shown below. Show all comments

Peter Kankowski,

Wow, RPN! :) But for this ad hoc evaluator, a programming language with eval function and syntax close to C would be a reasonable choice. For example, in JavaScript:

eval('var f = function (n) { return ' + s + '}');
var a = [];
for(var n = 0; n < 100000; n++)
	a[n] = f(n);

It's very fast (2 milliseconds in Firefox on my machine), because the expression is evaluated only once.

I will try to explain your algorithm as I understood it (if something is wrong, please correct me).

A) If the formula length is 20 figures or less, it's a simple lookup table for each number, e.g. for Irish it's "2012", which means "the 2nd form for zero, the 0th form for one, the 1st form for two, and the 2nd one for three or more". The last digit is used for all numbers greater than the formula length. These rules cover Western-European languages.

B) Romanian and Slovenian have a special pattern for the numbers from 0 to 99. Another pattern is used for numbers from 100 to 199, from 200 to 299, and so on (modulo 100). You encode the first pattern (exactly 20 characters), then the second pattern (its length must not be divisible by 10). The last digit is used for larger numbers, again.

C) Russian, Polish, and Baltic languages are similar to B), but the second pattern is also repeated from 20 to 29, from 30 to 39, from 40 to 49, etc. (modulo 10). The function applies this rule if the formula length is divisible by 10. It's easy to pad it to 40 characters with the repeating last digit.

Generally, it's a very clever hack. Thank you so much for sharing!



You are right, but initial development reason was a simple localization framework for Delphi (not JavaScript). I start from classic expression parser and finished with Formula.

>It's very fast (2 milliseconds in Firefox on my machine)

Interesting that Firefox don't use this "dynamic evaluation" method in its plugins system (JavaScript-based), but a lot of predefined functions for every language:



>I will try to explain your algorithm as I understood it

You are correct, but I'm not a linguist and in my mind was not any languages, plural forms and so on. I started thinking about 3 arrays (20 numbers from first hundred, 10 for decades loop, and exclusions for *10..*19) and flags (loop every 10, every 100, etc), then I combine all arrays in one string, use tail reduction and embed flags in string size.

By the way your method is similar (using condition to test each plural form) to Plural rules standart:


Peter Kankowski,

Thank you for the links. Note that Unicode and Mozilla have different rules for some languages. I asked them which ones are correct.

It's impressive how you started from the classic expression evaluator (similar to gettext) and came up with the smartly reduced lookup tables. Your formulas reminds me of the spellchecker by Doug McIlroy, who compressed 75 000 English words into 65 536 bytes of memory using a sparse hash table (described in Programming Pearls).

What license do you have for this code? May other people use it? May be, you would like to provide your full name to be credited in documentation / about box.



When testing formulas I found same weird things: expression for some languages presented in many different forms (even if you use reputable sources like W3C). Winner is Arabic: six different expressions and not only algorithms are differ, but even a count of plural forms!

>What license do you have for this code?

I didn't think that the ten lines of code may have any license ;-) But if you want some legal crap...



Peter, you mentioned McIlroy's spellchecker. If you're interested, his original source code is here:


(both methods mentioned in the paper - the bloom filter and the differential huffman hash)

Peter Kankowski,

NRO, thank you very much!


WARNING! The formula for Lithuanian "2011111111222222222220111111112222222222" seems to be wrong for numbers above 30.

The true algorithm for Lithuanian number could be written as:

uint8 Lithuanian_Number_Form (uint32 n)
  uint32 tmp;

	tmp = n % 100;
	if ((tmp >= 11) && (tmp <= 19))	// at first decode "teens"
		return 2;	// for n: 11-19, 111-119, 211-219, ... 911-919, 1011-1019, 1111-1119, ... 1911-1919, ...
	tmp = tmp % 10;
	if (tmp == 0)
		return 2;	// for n: 0, 10, 20, ... 90, 100, 110, 120, ...
	if (tmp == 1)
		return 0;	// for n: 1, NOT(11)!, 21, 31, 41, ... 91, 101, NOT(111)!, 121, 131, ... 991, 1001, NOT(1011)!, 1021, 1031, ... 1091, ...
	return 1;	// for n: 2-9, NOT(12-19)!, 22-29, 32-39, ... 92-99, 102-109, NOT(112-119)!, 122-129, 132-139, ... 192-199, ..., 1002-1009, NOT(1012-1019)!, 1022-1029

Peter Kankowski,

Niblis, it's the same algorithm. What are the exact numbers you have problems with? What do you expect to get and what do you get?


Peter, I am sorry. At first glance I thougt that characters in formula[30 .. 39] are for the numbers 30 .. 39, but after debugging the procedure, I found out, that they are for all "tens" and "teens" for numbers above 100: n*100 + (10..19), where n > 0. Nice algorihtm.

Your name:


Please ignore this field: