Skip to content
博客如何(不)在 React 组件外部使用翻译

如何(不)在 React 组件外使用翻译

2023年4月21日 · 作者 Jan Amann

你是否曾经好奇,为什么 next-intl 不提供在 React 组件外消费翻译的 API?

使用 next-intl 为你的应用程序实现国际化的传统方式是使用 useTranslations 钩子:

import {useTranslations} from 'next-intl';
 
function About() {
  const t = useTranslations('About');
  return <h1>{t('title')}</h1>;
}

为什么不可能在比如说工具函数中格式化消息?这里是否缺少了什么?

这看起来像是不必要的限制,但缺乏此功能是刻意为之,目的是鼓励采用经过验证的模式以避免潜在问题——尤其是那些容易被忽视的问题。

示例:格式化错误消息

假设你有一个 FeedbackForm 组件,会将用户反馈发送到后端接口。不幸的是,由于反馈量很大,服务器偶尔会返回 504 状态码。为了提升用户体验,你想实现自动重试并向用户提供适当的反馈。

以下是一个幼稚的实现。你能发现此实现有什么问题吗?

import {useTranslations, useNow} from 'next-intl';
import {addMinutes} from 'date-fns';
 
function sendFeedback() {
  // ❌ 不好的实现:返回格式化后的消息
  API.sendFeedback().catch((error) => {
    // 如果是网关超时,通知用户我们将在5分钟后重试
    if (error.status === 504) {
      // (这里为了示例假设 `t` 已定义)
      return t('timeout', {nextAttempt: addMinutes(new Date(), 5)});
    }
  });
}
 
function FeedbackForm({user}) {
  const t = useTranslations('Form');
  const [errorMessage, setErrorMessage] = useState();
 
  function onSubmit() {
    sendFeedback().catch((errorMessage) => {
      setErrorMessage(errorMessage);
    });
  }
 
  return (
    <form onSubmit={onSubmit}>
      {errorMessage != null && <p>{errorMessage}</p>}
      ...
    </form>
  );
}

你发现问题了吗?

我们一起来看看:

  1. nextAttempt 的值是在一个由事件处理器调用的工具函数中插值到消息里的。随着临近重试超时时间,我们无法保持剩余时间的更新。
  2. 如果用户切换语言,错误消息仍会保持之前选择的语言,导致体验不连贯。

更好的方式:在渲染时格式化

为避免上述问题,我们可以在 React 的渲染阶段格式化消息,将数据结构变成人类可读的字符串。

import {useTranslations, useNow} from 'next-intl';
import {addMinutes} from 'date-fns';
 
function FeedbackForm({user}) {
  const t = useTranslations('Form');
  const [retry, setRetry] = useState();
  const now = useNow({
    // 每分钟更新一次
    updateInterval: 1000 * 60
  });
 
  function onSubmit() {
    // ✅ 好的实现:将数据结构存储于状态中
    API.sendFeedback().catch((error) => {
      if (error.status === 504) {
        setRetry(addMinutes(now, 5));
      }
    });
  }
 
  return (
    <form onSubmit={onSubmit}>
      {retry != null && <p>{t('timeout', {nextAttempt: retry - now})}</p>}
      ...
    </form>
  );
}

现在,我们可以通过交互式倒计时提供更好的用户体验,显示距离下一次尝试的时间。

此外,这种方式对可能出现的意外状态更健壮,例如用户在超时消息显示时切换语言。

证明规则的例外

如果你使用的是 Next.js,你可能想在 API 路由路由处理程序或者 Metadata API 中对国际化消息进行翻译。

next-intl/server 提供了一组可等待的函数版本,这些函数通常是以钩子形式在组件内调用的。它们与 React 无关,可以用于这些场景。

import {getTranslations} from 'next-intl/server';
 
// `locale` 是通过 Next.js 的 `params` 接收到的
const locale = params.locale;
 
// 这会创建一个与 `useTranslations` 返回的函数相同的函数
const t = await getTranslations({locale});
 
// 结果:"Hello world!"
t('hello', {name: 'world'});

这看起来很熟悉

如果你使用 React 已经有一段时间了,可能经历过从 component{DidMount,DidUpdate,WillUnmount}useEffect 的转变。useEffect 更优的原因在于它促使开发者将应用保持同步,从而一系列潜在问题自然消失。

通过限制自己只在渲染期间格式化消息,我们处于类似的情形:翻译消息的渲染输出总是与应用状态同步,我们可以信赖应用的一致性。

相关内容:在结构化消息文档中“如何复用消息?”


Let’s keep in touch: