Localizing the explanation
explainRegex gives each line a ready-made English text plus a
language-neutral desc. For other languages, call formatExplain(desc, messages)
with a translated table — it can be partial, and any key you omit falls back to
English. No i18n library required; the English text is itself rendered this
way, so the two never drift.
import { explainRegex, formatExplain } from '@wzo/regex-diagram'
// Zero config — item.text is ready-made English:
for (const it of explainRegex('a+', 'i') ?? [])
console.log(it.text)
// Localize — hand formatExplain your translated table.
// Templates use {placeholder} slots; any key you omit stays English.
const zh = {
csDigit: '数字(0-9)',
qExact: '恰好重复 {n} 次',
qRange: '重复 {min} 到 {max} 次',
groupNamed: '捕获组 #{index}(命名为 "{name}")',
// …only the keys you want to translate
}
for (const it of explainRegex('(?<y>\\d{2,4})') ?? [])
console.log(formatExplain(it.desc, zh))
The message table
Every key formatExplain understands, with its default English template — this
is the EXPLAIN_EN table you spread from. {...} slots are filled at render
time; override any subset and the rest stay English.
export const EXPLAIN_EN = {
char: 'the character "{value}"',
space: 'a space',
text: 'the text "{value}"',
csAny: 'any character (except line breaks)',
csDigit: 'a digit (0-9)',
csDigitNeg: 'any non-digit',
csWord: 'a word character (letter, digit, or underscore)',
csWordNeg: 'a non-word character',
csSpace: 'a whitespace character',
csSpaceNeg: 'a non-whitespace character',
csProperty: 'a character with Unicode property {raw}',
classEmpty: 'nothing',
classEmptyNeg: 'any character',
classOneOf: 'one of: {parts}',
classExcept: 'any character except: {parts}',
listSep: ', ',
classExpr: 'a set-operation character class (see the diagram)',
anchorStart: 'the start of the string (or line with the m flag)',
anchorEnd: 'the end of the string (or line with the m flag)',
wordBoundary: 'a word boundary',
notWordBoundary: 'a position that is not a word boundary',
followedBy: 'followed by',
notFollowedBy: 'not followed by',
precededBy: 'preceded by',
notPrecededBy: 'not preceded by',
qStar: 'repeated zero or more times',
qPlus: 'repeated one or more times',
qMin: 'repeated {min} or more times',
qOptional: 'optional (zero or one time)',
qExact: 'repeated exactly {n} times',
qRange: 'repeated {min} to {max} times',
lazy: '{base} (lazy)',
groupNamed: 'capturing group #{index} (named "{name}")',
group: 'capturing group #{index}',
groupNonCapturing: 'a non-capturing group',
backref: 'a back-reference',
backrefTo: 'a back-reference to group #{index}',
option: 'option {n}',
withColon: '{text}:',
}
ExplainDesc
The description handed to formatExplain. Switch on kind if you want full
control instead of a table.
// (shape simplified; see the .d.ts for the exact discriminated unions)
type ExplainDesc =
| { kind: 'char', value: string }
| { kind: 'text', value: string }
| { kind: 'charset', set: 'any' | 'digit' | 'word' | 'space' | 'property', negate?: boolean, raw?: string }
| { kind: 'class', negate: boolean, members: ClassMember[] }
| { kind: 'anchor', at: 'start' | 'end' | 'word', negate?: boolean }
| { kind: 'lookaround', dir: 'ahead' | 'behind', negate: boolean }
| { kind: 'quantifier', min: number, max: number, greedy: boolean }
| { kind: 'group', capturing: boolean, index?: number, name?: string }
| { kind: 'backref', index?: number }
| { kind: 'option', n: number }
// …and 'space' / 'classExpr'