Process & operationsこの記事を日本語で読む

Building a localization test matrix for a multi-language release

Twelve languages. Four platforms. Controller and keyboard. Fresh save and imported save. Three aspect ratios. Two font-scaling settings. Written out honestly, the full combination is somewhere in the thousands of test runs, and you have three weeks and four testers.

The instinct is to start testing and see how far you get. That produces a plan shaped by whoever tested fastest and in what order they happened to work, and at the go/no-go meeting nobody can say what was covered — only what was found. The plan then looks fine right up until a language nobody reached ships with a broken font.

The job is not to test everything. It is to decide, deliberately and in writing, what you are not testing and why. A localization test matrix is that decision made visible. It is worth remembering the whole time that the matrix is not the QA plan for the game — it is the plan for the language dimension of the game, and it should stay small enough to actually finish.

Where the combinations come from

The explosion happens because teams multiply every axis they can think of against every language. Listing the axes first, before multiplying, is what makes the reduction possible:

  • Language — the game's own text language setting
  • OS or system locale — separate from the above, and often forgotten
  • Platform and store build — PC storefronts, consoles, mobile
  • Input device — controller, keyboard and mouse, touch
  • Display — resolution, aspect ratio, DPI scaling, and any in-game text size option
  • Save state — new game, mid-game save, imported save from an earlier version
  • Entitlement state — base game, DLC owned, preorder bonus, demo carry-over
  • Network state — online, offline, and any server-delivered text

The one on that list most often missed is system locale as an axis independent of the in-game language setting. A player can run the operating system in one language and your game in another, and in that configuration date formats, number separators, keyboard glyph names, and sometimes which system font is picked come from the OS rather than from your language setting. It is also the configuration in which a formatting bug is most likely to be invisible to a tester whose machine is set to the same language as the game they are testing.

Not every axis deserves to be multiplied, though, and that is the entire lever you have.

Cut the axes that cannot interact with language

For each axis, ask one question: can changing this change what text is displayed, how that text is laid out, or which font is used to draw it? If the answer is no, the axis belongs to general QA, not to the localization matrix. Multiplying twelve languages against an axis that cannot produce a language-specific failure is how a three-week plan becomes a three-month plan without finding anything.

Running that question over the usual axes narrows the field considerably:

  • Input device — mostly no, with one exception: button prompts and glyph names are localized text, so cover one device per prompt family rather than every device per language
  • Aspect ratio and resolution — yes. They change wrap points and available width, which is exactly where length-sensitive languages fail
  • Text size or accessibility scaling — yes, and it is the harshest overflow test you have; a language that fits at default size may not fit one step up
  • OS locale — yes, for formatting and font selection, but it can be tested against a small number of representative languages rather than all of them
  • Save state — mostly no, with one important exception: anywhere language-dependent text was written into the save rather than being looked up at display time
  • Entitlement and network state — usually no for language behavior, except for text delivered from a server, which has its own language negotiation and its own fallback path

Each cut should be written into the plan with its reasoning, not silently omitted. A line saying input device is not multiplied by language; button prompt strings are covered once per prompt family on PC and console is a defensible statement at a go/no-go meeting. A missing row is not, and cannot be told apart from an oversight six months later when someone asks whether that case was ever checked.

Tier the languages instead of treating them equally

Equal coverage across languages sounds fair and is a poor use of a fixed budget. Languages carry genuinely different amounts of risk, and the differences are predictable enough to plan around before testing starts.

Factors that push a language toward deeper coverage:

  • Script and layout behavior — CJK line breaking, right-to-left mirroring, and scripts needing shaping or combining marks all create failure modes the source language never exercises
  • Text expansion — languages that run substantially longer than the source will find every UI element with no slack in it
  • Grammar load — plurals with several forms, grammatical gender, and case systems break naive string concatenation in ways English does not reveal
  • How the translation was produced — machine translation with light post-editing carries more risk than a reviewed human translation, and should be tested accordingly
  • Whether it is new this release — a language shipping for the first time has never been exercised by players; an established one has years of reports behind it
  • Whether the translator ever saw the game — text translated from a spreadsheet without context fails in context more often
  • Audience size — not a quality argument, but a real factor in how much a defect costs you

Sort into three tiers from those factors. Tier A gets a full pass — a playthrough or a substantial vertical slice, in context. Tier B gets a targeted pass over the high-risk screens plus anything flagged by automated checks. Tier C gets a smoke test only: it boots, the menus are in the right language, the first-run flow is complete, nothing is obviously untranslated or clipped.

Two things keep tiering honest. Record the reason each language sits in its tier, so the tiering can be re-argued next release instead of being inherited as a fact. And re-tier when the evidence changes: a Tier C language producing a stream of player reports has told you it was misfiled, and a first-release Tier A language usually drops a tier once it has a stable history.

Let automated checks clear the mechanical rows first

Before a human runs a single cell of the matrix, the machine-checkable classes should already be at zero. Every one of these found by a tester is budget spent on something a script does faster, earlier, and across all languages at once — and finding it late means the fix arrives after the build the testers were working from.

  • Missing, empty, or partially delivered entries in any language
  • Placeholder and variable mismatches between source and translation
  • Markup or rich-text tags that were damaged, dropped, or unbalanced
  • Encoding damage and characters that cannot be rendered by the shipped font
  • Duplicate or orphaned keys after a merge
  • Strings exceeding a declared character budget for their UI slot

What no check can do is tell you whether a line is natural, whether the terminology is right for the situation it appears in, whether a sentence makes sense on the screen where it lands, whether a joke or a cultural reference survived, and whether text is actually truncated at the rendered width rather than the estimated one. Those are the things human passes should spend their limited hours on, which is precisely why the mechanical classes should not be competing for the same hours.

Sequence matters as much as coverage. Automated checks run per build; the human matrix runs against a build that has already passed them. A matrix executed against a build with two hundred missing entries produces two hundred reports about one problem and buries the five real ones underneath.

Writing the matrix down

The matrix itself should be boring: one row per cell, an owner, a status, and no blanks. A blank cell is read as a pass in every status meeting ever held, so make not run an explicit value that someone has to type.

# localization test matrix — one row per cell; blanks are not allowed
# scope: full = playthrough  |  hrs = high-risk screens  |  smoke = boot check

lang     tier  platform   config                 scope   owner  status
en       A     pc         1080p / kb+mouse       full    ai     pass
ja       A     pc         1080p / pad            full    ko     pass
ja       A     console-a  1080p / pad            hrs     ko     2 open
de       A     pc         1080p / kb+mouse       hrs     ms     3 open
de       A     pc         1080p / large text     hrs     ms     not run
ar       A     pc         1080p / kb+mouse       hrs     ms     5 open
zh-Hans  B     pc         1440p / pad            hrs     lin    pass
ru       B     pc         1080p / kb+mouse       hrs     lin    not run
ko       B     console-a  1080p / pad            hrs     rt     pass
fr       C     pc         1080p / kb+mouse       smoke   rt     pass
es       C     pc         1080p / pad            smoke   rt     pass
pt-BR    C     pc         1080p / kb+mouse       smoke   rt     not run

# cross-cutting rows, run once against representative languages only
os-locale mismatch (OS=ja, game=en; OS=en, game=de)   ai   pass
server-delivered text, offline fallback               lin  1 open

The high-risk screen list is the other half of the document, and it is what makes Tier B and Tier C cheap. It should be a fixed, short list that every language visits regardless of tier, chosen because failures there are either likely or expensive:

  • First-run flow — language selection, legal or consent screens, tutorial prompts
  • Any screen with dynamic values — counters, timers, currency, item quantities, player names
  • The densest UI screen you have — usually inventory, settings, or a stats panel
  • Error, disconnect, and purchase-failure messages, which are hard to reach and therefore rarely seen
  • Save, load, and language-switch flows, including switching mid-session
  • Store text and achievement or trophy strings, which are entered outside the build and are the classic gap

Running it, and what a pass actually means

Order the work by what unblocks the rest. Smoke every language first, across all tiers, before starting any deep pass. A language that does not boot, shows the wrong language, or renders empty boxes invalidates every plan built on it, and finding that on day one instead of day nine is often the entire value of having a matrix at all.

Define what a cell passing means before anyone runs one, because testers will otherwise each invent their own threshold. A useful definition: every screen in scope was reached in that configuration, every issue found was logged with a screenshot naming the build, language, platform, and screen, and any remaining open issues are classified by severity. Without evidence attached, a pass is a memory, and a memory cannot be re-checked after a fix.

Then treat unfinished cells as the primary output rather than an embarrassment. At go/no-go, the useful statement is not everything passed — it is these languages had a full pass, these had targeted coverage, these had smoke only, these three cells were not run and here is the risk we are accepting by shipping anyway. That sentence is a decision the whole team can make together. A green grid with quiet gaps in it is a decision one tired person made alone at two in the morning, and nobody in the room knows they are relying on it.

Related articles