Title: JShelter can be translated into different languages
Date: 2023-09-08 14:00
Series: i18n
series_index: 1

JShelter's audience is international. As not all people speak English, JShelter
is now adding support for internationalization. Hence, it can be translated
into languages other than English.

The webextension API already contains
[APIs](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/i18n)
that help to
[translate extensions](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Internationalization).
This way we can localize not only the interface of the extension but also its
description and even the name.

[TOC]

### Status

JShelter can be translated to other languages as of version 0.14. Besides
English, Czech and Russian translations are also available.

Nevertheless, we are looking for translators in other languages. If you are
willing to translate JShelter to a language that you know, please read on. We
suggest that you contact us early so that we have information about ongoing
translations and that we can tell you if someone else is already interested in
the same language as you.

### How can I translate JShelter? Option 1: Weblate

We use [Weblate](https://github.com/WeblateOrg/weblate) to manage the
translations. This software helps us monitor the status of the translations,
providing suggestions and improving the lives of the translators. If you want
to help, visit
[JShelter on Weblate](https://hosted.weblate.org/projects/jshelter/webextension/).
You can send us suggestions without registration. However, some features, like
notifications, are only available to registered users.

When you start translating, you should see a form like:

![A screenshot from Weblate]({attach}/images/i18n/webly.png)

The interface shows:

1. The name of the key of the currently translated string,
2. the original English translation,
3. the description of the usage of the string,
4. the translation to the current target language,
5. the information on similar and nearby strings that might be handy to grasp the context of the
   string,
6. glossary containing key terms used in the project.

Most of the strings that translators face are simple, as shown above. However,
some strings are composed with `placeholders` (see the translation option 2 for
more details on placeholders). For example, consider
[string `defaultLevelSelection`](https://hosted.weblate.org/translate/jshelter/webextension/en/?checksum=a11db49ff616c6f0&sort_by=-priority,position).
Its English translation is `Default level ($levelName$)`. Strings enclosed by
`$` signs (`levelName` in this case) are `placeholders`. Each placeholder is in
the database with the key composed as `the base key ###placeholders###
placeholder name`. So, in this case, the key of the placeholder is
`defaultLevelSelection###placeholders###levelName`.

Most placeholders should not be changed. The typical content of such
placeholders is like `$1`. It means that the extension generates the content,
and typically, translators are supposed to keep the string intact. Always read
carefully the description of the usage of the string that indicates how the
string should be translated.

Some placeholder strings contain the names of APIs or other technical terms.
For example, `jssgroupTimePrecisionDescription2###placeholders###apis` in
English contains `(Date, Performance, events, Gamepad API, and Web VR API)`.
Please read the instructions for each string carefully. In this case,
translators should keep the names of the APIs but translate the punctuation and
conjunctions.

Please consider the translations of each placeholder in the context of the base
string so that the whole string makes sense. It should be easy to find the base
key. Look at the start of the placeholder key or to the instructions. Weblate
should show the base string and its placeholders in the *Nearby strings*
section.

Strings containing URLs should not be translated. However, occasionally, there
is a localized version of the content at the original URL. In such cases, it
makes sense to provide URL to the localized version.

As we explain below, we do not support plural forms due to technical
limitations and our decisions. So, use phrasing that does not need plurals. See
the example for option 2 for more details.

### How can I translate JShelter? Option 2: JSON files in the repository

Technically, translations are strings located in JSON files, one JSON file per
language. The languages are stored in the
[`common/_locales`](https://codeberg.org/JShelter/webextension/src/branch/main/common/_locales)
directory. If you do not see your language there, you can add a new language by
creating a new directory with the
[language code](https://www.unicode.org/cldr/charts/43/supplemental/language_plural_rules.html)
as the name, see the
[docs](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Internationalization#localized_string_selection).

Then, copy the `en/messages.json` to the just created directory, and you have
the basis for the translation.

Next, go through all the strings and translate them to your language.

Typically, a translation entry looks like this:

```JSON
  "javascriptShield": {
    "message": "JavaScript Shield",
    "description": "The name of the JavaScript Shield displayed at multiple places"
  },
```

The first line (`javascriptShield`) displays the name of the string. JShelter
pairs the name of the string with the locations in the source code where this
translation should appear. So, do not change that line.

The second line (key `message`) shows the actual translation. Usually,
translators are supposed to change that line.

The third line is the description of the context of the string. So, for
example, the translator would learn where and how the extension uses the
message. Please do not translate the description.

So, for example, in the Czech translation, the entry becomes:

```JSON
   "javascriptShield": {
     "message": "JavaScriptový štít",
     "description": "The name of the JavaScript Shield displayed at multiple places"
   },
```

However, some entries are more tricky, let us look at:

```JSON
	"defaultLevelSelection": {
		"message": "Default level ($levelName$)",
		"description": "This text is displayed as the default level in the popup",
		"placeholders": {
			"levelName": {
				"content": "$1",
				"description": "Translated name of the default level used by the user",
				"example": "Recommended, see the keys JSSL*Name like JSSL2Name"
			}
		}
	},
```

The `message` contains a variable enclosed by the dollar sign (in this case,
`levelName`). Note that the `placeholders` section contains the specification
of the variable. We add substrings with special properties to the
`placeholders` section. Such substrings are either filled automatically or hold
data that should be translated according to the different rules than the
`message`. Follow the `description` to learn the rules.

In this case, the single `placeholders` entry contains three keys: `content`,
`description`, and `example`. The meaning of the description is the same as in
the original case. It explains the context of the variable. Additionally,
`example` gives example strings or their description to provide the translator
with more information about the possible content. The `content` controls how
the content is created. If the file does not give translators any further
instructions (like in this case), please do not translate the `placeholder`
section. So the translated entry becomes:

```JSON
	"defaultLevelSelection": {
		"message": "Výchozí úroveň ($levelName$)",
		"description": "This text is displayed as the default level in the popup",
		"placeholders": {
			"levelName": {
				"content": "$1",
				"description": "Translated name of the default level used by the user",
				"example": "Recommended, see the keys JSSL*Name like JSSL2Name"
			}
		}
	},
```

See that only the `message` string changed.

A more complicated example is:

```JSON
	"jssgroupTimePrecisionDescription2": {
		"message": "Limit the precision of high-resolution time stamps $apis$. Timestamps provided by the Geolocation API are wrapped as well if you enable \"$jssgroupPhysicalLocationGeolocation$\" protection",
		"description": "Displayed at various places",
		"placeholders": {
			"apis": {
				"content": "(Date, Performance, events, Gamepad API, and Web VR API)",
				"description": "Keep the names of the APIs but translate the punctuation and conjunctions"
			},
			"jssgroupPhysicalLocationGeolocation": {
				"content": "$1",
				"description": "Translated version of the jssgroupPhysicalLocationGeolocation string"
			}
		}
	},
```

This string holds two placeholders: `apis` and
`jssgroupPhysicalLocationGeolocation`. Let us start from the end.
`jssgroupPhysicalLocationGeolocation` should not be touched by the translator.
The text explains that `jssgroupPhysicalLocationGeolocation` will hold the
translation of the string with the same name during the execution of the
extension ("Physical location (geolocation)" in this case). Do not change
anything in entries like `jssgroupPhysicalLocationGeolocation` during the
translation.

Then, there is the entry called `apis`. If you look at the content, it does not
refer to any automatically filled string like the previous examples. Instead,
the string holds the names of several APIs. The `desription` provides
instructions on how to handle the translation. In this case, we suggest keeping
the APIs' names in English. That way, the user can search for the APIs for more
information. If you translate the API names, the user would likely be unable to
find any information about the API. However, different languages have different
rules for punctuation, and the conjunction *and* must be translated. So the
translation can look like:

```JSON
	"jssgroupTimePrecisionDescription2": {
		"message": "Omezuje přesnost časových značek dostupných v API jako je $apis$. Časové značky Geolocation API jsou také změněny, pokud je zároveň aktivní ochrana „$jssgroupPhysicalLocationGeolocation$“.",
		"description": "Displayed at various places",
		"placeholders": {
			"apis": {
				"content": "Date, Performance, Gamepad API, Web VR API a v rámci událostí",
				"description": "Keep the names of the APIs but translate the punctuation and conjunctions"
			},
			"jssgroupPhysicalLocationGeolocation": {
				"content": "$1",
				"description": "Translated version of the jssgroupPhysicalLocationGeolocation string"
			}
		}
	},
```

Note that the original translation used a different kind of quotation marks.
The English original uses the same character as the JSON delimiter. Hence, the
character '\' precedes the quotation mark. The Czech translation does not use
quotation marks conflicting with the JSON quotation marks, so it does not need
the '\' sign.

Occasionally, we extract URLs to the `placeholders` section to highlight that
they should not be translated like:

```JSON
	"newLevelsNotRecommended": {
		"message": "We do not recommend creating your own levels and changing configuration if you are concerned about browser fingerprinting. Please read <a href=\"$FAQURL$\">FAQ</a> and our <a href=\"$PAPERURL$\">paper</a>. By diverging from the configuration of other users, you make your re-identification easier.",
		"placeholders": {
			"faqurl": {
				"content": "https://jshelter.org/faq/"
			},
			"paperurl": {
				"content": "https://arxiv.org/abs/2204.01392"
			}
		},
		"description": "This message is displayed while creating a new level in options. Make sure that you keep correct HTML markup"
	},
```

Note that strings for translation can hold HTML markup in some cases. In that
case, we suggest keeping the same markup. However, if other languages handle
such texts differently, translators are free to apply such rules. The
translation can look like:

```JSON
	"newLevelsNotRecommended": {
		"message": "Nedoporučujeme tvorbu vlastních úrovní ochrany a změnu konfigurace pokud používáte JShelter jako ochranu před tvorbou otisku prohlížeče. Přečtěte si <a href=\"$FAQURL$\">FAQ</a> a náš <a href=\"$PAPERURL$\">článek</a>. V případě, že se vaše nastavení bude lišit od nastavení jiných uživatelů, usnadňujete vaši identifikaci v budoucnu.",
		"placeholders": {
			"faqurl": {
				"content": "https://jshelter.org/faq/"
			},
			"paperurl": {
				"content": "https://arxiv.org/abs/2204.01392"
			}
		},
		"description": "This message is displayed while creating a new level in options. Make sure that you keep correct HTML markup"
	},
```

On rare occasions, we do not expect the translators to touch the `message`
itself but rather work with the placeholders. For example, see:

```JSON
	"NBSDescription": {
		"message": "<p>$PARAGRAPH1$</p><p>$PARAGRAPH2$</p><p>$PARAGRAPH3$</p>",
		"description": "This is the description of NBS shown in options. Please do not modify the template string in the message but translate the paragraphs in the placeholders section. If you find it necessary, you can remove or add paragraphs.",
		"placeholders": {
			"paragraph1": {
				"content": "Long text.",
				"description": "Paragraph 1, please translate this text, keep the URLs or replace them to a translated version of the targets."
			},
			"paragraph2": {
				"content": "Long text.",
				"description": "Paragraph 2, please translate this text."
			},
			"paragraph3": {
				"content": "Long text.",
				"description": "Paragraph 3, please translate this text, note that <i>Manage exception list</i> refers to the ManageWhitelist string."
			}
		}
	},
```

You can see that the message holds a template with an HTML markup. In this
case, we opted for this approach for several reasons:

* We expect translators to preserve the same template (`message`) in most, if
  not all, languages.
* Each paragraph (`placeholders` entry) can have specific instructions.
* The text blocks are not as long as a single block containing all text would
  be.

English has a single plural form for cardinal numbers (usually a suffix `s` or
`es` appended to the singular form) and several forms of ordinal numbers (1st,
2nd, 3rd, 4th).
[Other languages behave differently.](https://www.unicode.org/cldr/charts/43/supplemental/language_plural_rules.html)
Unfortunately, the
[Webextension APIs do not handle plurals well](/i18n_developers/) and JShelter
could use plurals only in notifications of Network Boundary Shield, so we
decided to rephrase the messages so plurals are not needed:

```JSON
		"message": "Count of detected requests\nby $ORIGIN$\nthat accessed local\nnetwork: $COUNT$.",
```

### Final remarks

If you want to start translating a new language, let us know. For example, you
can open an issue in the
[issue tracker](https://codeberg.org/JShelter/webextension/issues) or send us
[e-mail](mailto:jshelter@gnu.org). If you are in doubt about how to translate a
string or do not understand its meaning, let us know in an
[issue](https://codeberg.org/JShelter/webextension/issues) or send us
[e-mail](mailto:jshelter@gnu.org).
