共享字符串
如何对跨多个组件和文件使用的字符串进行国际化
共享字符串是指在应用的多个位置使用的文本内容——例如导航标签、表单提示/消息或配置数据。与其在各处重复实现翻译逻辑,不如使用 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 产生的 encoded string 不能直接使用——必须先通过 useMessages 或 getMessages 解码。
客户端与服务器端的使用
Client Components
使用 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('Original string:', decodeMsg(encoded));
console.log('Translated string:', 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-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: '电子产品' },
    { 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-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>
  );
}常见问题
直接使用 Encoded Strings
请勿直接使用 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 参考:
这份指南怎么样?

