react-admin `<TranslatableInputs>` 多语言表单输入组件完整指南:分语言编辑、校验与编程式赋值
2026/9/21 15:29:20 网站建设 项目流程

react-admin<TranslatableInputs>多语言表单输入组件完整指南:分语言编辑、校验与编程式赋值

【免费下载链接】react-adminA frontend Framework for single-page applications on top of REST/GraphQL APIs, using TypeScript, React and Material Design项目地址: https://gitcode.com/gh_mirrors/re/react-admin

<TranslatableInputs>是 react-admin 提供的多语言表单输入容器组件,它允许开发者把一组"每个字段对应多语言值"的输入(如name.enname.fr)组织成带语言选项卡的编辑界面。本文基于当前仓库 docs/TranslatableInputs.md 展开,结合ra-ui-materialuira-core的源码实现,系统讲解其数据模型、全部 Props、自定义语言选择器、校验联动、以及如何配合react-hook-formsetValue编程式修改各语言值。

一、组件定位与适用场景

在多语言业务系统(如电商商品、CMS 内容、新闻稿)中,同一个字段往往需要保存多个语言的翻译文本。react-admin 对此有两种展示形态:

  • 只读展示:使用<TranslatableFields>分语言展示字段值;
  • 编辑输入:使用本文的<TranslatableInputs>,让用户在一个表单内为每种语言分别填写值。

<TranslatableInputs>不要求提供source属性(它本身只是容器),但它要求至少一个带source的子输入组件,同时必须通过locales属性声明要展示的语言。其源码定义位于 packages/ra-ui-materialui/src/input/TranslatableInputs.tsx,底层逻辑(语言切换、记录转换)由ra-coreuseTranslatable提供。

组件期望的字段值结构如下(每个字段都是"语言码 → 文本"的对象):

{ id: 1234, name: { en: 'White trousers', fr: 'Pantalon blanc', }, description: { en: 'Slim fit trousers for every day use', fr: 'Pantalon ajusté pour un usage quotidien', } }

二、基本用法

将需要多语言编辑的输入组件作为<TranslatableInputs>的子节点传入,并通过locales声明语言:

<TranslatableInputs locales={['en', 'fr']}> <TextInput source="name" /> <RichTextInput source="description" /> </TranslatableInputs>

页面渲染效果:上方是一个 Material UI 选项卡条,每个选项卡标签是语言码(如enfr),下方是对应当前选中语言的一组输入框。切换选项卡时,react-admin 会为该语言的每个子输入生成形如name.enname.fr的动态source,从而实现"一套字段结构、多语言独立编辑"。

从源码来看,TranslatableInputs的核心渲染流程(TranslatableInputs.tsx#L72-L114)是:

  1. 调用useTranslatable({ defaultLocale, locales })获取语言切换上下文;
  2. 通过TranslatableContextProvider注入该上下文;
  3. 渲染语言选择器(默认是<TranslatableInputsTabs>,即 Material UI 的 Tabs);
  4. 遍历locales,为每个语言渲染一个<TranslatableInputsTabContent>,把同一组子输入包进各语言的容器中。

每个TranslatableInputsTabContent(packages/ra-ui-materialui/src/input/TranslatableInputsTabContent.tsx)会做两件关键的事:

  • 通过SourceContextProvider注入一个getSource: source => ${source}.${locale}的上下文,让子输入自动挂上语言后缀;
  • 通过RecordContextProvider注入一份"当前语言的值记录"(由getRecordForLocale从原始记录中抽取),因为普通输入组件并不知道语言的存在,它们只从RecordContext取当前字段值。

这里getRecordForLocale的实现值得注意(packages/ra-core/src/i18n/useTranslatable.ts#L55-L74):它递归遍历记录的所有路径(getRecordPaths支持嵌套对象与数组),把形如title.fr的值提升为title,从而构造出仅含指定语言值的记录。这意味着<TranslatableInputs>也适用于嵌套结构(如数组内对象的翻译字段)。

三、Props 总览

<TranslatableInputs>的 Props 定义于 TranslatableInputs.tsx#L116-L125(接口继承UseTranslatableOptions),完整参数如下:

Prop必填类型默认值说明
locales必填Array<string>-语言码数组,顺序即选项卡顺序
defaultLocale可选string跟随用户语言默认展示的语言
fullWidth可选booleantrue设为false时输入组不撑满表单宽度
groupKey可选string-用于可访问性的唯一标识(同页多个实例时必填)
selector可选ReactElementMaterial UI Tabs自定义语言选择器元素
StackProps可选object{}透传给内部 MUI Stack 的属性
sx可选SxProps-Material UI 样式快捷方式

其余内部还支持classNamemargin'none' \| 'normal' \| 'dense')。从源码可见组件支持 MUI 主题级定制:其样式类名为RaTranslatableInputs,包含rootfullWidth两个规则,可通过 MUIcomponents覆盖(见 TranslatableInputs.tsx#L146-L162)。

四、defaultLocale:默认展示语言

react-admin 默认取当前用户的语言(useLocaleState中的localeFromUI)作为defaultLocale,这一点在 useTranslatable.ts#L26-L28 中可以看到:

const [localeFromUI] = useLocaleState(); const { defaultLocale = localeFromUI, locales } = options;

若希望某个表单固定以特定语言打开,用defaultLocale覆盖:

<TranslatableInputs locales={['en', 'fr']} defaultLocale="fr"> <TextInput source="name" /> <RichTextInput source="description" /> </TranslatableInputs>

源码中还有一个细节:selectedLocale为 falsy 时(例如defaultLocale为空字符串),会回退到'en'(useTranslatable.ts#L33)。

五、fullWidth:宽度控制

默认情况下,<TranslatableInputs>组会撑满表单宽度——源码根节点样式为flexGrow: 1,且启用fullWidth时追加width: '100%'(TranslatableInputs.tsx#L136-L144)。如需禁用:

<TranslatableInputs locales={['en', 'fr']} fullWidth={false}> <TextInput source="title" /> <TextInput source="description" /> </TranslatableInputs>

六、groupKey:同页多实例的可访问性标识

<TranslatableInputs>内部依赖一组固定的 DOM id 建立"选项卡 ↔ 内容面板"的关联(如translatable-header-${groupKey}${locale}translatable-content-${groupKey}${locale},见 TranslatableInputsTabContent.tsx#L68-L76)。如果同一页面出现多个<TranslatableInputs>,这些 id 会冲突,因此必须为每个实例提供唯一的groupKey

<TranslatableInputs locales={['en', 'fr']} groupKey="essential-fields"> <TextInput source="name" /> <RichTextInput source="description" /> </TranslatableInputs>

groupKey同时会被拼入FormGroupContext的名称(${groupKey}${locale}),确保各语言分组的表单上下文互不干扰。

七、locales:语言列表与选项卡标签

locales传入字符串数组,每个字符串既是语言码,也会成为输入值的键后缀。数组顺序决定了选项卡从左到右的展示顺序:

<TranslatableInputs locales={['en', 'fr']}> <TextInput source="name" /> <RichTextInput source="description" /> </TranslatableInputs>

选项卡默认以语言码作为标签。如果希望显示人类可读的语言名(如 "English" 而非 "en"),可以使用翻译键,格式为ra.locales.[locale_code],例如ra.locales.enra.locales.fr。以ra-i18n-polyglot或自定义翻译包在对应语言文件中配置这些键即可。

八、selector:自定义语言选择器

默认选择器是 Material UI Tabs(实现见 TranslatableInputsTabs.tsx:一个AppBar容器内嵌Tabs,每个选项卡的值是语言码)。你可以通过selector属性替换成任意自定义元素,例如一个原生<select>下拉框:

const Selector = () => { const { locales, selectLocale, selectedLocale, } = useTranslatableContext(); const handleChange = event => { selectLocale(event.target.value); }; return ( <select aria-label="Select the locale" onChange={handleChange} value={selectedLocale} > {locales.map(locale => ( <option key={locale} value={locale} // This allows to correctly link the containers for each locale to their labels id={`translatable-header-${locale}`} > {locale} </option> ))} </select> ); }; <TranslatableInputs record={record} resource="products" locales={['en', 'fr']} selector={<Selector />} > <TextInput source="name" /> <RichTextInput source="description" /> </TranslatableInputs>

自定义选择器通过useTranslatableContext获取上下文,可用值包括:

  • locales:语言码数组;
  • selectedLocale:当前选中语言;
  • selectLocale(locale):切换语言的方法;
  • getRecordForLocale(record, locale):抽取指定语言记录的工具函数。

注意:自定义selector若用原生元素,需要自己保证"选项卡 ↔ 面板"的id/aria-labelledby关联(上例中translatable-header-${locale}正是面板aria-labelledby所引用的 id)。useTranslatableContext在脱离TranslatableContextProvider使用时(即组件不在<TranslatableInputs>内部)会抛出错误,这一点由 useTranslatableContext.ts#L39-L43 保证。

九、StackProps:控制内部布局

每个语言的输入内容都被包裹在一个 MUIStack中,StackProps会原样透传({...StackProps},见 TranslatableInputs.tsx#L100-L110)。默认方向为垂直堆叠;设置direction: 'row'可以让输入并排显示:

<TranslatableInputs locales={['en', 'fr']} StackProps={{ direction: 'row' }} > <TextInput source="title" /> <TextInput source="description" sx={{ marginLeft: 2 }} /> </TranslatableInputs>

StackProps支持 MUI Stack 的全部属性,包括spacingdirectionalignItemsjustifyContent等,适合在空间紧张的表单中做紧凑排版。

十、sx:自定义样式

TranslatableInputs根节点同样支持sx,可用于设置边框、背景、间距等样式:

<TranslatableInputs locales={['en', 'fr']} sx={{ border: 'solid 1px red' }} > <TextInput source="title" /> <TextInput source="description" /> </TranslatableInputs>

sx会被应用到根容器(源码中由styled('div')创建的Root),同时仍可通过 MUI 主题的RaTranslatableInputs.styleOverrides做全局覆盖。

十一、校验:错误联动到选项卡

<TranslatableInputs>内的任何输入组件都可以照常使用 react-admin 的校验器。当某个语言的输入出现校验错误时,对应语言的选项卡标签会被标记为"错误"状态(标红高亮):

<TranslatableInputs locales={['en', 'fr']}> <TextInput source="name" validate={[required()]} /> <RichTextInput source="description" validate={[maxLength(100)]} /> </TranslatableInputs>

这一行为在仓库测试 packages/ra-ui-materialui/src/input/TranslatableInputs.spec.tsx 中有明确覆盖:测试通过断言tabs[1].classList.contains('RaTranslatableInputsTab-error')来验证含错误输入的选项卡是否正确获得错误样式类;同时测试也验证了未选中语言的面板会附加hidden样式类(display: none)实现隐藏切换。

实现机制上,每个语言的选项卡由TranslatableInputsTab渲染,它会感知所在语言的FormGroupContext状态;校验错误在表单层面按语言分组记录,因此可以精确反映到具体语言选项卡上。

十二、编程式修改多语言值:useSourceContext+setValue

<TranslatableInputs>的表单值依然由react-hook-form管理,因此可以直接调用其setValue方法修改某个输入的值。但难点在于:子输入在表单中注册的name按语言动态生成的(例如description.en),直接写死字段名无法适配。

react-admin 为此提供了SourceContext(packages/ra-core/src/core/SourceContext.tsx),通过useSourceContext钩子可以在任意层级拿到getSource(source)函数,它会返回该输入在当前上下文中的真实source。在<TranslatableInputs>内,这个函数就是"字段名 + 当前语言后缀"的拼接器(见 TranslatableInputsTabContent.tsx#L41-L56)。

源码细节:SourceContext默认值是一个"恒等函数"(getSource: source => source),因此在不处于任何特殊上下文时使用也不会报错;而TranslatableInputsTabContent提供的getSourcesource为空时会抛出'Children of TranslatableInputs must have a source',从实现层面强制了"子输入必须有source"的约束。

下面示例展示了如何利用getSource拿到各语言的动态name,再用setValuetitle的值预填到description

import { TranslatableInputs, TextInput, useSourceContext } from 'react-admin'; import { useFormContext } from 'react-hook-form'; import { Button } from '@mui/material'; const PrefillWithTitleButton = () => { const sourceContext = useSourceContext(); const { setValue, getValues } = useFormContext(); const onClick = () => { setValue( // sourceContext.getSource('description') will for instance return // 'description.en' sourceContext.getSource('description'), getValues(sourceContext.getSource('title')) ); }; return ( <Button onClick={onClick} size="small" sx={{ maxWidth: 140 }}> Prefill with title </Button> ); }; const MyInputs = () => ( <TranslatableInputs locales={['en', 'fr']}> <TextInput source="title" /> <TextInput source="description" helperText={false} /> <PrefillWithTitleButton /> </TranslatableInputs> );

关键点:

  • useSourceContext()可在<TranslatableInputs>的任何子组件中使用,拿到的是"当前语言上下文"下的getSource
  • 由于按钮同样位于TranslatableInputsTabContent内部,getSource('title')getSource('description')会自动带上当前选中语言的后缀(如title.frdescription.fr),因此预填逻辑天然跟随用户正在编辑的语言;
  • 若在<TranslatableInputs>之外使用useSourceContext,得到的是恒等上下文,getSource直接返回原字段名,符合普通表单的行为。

十三、小结

<TranslatableInputs>是 react-admin 处理多语言编辑场景的标准方案:

  • 数据模型:字段值为"语言码 → 文本"的对象,locales定义语言集合;
  • 使用要点:子输入必须有source,多实例必须给groupKey,默认语言可被defaultLocale覆盖;
  • 交互定制selector可完全替换默认 Tabs 选择器,StackProps/sx/MUI 主题覆盖可灵活调整布局与样式;
  • 校验:各语言独立校验,错误自动标记到对应选项卡;
  • 编程式控制:配合useSourceContext().getSourcereact-hook-formsetValue,可在任意语言上下文中精准读写字段值。

相关源码与测试可作为继续深入研究的入口:TranslatableInputs.tsx、TranslatableInputsTabContent.tsx、TranslatableInputsTabs.tsx、useTranslatable.ts、SourceContext.tsx、TranslatableInputs.spec.tsx。只读展示场景可对照参考<TranslatableFields>

【免费下载链接】react-adminA frontend Framework for single-page applications on top of REST/GraphQL APIs, using TypeScript, React and Material Design项目地址: https://gitcode.com/gh_mirrors/re/react-admin

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询