Lesson 19·Unit 6 · Shipping translations·Pre-translation QA·Lv 101·~6 min
Pseudolocalization workbench
Before spending money on real translation, spend a weekend on fake translation. Pseudoloc replaces 'Sign in to continue' with '[!! ßîğň ìñ ţö çöńţïñúé !!]': same shape, obviously not English. Anything that doesn't transform is a hardcoded string. Anything that overflows is a layout that won't fit Germany.
By the end
- ·Read pseudolocalized output and name the bug each part of the transform hunts.
- ·Explain why the transform has to leave ICU placeholders, HTML tags, and printf tokens alone.
- ·Put a pseudoloc build in CI, and say what a weekly glance at it will catch.
The problem
The most common localization bug is a string that never enters the translation pipeline because some engineer hardcoded it in a JSX attribute or a server error message. You find these the day after launch when a French user sends a screenshot of half-English UI. Pseudolocalization catches them all before launch, by running the app pretending to be translated. If something stays English, it's externalized wrong.
How it works
A fake locale that finds real bugs
Pseudolocalization turns your English strings into readable-but-strange English before you hire a single translator. Every part of the transform hunts a specific bug class. Accents (Ḥéļļö) mark text that flowed through the i18n layer, so anything you can read plainly in the UI is a hardcoded string that bypassed it. Padding simulates German-scale growth and finds truncation. Delimiters like ⟦…⟧ make silent clipping visible at a glance. An RTL flip previews mirroring months before you buy an Arabic translation.
A serious transform is also careful about what it does not touch. {placeholders}, HTML tags, URLs, and printf tokens pass through unaccented, because a mangled {count} does not fail QA: it crashes at runtime. (That is why the transform in this demo is a small tokenizer, not a regex over the whole string.)
It belongs in CI rather than in a quarterly audit because string bugs regress one commit at a time. A pseudoloc build that someone (or a screenshot diff) checks weekly catches the new hardcoded string while its author still remembers writing it.
See it yourself
Try these, in order
Each step triggers a specific failure you should recognize on sight.
- 1With the default JSON payload and “Accent” mode, read the items_in_cart string in the output. The ICU skeleton ({count, plural, =0 {…} one {…} other {…}}) keeps its keywords intact while the branch text gets accented. A transform that accents “plural” breaks the runtime.
- 2In the workbench at the top, switch to “Expand (40%)”. Every string gets padded. This is your text-expansion smoke test: any layout that clips here will clip in German.
- 3Switch to “RTL flip”. Strings render right-to-left with bidi control characters: a cheap first look at what your layout does under RTL before you buy an Arabic translation.
- 4Paste a strings file from your own project into the source box. Anything that comes out un-accented in the app UI is a hardcoded string that never went through your i18n layer: the class of bug pseudoloc exists to catch.
- 5Scroll to “A real screen under pseudoloc”. With its “Expand (40%)” button active, compare the “Upgrade plan” button in the Source card with its twin in the Pseudo-translated card. The pseudo button clips its label: the button has a fixed width, and the padded string overflows it. The textarea only showed you a longer string. The screen shows you the truncation bug that string causes.
- 6In the same section, click “RTL flip”, then “Accent”. Under RTL flip the whole pseudo card mirrors: badge, text alignment, and button swap sides. Under Accent nothing clips and nothing moves: accents mark coverage, expansion finds layout breaks, and the RTL flip previews mirroring. Each mode hunts a different bug class.
The transform leaves variables ({name}), HTML tags, and URLs untouched, the same rule any real pseudo-translator should follow.
A real screen under pseudoloc
Storage
Almost fullYour workspace is using 9.3 GB of its 10 GB quota.
Older file versions are removed first when you run out.
⟦·· Šţøŕàĝé ··⟧
⟦·· Åĺɱøšţ ƒúĺĺ ··⟧⟦·········· Ýøúŕ ŵøŕķšþàçé íš úšíñĝ 9.3 ĜƁ øƒ íţš 10 ĜƁ ǫúøţà. ··········⟧
⟦··········· Øĺðéŕ ƒíĺé ṽéŕšíøñš àŕé ŕéɱøṽéð ƒíŕšţ ŵĥéñ ýøú ŕúñ øúţ. ···········⟧
The textarea above shows the strings. This card shows the bug. Under Expand (40%), the fixed-width button clips its label, the same clipping a German translation would cause. Under RTL flip, the whole card mirrors: badge, text alignment, and button move to the other side. All of this appears before you buy a single Arabic translation.
The full transformation chain
How to use this in CI
1. Add a build step that produces a pseudo-translated locale file from your English source (the function in src/lib/pseudoloc.ts is a starting point, and production tools wrap ICU MessageFormat-aware parsers).
2. Run your screenshot / visual-regression test suite against the pseudo locale on every PR.
3. Fail the build if any English text appears un-transformed in the screenshots, or if any element overflows when expanded by 40%.
If you remember one thing
Pseudolocalize in CI, before any money goes to translators. It finds hardcoded strings, concatenation, and brittle layouts while they are still cheap to fix.
What to do about it
- Add pseudoloc as a CI mode. A build that runs with
LOCALE=qps-ploc(the standard pseudo-locale) should be screenshot-diffed against the English baseline. Any text that stays unchanged is flagged for review. - Use accent mode to find missing strings, expand mode to find layouts that won't fit longer translations, and RTL mode to find CSS that's not direction-aware.
- Preserve ICU variables, HTML tags, URLs, and printf placeholders; pseudo-translating those breaks the runtime resolver. The transform should treat them as protected regions.
- Make this a pre-merge gate on PRs that touch user-facing code. It's the cheapest l10n QA available.
Use this with
Stakeholders
Moments
- ·PR review for user-facing features
- ·Pre-launch QA
- ·First localization conversation with a new team
Field note
This is old, proven tooling: Windows has shipped built-in pseudo-locales (qps-ploc and its variants) since Vista, so teams could test localizability without waiting for translations. If the OS your users run ships pseudo-locales as a first-class feature, your web app can afford a CI step.
Quick check
3 questions · pass at 2+
Question 1/3
What class of bug does pseudolocalization catch that ordinary English QA cannot?
Question 2/3
Why must a pseudoloc transform protect ICU variables and HTML tags?
Question 3/3
Which of these is Windows' built-in pseudo-locale identifier?