diff --git a/_en/quality/translations.md b/_en/quality/translations.md new file mode 100644 index 0000000..56ed37b --- /dev/null +++ b/_en/quality/translations.md @@ -0,0 +1,191 @@ +--- +title: Translation (i18n / l10n) +layout: default +root: ../.. +idx: 8.05 +marked_text_encoding: + level: info + title: Marked Text Encoding + message: | + You don't need to know the exact format of marked text, but it might help + you understand. + + ``` + "\27(T@mymod)Hello everyone!\27E" + ``` + + * `\27` is the escape character - it's used to tell Minetest to pay attention as + something special is coming up. This is used for both translations and text + colorisation. + * `(T@mymod)` says that the following text is translatable using the `mymod` + textdomain. + * `Hello everyone!` is the translatable text in English, as passed to the + translator function. + * `\27E` is the escape character again and `E`, used to signal that the end has + been reached. +--- + +## Introduction + +Adding support for translation to your mods and games allows more people to +enjoy them. According to Google Play, 64% of Minetest Android users don't have +English as their primary language. Minetest doesn't track stats for user +languages across all platforms, but there's likely to be a high proportion of +non-English speaking users. + +Minetest allows you to translate your mods and games into different languages by +writing your text in English, and using translation files to map into other +languages. Translation is done on each player's client, allowing each player to +see a different language. + + +- [How does client-side translation work?](#how-does-client-side-translation-work) + - [Marked text](#marked-text) + - [Translation files](#translation-files) +- [Format strings](#format-strings) +- [Best practices and Common Falsehoods about Translation](#best-practices-and-common-falsehoods-about-translation) +- [Server-side translations](#server-side-translations) +- [Conclusion](#conclusion) + + +## How does client-side translation work? + +### Marked text + +The server needs to tell clients how to translate text. This is done by marking +text as translatable using a translator function (`S()`), returned by +`minetest.get_translator(textdomain)`: + +```lua +local S = minetest.get_translator("mymod") + +minetest.register_craftitem("mymod:item", { + description = S("My Item"), +}) +``` + +The first argument of `get_translator` is the `textdomain`, which acts as a +namespace. Rather than having all translations for a language stored in the same +file, translations are separated into textdomains, with a file per textdomain +per language. The textdomain should be the same as the mod name, as it helps +avoid mod conflicts. + +Marked text can be used in most places where human-readable text is accepted, +including formspecs, item def fields, infotext, and more. When including marked +text in formspecs, you need to escape the text using +`minetest.formspec_escape`. + +When the client encounters marked text, such as that passed to `description`, it +looks it up in the player's language's translation file. If a translation cannot +be found, it falls back to the English translation. + +Marked text contains the English source text, the textdomain, and any additional +arguments passed to `S()`. It's essentially a text encoding of the `S` call, +containing all the required information. + +{% include notice.html notice=page.marked_text_encoding %} + + +### Translation files + +Translation files are media files that can be found in the `locale` folder for +each mod. Currently, the only supported format is `.tr`, but support for more +common formats is likely in the future. Translation files must be named +in the following way: `[textdomain].[lang].tr`. + +Files in the `.tr` start with a comment specifying the textdomain, and then +further lines mapping from the English source text to the translation. + +For example, `mymod.fr.tr`: + +``` +# textdomain: mymod +Hello everyone!=Bonjour à tous ! +I like grapefruit=J'aime le pamplemousse +``` + +You should create translation files based on your mod/game's source code, +using a tool like +[update_translations](https://github.com/minetest-tools/update_translations). +This tool will look for `S(` in your Lua code, and automatically create a +template that translators can use to translate into their language. +It also handles updating the translation files when your source changes. + + +## Format strings + +It's common to need to include variable information within a translation +string. It's important that text isn't just concatenated, as that prevents +translators from changing the order of variables within a sentence. Instead, +you should use the translation system's format/arguments system: + +```lua +minetest.register_on_joinplayer(function(player) + minetest.chat_send_all(S("Everyone, say hi to @1!", player:get_player_name())) +end) +``` + +If you want to include a literal `@` in your translation, you'll need to escape +by writing `@@`. + +You should avoid concatenation *within* a sentence, but it's recommended that +you join multiple sentences using concatenation. This helps translators by +keeping strings smaller. + +```lua +S("Hwllo @1!", player_name) .. " " .. S("You have @1 new messages.", #msgs) +``` + + +## Best practices and Common Falsehoods about Translation + +* Avoid concatenating text and use format arguments instead. This gives + translators full control over changing the order of things. +* Create translation files automatically, using + [update_translations](https://github.com/minetest-tools/update_translations). +* It's common for variables to change the surrounding text, for example, with + gender and pluralisation. This is often hard to deal with, so is + frequently glossed over or worked around with gender neutral phrasings. +* Translations may be much longer or much smaller than the English text. Make + sure to leave plenty of space. +* Other languages may write numbers in a different way, for example, with commas + as decimal points. `1.000,23`, `1'000'000,32` +* Don't assume that other languages use capitalisation in the same way. + + +## Server-side translations + +Sometimes you need to know the translation of text on the server, for example, +to sort or search text. You can use `get_player_information` to get a player's +lang,uage and `get_translated_string` to translate marked text. + +```lua +local list = { + S("Hello world!"), + S("Potato") +} + +minetest.register_chatcommand("find", { + func = function(name, param) + local info = minetest.get_player_information(name) + local language = info and info.language or "en" + + for _, line in ipairs(list) do + local trans = minetest.get_translated_string(language, line) + if trans:contains(query) then + return line + end + end + end, +}) +``` + +## Conclusion + +The translation API allows making mods and games more accessible, but care is +needed in order to use it correctly. + +Minetest is continuously improving, and the translation API is likey to be +extended in the future. For example, support for gettext translation files will +allow common translator tools and platforms (like weblate) to be used, and +there's likely to be support for pluralisation and gender added. diff --git a/_sass/_content.scss b/_sass/_content.scss index d4b5283..3811eac 100644 --- a/_sass/_content.scss +++ b/_sass/_content.scss @@ -33,68 +33,6 @@ figure { padding: 0 0 0 6px; } -.notice-info { - background: #ececec !important; - border: 1px solid #aaa !important; -} - -.notice-danger { - background: #fcc !important; - border: 1px solid #a66 !important; -} - -.notice-warning { - background: #FED; - border: 1px solid #fc9; -} - -.notice-tip { - background: #ccf; - border: 1px solid #66a; -} - -.notice-green { - background: #161; - border: 1px solid #393; -} - -.notice { - margin: 10px; - display: block; - padding: 5px; - border-radius: 5px; - position: relative; -} - -.notice p { - margin: 0 0 17px 0; -} - -.notice p:last-child { - margin: 0; -} - -.notice > span { - position: absolute; - top: 0; - left: 0; - bottom: 0; - width: 40px; - font-size: 24px; - text-align: center; - display: block; -} - -.notice > div { - margin-left: 35px; -} - -.notice h2 { - margin: 0 0 5px 0; - padding: 0 0 2px 0; - font-size: 100%; -} - .header-link, .anchor { text-decoration: none; color: #bbb; diff --git a/_sass/_notice.scss b/_sass/_notice.scss new file mode 100644 index 0000000..90ffd2b --- /dev/null +++ b/_sass/_notice.scss @@ -0,0 +1,61 @@ +.notice { + margin: 2em 0; + display: block; + padding: 0.5rem; + border-radius: 0.5rem; + position: relative; + + p { + margin: 0 0 1em 0; + } + + p:last-child { + margin: 0; + } + + & > span { + position: absolute; + top: 0; + left: 0; + bottom: 0; + width: 40px; + font-size: 24px; + text-align: center; + display: block; + } + + & > div { + margin-left: 35px; + } + + h2 { + margin: 0 0 5px 0; + padding: 0 0 2px 0; + font-size: 100%; + } +} + +.notice-info { + background: #ececec !important; + border: 1px solid #aaa !important; +} + +.notice-danger { + background: #fcc !important; + border: 1px solid #a66 !important; +} + +.notice-warning { + background: #FED; + border: 1px solid #fc9; +} + +.notice-tip { + background: #ccf; + border: 1px solid #66a; +} + +.notice-green { + background: #161; + border: 1px solid #393; +} diff --git a/static/style.scss b/static/style.scss index 6528778..969916d 100644 --- a/static/style.scss +++ b/static/style.scss @@ -4,3 +4,4 @@ @import "main"; @import "content"; @import "code"; +@import "notice";