From qa-localization
Configures pseudo-localization for the app (replaces translatable strings with accented variants like "Submit" → "Şüƀɱîţ" + 35% length expansion) - surfaces UI issues without needing actual translators: hardcoded strings (any English remaining is unwrapped), truncation (text overflows), encoding (non-ASCII characters break), bidi handling (mixed scripts). Use as the lowest-cost pre-translation l10n smoke test.
How this skill is triggered — by the user, by Claude, or both
Slash command
/qa-localization:pseudo-localization-runnerThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Pseudo-localization is the practitioner technique for testing l10n
Pseudo-localization is the practitioner technique for testing l10n without real translations. Instead of translating "Submit" to French ("Soumettre"), the pseudo-localizer transforms it to something visually distinct but readable:
"Submit" → "Şüƀɱîţ" # accent / Latin-extended characters
"Submit" → "[Şüƀɱîţ]" # delimited markers (find unwrapped strings)
"Submit" → "Şüƀɱîţ ↵↵↵" # 35% length expansion (test truncation)
The transformed string:
Per stack:
| Stack | Library / approach |
|---|---|
| i18next (JS/TS) | i18next-pseudo plugin |
| FormatJS | Manual middleware in the message extractor |
| Django | django-modeltranslation + custom locale |
| Rails | i18n-pseudo |
| Anything | Build custom: walk the locale file; transform values |
// src/i18n.ts (i18next + i18next-pseudo)
import Pseudo from 'i18next-pseudo';
i18next
.use(Pseudo)
.init({
fallbackLng: 'en',
pseudo: {
enabled: process.env.NODE_ENV !== 'production',
letterMultiplier: 2, // ~35% length expansion
languageToPseudo: 'en', // wrap English strings
repeatedLetters: ['a', 'e', 'i', 'o', 'u'],
},
});
The letterMultiplier: 2 doubles vowels (Şü → Şüü) - the 35%
length-expansion convention.
# Activate pseudo-locale by URL param / cookie
APP_URL=http://localhost:3000?lng=en-XA # or whatever the pseudo-locale code is
The app renders with pseudo-translated text. QA / engineers walk the UI looking for issues.
| Symptom | Underlying issue |
|---|---|
| Pure-English text on the page | String not wrapped in t() (untranslated) |
Truncation (...) | Container too narrow for translated text |
| Layout broken (overlapping elements) | CSS doesn't accommodate longer strings |
| Mojibake / garbled characters | Encoding misconfigured |
Missing characters / boxes (□) | Font doesn't support extended Latin |
| Bidi text rendered wrong direction | Mixing LTR/RTL without proper markers |
Combine with playwright-snapshots
for automated detection:
// e2e/pseudo-loc.spec.ts
import { test, expect } from '@playwright/test';
test.use({ extraHTTPHeaders: { 'Accept-Language': 'en-XA' } });
test('checkout page renders correctly under pseudo-loc', async ({ page }) => {
await page.goto('/checkout');
await expect(page).toHaveScreenshot('checkout-pseudo.png');
});
The screenshot baseline is established under pseudo-locale; future runs catch regressions in l10n-friendliness.
- name: Pseudo-localization smoke
run: |
npm run dev:pseudo &
sleep 5
npx playwright test e2e/pseudo-loc.spec.ts
- uses: actions/upload-artifact@v4
if: failure()
with:
name: pseudo-loc-screenshots
path: test-results/
If no library is available, transform locally:
// scripts/pseudo-loc.js
function pseudoLocalize(s) {
const map = { a: 'à', e: 'è', i: 'ì', o: 'ò', u: 'ù',
A: 'Á', E: 'É', I: 'Í', O: 'Ó', U: 'Ú',
s: 'š', t: 'ţ' };
let out = '';
for (const c of s) {
out += map[c] || c;
if ('aeiouAEIOU'.includes(c)) out += c; // duplicate vowels for 35% length
}
return `[${out}]`; // delimiters help spot incomplete wraps
}
The transform is a one-time pass over the source locale file.
| Anti-pattern | Why it fails | Fix |
|---|---|---|
| Pseudo-loc in production | Real users see garbled UI. | Dev / staging only (Step 2). |
| 0% length expansion | Misses truncation issues. | 30-50% expansion (Step 2). |
| ASCII-only pseudo-loc | Misses encoding issues. | Use accented Latin (or non-Latin script for some chars). |
| Pseudo-loc as substitute for real translation | Pseudo-loc verifies infrastructure; not translator quality. | Use both: pseudo-loc continuously, real translation per release. |
| Skipping screenshot baseline under pseudo-loc | Layout regressions visible only when locale activated. | Pseudo-loc + visual regression (Step 5). |
en-XB) that mirrors text direction; valuable
but rare.w3.org/International/.i18n-string-coverage -
static-scan complement.rtl-rendering-tester -
RTL-specific tests.playwright-snapshots - visual regression for catching pseudo-loc regressions.npx claudepluginhub testland/qa --plugin qa-localizationProvides a checklist for code reviews covering functionality, security, performance, maintainability, tests, and quality. Use for pull requests, audits, team standards, and developer training.