JSON with ICU Plurals

Learn how to localize JSON key-value files (.json) using Transifex.

Nina avatar
Written by Nina
Updated over a week ago

File Extension(s)

.json

i18n type(s)

KEYVALUEJSON

Many JavaScript frameworks, such as AngularJS and React, use this format. 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 supports pluralized entries in JSON based on ICU's message format specifications (plural subset). For more details about how Transifex handles pluralized strings, please take a look at our documentation guide here.

Below 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.}}"
}

📝Note: Do you want to find out more about plurals in Transifex? Please check here.


Escape behavior


API Usage

How do 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 string's 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 essential. 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, you must 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
else:
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)
else:
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
else:
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

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

* The results are compatible with parser version 2.

📝 Note: Proofreading must be enabled for this logic to take effect. To learn how to enable proofreading, click here.


Default placeholders

These are the default placeholders that you could have in your file, and they will be recognized:

match: ['%1$s', '%(key1)s', '%s', '%d', '%.2f', '%-5d', '%+2d']


Context

When a JSON file is updated, you can maintain the instructions and tags you used previously. This can be done by ensuring the key IDs are the same when changing the string value.


💡Tip

Looking for more help? Get support from our Transifex Community Forum!

Find answers or post to get help from Transifex Support and our Community.

Did this answer your question?