Spaces:
Sleeping
Sleeping
File size: 2,147 Bytes
cd99321 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 | import type { JSX } from 'react'
import { useTranslation } from './TranslationContext'
interface TransHtmlProps<T extends keyof JSX.IntrinsicElements = 'span'> {
/**
* Translation key whose template legitimately contains markup (e.g.
* `'Turn <strong>{title}</strong> into a Journey'`).
*/
html: string
/**
* Values to interpolate into `{paramName}` placeholders. Every value is
* HTML-escaped before substitution, so passing user-controlled data is safe.
*/
params?: Record<string, string | number>
/**
* Element to render. Defaults to `<span>`. Use the tag that fits the
* surrounding flow — block, inline, list item, etc.
*/
as?: T
className?: string
/**
* `id` is forwarded so the component can be the target of `aria-labelledby`
* or `htmlFor`. Other ARIA attributes can be added if needed; we intentionally
* keep the surface small to discourage overloading this with arbitrary props.
*/
id?: string
}
/**
* Renders a translation that contains markup (e.g. `<strong>`) safely.
*
* Replaces the pattern that bit us in the Journey suggestion banner:
* <span dangerouslySetInnerHTML={{ __html: t('...', { user_input }) }} />
*
* That pattern interpolates `user_input` into the template *before* React
* ever sees it, so a trip title like `<script>alert(1)</script>` would inject
* a script tag. `TransHtml` runs `tHtml()` which:
*
* 1. HTML-escapes every interpolated value, neutralising it.
* 2. Sanitises the resulting string against an inline tag allow-list.
*
* Use this for any user-controlled value that lands in a markup template.
* Plain text-only templates can continue to use `<>{t('key', params)}</>`.
*/
export function TransHtml<T extends keyof JSX.IntrinsicElements = 'span'>({
html,
params,
as,
className,
id,
}: TransHtmlProps<T>) {
const { tHtml } = useTranslation()
const Tag = (as ?? 'span') as keyof JSX.IntrinsicElements
return (
// eslint-disable-next-line react/no-danger -- sanitised by tHtml (defence in depth)
<Tag className={className} id={id} dangerouslySetInnerHTML={{ __html: tHtml(html, params) }} />
)
}
|