One game, one set of statistics, one set of answer codes — and every player reads the text in their own language. Translations live beside the game and replace the text when the page is shown; where there is no translation, the player sees the original. A player whose language matches the game's language gets nothing replaced at all.
This is the recommended way. The older {% match lang %} template (Templates) keeps working, but new games are better made with translations: the task text does not turn into N copies of itself, a change to the markup does not have to be repeated per language, and the editor shows what is still untranslated.
In the game editor, the “Game settings” tab, the “Localization” block:
Example: the game is written in Russian and translated into Ukrainian and English, while Polish falls back to Ukrainian (closer than Russian). A Polish player sees Polish where it exists, Ukrainian where Polish does not yet, and Russian where neither does.
As soon as there is at least one translation language, a “Translation” tab appears in the editor.
It is a table of “original → translation” pairs. The string from the game on the left, a box for the translation on the right.
Bold, links, images and templates inside a paragraph are not typed into the translation by hand: in their place stand chips — small numbered boxes. Click a chip and it goes into the translation where the caret is. Chips may be reordered (word order differs between languages), but each one has to appear in the translation exactly once — otherwise the translation is not saved and the editor says so.
Substitutions behave the same way — !username!, !task_n! and the rest: they stay in the translation as they are, simply where the sentence wants them.
A useful consequence: the tags themselves, the link addresses and the classes live only in the original. Fix a link in the task and it changes in every language at once.
translate=“no” in the task HTML is the stronger way: such a piece never becomes a string at all, and even the browser's own auto-translation leaves it alone. For code words and answers inside the text this matters more than it sounds: an auto-translated code word breaks the game.The second tab of the table, with the same groups in it. Addresses of images, sounds, videos and links are not translated by default — nearly all of them are language-neutral. But a translation of an address can be set: that is how a player is sent to a page in their own language, or gets the voice-over in it, while the paragraph and the markup stay single. These strings do not count towards completeness.
Subtitles do not need the overlay — HTML already does them: several <track srclang=“…”> inside one <video>, and the browser picks.
The identity of a string is its text. Edit the original and the translation no longer belongs to it: the string becomes untranslated again and the old translation moves to the “Stale translations” section. If the edit was small (a typo), the editor offers it back with a “restore” button right under the new string. The ones no longer wanted can be deleted — one by one or all at once.
At the bottom of the tab: “Load translations from game” — a game number and a button. Only the pairs whose original exists in this game as well are taken; the report says how many matched and how many were skipped. Existing translations are not overwritten by default. It makes sense for a series of games by one author with the same original language: a game written in another language will have no matches.
Translations are part of the editor's draft, next to the text itself. Unsaved tasks can be translated too: write the task, translate it right away, save once. The edit to the original and the fixed translations reach the server in one operation, so there is never a moment when players see new text with an old translation.
Translations travel with the game in its JSON — the translations section — and come back on import. A copy of the game gets its translations too; copying a single task into another game does not carry translations over (the dictionary belongs to the game, and another game's strings would be noise there) — “Load translations from game” is for that.
Open the game with an explicit language: https://qeng.org/game/1234/?lang=uk. Check a language the game does not have, too — that is how you see whether the “other languages” row works.
For those the older way — {% match lang %} — still applies.