共享字符串
如何为跨多个组件和文件使用的字符串实现国际化
“共享字符串”是指在应用中多个位置复用的文本值,例如导航标签、表单提示或配置信息。与其在各处重复实现翻译,不如使用 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 Hook:
import { useMessages } from 'gt-react';
const encodedString = msg('你好,世界!');
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 的使用场景
- 开发与调试:记录原始字符串用于排查问题
- 回退处理:翻译失败时使用原文
- 字符串比较:与已知原始值进行比对
- 分析:追踪原始字符串的使用情况
// 示例:Fallback 处理
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 })
  }
];// 在组件中使用
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 {一件商品} 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('Welcome back')
};
// 稍后在组件中 - 忘记解码
return <h1>{config.title}</h1>; // 显示编码字符串
// ✅ 正确 - 使用时解码
const m = useMessages();
return <h1>{m(config.title)}</h1>; // 显示翻译后的标题下一步
- Dictionaries 指南 - 通过结构化数据组织翻译
- Languages 指南 - 配置支持的语言
- API 参考:
这份指南怎么样?

