web-i18n-react-intl-3ff18b — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited web-i18n-react-intl-3ff18b (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Quick Guide:FormattedMessagerenders translated text in JSX,useIntlreturns strings for attributes and programmatic use, anddefineMessagesproduces descriptors the FormatJS CLI can extract.IntlProvidersupplies the context, and itsonErroris what separates a missing translation from a real failure. Everypluralandselectneeds anotherbranch. Version boundary: v7.x runs on React 16.6 through 19; v8 and later require React 19.
Detailed Resources:
createIntl, locale switching, types, lazy loadingFormattedMessage, including rich text with tag values. Followcomponent, or a comparison — useIntl().formatMessage(). Also in examples/core.md.
createIntl from@formatjs/intl, optionally re-supplied through RawIntlProvider. In examples/core.md.
<critical_requirements>
Wrap the tree in `IntlProvider` with `locale`, `messages` and `defaultLocale`. Every FormattedMessage and useIntl call reads that context, and defaultLocale is what a missing translation falls back to instead of surfacing the raw ID.
Give every `plural` and `select` an `other` branch. ICU requires it, and a message without one throws when formatted rather than when authored.
Match the major version to the React version in the project. v7.x covers React 16.6 through 19; v8 and later dropped everything before React 19.
</critical_requirements>
Auto-detection: react-intl, FormatJS, FormattedMessage, useIntl, IntlProvider, RawIntlProvider, defineMessages, defineMessage, createIntl, formatMessage, FormattedDate, FormattedNumber, FormattedRelativeTime, ICU message format
Applies to:
Handled elsewhere:
settles nothing about detection, routing or storage
do that better than a client-side provider, and this skill does not compete with it
between is not this skill's concern
<philosophy>
ICU Message Format is the point: it is what translation vendors already speak, so a message written in it moves through a professional workflow without a conversion step. Everything else follows. Formatting is delegated to the browser's own Intl APIs rather than reimplemented, which is why locale-specific behaviour is correct for locales nobody tested. And the API is deliberately doubled — a component for JSX and a hook for strings — because a ReactNode cannot be put in an attribute.
</philosophy>
<patterns>
onError is where a missing translation is separated from a real failure, and defaultRichTextElements gives <b>, <i> and <br> one definition for the whole app.
<IntlProvider
locale={locale}
defaultLocale={DEFAULT_LOCALE}
messages={messages}
defaultRichTextElements={DEFAULT_RICH_TEXT_ELEMENTS}
onError={(err) => {
if (err.code === "MISSING_TRANSLATION") return;
throw err;
}}
>
{children}
</IntlProvider>Full code: examples/core.md
For text rendered directly in JSX, including messages carrying ICU syntax.
<FormattedMessage
id="greeting.unread"
defaultMessage="{count, plural, =0 {No messages} one {# message} other {# messages}}"
values={{ count: unreadCount }}
/>It returns a ReactNode, so an attribute — placeholder, aria-label, title — needs Pattern 3 instead.
Full code: examples/core.md
For any context that needs a string: attributes, document titles, third-party props, or a value the code then compares.
const intl = useIntl();
const placeholder = intl.formatMessage({
id: "search.placeholder",
defaultMessage: "Search products...",
});Full code: examples/core.md
Descriptors the CLI can find statically. description is the only channel a translator has for context.
export const productMessages = defineMessages({
reviewCount: {
id: "product.reviewCount",
defaultMessage:
"{count, plural, =0 {No reviews} one {# review} other {# reviews}}",
description: "Number of product reviews with pluralization",
},
});Spread a descriptor into FormattedMessage, or pass it to intl.formatMessage.
Full code: examples/core.md
Tags in the message map to values, so the sentence stays in one translation unit and the translator can reorder the tags to fit the target grammar.
<FormattedMessage
id="terms.notice"
defaultMessage="You agree to our <terms>Terms</terms> and <privacy>Privacy Policy</privacy>."
values={{
terms: (chunks) => <a href="/terms">{chunks}</a>,
privacy: (chunks) => <a href="/privacy">{chunks}</a>,
}}
/>Full code: examples/core.md
A component per value kind, each with an imperative twin on useIntl for string contexts.
<FormattedDate value={date} year="numeric" month="long" day="numeric" />
<FormattedNumber value={amount} style="currency" currency={currency} />
<FormattedList type="conjunction" value={names} />Full code: examples/formatting.md
Augment the FormatjsIntl.Message interface and a wrong ID becomes a compile error.
declare global {
namespace FormatjsIntl {
interface Message {
ids: keyof typeof messages;
}
}
}Add "esnext.intl" to compilerOptions.lib.
Full code: examples/core.md
Extract descriptors, send the JSON out for translation, compile what comes back.
formatjs extract 'src/**/*.{ts,tsx}' --out-file lang/en.json
formatjs compile lang/en.json --out-file compiled/en.json --astCompiling to AST skips parsing at runtime — 30-50% off first render for a large catalog.
Plural category counts differ by language: English has one/other, Russian adds few and many, Arabic adds zero and two.
{count, plural, =0 {No items} one {# item} other {# items}}
{position, selectordinal, one {#st} two {#nd} few {#rd} other {#th}}
{gender, select, male {He} female {She} other {They}} liked your post.Full code: examples/pluralization.md
</patterns>
<performance>
Compile to AST at build time — Pattern 8's formatjs compile --ast step, which is where the largest single win is.
Load one locale at a time with dynamic imports, and cache what comes back so a switch back is free. Implementation in examples/core.md.
Define messages outside the component. An object literal passed inline to FormattedMessage is a new reference every render, which defeats memoization.
`createIntl` + `createIntlCache` + `RawIntlProvider` puts the intl object under explicit control when you want to memoize it yourself.
</performance>
<red_flags>
Breaks at runtime:
plural or select with no other branch — ICU requires it and formatting throwsuseIntl or FormattedMessage outside IntlProvider — there is no context to readFormattedMessage in an attribute — it is a ReactNode, so placeholder, aria-label andtitle receive an object
injectIntl — removed in v10; useIntl replaces itSurprising behaviour:
formatMessage returns string normally, but string | ReactNode[] as soon as a value is a richtext tag function
chunks as an array, not a single elementformatNumber with style: "percent" expects a fraction — 0.25 renders as 25%formatRelativeTime is relative to now: negative is past, positive is future' escapes the next special character and '' produces aliteral apostrophe, so an unescaped apostrophe in an English message can swallow the rest of it
defaultLocale, a missing translation renders the raw message IDonError, every missing translation writes to the console{count} on its own never pluralizes, and a plural branch without # renders no number at allonWarn on IntlProvider is what quiets defaultRichTextElements warnings when messages are notpre-compiled
Anti-patterns with the code that fixes them: reference.md.
</red_flags>
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.