Skip to main content

Pseudolocalization and Unicode Stress Test Strings API

MOCK DATA

Hostile-but-safe text fixtures: RTL, bidi controls, CJK width, combining marks, ZWJ emoji, zero-width and length bombs — plus pseudo-localization.

Generated test data for fixtures and development

Base URL
/api/stress-strings
Capabilities
2 routes Seedable
Last updated
July 29, 2026
Data source

GET /api/stress-strings

Records come back in a seeded random order, so count=10 samples the whole corpus instead of returning the first ten. Pin seed to freeze the sample. ?exclude=value drops the raw string and keeps the print-safe escaped form — which is what the whole-corpus example below does, because an unterminated RIGHT-TO-LEFT OVERRIDE pasted into an HTML page reorders the rest of the line it lands on.

Live requestRuns against the public API
No key required
GET/api/stress-strings?exclude=value

Request parameters

Only return fixtures from this hazard family. Omit for the whole corpus. `length` is the only generated family — the rest are authored.

Maximum UTF-16 code-unit length of every returned fixture. Generated `length` fixtures are built as close to this cap as their repeating unit allows; curated fixtures longer than it are omitted with a warning rather than truncated.

Advanced response options7 options

Limit the number of returned records (1–100). Defaults to 10.

Deterministic output: the same seed always returns the same records. Omit for random (the used seed is echoed in meta.seed).

Return only these fields (comma-separated). Mutually exclusive with 'exclude'.

Return all fields except these (comma-separated).

Response format: json envelope, ndjson (one record per line) or csv.

Pretty-print the JSON response.

Drop the envelope: return the raw array/object without data/meta wrapper.

Response

Example parameters are ready. Send the request to inspect the live response.

Route reference

Code samples Ready-to-copy requests in 4 languages
Choose a code sample language
curl "https://randomapi.dev/api/stress-strings?exclude=value"
const res = await fetch("https://randomapi.dev/api/stress-strings?exclude=value");
const { data, meta } = await res.json();
import requests

data = requests.get("https://randomapi.dev/api/stress-strings?exclude=value").json()["data"]
$json = json_decode(file_get_contents(
  "https://randomapi.dev/api/stress-strings?exclude=value"
), true);
$data = $json["data"];
Parameters Route-specific request inputs 2
category enum

Only return fixtures from this hazard family. Omit for the whole corpus. `length` is the only generated family — the rest are authored.

allowed: rtl | bidi-control | cjk-width | combining | emoji-zwj | zero-width | homoglyph | whitespace | case-folding | normalization-unstable | length
example: category=bidi-control
maxLength int

Maximum UTF-16 code-unit length of every returned fixture. Generated `length` fixtures are built as close to this cap as their repeating unit allows; curated fixtures longer than it are omitted with a warning rather than truncated.

default: 256
allowed: 1 – 4000
example: maxLength=64
Universal parameters Shared response and formatting options 7
count int

Limit the number of returned records (1–100). Defaults to 10.

default: 10
allowed: 1 – 100
example: count=3
seed int

Deterministic output: the same seed always returns the same records. Omit for random (the used seed is echoed in meta.seed).

example: seed=42
fields list

Return only these fields (comma-separated). Mutually exclusive with 'exclude'.

example: fields=id,category
exclude list

Return all fields except these (comma-separated).

example: exclude=whyItBreaks
format enum

Response format: json envelope, ndjson (one record per line) or csv.

default: json
allowed: json | ndjson | csv
example: format=csv
pretty boolean

Pretty-print the JSON response.

default: false
example: pretty=true
unwrap boolean

Drop the envelope: return the raw array/object without data/meta wrapper.

default: false
example: unwrap=true
Response schema Fields returned in each record 10
id string

Stable kebab-case identifier for this fixture — safe to pin in a snapshot test.

example: whitespace-no-break-space

category string

Hazard family: rtl | bidi-control | cjk-width | combining | emoji-zwj | zero-width | homoglyph | whitespace | case-folding | normalization-unstable | length.

example: whitespace

value string

The fixture itself, exactly as authored. It travels raw through JSON, NDJSON and CSV — print it with care, or use `escaped` instead.

example: 10 kg

escaped string

Print-safe rendering of `value`: every control, format character, combining mark, surrogate, line/paragraph separator and non-ASCII space becomes a \uXXXX escape (\u{XXXXX} above the BMP), and a literal backslash is doubled. Visible characters — RTL letters, CJK, emoji bases — are untouched, so an `escaped` value carrying Hebrew or Arabic letters still reorders the neutrals around it.

example: 10\u00A0kg

codePoints object[]

The DISTINCT code points of `value` in order of first appearance, each with its Unicode `name` and two-letter General_Category. Use /api/unicode/inspect for a full positional walk.

example: [{"cp":"U+0031","name":"DIGIT ONE","category":"Nd"},{"cp":"U+0030","name":"DIGIT ZERO","category":"Nd"},{"cp":"U+00A0","name":"NO-BREAK SPACE","category":"Zs"},{"cp":"U+006B","name":"LATIN SMALL LETTER K","category":"Ll"},{"cp":"U+0067","name":"LATIN SMALL LETTER G","category":"Ll"}]

lengthUnits object

All four counts that software routinely conflates: `codePoints`, `utf16Units` (String.length), `utf8Bytes` and `graphemes` (UAX #29 clusters).

example: {"codePoints":5,"utf16Units":5,"utf8Bytes":6,"graphemes":5}

unstableUnder string[]

Which of NFC, NFD, NFKC and NFKD actually change this value, computed from the string itself. Empty when the value is stable under all four.

example: ["NFKC","NFKD"]

hazard string

What is dangerous about this fixture, in one sentence.

example: A NO-BREAK SPACE (U+00A0) where an ASCII space is expected.

expectedBehavior string

What correct software should do with it — the assertion your test is really making.

example: Normalize Unicode spaces to U+0020 on input, or match whitespace with a Unicode-aware class everywhere.

whyItBreaks string

The mechanism by which naive code gets this wrong.

example: JavaScript's \s matches it but a literal ' ' does not, and NFKC turns it into a plain space — so three layers of the same stack can disagree about the token boundaries.

Documented examples

Build-generated requests and complete responses
5
Ten hazard strings from across the corpus, print-safe
GET /api/stress-strings?exclude=value
{
  "data": [
    {
      "id": "bidi-source-comment-override",
      "category": "bidi-control",
      "escaped": "/* \\u202E begin admin block */",
      "codePoints": [
        {
          "cp": "U+002F",
          "name": "SOLIDUS",
          "category": "Po"
        },
        {
          "cp": "U+002A",
          "name": "ASTERISK",
          "category": "Po"
        },
        {
          "cp": "U+0020",
          "name": "SPACE",
          "category": "Zs"
        },
        {
          "cp": "U+202E",
          "name": "RIGHT-TO-LEFT OVERRIDE",
          "category": "Cf"
        },
        {
          "cp": "U+0062",
          "name": "LATIN SMALL LETTER B",
          "category": "Ll"
        },
        {
          "cp": "U+0065",
          "name": "LATIN SMALL LETTER E",
          "category": "Ll"
        },
        {
          "cp": "U+0067",
          "name": "LATIN SMALL LETTER G",
    …
    "generatedAt": "2026-07-29T08:13:22.000Z"
  }
}
Every bidi-control fixture, escape-rendered
GET /api/stress-strings?category=bidi-control&fields=id,escaped,hazard&count=100
{
  "data": [
    {
      "id": "bidi-source-comment-override",
      "escaped": "/* \\u202E begin admin block */",
      "hazard": "An unterminated RIGHT-TO-LEFT OVERRIDE inside what reads as an ordinary source comment — the code-point shape behind CVE-2021-42574, 'Trojan Source'."
    },
    {
      "id": "bidi-rlo-balanced",
      "escaped": "start\\u202Ereversed\\u202Cend",
      "hazard": "A RIGHT-TO-LEFT OVERRIDE (U+202E) closed by POP DIRECTIONAL FORMATTING (U+202C), forcing the text between them into right-to-left order."
    },
    {
      "id": "bidi-isolate-balanced",
      "escaped": "left\\u2066inner\\u2069right",
      "hazard": "A LEFT-TO-RIGHT ISOLATE (U+2066) closed by POP DIRECTIONAL ISOLATE (U+2069)."
    },
    {
      "id": "bidi-invisible-rlm",
      "escaped": "total: 5\\u200F",
      "hazard": "A trailing RIGHT-TO-LEFT MARK (U+200F): zero width, no glyph, but strongly right-to-left."
    },
    {
      "id": "bidi-unterminated-isolate",
      "escaped": "prefix\\u2067leaks",
      "hazard": "A RIGHT-TO-LEFT ISOLATE (U+2067) that is never closed by U+2069."
    }
  ],
  "meta": {
    "endpoint": "stress-strings",
    "count": 5,
    "seed": 42,
    "params": {
      "category": "bidi-control",
      "maxLength": 256
    },
    "generatedAt": "2026-07-29T08:13:22.000Z"
  }
}
Zero-width characters, reproducible order
GET /api/stress-strings?category=zero-width&seed=42
{
  "data": [
    {
      "id": "zero-width-word-joiner",
      "category": "zero-width",
      "value": "a⁠b",
      "escaped": "a\\u2060b",
      "codePoints": [
        {
          "cp": "U+0061",
          "name": "LATIN SMALL LETTER A",
          "category": "Ll"
        },
        {
          "cp": "U+2060",
          "name": "WORD JOINER",
          "category": "Cf"
        },
        {
          "cp": "U+0062",
          "name": "LATIN SMALL LETTER B",
          "category": "Ll"
        }
      ],
      "lengthUnits": {
        "codePoints": 3,
        "utf16Units": 3,
        "utf8Bytes": 5,
        "graphemes": 3
      },
      "unstableUnder": [],
      "hazard": "A WORD JOINER (U+2060) — the zero-width character that forbids a line break.",
      "expectedBehavior": "Handle it as a format character (General_Category Cf), not as whitespace.",
      "whyItBreaks": "It is not matched by \\s, so whitespace-based tokenizers and trimmers leave it in place while it still changes line breaking."
    },
    {
      "id": "zero-width-non-joiner",
      "category": "zero-width",
      "value": "ab‌cd",
      "escaped": "ab\\u200Ccd",
    …
    "generatedAt": "2026-07-29T08:13:22.000Z"
  }
}
Length bombs capped at 512 UTF-16 units
GET /api/stress-strings?category=length&maxLength=512
{
  "data": [
    {
      "id": "length-rtl-word-run",
      "category": "length",
      "value": "مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا ",
      "escaped": "مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا ",
      "codePoints": [
        {
          "cp": "U+0645",
          "name": "ARABIC LETTER MEEM",
          "category": "Lo"
        },
        {
          "cp": "U+0631",
          "name": "ARABIC LETTER REH",
          "category": "Lo"
        },
        {
          "cp": "U+062D",
          "name": "ARABIC LETTER HAH",
          "category": "Lo"
        },
        {
          "cp": "U+0628",
          "name": "ARABIC LETTER BEH",
          "category": "Lo"
        },
        {
          "cp": "U+0627",
          "name": "ARABIC LETTER ALEF",
          "category": "Lo"
        },
        {
          "cp": "U+0020",
          "name": "SPACE",
          "category": "Zs"
        }
      ],
      "lengthUnits": {
    …
    "generatedAt": "2026-07-29T08:13:22.000Z"
  }
}
Ids and print-safe forms as CSV
GET /api/stress-strings?fields=id,category,escaped&format=csv&count=8
id,category,escaped
bidi-source-comment-override,bidi-control,/* \u202E begin admin block */
homoglyph-cyrillic-admin,homoglyph,аdmin
whitespace-no-break-space,whitespace,10\u00A0kg
zero-width-space,zero-width,word\u200Bbreak
cjk-halfwidth-katakana,cjk-width,ハンカク
emoji-skin-tone,emoji-zwj,👍🏽
normalization-vulgar-fraction,normalization-unstable,½ cup
zero-width-variation-selector,zero-width,❤\uFE0E

GET /api/stress-strings/pseudo

Accents ASCII letters 1:1, pads with MIDDLE DOT to the requested expansion, and brackets the result. Deterministic: the same query always returns the same string.

Live requestRuns against the public API
No key required
GET/api/stress-strings/pseudo?text=Save+changes

Request parameters

The text to pseudo-localize, 1–2000 UTF-16 code units. Passed through character for character — leading and trailing whitespace is preserved, because that is often the bug.

Minimum length increase as a fraction of the input, measured in code points. 0.35 is the increase commonly budgeted for English into German; 0 disables padding entirely.

'accent' maps ASCII letters only, 'bracket' wraps only, 'both' does each. Padding is applied in every style.

Advanced response options4 options

Return only these fields (comma-separated). Mutually exclusive with 'exclude'.

Return all fields except these (comma-separated).

Pretty-print the JSON response.

Drop the envelope: return the raw array/object without data/meta wrapper.

Response

Example parameters are ready. Send the request to inspect the live response.

Route reference

Code samples Ready-to-copy requests in 4 languages
Choose a code sample language
curl "https://randomapi.dev/api/stress-strings/pseudo?text=Save%20changes"
const res = await fetch("https://randomapi.dev/api/stress-strings/pseudo?text=Save%20changes");
const { data, meta } = await res.json();
import requests

data = requests.get("https://randomapi.dev/api/stress-strings/pseudo?text=Save%20changes").json()["data"]
$json = json_decode(file_get_contents(
  "https://randomapi.dev/api/stress-strings/pseudo?text=Save%20changes"
), true);
$data = $json["data"];
Parameters Route-specific request inputs 3
text string required

The text to pseudo-localize, 1–2000 UTF-16 code units. Passed through character for character — leading and trailing whitespace is preserved, because that is often the bug.

allowed: 1 – 2000
example: text=Save changes
expansion float

Minimum length increase as a fraction of the input, measured in code points. 0.35 is the increase commonly budgeted for English into German; 0 disables padding entirely.

default: 0.35
allowed: 0 – 1
example: expansion=0.6
style enum

'accent' maps ASCII letters only, 'bracket' wraps only, 'both' does each. Padding is applied in every style.

default: both
allowed: accent | bracket | both
example: style=accent
Universal parameters Shared response and formatting options 4
fields list

Return only these fields (comma-separated). Mutually exclusive with 'exclude'.

example: fields=input,output
exclude list

Return all fields except these (comma-separated).

example: exclude=outputUtf8Bytes
pretty boolean

Pretty-print the JSON response.

default: false
example: pretty=true
unwrap boolean

Drop the envelope: return the raw array/object without data/meta wrapper.

default: false
example: unwrap=true
Response schema Fields returned in each record 11
input string

The text exactly as submitted; no trimming is applied.

example: Save changes

output string

The pseudo-localized string.

example: ⟦Śáṽé ćĥáñğéś···⟧

style string

Applied style: accent | bracket | both.

example: both

requestedExpansion float

The requested minimum length increase, as a fraction of the input.

example: 0.35

achievedExpansion float

Actual increase in code points, (output - input) / input, rounded UP to 6 decimals — rounding to nearest could report a hair under the request for an expansion with more than six decimals. Always ≥ requestedExpansion.

example: 0.416667

inputCodePoints integer

Code points in the input.

example: 12

outputCodePoints integer

Code points in the output.

example: 17

paddingCodePoints integer

How many MIDDLE DOT (U+00B7) padding characters were appended.

example: 3

lettersMapped integer

How many ASCII letters the accent map replaced; 0 when style=bracket.

example: 11

outputUtf16Units integer

String.length of the output. It exceeds the code point count only when the input itself carries astral characters — the accent map only ever emits BMP code points.

example: 17

outputUtf8Bytes integer

UTF-8 byte length of the output: the number a byte-sized database column actually has to hold.

example: 36

Documented examples

Build-generated requests and complete responses
4
Pseudo-localize a UI label
GET /api/stress-strings/pseudo?text=Save%20changes
{
  "data": {
    "input": "Save changes",
    "output": "⟦Śáṽé ćĥáñğéś···⟧",
    "style": "both",
    "requestedExpansion": 0.35,
    "achievedExpansion": 0.416667,
    "inputCodePoints": 12,
    "outputCodePoints": 17,
    "paddingCodePoints": 3,
    "lettersMapped": 11,
    "outputUtf16Units": 17,
    "outputUtf8Bytes": 36
  },
  "meta": {
    "endpoint": "stress-strings",
    "route": "/pseudo",
    "params": {
      "text": "Save changes",
      "expansion": 0.35,
      "style": "both"
    },
    "generatedAt": "2026-07-29T08:13:22.000Z"
  }
}
Accent the letters, keep the length
GET /api/stress-strings/pseudo?text=Save%20changes&style=accent&expansion=0
{
  "data": {
    "input": "Save changes",
    "output": "Śáṽé ćĥáñğéś",
    "style": "accent",
    "requestedExpansion": 0,
    "achievedExpansion": 0,
    "inputCodePoints": 12,
    "outputCodePoints": 12,
    "paddingCodePoints": 0,
    "lettersMapped": 11,
    "outputUtf16Units": 12,
    "outputUtf8Bytes": 24
  },
  "meta": {
    "endpoint": "stress-strings",
    "route": "/pseudo",
    "params": {
      "text": "Save changes",
      "expansion": 0,
      "style": "accent"
    },
    "generatedAt": "2026-07-29T08:13:22.000Z"
  }
}
Worst-case 60% expansion
GET /api/stress-strings/pseudo?text=Delete%20account&expansion=0.6
{
  "data": {
    "input": "Delete account",
    "output": "⟦Đéłéţé áććóúñţ·······⟧",
    "style": "both",
    "requestedExpansion": 0.6,
    "achievedExpansion": 0.642858,
    "inputCodePoints": 14,
    "outputCodePoints": 23,
    "paddingCodePoints": 7,
    "lettersMapped": 13,
    "outputUtf16Units": 23,
    "outputUtf8Bytes": 47
  },
  "meta": {
    "endpoint": "stress-strings",
    "route": "/pseudo",
    "params": {
      "text": "Delete account",
      "expansion": 0.6,
      "style": "both"
    },
    "generatedAt": "2026-07-29T08:13:22.000Z"
  }
}
Brackets only, to spot unlocalized strings
GET /api/stress-strings/pseudo?text=Settings&style=bracket
{
  "data": {
    "input": "Settings",
    "output": "⟦Settings·⟧",
    "style": "bracket",
    "requestedExpansion": 0.35,
    "achievedExpansion": 0.375,
    "inputCodePoints": 8,
    "outputCodePoints": 11,
    "paddingCodePoints": 1,
    "lettersMapped": 0,
    "outputUtf16Units": 11,
    "outputUtf8Bytes": 16
  },
  "meta": {
    "endpoint": "stress-strings",
    "route": "/pseudo",
    "params": {
      "text": "Settings",
      "expansion": 0.35,
      "style": "bracket"
    },
    "generatedAt": "2026-07-29T08:13:22.000Z"
  }
}

About this API

Coverage & behavior

A pseudo-localization endpoint and a curated set of safe Unicode stress fixtures — free, keyless and fully documented.

/pseudo — pseudo-localization

/api/stress-strings/pseudo?text=Save changes implements the same three-step technique Windows ships as the qps-ploc pseudo-locale. The steps are the standard ones; the specific accent map is ours.

  1. Accent — every ASCII letter is replaced through a fixed 1:1 map of 52 non-ASCII Latin letters with diacritics. Every target is a single BMP code point, so the character count never changes and nothing that is not an ASCII letter is touched.
  2. Pad — MIDDLE DOT (U+00B7) characters are appended until the output is at least expansion longer than the input, in code points. expansion defaults to 0.35, the length increase commonly budgeted for English into German.
  3. Bracket — the result is wrapped in MATHEMATICAL WHITE SQUARE BRACKETS (U+27E6 and U+27E7). A string that comes back without them was never sent through the localization pipeline; a string missing the closing bracket was truncated.

style=accent skips step 3, style=bracket skips step 1, and the default both does everything. Padding always applies, so expansion is meaningful for every style. Pseudo-localization is a QA technique, not a translation — the output is deliberately not a real language, and no claim is made that it resembles one.

/ — the hazard corpus

Every fixture reports its distinct codePoints with the Unicode name and General_Category of each, a print-safe escaped form, all four lengthUnits (code points, UTF-16 units, UTF-8 bytes and UAX #29 graphemes) and which normalization forms change it. category filters to one hazard family and maxLength caps the UTF-16 length of every returned fixture: the generated length fixtures are built as close to that cap as their repeating unit allows, and any curated fixture longer than it is omitted with a warning, never substituted. A filter that matches nothing returns an empty list and says so.

What this is not

These are encoding, rendering, normalization and length hazards — deliberately not attack payloads. There is no XSS, no SQL injection, no command injection, no slurs and no shock content anywhere in the corpus. If you need the attack-payload half, use the Big List of Naughty Strings directly; publishing that content as a free, unauthenticated, crawlable JSON API is a line we will not cross.

We never claim what a string will look like. Every field describes code points and their Unicode properties. Glyph shapes, ligatures, mark positioning and whether a sequence gets one glyph at all depend entirely on the font and shaping engine on the client, so "expected behavior" is always an assertion about your code, never about pixels.

The corpus is ours and curated: 45 authored fixtures plus 6 generated length fixtures, chosen to cover the failure modes that actually reach production. It makes no claim to exhaustiveness, and it is not a Unicode conformance test suite — for property lookups, normalization and segmentation of your own strings, use the unicode endpoint (/api/unicode/inspect, /normalize, /segment — it has no root route).

Use it for

  • Pseudo-localize UI strings to find truncation and hardcoded text before translation starts
  • Fuzz a form, CSV importer or search index with bidi controls, zero-width characters and combining marks
  • Assert that grapheme-aware truncation keeps ZWJ emoji and Devanagari clusters intact
  • Check a byte-sized database column against strings whose UTF-8 size is four times their character count

Frequently asked questions

How do I pseudo-localize a string for UI testing?

Call /api/stress-strings/pseudo?text=Save changes. It accents every ASCII letter through a fixed 1:1 map, then pads with MIDDLE DOT (U+00B7) and wraps in MATHEMATICAL WHITE SQUARE BRACKETS (U+27E6/U+27E7) so the output is at least 35% longer than the input — which makes truncation and unlocalized strings obvious.

Does this API include the Big List of Naughty Strings?

No, deliberately. BLNS bundles XSS and SQL injection payloads plus slurs and shock content; this corpus is encoding, rendering, normalization and length hazards only. Use BLNS directly if you need the attack-payload half.

How do I get a Trojan Source style bidi test string?

?category=bidi-control returns fixtures built from RIGHT-TO-LEFT OVERRIDE, the isolate characters and their pop counterparts, including one unterminated isolate and one comment-shaped override — the code-point shape behind CVE-2021-42574.

Why does every fixture also come back escaped?

Because printing a raw bidi control in a log, terminal or docs page can reorder the text around it with nothing visible to explain why. The escaped field replaces every control, format character, combining mark, line separator and non-ASCII space with a \uXXXX escape, so no invisible character survives into it. Right-to-left letters are preserved as-is — they still reorder neighbouring neutrals under UAX #9, so render escaped in a direction-isolated element.

Are the Unicode character names and categories real?

Yes. Every code point's name and category come from a pinned Unicode Character Database 17.0.0 snapshot, and the test suite re-checks each General_Category against the JavaScript engine's own tables. The strings themselves are authored by us, which is why the endpoint is labelled mock.

Will the API tell me how a string renders?

No. It reports code points, Unicode properties and four different length counts. Actual appearance depends on the font and shaping engine on your client, so we never assert a visual result.

Standards & references