Refine 国际化实战:用德语 common.json 翻译文件覆盖内置 UI 文本(i18n Provider 与 react-i18next 接入指南)
2026/9/13 3:06:25 网站建设 项目流程

Refine 国际化实战:用德语 common.json 翻译文件覆盖内置 UI 文本(i18n Provider 与 react-i18next 接入指南)

【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards & B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refine

本篇技术指南以 Refine 官方 i18n 文档中的德语翻译文件(/locales/de/common.json)为核心,系统讲解 Refine 的国际化(i18n)机制:从i18nProvider接口、react-i18next 实例化、语言切换 Header,到内置组件(错误页、通知、文档标题、自动保存指示器)如何消费common.json中的翻译键。读完本文,你将掌握如何为 Refine 后台应用添加完整的多语言支持,并具备用自定义翻译文件覆盖 Refine 所有内置文本的能力。

i18n 机制总览:Refine 如何与任意 i18n 框架协作

Refine 的国际化设计理念是"框架无关":它不绑定任何特定的 i18n 库,而是定义了一个极简的i18nProvider接口作为中间层。只要基于你选定的 i18n 库(如 react-i18next、react-intl 等)实现了这个接口,再注入到<Refine />组件,Refine 内置的所有组件(按钮、表格、通知、错误页、文档标题等)就会自动使用翻译能力。

i18nProvider接口定义在@refinedev/core中,只有三个方法:

import { I18nProvider } from "@refinedev/core"; const i18nProvider: I18nProvider = { translate: (key: string, options?: any, defaultMessage?: string) => string, changeLocale: (lang: string, options?: any) => Promise, getLocale: () => string, };
  • translate(key, options, defaultMessage):根据键返回翻译文本,options用于注入插值变量(如{{resource}});
  • changeLocale(lang):切换语言并返回 Promise;
  • getLocale():返回当前语言标识。

该接口与本文主角德语翻译文件配合的完整教程位于 Refine v5 官方 i18n 指南,同一份德语翻译内容也被 v4 版本文档 复用,说明这套翻译键体系在 Refine 各版本间保持稳定。

完整德语翻译文件:/locales/de/common.json

Refine 的官方指南使用react-i18next框架,翻译文件采用"语言/命名空间"的目录约定,存放在静态资源目录下。德语的完整文件内容如下(本文件即 documentation/docs/partials/_partial-translation-file-de.md 所定义的正文):

{ "pages": { "login": { "title": "Melden Sie sich bei Ihrem Konto an", "signin": "Einloggen", "signup": "Anmelden", "divider": "oder", "fields": { "email": "Email", "password": "Passwort" }, "errors": { "validEmail": "Ungültige E-Mail-Adresse", "requiredEmail": "E-Mail ist erforderlich", "requiredPassword": "Passwort wird benötigt" }, "buttons": { "submit": "Anmeldung", "forgotPassword": "Passwort vergessen?", "noAccount": "Sie haben kein Konto?", "rememberMe": "Erinnere dich an mich" } }, "forgotPassword": { "title": "Haben Sie Ihr Passwort vergessen?", "fields": { "email": "Email" }, "errors": { "validEmail": "Ungültige E-Mail-Adresse", "requiredEmail": "E-Mail ist erforderlich" }, "buttons": { "submit": "Anweisungen zum Zurücksetzen senden" } }, "register": { "title": "Registrieren Sie sich für Ihr Konto", "fields": { "email": "Email", "password": "Passwort" }, "errors": { "validEmail": "Ungültige E-Mail-Adresse", "requiredEmail": "E-Mail ist erforderlich", "requiredPassword": "Passwort wird benötigt" }, "buttons": { "submit": "Registrieren", "haveAccount": "Ein Konto haben?" } }, "updatePassword": { "title": "Kennwort aktualisieren", "fields": { "password": "Neues Passwort", "confirmPassword": "Bestätige neues Passwort" }, "errors": { "confirmPasswordNotMatch": "Passwörter stimmen nicht überein", "requiredPassword": "Passwort wird benötigt", "requiredConfirmPassword": "Das Feld „Passwort bestätigen“ ist erforderlich" }, "buttons": { "submit": "Aktualisieren" } }, "error": { "info": "Sie haben vergessen, {{action}} component zu {{resource}} hinzufügen.", "404": "Leider existiert diese Seite nicht.", "resource404": "Haben Sie die {{resource}} resource erstellt?", "backHome": "Zurück" } }, "actions": { "list": "Aufführen", "create": "Erstellen", "edit": "Bearbeiten", "show": "Zeigen" }, "buttons": { "create": "Erstellen", "save": "Speichern", "logout": "Abmelden", "delete": "Löschen", "edit": "Bearbeiten", "cancel": "Abbrechen", "confirm": "Sicher?", "filter": "Filter", "clear": "Löschen", "refresh": "Erneuern", "show": "Zeigen", "undo": "Undo", "import": "Importieren", "clone": "Klon", "notAccessTitle": "Sie haben keine zugriffsberechtigung" }, "warnWhenUnsavedChanges": "Nicht gespeicherte Änderungen werden nicht übernommen.", "notifications": { "success": "Erfolg", "error": "Fehler (status code: {{statusCode}})", "undoable": "Sie haben {{seconds}} Sekunden Zeit für Undo.", "createSuccess": "{{resource}} erfolgreich erstellt.", "createError": "Fehler beim Erstellen {{resource}} (status code: {{statusCode}})", "deleteSuccess": "{{resource}} erfolgreich gelöscht.", "deleteError": "Fehler beim Löschen {{resource}} (status code: {{statusCode}})", "editSuccess": "{{resource}} erfolgreich bearbeitet.", "editError": "Fehler beim Bearbeiten {{resource}} (status code: {{statusCode}})", "importProgress": "{{processed}}/{{total}} importiert" }, "loading": "Wird geladen", "tags": { "clone": "Klon" }, "dashboard": { "title": "Dashboard" }, "posts": { "posts": "Einträge", "fields": { "id": "Id", "title": "Titel", "category": "Kategorie", "status": { "title": "Status", "published": "Veröffentlicht", "draft": "Draft", "rejected": "Abgelehnt" }, "content": "Inhalh", "createdAt": "Erstellt am" }, "titles": { "create": "Erstellen", "edit": "Bearbeiten", "list": "Einträge", "show": "Eintrag zeigen" } }, "table": { "actions": "Aktionen" }, "documentTitle": { "default": "refine", "suffix": " | Refine", "post": { "list": "Beiträge | Refine", "show": "#{{id}} Beitrag anzeigen | Refine", "edit": "#{{id}} Beitrag bearbeiten | Refine", "create": "Neuen Beitrag erstellen | Refine", "clone": "#{{id}} Beitrag klonen | Refine" } }, "autoSave": { "success": "gespeichert", "error": "fehler beim automatischen speichern", "loading": "speichern...", "idle": "warten auf anderungen" } }

对照英文版翻译文件(_partial-translation-file-en.md)可以看出:两份文件的键结构完全一致,仅值不同。这正是 Refine 国际化设计的关键——所有内置组件的文本都是通过固定键查找的,你只需提供同构的common.json即可整体替换界面语言,无需改动任何组件代码。

翻译键分组解析:每个键背后的源码消费方

德语common.json覆盖了 Refine 后台应用的几乎所有内置 UI 文本。结合仓库源码,我们可以逐一确认这些键的真实消费场景,从而在翻译时做到"知其然,也知其所以然"。

pages:认证页与错误页文本

pages.loginpages.forgotPasswordpages.registerpages.updatePassword四组键对应 Refine 内置的认证相关页面(登录、忘记密码、注册、更新密码),涵盖标题、字段标签、校验错误消息和按钮文案。

pages.error组则对应错误页面。在 core 包的错误页组件 中,当路由解析到某个resourceaction时,会调用:

translate( "pages.error.info", { action: action, resource: resource.name }, `You may have forgotten to add the "${action}" component to "${resource.name}" resource.`, );

注意德语值"Sie haben vergessen, {{action}} component zu {{resource}} hinzufügen."中的{{action}}{{resource}}就是这里的插值参数。同样的键在 antd 包的错误页 中被复用,pages.error.404pages.error.backHome分别渲染"页面不存在"标题与"返回首页"按钮。这意味着只改翻译文件,就能让 antd、chakra、mui 等任意 UI 适配层的错误页全部变为德语。

actions 与 buttons:操作命名与按钮文本

actions组(list/create/edit/show)是 CRUD 操作的语义化名称;buttons组则覆盖按钮级文案,其中值得关注的是buttons.notAccessTitle——在 core 包的按钮权限检查 Hook 中,当用户对某资源无操作权限时,按钮的 Tooltip 会读取该键,德语值 "Sie haben keine zugriffsberechtigung" 即"您没有访问权限"。对应的单元测试(button-can-access/index.spec.tsx)也验证了该键的翻译调用。

notifications:数据变更与导入进度通知

notifications组是 Refine 数据 Hook(useCreateuseUpdateuseDelete等)自动弹出通知的文案来源。以创建为例,useCreate.ts 在成功后调用notifications.createSuccess,失败时调用notifications.createError

// 成功路径 "notifications.createSuccess" // "{{resource}} erfolgreich erstellt." // 失败路径 "notifications.createError" // "Fehler beim Erstellen {{resource}} (status code: {{statusCode}})"

德语键中出现的{{resource}}(资源名)与{{statusCode}}(HTTP 状态码)均由数据 Hook 在运行时注入。notifications.importProgress则在 antd 的导入 Hook 中用于显示{{processed}}/{{total}}的导入进度。notifications.undoable用于可撤销操作,见 core 包的 undoableQueue 组件,{{seconds}}为撤销倒计时秒数。

warnWhenUnsavedChanges 与 autoSave:表单状态提示

warnWhenUnsavedChanges用于"未保存更改"离开确认场景。该开关在RefineOptions中定义,默认值为false(见 refine 上下文默认值),开启后表单有未保存修改时展示提示。

autoSave组对应 AutoSaveIndicator 组件,四个状态键分别表示:autoSave.success(已保存)、autoSave.error(保存失败)、autoSave.loading(保存中)、autoSave.idle(等待变更)。注意德语文件中的"warten auf anderungen""fehler beim automatischen speichern"存在拼写瑕疵("Änderungen"、"Fehler"),这恰好说明翻译文件完全由开发者掌控,任何文本(包括纠错)都可直接修改common.json生效。

documentTitle:浏览器标签页标题

documentTitle组控制浏览器标签页标题。核心实现在 generateDocumentTitle/index.ts:先读取documentTitle.default(默认 "refine")与documentTitle.suffix(默认 " | Refine"),再按documentTitle.${identifier}.${action}的模式查找资源级标题,{{id}}用于注入记录 ID。德语文件中post资源的五种子键(list/show/edit/create/clone)完整覆盖了博客场景,例如"#{{id}} Beitrag bearbeiten | Refine"即"编辑第 #N 篇文章"。

posts / table / dashboard / tags / loading:业务资源与通用文本

posts组是演示资源(博客文章)的字段标签与页面标题,table.actions是表格操作列标题,dashboard.title是仪表盘标题,loading是通用加载提示,tags.clone是克隆标签。这些键在官方的 Ant Design 示例(i18n 指南)中与PostList页面配合使用,是理解"资源级翻译"的直观样例。

从安装到启用:在 Refine 中接入 react-i18next

下面按照官方指南的完整流程,将德语翻译文件接入应用。示例 UI 库使用 Ant Design(其他 UI 库流程相同),且推荐使用create refine-app初始化项目(CLI 勾选 i18n 支持即可自动配置)。

第一步:安装依赖

在项目目录中安装 react-i18next、i18next 及两个官方插件:

npm install react-i18next i18next i18next-http-backend i18next-browser-languagedetector
  • i18next-http-backend:从 HTTP 地址懒加载翻译文件;
  • i18next-browser-languagedetector:自动检测浏览器语言。

第二步:创建 i18n 实例

新建src/i18n.ts,配置loadPath指向翻译文件目录,声明支持语言与默认命名空间:

import i18n from "i18next"; import { initReactI18next } from "react-i18next"; import Backend from "i18next-http-backend"; import detector from "i18next-browser-languagedetector"; i18n .use(Backend) .use(detector) .use(initReactI18next) .init({ supportedLngs: ["en", "de"], backend: { loadPath: "/locales/{{lng}}/{{ns}}.json", // locale files path }, ns: ["common"], defaultNS: "common", fallbackLng: ["en", "de"], }); export default i18n;

要点说明:{{lng}}会被替换为语言代码(en/de),{{ns}}替换为命名空间(common),最终请求/locales/de/common.json——正是本文讲解的德语翻译文件路径。

第三步:用 React.Suspense 包裹应用

翻译文件为懒加载,因此在src/index.tsx中导入 i18n 实例并用React.Suspense包裹应用,加载期间显示 fallback:

import React from "react"; import { createRoot } from "react-dom/client"; import App from "./App"; import "./i18n"; const container = document.getElementById("root"); const root = createRoot(container!); root.render( <React.StrictMode> <React.Suspense fallback="loading"> <App /> </React.Suspense> </React.StrictMode>, );

第四步:创建并注入 i18nProvider

src/App.tsx中基于 react-i18next 的useTranslation实现三方法接口,并传入<Refine>

import type { I18nProvider } from "@refinedev/core"; import { Refine } from "@refinedev/core"; import { useTranslation } from "react-i18next"; const App: React.FC = () => { const { t, i18n } = useTranslation(); const i18nProvider: I18nProvider = { translate: (key: string, options?: any) => t(key, options), changeLocale: (lang: string) => i18n.changeLanguage(lang), getLocale: () => i18n.language, }; return ( <Refine i18nProvider={i18nProvider} /* ... */ > {/* ... */} </Refine> ); };

注入后,Refine 暴露的useTranslationHook 便全局可用,所有内置组件也会通过translate自动读取对应语言的common.json

第五步:放置翻译文件

项目静态资源目录结构如下,ende两份common.json内容同构:

|-- public | |-- locales | |-- en | | |-- common.json | |-- de | |-- common.json |-- src |-- package.json |-- tsconfig.json

界面切换语言:Header 组件与 useTranslation

接下来创建<Header />组件,用changeLocale实现中英/德英切换。官方示例使用 Ant Design 的Dropdown展示语言列表,并用国旗 SVG 图标辅助识别:

import { DownOutlined } from "@ant-design/icons"; import { useTranslation } from "@refinedev/core"; import { Avatar, Button, Dropdown, Layout, Menu, Space } from "antd"; export const Header: React.FC = () => { const { i18n } = useTranslation(); const { getLocale, changeLocale } = useTranslation(); const currentLocale = getLocale(); const menu = ( <Menu selectedKeys={currentLocale ? [currentLocale] : []}> {[...(i18n.languages || [])].sort().map((lang: string) => ( <Menu.Item key={lang} onClick={() => changeLocale(lang)} icon={ <span style={{ marginRight: 8 }}> <Avatar size={16} src={`/images/flags/${lang}.svg`} /> </span> } > {lang === "en" ? "English" : "German"} </Menu.Item> ))} </Menu> ); return ( <Layout.Header style={{ display: "flex", justifyContent: "flex-end", alignItems: "center", padding: "0px 24px", height: "48px", backgroundColor: "#FFF", }} > <Dropdown overlay={menu}> <Button type="link"> <Space> <Avatar size={16} src={`/images/flags/${currentLocale}.svg`} /> {currentLocale === "en" ? "English" : "German"} <DownOutlined /> </Space> </Button> </Dropdown> </Layout.Header> ); };

随后将 Header 挂到ThemedLayout

import { Refine, Resource } from "@refinedev/core"; import { ThemedLayout } from "@refinedev/antd"; import { useTranslation } from "react-i18next"; import "./i18n"; import { Header } from "components"; const App: React.FC = () => { const { t, i18n } = useTranslation(); const i18nProvider = { translate: (key: string, options?: any) => t(key, options), changeLocale: (lang: string) => i18n.changeLanguage(lang), getLocale: () => i18n.language, }; return ( <Refine i18nProvider={i18nProvider} /* ... */ > <ThemedLayout header={<Header />} > {/* ... */} </ThemedLayout> </Refine> ); };

业务页面中的翻译使用:PostList 实例

最后在业务页面中通过useTranslationtranslate方法引用common.json的键。以文章列表页为例,表格的"标题""分类""操作"三列分别对应posts.fields.titleposts.fields.categorytable.actions,分类数据通过useMany关联查询:

import { useTranslation, useMany, } from "@refinedev/core"; import { List, useTable, TextField, EditButton, ShowButton, } from "@refinedev/antd"; import { Table, Space } from "antd"; import { IPost, ICategory } from "interfaces"; export const PostList: React.FC = () => { const { translate } = useTranslation(); const { tableProps } = useTable<IPost>(); const categoryIds = tableProps?.dataSource?.map((item) => item.category.id) ?? []; const { data, isLoading } = useMany<ICategory>({ resource: "categories", ids: categoryIds, queryOptions: { enabled: categoryIds.length > 0, }, }); return ( <List> <Table {...tableProps} rowKey="id"> <Table.Column dataIndex="id" title="ID" /> <Table.Column dataIndex="title" title={translate("posts.fields.title")} /> <Table.Column dataIndex={["category", "id"]} title={translate("posts.fields.category")} render={(value) => { if (isLoading) { return <TextField value="Loading..." />; } return ( <TextField value={data?.data.find((item) => item.id === value)?.title} /> ); }} /> <Table.Column<IPost> title={translate("table.actions")} dataIndex="actions" key="actions" render={(_value, record) => ( <Space> <EditButton size="small" recordItemId={record.id} /> <ShowButton size="small" recordItemId={record.id} /> </Space> )} /> </Table> </List> ); };

配套的 TypeScript 接口定义(interfaces/index.d.ts)如下:

export interface ICategory { id: number; title: string; } export interface IPost { id: number; title: string; content: string; status: "published" | "draft" | "rejected"; category: { id: number }; }

切换语言时,changeLocale("de")会触发i18n.changeLanguage,随后页面与内置组件通过translate重新取词,实现全界面即时切换为德语。

自定义覆盖的关键点

  1. 键结构必须同构:要覆盖 Refine 内置文本,只需复制 英文翻译文件 的结构,替换对应语言的值,并将文件放到/locales/<语言代码>/common.json。所有键均已在上文结合源码逐一验证。
  2. 插值变量不可省略{{resource}}{{statusCode}}{{seconds}}{{processed}}{{total}}{{id}}{{action}}是运行时由 Refine 注入的占位符,翻译时保留它们才能正确显示动态信息。
  3. fallback 兜底:i18n 实例中的fallbackLng: ["en", "de"]保证某一语言缺少键时自动回退到另一语言,避免出现裸键名。
  4. 与 UI 适配层解耦:同一份common.json同时服务于 core 与 antd 等适配包(如错误页在两个层级都会读取pages.error.*),因此一份翻译即可覆盖全栈 UI 文本。
  5. 自动保存与文档标题autoSave.*documentTitle.*是容易被遗漏的两组键,前者驱动 AutoSaveIndicator 的四种状态文案,后者决定浏览器标签标题的默认值与后缀,正式上线前建议一并翻译。

按照上述流程,你就可以为 Refine 应用交付完整的德语(或任意语言)体验——从登录注册、错误页到表格按钮与浏览器标题,全部由一份common.json驱动,无需改动任何组件源码。

【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards & B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refine

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

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

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

立即咨询