JShelter can be translated into different languages

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 that help to translate extensions. This way we can localize not only the interface of the extension but also its description and even the name.

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 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. 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

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. 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 directory. If you do not see your language there, you can add a new language by creating a new directory with the language code as the name, see the docs.

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:

  "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:

   "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:

    "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:

    "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:

    "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:

    "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:

    "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:

    "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:

    "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. Unfortunately, the Webextension APIs do not handle plurals well and JShelter could use plurals only in notifications of Network Boundary Shield, so we decided to rephrase the messages so plurals are not needed:

        "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 or send us e-mail. If you are in doubt about how to translate a string or do not understand its meaning, let us know in an issue or send us e-mail.