src/skills/web-i18n-react-intl/SKILL.md
ICU message format internationalization
npx skillsauth add agents-inc/skills web-i18n-react-intlInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Quick Guide: Use react-intl for internationalization with ICU Message Format.
FormattedMessagefor JSX content,useIntlfor string attributes and programmatic use,defineMessagesfor extractable message descriptors. Wrap app withIntlProviderand configureonErrorfor missing translations. Always include theothercategory in plurals and selects.Version Note: react-intl v7.x supports React 16.6+/17/18/19. v8+ requires React 19 only (React 18 support dropped). Current latest: v10.x.
<critical_requirements>
All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,
import type, named constants)
(You MUST wrap the application root with IntlProvider and configure locale, messages, and defaultLocale)
(You MUST include the other category in ALL plural and select ICU messages - omission causes runtime errors)
(You MUST use named constants for locale codes - NO inline locale strings)
(You MUST verify React version compatibility: v7.x supports React 16.6-19, v8+ requires React 19 only)
</critical_requirements>
Auto-detection: react-intl, FormatJS, FormattedMessage, useIntl, IntlProvider, defineMessages, ICU message format, formatMessage, FormattedDate, FormattedNumber, FormattedRelativeTime
When to use:
Key patterns covered:
When NOT to use:
createIntl from @formatjs/intl)Detailed Resources:
React-intl follows the principle of ICU Message Format standardization with both declarative and imperative APIs. Translations use industry-standard ICU syntax enabling compatibility with professional translation management systems. The library is built on browser-native Intl APIs for optimal performance and accurate locale-aware formatting.
Core principles:
Wrap your application root with IntlProvider. Configure onError to distinguish missing translations from actual errors, set defaultLocale for fallback, and define defaultRichTextElements for consistent markup.
export function AppIntlProvider({ children, locale, messages }: Props) {
return (
<IntlProvider
locale={locale}
defaultLocale={DEFAULT_LOCALE}
messages={messages}
defaultRichTextElements={DEFAULT_RICH_TEXT_ELEMENTS}
onError={(err) => {
if (err.code === "MISSING_TRANSLATION") {
console.warn(`Missing translation: ${err.message}`);
return;
}
throw err;
}}
>
{children}
</IntlProvider>
);
}
Why good: custom onError distinguishes missing translations from actual errors, defaultLocale provides fallback, defaultRichTextElements ensure consistent markup
See examples/core.md for full setup with locale config, lazy loading, and app integration.
Use FormattedMessage for rendering translated text directly in JSX elements. Supports ICU syntax for interpolation, pluralization, and rich text.
<FormattedMessage
id="greeting.unread"
defaultMessage="{count, plural, =0 {No messages} one {# message} other {# messages}}"
values={{ count: unreadCount }}
/>
When to use: Text content rendered directly in JSX, rich text with embedded formatting.
When not to use: String attributes like placeholder, aria-label, title (use useIntl instead).
Use useIntl when you need formatted strings for attributes, props, or programmatic use.
const intl = useIntl();
const placeholder = intl.formatMessage({
id: "search.placeholder",
defaultMessage: "Search products...",
});
<input placeholder={placeholder} aria-label={ariaLabel} />
When to use: Input placeholders, ARIA labels, document titles, third-party component props, conditional logic based on formatted values.
Group related messages with defineMessages for CLI extraction and IDE autocomplete.
export const productMessages = defineMessages({
title: {
id: "product.title",
defaultMessage: "Product Details",
description: "Page title for product detail page",
},
reviewCount: {
id: "product.reviewCount",
defaultMessage:
"{count, plural, =0 {No reviews} one {# review} other {# reviews}}",
description: "Number of product reviews with pluralization",
},
});
Why good: centralizes related messages, descriptions provide translator context, CLI extracts these automatically, IDE autocomplete for references
See examples/core.md for usage patterns with FormattedMessage and useIntl.
Use XML-like tags in messages for embedded markup. Translators can reorder tags per language grammar while the complete sentence stays in one translation unit.
<FormattedMessage
id="terms.notice"
defaultMessage="By signing up, 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>,
}}
/>
Configure global tag handlers via defaultRichTextElements on IntlProvider for <b>, <i>, <br> tags.
Locale-aware formatting for dates, numbers, currency, relative time, and lists.
<FormattedDate value={date} year="numeric" month="long" day="numeric" />
// en-US: "January 15, 2024" | de-DE: "15. Januar 2024"
<FormattedNumber value={amount} style="currency" currency={currency} />
// en-US: "$1,234.56" | de-DE: "1.234,56 EUR"
<FormattedList type="conjunction" value={names} />
// en: "Alice, Bob, and Charlie" | es: "Alice, Bob y Charlie"
Use imperative equivalents (intl.formatDate(), intl.formatNumber()) when you need strings for attributes or programmatic use.
See examples/formatting.md for comprehensive date/time, number, currency, relative time, and list examples.
Enable type-safe message IDs with TypeScript module augmentation.
// src/types/intl.d.ts
import type messages from "../lang/en.json";
type MessageIds = keyof typeof messages;
declare global {
namespace FormatjsIntl {
interface Message {
ids: MessageIds;
}
}
}
Typos in message IDs become compile-time errors. Add "esnext.intl" to compilerOptions.lib in tsconfig.json.
Use FormatJS CLI for extracting and compiling messages.
formatjs extract 'src/**/*.{ts,tsx}' --out-file lang/en.jsonformatjs compile lang/en.json --out-file compiled/en.json --astWhy compile to AST: 30-50% faster initial render for large message catalogs - skips runtime parsing.
ICU plural syntax handles language-specific rules. Always include other as fallback.
{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.
Different languages have different plural categories (English: one/other, Russian: one/few/many/other, Arabic: zero/one/two/few/many/other).
See examples/pluralization.md for nested patterns, ordinals, select, and language-specific examples.
</patterns>Compile messages to AST at build time to skip runtime parsing. Impact: 30-50% faster initial render for large catalogs.
Use dynamic imports to load only the current locale's messages. Cache loaded messages to prevent duplicate fetches. See examples/core.md for implementation.
Use createIntl + createIntlCache + RawIntlProvider for manual control over intl object creation. Useful when you want to memoize the intl instance explicitly.
Define messages outside components with defineMessages rather than passing inline objects to FormattedMessage. Inline objects create new references each render, preventing memoization optimizations.
<red_flags>
High Priority Issues:
IntlProvider wrapper - All useIntl and FormattedMessage calls fail without contextother category in plural/select - Runtime error: "other" is REQUIRED in ICU syntaxMedium Priority Issues:
defaultLocale on IntlProvider - No fallback for missing translationsonError handler - Console noise for every missing translationCommon Mistakes:
{count} instead of {count, plural, ...} for countable items# in plural branches (shows nothing instead of the count value)Gotchas & Edge Cases:
FormattedMessage returns ReactNode, not string - cannot use for HTML attributesformatMessage returns string for plain values, but string | ReactNode[] when rich text tag functions are in valueschunks array, not single elementformatNumber with style: "percent" expects decimal (0.25 for 25%), not percentageformatRelativeTime value is relative to NOW - negative for past, positive for future' escapes special characters, double single quote '' produces literal apostropheinjectIntl HOC was removed in v10 - use useIntl hook insteadonWarn prop on IntlProvider to suppress or handle defaultRichTextElements warnings when messages are not pre-compiled</red_flags>
<critical_reminders>
All code must follow project conventions in CLAUDE.md
(You MUST wrap the application root with IntlProvider and configure locale, messages, and defaultLocale)
(You MUST include the other category in ALL plural and select ICU messages - omission causes runtime errors)
(You MUST use named constants for locale codes - NO inline locale strings)
(You MUST verify React version compatibility: v7.x supports React 16.6-19, v8+ requires React 19 only)
Failure to follow these rules will cause runtime errors and broken internationalization.
</critical_reminders>
development
Material Design component library for Vue 3
development
VitePress 1.x — Vue-powered static site generator for documentation sites, built on Vite
tools
Docusaurus 3.x documentation framework — site configuration, docs/blog plugins, sidebars, versioning, MDX, swizzling, and deployment
development
TanStack Form patterns - useForm, form.Field, validators, arrays, linked fields, createFormHook, type safety