共享字符串
如何对跨多个组件和文件使用的字符串进行国际化
共享字符串是指在应用的多个位置使用的文本值,例如导航标签、表单消息或配置数据。与其在各处重复翻译逻辑,不如使用 msg 标记需要翻译的字符串,并使用 useMessages 对其进行解码。
共享内容的问题
请看一个在整个应用中复用的导航配置:
// navData.ts
export const navData = [
{
label: '首页',
description: '网站首页',
href: '/'
},
{
label: '关于我们',
description: '公司介绍',
href: '/about'
}
];要把它实现国际化,你通常需要:
- 把它改写为一个接收翻译函数的函数
- 将所有用例更新为用
t调用该函数 - 在整个代码库中管理由此带来的复杂性
这会增加维护负担并降低代码可读性。msg 函数通过允许你就地标记待翻译的字符串,并在需要时再进行解码,从而解决了这一问题。
快速开始
使用 msg 标注字符串,并使用 useMessages 对其进行解码:
// navData.ts - 标记字符串以供翻译
import { msg } from 'gt-next';
export const navData = [
{
label: msg('首页'),
description: msg('首页'),
href: '/'
},
{
label: msg('关于'),
description: msg('关于公司'),
href: '/about'
}
];// 组件用法 - 解码已标记的字符串
import { useMessages } from 'gt-next';
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或getMessages对字符串进行解码并翻译
// msg() 使用元数据对字符串进行编码
const encoded = msg('Hello, world!');
console.log(encoded); // "Hello, world!:eyIkX2hhc2giOiJkMjA3MDliZGExNjNlZmM2In0="
// useMessages() 负责解码并翻译
const m = useMessages();
const translated = m(encoded); // 显示为用户的语言中的 "Hello, world!"来自 msg 的编码字符串不能直接使用——必须先通过 useMessages 或 getMessages 解码。
客户端与服务器端用法
客户端组件
使用 useMessages 钩子:
import { useMessages } from 'gt-next';
const encodedString = msg('你好,世界!');
function MyComponent() {
const m = useMessages();
return <div>{m(encodedString)}</div>;
}服务器组件
使用 getMessages 函数:
import { getMessages } from 'gt-next/server';
const encodedString = msg('你好,世界!');
async function MyServerComponent() {
const m = await getMessages();
return <div>{m(encodedString)}</div>;
}使用 decodeMsg 获取原始字符串
有时你需要直接访问未翻译的原始字符串,例如用于日志记录、调试或对比。使用 decodeMsg 提取原始文本:
import { decodeMsg } from 'gt-next';
const encoded = msg('Hello, world!');
const original = decodeMsg(encoded); // "Hello, world!"(原文)
const translated = m(encoded); // "Hello, world!"(用户语言)
// 适用于记录或调试
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);
}
}使用变量
对于包含动态内容的字符串,请使用占位符并传入变量:
// 使用变量标注字符串
const items = 100;
export const pricing = [
{
name: 'Basic',
price: 100,
description: msg('基础方案包含 {items} 个条目', { items })
}
];// Use in component
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 {没有商品} =1 {有 1 件商品} other {有 {count} 件商品}}', { count });在 Unicode 文档中了解更多 ICU 消息格式相关内容。
示例
导航配置
// config/navigation.ts
import { msg } from 'gt-next';
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-next';
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-next';
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-next';
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-next';
function mockProducts() {
return [
{ name: 'iPhone 15', company: 'Apple', category: 'Electronics' },
{ name: 'Galaxy S24', company: 'Samsung', category: 'Electronics' }
];
}
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-next';
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 参考:
本指南如何?