4.5 KiB
InterpolationEngine
(no submodules)
Purpose
InterpolationEngine replaces placeholder tokens (default: {{ name }}) in message template strings with caller-supplied values. It is the rendering workhorse behind MessageCatalog.t() and standalone template usage. The engine is designed to be instantiated once and reused across many render calls, with all configuration captured in the constructor.
The engine handles the common i18n case of named variable substitution, and additionally supports pluggable per-slot value formatters (e.g., number, date, currency) via a named formatter registry. Values can be HTML-escaped on output to prevent XSS when templates are rendered into HTML.
Features
- Named placeholder substitution:
{{ name }},{{ count, number }}, etc. - Custom delimiter pairs (e.g.,
%{/}for Ruby-style templates) - Per-slot
ValueFormatterregistry — applied when a placeholder contains a format name - Optional
defaultFormattercalled for all slots without an explicit formatter - HTML escaping of rendered values (
escapeHtml: true) viaescapeHtmlChars - Recursive interpolation with configurable depth limit (
maxDepth) missingVariablestracking in the result — non-throwing for production resilience
Usage: Basic Example
import { InterpolationEngine } from "@suaveplan/i18n-icu-parser/interpolation";
const engine = new InterpolationEngine();
const { text } = engine.render(
"Hello, {{ name }}! You have {{ count }} messages.",
{ name: "Alice", count: 3 }
);
// text === "Hello, Alice! You have 3 messages."
Usage: Advanced Example
import { InterpolationEngine } from "@suaveplan/i18n-icu-parser/interpolation";
const engine = new InterpolationEngine({
escapeHtml: true,
formatters: {
upper: (v) => String(v).toUpperCase(),
number: (v, locale) =>
new Intl.NumberFormat(locale).format(Number(v)),
},
});
const { text, missingVariables } = engine.render(
"Welcome, {{ user, upper }}! Balance: {{ amount, number }}.",
{ user: "alice", amount: 1234567 },
"en-US"
);
// text === "Welcome, ALICE! Balance: 1,234,567."
// missingVariables === []
// HTML escaping
const { text: safe } = engine.render(
"Comment: {{ comment }}",
{ comment: "<script>alert('xss')</script>" }
);
// safe === "Comment: <script>alert('xss')</script>"
API Reference
new InterpolationEngine(options?: InterpolationOptions)
| Option | Type | Default | Description |
|---|---|---|---|
prefix |
string |
"{{" |
Placeholder open delimiter |
suffix |
string |
"}}" |
Placeholder close delimiter |
escapeHtml |
boolean |
false |
HTML-escape rendered values |
maxDepth |
number |
10 |
Maximum recursion depth |
formatters |
Record<string, ValueFormatter>? |
{} |
Named formatter map |
defaultFormatter |
ValueFormatter? |
— | Applied when no named formatter matches |
.render(template, values?, locale?): InterpolationResult
Renders template replacing all placeholders. Returns { text: string; missingVariables: string[] }.
.addFormatter(name, formatter): void
Registers or replaces a named formatter at runtime.
.removeFormatter(name): boolean
Removes a named formatter; returns true if it existed.
.hasPlaceholders(template): boolean
Returns true if the template contains at least one placeholder.
.extractVariables(template): string[]
Returns all unique variable names referenced in the template.
escapeHtmlChars(str: string): string
Standalone export. Escapes &, <, >, ", '.
escapeRegex(str: string): string
Standalone export. Escapes all regex metacharacters for safe use in RegExp.
Implementation Notes
- The placeholder regex is rebuilt on each call to
replaceValuesrather than cached, becauseoptions.prefix/suffixcould theoretically contain characters whose escaped forms would produce a different pattern — rebuilding is correct and the cost is negligible for the expected template sizes - Recursive interpolation is capped at
maxDepth(default 10) to prevent infinite loops when a value itself contains placeholder syntax - HTML escaping is applied after formatting so that formatter output (e.g.,
"$1,234") is escaped as a unit, not before missingVariablesaccumulates in-place via the closure; the engine never throws on a missing variable — it leaves the placeholder in place and records the name