Translating

Guide author13 min readUpdated

In short

The languages and their status, the locale file, formats, the glossary, the i18n commands, and adding or reviewing a language.

On this page
  1. Languages
  2. The file
  3. Formats
  4. Game terms and the glossary
  5. Commands
  6. For developers
  7. English left on screen
  8. Game names
  9. The AddOns list
  10. Guide texts
  11. Adding a language
  12. Reviewing an AI-made language

Polestar follows the game's language: a German client reads Polestar's interface in German, and any text without a translation in English. The translation tools are adapted from the translated names database (@moust4ki), GPL-3.0.

Languages#

node tools/polestar.js i18n status lists every language with how much of it is translated (--markdown prints the table for the README and release notes). The list, with each language's status and translators, is tools/lib/i18n/locales.js.

  • reviewedwritten or checked by a native speaker. The tools never fill or rewrite its translations. German (deDE, thecodingcortex), Latin American Spanish (esMX, CDVolvik), French (frFR, Yendi).
  • aimade by a machine. On that client, Options > About says so and links to the Discord. Spanish of Spain (esES), Korean (koKR), Brazilian Portuguese (ptBR), Russian (ruRU), Chinese (zhCN, zhTW).

Spain's and Latin America's clients use different words for game terms (mazmorra, calabozo), so esES and esMX are separate files. English clients (enUS, enGB) read the English text.

The file#

Each language has one file, addon/Polestar/Locales/<locale>.lua, and one for the optional Recorder's few chat lines, addon/Polestar_Recorder/Locales/<locale>.lua, written the same way:

lua
-- German (deDE). ...

local _, ns = ...
if not (GetLocale and GetLocale() == "deDE") then return end
local L = ns.L

-- Core/Commands.lua
L["Toggle guide window"] = "Guide-Fenster ein-/ausblenden"
-- L["Next step"] = ""
  • The key, between L[ and ], is the exact English text the code shows. Leave it byte for byte as it is; translate the right-hand side only.
  • A line starting -- L[ is not translated yet and shows in English. To translate it, remove the -- and write the translation between the quotes.
  • Lines are grouped under the file that first uses them (-- Core/Commands.lua), so you can see where a text appears. Your own comment lines and group headers stay as you wrote them.
  • A key "context: text" ("boss: Chest") is one meaning of a word: translate the text after the colon, in that meaning.
  • The block at the end, -- No longer in the code (its English changed or it went away): reuse or delete., keeps your translations of texts the code no longer has, commented out. When the English of a text changed, its new key is in the file as a -- L[...] = "" line: copy your old translation there, adapt it, and delete the old line.

Formats#

Keep every %s, %d, %.1f of the key, of the same kind, in the same order. If the sentence needs another order, number them all: "%2$s niveau %1$d". Keep |4singular:plural; (translate both forms), |cAARRGGBB…|r colours and |T…|t textures. In a text with a %s or %d, write a percent sign %%.

Game terms and the glossary#

Where the game names something itself, Polestar shows the game's word and there is nothing to translate: reputation standings, item qualities, the Alliance and the Horde, gear slots, the Dungeon Journal.

For the rest, use the game's vocabulary. tools/lib/i18n/glossary/<locale>.json holds about forty terms (quest, step, flight master, hearthstone, dungeon…) with the word chosen for that language: the game client's own word first, then the reviewed translators' choice. i18n check warns where the translation of a text holding one of those terms does not use the glossary's word. A warning is a question, not an error: fix the line, or keep your wording where it reads better.

json
"flight master": { "term": "Flugmeister", "client": "MINIMAP_TRACKING_FLIGHTMASTER" }

term is the word; match (optional) lists the forms that count, compared without case or accents anywhere in the translation (["annehm", "angenommen"]); client names the game's string the word comes from; note is any remark. The English terms are TERMS in tools/lib/i18n/glossary.js. The game's strings (Blizzard's GlobalStrings) are not in the repository: download them for your language from https://<tables>/db2/GlobalStrings/csv?build=<build>&locale=<locale> into data/sources/<tables>/ (ignored by git) when you need them.

Commands#

  • node tools/polestar.js i18n sync [<locale>...] adds every text the code gained to the file, commented out at the end of its file's group, and moves translations of texts the code lost to the block at the end. It never changes a line that holds a translation. A language named that has no file gets one, loaded from addon/Polestar/Core/Core.xml (a new Recorder file goes in Polestar_Recorder.toc by hand). Without a locale it updates every existing file. It covers Polestar and the Recorder; --addon Polestar_Recorder (or Polestar) limits it, and status, keys and import to that one (Polestar's by default).
  • node tools/polestar.js i18n check [<locale>...] lists each language's translated and missing counts and fails on a file Lua cannot load, a stale key (a translated text the code no longer has, outside the end block), a key set twice, or a translation whose formats differ from the English. Missing translations are fine. CI runs it (npm run i18n:check), and npm test runs the same checks (tests/i18n.test.js).
  • node tools/polestar.js i18n status prints the table of languages.
  • node tools/polestar.js i18n keys lists every key with the files and lines that use it.
  • node tools/polestar.js i18n import <locale> <file.lua>... merges translations written elsewhere: a file that returns { ["English"] = "translation" }, sets L["English"] = "...", or calls ns.AddTranslations (the translated names database's layout). It fills only texts not translated yet, refuses a broken format, and puts texts the code lacks in the end block.
  • node tools/polestar.js i18n fit [<locale>[=<file>]...] lays out every preview scene and every Options page in English and each language, and lists the translated text the window would cut with an ellipsis or let cross its edge (deDE=my-deDE.lua checks a file of your own). Write your translation as long as it needs to be: the layout makes room (labels and descriptions wrap, controls size to their text), and a text this still lists is a layout bug to report, not yours to shorten. --slack 1.15 measures about as wide as the game's own font.
  • node tools/polestar.js i18n leaks [<locale>...] runs the addon in each language and lists the English it leaves on screen, grouped by where it came from ("English left on screen" below). --scene guide,options runs only those scenes, --guide <id>,... puts other guides in the guide window, --json prints one document, --update-baseline rewrites the baseline (below).

For developers#

Write every player-facing text as a literal L["..."] (or ns.LC("context", "text")) where it is used, so i18n keys finds it. After adding, changing or removing texts, run node tools/polestar.js i18n sync and commit the locale files with the change: a stale key fails npm test. A text looked up through a variable (L[category]) must be listed in DATA_KEYS in tools/lib/i18n/keys.js (the Dungeons attunement notes are read from tools/lib/codex/attunements.json there); a generator writes its prose as literal keys (talents build: the plan notes in Talents/Builds.lua). Data that names a game thing carries its id, never its English name alone, so the display can ask Polestar.Names for it. A game term the client names (ITEM_QUALITY3_DESC, FACTION_HORDE…) is read from the client, not put in L, and its English value goes in tests/lua/wow_stub.lua. Tests that need a translated client fake a language Polestar does not ship: fakeLocale('itIT', { ... }) (tests/helpers/locale.js).

English left on screen#

node tools/polestar.js i18n leaks checks that nothing English reaches the screen on a client in another language, so a new feature cannot add English unnoticed. It runs the addon on the client stub (tests/lua) in each language with the shipped translations, names addon and guide texts, and has the client answer in "its language": every name the client itself gives (quest titles, item, zone, map, area, spell, faction, class and race names, the player's name) comes back as a placeholder holding no word. Scenes then drive the windows: the guide window step by step with every tooltip, all guides (both with every step shown, whatever class, race or faction it is for), the welcome, the picker, Options, Lookup, Dungeons, Rares, talents, the journey planner, Polestar's tooltip lines, every slash command, the world map and alerts, and the Recorder. Every text the addons put up is recorded (font strings and buttons, tooltips, chat, popups, menus) with its scene and the line that set it.

A word of three or more Latin letters is English left on screen unless it is the language's own or tools/lib/i18n/leaks-allow.json allows it:

Read the rest of this section595 words

The language's own words

Russian, Korean and Chinese have none in Latin letters. German, Spanish, French and Portuguese learn the words of their real translations (interface strings and guide texts), after cutting what is the same in every language (products, slash commands and their arguments, tokens, formats): a translation that equals its English, or copies most of it, teaches nothing.

Names the client gave

Every name Polestar.Names returned in the run is the client's, in whole and by its words.

The allow list

product and people's names (Polestar, Discord, TomTom, the font credits...), terms every client shows in English (XP, PvP, Shift, Ctrl+C), patterns for links, files, versions, slash commands with their documented arguments (slashArguments and every registered command; the prose after one is checked), guide ids and format directives, and per language the names and words it writes as English does (Uldaman; German Navigation).

What a scene types (a search, a command's arguments) does not count where a text repeats it. Each finding names its source:

interface string not translated

a key the language's file leaves empty (i18n sync, then translate it);

guide text not translated

a guide text its texts file leaves empty (i18n guides sync);

name missing from the names pack

Polestar.Names was asked and had no name, so the guide's English showed (i18n names);

English name, Polestar.Names not asked

the code shows a name from a guide or the data without asking Polestar.Names for it by its id;

English a translation keeps

the words are ones a translation of the language writes as in English (a Forever-only place such as Hyjal Summit);

English in the code

a literal or a data text that does not go through L.

tools/lib/i18n/leaks-baseline.json lists, per language, what cannot be fixed in Polestar yet, each with a reason and the scenes it shows in: names as "kind:id" (no-source: no source of i18n names has it, mostly Forever's own NPCs, objects and items, which the client shows once the player has seen them; same-as-english: every source names it as in English, which i18n names records in data/local/names-same-as-english.json for the baseline), and texts as "text:<the text>" whose English a translation keeps (kept-english). A text whose only English is such is no finding.

It fails (exit 1) on any finding, on a scene that stops with an error, and on a baseline entry that no longer shows in English in scenes that all ran (so the file only shrinks: remove the entry, or run with --update-baseline, which also adds the names and kept texts a new guide leaves in English, and never takes English from the code or an untranslated string or guide text). npm test runs a part of it (tests/i18n-leaks.test.js: Russian and Simplified Chinese; the guide window on three guides, Options, the picker, and Lookup, Dungeons and Rares where the Codex and quest database are built), and plants English to show the check finds it: an interface string "translated" as its English, English the code writes out, dropped guide texts (a paladin's among them) and prose after a slash command. Every scene needs the Codex and the quest database built (codex build, questdb build): without them the addon is an incomplete install with its own warnings, so the scenes skip, as on CI; run i18n leaks before a pull request that changes what the addon shows. A text that is right to show in Latin letters goes on the allow list with the reason in its group, never to make a finding go away. Developer output stays English on purpose: the guide lines /polestar where prints and the details block of a bug report, both read by maintainers, are on the allow list.

Game names#

Names of quests, NPCs, objects, items, instances, dungeon encounters, factions and flight points are not translated by hand: they are the game's own. A client in another language reads them from the client where it has them (quest titles, item names) and from Polestar's names addon of its language, Polestar_Names_<locale>, loaded only on that client. A name no source has stays English. The client is the truth: a name it shows in play (an NPC targeted, moused over or talked to, a quest-giving object opened) is kept, per language, and shown from then on. docs/ARCHITECTURE.md, "Names", has the details.

  • node tools/polestar.js i18n names [<locale>...] rebuilds the addons. Per id the first source with a name wins: the client's own tables in that language (the game-table export), the Forever web database in that language (what is cached under data/sources/<web-db>/names), the community names database (cloned into data/sources/<names-db> at the commit pinned in tools/lib/i18n/names.js, every file checked against its sha256) and the Era quest database's db/<locale> (the clone data build uses). It keeps only the ids the compiled guides, the quest database (questdb build, or --questdb <dir>), the Codex's data (codex build, or --codex <dir>) and the travel data name. A name equal to the English one is not stored (a later source's translation wins), nor is one marking unused content ([PH], (DNT)...). The client tables come from the newest build that has them all in English and every language; without one the build stops (--no-tables leaves them out on purpose). The web database names are read from the cache in data/sources/<web-db>/names/, and without it that source is empty. --offline reads only the caches, --no-<quest-db> leaves the community names database out. It prints, per language, how many ids of each kind have no name. Commit the folders it writes.
  • A wrong name is fixed at its source: in the client (it changes there), in the community names database (tell Yendi, then move the pin to his new commit and rebuild) or, for one only the Era quest database has, in the Era quest database.
  • To move the pin, read what changed in Ding's Ding/DB/, then set the commit, version and sha256s of DING in tools/lib/i18n/names.js; a file that differs from its pin stops the build.
  • A new language needs its the web database path (LOCALE_PATHS in tools/lib/codex/<web-db>.js, for the cache and the packs' credit), a the community names database file and DING pin if Ding has it, its AddOns-list text (tools/lib/i18n/toc.js) and the folder's entry in the release list (tools/lib/dsl/project.js takes every locale of locales.js).

The AddOns list#

The game's AddOns list shows each addon's title and notes in the client's language from its TOC's ## Title-<locale> and ## Notes-<locale>. Every Polestar addon's are written once in tools/lib/i18n/toc.js (Polestar and Codex stay as they are); the generated TOCs take them from there, the hand-written ones (Polestar, Polestar_Nav, Polestar_ItemDB, Polestar_Recorder) carry the same lines, and tests/i18n-toc.test.js checks that every TOC under addon/ has all nine and that they match.

Guide texts#

A guide's own words (its title, why and route-choice lines, each step's title and tip, each goal's note and label) are translated per pack, in the pack's locales/ folder: guides/locales/<locale>/ for Polestar's own, one file per guide at the guide's path (forever/alliance/02-westfall.lua), plus _pack.lua for the pack's own texts (its title and notes, its guides' menu folders). A file returns the English of each text with its translation:

lua
return {
	-- Step 1: Sentinel Hill
	-- {npc:234} Gryan Stoutmantle · {npc:821} Captain Danuvin
	["{npc:234} and {npc:821} stand at the top of the stone tower."] = "{npc:234} und {npc:821} stehen oben auf dem Steinturm.",
	["Fill the vial at the well"] = "",
}
Read the rest of this section448 words
  • Names are not translated.The compiler writes the game's names inside a text as tokens, {npc:234} (kinds: npc, object, item, quest, zone, area, spell, faction, skill), and the game shows each in the client's language. The comment above the line lists them with their English. Keep every token of the English, as many times, wherever the sentence needs it; add none. A text that is only names (a step titled after its NPC) has no line.
  • A token marked (no name in this language: may be words) is an object no source names in your language (a district's sign, a tavern): the game would show it in English. Write its name in your words instead; i18n guides check accepts the translation without that token.
  • A name the text uses in the English plural stays words ("Harvest Watchers"): the comment -- plural, as words: Harvest Watchers = {npc:115} Harvest Watcher gives its NPC. Translate the plural yourself from the game's singular name (Polestar_Names_<locale>, i18n names), or, where the game has no name for it in your language, write the singular's token in the translation: "Bandidos ({npc:251918})", "{npc:251918} ×8". A token of that line is allowed though the English has none.
  • "" is not translated yet: the English shows. Keep the key byte for byte.
  • Keep the line breaks (\n) of the English.
  • -- No longer in the guide (its English changed or it went away) heads the translations whose English the guide lost, commented out; i18n guides check names the new English of a changed one where it can tell. Copy the translation to the new line, adapt it, and delete the old one.
  • The files are written whole by sync: comments you add are rewritten.

The compiler puts each language's translations into the pack's texts addon for it, <pack folder>_<locale> (Polestar_Guides_deDE), which only that client loads, on demand (docs/PACK_FORMAT.md, "Texts addons"). Commit the locale files and the texts addons compile writes.

  • node tools/polestar.js i18n guides sync [<locale>...] writes every guide's file and _pack.lua for each language named (without one: each the pack has a folder for): every text in reading order, the translations kept, the lost ones moved to the end. --pack <dir> names another pack.
  • node tools/polestar.js i18n guides check [<locale>...] fails on a file Lua cannot load, a translation that leaves out or adds a token or a line break, a stale translation, or a file no guide has. lint and compile warn about the same, and leave a broken translation out (the English shows).
  • node tools/polestar.js i18n guides stats sizes the work: the texts that need words, with their words and characters, in all and per guide folder, and how many names the compiler made tokens.

Adding a language#

Add it to LOCALES in tools/lib/i18n/locales.js with its status and translators (and to ns.AI_LOCALES in addon/Polestar/Core/Init.lua when its status is ai), then run node tools/polestar.js i18n sync <locale>.

Reviewing an AI-made language#

Play with it, correct what reads wrong, and open a pull request that also sets its status to "reviewed" with your name in translators (tools/lib/i18n/locales.js) and removes it from ns.AI_LOCALES (addon/Polestar/Core/Init.lua).

Was this page helpful?

Start with, or act

  • PlayersInstall and first run
  • PlayersCommunity guides
  • PlayersFAQ
  • Guide authorsWriting a community guide
  • Guide authorsGuide format cheatsheet
  • ContributorsContributing
  • ActionCopy the AddOns folder (Windows)C:\Program Files (x86)\World of Warcraft\_classic_beta_\Interface\AddOns
  • ActionCopy the AddOns folder (macOS)/Applications/World of Warcraft/_classic_beta_/Interface/AddOns
  • ActionFind an optionEvery setting with its default; type its name here to jump to it.
  • ActionEvery slash command