author | Axel Hecht <axel@pike.org> |
Tue, 12 Sep 2017 11:27:49 +0200 | |
changeset 666992 | 478035c832f0d9fe767e186be6442160bf2aa392 |
parent 666884 | e4261f5b96ebfd63e7cb8af3035ff9fea90c74a5 |
child 666993 | 33402ceaadc8d22a545b2eff5d7d09174ef8e476 |
push id | 80582 |
push user | axel@mozilla.com |
push date | Tue, 19 Sep 2017 14:37:32 +0000 |
reviewers | stas |
bugs | 1382005 |
milestone | 57.0a1 |
--- a/build/virtualenv_packages.txt +++ b/build/virtualenv_packages.txt @@ -3,16 +3,17 @@ mozilla.pth:python/mozboot mozilla.pth:python/mozbuild mozilla.pth:python/mozlint mozilla.pth:python/mozversioncontrol mozilla.pth:third_party/python/blessings mozilla.pth:third_party/python/compare-locales mozilla.pth:third_party/python/configobj mozilla.pth:third_party/python/cram mozilla.pth:third_party/python/dlmanager +mozilla.pth:third_party/python/fluent mozilla.pth:third_party/python/futures mozilla.pth:third_party/python/hglib mozilla.pth:third_party/python/jsmin optional:setup.py:third_party/python/psutil:build_ext:--inplace mozilla.pth:third_party/python/psutil mozilla.pth:third_party/python/pylru mozilla.pth:third_party/python/which mozilla.pth:third_party/python/pystache
new file mode 100644 --- /dev/null +++ b/third_party/python/fluent/PKG-INFO @@ -0,0 +1,16 @@ +Metadata-Version: 1.1 +Name: fluent +Version: 0.4.2 +Summary: Localization library for expressive translations. +Home-page: https://github.com/projectfluent/python-fluent +Author: Mozilla +Author-email: l10n-drivers@mozilla.org +License: APL 2 +Description: UNKNOWN +Keywords: fluent,localization,l10n +Platform: UNKNOWN +Classifier: Development Status :: 3 - Alpha +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: Apache Software License +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3.5
new file mode 100644 --- /dev/null +++ b/third_party/python/fluent/fluent/migrate/__init__.py @@ -0,0 +1,10 @@ +# coding=utf8 + +from .context import MergeContext # noqa: F401 +from .transforms import ( # noqa: F401 + Source, COPY, REPLACE_IN_TEXT, REPLACE, PLURALS, CONCAT +) +from .helpers import ( # noqa: F401 + LITERAL, EXTERNAL_ARGUMENT, MESSAGE_REFERENCE +) +from .changesets import convert_blame_to_changesets # noqa: F401
new file mode 100644 --- /dev/null +++ b/third_party/python/fluent/fluent/migrate/changesets.py @@ -0,0 +1,58 @@ +# coding=utf8 + +import time + + +def by_first_commit(item): + """Order two changesets by their first commit date.""" + return item['first_commit'] + + +def convert_blame_to_changesets(blame_json): + """Convert a blame dict into a list of changesets. + + The blame information in `blame_json` should be a dict of the following + structure: + + { + 'authors': [ + 'A.N. Author <author@example.com>', + ], + 'blame': { + 'path/one': { + 'key1': [0, 1346095921.0], + }, + } + } + + It will be transformed into a list of changesets which can be fed into + `MergeContext.serialize_changeset`: + + [ + { + 'author': 'A.N. Author <author@example.com>', + 'first_commit': 1346095921.0, + 'changes': { + ('path/one', 'key1'), + } + }, + ] + + """ + now = time.time() + changesets = [ + { + 'author': author, + 'first_commit': now, + 'changes': set() + } for author in blame_json['authors'] + ] + + for path, keys_info in blame_json['blame'].items(): + for key, (author_index, timestamp) in keys_info.items(): + changeset = changesets[author_index] + changeset['changes'].add((path, key)) + if timestamp < changeset['first_commit']: + changeset['first_commit'] = timestamp + + return sorted(changesets, key=by_first_commit)
new file mode 100644 --- /dev/null +++ b/third_party/python/fluent/fluent/migrate/cldr.py @@ -0,0 +1,55 @@ +# coding=utf8 + +import pkgutil +import json + + +def in_canonical_order(item): + return canonical_order.index(item) + + +cldr_plurals = json.loads( + pkgutil.get_data('fluent.migrate', 'cldr_data/plurals.json').decode('utf-8') +) + +rules = cldr_plurals['supplemental']['plurals-type-cardinal'] +canonical_order = ('zero', 'one', 'two', 'few', 'many', 'other') + +categories = {} +for lang, rules in rules.items(): + categories[lang] = tuple(sorted(map( + lambda key: key.replace('pluralRule-count-', ''), + rules.keys() + ), key=in_canonical_order)) + + +def get_plural_categories(lang): + """Return a tuple of CLDR plural categories for `lang`. + + If an exact match for `lang` is not available, recursively fall back to + a language code with the last subtag stripped. That is, if `ja-JP-mac` is + not defined in CLDR, the code will try `ja-JP` and then `ja`. + + If no matches are found, a `RuntimeError` is raised. + + >>> get_plural_categories('sl') + ('one', 'two', 'few', 'other') + >>> get_plural_categories('ga-IE') + ('one', 'few', 'two', 'few', 'other') + >>> get_plural_categories('ja-JP-mac') + ('other') + + """ + + langs_categories = categories.get(lang, None) + + if langs_categories is None: + # Remove the trailing subtag. + fallback_lang, _, _ = lang.rpartition('-') + + if fallback_lang == '': + raise RuntimeError('Unknown language: {}'.format(lang)) + + return get_plural_categories(fallback_lang) + + return langs_categories
new file mode 100644 --- /dev/null +++ b/third_party/python/fluent/fluent/migrate/cldr_data/plurals.json @@ -0,0 +1,857 @@ +{ + "supplemental": { + "version": { + "_number": "$Revision: 12805 $", + "_unicodeVersion": "9.0.0", + "_cldrVersion": "30" + }, + "plurals-type-cardinal": { + "af": { + "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "ak": { + "pluralRule-count-one": "n = 0..1 @integer 0, 1 @decimal 0.0, 1.0, 0.00, 1.00, 0.000, 1.000, 0.0000, 1.0000", + "pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.1~0.9, 1.1~1.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "am": { + "pluralRule-count-one": "i = 0 or n = 1 @integer 0, 1 @decimal 0.0~1.0, 0.00~0.04", + "pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 1.1~2.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "ar": { + "pluralRule-count-zero": "n = 0 @integer 0 @decimal 0.0, 0.00, 0.000, 0.0000", + "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", + "pluralRule-count-two": "n = 2 @integer 2 @decimal 2.0, 2.00, 2.000, 2.0000", + "pluralRule-count-few": "n % 100 = 3..10 @integer 3~10, 103~110, 1003, … @decimal 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 103.0, 1003.0, …", + "pluralRule-count-many": "n % 100 = 11..99 @integer 11~26, 111, 1011, … @decimal 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 111.0, 1011.0, …", + "pluralRule-count-other": " @integer 100~102, 200~202, 300~302, 400~402, 500~502, 600, 1000, 10000, 100000, 1000000, … @decimal 0.1~0.9, 1.1~1.7, 10.1, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "ars": { + "pluralRule-count-zero": "n = 0 @integer 0 @decimal 0.0, 0.00, 0.000, 0.0000", + "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", + "pluralRule-count-two": "n = 2 @integer 2 @decimal 2.0, 2.00, 2.000, 2.0000", + "pluralRule-count-few": "n % 100 = 3..10 @integer 3~10, 103~110, 1003, … @decimal 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 103.0, 1003.0, …", + "pluralRule-count-many": "n % 100 = 11..99 @integer 11~26, 111, 1011, … @decimal 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 111.0, 1011.0, …", + "pluralRule-count-other": " @integer 100~102, 200~202, 300~302, 400~402, 500~502, 600, 1000, 10000, 100000, 1000000, … @decimal 0.1~0.9, 1.1~1.7, 10.1, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "as": { + "pluralRule-count-one": "i = 0 or n = 1 @integer 0, 1 @decimal 0.0~1.0, 0.00~0.04", + "pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 1.1~2.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "asa": { + "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "ast": { + "pluralRule-count-one": "i = 1 and v = 0 @integer 1", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "az": { + "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "be": { + "pluralRule-count-one": "n % 10 = 1 and n % 100 != 11 @integer 1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, … @decimal 1.0, 21.0, 31.0, 41.0, 51.0, 61.0, 71.0, 81.0, 101.0, 1001.0, …", + "pluralRule-count-few": "n % 10 = 2..4 and n % 100 != 12..14 @integer 2~4, 22~24, 32~34, 42~44, 52~54, 62, 102, 1002, … @decimal 2.0, 3.0, 4.0, 22.0, 23.0, 24.0, 32.0, 33.0, 102.0, 1002.0, …", + "pluralRule-count-many": "n % 10 = 0 or n % 10 = 5..9 or n % 100 = 11..14 @integer 0, 5~19, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …", + "pluralRule-count-other": " @decimal 0.1~0.9, 1.1~1.7, 10.1, 100.1, 1000.1, …" + }, + "bem": { + "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "bez": { + "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "bg": { + "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "bh": { + "pluralRule-count-one": "n = 0..1 @integer 0, 1 @decimal 0.0, 1.0, 0.00, 1.00, 0.000, 1.000, 0.0000, 1.0000", + "pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.1~0.9, 1.1~1.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "bm": { + "pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "bn": { + "pluralRule-count-one": "i = 0 or n = 1 @integer 0, 1 @decimal 0.0~1.0, 0.00~0.04", + "pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 1.1~2.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "bo": { + "pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "br": { + "pluralRule-count-one": "n % 10 = 1 and n % 100 != 11,71,91 @integer 1, 21, 31, 41, 51, 61, 81, 101, 1001, … @decimal 1.0, 21.0, 31.0, 41.0, 51.0, 61.0, 81.0, 101.0, 1001.0, …", + "pluralRule-count-two": "n % 10 = 2 and n % 100 != 12,72,92 @integer 2, 22, 32, 42, 52, 62, 82, 102, 1002, … @decimal 2.0, 22.0, 32.0, 42.0, 52.0, 62.0, 82.0, 102.0, 1002.0, …", + "pluralRule-count-few": "n % 10 = 3..4,9 and n % 100 != 10..19,70..79,90..99 @integer 3, 4, 9, 23, 24, 29, 33, 34, 39, 43, 44, 49, 103, 1003, … @decimal 3.0, 4.0, 9.0, 23.0, 24.0, 29.0, 33.0, 34.0, 103.0, 1003.0, …", + "pluralRule-count-many": "n != 0 and n % 1000000 = 0 @integer 1000000, … @decimal 1000000.0, 1000000.00, 1000000.000, …", + "pluralRule-count-other": " @integer 0, 5~8, 10~20, 100, 1000, 10000, 100000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, …" + }, + "brx": { + "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "bs": { + "pluralRule-count-one": "v = 0 and i % 10 = 1 and i % 100 != 11 or f % 10 = 1 and f % 100 != 11 @integer 1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, … @decimal 0.1, 1.1, 2.1, 3.1, 4.1, 5.1, 6.1, 7.1, 10.1, 100.1, 1000.1, …", + "pluralRule-count-few": "v = 0 and i % 10 = 2..4 and i % 100 != 12..14 or f % 10 = 2..4 and f % 100 != 12..14 @integer 2~4, 22~24, 32~34, 42~44, 52~54, 62, 102, 1002, … @decimal 0.2~0.4, 1.2~1.4, 2.2~2.4, 3.2~3.4, 4.2~4.4, 5.2, 10.2, 100.2, 1000.2, …", + "pluralRule-count-other": " @integer 0, 5~19, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0, 0.5~1.0, 1.5~2.0, 2.5~2.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "ca": { + "pluralRule-count-one": "i = 1 and v = 0 @integer 1", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "ce": { + "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "cgg": { + "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "chr": { + "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "ckb": { + "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "cs": { + "pluralRule-count-one": "i = 1 and v = 0 @integer 1", + "pluralRule-count-few": "i = 2..4 and v = 0 @integer 2~4", + "pluralRule-count-many": "v != 0 @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …", + "pluralRule-count-other": " @integer 0, 5~19, 100, 1000, 10000, 100000, 1000000, …" + }, + "cy": { + "pluralRule-count-zero": "n = 0 @integer 0 @decimal 0.0, 0.00, 0.000, 0.0000", + "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", + "pluralRule-count-two": "n = 2 @integer 2 @decimal 2.0, 2.00, 2.000, 2.0000", + "pluralRule-count-few": "n = 3 @integer 3 @decimal 3.0, 3.00, 3.000, 3.0000", + "pluralRule-count-many": "n = 6 @integer 6 @decimal 6.0, 6.00, 6.000, 6.0000", + "pluralRule-count-other": " @integer 4, 5, 7~20, 100, 1000, 10000, 100000, 1000000, … @decimal 0.1~0.9, 1.1~1.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "da": { + "pluralRule-count-one": "n = 1 or t != 0 and i = 0,1 @integer 1 @decimal 0.1~1.6", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0, 2.0~3.4, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "de": { + "pluralRule-count-one": "i = 1 and v = 0 @integer 1", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "dsb": { + "pluralRule-count-one": "v = 0 and i % 100 = 1 or f % 100 = 1 @integer 1, 101, 201, 301, 401, 501, 601, 701, 1001, … @decimal 0.1, 1.1, 2.1, 3.1, 4.1, 5.1, 6.1, 7.1, 10.1, 100.1, 1000.1, …", + "pluralRule-count-two": "v = 0 and i % 100 = 2 or f % 100 = 2 @integer 2, 102, 202, 302, 402, 502, 602, 702, 1002, … @decimal 0.2, 1.2, 2.2, 3.2, 4.2, 5.2, 6.2, 7.2, 10.2, 100.2, 1000.2, …", + "pluralRule-count-few": "v = 0 and i % 100 = 3..4 or f % 100 = 3..4 @integer 3, 4, 103, 104, 203, 204, 303, 304, 403, 404, 503, 504, 603, 604, 703, 704, 1003, … @decimal 0.3, 0.4, 1.3, 1.4, 2.3, 2.4, 3.3, 3.4, 4.3, 4.4, 5.3, 5.4, 6.3, 6.4, 7.3, 7.4, 10.3, 100.3, 1000.3, …", + "pluralRule-count-other": " @integer 0, 5~19, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0, 0.5~1.0, 1.5~2.0, 2.5~2.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "dv": { + "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "dz": { + "pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "ee": { + "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "el": { + "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "en": { + "pluralRule-count-one": "i = 1 and v = 0 @integer 1", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "eo": { + "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "es": { + "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "et": { + "pluralRule-count-one": "i = 1 and v = 0 @integer 1", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "eu": { + "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "fa": { + "pluralRule-count-one": "i = 0 or n = 1 @integer 0, 1 @decimal 0.0~1.0, 0.00~0.04", + "pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 1.1~2.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "ff": { + "pluralRule-count-one": "i = 0,1 @integer 0, 1 @decimal 0.0~1.5", + "pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 2.0~3.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "fi": { + "pluralRule-count-one": "i = 1 and v = 0 @integer 1", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "fil": { + "pluralRule-count-one": "v = 0 and i = 1,2,3 or v = 0 and i % 10 != 4,6,9 or v != 0 and f % 10 != 4,6,9 @integer 0~3, 5, 7, 8, 10~13, 15, 17, 18, 20, 21, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.3, 0.5, 0.7, 0.8, 1.0~1.3, 1.5, 1.7, 1.8, 2.0, 2.1, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …", + "pluralRule-count-other": " @integer 4, 6, 9, 14, 16, 19, 24, 26, 104, 1004, … @decimal 0.4, 0.6, 0.9, 1.4, 1.6, 1.9, 2.4, 2.6, 10.4, 100.4, 1000.4, …" + }, + "fo": { + "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "fr": { + "pluralRule-count-one": "i = 0,1 @integer 0, 1 @decimal 0.0~1.5", + "pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 2.0~3.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "fur": { + "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "fy": { + "pluralRule-count-one": "i = 1 and v = 0 @integer 1", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "ga": { + "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", + "pluralRule-count-two": "n = 2 @integer 2 @decimal 2.0, 2.00, 2.000, 2.0000", + "pluralRule-count-few": "n = 3..6 @integer 3~6 @decimal 3.0, 4.0, 5.0, 6.0, 3.00, 4.00, 5.00, 6.00, 3.000, 4.000, 5.000, 6.000, 3.0000, 4.0000, 5.0000, 6.0000", + "pluralRule-count-many": "n = 7..10 @integer 7~10 @decimal 7.0, 8.0, 9.0, 10.0, 7.00, 8.00, 9.00, 10.00, 7.000, 8.000, 9.000, 10.000, 7.0000, 8.0000, 9.0000, 10.0000", + "pluralRule-count-other": " @integer 0, 11~25, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.1, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "gd": { + "pluralRule-count-one": "n = 1,11 @integer 1, 11 @decimal 1.0, 11.0, 1.00, 11.00, 1.000, 11.000, 1.0000", + "pluralRule-count-two": "n = 2,12 @integer 2, 12 @decimal 2.0, 12.0, 2.00, 12.00, 2.000, 12.000, 2.0000", + "pluralRule-count-few": "n = 3..10,13..19 @integer 3~10, 13~19 @decimal 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0, 3.00", + "pluralRule-count-other": " @integer 0, 20~34, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.1, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "gl": { + "pluralRule-count-one": "i = 1 and v = 0 @integer 1", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "gsw": { + "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "gu": { + "pluralRule-count-one": "i = 0 or n = 1 @integer 0, 1 @decimal 0.0~1.0, 0.00~0.04", + "pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 1.1~2.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "guw": { + "pluralRule-count-one": "n = 0..1 @integer 0, 1 @decimal 0.0, 1.0, 0.00, 1.00, 0.000, 1.000, 0.0000, 1.0000", + "pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.1~0.9, 1.1~1.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "gv": { + "pluralRule-count-one": "v = 0 and i % 10 = 1 @integer 1, 11, 21, 31, 41, 51, 61, 71, 101, 1001, …", + "pluralRule-count-two": "v = 0 and i % 10 = 2 @integer 2, 12, 22, 32, 42, 52, 62, 72, 102, 1002, …", + "pluralRule-count-few": "v = 0 and i % 100 = 0,20,40,60,80 @integer 0, 20, 40, 60, 80, 100, 120, 140, 1000, 10000, 100000, 1000000, …", + "pluralRule-count-many": "v != 0 @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …", + "pluralRule-count-other": " @integer 3~10, 13~19, 23, 103, 1003, …" + }, + "ha": { + "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "haw": { + "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "he": { + "pluralRule-count-one": "i = 1 and v = 0 @integer 1", + "pluralRule-count-two": "i = 2 and v = 0 @integer 2", + "pluralRule-count-many": "v = 0 and n != 0..10 and n % 10 = 0 @integer 20, 30, 40, 50, 60, 70, 80, 90, 100, 1000, 10000, 100000, 1000000, …", + "pluralRule-count-other": " @integer 0, 3~17, 101, 1001, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "hi": { + "pluralRule-count-one": "i = 0 or n = 1 @integer 0, 1 @decimal 0.0~1.0, 0.00~0.04", + "pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 1.1~2.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "hr": { + "pluralRule-count-one": "v = 0 and i % 10 = 1 and i % 100 != 11 or f % 10 = 1 and f % 100 != 11 @integer 1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, … @decimal 0.1, 1.1, 2.1, 3.1, 4.1, 5.1, 6.1, 7.1, 10.1, 100.1, 1000.1, …", + "pluralRule-count-few": "v = 0 and i % 10 = 2..4 and i % 100 != 12..14 or f % 10 = 2..4 and f % 100 != 12..14 @integer 2~4, 22~24, 32~34, 42~44, 52~54, 62, 102, 1002, … @decimal 0.2~0.4, 1.2~1.4, 2.2~2.4, 3.2~3.4, 4.2~4.4, 5.2, 10.2, 100.2, 1000.2, …", + "pluralRule-count-other": " @integer 0, 5~19, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0, 0.5~1.0, 1.5~2.0, 2.5~2.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "hsb": { + "pluralRule-count-one": "v = 0 and i % 100 = 1 or f % 100 = 1 @integer 1, 101, 201, 301, 401, 501, 601, 701, 1001, … @decimal 0.1, 1.1, 2.1, 3.1, 4.1, 5.1, 6.1, 7.1, 10.1, 100.1, 1000.1, …", + "pluralRule-count-two": "v = 0 and i % 100 = 2 or f % 100 = 2 @integer 2, 102, 202, 302, 402, 502, 602, 702, 1002, … @decimal 0.2, 1.2, 2.2, 3.2, 4.2, 5.2, 6.2, 7.2, 10.2, 100.2, 1000.2, …", + "pluralRule-count-few": "v = 0 and i % 100 = 3..4 or f % 100 = 3..4 @integer 3, 4, 103, 104, 203, 204, 303, 304, 403, 404, 503, 504, 603, 604, 703, 704, 1003, … @decimal 0.3, 0.4, 1.3, 1.4, 2.3, 2.4, 3.3, 3.4, 4.3, 4.4, 5.3, 5.4, 6.3, 6.4, 7.3, 7.4, 10.3, 100.3, 1000.3, …", + "pluralRule-count-other": " @integer 0, 5~19, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0, 0.5~1.0, 1.5~2.0, 2.5~2.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "hu": { + "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "hy": { + "pluralRule-count-one": "i = 0,1 @integer 0, 1 @decimal 0.0~1.5", + "pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 2.0~3.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "id": { + "pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "ig": { + "pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "ii": { + "pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "in": { + "pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "is": { + "pluralRule-count-one": "t = 0 and i % 10 = 1 and i % 100 != 11 or t != 0 @integer 1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, … @decimal 0.1~1.6, 10.1, 100.1, 1000.1, …", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "it": { + "pluralRule-count-one": "i = 1 and v = 0 @integer 1", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "iu": { + "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", + "pluralRule-count-two": "n = 2 @integer 2 @decimal 2.0, 2.00, 2.000, 2.0000", + "pluralRule-count-other": " @integer 0, 3~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "iw": { + "pluralRule-count-one": "i = 1 and v = 0 @integer 1", + "pluralRule-count-two": "i = 2 and v = 0 @integer 2", + "pluralRule-count-many": "v = 0 and n != 0..10 and n % 10 = 0 @integer 20, 30, 40, 50, 60, 70, 80, 90, 100, 1000, 10000, 100000, 1000000, …", + "pluralRule-count-other": " @integer 0, 3~17, 101, 1001, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "ja": { + "pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "jbo": { + "pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "jgo": { + "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "ji": { + "pluralRule-count-one": "i = 1 and v = 0 @integer 1", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "jmc": { + "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "jv": { + "pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "jw": { + "pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "ka": { + "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "kab": { + "pluralRule-count-one": "i = 0,1 @integer 0, 1 @decimal 0.0~1.5", + "pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 2.0~3.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "kaj": { + "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "kcg": { + "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "kde": { + "pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "kea": { + "pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "kk": { + "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "kkj": { + "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "kl": { + "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "km": { + "pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "kn": { + "pluralRule-count-one": "i = 0 or n = 1 @integer 0, 1 @decimal 0.0~1.0, 0.00~0.04", + "pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 1.1~2.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "ko": { + "pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "ks": { + "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "ksb": { + "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "ksh": { + "pluralRule-count-zero": "n = 0 @integer 0 @decimal 0.0, 0.00, 0.000, 0.0000", + "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", + "pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.1~0.9, 1.1~1.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "ku": { + "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "kw": { + "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", + "pluralRule-count-two": "n = 2 @integer 2 @decimal 2.0, 2.00, 2.000, 2.0000", + "pluralRule-count-other": " @integer 0, 3~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "ky": { + "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "lag": { + "pluralRule-count-zero": "n = 0 @integer 0 @decimal 0.0, 0.00, 0.000, 0.0000", + "pluralRule-count-one": "i = 0,1 and n != 0 @integer 1 @decimal 0.1~1.6", + "pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 2.0~3.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "lb": { + "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "lg": { + "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "lkt": { + "pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "ln": { + "pluralRule-count-one": "n = 0..1 @integer 0, 1 @decimal 0.0, 1.0, 0.00, 1.00, 0.000, 1.000, 0.0000, 1.0000", + "pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.1~0.9, 1.1~1.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "lo": { + "pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "lt": { + "pluralRule-count-one": "n % 10 = 1 and n % 100 != 11..19 @integer 1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, … @decimal 1.0, 21.0, 31.0, 41.0, 51.0, 61.0, 71.0, 81.0, 101.0, 1001.0, …", + "pluralRule-count-few": "n % 10 = 2..9 and n % 100 != 11..19 @integer 2~9, 22~29, 102, 1002, … @decimal 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 22.0, 102.0, 1002.0, …", + "pluralRule-count-many": "f != 0 @decimal 0.1~0.9, 1.1~1.7, 10.1, 100.1, 1000.1, …", + "pluralRule-count-other": " @integer 0, 10~20, 30, 40, 50, 60, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "lv": { + "pluralRule-count-zero": "n % 10 = 0 or n % 100 = 11..19 or v = 2 and f % 100 = 11..19 @integer 0, 10~20, 30, 40, 50, 60, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …", + "pluralRule-count-one": "n % 10 = 1 and n % 100 != 11 or v = 2 and f % 10 = 1 and f % 100 != 11 or v != 2 and f % 10 = 1 @integer 1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, … @decimal 0.1, 1.0, 1.1, 2.1, 3.1, 4.1, 5.1, 6.1, 7.1, 10.1, 100.1, 1000.1, …", + "pluralRule-count-other": " @integer 2~9, 22~29, 102, 1002, … @decimal 0.2~0.9, 1.2~1.9, 10.2, 100.2, 1000.2, …" + }, + "mas": { + "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "mg": { + "pluralRule-count-one": "n = 0..1 @integer 0, 1 @decimal 0.0, 1.0, 0.00, 1.00, 0.000, 1.000, 0.0000, 1.0000", + "pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.1~0.9, 1.1~1.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "mgo": { + "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "mk": { + "pluralRule-count-one": "v = 0 and i % 10 = 1 or f % 10 = 1 @integer 1, 11, 21, 31, 41, 51, 61, 71, 101, 1001, … @decimal 0.1, 1.1, 2.1, 3.1, 4.1, 5.1, 6.1, 7.1, 10.1, 100.1, 1000.1, …", + "pluralRule-count-other": " @integer 0, 2~10, 12~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0, 0.2~1.0, 1.2~1.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "ml": { + "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "mn": { + "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "mo": { + "pluralRule-count-one": "i = 1 and v = 0 @integer 1", + "pluralRule-count-few": "v != 0 or n = 0 or n != 1 and n % 100 = 1..19 @integer 0, 2~16, 101, 1001, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …", + "pluralRule-count-other": " @integer 20~35, 100, 1000, 10000, 100000, 1000000, …" + }, + "mr": { + "pluralRule-count-one": "i = 0 or n = 1 @integer 0, 1 @decimal 0.0~1.0, 0.00~0.04", + "pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 1.1~2.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "ms": { + "pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "mt": { + "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", + "pluralRule-count-few": "n = 0 or n % 100 = 2..10 @integer 0, 2~10, 102~107, 1002, … @decimal 0.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 10.0, 102.0, 1002.0, …", + "pluralRule-count-many": "n % 100 = 11..19 @integer 11~19, 111~117, 1011, … @decimal 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 111.0, 1011.0, …", + "pluralRule-count-other": " @integer 20~35, 100, 1000, 10000, 100000, 1000000, … @decimal 0.1~0.9, 1.1~1.7, 10.1, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "my": { + "pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "nah": { + "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "naq": { + "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", + "pluralRule-count-two": "n = 2 @integer 2 @decimal 2.0, 2.00, 2.000, 2.0000", + "pluralRule-count-other": " @integer 0, 3~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "nb": { + "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "nd": { + "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "ne": { + "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "nl": { + "pluralRule-count-one": "i = 1 and v = 0 @integer 1", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "nn": { + "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "nnh": { + "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "no": { + "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "nqo": { + "pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "nr": { + "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "nso": { + "pluralRule-count-one": "n = 0..1 @integer 0, 1 @decimal 0.0, 1.0, 0.00, 1.00, 0.000, 1.000, 0.0000, 1.0000", + "pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.1~0.9, 1.1~1.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "ny": { + "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "nyn": { + "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "om": { + "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "or": { + "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "os": { + "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "pa": { + "pluralRule-count-one": "n = 0..1 @integer 0, 1 @decimal 0.0, 1.0, 0.00, 1.00, 0.000, 1.000, 0.0000, 1.0000", + "pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.1~0.9, 1.1~1.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "pap": { + "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "pl": { + "pluralRule-count-one": "i = 1 and v = 0 @integer 1", + "pluralRule-count-few": "v = 0 and i % 10 = 2..4 and i % 100 != 12..14 @integer 2~4, 22~24, 32~34, 42~44, 52~54, 62, 102, 1002, …", + "pluralRule-count-many": "v = 0 and i != 1 and i % 10 = 0..1 or v = 0 and i % 10 = 5..9 or v = 0 and i % 100 = 12..14 @integer 0, 5~19, 100, 1000, 10000, 100000, 1000000, …", + "pluralRule-count-other": " @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "prg": { + "pluralRule-count-zero": "n % 10 = 0 or n % 100 = 11..19 or v = 2 and f % 100 = 11..19 @integer 0, 10~20, 30, 40, 50, 60, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …", + "pluralRule-count-one": "n % 10 = 1 and n % 100 != 11 or v = 2 and f % 10 = 1 and f % 100 != 11 or v != 2 and f % 10 = 1 @integer 1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, … @decimal 0.1, 1.0, 1.1, 2.1, 3.1, 4.1, 5.1, 6.1, 7.1, 10.1, 100.1, 1000.1, …", + "pluralRule-count-other": " @integer 2~9, 22~29, 102, 1002, … @decimal 0.2~0.9, 1.2~1.9, 10.2, 100.2, 1000.2, …" + }, + "ps": { + "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "pt": { + "pluralRule-count-one": "n = 0..2 and n != 2 @integer 0, 1 @decimal 0.0, 1.0, 0.00, 1.00, 0.000, 1.000, 0.0000, 1.0000", + "pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.1~0.9, 1.1~1.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "pt-PT": { + "pluralRule-count-one": "n = 1 and v = 0 @integer 1", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "rm": { + "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "ro": { + "pluralRule-count-one": "i = 1 and v = 0 @integer 1", + "pluralRule-count-few": "v != 0 or n = 0 or n != 1 and n % 100 = 1..19 @integer 0, 2~16, 101, 1001, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …", + "pluralRule-count-other": " @integer 20~35, 100, 1000, 10000, 100000, 1000000, …" + }, + "rof": { + "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "root": { + "pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "ru": { + "pluralRule-count-one": "v = 0 and i % 10 = 1 and i % 100 != 11 @integer 1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, …", + "pluralRule-count-few": "v = 0 and i % 10 = 2..4 and i % 100 != 12..14 @integer 2~4, 22~24, 32~34, 42~44, 52~54, 62, 102, 1002, …", + "pluralRule-count-many": "v = 0 and i % 10 = 0 or v = 0 and i % 10 = 5..9 or v = 0 and i % 100 = 11..14 @integer 0, 5~19, 100, 1000, 10000, 100000, 1000000, …", + "pluralRule-count-other": " @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "rwk": { + "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "sah": { + "pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "saq": { + "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "sdh": { + "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "se": { + "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", + "pluralRule-count-two": "n = 2 @integer 2 @decimal 2.0, 2.00, 2.000, 2.0000", + "pluralRule-count-other": " @integer 0, 3~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "seh": { + "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "ses": { + "pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "sg": { + "pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "sh": { + "pluralRule-count-one": "v = 0 and i % 10 = 1 and i % 100 != 11 or f % 10 = 1 and f % 100 != 11 @integer 1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, … @decimal 0.1, 1.1, 2.1, 3.1, 4.1, 5.1, 6.1, 7.1, 10.1, 100.1, 1000.1, …", + "pluralRule-count-few": "v = 0 and i % 10 = 2..4 and i % 100 != 12..14 or f % 10 = 2..4 and f % 100 != 12..14 @integer 2~4, 22~24, 32~34, 42~44, 52~54, 62, 102, 1002, … @decimal 0.2~0.4, 1.2~1.4, 2.2~2.4, 3.2~3.4, 4.2~4.4, 5.2, 10.2, 100.2, 1000.2, …", + "pluralRule-count-other": " @integer 0, 5~19, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0, 0.5~1.0, 1.5~2.0, 2.5~2.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "shi": { + "pluralRule-count-one": "i = 0 or n = 1 @integer 0, 1 @decimal 0.0~1.0, 0.00~0.04", + "pluralRule-count-few": "n = 2..10 @integer 2~10 @decimal 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 2.00, 3.00, 4.00, 5.00, 6.00, 7.00, 8.00", + "pluralRule-count-other": " @integer 11~26, 100, 1000, 10000, 100000, 1000000, … @decimal 1.1~1.9, 2.1~2.7, 10.1, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "si": { + "pluralRule-count-one": "n = 0,1 or i = 0 and f = 1 @integer 0, 1 @decimal 0.0, 0.1, 1.0, 0.00, 0.01, 1.00, 0.000, 0.001, 1.000, 0.0000, 0.0001, 1.0000", + "pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.2~0.9, 1.1~1.8, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "sk": { + "pluralRule-count-one": "i = 1 and v = 0 @integer 1", + "pluralRule-count-few": "i = 2..4 and v = 0 @integer 2~4", + "pluralRule-count-many": "v != 0 @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …", + "pluralRule-count-other": " @integer 0, 5~19, 100, 1000, 10000, 100000, 1000000, …" + }, + "sl": { + "pluralRule-count-one": "v = 0 and i % 100 = 1 @integer 1, 101, 201, 301, 401, 501, 601, 701, 1001, …", + "pluralRule-count-two": "v = 0 and i % 100 = 2 @integer 2, 102, 202, 302, 402, 502, 602, 702, 1002, …", + "pluralRule-count-few": "v = 0 and i % 100 = 3..4 or v != 0 @integer 3, 4, 103, 104, 203, 204, 303, 304, 403, 404, 503, 504, 603, 604, 703, 704, 1003, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …", + "pluralRule-count-other": " @integer 0, 5~19, 100, 1000, 10000, 100000, 1000000, …" + }, + "sma": { + "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", + "pluralRule-count-two": "n = 2 @integer 2 @decimal 2.0, 2.00, 2.000, 2.0000", + "pluralRule-count-other": " @integer 0, 3~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "smi": { + "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", + "pluralRule-count-two": "n = 2 @integer 2 @decimal 2.0, 2.00, 2.000, 2.0000", + "pluralRule-count-other": " @integer 0, 3~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "smj": { + "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", + "pluralRule-count-two": "n = 2 @integer 2 @decimal 2.0, 2.00, 2.000, 2.0000", + "pluralRule-count-other": " @integer 0, 3~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "smn": { + "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", + "pluralRule-count-two": "n = 2 @integer 2 @decimal 2.0, 2.00, 2.000, 2.0000", + "pluralRule-count-other": " @integer 0, 3~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "sms": { + "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", + "pluralRule-count-two": "n = 2 @integer 2 @decimal 2.0, 2.00, 2.000, 2.0000", + "pluralRule-count-other": " @integer 0, 3~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "sn": { + "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "so": { + "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "sq": { + "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "sr": { + "pluralRule-count-one": "v = 0 and i % 10 = 1 and i % 100 != 11 or f % 10 = 1 and f % 100 != 11 @integer 1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, … @decimal 0.1, 1.1, 2.1, 3.1, 4.1, 5.1, 6.1, 7.1, 10.1, 100.1, 1000.1, …", + "pluralRule-count-few": "v = 0 and i % 10 = 2..4 and i % 100 != 12..14 or f % 10 = 2..4 and f % 100 != 12..14 @integer 2~4, 22~24, 32~34, 42~44, 52~54, 62, 102, 1002, … @decimal 0.2~0.4, 1.2~1.4, 2.2~2.4, 3.2~3.4, 4.2~4.4, 5.2, 10.2, 100.2, 1000.2, …", + "pluralRule-count-other": " @integer 0, 5~19, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0, 0.5~1.0, 1.5~2.0, 2.5~2.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "ss": { + "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "ssy": { + "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "st": { + "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "sv": { + "pluralRule-count-one": "i = 1 and v = 0 @integer 1", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "sw": { + "pluralRule-count-one": "i = 1 and v = 0 @integer 1", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "syr": { + "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "ta": { + "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "te": { + "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "teo": { + "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "th": { + "pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "ti": { + "pluralRule-count-one": "n = 0..1 @integer 0, 1 @decimal 0.0, 1.0, 0.00, 1.00, 0.000, 1.000, 0.0000, 1.0000", + "pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.1~0.9, 1.1~1.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "tig": { + "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "tk": { + "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "tl": { + "pluralRule-count-one": "v = 0 and i = 1,2,3 or v = 0 and i % 10 != 4,6,9 or v != 0 and f % 10 != 4,6,9 @integer 0~3, 5, 7, 8, 10~13, 15, 17, 18, 20, 21, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.3, 0.5, 0.7, 0.8, 1.0~1.3, 1.5, 1.7, 1.8, 2.0, 2.1, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …", + "pluralRule-count-other": " @integer 4, 6, 9, 14, 16, 19, 24, 26, 104, 1004, … @decimal 0.4, 0.6, 0.9, 1.4, 1.6, 1.9, 2.4, 2.6, 10.4, 100.4, 1000.4, …" + }, + "tn": { + "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "to": { + "pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "tr": { + "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "ts": { + "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "tzm": { + "pluralRule-count-one": "n = 0..1 or n = 11..99 @integer 0, 1, 11~24 @decimal 0.0, 1.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0, 20.0, 21.0, 22.0, 23.0, 24.0", + "pluralRule-count-other": " @integer 2~10, 100~106, 1000, 10000, 100000, 1000000, … @decimal 0.1~0.9, 1.1~1.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "ug": { + "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "uk": { + "pluralRule-count-one": "v = 0 and i % 10 = 1 and i % 100 != 11 @integer 1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, …", + "pluralRule-count-few": "v = 0 and i % 10 = 2..4 and i % 100 != 12..14 @integer 2~4, 22~24, 32~34, 42~44, 52~54, 62, 102, 1002, …", + "pluralRule-count-many": "v = 0 and i % 10 = 0 or v = 0 and i % 10 = 5..9 or v = 0 and i % 100 = 11..14 @integer 0, 5~19, 100, 1000, 10000, 100000, 1000000, …", + "pluralRule-count-other": " @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "ur": { + "pluralRule-count-one": "i = 1 and v = 0 @integer 1", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "uz": { + "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "ve": { + "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "vi": { + "pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "vo": { + "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "vun": { + "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "wa": { + "pluralRule-count-one": "n = 0..1 @integer 0, 1 @decimal 0.0, 1.0, 0.00, 1.00, 0.000, 1.000, 0.0000, 1.0000", + "pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.1~0.9, 1.1~1.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "wae": { + "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "wo": { + "pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "xh": { + "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "xog": { + "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "yi": { + "pluralRule-count-one": "i = 1 and v = 0 @integer 1", + "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "yo": { + "pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "yue": { + "pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "zh": { + "pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + }, + "zu": { + "pluralRule-count-one": "i = 0 or n = 1 @integer 0, 1 @decimal 0.0~1.0, 0.00~0.04", + "pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 1.1~2.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" + } + } + } +}
new file mode 100644 --- /dev/null +++ b/third_party/python/fluent/fluent/migrate/cldr_data/unicode-license.txt @@ -0,0 +1,53 @@ +UNICODE, INC. LICENSE AGREEMENT - DATA FILES AND SOFTWARE + +Unicode Data Files include all data files under the directories +http://www.unicode.org/Public/, http://www.unicode.org/reports/, +http://www.unicode.org/cldr/data/, http://source.icu-project.org/repos/icu/, +and http://www.unicode.org/utility/trac/browser/. + +Unicode Data Files do not include PDF online code charts under the +directory http://www.unicode.org/Public/. + +Software includes any source code published in the Unicode Standard +or under the directories +http://www.unicode.org/Public/, http://www.unicode.org/reports/, +http://www.unicode.org/cldr/data/, http://source.icu-project.org/repos/icu/, +and http://www.unicode.org/utility/trac/browser/. + +NOTICE TO USER: Carefully read the following legal agreement. +BY DOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING UNICODE INC.'S DATA +FILES ("DATA FILES"), AND/OR SOFTWARE ("SOFTWARE"), YOU UNEQUIVOCALLY ACCEPT, +AND AGREE TO BE BOUND BY, ALL OF THE TERMS AND CONDITIONS OF THIS AGREEMENT. +IF YOU DO NOT AGREE, DO NOT DOWNLOAD, INSTALL, COPY, DISTRIBUTE OR USE THE +DATA FILES OR SOFTWARE. + +COPYRIGHT AND PERMISSION NOTICE + +Copyright © 1991-2016 Unicode, Inc. All rights reserved. Distributed under +the Terms of Use in http://www.unicode.org/copyright.html. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of the Unicode data files and any associated documentation (the "Data Files") +or Unicode software and any associated documentation (the "Software") to deal +in the Data Files or Software without restriction, including without +limitation the rights to use, copy, modify, merge, publish, distribute, +and/or sell copies of the Data Files or Software, and to permit persons to +whom the Data Files or Software are furnished to do so, provided that either +(a) this copyright and permission notice appear with all copies of the Data +Files or Software, or +(b) this copyright and permission notice appear in associated Documentation. + +THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF +THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS +INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR +CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, +DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE +OF THE DATA FILES OR SOFTWARE. + +Except as contained in this notice, the name of a copyright holder shall not +be used in advertising or otherwise to promote the sale, use or other +dealings in these Data Files or Software without prior written authorization +of the copyright holder.
new file mode 100644 --- /dev/null +++ b/third_party/python/fluent/fluent/migrate/context.py @@ -0,0 +1,270 @@ +# coding=utf8 +from __future__ import unicode_literals + +import os +import codecs +import logging + +import fluent.syntax.ast as FTL +from fluent.syntax.parser import FluentParser +from fluent.syntax.serializer import FluentSerializer +from fluent.util import fold +try: + from compare_locales.parser import getParser +except ImportError: + def getParser(path): + raise RuntimeError('compare-locales required') + +from .cldr import get_plural_categories +from .transforms import Source +from .merge import merge_resource +from .util import get_message + + +class MergeContext(object): + """Stateful context for merging translation resources. + + `MergeContext` must be configured with the target language and the + directory locations of the input data. + + The transformation takes four types of input data: + + - The en-US FTL reference files which will be used as templates for + message order, comments and sections. + + - The current FTL files for the given language. + + - The legacy (DTD, properties) translation files for the given + language. The translations from these files will be transformed + into FTL and merged into the existing FTL files for this language. + + - A list of `FTL.Message` objects some of whose nodes are special + helper or transform nodes: + + helpers: LITERAL, EXTERNAL_ARGUMENT, MESSAGE_REFERENCE + transforms: COPY, REPLACE_IN_TEXT, REPLACE, PLURALS, CONCAT + """ + + def __init__(self, lang, reference_dir, localization_dir): + self.fluent_parser = FluentParser(with_spans=False) + self.fluent_serializer = FluentSerializer() + + # An iterable of plural category names relevant to the context's + # language. E.g. ('one', 'other') for English. + try: + self.plural_categories = get_plural_categories(lang) + except RuntimeError as e: + print(e.message) + self.plural_categories = 'en' + + # Paths to directories with input data, relative to CWD. + self.reference_dir = reference_dir + self.localization_dir = localization_dir + + # Parsed input resources stored by resource path. + self.reference_resources = {} + self.localization_resources = {} + + # An iterable of `FTL.Message` objects some of whose nodes can be the + # transform operations. + self.transforms = {} + + # A dict whose keys are `(path, key)` tuples corresponding to target + # FTL translations, and values are sets of `(path, key)` tuples + # corresponding to localized entities which will be migrated. + self.dependencies = {} + + def read_ftl_resource(self, path): + """Read an FTL resource and parse it into an AST.""" + f = codecs.open(path, 'r', 'utf8') + try: + contents = f.read() + finally: + f.close() + + ast = self.fluent_parser.parse(contents) + + annots = [ + annot + for entry in ast.body + for annot in entry.annotations + ] + + if len(annots): + logger = logging.getLogger('migrate') + for annot in annots: + msg = annot.message + logger.warn(u'Syntax error in {}: {}'.format(path, msg)) + + return ast + + def read_legacy_resource(self, path): + """Read a legacy resource and parse it into a dict.""" + parser = getParser(path) + parser.readFile(path) + # Transform the parsed result which is an iterator into a dict. + return {entity.key: entity.val for entity in parser} + + def add_reference(self, path, realpath=None): + """Add an FTL AST to this context's reference resources.""" + fullpath = os.path.join(self.reference_dir, realpath or path) + try: + ast = self.read_ftl_resource(fullpath) + except IOError as err: + logger = logging.getLogger('migrate') + logger.error(u'Missing reference file: {}'.format(path)) + raise err + except UnicodeDecodeError as err: + logger = logging.getLogger('migrate') + logger.error(u'Error reading file {}: {}'.format(path, err)) + raise err + else: + self.reference_resources[path] = ast + + def add_localization(self, path): + """Add an existing localization resource. + + If it's an FTL resource, add an FTL AST. Otherwise, it's a legacy + resource. Use a compare-locales parser to create a dict of (key, + string value) tuples. + """ + fullpath = os.path.join(self.localization_dir, path) + if fullpath.endswith('.ftl'): + try: + ast = self.read_ftl_resource(fullpath) + except IOError: + logger = logging.getLogger('migrate') + logger.warn(u'Missing localization file: {}'.format(path)) + except UnicodeDecodeError as err: + logger = logging.getLogger('migrate') + logger.warn(u'Error reading file {}: {}'.format(path, err)) + else: + self.localization_resources[path] = ast + else: + try: + collection = self.read_legacy_resource(fullpath) + except IOError: + logger = logging.getLogger('migrate') + logger.warn(u'Missing localization file: {}'.format(path)) + else: + self.localization_resources[path] = collection + + def add_transforms(self, path, transforms): + """Define transforms for path. + + Each transform is an extended FTL node with `Transform` nodes as some + values. Transforms are stored in their lazy AST form until + `merge_changeset` is called, at which point they are evaluated to real + FTL nodes with migrated translations. + + Each transform is scanned for `Source` nodes which will be used to + build the list of dependencies for the transformed message. + """ + def get_sources(acc, cur): + if isinstance(cur, Source): + acc.add((cur.path, cur.key)) + return acc + + for node in transforms: + # Scan `node` for `Source` nodes and collect the information they + # store into a set of dependencies. + dependencies = fold(get_sources, node, set()) + # Set these sources as dependencies for the current transform. + self.dependencies[(path, node.id.name)] = dependencies + + path_transforms = self.transforms.setdefault(path, []) + path_transforms += transforms + + def get_source(self, path, key): + """Get an entity value from the localized source. + + Used by the `Source` transform. + """ + if path.endswith('.ftl'): + resource = self.localization_resources[path] + return get_message(resource.body, key) + else: + resource = self.localization_resources[path] + return resource.get(key, None) + + def merge_changeset(self, changeset=None): + """Return a generator of FTL ASTs for the changeset. + + The input data must be configured earlier using the `add_*` methods. + if given, `changeset` must be a set of (path, key) tuples describing + which legacy translations are to be merged. + + Given `changeset`, return a dict whose keys are resource paths and + values are `FTL.Resource` instances. The values will also be used to + update this context's existing localization resources. + """ + + if changeset is None: + # Merge all known legacy translations. + changeset = { + (path, key) + for path, strings in self.localization_resources.iteritems() + for key in strings.iterkeys() + } + + for path, reference in self.reference_resources.iteritems(): + current = self.localization_resources.get(path, FTL.Resource()) + transforms = self.transforms.get(path, []) + + def in_changeset(ident): + """Check if entity should be merged. + + If at least one dependency of the entity is in the current + set of changeset, merge it. + """ + message_deps = self.dependencies.get((path, ident), None) + + # Don't merge if we don't have a transform for this message. + if message_deps is None: + return False + + # As a special case, if a transform exists but has no + # dependecies, it's a hardcoded `FTL.Node` which doesn't + # migrate any existing translation but rather creates a new + # one. Merge it. + if len(message_deps) == 0: + return True + + # If the intersection of the dependencies and the current + # changeset is non-empty, merge this message. + return message_deps & changeset + + # Merge legacy translations with the existing ones using the + # reference as a template. + snapshot = merge_resource( + self, reference, current, transforms, in_changeset + ) + + # If none of the transforms is in the given changeset, the merged + # snapshot is identical to the current translation. We compare + # JSON trees rather then use filtering by `in_changeset` to account + # for translations removed from `reference`. + if snapshot.to_json() == current.to_json(): + continue + + # Store the merged snapshot on the context so that the next merge + # already takes it into account as the existing localization. + self.localization_resources[path] = snapshot + + # The result for this path is a complete `FTL.Resource`. + yield path, snapshot + + def serialize_changeset(self, changeset): + """Return a dict of serialized FTLs for the changeset. + + Given `changeset`, return a dict whose keys are resource paths and + values are serialized FTL snapshots. + """ + + return { + path: self.fluent_serializer.serialize(snapshot) + for path, snapshot in self.merge_changeset(changeset) + } + + +logging.basicConfig()
new file mode 100644 --- /dev/null +++ b/third_party/python/fluent/fluent/migrate/helpers.py @@ -0,0 +1,35 @@ +# coding=utf8 +"""Fluent AST helpers. + +The functions defined in this module offer a shorthand for defining common AST +nodes. + +They take a string argument and immediately return a corresponding AST node. +(As opposed to Transforms which are AST nodes on their own and only return the +migrated AST nodes when they are evaluated by a MergeContext.) """ + +from __future__ import unicode_literals + +import fluent.syntax.ast as FTL + + +def LITERAL(value): + """Create a Pattern with a single TextElement.""" + elements = [FTL.TextElement(value)] + return FTL.Pattern(elements) + + +def EXTERNAL_ARGUMENT(name): + """Create an ExternalArgument expression.""" + + return FTL.ExternalArgument( + id=FTL.Identifier(name) + ) + + +def MESSAGE_REFERENCE(name): + """Create a MessageReference expression.""" + + return FTL.MessageReference( + id=FTL.Identifier(name) + )
new file mode 100644 --- /dev/null +++ b/third_party/python/fluent/fluent/migrate/merge.py @@ -0,0 +1,58 @@ +# coding=utf8 +from __future__ import unicode_literals + +import fluent.syntax.ast as FTL + +from .transforms import evaluate +from .util import get_message, get_transform + + +def merge_resource(ctx, reference, current, transforms, in_changeset): + """Transform legacy translations into FTL. + + Use the `reference` FTL AST as a template. For each en-US string in the + reference, first check if it's in the currently processed changeset with + `in_changeset`; then check for an existing translation in the current FTL + `localization` or for a migration specification in `transforms`. + """ + + def merge_body(body): + return [ + entry + for entry in map(merge_entry, body) + if entry is not None + ] + + def merge_entry(entry): + # All standalone comments will be merged. + if isinstance(entry, FTL.Comment): + return entry + + # All section headers will be merged. + if isinstance(entry, FTL.Section): + return entry + + # Ignore Junk + if isinstance(entry, FTL.Junk): + return None + + ident = entry.id.name + + # If the message is present in the existing localization, we add it to + # the resulting resource. This ensures consecutive merges don't remove + # translations but rather create supersets of them. + existing = get_message(current.body, ident) + if existing is not None: + return existing + + transform = get_transform(transforms, ident) + + # Make sure this message is supposed to be migrated as part of the + # current changeset. + if transform is not None and in_changeset(ident): + if transform.comment is None: + transform.comment = entry.comment + return evaluate(ctx, transform) + + body = merge_body(reference.body) + return FTL.Resource(body, reference.comment)
new file mode 100644 --- /dev/null +++ b/third_party/python/fluent/fluent/migrate/transforms.py @@ -0,0 +1,315 @@ +# coding=utf8 +"""Migration Transforms. + +Transforms are AST nodes which describe how legacy translations should be +migrated. They are created inert and only return the migrated AST nodes when +they are evaluated by a MergeContext. + +All Transforms evaluate to Fluent Patterns. This makes them suitable for +defining migrations of values of message, attributes and variants. The special +CONCAT Transform is capable of joining multiple Patterns returned by evaluating +other Transforms into a single Pattern. It can also concatenate Fluent +Expressions, like MessageReferences and ExternalArguments. + +The COPY, REPLACE and PLURALS Transforms inherit from Source which is a special +AST Node defining the location (the file path and the id) of the legacy +translation. During the migration, the current MergeContext scans the +migration spec for Source nodes and extracts the information about all legacy +translations being migrated. Thus, + + COPY('file.dtd', 'hello') + +is equivalent to: + + LITERAL(Source('file.dtd', 'hello')) + +where LITERAL is a helper defined in the helpers.py module for creating Fluent +Patterns from the text passed as the argument. + +The LITERAL helper and the special REPLACE_IN_TEXT Transforms are useful for +working with text rather than (path, key) source definitions. This is the case +when the migrated translation requires some hardcoded text, e.g. <a> and </a> +when multiple translations become a single one with a DOM overlay. + + FTL.Message( + id=FTL.Identifier('update-failed'), + value=CONCAT( + COPY('aboutDialog.dtd', 'update.failed.start'), + LITERAL('<a>'), + COPY('aboutDialog.dtd', 'update.failed.linkText'), + LITERAL('</a>'), + COPY('aboutDialog.dtd', 'update.failed.end'), + ) + ) + +The REPLACE_IN_TEXT Transform also takes text as input, making in possible to +pass it as the foreach function of the PLURALS Transform. In this case, each +slice of the plural string will be run through a REPLACE_IN_TEXT operation. +Those slices are strings, so a REPLACE(path, key, …) isn't suitable for them. + + FTL.Message( + FTL.Identifier('delete-all'), + value=PLURALS( + 'aboutDownloads.dtd', + 'deleteAll', + EXTERNAL_ARGUMENT('num'), + lambda text: REPLACE_IN_TEXT( + text, + { + '#1': EXTERNAL_ARGUMENT('num') + } + ) + ) + ) +""" + +from __future__ import unicode_literals + +import fluent.syntax.ast as FTL +from .helpers import LITERAL + + +def evaluate(ctx, node): + def eval_node(subnode): + if isinstance(subnode, Transform): + return subnode(ctx) + else: + return subnode + + return node.traverse(eval_node) + + +class Transform(FTL.BaseNode): + def __call__(self, ctx): + raise NotImplementedError + + +class Source(Transform): + """Declare the source translation to be migrated with other transforms. + + When evaluated `Source` returns a simple string value. All \\uXXXX from + the original translations are converted beforehand to the literal + characters they encode. + + HTML entities are left unchanged for now because we can't know if they + should be converted to the characters they represent or not. Consider the + following example in which `&` could be replaced with the literal `&`: + + Privacy & History + + vs. these two examples where the HTML encoding should be preserved: + + Erreur ! + Use /help <command> for more information. + + """ + + # XXX Perhaps there's a strict subset of HTML entities which must or must + # not be replaced? + + def __init__(self, path, key): + self.path = path + self.key = key + + def __call__(self, ctx): + return ctx.get_source(self.path, self.key) + + +class COPY(Source): + """Create a Pattern with the translation value from the given source.""" + + def __call__(self, ctx): + source = super(self.__class__, self).__call__(ctx) + return LITERAL(source) + + +class REPLACE_IN_TEXT(Transform): + """Replace various placeables in the translation with FTL placeables. + + The original placeables are defined as keys on the `replacements` dict. + For each key the value is defined as a list of FTL Expressions to be + interpolated. + """ + + def __init__(self, value, replacements): + self.value = value + self.replacements = replacements + + def __call__(self, ctx): + + # Only replace placeable which are present in the translation. + replacements = { + key: evaluate(ctx, repl) + for key, repl in self.replacements.iteritems() + if key in self.value + } + + # Order the original placeables by their position in the translation. + keys_in_order = sorted( + replacements.keys(), + lambda x, y: self.value.find(x) - self.value.find(y) + ) + + # Used to reduce the `keys_in_order` list. + def replace(acc, cur): + """Convert original placeables and text into FTL Nodes. + + For each original placeable the translation will be partitioned + around it and the text before it will be converted into an + `FTL.TextElement` and the placeable will be replaced with its + replacement. The text following the placebale will be fed again to + the `replace` function. + """ + + parts, rest = acc + before, key, after = rest.value.partition(cur) + + placeable = FTL.Placeable(replacements[key]) + + # Return the elements found and converted so far, and the remaining + # text which hasn't been scanned for placeables yet. + return ( + parts + [FTL.TextElement(before), placeable], + FTL.TextElement(after) + ) + + def is_non_empty(elem): + """Used for filtering empty `FTL.TextElement` nodes out.""" + return not isinstance(elem, FTL.TextElement) or len(elem.value) + + # Start with an empty list of elements and the original translation. + init = ([], FTL.TextElement(self.value)) + parts, tail = reduce(replace, keys_in_order, init) + + # Explicitly concat the trailing part to get the full list of elements + # and filter out the empty ones. + elements = filter(is_non_empty, parts + [tail]) + + return FTL.Pattern(elements) + + +class REPLACE(Source): + """Create a Pattern with interpolations from given source. + + Interpolations in the translation value from the given source will be + replaced with FTL placeables using the `REPLACE_IN_TEXT` transform. + """ + + def __init__(self, path, key, replacements): + super(self.__class__, self).__init__(path, key) + self.replacements = replacements + + def __call__(self, ctx): + value = super(self.__class__, self).__call__(ctx) + return REPLACE_IN_TEXT(value, self.replacements)(ctx) + + +class PLURALS(Source): + """Create a Pattern with plurals from given source. + + Build an `FTL.SelectExpression` with the supplied `selector` and variants + extracted from the source. The source needs to be a semicolon-separated + list of variants. Each variant will be run through the `foreach` function, + which should return an `FTL.Node` or a `Transform`. + """ + + def __init__(self, path, key, selector, foreach=LITERAL): + super(self.__class__, self).__init__(path, key) + self.selector = selector + self.foreach = foreach + + def __call__(self, ctx): + value = super(self.__class__, self).__call__(ctx) + selector = evaluate(ctx, self.selector) + variants = value.split(';') + keys = ctx.plural_categories + last_index = min(len(variants), len(keys)) - 1 + + def createVariant(zipped_enum): + index, (key, variant) = zipped_enum + # Run the legacy variant through `foreach` which returns an + # `FTL.Node` describing the transformation required for each + # variant. Then evaluate it to a migrated FTL node. + value = evaluate(ctx, self.foreach(variant)) + return FTL.Variant( + key=FTL.Symbol(key), + value=value, + default=index == last_index + ) + + select = FTL.SelectExpression( + expression=selector, + variants=map(createVariant, enumerate(zip(keys, variants))) + ) + + placeable = FTL.Placeable(select) + return FTL.Pattern([placeable]) + + +class CONCAT(Transform): + """Concatenate elements of many patterns.""" + + def __init__(self, *patterns): + self.patterns = list(patterns) + + def __call__(self, ctx): + # Flatten the list of patterns of which each has a list of elements. + def concat_elements(acc, cur): + if isinstance(cur, FTL.Pattern): + acc.extend(cur.elements) + return acc + elif (isinstance(cur, FTL.TextElement) or + isinstance(cur, FTL.Placeable)): + acc.append(cur) + return acc + + raise RuntimeError( + 'CONCAT accepts FTL Patterns and Expressions.' + ) + + # Merge adjecent `FTL.TextElement` nodes. + def merge_adjecent_text(acc, cur): + if type(cur) == FTL.TextElement and len(acc): + last = acc[-1] + if type(last) == FTL.TextElement: + last.value += cur.value + else: + acc.append(cur) + else: + acc.append(cur) + return acc + + elements = reduce(concat_elements, self.patterns, []) + elements = reduce(merge_adjecent_text, elements, []) + return FTL.Pattern(elements) + + def traverse(self, fun): + def visit(value): + if isinstance(value, FTL.BaseNode): + return value.traverse(fun) + if isinstance(value, list): + return fun(map(visit, value)) + else: + return fun(value) + + node = self.__class__( + *[ + visit(value) for value in self.patterns + ] + ) + + return fun(node) + + def to_json(self): + def to_json(value): + if isinstance(value, FTL.BaseNode): + return value.to_json() + else: + return value + + return { + 'type': self.__class__.__name__, + 'patterns': [ + to_json(value) for value in self.patterns + ] + }
new file mode 100644 --- /dev/null +++ b/third_party/python/fluent/fluent/migrate/util.py @@ -0,0 +1,56 @@ +# coding=utf8 +from __future__ import unicode_literals + +import fluent.syntax.ast as FTL +from fluent.syntax.parser import FluentParser +from fluent.util import ftl + + +fluent_parser = FluentParser(with_spans=False) + + +def parse(Parser, string): + if Parser is FluentParser: + return fluent_parser.parse(string) + + # Parsing a legacy resource. + + # Parse the string into the internal Context. + parser = Parser() + # compare-locales expects ASCII strings. + parser.readContents(string.encode('utf8')) + # Transform the parsed result which is an iterator into a dict. + return {ent.key: ent for ent in parser} + + +def ftl_resource_to_ast(code): + return fluent_parser.parse(ftl(code)) + + +def ftl_resource_to_json(code): + return fluent_parser.parse(ftl(code)).to_json() + + +def ftl_message_to_json(code): + return fluent_parser.parse_entry(ftl(code)).to_json() + + +def to_json(merged_iter): + return { + path: resource.to_json() + for path, resource in merged_iter + } + + +def get_message(body, ident): + """Get message called `ident` from the `body` iterable.""" + for entity in body: + if isinstance(entity, FTL.Message) and entity.id.name == ident: + return entity + + +def get_transform(body, ident): + """Get entity called `ident` from the `body` iterable.""" + for transform in body: + if transform.id.name == ident: + return transform
new file mode 100644 --- /dev/null +++ b/third_party/python/fluent/fluent/syntax/__init__.py @@ -0,0 +1,12 @@ +from .parser import FluentParser +from .serializer import FluentSerializer + + +def parse(source, **kwargs): + parser = FluentParser(**kwargs) + return parser.parse(source) + + +def serialize(resource, **kwargs): + serializer = FluentSerializer(**kwargs) + return serializer.serialize(resource)
new file mode 100644 --- /dev/null +++ b/third_party/python/fluent/fluent/syntax/ast.py @@ -0,0 +1,309 @@ +from __future__ import unicode_literals +import sys +import json + + +def to_json(value): + if isinstance(value, BaseNode): + return value.to_json() + if isinstance(value, list): + return list(map(to_json, value)) + else: + return value + + +def from_json(value): + if isinstance(value, dict): + cls = getattr(sys.modules[__name__], value['type']) + args = { + k: from_json(v) + for k, v in value.items() + if k != 'type' + } + return cls(**args) + if isinstance(value, list): + return list(map(from_json, value)) + else: + return value + + +def scalars_equal(node1, node2, ignored_fields): + """Compare two nodes which are not lists.""" + + if type(node1) != type(node2): + return False + + if isinstance(node1, BaseNode): + return node1.equals(node2, ignored_fields) + + return node1 == node2 + + +class BaseNode(object): + """Base class for all Fluent AST nodes. + + All productions described in the ASDL subclass BaseNode, including Span and + Annotation. Implements __str__, to_json and traverse. + """ + + def traverse(self, fun): + """Postorder-traverse this node and apply `fun` to all child nodes. + + Traverse this node depth-first applying `fun` to subnodes and leaves. + Children are processed before parents (postorder traversal). + + Return a new instance of the node. + """ + + def visit(value): + """Call `fun` on `value` and its descendants.""" + if isinstance(value, BaseNode): + return value.traverse(fun) + if isinstance(value, list): + return fun(list(map(visit, value))) + else: + return fun(value) + + node = self.__class__( + **{ + name: visit(value) + for name, value in vars(self).items() + } + ) + + return fun(node) + + def equals(self, other, ignored_fields=['span']): + """Compare two nodes. + + Nodes are deeply compared on a field by field basis. If possible, False + is returned early. When comparing attributes, tags and variants in + SelectExpressions, the order doesn't matter. By default, spans are not + taken into account. + """ + + self_keys = set(vars(self).keys()) + other_keys = set(vars(other).keys()) + + if ignored_fields: + for key in ignored_fields: + self_keys.discard(key) + other_keys.discard(key) + + if self_keys != other_keys: + return False + + for key in self_keys: + field1 = getattr(self, key) + field2 = getattr(other, key) + + # List-typed nodes are compared item-by-item. When comparing + # attributes, tags and variants, the order of items doesn't matter. + if isinstance(field1, list) and isinstance(field2, list): + if len(field1) != len(field2): + return False + + # These functions are used to sort lists of items for when + # order doesn't matter. Annotations are also lists but they + # can't be keyed on any of their fields reliably. + field_sorting = { + 'attributes': lambda elem: elem.id.name, + 'tags': lambda elem: elem.name.name, + 'variants': lambda elem: elem.key.name, + } + + if key in field_sorting: + sorting = field_sorting[key] + field1 = sorted(field1, key=sorting) + field2 = sorted(field2, key=sorting) + + for elem1, elem2 in zip(field1, field2): + if not scalars_equal(elem1, elem2, ignored_fields): + return False + + elif not scalars_equal(field1, field2, ignored_fields): + return False + + return True + + def to_json(self): + obj = { + name: to_json(value) + for name, value in vars(self).items() + } + obj.update( + {'type': self.__class__.__name__} + ) + return obj + + def __str__(self): + return json.dumps(self.to_json()) + + +class SyntaxNode(BaseNode): + """Base class for AST nodes which can have Spans.""" + + def __init__(self, span=None, **kwargs): + super(SyntaxNode, self).__init__(**kwargs) + self.span = span + + def add_span(self, start, end): + self.span = Span(start, end) + + +class Resource(SyntaxNode): + def __init__(self, body=None, comment=None, **kwargs): + super(Resource, self).__init__(**kwargs) + self.body = body or [] + self.comment = comment + + +class Entry(SyntaxNode): + def __init__(self, annotations=None, **kwargs): + super(Entry, self).__init__(**kwargs) + self.annotations = annotations or [] + + def add_annotation(self, annot): + self.annotations.append(annot) + + +class Message(Entry): + def __init__(self, id, value=None, attributes=None, tags=None, + comment=None, **kwargs): + super(Message, self).__init__(**kwargs) + self.id = id + self.value = value + self.attributes = attributes or [] + self.tags = tags or [] + self.comment = comment + +class Pattern(SyntaxNode): + def __init__(self, elements, **kwargs): + super(Pattern, self).__init__(**kwargs) + self.elements = elements + +class TextElement(SyntaxNode): + def __init__(self, value, **kwargs): + super(TextElement, self).__init__(**kwargs) + self.value = value + +class Placeable(SyntaxNode): + def __init__(self, expression, **kwargs): + super(Placeable, self).__init__(**kwargs) + self.expression = expression + +class Expression(SyntaxNode): + def __init__(self, **kwargs): + super(Expression, self).__init__(**kwargs) + +class StringExpression(Expression): + def __init__(self, value, **kwargs): + super(StringExpression, self).__init__(**kwargs) + self.value = value + +class NumberExpression(Expression): + def __init__(self, value, **kwargs): + super(NumberExpression, self).__init__(**kwargs) + self.value = value + +class MessageReference(Expression): + def __init__(self, id, **kwargs): + super(MessageReference, self).__init__(**kwargs) + self.id = id + +class ExternalArgument(Expression): + def __init__(self, id, **kwargs): + super(ExternalArgument, self).__init__(**kwargs) + self.id = id + +class SelectExpression(Expression): + def __init__(self, expression, variants, **kwargs): + super(SelectExpression, self).__init__(**kwargs) + self.expression = expression + self.variants = variants + +class AttributeExpression(Expression): + def __init__(self, id, name, **kwargs): + super(AttributeExpression, self).__init__(**kwargs) + self.id = id + self.name = name + +class VariantExpression(Expression): + def __init__(self, id, key, **kwargs): + super(VariantExpression, self).__init__(**kwargs) + self.id = id + self.key = key + +class CallExpression(Expression): + def __init__(self, callee, args, **kwargs): + super(CallExpression, self).__init__(**kwargs) + self.callee = callee + self.args = args + +class Attribute(SyntaxNode): + def __init__(self, id, value, **kwargs): + super(Attribute, self).__init__(**kwargs) + self.id = id + self.value = value + +class Tag(SyntaxNode): + def __init__(self, name, **kwargs): + super(Tag, self).__init__(**kwargs) + self.name = name + +class Variant(SyntaxNode): + def __init__(self, key, value, default=False, **kwargs): + super(Variant, self).__init__(**kwargs) + self.key = key + self.value = value + self.default = default + +class NamedArgument(SyntaxNode): + def __init__(self, name, val, **kwargs): + super(NamedArgument, self).__init__(**kwargs) + self.name = name + self.val = val + +class Identifier(SyntaxNode): + def __init__(self, name, **kwargs): + super(Identifier, self).__init__(**kwargs) + self.name = name + +class Symbol(Identifier): + def __init__(self, name, **kwargs): + super(Symbol, self).__init__(name, **kwargs) + +class Comment(Entry): + def __init__(self, content=None, **kwargs): + super(Comment, self).__init__(**kwargs) + self.content = content + +class Section(Entry): + def __init__(self, name, comment=None, **kwargs): + super(Section, self).__init__(**kwargs) + self.name = name + self.comment = comment + +class Function(Identifier): + def __init__(self, name, **kwargs): + super(Function, self).__init__(name, **kwargs) + +class Junk(Entry): + def __init__(self, content=None, **kwargs): + super(Junk, self).__init__(**kwargs) + self.content = content + + +class Span(BaseNode): + def __init__(self, start, end, **kwargs): + super(Span, self).__init__(**kwargs) + self.start = start + self.end = end + + +class Annotation(SyntaxNode): + def __init__(self, code, args=None, message=None, **kwargs): + super(Annotation, self).__init__(**kwargs) + self.code = code + self.args = args or [] + self.message = message
new file mode 100644 --- /dev/null +++ b/third_party/python/fluent/fluent/syntax/errors.py @@ -0,0 +1,43 @@ +from __future__ import unicode_literals + + +class ParseError(Exception): + def __init__(self, code, *args): + self.code = code + self.args = args + self.message = get_error_message(code, args) + + +def get_error_message(code, args): + if code == 'E00001': + return 'Generic error' + if code == 'E0002': + return 'Expected an entry start' + if code == 'E0003': + return 'Expected token: "{}"'.format(args[0]) + if code == 'E0004': + return 'Expected a character from range: "{}"'.format(args[0]) + if code == 'E0005': + msg = 'Expected entry "{}" to have a value or attributes' + return msg.format(args[0]) + if code == 'E0006': + return 'Expected field: "{}"'.format(args[0]) + if code == 'E0007': + return 'Keyword cannot end with a whitespace' + if code == 'E0008': + return 'Callee has to be a simple identifier' + if code == 'E0009': + return 'Key has to be a simple identifier' + if code == 'E0010': + return 'Expected one of the variants to be marked as default (*)' + if code == 'E0011': + return 'Expected at least one variant after "->"' + if code == 'E0012': + return 'Tags cannot be added to messages with attributes' + if code == 'E0013': + return 'Expected variant key' + if code == 'E0014': + return 'Expected literal' + if code == 'E0015': + return 'Only one variant can be marked as default (*)' + return code
new file mode 100644 --- /dev/null +++ b/third_party/python/fluent/fluent/syntax/ftlstream.py @@ -0,0 +1,208 @@ +from __future__ import unicode_literals +from .stream import ParserStream +from .errors import ParseError + + +INLINE_WS = (' ', '\t') + + +class FTLParserStream(ParserStream): + def peek_inline_ws(self): + ch = self.current_peek() + while ch: + if ch not in INLINE_WS: + break + ch = self.peek() + + def skip_blank_lines(self): + while True: + self.peek_inline_ws() + + if self.current_peek_is('\n'): + self.skip_to_peek() + self.next() + else: + self.reset_peek() + break + + def skip_inline_ws(self): + while self.ch: + if self.ch not in INLINE_WS: + break + self.next() + + def expect_char(self, ch): + if self.ch == ch: + self.next() + return True + + if ch == '\n': + # Unicode Character 'SYMBOL FOR NEWLINE' (U+2424) + raise ParseError('E0003', '\u2424') + + raise ParseError('E0003', ch) + + def take_char_if(self, ch): + if self.ch == ch: + self.next() + return True + return False + + def take_char(self, f): + ch = self.ch + if ch is not None and f(ch): + self.next() + return ch + return None + + def is_id_start(self): + if self.ch is None: + return False + + cc = ord(self.ch) + + return (cc >= 97 and cc <= 122) or \ + (cc >= 65 and cc <= 90) or \ + cc == 95 + + def is_number_start(self): + cc = ord(self.ch) + + return (cc >= 48 and cc <= 57) or cc == 45 + + def is_peek_next_line_variant_start(self): + if not self.current_peek_is('\n'): + return False + + self.peek() + + ptr = self.get_peek_index() + + self.peek_inline_ws() + + if (self.get_peek_index() - ptr == 0): + self.reset_peek() + return False + + if self.current_peek_is('*'): + self.peek() + + if self.current_peek_is('[') and not self.peek_char_is('['): + self.reset_peek() + return True + + self.reset_peek() + return False + + def is_peek_next_line_attribute_start(self): + if not self.current_peek_is('\n'): + return False + + self.peek() + + ptr = self.get_peek_index() + + self.peek_inline_ws() + + if (self.get_peek_index() - ptr == 0): + self.reset_peek() + return False + + if self.current_peek_is('.'): + self.reset_peek() + return True + + self.reset_peek() + return False + + def is_peek_next_line_pattern(self): + if not self.current_peek_is('\n'): + return False + + self.peek() + + ptr = self.get_peek_index() + + self.peek_inline_ws() + + if (self.get_peek_index() - ptr == 0): + self.reset_peek() + return False + + if (self.current_peek_is('}') or + self.current_peek_is('.') or + self.current_peek_is('#') or + self.current_peek_is('[') or + self.current_peek_is('*')): + self.reset_peek() + return False + + self.reset_peek() + return True + + def is_peek_next_line_tag_start(self): + + if not self.current_peek_is('\n'): + return False + + self.peek() + + ptr = self.get_peek_index() + + self.peek_inline_ws() + + if (self.get_peek_index() - ptr == 0): + self.reset_peek() + return False + + if self.current_peek_is('#'): + self.reset_peek() + return True + + self.reset_peek() + return False + + def skip_to_next_entry_start(self): + while self.ch: + if self.current_is('\n') and not self.peek_char_is('\n'): + self.next() + + if self.ch is None or self.is_id_start() or \ + (self.current_is('/') and self.peek_char_is('/')) or \ + (self.current_is('[') and self.peek_char_is('[')): + break + self.next() + + def take_id_start(self): + if self.is_id_start(): + ret = self.ch + self.next() + return ret + + raise ParseError('E0004', 'a-zA-Z_') + + def take_id_char(self): + def closure(ch): + cc = ord(ch) + return ((cc >= 97 and cc <= 122) or + (cc >= 65 and cc <= 90) or + (cc >= 48 and cc <= 57) or + cc == 95 or cc == 45) + return self.take_char(closure) + + def take_symb_char(self): + def closure(ch): + if ch is None: + return False + cc = ord(ch) + return (cc >= 97 and cc <= 122) or \ + (cc >= 65 and cc <= 90) or \ + (cc >= 48 and cc <= 57) or \ + cc == 95 or cc == 45 or cc == 32 + return self.take_char(closure) + + def take_digit(self): + def closure(ch): + cc = ord(ch) + return (cc >= 48 and cc <= 57) + return self.take_char(closure)
new file mode 100644 --- /dev/null +++ b/third_party/python/fluent/fluent/syntax/parser.py @@ -0,0 +1,579 @@ +from __future__ import unicode_literals +import re +from .ftlstream import FTLParserStream +from . import ast +from .errors import ParseError + + +def with_span(fn): + def decorated(self, ps, *args): + if not self.with_spans: + return fn(self, ps, *args) + + start = ps.get_index() + node = fn(self, ps, *args) + + # Don't re-add the span if the node already has it. This may happen + # when one decorated function calls another decorated function. + if node.span is not None: + return node + + # Spans of Messages and Sections should include the attached Comment. + if isinstance(node, ast.Message) or isinstance(node, ast.Section): + if node.comment is not None: + start = node.comment.span.start + + end = ps.get_index() + node.add_span(start, end) + return node + + return decorated + + +class FluentParser(object): + def __init__(self, with_spans=True): + self.with_spans = with_spans + + def parse(self, source): + comment = None + + ps = FTLParserStream(source) + ps.skip_blank_lines() + + entries = [] + + while ps.current(): + entry = self.get_entry_or_junk(ps) + + if isinstance(entry, ast.Comment) and len(entries) == 0: + comment = entry + else: + entries.append(entry) + + ps.skip_blank_lines() + + res = ast.Resource(entries, comment) + + if self.with_spans: + res.add_span(0, ps.get_index()) + + return res + + def parse_entry(self, source): + ps = FTLParserStream(source) + ps.skip_blank_lines() + return self.get_entry_or_junk(ps) + + def get_entry_or_junk(self, ps): + entry_start_pos = ps.get_index() + + try: + return self.get_entry(ps) + except ParseError as err: + error_index = ps.get_index() + ps.skip_to_next_entry_start() + next_entry_start = ps.get_index() + + # Create a Junk instance + slice = ps.get_slice(entry_start_pos, next_entry_start) + junk = ast.Junk(slice) + if self.with_spans: + junk.add_span(entry_start_pos, next_entry_start) + annot = ast.Annotation(err.code, err.args, err.message) + annot.add_span(error_index, error_index) + junk.add_annotation(annot) + return junk + + def get_entry(self, ps): + comment = None + + if ps.current_is('/'): + comment = self.get_comment(ps) + + if ps.current_is('['): + return self.get_section(ps, comment) + + if ps.is_id_start(): + return self.get_message(ps, comment) + + if comment: + return comment + + raise ParseError('E0002') + + @with_span + def get_comment(self, ps): + ps.expect_char('/') + ps.expect_char('/') + ps.take_char_if(' ') + + content = '' + + def until_eol(x): + return x != '\n' + + while True: + ch = ps.take_char(until_eol) + while ch: + content += ch + ch = ps.take_char(until_eol) + + ps.next() + + if ps.current_is('/'): + content += '\n' + ps.next() + ps.expect_char('/') + ps.take_char_if(' ') + else: + break + + return ast.Comment(content) + + @with_span + def get_section(self, ps, comment): + ps.expect_char('[') + ps.expect_char('[') + + ps.skip_inline_ws() + + symb = self.get_symbol(ps) + + ps.skip_inline_ws() + + ps.expect_char(']') + ps.expect_char(']') + + ps.skip_inline_ws() + + ps.expect_char('\n') + + return ast.Section(symb, comment) + + @with_span + def get_message(self, ps, comment): + id = self.get_identifier(ps) + + ps.skip_inline_ws() + + pattern = None + attrs = None + tags = None + + if ps.current_is('='): + ps.next() + ps.skip_inline_ws() + + pattern = self.get_pattern(ps) + + if ps.is_peek_next_line_attribute_start(): + attrs = self.get_attributes(ps) + + if ps.is_peek_next_line_tag_start(): + if attrs is not None: + raise ParseError('E0012') + tags = self.get_tags(ps) + + if pattern is None and attrs is None: + raise ParseError('E0005', id.name) + + return ast.Message(id, pattern, attrs, tags, comment) + + @with_span + def get_attribute(self, ps): + ps.expect_char('.') + + key = self.get_identifier(ps) + + ps.skip_inline_ws() + ps.expect_char('=') + ps.skip_inline_ws() + + value = self.get_pattern(ps) + + if value is None: + raise ParseError('E0006', 'value') + + return ast.Attribute(key, value) + + def get_attributes(self, ps): + attrs = [] + + while True: + ps.expect_char('\n') + ps.skip_inline_ws() + + attr = self.get_attribute(ps) + attrs.append(attr) + + if not ps.is_peek_next_line_attribute_start(): + break + return attrs + + @with_span + def get_tag(self, ps): + ps.expect_char('#') + symb = self.get_symbol(ps) + return ast.Tag(symb) + + def get_tags(self, ps): + tags = [] + + while True: + ps.expect_char('\n') + ps.skip_inline_ws() + + tag = self.get_tag(ps) + tags.append(tag) + + if not ps.is_peek_next_line_tag_start(): + break + return tags + + @with_span + def get_identifier(self, ps): + name = '' + + name += ps.take_id_start() + + ch = ps.take_id_char() + while ch: + name += ch + ch = ps.take_id_char() + + return ast.Identifier(name) + + def get_variant_key(self, ps): + ch = ps.current() + + if ch is None: + raise ParseError('E0013') + + if ps.is_number_start(): + return self.get_number(ps) + + return self.get_symbol(ps) + + @with_span + def get_variant(self, ps, has_default): + default_index = False + + if ps.current_is('*'): + if has_default: + raise ParseError('E0015') + ps.next() + default_index = True + + ps.expect_char('[') + + key = self.get_variant_key(ps) + + ps.expect_char(']') + + ps.skip_inline_ws() + + value = self.get_pattern(ps) + + if value is None: + raise ParseError('E0006', 'value') + + return ast.Variant(key, value, default_index) + + def get_variants(self, ps): + variants = [] + has_default = False + + while True: + ps.expect_char('\n') + ps.skip_inline_ws() + + variant = self.get_variant(ps, has_default) + + if variant.default: + has_default = True + + variants.append(variant) + + if not ps.is_peek_next_line_variant_start(): + break + + if not has_default: + raise ParseError('E0010') + + return variants + + @with_span + def get_symbol(self, ps): + name = '' + + name += ps.take_id_start() + + while True: + ch = ps.take_symb_char() + if ch: + name += ch + else: + break + + return ast.Symbol(name.rstrip()) + + def get_digits(self, ps): + num = '' + + ch = ps.take_digit() + while ch: + num += ch + ch = ps.take_digit() + + if len(num) == 0: + raise ParseError('E0004', '0-9') + + return num + + @with_span + def get_number(self, ps): + num = '' + + if ps.current_is('-'): + num += '-' + ps.next() + + num += self.get_digits(ps) + + if ps.current_is('.'): + num += '.' + ps.next() + num += self.get_digits(ps) + + return ast.NumberExpression(num) + + @with_span + def get_pattern(self, ps): + elements = [] + ps.skip_inline_ws() + + # Special-case: trim leading whitespace and newlines. + if ps.is_peek_next_line_pattern(): + ps.skip_blank_lines() + ps.skip_inline_ws() + + while ps.current(): + ch = ps.current() + + # The end condition for get_pattern's while loop is a newline + # which is not followed by a valid pattern continuation. + if ch == '\n' and not ps.is_peek_next_line_pattern(): + break + + if ch == '{': + element = self.get_placeable(ps) + else: + element = self.get_text_element(ps) + + elements.append(element) + + return ast.Pattern(elements) + + @with_span + def get_text_element(self, ps): + buf = '' + + while ps.current(): + ch = ps.current() + + if ch == '{': + return ast.TextElement(buf) + + if ch == '\n': + if not ps.is_peek_next_line_pattern(): + return ast.TextElement(buf) + + ps.next() + ps.skip_inline_ws() + + # Add the new line to the buffer + buf += ch + continue + + if ch == '\\': + ch2 = ps.next() + + if ch2 == '{' or ch2 == '"': + buf += ch2 + else: + buf += ch + ch2 + + else: + buf += ch + + ps.next() + + return ast.TextElement(buf) + + @with_span + def get_placeable(self, ps): + ps.expect_char('{') + expression = self.get_expression(ps) + ps.expect_char('}') + return ast.Placeable(expression) + + @with_span + def get_expression(self, ps): + + if ps.is_peek_next_line_variant_start(): + variants = self.get_variants(ps) + + ps.expect_char('\n') + ps.expect_char(' ') + ps.skip_inline_ws() + + return ast.SelectExpression(None, variants) + + ps.skip_inline_ws() + + selector = self.get_selector_expression(ps) + + ps.skip_inline_ws() + + if ps.current_is('-'): + ps.peek() + if not ps.current_peek_is('>'): + ps.reset_peek() + else: + ps.next() + ps.next() + + ps.skip_inline_ws() + + variants = self.get_variants(ps) + + if len(variants) == 0: + raise ParseError('E0011') + + ps.expect_char('\n') + ps.expect_char(' ') + ps.skip_inline_ws() + + return ast.SelectExpression(selector, variants) + + return selector + + @with_span + def get_selector_expression(self, ps): + literal = self.get_literal(ps) + + if not isinstance(literal, ast.MessageReference): + return literal + + ch = ps.current() + + if (ch == '.'): + ps.next() + attr = self.get_identifier(ps) + return ast.AttributeExpression(literal.id, attr) + + if (ch == '['): + ps.next() + key = self.get_variant_key(ps) + ps.expect_char(']') + return ast.VariantExpression(literal.id, key) + + if (ch == '('): + ps.next() + + args = self.get_call_args(ps) + + ps.expect_char(')') + + if not re.match('^[A-Z_-]+$', literal.id.name): + raise ParseError('E0008') + + return ast.CallExpression( + ast.Function(literal.id.name), + args + ) + + return literal + + @with_span + def get_call_arg(self, ps): + exp = self.get_selector_expression(ps) + + ps.skip_inline_ws() + + if not ps.current_is(':'): + return exp + + if not isinstance(exp, ast.MessageReference): + raise ParseError('E0009') + + ps.next() + ps.skip_inline_ws() + + val = self.get_arg_val(ps) + + return ast.NamedArgument(exp.id, val) + + def get_call_args(self, ps): + args = [] + + ps.skip_inline_ws() + + while True: + if ps.current_is(')'): + break + + arg = self.get_call_arg(ps) + args.append(arg) + + ps.skip_inline_ws() + + if ps.current_is(','): + ps.next() + ps.skip_inline_ws() + continue + else: + break + + return args + + def get_arg_val(self, ps): + if ps.is_number_start(): + return self.get_number(ps) + elif ps.current_is('"'): + return self.get_string(ps) + raise ParseError('E0006', 'value') + + @with_span + def get_string(self, ps): + val = '' + + ps.expect_char('"') + + ch = ps.take_char(lambda x: x != '"') + while ch: + val += ch + ch = ps.take_char(lambda x: x != '"') + + ps.next() + + return ast.StringExpression(val) + + @with_span + def get_literal(self, ps): + ch = ps.current() + + if ch is None: + raise ParseError('E0014') + + if ps.is_number_start(): + return self.get_number(ps) + elif ch == '"': + return self.get_string(ps) + elif ch == '$': + ps.next() + name = self.get_identifier(ps) + return ast.ExternalArgument(name) + + name = self.get_identifier(ps) + return ast.MessageReference(name)
new file mode 100644 --- /dev/null +++ b/third_party/python/fluent/fluent/syntax/serializer.py @@ -0,0 +1,273 @@ +from __future__ import unicode_literals +from . import ast + + +def indent(content): + return " ".join( + content.splitlines(True) + ) + + +def contain_new_line(elems): + return bool([ + elem for elem in elems + if isinstance(elem, ast.TextElement) and "\n" in elem.value + ]) + + +class FluentSerializer(object): + def __init__(self, with_junk=False): + self.with_junk = with_junk + + def serialize(self, resource): + parts = [] + if resource.comment: + parts.append( + "{}\n\n".format( + serialize_comment(resource.comment) + ) + ) + for entry in resource.body: + if not isinstance(entry, ast.Junk) or self.with_junk: + parts.append(self.serialize_entry(entry)) + + return "".join(parts) + + def serialize_entry(self, entry): + if isinstance(entry, ast.Message): + return serialize_message(entry) + if isinstance(entry, ast.Section): + return serialize_section(entry) + if isinstance(entry, ast.Comment): + return "\n{}\n\n".format(serialize_comment(entry)) + if isinstance(entry, ast.Junk): + return serialize_junk(entry) + raise Exception('Unknown entry type: {}'.format(entry.type)) + + +def serialize_comment(comment): + return "".join([ + "{}{}".format("// ", line) + for line in comment.content.splitlines(True) + ]) + + +def serialize_section(section): + if section.comment: + return "\n\n{}\n[[ {} ]]\n\n".format( + serialize_comment(section.comment), + serialize_symbol(section.name) + ) + else: + return "\n\n[[ {} ]]\n\n".format( + serialize_symbol(section.name) + ) + + +def serialize_junk(junk): + return junk.content + + +def serialize_message(message): + parts = [] + + if message.comment: + parts.append(serialize_comment(message.comment)) + parts.append("\n") + + parts.append(serialize_identifier(message.id)) + + if message.value: + parts.append(" =") + parts.append(serialize_value(message.value)) + + if message.tags: + for tag in message.tags: + parts.append(serialize_tag(tag)) + + if message.attributes: + for attribute in message.attributes: + parts.append(serialize_attribute(attribute)) + + parts.append("\n") + + return ''.join(parts) + + +def serialize_tag(tag): + return "\n #{}".format( + serialize_symbol(tag.name), + ) + + +def serialize_attribute(attribute): + return "\n .{} ={}".format( + serialize_identifier(attribute.id), + indent(serialize_value(attribute.value)) + ) + + +def serialize_value(pattern): + multi = contain_new_line(pattern.elements) + schema = "\n {}" if multi else " {}" + + content = serialize_pattern(pattern) + return schema.format(indent(content)) + + +def serialize_pattern(pattern): + return "".join([ + serialize_element(elem) + for elem in pattern.elements + ]) + + +def serialize_element(element): + if isinstance(element, ast.TextElement): + return serialize_text_element(element) + if isinstance(element, ast.Placeable): + return serialize_placeable(element) + raise Exception('Unknown element type: {}'.format(element.type)) + + +def serialize_text_element(text): + return text.value + + +def serialize_placeable(placeable): + expr = placeable.expression + + if isinstance(expr, ast.Placeable): + return "{{{}}}".format( + serialize_placeable(expr)) + if isinstance(expr, ast.SelectExpression): + return "{{{}}}".format( + serialize_select_expression(expr)) + if isinstance(expr, ast.Expression): + return "{{ {} }}".format( + serialize_expression(expr)) + + +def serialize_expression(expression): + if isinstance(expression, ast.StringExpression): + return serialize_string_expression(expression) + if isinstance(expression, ast.NumberExpression): + return serialize_number_expression(expression) + if isinstance(expression, ast.MessageReference): + return serialize_message_reference(expression) + if isinstance(expression, ast.ExternalArgument): + return serialize_external_argument(expression) + if isinstance(expression, ast.AttributeExpression): + return serialize_attribute_expression(expression) + if isinstance(expression, ast.VariantExpression): + return serialize_variant_expression(expression) + if isinstance(expression, ast.CallExpression): + return serialize_call_expression(expression) + raise Exception('Unknown expression type: {}'.format(expression.type)) + + +def serialize_string_expression(expr): + return "\"{}\"".format(expr.value) + + +def serialize_number_expression(expr): + return expr.value + + +def serialize_message_reference(expr): + return serialize_identifier(expr.id) + + +def serialize_external_argument(expr): + return "${}".format(serialize_identifier(expr.id)) + + +def serialize_select_expression(expr): + parts = [] + + if expr.expression: + selector = " {} ->".format( + serialize_expression(expr.expression) + ) + parts.append(selector) + + for variant in expr.variants: + parts.append(serialize_variant(variant)) + + parts.append("\n") + + return "".join(parts) + + +def serialize_variant(variant): + return "\n{}[{}]{}".format( + " *" if variant.default else " ", + serialize_variant_key(variant.key), + indent(serialize_value(variant.value)) + ) + + +def serialize_attribute_expression(expr): + return "{}.{}".format( + serialize_identifier(expr.id), + serialize_identifier(expr.name), + ) + + +def serialize_variant_expression(expr): + return "{}[{}]".format( + serialize_identifier(expr.id), + serialize_variant_key(expr.key), + ) + + +def serialize_call_expression(expr): + return "{}({})".format( + serialize_function(expr.callee), + ", ".join([ + serialize_call_argument(arg) + for arg in expr.args + ]) + ) + + +def serialize_call_argument(arg): + if isinstance(arg, ast.Expression): + return serialize_expression(arg) + if isinstance(arg, ast.NamedArgument): + return serialize_named_argument(arg) + + +def serialize_named_argument(arg): + return "{}: {}".format( + serialize_identifier(arg.name), + serialize_argument_value(arg.val) + ) + + +def serialize_argument_value(argval): + if isinstance(argval, ast.StringExpression): + return serialize_string_expression(argval) + if isinstance(argval, ast.NumberExpression): + return serialize_number_expression(argval) + raise Exception('Unknown argument type: {}'.format(argval.type)) + + +def serialize_identifier(identifier): + return identifier.name + + +def serialize_symbol(symbol): + return symbol.name + + +def serialize_variant_key(key): + if isinstance(key, ast.Symbol): + return serialize_symbol(key) + if isinstance(key, ast.NumberExpression): + return serialize_number_expression(key) + raise Exception('Unknown variant key type: {}'.format(key.type)) + + +def serialize_function(function): + return function.name
new file mode 100644 --- /dev/null +++ b/third_party/python/fluent/fluent/syntax/stream.py @@ -0,0 +1,120 @@ +from __future__ import unicode_literals + + +class StringIter(): + def __init__(self, source): + self.source = source + self.len = len(source) + self.i = 0 + + def next(self): + if self.i < self.len: + ret = self.source[self.i] + self.i += 1 + return ret + return None + + def get_slice(self, start, end): + return self.source[start:end] + + +class ParserStream(): + def __init__(self, string): + self.iter = StringIter(string) + self.buf = [] + self.peek_index = 0 + self.index = 0 + + self.ch = None + + self.iter_end = False + self.peek_end = False + + self.ch = self.iter.next() + + def next(self): + if self.iter_end: + return None + + if len(self.buf) == 0: + self.ch = self.iter.next() + else: + self.ch = self.buf.pop(0) + + self.index += 1 + + if self.ch == None: + self.iter_end = True + self.peek_end = True + + self.peek_index = self.index + + return self.ch + + def current(self): + return self.ch + + def current_is(self, ch): + return self.ch == ch + + def current_peek(self): + if self.peek_end: + return None + + diff = self.peek_index - self.index + + if diff == 0: + return self.ch + return self.buf[diff - 1] + + def current_peek_is(self, ch): + return self.current_peek() == ch + + def peek(self): + if self.peek_end: + return None + + self.peek_index += 1 + + diff = self.peek_index - self.index + + if diff > len(self.buf): + ch = self.iter.next() + if ch is not None: + self.buf.append(ch) + else: + self.peek_end = True + return None + + return self.buf[diff - 1] + + def get_index(self): + return self.index + + def get_peek_index(self): + return self.peek_index + + def peek_char_is(self, ch): + if self.peek_end: + return False + + ret = self.peek() + + self.peek_index -= 1 + + return ret == ch + + def reset_peek(self): + self.peek_index = self.index + self.peek_end = self.iter_end + + def skip_to_peek(self): + diff = self.peek_index - self.index + + for i in range(0, diff): + self.ch = self.buf.pop(0) + + self.index = self.peek_index + + def get_slice(self, start, end): + return self.iter.get_slice(start, end)
new file mode 100644 --- /dev/null +++ b/third_party/python/fluent/fluent/util.py @@ -0,0 +1,42 @@ +# coding=utf8 +import textwrap + +import fluent.syntax.ast as FTL + + +def ftl(code): + """Nicer indentation for FTL code. + + The code returned by this function is meant to be compared against the + output of the FTL Serializer. The input code will end with a newline to + match the output of the serializer. + """ + + # The code might be triple-quoted. + code = code.lstrip('\n') + + return textwrap.dedent(code) + + +def fold(fun, node, init): + """Reduce `node` to a single value using `fun`. + + Apply `fun` against an accumulator and each subnode of `node` (in postorder + traversal) to reduce it to a single value. + """ + + def fold_(vals, acc): + if not vals: + return acc + + head = list(vals)[0] + tail = list(vals)[1:] + + if isinstance(head, FTL.BaseNode): + acc = fold(fun, head, acc) + if isinstance(head, list): + acc = fold_(head, acc) + + return fold_(tail, fun(acc, head)) + + return fold_(vars(node).values(), init)
new file mode 100644 --- /dev/null +++ b/third_party/python/fluent/setup.cfg @@ -0,0 +1,4 @@ +[egg_info] +tag_build = +tag_date = 0 +
new file mode 100644 --- /dev/null +++ b/third_party/python/fluent/setup.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python + +from setuptools import setup + +setup(name='fluent', + version='0.4.2', + description='Localization library for expressive translations.', + author='Mozilla', + author_email='l10n-drivers@mozilla.org', + license='APL 2', + url='https://github.com/projectfluent/python-fluent', + keywords=['fluent', 'localization', 'l10n'], + classifiers=[ + 'Development Status :: 3 - Alpha', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: Apache Software License', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3.5', + ], + packages=['fluent', 'fluent.syntax', 'fluent.migrate'], + package_data={ + 'fluent.migrate': ['cldr_data/*'] + } + )