Skip to content

Tag Interpolation

Tag interpolation lets translators use tags like <link> and <bold> inside translation values. Your code maps those tag names to real UI elements or components, and the <T> component renders the result.

What this feature does:

  • Lets translators move semantic tags around inside translated sentences.
  • Lets developers decide what each tag renders to: a link, styled text, a framework component, or an allowed HTML tag.
  • Escapes plain translation text and interpolation values by default.
  • Falls back to inner text when a tag has no handler, unless strict mode is enabled.

What this feature does not do:

  • It does not render arbitrary HTML from translation strings.
  • It does not let translators set attributes like href, class, or event handlers. Attributes come from your code.
  • It does not instantiate framework components by tag name from the translation file.
  • It does not make t() return HTML. Use <T> for UI rich text.

In Svelte, <T> internally renders a library-generated HTML string via {@html}. Translation text and attributes are escaped, unsafe attributes are blocked, and only allowed tag names are emitted.

A translation value contains named tags:

{
"help": "Click <link>here</link> for help"
}

Your code provides a handler for each tag. Comvi i18n parses the translation, matches tags to handlers, and returns the result:

import { T } from '@comvi/react';
// React element — children auto-injected via cloneElement
<T
i18nKey="help"
components={{ link: <a href="/help" /> }}
/>
// → Click <a href="/help">here</a> for help
// Function handler — full control
<T
i18nKey="help"
components={{
link: ({ children }) => <a href="/help">{children}</a>,
}}
/>

Translators move the tags freely without touching code. The developer controls what each tag renders.

TypeExampleBehavior
React element<a href="/help" />Children auto-injected via cloneElement
Function({ children }) => <a>{children}</a>Full control over rendering
String"strong"Renders as that HTML element
<T
i18nKey="demo"
components={{
link: <a href="/help" />, // React element
bold: ({ children }) => <b>{children}</b>, // Function
highlight: "mark", // String → <mark>
}}
/>

Tags can be nested. Each tag gets its own handler:

{
"msg": "Click <a><b>here</b></a> to continue"
}
<T
i18nKey="msg"
components={{
a: <a href="/next" />,
b: <strong />,
}}
/>
// → Click <a href="/next"><strong>here</strong></a> to continue

Use <tag/> for elements without children:

{
"address": "Line 1<br/>Line 2<br/>Line 3"
}
<T i18nKey="address" components={{ br: <br /> }} />

Tags and ICU parameters work together. Use {param} and {count, plural, ...} inside tagged sections:

{
"promo": "Get <discount>{percent}% off</discount> today!",
"cart": "{count, plural, one {<b># item</b>} other {<b># items</b>}}"
}
<T
i18nKey="promo"
params={{ percent: 20 }}
components={{
discount: ({ children }) => <span className="text-red-500">{children}</span>,
}}
/>
// → Get <span class="text-red-500">20% off</span> today!
<T
i18nKey="cart"
params={{ count: 5 }}
components={{ b: <strong /> }}
/>
// → <strong>5 items</strong>

For simple formatting tags like <strong> or <em>, you can whitelist them globally so they render as HTML without needing handlers:

src/i18n.ts
const i18n = createI18n({
locale: 'en',
tagInterpolation: {
basicHtmlTags: ['strong', 'em', 'br', 'b', 'i', 'p', 'span'],
},
});
{
"note": "This is <strong>important</strong> and <em>urgent</em>"
}

Render the translation through <T>:

<T i18nKey="note" />

The whitelisted tags render as HTML elements automatically — no handler needed. Plain t('note') still returns text only ("This is important and urgent").

Handlers always take precedence over the whitelist. If you provide a handler for a whitelisted tag, the handler is used.

Control what happens when a tag in the translation has no handler:

src/i18n.ts
const i18n = createI18n({
locale: 'en',
tagInterpolation: {
strict: false, // false | 'warn' | true (default: false)
},
});
ModeBehaviorBest for
false (default)Falls back to inner text silentlyProduction
'warn'Calls onTagWarning or reports a warning, falls back to inner textDevelopment
trueThrows errorTesting / CI

Example with strict: false (default):

{
"msg": "Click <link>here</link>"
}
t('msg'); // No handler for <link> → "Click here"

When strict: 'warn', the library calls onTagWarning(tagName) for each unhandled tag. If you do not provide onTagWarning, the i18n instance reports the warning through its configured onError handler.

src/i18n.ts
const i18n = createI18n({
locale: 'en',
tagInterpolation: {
strict: 'warn',
onTagWarning: (tagName) => {
Sentry.captureMessage(`i18n: missing handler for <${tagName}>`, 'warning');
},
},
});

Important: onTagWarning is only called when strict: 'warn'. With strict: false warnings are silent, and with strict: true the library throws to the caller.

Use a backslash to include literal angle brackets in translations:

{
"code": "Use the \\<div> element"
}
t('code'); // → "Use the <div> element"

HTML entities are also supported: &lt;<, &gt;>, &amp;&.

Malformed tags are handled gracefully:

  • Unclosed tags (<link>here) — logs a warning, returns the raw text
  • Mismatched tags (<a><b>text</a></b>) — logs a warning, returns the raw text
  • Missing handlers — behavior depends on strict mode

No crashes in production. Errors are visible in development via console warnings.