Engineering guide

Localization Testing and QA Workflow

A release-ready localization QA workflow for pseudolocalization, automation, visual regression, native review, regional devices, and release gates.

Reviewed 19 July 2026 · Editorial owner: localization.guide

The workflow in six gates

  1. 1. Static checks

    Reject hardcoded UI strings, invalid locale tags, missing keys, placeholder mismatches, and unsafe concatenation in continuous integration.

  2. 2. Pseudolocalization

    Transform source messages to expose clipping, fixed widths, untranslated strings, token corruption, and bidirectional assumptions before translation.

  3. 3. Locale automation

    Run number, currency, date, timezone, plural, address, and phone fixtures with explicit locales and timezones.

  4. 4. Visual regression

    Capture representative long-text, CJK, combining-mark, emoji, and RTL screens at phone, tablet, desktop, and 200% zoom.

  5. 5. Linguistic QA

    Have qualified native-language reviewers assess meaning, terminology, tone, grammar, cultural suitability, and functional context.

  6. 6. Regional validation

    Test real device language, region, timezone, keyboard, calendar, numbering system, and currency combinations before release.

A pseudolocalization transform that finds real bugs

A useful pseudo locale keeps messages readable while expanding text, replacing letters with accented variants, preserving interpolation tokens, and surrounding output with unmistakable markers. Use a separate mirrored RTL pseudo locale; simply reversing a whole string does not model the Unicode bidirectional algorithm.

const accentMap: Record<string, string> = {
  a: "à", e: "ë", i: "ï", o: "ô", u: "ü",
  A: "Å", E: "Ë", I: "Ï", O: "Ö", U: "Û",
};

export function pseudo(message: string): string {
  const tokens: string[] = [];
  const protectedMessage = message.replace(/{[^}]+}|%w|<[^>]+>/g, (token) => {
    tokens.push(token);
    return "__TOKEN_" + (tokens.length - 1) + "__";
  });

  const accented = [...protectedMessage]
    .map((char) => accentMap[char] ?? char)
    .join("");
  const expanded = accented + " " + "~".repeat(Math.ceil(message.length * 0.35));

  return "⟦" + expanded.replace(/__TOKEN_(d+)__/g, (_, index) => tokens[Number(index)]) + "⟧";
}
  • Fail if any source-language message appears unchanged outside an allowlist.
  • Verify placeholders, HTML tags, ICU plural branches, and line breaks survive the transformation.
  • Include a long expansion locale and an RTL mirrored locale in every pull-request preview.

Locale test matrix

AreaAutomated fixtureHuman/device check
Numbers and currenciesGrouping, digits, signs, minor units, formatToPartsSpacing, bidi marks, local expectations
Dates and timeTimezone boundaries, DST, calendars, week startsDevice region versus language
MessagesMissing keys, plurals, select branches, placeholdersMeaning, grammar, tone, terminology
FormsUnicode input, validation, autocomplete, pasteKeyboard, IME, field order, error recovery
LayoutOverflow assertions and screenshot diffsWrapping, clipping, mirrored controls, 200% zoom
FontsCharacter coverage smoke setCJK, Arabic, Hebrew, combining marks, emoji, fallback
Addresses and phoneCountry fixtures and parsing outcomesRegional labels, optional fields, user expectations

Choose representative stress locales

Text and layout

Use German-style long compounds, Finnish inflection, CJK text without spaces, Arabic and Hebrew RTL, Thai line breaking, Vietnamese diacritics, combining marks, and emoji sequences. No single locale is a universal “worst case.”

Regional settings

Separate language from region, timezone, calendar, numbering system, measurement units, and preferred currency. An English UI with an Indian region or an Arabic UI using Latin digits is a valid user configuration.

Browser and device simulation

  1. Change the operating-system language and region independently; restart the app where the platform requires it.
  2. Set a timezone that crosses the tested date boundary and include a daylight-saving transition.
  3. Install and use a regional keyboard or input method editor, then test typing, composition, selection, and paste.
  4. Request an alternate numbering system through a Unicode locale extension such as `ar-EG-u-nu-latn`.
  5. Test narrow phone widths, landscape, desktop, browser text zoom, and 200% page zoom.
  6. Inspect logical navigation order, focus visibility, screen-reader labels, and whether RTL changes meaningfully directional icons.

Automate stable contracts

Set locale and timezone explicitly in tests. Prefer semantic assertions over machine-default output and keep screenshots on a pinned browser/runtime.

import test from "node:test";
import assert from "node:assert/strict";

test("JPY uses its runtime currency precision", () => {
  const formatter = new Intl.NumberFormat("ja-JP", {
    style: "currency", currency: "JPY",
  });
  assert.equal(formatter.resolvedOptions().maximumFractionDigits, 0);
});

test("pseudo keeps the interpolation token", () => {
  const output = pseudo("Welcome, {name}");
  assert.match(output, /{name}/);
  assert.match(output, /^⟦/);
});

Copyable release gate

  • □ Message extraction and placeholder validation pass.
  • □ Pseudo, long-text, CJK, and RTL previews have no blocked defects.
  • □ Locale fixtures pass with explicit runtime, locale, and timezone.
  • □ Native-language reviewers approve the release-candidate build.
  • □ Critical flows pass on representative regional devices and input methods.
  • □ Accessibility, visual regression, fallback, and missing-translation behavior pass.
  • □ Known exceptions have owners, severity, affected locales, and expiry dates.

Primary and official sources