• Documentation
  • File formats
  • JSON with ICU Plurals

JSON with ICU Plurals

  • File extension: json
  • i18n type: KEYVALUEJSON

This format is used by many JavaScript frameworks such as AngularJS and React. It's a JSON file where every key is associated with a value (a nested JSON document, a nested list, or a translation string).

For example:

    "join": "Join",
    "nest": {
      "split": "Split",
      "another_nest": {
        "split": "Split"
    "list": ["List", "Values", {"JSON": {"Embedded": "Document"}}]  
    "files": "{count, plural, one {{count} file.} other {{count} files.}}"

Plurals Support

Transifex offers support for pluralized entries in JSON based on ICU's message format specifications (plural subset). For more details about how pluralized strings are handled by Transifex, please refer to our documentation guide here.

Below, there are some illustrative examples based on the language plural rules that CLDR standards provide:

English file:

  "files": "{count, plural, one {You have {count} file.} other {You have {count} files.}}"

Russian file:

  "files": "{count, plural, one {У вас есть файл {count}.} few {У вас есть файлы {count}.} many {У вас есть файлы {count}.} other {У вас есть файлы {count}.}}"

Croatian file:

  "files": "{count, plural, one {Imate {count} datoteku.} few {Imate {count} datoteke.} other {Imate {count} datoteke.}}"

API Usage

How we calculate each hash

At Transifex we use some special techniques to parse and compile JSON files. These techniques, unfortunately, lead to special treatment of a string's respective key. For example, if we were to use a 'flat' JSON file such as this:

# Simple JSON
    "The cat": "Die Katze",
    "The dog": "Der Hund",
    "The bird": "Der Vogel"

The hash can be easily calculated e.g. MD5("The cat"). However, with nested files, such as this:

# Complex JSON
    "Colours": ["Red", "Blue", "Green", "Yellow"],
    "Vehicles": {"Car": "das Auto",
                 "Bike": "das Fahrrad"}

We must consider the string's root("Colours") and its location within the list. Therefore, we use a form of notation to mark each strings' location:

. = a JSON nest e.g. "Vehicles.Car"

..N.. = the Nth item within a list e.g. "Colours..0.."

Using this notation we can represent the total path in a single string. The previously defined complex JSON would then be parsed into:

(path, string)

"Colours..0.." , "Red"
"Colours..1.." , "Blue"
"Colours..2.." , "Green"
"Colours..3.." , "Yellow"

"Vehicles.Car"  , "das Auto"
"Vehicles.Bike" , "das Fahrrad"

What should you do?

Escape special characters

So, at this point you've probably noticed that . characters are important, very important. Therefore, if you wish to calculate the hash of a JSON string, you must escape all \ and . characters before calculating the hash. This can be achieved using an algorithm (Python) similar to the following:

from hashlib import md5

# Escape backslash characters
source_string = source_string.replace(r'\', r'\\')

#Escape dot characters
source_string = source_string.replace('.', r'\.')

# JSON doesn't use context. Use an empty string instead
keys = [source_string, '']

return md5(':'.join(keys).encode('utf-8')).hexdigest()

Calculate nest notation

Additionally, if your JSON file is nested then you must also calculate the string's path, including its nested notation. This can be achieved using an algorithm (Python) similar to the following:

from hashlib import md5

def escape(key):
    key = key.replace('\', r'\\')
    return key.replace('.', r'\.')

def generate_hashes_with_strings(nest_value, nest_key='', order=0):

    # Are we now looking at a list or a dict?
    if isinstance(nest_value, dict):
        iter_tuple = nest_value.iteritems()
        in_list = False
        iter_tuple = enumerate(nest_value)
        in_list = True

    # Loop through each element and re-call this function
    # if it's a list or a dict.
    for key, value in iter_tuple:
        if not in_list:
            escaped_key = escape(key)
            escaped_key = u'..{}..'.format(key)

        if isinstance(value, dict):
            new_nest = '{}{}{}'.format(nest_key, escaped_key, '.')
            for key, value in generate_hashes_with_strings(value, new_nest, order):
                yield key, value
        elif isinstance(value, list):
            new_nest = '{}{}'.format(nest_key, escaped_key)
            for key, value in generate_hashes_with_strings(value, new_nest, order):
                yield key, value
            entity_key = u'{}{}'.format(nest_key, escaped_key)

            keys = [entity_key, '']
            hashed_keys = md5(':'.join(keys).encode('utf-8')).hexdigest()
            yield hashed_keys, value

        order += 1

Parser Behaviour When Pulling Translations via the API/CLI or Exporting Translations via the UI

  • The following table outlines what occurs to untranslated, unreviewed and un-proofread strings when using the API, CLI or UI to manipulate translation files.


* Proofreading needs to be enabled as an option for this logic to take effect. To learn how to enable proofreading click here.