Writing game text that is easy to translate
Everything a player reads in your game started as a line you wrote: a button label, a quest description, the sentence that appears when they cannot afford something in a shop. If that game is ever translated, this source text is the entire input a translator receives. Whatever is ambiguous, fragmented or context-dependent in the original does not get repaired on the way out — it gets multiplied, once per language.
That makes the source text one of the few parts of localization you control completely, before spending anything. Text written with translation in mind produces fewer questions, less rework and more accurate results, and — the part people usually miss — it is normally clearer for players in the original language too. The same qualities that make a line easy to translate make it easy to read.
None of this is a request to write flat, cautious prose. Voice, jokes and personality survive translation perfectly well when they are written so a translator can see what they are doing. What does not survive is text whose meaning lives in your head instead of on the page. The rest of this article is about keeping it on the page.
One string, one complete message
A string is a unit of meaning, not a unit of screen space. The most common structural mistake is assembling a sentence out of pieces at runtime — a verb plus a noun, a number plus an item name, a prefix plus a variable middle. In English the pieces snap together in a fixed order and the result reads fine. In other languages the order is different, nouns change form after numbers, adjectives agree with the noun, and particles attach to whatever came before. A translator handed three fragments cannot fix any of that, because the joining happens in your code, where they have no access.
The second version of the same mistake is reusing one string in two places because it happens to read identically in your language. 'Open' as a button that opens a chest and 'Open' as the state a door is currently in are the same four letters in English and different words in most other languages — one is a verb, one is an adjective. The same trap catches 'New', 'Save', 'Free', 'Play' and 'Back'. Give each usage its own key even when the source text is duplicated; a duplicated source line costs you nothing, a merged one costs a wrong word on screen.
The practical test is simple. Read the string alone, as it will appear in a spreadsheet, with no surrounding code and no screenshot. If you cannot tell what it means or what part of speech it is, neither can the person translating it.
// Assembled at runtime: the translator never sees a whole sentence,
// and the word order is hardcoded into your program
const msg = "You found " + count + " " + itemName + "!";
// One complete message per key, with named variables
"pickup.found_items": "You found {count} x {item}!"
// Same word in English, different words elsewhere: separate keys
"menu.file.open": "Open" // verb, a button the player presses
"door.state.open": "Open" // adjective, describes the door right nowSay the things your language lets you leave out
Every language allows some information to be omitted because listeners infer it. Japanese routinely drops the subject; English drops it far less but is famously vague about 'you', which can be one person or a group, formal or familiar. Those gaps are invisible while you write, because you know who is speaking to whom. They become blocking questions the moment someone has to render the line in a language that forces the distinction.
A line like 'Ready?' has to become a specific form in French, German, Spanish or Korean: are you addressing one companion or a party, a superior or a friend? 'It is broken' needs to know what 'it' is, because languages with grammatical gender must choose an agreement for the pronoun. A line of dialogue with no subject needs to know who is acting and whether that character is male, female or neither. None of these are translation problems. They are missing-information problems, and they arrive as a list of questions in your inbox — or, worse, as guesses that ship.
The fix is usually not to rewrite the line into something stiff. It is to attach the missing fact where the translator will see it: speaker, addressee, and the referent of any pronoun. Where the source itself is genuinely ambiguous even to your own players, that is worth knowing too, because then the ambiguity is deliberate and the translator should preserve it rather than resolve it.
Design the sentences that hold variables
A placeholder is a hole in a sentence that you are promising to fill at runtime. Give it a name rather than a position, because a translator will often need to move it: what comes first in your language may need to come last in theirs, and a numbered slot in a reordered sentence is very easy to get wrong. A named slot survives reordering intact.
Numbers deserve particular care. Appending an 's' for anything above one is an English-only rule; several languages have three or more plural categories that depend on the actual value, and Japanese and Chinese have none at all. Either use a message format that can express plural rules properly, or design the string so the problem never arises — a label like 'Items: {count}' needs no plural form in any language, which is why it appears in so many shipped games.
Two further rules save a surprising amount of grief. Never place a variable where the surrounding grammar depends on its value: an article ('a' versus 'an'), an adjective ending, or a verb form that agrees with the inserted noun will be wrong for some values in some languages, and no amount of translator skill fixes a sentence whose grammar is decided by your data. And always tell the translator what the value looks like — a player name that may contain any script, an item name that might already be plural, a number that can reach six digits. The shape of the value changes how the sentence around it should be written.
// Fragile: the article depends on a value the translator cannot see
const line = "You picked up a " + itemName; // "a apple"
// Safer: no grammar depends on the inserted value
"pickup.generic": "Picked up: {item}"
// Numbered slots break when a language needs a different order
"quest.reward": "{0} gave you {1}" // which is which?
"quest.reward": "{giver} gave you {reward}"Jokes, puns and things that only work in one language
Wordplay rarely survives a literal translation, and the wrong reaction to that is stripping the humour out of your game. The right reaction is telling the translator what the joke is doing. A pun that exists only to make a line fun can be replaced with a different joke that lands in the target language, and a good game translator will happily do that — but only if they know they are allowed to, and only if they know which beat has to be funny.
The distinction that matters is whether the wordplay is load-bearing. A throwaway pun in a shop line is free to be rewritten. A pun that is the answer to a puzzle, an anagram the player has to solve, a rhyme that a voice clip is timed to, or a word printed on a piece of artwork cannot be freely replaced, because something else in the game depends on the exact letters. Mark those explicitly. They are the cases where a translator needs to negotiate with you rather than solve it alone.
The same applies to culture-bound references: a domestic celebrity, a television catchphrase, a school-system detail, a joke that assumes the player knows what a particular convenience store smells like. These are not mistakes, and they often carry a lot of the writing's character. Just flag them, say what feeling they are meant to produce, and state whether an equivalent local reference is acceptable or whether the foreignness is the point.
Write the note while you still remember
The cheapest moment to record context is the moment you write the line, when you already know who is speaking, what screen it appears on, and why the wording is what it is. The most expensive moment is months later, at handoff, when you are reading your own string table trying to reconstruct what 'ui.warn.03' was about.
You do not need a documentation system for this. A few extra columns next to the text will carry most of the value: who is speaking, who they are speaking to, where the string appears, any hard character limit, and a free-form note for anything unusual. Fill them in as you write, and leave them empty when there is nothing worth saying — the goal is to capture the context that changes the translation, not to annotate every row.
Character limits in particular are worth writing down at authoring time rather than discovering after the fact. If a button is twelve characters wide in your font, that constraint exists whether or not anyone records it. Written down, it becomes something a translator can work within. Left unwritten, it becomes a bug report after the localized build is already assembled.
key,source,speaker,context,limit shop.no_gold,"You can't afford that.",Shopkeeper,"Gruff; shown when a purchase fails",40 ui.button.open,"Open",,"Button on the chest inspect screen; verb",12 mira.ch02.greet,"Oh — it's you again.",Mira,"To the player; pleased but pretending not to be",
One pass over your own text before it leaves
Before a single line goes to a translator, read your own export end to end. Reading strings in a flat list — the way the translator will see them — is disorienting at first, and that is exactly the point: it shows you which lines carry no meaning without the game around them.
Doing this over a few hundred strings takes an afternoon, and it is the cheapest quality work available in a game project. If you do go on to translate, you get fewer clarification questions, less rework and a better result, because the translator spends their effort on the writing rather than on reverse-engineering your intent.
If you never translate at all, you still end up with a text layer where every message is complete, terminology is consistent, and nothing is assembled at runtime — which is a codebase that is easier to change and player-facing copy that reads better. The discipline is not overhead you carry for a hypothetical foreign audience. It is careful writing, with the side effect that it leaves the door to that audience open.
- Every string is a complete message, not a fragment joined to another fragment in code
- No string is reused in two places where it plays a different grammatical role
- Every pronoun has a stated referent, and every line has a stated speaker and addressee
- Placeholders are named, not numbered, and no grammar depends on the value they carry
- Anything counting things either uses a plural-aware format or is written to avoid plurals
- Puns, rhymes and references are flagged, with a note on whether they may be rewritten
- Hard character limits are recorded per string, not left to be discovered in QA
- Terminology is consistent — one name per concept, everywhere, including the UI