Homepage 组件国际化指南:基于 next-i18next 的 Widget 翻译与本地化实战
【免费下载链接】homepageA highly customizable homepage (or startpage / application dashboard) with Docker and service API integrations.项目地址: https://gitcode.com/GitHub_Trending/ho/homepage
本篇指南聚焦 Homepage(自托管主页 / 应用仪表盘)中自定义 Widget(组件)的国际化(i18n)与本地化(l10n)实现。它面向想要为自己开发的 Widget 接入多语言支持的开发者,系统讲解翻译框架的接入方式、翻译字符串的配置流程、内置通用翻译(数值格式化与常用文本)的用法,并结合仓库源码说明这些能力背后的实现原理。读完本文,你将能够为任意新增 Widget 正确接入useTranslation、注册英文翻译条目,并复用项目内置的字节/速率/百分比/日期等格式化能力,从而让 Widget 在多语言环境下正确显示。
Widget 翻译体系概览
Homepage 中 Widget 内的所有文本与数值内容都应进行翻译与本地化。整个体系的默认语言是英语(en),其他语言通过 Crowdin 社区协作平台补充(各语言的翻译文件统一存放在 public/locales 目录下,例如 public/locales/zh-Hans/common.json、public/locales/en/common.json)。
在技术选型上,Homepage 使用next-i18next库来处理翻译。该库提供了一组 hooks 与工具函数,帮助开发者本地化自己的 Widget;Homepage 在其基础上还进行了扩展——这一点从 next-i18next.config.js 中注册的自定义 formatter 可以看出,后文会详细展开。
整个应用在入口 src/pages/_app.jsx 中通过appWithTranslation(MyApp, nextI18nextConfig)包裹根组件,将next-i18next与项目配置(见 next-i18next.config.js:defaultLocale: "en"、locales: ["en"]、serializeConfig: false)注入到全局,所有页面与组件因此都能直接使用翻译能力。
在组件中接入翻译:useTranslation 与 Block
Widget 组件是一个 React 组件,翻译接入的核心代码非常简单。官方推荐的写法如下(来自 docs/widgets/authoring/translations.md):
import { useTranslation } from "next-i18next"; import Container from "components/services/widget/container"; import Block from "components/services/widget/block"; export default function Component() { const { t } = useTranslation(); return ( <Container service={service}> <Block label="yourwidget.key1" /> <Block label="yourwidget.key2" /> <Block label="yourwidget.key3" /> </Container> ); }要点说明:
useTranslation()返回的t函数负责根据当前语言环境解析翻译 key;<Block label="yourwidget.key1" />中的label就是翻译 key,Block组件内部会自动对它做翻译;- 当 Widget 处于加载占位状态(
value未传入)时,同样通过label渲染文本,因此加载态、错误态、正常态都不需要硬编码任何字符串。
从源码看,src/components/services/widget/block.jsx 中Block组件本身就从next-i18next/pages导入useTranslation,并在渲染标签时调用{t(label)}(见 block.jsx)。注意:文档示例使用next-i18next顶层导入,而仓库内的实际组件(如 src/widgets/adguard/component.jsx)普遍使用next-i18next/pages子路径导入useTranslation,两者在本项目中均可正常工作。
以 AdGuard Widget 的真实实现为例(src/widgets/adguard/component.jsx):
<Block label="adguard.queries" value={t("common.number", { value: adguardData.num_dns_queries })} /> <Block label="adguard.blocked" value={t("common.number", { value: adguardData.num_blocked_filtering })} /> <Block label="adguard.filtered" value={t("common.number", { value: filtered })} /> <Block label="adguard.latency" value={t("common.ms", { value: adguardData.avg_processing_time * 1000, style: "unit", unit: "millisecond" })} highlightValue={adguardData.avg_processing_time * 1000} />可以看到,文本类内容走label翻译 key,数值类内容则交给t("common.xxx", { value })复用内置格式化——这是所有官方 Widget 遵循的统一模式。
添加 Widget 自己的翻译字符串
要为你的 Widget 添加英文翻译,只需两步:
- 打开 public/locales/en/common.json;
- 在文件列表末尾为你的 Widget 新增一个对象,结构如下:
"yourwidget": { "key1": "Value 1", "key2": "Value 2", "key3": "Value 3" }添加后,组件中的label="yourwidget.key1"就会解析为"Value 1"。仓库中每个官方 Widget 都在该文件中注册了自己的命名空间,例如 public/locales/en/common.json 中的duplicati、docker、ping、siteMonitor等,新增时照此追加即可。
注意:即使你母语是其他语言,在仓库中也只需添加英文翻译。等你的 Widget 合并后,再通过 Crowdin 平台补充你母语的翻译即可——这是为了保证单一事实来源、避免贡献者在仓库内直接编辑各语言文件造成冲突。
关于 key 命名
一个值得注意的细节:common.json是一个扁平的 JSON 对象,common、resources、adguard等都是其中的顶级命名空间。你的 Widget key 不应与既有顶级 key 冲突,label中使用的完整路径(如yourwidget.key1)与 JSON 的嵌套结构一一对应,Block组件用t()解析它。
复用内置通用翻译(Common Translations)
Homepage 提供了一组开箱即用的通用翻译,专门用于格式化数值内容、日期等常见元素,开发者无需自己实现。
数值格式化
以下表格(原样继承自 docs/widgets/authoring/translations.md)列出了全部数值类通用 key:
| Key | 示例输出 | 说明 |
|---|---|---|
common.bytes | 1,000 B | 以字节为单位格式化数值 |
common.bits | 1,000 bit | 以比特为单位格式化数值 |
common.bbytes | 1 KiB | 以二进制字节(1024 进制)格式化 |
common.bbits | 1 Kibit | 以二进制比特(1024 进制)格式化 |
common.byterate | 1,000 B/s | 格式化字节速率 |
common.bibyterate | 1 KiB/s | 格式化二进制字节速率 |
common.bitrate | 1,000 bit/s | 格式化比特速率 |
common.bibitrate | 1 Kibit/s | 格式化二进制比特速率 |
common.percent | 50% | 格式化百分比 |
common.number | 1,000 | 格式化普通数值 |
common.ms | 1,000 ms | 以毫秒为单位格式化数值 |
common.date | 2024-01-01 | 格式化日期 |
common.relativeDate | 1 day ago | 格式化相对日期 |
common.duration | 1 day, 1 hour | 格式化时长 |
这些 key 在 public/locales/en/common.json 中的实际定义是 i18next 的插值 + formatter 语法,例如:
"bytes": "{{value, bytes}}", "bits": "{{value, bytes(bits: true)}}", "bbytes": "{{value, bytes(binary: true)}}", "bbits": "{{value, bytes(bits: true; binary: true)}}", "byterate": "{{value, rate(bits: false)}}", "bibyterate": "{{value, rate(bits: false; binary: true)}}", "percent": "{{value, percent}}", "date": "{{value, date}}", "relativeDate": "{{value, relativeDate}}", "duration": "{{value, duration}}"它们底层的格式化逻辑全部注册在 next-i18next.config.js 中,通过i18next.services.formatter.add(...)挂载为 i18next 的自定义 formatter,其实现要点如下:
bytes:基于prettyBytes实现(代码注释标明其灵感来自 pretty-bytes 工具,见 next-i18next.config.js),支持bits、binary选项切换 1000/1024 进制与 B/bit 单位,并通过toLocaleString(number, locale, options)实现按语言环境的千分位分组;rate:在字节格式化基础上追加/s后缀,且binary决定以 1024 还是 1000 为底数(next-i18next.config.js);percent:先将数值除以 100 再用Intl.NumberFormat(lng, { style: "percent", ... })格式化(next-i18next.config.js);date:基于Intl.DateTimeFormat(lng, options),可按语言环境输出日期(next-i18next.config.js);relativeDate:基于Intl.RelativeTimeFormat实现相对时间(如 "1 day ago"),内部按秒/分钟/小时/天/周/月/年选取合适粒度(next-i18next.config.js);duration:将秒数拆分为月/天/小时/分钟/秒,并复用t("common.months")、t("common.days")、t("common.hours")、t("common.minutes")、t("common.seconds")这些本地化单位后缀拼接输出(next-i18next.config.js)。
文本翻译
常用文本类 key 如下:
| Key | 翻译 | 说明 |
|---|---|---|
resources.cpu | CPU | CPU 使用率 |
resources.mem | MEM | 内存使用率 |
resources.total | Total | 总资源 |
resources.free | Free | 空闲资源 |
resources.used | Used | 已用资源 |
resources.load | Load | 负载值 |
resources.temp | TEMP | 温度值 |
resources.max | Max | 最大值 |
resources.uptime | UP | 运行时长 |
这些 key 在 public/locales/en/common.json 的resources命名空间下有完整定义,可供任何 Widget 直接引用。
真实 Widget 中的本地化实践
仓库中的资源类 Widget 是复用上述通用翻译的最佳范例:
- src/components/widgets/resources/memory.jsx 使用
t("common.bytes", { value: data.memory.available, maximumFractionDigits: 1, binary: true })——传入binary: true与小数位限制,输出如1.2 GiB这样的二进制字节格式; - src/components/widgets/resources/network.jsx 使用
t("common.byterate", { value: data.network.tx_sec })显示网络速率,并用↑/↓区分上传/下载方向; - src/components/widgets/resources/uptime.jsx 使用
t("common.duration", { value: data.uptime })将秒数转换为人类可读的时长。
这些调用说明一个通用模式:把原始数值传给t("common.xxx", { value, ...options }),由内置 formatter 负责语言环境相关的格式化,而不是在组件里手动拼字符串。
非 React 场景:直接使用 i18next 实例
除组件 hook 外,Homepage 还扩展了直接使用 i18next 实例的方式。例如 src/components/services/kubernetes-status.jsx 中直接import { t } from "i18next",在普通函数体内调用t("docker.unknown")、t("docker.error")等 key。这适用于无法使用useTranslationhook 的场景(如普通函数、非组件模块),是仓库对 next-i18next 能力的一种扩展用法。
开发与测试建议
为 Widget 接入翻译后,请遵循以下实践(详见 docs/widgets/authoring/getting-started.md 的测试章节):
- 组件内不要出现硬编码文本:所有文本与数值一律走
label或t(); - 只添加英文翻译:即使你懂其他语言,也仅在
common.json中添加英文条目,其余语言交给 Crowdin; - 编写组件测试:Widget 应包含
component.test.jsx,覆盖加载占位状态、错误状态与正常渲染路径,并断言翻译 key 能正确渲染(仓库中的测试工具见 src/test-utils/widget-assertions.js 与 src/test-utils/render-with-providers.jsx); - 保持命名空间唯一:
common.json是全局共享文件,你的 Widget key 命名空间不要与其他 Widget 冲突。
更多关于 Widget 编写规范的内容,可参考 docs/widgets/authoring/index.md、docs/widgets/authoring/component.md 与 docs/widgets/authoring/metadata.md。
小结
Homepage 的 Widget 国际化体系可以总结为三条主线:
- 接入:组件内通过
useTranslation获得t函数,Block的label即翻译 key; - 注册:在 public/locales/en/common.json 末尾追加自己的命名空间对象,只需提供英文;
- 复用:数值与日期格式化直接引用
common.bytes、common.byterate、common.percent、common.duration等内置 key,底层由 next-i18next.config.js 注册的 i18next formatter 按语言环境统一处理。
遵循这套约定,你开发的 Widget 就能与 Homepage 现有的 40+ 语言生态无缝衔接,在任意语言环境下都保持一致的展示质量。
【免费下载链接】homepageA highly customizable homepage (or startpage / application dashboard) with Docker and service API integrations.项目地址: https://gitcode.com/GitHub_Trending/ho/homepage
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考