共享字符串
如何对跨多个组件和文件使用的字符串进行国际化
共享字符串是指在应用的多个位置使用的文本值,例如导航标签、表单消息或配置数据。与其在各处重复编写翻译逻辑,不如使用 msg 标注需要翻译的字符串,并配合 useMessages 进行解码。
共享内容的问题
设想一份在整个应用中通用的导航配置:
// navData.ts
export const navData = [
{
label: '首页',
description: '首页',
href: '/'
},
{
label: '关于',
description: '关于公司',
href: '/about'
}
];要实现国际化,你通常需要:
- 将其改写为可接受翻译函数的函数
- 将所有使用处更新为用
t调用该函数 - 在整个代码库中管理随之而来的复杂性
这会增加维护负担,并让代码更难阅读。msg 函数通过支持在原位标记待翻译字符串,并在需要时再解码,来解决这个问题。
快速开始
使用 msg 标记字符串,使用 useMessages 解码它们:
// navData.ts - 标记字符串以供翻译
import { msg } from 'gt-react';
export const navData = [
{
label: msg('首页'),
description: msg('首页'),
href: '/'
},
{
label: msg('关于'),
description: msg('关于公司的信息'),
href: '/about'
}
];// 组件用法 - 解码已标记的字符串
import { useMessages } from 'gt-react';
import { navData } from './navData';
function Navigation() {
const m = useMessages();
return (
<nav>
{navData.map((item) => (
<a key={item.href} href={item.href} title={m(item.description)}>
{m(item.label)}
</a>
))}
</nav>
);
}共享字符串的工作原理
共享字符串系统分为两个阶段:
- 标记阶段:
msg使用翻译元数据对字符串进行编码 - 解码阶段:
useMessages对字符串进行解码并翻译
// msg() 使用元数据对字符串进行编码
const encoded = msg('Hello, world!');
console.log(encoded); // "Hello, world!:eyIkX2hhc2giOiJkMjA3MDliZGExNjNlZmM2In0="
// useMessages() 解码并翻译
const m = useMessages();
const translated = m(encoded); // 以用户的语言显示 "Hello, world!"来自msg的 encoded string 无法直接使用,必须通过 useMessages 解码后方可使用。
组件
使用 useMessages 钩子:
import { useMessages } from 'gt-react';
const encodedString = msg('Hello, world!');
function MyComponent() {
const m = useMessages();
return <div>{m(encodedString)}</div>;
}使用 decodeMsg 获取原始字符串
有时你需要直接访问未翻译的原始字符串,例如用于日志、调试或对比。使用 decodeMsg 提取原始文本:
import { decodeMsg } from 'gt-react';
const encoded = msg('Hello, world!');
const original = decodeMsg(encoded); // "Hello, world!" (original)
const translated = m(encoded); // "Hello, world!" (in user's language)
// 用于日志记录或调试
console.log('原始字符串:', decodeMsg(encoded));
console.log('已翻译字符串:', m(encoded));decodeMsg 的使用场景
- 开发与调试:记录原始字符串以便排查问题
- 回退处理:翻译失败时使用原文
- 字符串比较:与已知原始值进行比对
- 分析:追踪原始字符串的使用情况
// 示例:回退处理
function getDisplayText(encodedStr) {
const m = useMessages();
try {
return m(encodedStr);
} catch (error) {
console.warn('翻译失败,使用原文:', decodeMsg(encodedStr));
return decodeMsg(encodedStr);
}
}使用变量
对于包含动态内容的字符串,请使用占位符并传入变量:
// Mark string with variables
const items = 100;
export const pricing = [
{
name: '基础版',
price: 100,
description: msg('基础版包含 {items} 个项目', { items })
}
];// 在组件中使用
function PricingCard() {
const m = useMessages();
return (
<div>
<h3>{pricing[0].name}</h3>
<p>{m(pricing[0].description)}</p>
</div>
);
}ICU 消息格式
要进行更高级的格式化,请使用 ICU 语法:
const count = 10;
const message = msg('购物车中有 {count, plural, =0 {0 件商品} =1 {1 件商品} other {{count} 件商品}}', { count });在 Unicode 文档中进一步了解 ICU 消息格式。
示例
导航配置
// config/navigation.ts
import { msg } from 'gt-react';
export const mainNav = [
{
label: msg('首页'),
href: '/',
icon: 'home'
},
{
label: msg('产品'),
href: '/products',
icon: 'package'
},
{
label: msg('关于我们'),
href: '/about',
icon: 'info'
}
];
export const footerLinks = [
{
title: msg('公司'),
links: [
{ label: msg('关于我们'), href: '/about' },
{ label: msg('加入我们'), href: '/careers' },
{ label: msg('联系我们'), href: '/contact' }
]
},
{
title: msg('支持'),
links: [
{ label: msg('帮助中心'), href: '/help' },
{ label: msg('文档'), href: '/docs' },
{ label: msg('API 参考'), href: '/api' }
]
}
];// components/Navigation.tsx
import { useMessages } from 'gt-react';
import { mainNav } from '../config/navigation';
function Navigation() {
const m = useMessages();
return (
<nav>
{mainNav.map((item) => (
<a key={item.href} href={item.href}>
<Icon name={item.icon} />
{m(item.label)}
</a>
))}
</nav>
);
}表单配置
// config/forms.ts
import { msg } from 'gt-react';
export const formMessages = {
placeholders: {
email: msg('请输入您的电子邮箱地址'),
password: msg('请输入您的密码'),
message: msg('在此输入您的消息…')
},
actions: {
send: msg('发送消息'),
save: msg('保存更改'),
cancel: msg('取消')
},
validation: {
required: msg('此字段为必填'),
email: msg('请输入有效的电子邮箱地址'),
minLength: msg('至少需要 {min} 个字符', { min: 8 }),
maxLength: msg('不得超过 {max} 个字符', { max: 100 })
},
success: {
saved: msg('更改已保存'),
sent: msg('消息已发送'),
updated: msg('个人资料已更新')
},
errors: {
network: msg('网络错误—请重试'),
server: msg('服务器错误—请联系支持'),
timeout: msg('请求超时—请重试')
}
};// components/ContactForm.tsx
import { useMessages } from 'gt-react';
import { formMessages } from '../config/forms';
function ContactForm() {
const m = useMessages();
const [errors, setErrors] = useState({});
return (
<form>
<input
type="email"
placeholder={m(formMessages.placeholders.email)}
required
/>
{errors.email && <span>{m(formMessages.validation.email)}</span>}
<button type="submit">
{m(formMessages.actions.send)}
</button>
</form>
);
}动态内容生成
// utils/productData.ts
import { msg } from 'gt-react';
function mockProducts() {
return [
{ name: 'iPhone 15', company: 'Apple', category: '电子产品' },
{ name: 'Galaxy S24', company: 'Samsung', category: '电子产品' }
];
}
export function getProductData() {
const products = mockProducts();
return products.map(product => ({
...product,
description: msg('{name} 是 {company} 推出的{category}产品', {
name: product.name,
category: product.category,
company: product.company
})
}));
}// components/ProductList.tsx
import { useMessages } from 'gt-react';
import { getProductData } from '../utils/productData';
function ProductList() {
const m = useMessages();
const products = getProductData();
return (
<div>
{products.map(product => (
<div key={product.name}>
<h3>{product.name}</h3>
<p>{m(product.description)}</p>
</div>
))}
</div>
);
}常见问题
直接使用编码字符串
切勿直接使用msg的输出:
// ❌ 错误 - 直接使用编码字符串
const encoded = msg('Hello, world!');
return <div>{encoded}</div>; // 显示编码字符串,而非翻译
// ✅ 正确 - 先解码字符串
const encoded = msg('Hello, world!');
const m = useMessages();
return <div>{m(encoded)}</div>; // 显示正确翻译msg() 中的动态内容
字符串必须在构建阶段已确定:
// ❌ 错误 - 动态模板字面量
const name = 'John';
const message = msg(`Hello, ${name}`); // 构建时错误
// ✅ 正确 - 使用变量
const name = 'John';
const message = msg('Hello, {name}', { name });忘记解码
每个 msg 字符串都需要解码:
// ❌ 缺少解码
const config = {
title: msg('Dashboard'),
subtitle: msg('欢迎回来')
};
// 在组件中稍后使用时——忘记解码
return <h1>{config.title}</h1>; // 显示为已编码的字符串
// ✅ 正确做法——在使用时解码
const m = useMessages();
return <h1>{m(config.title)}</h1>; // 显示已翻译的标题后续步骤
- Dictionaries 指南 - 使用结构化数据整理翻译
- Languages 指南 - 配置支持语言
- API 参考:
本指南如何?