bug 1382005, vendor new dependency for compare-locales, python-fluent, r?stas draft
authorAxel Hecht <axel@pike.org>
Tue, 12 Sep 2017 11:27:49 +0200
changeset 666992 478035c832f0d9fe767e186be6442160bf2aa392
parent 666884 e4261f5b96ebfd63e7cb8af3035ff9fea90c74a5
child 666993 33402ceaadc8d22a545b2eff5d7d09174ef8e476
push id80582
push useraxel@mozilla.com
push dateTue, 19 Sep 2017 14:37:32 +0000
reviewersstas
bugs1382005
milestone57.0a1
bug 1382005, vendor new dependency for compare-locales, python-fluent, r?stas This is the 0.4.2 release of python-fluent. MozReview-Commit-ID: 6d4byIf7EES
build/virtualenv_packages.txt
third_party/python/fluent/PKG-INFO
third_party/python/fluent/fluent/__init__.py
third_party/python/fluent/fluent/migrate/__init__.py
third_party/python/fluent/fluent/migrate/changesets.py
third_party/python/fluent/fluent/migrate/cldr.py
third_party/python/fluent/fluent/migrate/cldr_data/plurals.json
third_party/python/fluent/fluent/migrate/cldr_data/unicode-license.txt
third_party/python/fluent/fluent/migrate/context.py
third_party/python/fluent/fluent/migrate/helpers.py
third_party/python/fluent/fluent/migrate/merge.py
third_party/python/fluent/fluent/migrate/transforms.py
third_party/python/fluent/fluent/migrate/util.py
third_party/python/fluent/fluent/syntax/__init__.py
third_party/python/fluent/fluent/syntax/ast.py
third_party/python/fluent/fluent/syntax/errors.py
third_party/python/fluent/fluent/syntax/ftlstream.py
third_party/python/fluent/fluent/syntax/parser.py
third_party/python/fluent/fluent/syntax/serializer.py
third_party/python/fluent/fluent/syntax/stream.py
third_party/python/fluent/fluent/util.py
third_party/python/fluent/setup.cfg
third_party/python/fluent/setup.py
--- 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
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 `&amp;` could be replaced with the literal `&`:
+
+        Privacy &amp; History
+
+    vs. these two examples where the HTML encoding should be preserved:
+
+        Erreur&nbsp;!
+        Use /help &lt;command&gt; 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/*']
+      }
+      )