Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

177 lines (146 sloc) 6.116 kb
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
# Program used to generate /packages/api-utils/lib/l10n/plural-rules.js
# Fetch unicode.org data in order to build functions specific to each language
# that will return for a given integer, its plural form name.
# Plural form names are: zero, one, two, few, many, other.
#
# More information here:
# http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html
# http://cldr.unicode.org/index/cldr-spec/plural-rules
# Usage:
# $ python plural-rules-generator.py > ../packages/api-utils/lib/l10n/plural-rules.js
import urllib2
import xml.dom.minidom
import json
import re
PRINT_CONDITIONS_IN_COMMENTS = False
UNICODE_ORG_XML_URL = "http://unicode.org/repos/cldr/trunk/common/supplemental/plurals.xml"
CONDITION_RE = r'n( mod \d+)? (is|in|within|(not in))( not)? ([^\s]+)'
# For a given regexp.MatchObject `g` for `CONDITION_RE`,
# returns the equivalent JS piece of code
# i.e. maps pseudo conditional language from unicode.org XML to JS code
def parseCondition(g):
lvalue = "n"
if g.group(1):
lvalue = "(n %% %d)" % int(g.group(1).replace("mod ", ""))
operator = g.group(2)
if g.group(4):
operator += " not"
rvalue = g.group(5)
if operator == "is":
return "%s == %s" % (lvalue, rvalue)
if operator == "is not":
return "%s != %s" % (lvalue, rvalue)
# "in", "within" or "not in" case:
notPrefix = ""
if operator == "not in":
notPrefix = "!"
# `rvalue` is a comma seperated list of either:
# - numbers: 42
# - ranges: 42..72
sections = rvalue.split(',')
if ".." not in rvalue:
# If we don't have range, but only a list of integer,
# we can simplify the generated code by using `isIn`
# n in 1,3,6,42
return "%sisIn(%s, [%s])" % (notPrefix, lvalue, ", ".join(sections))
# n in 1..42
# n in 1..3,42
subCondition = []
integers = []
for sub in sections:
if ".." in sub:
left, right = sub.split("..")
subCondition.append("isBetween(%s, %d, %d)" % (
lvalue,
int(left),
int(right)
))
else:
integers.append(int(sub))
if len(integers) > 1:
subCondition.append("isIn(%s, [%s])" % (lvalue, ", ".join(integers)))
elif len(integers) == 1:
subCondition.append("(%s == %s)" % (lvalue, integers[0]))
return "%s(%s)" % (notPrefix, " || ".join(subCondition))
def computeRules():
# Fetch plural rules data directly from unicode.org website:
url = UNICODE_ORG_XML_URL
f = urllib2.urlopen(url)
doc = xml.dom.minidom.parse(f)
# Read XML document and extract locale to rules mapping
localesMapping = {}
algorithms = {}
for index,pluralRules in enumerate(doc.getElementsByTagName("pluralRules")):
if not index in algorithms:
algorithms[index] = {}
for locale in pluralRules.getAttribute("locales").split():
localesMapping[locale] = index
for rule in pluralRules.childNodes:
if rule.nodeType != rule.ELEMENT_NODE or rule.tagName != "pluralRule":
continue
pluralForm = rule.getAttribute("count")
algorithm = rule.firstChild.nodeValue
algorithms[index][pluralForm] = algorithm
# Go through all rules and compute a Javascript code for each of them
rules = {}
for index,rule in algorithms.iteritems():
lines = []
for pluralForm in rule:
condition = rule[pluralForm]
originalCondition = str(condition)
# Convert pseudo language to JS code
condition = rule[pluralForm].lower()
condition = re.sub(CONDITION_RE, parseCondition, condition)
condition = re.sub(r'or', "||", condition)
condition = re.sub(r'and', "&&", condition)
# Prints original condition in unicode.org pseudo language
if PRINT_CONDITIONS_IN_COMMENTS:
lines.append( '// %s' % originalCondition )
lines.append( 'if (%s)' % condition )
lines.append( ' return "%s";' % pluralForm )
rules[index] = "\n ".join(lines)
return localesMapping, rules
localesMapping, rules = computeRules()
rulesLines = []
for index in rules:
lines = rules[index]
rulesLines.append('"%d": function (n) {' % index)
rulesLines.append(' %s' % lines)
rulesLines.append(' return "other"')
rulesLines.append('},')
print """/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// This file is automatically generated with /python-lib/plural-rules-generator.py
// Fetching data from: %s
// Mapping of short locale name == to == > rule index in following list
const LOCALES_TO_RULES = %s;
// Utility functions for plural rules methods
function isIn(n, list) list.indexOf(n) !== -1;
function isBetween(n, start, end) start <= n && n <= end;
// List of all plural rules methods, that maps an integer to the plural form name to use
const RULES = {
%s
};
/**
* Return a function that gives the plural form name for a given integer
* for the specified `locale`
* let fun = getRulesForLocale('en');
* fun(1) -> 'one'
* fun(0) -> 'other'
* fun(1000) -> 'other'
*/
exports.getRulesForLocale = function getRulesForLocale(locale) {
let index = LOCALES_TO_RULES[locale];
if (!(index in RULES)) {
console.warn('Plural form unknown for locale "' + locale + '"');
return function () { return "other"; };
}
return RULES[index];
}
""" % (UNICODE_ORG_XML_URL,
json.dumps(localesMapping, sort_keys=True, indent=2),
"\n ".join(rulesLines))
Jump to Line
Something went wrong with that request. Please try again.