Pseudo-localization
@transglot/pseudo: generate an accented, lengthened pseudo-locale from your source JSON to find hardcoded strings and layout overflow before you translate.
@transglot/pseudo turns source strings into a pseudo-locale: every letter is swapped for an accented look-alike, the text is lengthened, and the value is wrapped in brackets. Run your app in that locale and two kinds of bug stand out:
- Text that is not accented is hardcoded and never went through your i18n layer.
- A label that is clipped, loses its closing
]], or wraps badly will break when a longer language arrives.
Placeholders and markup are never changed, so the pseudo-locale still renders. The package is a library and a command-line tool, runs offline, and does not talk to transglot.
Install
npm install --save-dev @transglot/pseudoIt needs Node.js 20 or later and has no dependencies.
Command line
npx transglot-pseudo locales/en.json --out locales/en-XA.jsonGiven this source file:
{
"home": {
"title": "Welcome back",
"greeting": "Hello, {name}!"
},
"cart.items": "{count, plural, one {# item} other {# items}}",
"save": "Save <b>now</b>"
}it writes:
{
"home": {
"title": "[[Ŵéłçöɱáéíöüé ƀáçķ]]",
"greeting": "[[Ĥéłáéíłö, {name}!]]"
},
"cart.items": "[[{count, plural, one {# item} other {# items}}]]",
"save": "[[Šááéíṽé <b>ñöŵ</b>]]"
}| Option | Default | Effect |
|---|---|---|
<sourceFile> | required | A JSON file, flat or nested. |
--out <file> | stdout | Write the result to a file instead of printing it, so the command also works in a pipeline. |
--format json_nested or --format json_flat | json_nested | json_nested transforms every string at any depth. json_flat transforms only the top-level values (strings and arrays of strings) and leaves nested objects untouched. |
--expansion <n> | 0.4 | How much longer to make the text, as a fraction: 0.4 adds about 40%. 0 turns lengthening off. |
--no-brackets | Do not wrap values in [[ and ]]. | |
-h, --help | Print usage. |
The command exits 0 on success, 1 when the source file cannot be read or is not valid JSON, and 2 on an unknown option, an invalid value, or a missing source file.
Give the pseudo-locale a code no real user has. en-XA is the usual choice. Register it in your i18n setup like any other locale, switch to it, and click through your screens. Regenerate it when your source strings change; a CI step or pre-commit hook keeps it current.
What is never transformed
Only the readable text between placeholders and markup changes. These stay exactly as written:
| Kind | Examples |
|---|---|
| HTML and XML tags | <b>, </b>, <br/>, <a title="a>b"> |
| Double-brace placeholders | {{name}} |
| ICU plural, select and selectordinal blocks, kept whole including the text inside | {count, plural, one {# item} other {# items}} |
| Single-brace placeholders | {name}, {0}, {count, number} |
| printf-style placeholders | %s, %d, %1$s, %.2f, %(name)s, %% |
| Colon parameters | :name |
Lengthening adds accented vowels inside the longest run of text, so a value that is only a placeholder is not padded.
Library
import { pseudoLocalize, pseudoLocalizeJson } from '@transglot/pseudo';
pseudoLocalize('Save changes');
// "[[Šáṽé çáéíöüĥáñğéš]]"
pseudoLocalize('Hi %s, you have :count items', { bracketOpen: '«', bracketClose: '»' });
// "«Ĥí %s, ýöüáéíöüáéí ĥáṽé :count íŧéɱš»"
const pseudo = pseudoLocalizeJson(JSON.parse(source), { nested: true, expansion: 0.3 });pseudoLocalize(value, options?)
pseudoLocalize(value: string, options?: PseudoOptions): string transforms one string.
Prop
Type
pseudoLocalizeJson(data, options?)
pseudoLocalizeJson(data: unknown, options?: PseudoJsonOptions): unknown transforms a parsed JSON tree. Numbers, booleans and null pass through unchanged. It takes the same options as pseudoLocalize, plus:
Prop
Type
tokenize(input)
tokenize(input: string) splits a string into the ordered runs of transformable text and protected placeholders that pseudoLocalize works from. Each token is { type: 'text' | 'protected', value: string }.