Appearance
React-Intl使用 - FormatJS国际化完整指南
1. React-Intl概述
1.1 什么是React-Intl
React-Intl是FormatJS的一部分,提供了React组件和API用于国际化。它基于ECMAScript国际化API(Intl),提供强大的格式化能力。
核心特性:
- 基于标准的Intl API
- 声明式组件API
- 丰富的格式化功能
- TypeScript支持
- 消息提取工具
1.2 与react-i18next对比
typescript
const comparison = {
'react-intl': {
pros: ['标准化API', 'ICU消息格式', '强大格式化', 'CLI工具'],
cons: ['学习曲线陡峭', '包体积较大'],
bestFor: ['企业应用', '复杂格式化需求']
},
'react-i18next': {
pros: ['简单易用', '灵活配置', '丰富插件'],
cons: ['格式化功能较弱'],
bestFor: ['快速开发', '中小型应用']
}
};2. 安装与配置
2.1 安装依赖
bash
# 核心库
npm install react-intl
# 消息提取工具
npm install --save-dev @formatjs/cli
# TypeScript类型
npm install --save-dev @types/react-intl2.2 基础配置
tsx
// App.tsx
import { IntlProvider } from 'react-intl';
import messages_en from './translations/en.json';
import messages_zh from './translations/zh.json';
const messages: Record<string, any> = {
'en': messages_en,
'zh': messages_zh
};
function App() {
const [locale, setLocale] = useState('en');
return (
<IntlProvider
locale={locale}
messages={messages[locale]}
defaultLocale="en"
>
<Main />
</IntlProvider>
);
}2.3 翻译文件格式
json
// translations/en.json
{
"app.welcome": "Welcome",
"app.greeting": "Hello, {name}!",
"app.itemCount": "{count, plural, =0 {No items} one {# item} other {# items}}",
"app.date": "Today is {date, date, long}",
"app.price": "Price: {amount, number, currency}"
}
// translations/zh.json
{
"app.welcome": "欢迎",
"app.greeting": "你好,{name}!",
"app.itemCount": "{count}个项目",
"app.date": "今天是{date, date, long}",
"app.price": "价格:{amount, number, currency}"
}3. 基础使用
3.1 FormattedMessage组件
tsx
import { FormattedMessage } from 'react-intl';
// 简单消息
function Welcome() {
return (
<h1>
<FormattedMessage id="app.welcome" />
</h1>
);
}
// 带变量
function Greeting() {
return (
<p>
<FormattedMessage
id="app.greeting"
values={{ name: 'John' }}
/>
</p>
);
}
// 默认消息(翻译缺失时显示)
function WithDefault() {
return (
<FormattedMessage
id="app.message"
defaultMessage="Default message"
/>
);
}
// 描述(帮助翻译人员理解上下文)
function WithDescription() {
return (
<FormattedMessage
id="app.submit"
defaultMessage="Submit"
description="Button to submit the form"
/>
);
}3.2 useIntl Hook
tsx
import { useIntl } from 'react-intl';
function Component() {
const intl = useIntl();
// 格式化消息
const message = intl.formatMessage({ id: 'app.welcome' });
// 带变量
const greeting = intl.formatMessage(
{ id: 'app.greeting' },
{ name: 'John' }
);
// 用于属性
return (
<div>
<input
placeholder={intl.formatMessage({ id: 'app.search' })}
aria-label={intl.formatMessage({ id: 'app.searchLabel' })}
/>
<h1>{message}</h1>
<p>{greeting}</p>
</div>
);
}3.3 defineMessages
tsx
import { defineMessages, useIntl } from 'react-intl';
// 定义消息
const messages = defineMessages({
welcome: {
id: 'app.welcome',
defaultMessage: 'Welcome'
},
greeting: {
id: 'app.greeting',
defaultMessage: 'Hello, {name}!',
description: 'Greeting message'
},
submit: {
id: 'app.submit',
defaultMessage: 'Submit'
}
});
// 使用
function Component() {
const intl = useIntl();
return (
<div>
<h1>{intl.formatMessage(messages.welcome)}</h1>
<p>{intl.formatMessage(messages.greeting, { name: 'John' })}</p>
<button>{intl.formatMessage(messages.submit)}</button>
</div>
);
}4. 格式化功能
4.1 日期时间格式化
tsx
import { FormattedDate, FormattedTime, FormattedRelativeTime } from 'react-intl';
function DateFormatting() {
const date = new Date();
return (
<div>
{/* 日期格式 */}
<FormattedDate value={date} />
<FormattedDate value={date} year="numeric" month="long" day="2-digit" />
<FormattedDate value={date} dateStyle="full" />
{/* 时间格式 */}
<FormattedTime value={date} />
<FormattedTime value={date} hour="2-digit" minute="2-digit" second="2-digit" />
{/* 相对时间 */}
<FormattedRelativeTime value={-1} unit="hour" />
<FormattedRelativeTime value={2} unit="day" />
<FormattedRelativeTime value={-30} unit="minute" updateIntervalInSeconds={1} />
</div>
);
}
// Hook方式
function DateHook() {
const intl = useIntl();
const date = new Date();
const formatted = intl.formatDate(date, {
year: 'numeric',
month: 'long',
day: 'numeric'
});
const time = intl.formatTime(date);
const relative = intl.formatRelativeTime(-1, 'hour');
return (
<div>
<p>{formatted}</p>
<p>{time}</p>
<p>{relative}</p>
</div>
);
}4.2 数字格式化
tsx
import { FormattedNumber } from 'react-intl';
function NumberFormatting() {
return (
<div>
{/* 基础数字 */}
<FormattedNumber value={1234567.89} />
{/* 百分比 */}
<FormattedNumber value={0.157} style="percent" />
{/* 货币 */}
<FormattedNumber
value={99.99}
style="currency"
currency="USD"
/>
{/* 紧凑格式 */}
<FormattedNumber
value={1234567}
notation="compact"
compactDisplay="short"
/>
{/* 单位 */}
<FormattedNumber
value={100}
style="unit"
unit="kilometer"
/>
</div>
);
}
// Hook方式
function NumberHook() {
const intl = useIntl();
const number = intl.formatNumber(1234567.89);
const percent = intl.formatNumber(0.157, { style: 'percent' });
const currency = intl.formatNumber(99.99, {
style: 'currency',
currency: 'USD'
});
return (
<div>
<p>{number}</p>
<p>{percent}</p>
<p>{currency}</p>
</div>
);
}4.3 复数规则
tsx
// ICU消息格式
const messages = {
items: '{count, plural, =0 {No items} one {# item} other {# items}}'
};
function PluralExample() {
return (
<div>
<FormattedMessage id="items" values={{ count: 0 }} />
{/* 输出: No items */}
<FormattedMessage id="items" values={{ count: 1 }} />
{/* 输出: 1 item */}
<FormattedMessage id="items" values={{ count: 5 }} />
{/* 输出: 5 items */}
</div>
);
}
// 复杂复数
const complexMessages = {
cart: `You have {itemCount, plural,
=0 {no items}
one {# item}
other {# items}
} and {productCount, plural,
=0 {no products}
one {# product}
other {# products}
} in your cart.`
};4.4 选择格式
tsx
// select格式
const messages = {
gender: '{gender, select, male {He} female {She} other {They}} invited you',
status: '{status, select, pending {⏳ Pending} approved {✅ Approved} rejected {❌ Rejected} other {Unknown}}'
};
function SelectExample() {
return (
<div>
<FormattedMessage
id="gender"
values={{ gender: 'female' }}
/>
{/* 输出: She invited you */}
<FormattedMessage
id="status"
values={{ status: 'approved' }}
/>
{/* 输出: ✅ Approved */}
</div>
);
}5. 高级功能
5.1 富文本消息
tsx
// 使用标签
const messages = {
terms: 'I agree to the <link>terms and conditions</link>',
bold: 'This is <b>bold</b> text',
multiple: 'Click <link1>here</link1> or <link2>there</link2>'
};
function RichText() {
return (
<FormattedMessage
id="terms"
values={{
link: (chunks) => <a href="/terms">{chunks}</a>
}}
/>
);
}
// 多个标签
function MultipleRichText() {
return (
<FormattedMessage
id="multiple"
values={{
link1: (chunks) => <a href="/page1">{chunks}</a>,
link2: (chunks) => <a href="/page2">{chunks}</a>
}}
/>
);
}
// 嵌套标签
function NestedRichText() {
return (
<FormattedMessage
id="nested"
defaultMessage="<p>This is <b>bold and <i>italic</i></b> text</p>"
values={{
p: (chunks) => <p>{chunks}</p>,
b: (chunks) => <strong>{chunks}</strong>,
i: (chunks) => <em>{chunks}</em>
}}
/>
);
}5.2 自定义格式化
tsx
// 配置自定义格式
<IntlProvider
locale={locale}
messages={messages}
formats={{
number: {
USD: {
style: 'currency',
currency: 'USD'
},
EUR: {
style: 'currency',
currency: 'EUR'
}
},
date: {
short: {
day: 'numeric',
month: 'short',
year: 'numeric'
},
long: {
day: 'numeric',
month: 'long',
year: 'numeric',
weekday: 'long'
}
}
}}
>
<App />
</IntlProvider>
// 使用自定义格式
function CustomFormats() {
return (
<div>
<FormattedNumber value={99.99} format="USD" />
<FormattedDate value={new Date()} format="long" />
</div>
);
}5.3 消息描述符
tsx
// 类型安全的消息
interface MessageDescriptor {
id: string;
defaultMessage?: string;
description?: string;
}
const messages: Record<string, MessageDescriptor> = {
welcome: {
id: 'app.welcome',
defaultMessage: 'Welcome to our app',
description: 'Welcome message on homepage'
},
submit: {
id: 'form.submit',
defaultMessage: 'Submit',
description: 'Submit button label'
}
};
// 使用
function Component() {
const intl = useIntl();
return (
<div>
<h1>{intl.formatMessage(messages.welcome)}</h1>
<button>{intl.formatMessage(messages.submit)}</button>
</div>
);
}6. 消息提取
6.1 CLI工具
bash
# 提取消息
npx formatjs extract 'src/**/*.{ts,tsx}' --out-file lang/en.json --id-interpolation-pattern '[sha512:contenthash:base64:6]'
# 编译消息(优化)
npx formatjs compile lang/en.json --out-file compiled-lang/en.json
# 批处理
npm run extract:messagesjson
// package.json
{
"scripts": {
"extract:messages": "formatjs extract 'src/**/*.{ts,tsx}' --out-file lang/en.json",
"compile:messages": "formatjs compile-folder lang compiled-lang"
}
}6.2 Babel插件
bash
npm install --save-dev babel-plugin-formatjsjavascript
// .babelrc
{
"plugins": [
["formatjs", {
"idInterpolationPattern": "[sha512:contenthash:base64:6]",
"ast": true
}]
]
}6.3 自动生成ID
tsx
// 使用默认消息自动生成ID
<FormattedMessage
defaultMessage="Welcome to our app"
description="Welcome message"
/>
// 提取后生成:
{
"hak27d": {
"defaultMessage": "Welcome to our app",
"description": "Welcome message"
}
}7. TypeScript集成
7.1 类型定义
typescript
// types/intl.d.ts
import { MessageDescriptor } from 'react-intl';
import en from '../translations/en.json';
type MessageKeys = keyof typeof en;
declare global {
namespace FormatjsIntl {
interface Message {
ids: MessageKeys;
}
}
}7.2 类型安全Hook
typescript
// hooks/useTypedIntl.ts
import { useIntl } from 'react-intl';
import messages from '../translations/en.json';
type MessageId = keyof typeof messages;
export function useTypedIntl() {
const intl = useIntl();
return {
t: (id: MessageId, values?: Record<string, any>) =>
intl.formatMessage({ id }, values),
formatDate: intl.formatDate,
formatNumber: intl.formatNumber
};
}
// 使用
function Component() {
const { t } = useTypedIntl();
// 类型检查和自动完成
const text = t('app.welcome');
return <div>{text}</div>;
}8. 性能优化
8.1 消息编译
bash
# 编译消息为AST格式
npx formatjs compile lang/en.json --out-file compiled-lang/en.json --asttsx
// 使用编译后的消息
import compiledMessages from './compiled-lang/en.json';
<IntlProvider
locale="en"
messages={compiledMessages}
>
<App />
</IntlProvider>8.2 代码分割
tsx
// 动态加载翻译
const loadMessages = async (locale: string) => {
const messages = await import(`./translations/${locale}.json`);
return messages.default;
};
function App() {
const [locale, setLocale] = useState('en');
const [messages, setMessages] = useState({});
useEffect(() => {
loadMessages(locale).then(setMessages);
}, [locale]);
if (!Object.keys(messages).length) {
return <div>Loading...</div>;
}
return (
<IntlProvider locale={locale} messages={messages}>
<Main />
</IntlProvider>
);
}8.3 缓存策略
tsx
// 缓存翻译消息
const messagesCache = new Map<string, any>();
async function getCachedMessages(locale: string) {
if (messagesCache.has(locale)) {
return messagesCache.get(locale);
}
const messages = await import(`./translations/${locale}.json`);
messagesCache.set(locale, messages.default);
return messages.default;
}9. 测试
9.1 测试设置
tsx
// test-utils.tsx
import { IntlProvider } from 'react-intl';
import { render, RenderOptions } from '@testing-library/react';
import messages from './translations/en.json';
const AllTheProviders = ({ children }: { children: React.ReactNode }) => {
return (
<IntlProvider locale="en" messages={messages}>
{children}
</IntlProvider>
);
};
const customRender = (
ui: React.ReactElement,
options?: Omit<RenderOptions, 'wrapper'>
) => render(ui, { wrapper: AllTheProviders, ...options });
export * from '@testing-library/react';
export { customRender as render };9.2 组件测试
tsx
// Component.test.tsx
import { render, screen } from './test-utils';
import Welcome from './Welcome';
describe('Welcome', () => {
it('should render welcome message', () => {
render(<Welcome />);
expect(screen.getByText('Welcome')).toBeInTheDocument();
});
it('should format number correctly', () => {
render(<Welcome count={1234} />);
expect(screen.getByText('1,234')).toBeInTheDocument();
});
});9.3 消息验证测试
typescript
// messages.test.ts
import en from './translations/en.json';
import zh from './translations/zh.json';
describe('Translation messages', () => {
it('should have same keys in all languages', () => {
const enKeys = Object.keys(en).sort();
const zhKeys = Object.keys(zh).sort();
expect(enKeys).toEqual(zhKeys);
});
it('should have no empty values', () => {
Object.values(en).forEach(value => {
expect(value).toBeTruthy();
});
});
});10. 实战示例
10.1 完整的表单组件
tsx
import { defineMessages, useIntl } from 'react-intl';
const messages = defineMessages({
nameLabel: {
id: 'form.name.label',
defaultMessage: 'Name'
},
namePlaceholder: {
id: 'form.name.placeholder',
defaultMessage: 'Enter your name'
},
nameRequired: {
id: 'form.name.required',
defaultMessage: 'Name is required'
},
emailLabel: {
id: 'form.email.label',
defaultMessage: 'Email'
},
submit: {
id: 'form.submit',
defaultMessage: 'Submit'
},
success: {
id: 'form.success',
defaultMessage: 'Form submitted successfully!'
}
});
function ContactForm() {
const intl = useIntl();
const [errors, setErrors] = useState<Record<string, string>>({});
const validate = (values: any) => {
const errors: Record<string, string> = {};
if (!values.name) {
errors.name = intl.formatMessage(messages.nameRequired);
}
return errors;
};
return (
<form>
<div>
<label>{intl.formatMessage(messages.nameLabel)}</label>
<input
placeholder={intl.formatMessage(messages.namePlaceholder)}
aria-label={intl.formatMessage(messages.nameLabel)}
/>
{errors.name && <span>{errors.name}</span>}
</div>
<button type="submit">
{intl.formatMessage(messages.submit)}
</button>
</form>
);
}10.2 语言切换器
tsx
function LanguageSwitcher() {
const intl = useIntl();
const [locale, setLocaleState] = useState(intl.locale);
const languages = [
{ code: 'en', name: 'English' },
{ code: 'zh', name: '中文' },
{ code: 'ja', name: '日本語' }
];
const changeLanguage = (newLocale: string) => {
setLocaleState(newLocale);
// 重新加载页面或更新IntlProvider
window.location.reload();
};
return (
<select value={locale} onChange={(e) => changeLanguage(e.target.value)}>
{languages.map(lang => (
<option key={lang.code} value={lang.code}>
{lang.name}
</option>
))}
</select>
);
}11. 最佳实践
typescript
const bestPractices = {
messages: [
'使用有意义的ID命名',
'提供defaultMessage和description',
'使用defineMessages组织消息',
'避免在消息中硬编码样式'
],
formatting: [
'使用ICU消息语法',
'正确处理复数和性别',
'使用标准日期时间格式',
'货币格式包含货币代码'
],
development: [
'使用消息提取工具',
'启用TypeScript类型检查',
'测试所有语言',
'监控缺失的翻译'
],
performance: [
'编译消息为AST',
'按需加载翻译',
'缓存翻译资源',
'代码分割'
]
};13. 高级ICU消息格式
13.1 复杂嵌套格式
tsx
// 嵌套选择器
const messages = {
'complex.message': '{gender, select, male {{count, plural, one {He has one item} other {He has {count} items}}} female {{count, plural, one {She has one item} other {She has {count} items}}} other {{count, plural, one {They have one item} other {They have {count} items}}}}'
};
// 使用
<FormattedMessage
id="complex.message"
values={{ gender: 'female', count: 5 }}
/>
// 输出: She has 5 items13.2 ordinal选择器
tsx
const messages = {
'place': 'You finished {place, selectordinal, one {#st} two {#nd} few {#rd} other {#th}}'
};
<FormattedMessage id="place" values={{ place: 1 }} />
// 输出: You finished 1st
<FormattedMessage id="place" values={{ place: 2 }} />
// 输出: You finished 2nd13.3 offset参数
tsx
const messages = {
'likes': '{count, plural, offset:1 =0 {No one likes this} =1 {You like this} one {You and one other person like this} other {You and # others like this}}'
};
<FormattedMessage id="likes" values={{ count: 0 }} />
// 输出: No one likes this
<FormattedMessage id="likes" values={{ count: 3 }} />
// 输出: You and 2 others like this14. 性能优化深度实践
14.1 消息预编译
bash
# 编译消息文件为AST
formatjs compile 'src/locales/en.json' --out-file 'src/locales/en.json.ast'tsx
// 加载预编译消息
import enAST from './locales/en.json.ast';
<IntlProvider messages={enAST} locale="en">
<App />
</IntlProvider>14.2 消息分包加载
tsx
// 动态导入翻译
async function loadMessages(locale: string) {
const messages = await import(`./locales/${locale}.json`);
return messages.default;
}
function App() {
const [messages, setMessages] = useState(null);
const [locale, setLocale] = useState('en');
useEffect(() => {
loadMessages(locale).then(setMessages);
}, [locale]);
if (!messages) return <Loading />;
return (
<IntlProvider messages={messages} locale={locale}>
<MainApp />
</IntlProvider>
);
}14.3 消息缓存策略
tsx
const messageCache = new Map<string, Record<string, string>>();
async function getCachedMessages(locale: string) {
if (messageCache.has(locale)) {
return messageCache.get(locale)!;
}
const messages = await loadMessages(locale);
messageCache.set(locale, messages);
return messages;
}15. 实战最佳实践
15.1 大型项目消息组织
locales/
├── en/
│ ├── common.json
│ ├── dashboard.json
│ ├── products.json
│ ├── users.json
│ └── settings.json
├── zh/
│ ├── common.json
│ ├── dashboard.json
│ ├── products.json
│ ├── users.json
│ └── settings.json
└── index.tstypescript
// locales/index.ts
export async function loadLocaleData(locale: string, module: string) {
const messages = await import(`./${locale}/${module}.json`);
return messages.default;
}
// 使用
const dashboardMessages = await loadLocaleData('en', 'dashboard');15.2 翻译管理工作流
typescript
// scripts/extract-messages.ts
import { extract } from '@formatjs/cli';
import glob from 'glob';
import fs from 'fs';
async function extractMessages() {
const files = glob.sync('src/**/*.{ts,tsx}');
const extracted = await extract(files, {
idInterpolationPattern: '[sha512:contenthash:base64:6]'
});
const messages = JSON.parse(extracted);
// 输出到待翻译文件
fs.writeFileSync(
'locales/messages.json',
JSON.stringify(messages, null, 2)
);
console.log(`提取了 ${Object.keys(messages).length} 条消息`);
}
extractMessages();15.3 翻译完整性检查
typescript
// scripts/check-translations.ts
import en from '../locales/en.json';
import zh from '../locales/zh.json';
import fr from '../locales/fr.json';
function checkTranslations() {
const locales = { en, zh, fr };
const baseKeys = new Set(Object.keys(en));
const report: Record<string, string[]> = {};
Object.entries(locales).forEach(([locale, messages]) => {
const currentKeys = new Set(Object.keys(messages));
const missing = [...baseKeys].filter(key => !currentKeys.has(key));
const extra = [...currentKeys].filter(key => !baseKeys.has(key));
if (missing.length > 0 || extra.length > 0) {
report[locale] = [
...missing.map(k => `缺少: ${k}`),
...extra.map(k => `多余: ${k}`)
];
}
});
if (Object.keys(report).length > 0) {
console.error('翻译完整性检查失败:', report);
process.exit(1);
}
console.log('✓ 所有翻译完整');
}
checkTranslations();16. 常见问题与解决方案
16.1 消息未定义
tsx
// 问题: 消息ID不存在
<FormattedMessage id="non.existent.key" />
// 解决: 提供默认消息
<FormattedMessage
id="non.existent.key"
defaultMessage="Default text"
/>
// 或全局处理
<IntlProvider
messages={messages}
locale={locale}
onError={(err) => {
if (err.code === 'MISSING_TRANSLATION') {
console.warn('Missing translation:', err.descriptor.id);
return; // 不抛出错误
}
throw err;
}}
>
<App />
</IntlProvider>16.2 格式化性能问题
tsx
// 问题: 重复格式化
function BadComponent() {
return (
<div>
{items.map(item => (
<FormattedNumber value={item.price} style="currency" currency="USD" />
))}
</div>
);
}
// 解决: 使用formatNumber
function GoodComponent() {
const intl = useIntl();
return (
<div>
{items.map(item => (
<span>{intl.formatNumber(item.price, { style: 'currency', currency: 'USD' })}</span>
))}
</div>
);
}16.3 富文本嵌入
tsx
// 嵌入React组件
const messages = {
'agreement': 'By signing up, you agree to our <terms>Terms of Service</terms> and <privacy>Privacy Policy</privacy>'
};
<FormattedMessage
id="agreement"
values={{
terms: (chunks) => <Link to="/terms">{chunks}</Link>,
privacy: (chunks) => <Link to="/privacy">{chunks}</Link>
}}
/>17. 与其他库集成
17.1 与React Router集成
tsx
// 多语言路由
import { useParams, Navigate } from 'react-router-dom';
function LocaleRoute() {
const { locale } = useParams<{ locale: string }>();
const supportedLocales = ['en', 'zh', 'fr'];
if (!supportedLocales.includes(locale!)) {
return <Navigate to="/en" replace />;
}
return (
<IntlProvider messages={messages[locale]} locale={locale}>
<App />
</IntlProvider>
);
}
// 路由配置
<Routes>
<Route path="/:locale/*" element={<LocaleRoute />} />
<Route path="*" element={<Navigate to="/en" replace />} />
</Routes>17.2 与Form库集成
tsx
// 表单验证消息国际化
import { useForm } from 'react-hook-form';
import { useIntl } from 'react-intl';
function SignupForm() {
const intl = useIntl();
const { register, formState: { errors } } = useForm();
return (
<form>
<input
{...register('email', {
required: intl.formatMessage({ id: 'validation.email.required' }),
pattern: {
value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i,
message: intl.formatMessage({ id: 'validation.email.invalid' })
}
})}
/>
{errors.email && <span>{errors.email.message}</span>}
</form>
);
}17.3 与状态管理集成
tsx
// Redux集成
import { createSlice } from '@reduxjs/toolkit';
const i18nSlice = createSlice({
name: 'i18n',
initialState: {
locale: 'en',
messages: {}
},
reducers: {
setLocale: (state, action) => {
state.locale = action.payload;
},
setMessages: (state, action) => {
state.messages = action.payload;
}
}
});
// 使用
function App() {
const { locale, messages } = useSelector((state) => state.i18n);
return (
<IntlProvider messages={messages} locale={locale}>
<MainApp />
</IntlProvider>
);
}12. 总结
React-Intl的关键要点:
- 标准化: 基于ECMAScript Intl API
- ICU格式: 强大的消息格式化语法
- 类型安全: 完整的TypeScript支持
- 工具链: 消息提取和编译工具
- 性能: AST编译和按需加载
- 测试: 完善的测试工具支持
- 高级特性: 复杂嵌套、消息分包
- 最佳实践: 消息组织、翻译管理
- 问题解决: 常见问题及解决方案
- 集成: 与主流库无缝集成
通过正确使用React-Intl,可以构建专业级的国际化React应用。