SuperAPI AI 工具集

sanity-i18n-translate

前端与建站

查看项目

更新于 10-16 06:03

技能介绍

用途

<Translate> 组件(来自 packages/sanity/src/core/i18n/Translate.tsx)用于渲染包含标记的本地化资源,例如 'Search for "<Red>{{keyword}}</Red>"'。components 属性把本地化字符串中的标签名映射到 React 组件或原生 HTML 标签名。

当消息不含标记时,应优先使用普通的 t() 函数——<Translate> 的渲染开销更大。

核心规则:绝不要内联定义组件

components 映射中的组件必须是稳定的、模块作用域的组件。绝不要在渲染期间内联定义它们——这样每次渲染都会创建新的组件标识,React 会卸载并重新挂载子树(丢失状态、DOM 和焦点)。这与 react/no-unstable-nested-components 属于同一类 bug,并且对 <Translate> 由仓库内的 oxlint 规则 @repo/i18n/no-inline-translate-components 强制执行(该规则实现在 @repo/oxlint-plugin-i18n 工作区包中,通过 .oxlintrc.json 的 jsPlugins 接入)。

该规则只能看到直接写在 JSX 属性中的对象字面量。以其他方式在渲染期间构建的映射(useMemo、useCallback、工厂调用)同样错误——也应提升到模块作用域,即使规则无法标记它们。

// ❌ 错误 - 每次渲染都是新的组件标识(且无法通过 lint 规则)
;<Translate t={t} i18nKey="key" components={{Badge: ({children}) => <strong>{children}</strong>}} />

// ❌ 错误 - useMemo 无法解决依赖变化时的标识问题
const components = useMemo(() => ({Badge: ({children}) => <b>{children}</b>}), [])

各种形态的修复方式

纯 HTML 包装——映射为字符串形式的原生标签名。字符串永远不会接收 componentProps,因此不会产生多余的 DOM 属性:

<Translate t={t} i18nKey="key" components={{Code: 'code', Emphasis: 'em'}} />

静态标记(固定链接、带样式的包装)——提升为模块作用域组件:

function DocsLink({children}: {children?: ReactNode}) {
  return <a href="https://www.sanity.io/docs">{children}</a>
}

;<Translate t={t} i18nKey="key" components={{DocsLink}} />

需要来自渲染数据的组件——将其提升,并通过 componentProps 传递数据。该对象会被转发给映射中的每个非字符串组件(包括 memo 组件等特殊组件),因此只声明每个组件实际读取的 props:

function VersionBadge({children, tone}: {children?: ReactNode; tone?: BadgeTone}) {
  return <VersionInlineBadge $tone={tone}>{children}</VersionInlineBadge>
}

;<Translate
  t={t}
  i18nKey="key"
  components={{VersionBadge}}
  componentProps={{tone: getReleaseTone(release)}}
/>

注意事项

  • componentProps 驱动泛型:字面量值会被拓宽({tone: 'caution'} 推断为 {tone: string})。对必须保持窄类型的字面量使用 as const:componentProps={{tone: 'caution' as const}}。
  • TComponentProps 被约束为 object——原始类型和 null 会导致编译错误。
  • 组件仅在包装标签(<X>...</X>)中接收 children;自闭合标签(<X/>)渲染组件时不带 children。请将 children 声明为可选。
  • 未映射的小写标签只有在 Translate.tsx 的 RECOGNIZED_HTML_TAGS 中列出时才会渲染;其他任何内容都会回退为插值的纯文本并输出控制台警告。
  • 也不要在提升后的组件内部调用组件工厂(例如 getVersionInlineBadge)——这会在下一层重新制造渲染时标识问题,React Compiler lint 会标记它。应改为通过 prop 渲染底层组件。

参考实现

  • packages/sanity/src/core/releases/tool/detail/ReleaseActivityListItem.tsx——componentProps 传递事件对象。
  • packages/@sanity/vision/src/components/VisionGuiResult.tsx——componentProps 向一对自闭合组件传递数据。
  • packages/sanity/src/core/studio/components/navbar/search/components/common/FilterLabel.tsx——多个组件共享同一个 componentProps 对象。
  • packages/sanity/src/core/i18n/tests/Translate.test.tsx——行为覆盖,包括 memo 组件以及不向原生标签转发。