一键导入
add-translation
How to add, update, and manage i18n translations in Deenruv plugins and admin UI
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
How to add, update, and manage i18n translations in Deenruv plugins and admin UI
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Build admin UI components and pages using @deenruv/react-ui-devkit (shadcn/Tailwind-based)
Step-by-step guide to create a new Deenruv plugin with server and UI components
Write E2E tests for Deenruv plugins using Vitest and @deenruv/testing utilities
How to extend the Deenruv GraphQL API with new types, queries, mutations, and resolvers
| name | add-translation |
| description | How to add, update, and manage i18n translations in Deenruv plugins and admin UI |
Deenruv has two separate translation systems. Identify which one applies before making changes.
| System | Where | Library | Languages |
|---|---|---|---|
| Admin Panel | apps/panel/, plugins/*/src/plugin-ui/ | i18next + react-i18next | en, pl (extendable) |
| Docs Landing | apps/docs/src/components/landing/ | Custom typed objects | en, pl |
NEVER import react-i18next directly. Always use useTranslation from @deenruv/react-ui-devkit:
// CORRECT
import { useTranslation } from "@deenruv/react-ui-devkit";
// WRONG — do NOT do this
import { useTranslation } from "react-i18next";
The devkit wrapper provides tEntity and binds to the correct i18next instance via window.__DEENRUV_SETTINGS__.i18n.
Create plugin-ui/translation-ns.ts using a Symbol-based namespace:
// plugins/<name>-plugin/src/plugin-ui/translation-ns.ts
export const translationNS = Symbol("<name>-plugin").toString();
The Symbol pattern ensures namespace uniqueness across all plugins. Use the plugin directory name as the Symbol label.
Create locale directories with JSON translations for both en and pl:
plugin-ui/locales/
├── en/
│ ├── index.ts # Barrel export
│ └── <feature>.json # English translations
└── pl/
├── index.ts # Barrel export
└── <feature>.json # Polish translations
JSON file (locales/en/<feature>.json):
{
"nav": {
"group": "My Feature",
"link": "Feature Settings"
},
"title": "Feature Configuration",
"form": {
"name": "Name",
"description": "Description",
"save": "Save Changes"
},
"modal": {
"add": { "title": "Add item", "action": "Add", "success": "Added successfully" },
"remove": { "title": "Remove item", "action": "Remove" },
"cancel": "Cancel"
}
}
Barrel file (locales/en/index.ts):
import feature from "./<feature>.json";
export default [feature];
The barrel must export a default array of JSON modules. If you have multiple JSON files per language, include them all:
export default [feature, settings];
Wire translations into createDeenruvUIPlugin:
// plugin-ui/index.tsx
import { createDeenruvUIPlugin } from "@deenruv/react-ui-devkit";
import pl from "./locales/pl";
import en from "./locales/en";
import { translationNS } from "./translation-ns.js";
export const MyFeatureUIPlugin = createDeenruvUIPlugin({
version: "1.0.0",
name: "my-feature-plugin-ui",
translations: { ns: translationNS, data: { en, pl } },
// pages, navMenuGroups, navMenuLinks, etc.
});
The framework calls i18next.addResourceBundle(lng, ns, translations) for each language at plugin registration time.
import { useTranslation } from "@deenruv/react-ui-devkit";
import { translationNS } from "../translation-ns.js";
export const FeaturePage = () => {
const { t } = useTranslation(translationNS);
return (
<div>
<h1>{t("title")}</h1>
<p>{t("form.description")}</p>
<button>{t("form.save")}</button>
</div>
);
};
Nav menu labels use labelId keys that the framework auto-prefixes with the plugin namespace:
navMenuGroups: [
{ id: "my-feature", labelId: "nav.group", placement: { groupId: "settings" } },
],
navMenuLinks: [
{ groupId: "my-feature", id: "feature-settings", labelId: "nav.link", href: "", icon: SettingsIcon },
],
The framework resolves labelId to ${ns}.nav.group automatically — just use the key relative to your JSON root.
The tEntity helper handles pluralized entity actions:
const { t, tEntity } = useTranslation("common");
tEntity("action.delete", "product", "one"); // "Delete product"
tEntity("action.delete", "product", "many"); // "Delete products"
tEntity("action.delete", "product", 5); // "Delete products" (count > 1)
Internally it translates the entity name via entity.<name> keys from the common namespace and injects it as a value interpolation variable.
File: apps/docs/src/components/landing/translations.ts
Translations are plain typed objects keyed by section, then by language:
export const translations = {
mySection: {
en: {
title: 'English Title',
description: 'English description',
},
pl: {
title: 'Polski Tytuł',
description: 'Opis po polsku',
},
},
};
Use the exported t() helper function:
import { t } from './translations';
// In component (lang comes from URL params or context)
const text = t('mySection', lang);
return <h1>{text.title}</h1>;
The t() function signature is t(section, lang) and falls back to en if the language key is missing.
Langexport type Lang = 'en' | 'pl';
When adding a new language, update this union type.
locales/de/) with matching JSON files and barrel index.tstranslations.data: { en, pl, de }translations.ts; update the Lang type unionreact-i18next directly instead of @deenruv/react-ui-devkittranslations in createDeenruvUIPluginuseTranslation(ns) — without it you get keys from "common" onlyen translations — always provide both en and plexport default [json1, json2]"form.save" not "formSave")translation-ns.ts with Symbol pattern)en and plindex.ts files export default arrays of JSON importstranslations property set in createDeenruvUIPlugin()useTranslation(translationNS) from @deenruv/react-ui-devkitlabelId keys match JSON key paths