ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

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

Refine 国际化实战:用德语 common.json 翻译文件覆盖内置 UI 文本(i18n Provider 与 react-i18next 接入指南) 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)切换语言并返回 PromisegetLocale()返回当前语言标识。该接口与本文主角德语翻译文件配合的完整教程位于 Refine v5 官方 i18n 指南同一份德语翻译内容也被 v4 版本文档 复用说明这套翻译键体系在 Refine 各版本间保持稳定。完整德语翻译文件/locales/de/common.jsonRefine 的官方指南使用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.login、pages.forgotPassword、pages.register、pages.updatePassword四组键对应 Refine 内置的认证相关页面登录、忘记密码、注册、更新密码涵盖标题、字段标签、校验错误消息和按钮文案。pages.error组则对应错误页面。在 core 包的错误页组件 中当路由解析到某个resource与action时会调用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.404、pages.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 数据 HookuseCreate、useUpdate、useDelete等自动弹出通知的文案来源。以创建为例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-languagedetectori18next-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包裹应用加载期间显示 fallbackimport 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 fallbackloading App / /React.Suspense /React.StrictMode, );第四步创建并注入 i18nProvider在src/App.tsx中基于 react-i18next 的useTranslation实现三方法接口并传入Refineimport 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。第五步放置翻译文件项目静态资源目录结构如下en与de两份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 typelink Space Avatar size{16} src{/images/flags/${currentLocale}.svg} / {currentLocale en ? English : German} DownOutlined / /Space /Button /Dropdown /Layout.Header ); };随后将 Header 挂到ThemedLayoutimport { 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 实例最后在业务页面中通过useTranslation的translate方法引用common.json的键。以文章列表页为例表格的标题分类操作三列分别对应posts.fields.title、posts.fields.category、table.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 } useTableIPost(); const categoryIds tableProps?.dataSource?.map((item) item.category.id) ?? []; const { data, isLoading } useManyICategory({ resource: categories, ids: categoryIds, queryOptions: { enabled: categoryIds.length 0, }, }); return ( List Table {...tableProps} rowKeyid Table.Column dataIndexid titleID / Table.Column dataIndextitle title{translate(posts.fields.title)} / Table.Column dataIndex{[category, id]} title{translate(posts.fields.category)} render{(value) { if (isLoading) { return TextField valueLoading... /; } return ( TextField value{data?.data.find((item) item.id value)?.title} / ); }} / Table.ColumnIPost title{translate(table.actions)} dataIndexactions keyactions render{(_value, record) ( Space EditButton sizesmall recordItemId{record.id} / ShowButton sizesmall 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重新取词实现全界面即时切换为德语。自定义覆盖的关键点键结构必须同构要覆盖 Refine 内置文本只需复制 英文翻译文件 的结构替换对应语言的值并将文件放到/locales/语言代码/common.json。所有键均已在上文结合源码逐一验证。插值变量不可省略{{resource}}、{{statusCode}}、{{seconds}}、{{processed}}、{{total}}、{{id}}、{{action}}是运行时由 Refine 注入的占位符翻译时保留它们才能正确显示动态信息。fallback 兜底i18n 实例中的fallbackLng: [en, de]保证某一语言缺少键时自动回退到另一语言避免出现裸键名。与 UI 适配层解耦同一份common.json同时服务于 core 与 antd 等适配包如错误页在两个层级都会读取pages.error.*因此一份翻译即可覆盖全栈 UI 文本。自动保存与文档标题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),仅供参考
返回列表