共享字符串的翻译

如何对共享字符串进行国际化处理

概览

共享字符串是指在应用中多个位置使用的字符串或常量。

例如,你可能有一个在多个组件中使用的字符串,或一个在多个文件中使用的常量。

在传统的 i18n 库中,你需要为每个共享字符串创建一份字典。

本指南将带你了解如何使用 msg() 函数和 useMessages() 钩子,在你的 Next.js 应用中轻松实现共享字符串的国际化。

我们将涵盖以下内容:

何时使用 msg() 函数

如何使用 msg() 函数和 useMessages() 钩子

使用变量

示例

常见陷阱


何时使用 msg() 函数

msg() 是一个用于标记待翻译字符串的简单函数。

来看一个包含多条字符串的共享对象示例:

src/navData.ts
export const navData = {
  home: {
    label: 'Home',
    description: 'The home page',
    icon: 'home',
    href: '/',
  },
  about: {
    label: 'About',
    description: 'Information about the company',
    icon: 'info',
    href: '/about',
  },
}

通常,要对这个对象进行国际化,你需要要么为每个字符串创建字典条目,要么把该对象重构为一个返回已翻译数据的函数。

例如,你可以这样写:

src/navData.ts
export const getNavData = (t: (content: string) => string) => ({
  home: {
    label: t('Home'),
    description: t('The home page'),
    icon: 'home',
    href: '/',
  },
  about: {
    label: t('About'),
    description: t('Information about the company'),
    icon: 'info',
    href: '/about',
  },
})

在应用中所有原本导入 navData 的地方,都需要改为调用 getNavData(t)

src/app/page.tsx
import { getNavData } from '@/navData';

export default function Home() {
  const t = useGT();
  const navData = getNavData(t);
  return <div>{navData.home.label}</div>;
}

这会带来不少额外工作。解决方案是使用 msg() 函数。


如何使用 msg() 函数

要使用 msg() 函数,直接将字符串传入该函数。 该函数会返回一个特殊的编码字符串,可用于翻译。

src/navData.ts
import { msg } from 'gt-next';

export const navData = [
  {
    label: msg('Home'), // Output has type `string`
    description: msg('The home page'),
    icon: 'home',
    href: '/',
  },
  {
    label: msg('About'),
    description: msg('Information about the company'),
    icon: 'info',
    href: '/about',
  },
]

然后,只需将该编码字符串传递给 useMessages() 钩子即可。

src/app/page.tsx
import { useMessages } from 'gt-next';
import { navData } from '@/navData';

export default function Home() {
  const m = useMessages();
  return (
    <div>
      {navData.map((item) => (
        <div key={item.label}>{m(item.label)}</div>
      ))}
    </div>
  );
}

如果在异步组件中,请使用 getMessages() 函数替代 useMessages() 钩子。

src/app/page.tsx
import { getMessages } from 'gt-next/server';

export default async function Home() {
  const m = await getMessages();
  return (
    <div>
      {navData.map((item) => (
        <div key={item.label}>{m(item.label)}</div>
      ))}
    </div>
  );
}

注意:msg() 函数返回的字符串是编码过的,会与原始输入字符串不同。 若需还原原始字符串,请使用 decodeMsg() 进行解码。


使用变量

如果你的字符串是模板字符串并包含变量,可以将这些变量传递给 msg() 函数。

只需把模板字符串中的 ${} 写法改为 {variable},并将一个对象作为 msg() 的第二个参数传入,其中变量名作为键。

src/navData.ts
const items = 100;
export const pricing = [
  {
    name: 'Basic',
    price: 100,
    description: `The basic plan includes ${items} items`
  },
]

应改为:

src/navData.ts
import { msg } from 'gt-next';

const items = 100;
export const pricing = [
  {
    name: 'Basic',
    price: 100,
    description: msg('The basic plan includes {items} items', { items })
  },
]

{items} 占位符会被 items 变量的值替换。

这样就可以在翻译中展示动态值。

有关 API 的更多信息,请参见 API 参考

gt-next 支持 ICU 消息格式,可用于对变量进行格式化。

const price = 100;
msg('There are {count, plural, =0 {no items} =1 {one item} other {{count} items}} in the cart', { count: 10 });

ICU 消息格式是格式化变量的强大方式。 更多信息请参见 ICU 消息格式文档


示例

  1. 翻译共享的全局对象
src/llms.ts
import { msg } from 'gt-next';

export const llmData = [
  {
    name: 'GPT-4.1',
    id: 'gpt-4.1',
    description: msg('GPT-4.1 is a large language model developed by OpenAI'),
  },
  {
    name: 'Claude 3.7 Sonnet',
    id: 'claude-3-7-sonnet',
    description: msg('Claude 3.7 Sonnet is a large language model developed by Anthropic'),
  },
];
src/app/page.tsx
import { llmData } from '@/llms';
import { useMessages } from 'gt-next';

export default function MyComponent() {
  const m = useMessages();
  return (
    <div>
      {llmData.map((llm) => (
        <div key={llm.id}>
          <h1>{llm.name}</h1>
          <p>{m(llm.description)}</p>
        </div>
      ))}
    </div>
  )
}
src/llms.ts
export const llms = [
  {
    name: 'GPT-4.1',
    id: 'gpt-4.1',
    description: 'GPT-4.1 is a large language model developed by OpenAI',
  },
  {
    name: 'Claude 3.7 Sonnet',
    id: 'claude-3-7-sonnet',
    description: 'Claude 3.7 Sonnet is a large language model developed by Anthropic',
  },
]
src/app/page.tsx
import { llms } from '@/llms';

export default function MyComponent() {
  return (
    <div>
      {llms.map((llm) => (
        <div key={llm.id}>
          <h1>{llm.name}</h1>
          <p>{llm.description}</p>
        </div>
      ))}
    </div>
  )
}
  1. 翻译函数返回值
src/llms.ts
import { msg } from 'gt-next';

function mockData() {
  return [
    {
      name: 'GPT-4.1',
      id: 'gpt-4.1',
      company: 'OpenAI',
    },
    {
      name: 'Claude 3.7 Sonnet',
      id: 'claude-3-7-sonnet',
      company: 'Anthropic',
    },
  ];
}

export function getData() {
  const data = mockData();
  const modifiedData = data.map((item) => ({
    ...item,
    description: msg('{name} 是由 {company} 开发的大型语言模型', {
      name: item.name,
      company: item.company,
    }),
  }));
  return modifiedData;
}
src/app/page.tsx
import { getData } from '@/llms';
import { useMessages } from 'gt-next';

export default function MyComponent() {
  const m = useMessages();
  const data = getData();
  return (
    <div>
      {data.map((item) => (
        <div key={item.id}>
          <h1>{item.name}</h1>
          <p>{m(item.description)}</p>
        </div>
      ))}
    </div>
  )
}
src/llms.ts
import { msg } from 'gt-next';

function mockData() {
  return [
    {
      name: 'GPT-4.1',
      id: 'gpt-4.1',
      company: 'OpenAI',
    },
    {
      name: 'Claude 3.7 Sonnet',
      id: 'claude-3-7-sonnet',
      company: 'Anthropic',
    },
  ];
}

export function getData() {
  const data = mockData();
  const modifiedData = data.map((item) => ({
    ...item,
    description: `${item.name} is a large language model developed by ${item.company}`,
  }));
  return modifiedData;
}
src/app/page.tsx
import { getData } from '@/llms';

export default function MyComponent() {
  const data = getData();
  return (
    <div>
      {data.map((item) => (
        <div key={item.id}>
          <h1>{item.name}</h1>
          <p>{item.description}</p>
        </div>
      ))}
    </div>
  )
}

常见陷阱

忘记调用 useMessages()

msg() 会对输入字符串进行编码,因此不能直接在 JSX 或其他地方使用。

如果你直接使用编码后的字符串,会得到一个类似下面的奇怪内容:

const encodedString = msg('Hello, world!');
console.log(encodedString); // "Hello, world!:eyIkX2hhc2giOiJkMjA3MDliZGExNjNlZmM2In0="

要解决这个问题,需要通过 useMessages() hook 获取翻译后的字符串。

import { useMessages } from 'gt-next';

const encodedString = msg('Hello, world!');

export default function MyComponent() {
  const m = useMessages();
  return <div>{m(encodedString)}</div>; // 当前语言中的 "Hello, world!"
}

或者,如果你想还原原始字符串,可以使用 decodeMsg() 函数。

import { decodeMsg } from 'gt-next';

const encodedString = msg('Hello, world!');
const decodedString = decodeMsg(encodedString);
console.log(decodedString); // "Hello, world!"

将动态内容包裹在 msg()

所有字符串必须在构建时已知。这意味着不能将动态内容包裹在 msg() 中。

例如,下面是无效的:

const dynamicContent = msg(`Hello, ${name}`);

正确做法是将动态内容作为变量传入,再交给 msg()

const dynamicContent = msg('Hello, {name}', { name });

如果你尝试用 msg() 包裹动态内容,CLI 工具会发出警告。


后续步骤

这份指南怎么样?