Engineeringこの記事を日本語で読む

Localizing achievements and trophies: platform fields and text limits

You finished the localization pass. The menus are translated, the dialogue is translated, the store page is translated, and a fresh set of eyes played through the whole thing in the new language without finding anything. Then a player posts a screenshot of an achievement popup, and the achievement name is still in your original language.

Nothing is broken in your build, because that text was never in your build. Achievement and trophy names, their descriptions, rich presence strings, DLC and bundle titles — these live in the platform's partner backend. You typed them into a web form months ago and they have been sitting on the platform's servers ever since, invisible to every export script, every word count, and every file a translator has ever received from you.

This article covers where that text actually lives, why it is the single most commonly missed piece of a localization pass, how the character limits on those fields behave once you translate, and how to manage them so they do not drift out of sync with the game they describe.

The text that is not in your project

The first step is simply to enumerate it. Most teams have never written this list down, which is exactly why it goes missing. The specifics differ per platform, but the categories are consistent: anything the platform renders on your behalf, outside your rendering code, is text you supplied through a form rather than through a file.

  • Achievement and trophy display names and their descriptions, including hidden ones
  • Rich presence strings — the 'currently playing…' line shown to friends, usually assembled from tokens you register and a localization file you upload per language
  • DLC, bundle, package and edition names, which appear in the store, in the library, and inside your own game's purchase prompts
  • In-app purchase or microtransaction item names and descriptions, if your game has them
  • Leaderboard and stat display names, wherever the platform shows a leaderboard UI rather than your own
  • Store page fields: short description, long description, feature lists, system requirement notes, and the captions attached to screenshots
  • The application name itself, and any subtitle attached to it in the platform's own listings

Why this text gets missed

Every property of this text works against remembering it. It was created at a different time, by a different person, in a different tool. Achievements are usually set up by whoever wired the achievement API, in the original language, long before anyone was thinking about a second language. By the time a localization pass starts, that work is done and nobody revisits the backend.

It is also structurally invisible to the process you built. If your workflow is 'export the string files, count the words, send them out, import what comes back, diff the result' — a workflow that is otherwise correct — then text that has never been in a string file is not in the export, not in the count, not in the quote, and not in the file the translator opens. It does not show up as a missing translation, because there is no key for it to be missing against.

Finally, no ordinary test catches it. The game runs perfectly. The achievement popup only appears at the moment of unlocking, which may be hours into a playthrough, and QA is usually run against a build rather than against a live platform account switched to the target language. A tester who plays the whole game in Japanese may never see a single achievement notification, and the achievement list is one more screen away in a client UI that most test plans do not mention.

Character limits you do not control

Every one of these fields has a maximum length, and unlike a text box you drew yourself, you cannot widen it. The limits are usually enforced at the point of entry — the form simply refuses to accept a longer value, or truncates it on save — which means the problem surfaces at upload time, often with a translator no longer available and a programmer improvising a shorter version on the spot.

The limits are counted in characters, and that interacts badly with translation in two opposite directions. Translating into German, Russian, or Finnish routinely produces strings noticeably longer than the English original, so a name that comfortably fit the field in one language will be rejected in another. Translating into Japanese or Chinese usually produces a shorter character count, but each of those characters is roughly twice as wide on screen, so a string that passes the count check can still be visually truncated in a notification toast or a narrow list row.

That second failure is the nastier one, because nothing rejects it. The backend accepts the value, the field validates, and the string is quietly cut off with an ellipsis in the one place players actually read it. The only way to know is to look at the rendered result rather than the form.

The practical consequence is that the limit belongs in the file you hand the translator, as data attached to each row — not mentioned in a covering email, and not discovered after the fact. A translator who knows a field takes a hard maximum will write to fit it, which is a normal and well-understood constraint in this line of work. A translator who is not told will write the best translation and you will mangle it later.

Achievement names are the hardest short text in your game

Achievement names are short, but they are not simple. They are frequently puns, idioms, song titles, or references to a line of dialogue — the one place a writer gets to be clever in six words. Translated without context, a pun becomes a flat literal phrase, and the description underneath no longer explains it.

Name and description are a pair and must be translated as a pair. The description usually carries the actual condition ('defeat the boss without taking damage'), while the name carries the joke. Splitting them across rows in a spreadsheet, or across two different translators, reliably breaks the relationship between them.

The translator also needs to know what the player did to earn it. An internal API name like ACH_BOSS_02_NODMG tells them nothing about which boss, at what point in the story, or whether the name is referencing something that character says. This is the same context problem as any other short string, but concentrated: achievement text has the least surrounding text and the highest density of wordplay in the entire project.

Spoilers deserve their own thought. On most platforms the achievement list is public before anyone unlocks anything — it is visible on the store page and in the community. Hidden achievements exist, but exactly what stays hidden and when it is revealed varies by platform and by client, so do not rely on the platform to conceal a late-game plot point for you. If a name would spoil something, write it so it does not, in every language.

The last trap is terminology drift. If an item is called one thing in your translated game text and something else in the achievement that references it, players notice immediately, and it reads as carelessness. Because achievement text is often translated separately — different file, different day, sometimes different person — it is the most common place in a project for the glossary to be ignored.

Keep one source of truth and treat the backend as an output

The fix is structural: stop treating the platform backend as the place where this text lives, and start treating it as a destination you publish to. The text lives with the rest of your strings — in your localization files, or at minimum in the same spreadsheet your translator already works in — and the backend is populated from it.

That means adding rows for platform text with a few extra columns the rest of your strings do not need: which platform and which field the row belongs to, the identifier the backend knows it by, the maximum length, and a context note describing what the player actually did.

platform,field,api_name,max_chars,context,en,ja
steam,achievement.name,ACH_BOSS_02_NODMG,<from backend>,"Pun on the boss's catchphrase in ch.3",...,...
steam,achievement.desc,ACH_BOSS_02_NODMG,<from backend>,"Unlock: beat the ch.3 boss with no damage taken",...,...
steam,dlc.name,DLC_SOUNDTRACK,<from backend>,"Shown in store and in the library",...,...
steam,richpresence,#Status_InDungeon,<from backend>,"Shown to friends while in a dungeon; %area% is substituted",...,...

Publishing, verifying, and what you can change later

Fill in the max_chars column by reading it off the backend field itself rather than guessing, and re-read it occasionally — platforms adjust these. Where the backend offers a bulk upload or an API for achievement metadata, use it, because retyping dozens of localized strings into a web form is exactly the kind of work that introduces a wrong-language row nobody spots. Where it does not, work down the sheet row by row and tick them off, one language at a time rather than one achievement at a time, so the language you are currently pasting is always the same.

Verification has to happen outside your build. Switch the platform client — and, separately, the account's own language preference, which is often a different setting — to the target language and look at the achievement list as a player sees it. Unlock one to see the popup, since the popup is narrower than the list and is where truncation shows up first. Load your own store page in that language and read the achievement section there too. On mobile, check the store listing on an actual device: the row widths in a phone listing are far tighter than anything in a browser.

Finally, understand what is editable later. Display text — names and descriptions — is generally editable at any time, so a translation fix is not a crisis and you should not stall a release over an imperfect achievement name. The identifier and the unlock condition are a different matter: players who already unlocked an achievement keep it, so redefining what an existing achievement means quietly rewrites their history. Add a new one instead. And on console platforms, changes to submitted metadata can require going back through a submission process with its own review time, so if you are shipping there, put the localized achievement text on the same schedule as the build rather than treating it as a post-launch cleanup task.

Related articles