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:
The interface shows:
- The name of the key of the currently translated string,
- the original English translation,
- the description of the usage of the string,
- the translation to the current target language,
- the information on similar and nearby strings that might be handy to grasp the context of the string,
- 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.