@nocobase/portal-template-default 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.MD +90 -1
- package/components.json +3 -1
- package/package.json +21 -5
- package/registry/nocobase-acl/README.md +19 -0
- package/registry/nocobase-acl/components/acl-boundary.tsx +106 -0
- package/registry/nocobase-acl/components/index.ts +3 -0
- package/registry/nocobase-acl/components/role-options.ts +36 -0
- package/registry/nocobase-acl/components/role-switcher.tsx +135 -0
- package/registry/nocobase-acl/demo/boundary-api.tsx +186 -0
- package/registry/nocobase-acl/demo/components.tsx +231 -0
- package/registry/nocobase-acl/demo/index.tsx +505 -0
- package/registry/nocobase-acl/demo/lazy-route.tsx +10 -0
- package/registry/nocobase-acl/demo/prompt-generator.tsx +134 -0
- package/registry/nocobase-acl/demo/role-switcher-prompt-generator.tsx +198 -0
- package/registry/nocobase-acl/demo/scenario-section.tsx +37 -0
- package/registry/nocobase-acl/extension.tsx +71 -0
- package/registry/nocobase-acl/index.ts +1 -0
- package/registry/nocobase-acl/tests/acl-regression.mjs +314 -0
- package/registry/nocobase-ai/README.md +70 -0
- package/registry/nocobase-ai/adapters/react-hook-form.ts +20 -0
- package/registry/nocobase-ai/assets/nocobase-ai-chat.svg +50 -0
- package/registry/nocobase-ai/components/ai-root-provider.tsx +32 -0
- package/registry/nocobase-ai/components/chat/ai-employee-avatar.tsx +31 -0
- package/registry/nocobase-ai/components/chat/chat-attachment.tsx +92 -0
- package/registry/nocobase-ai/components/chat/chat-compact.tsx +90 -0
- package/registry/nocobase-ai/components/chat/chat-composer.tsx +429 -0
- package/registry/nocobase-ai/components/chat/chat-empty-state.tsx +51 -0
- package/registry/nocobase-ai/components/chat/chat-header.tsx +88 -0
- package/registry/nocobase-ai/components/chat/chat-history-dialog.tsx +77 -0
- package/registry/nocobase-ai/components/chat/chat-message.tsx +252 -0
- package/registry/nocobase-ai/components/chat/chat-messages.tsx +173 -0
- package/registry/nocobase-ai/components/chat/chat-window.tsx +118 -0
- package/registry/nocobase-ai/components/chat/conversation-list.tsx +343 -0
- package/registry/nocobase-ai/components/chat/markdown-message.tsx +165 -0
- package/registry/nocobase-ai/components/chat/model-select-options.tsx +30 -0
- package/registry/nocobase-ai/components/chat/reasoning-panel.tsx +29 -0
- package/registry/nocobase-ai/components/chat/sub-agent-conversation.tsx +142 -0
- package/registry/nocobase-ai/components/chat/tool-call-card.tsx +316 -0
- package/registry/nocobase-ai/components/chat/user-prompt-editor.tsx +99 -0
- package/registry/nocobase-ai/components/chat/work-context-chip.tsx +114 -0
- package/registry/nocobase-ai/components/index.ts +59 -0
- package/registry/nocobase-ai/components/page-elements/ai-form.tsx +44 -0
- package/registry/nocobase-ai/components/page-elements/page-element-provider.tsx +469 -0
- package/registry/nocobase-ai/components/surfaces/chat-dialog.tsx +31 -0
- package/registry/nocobase-ai/components/surfaces/chat-inline.tsx +18 -0
- package/registry/nocobase-ai/components/surfaces/chat-page.tsx +18 -0
- package/registry/nocobase-ai/components/surfaces/chat-side-panel-layout.tsx +54 -0
- package/registry/nocobase-ai/components/surfaces/chat-side-panel.tsx +74 -0
- package/registry/nocobase-ai/components/surfaces/chat-surface-actions.tsx +54 -0
- package/registry/nocobase-ai/components/tools/builtin-tool-renderers.tsx +31 -0
- package/registry/nocobase-ai/components/tools/business-report-dialog.tsx +364 -0
- package/registry/nocobase-ai/components/tools/business-report-renderer.tsx +122 -0
- package/registry/nocobase-ai/components/tools/business-report-utils.tsx +282 -0
- package/registry/nocobase-ai/components/tools/chart-renderer.tsx +99 -0
- package/registry/nocobase-ai/components/tools/echarts-preview.tsx +59 -0
- package/registry/nocobase-ai/components/tools/echarts-runtime-advanced.ts +18 -0
- package/registry/nocobase-ai/components/tools/echarts-runtime-common.ts +20 -0
- package/registry/nocobase-ai/components/tools/echarts-runtime-components.ts +34 -0
- package/registry/nocobase-ai/components/tools/echarts-runtime-hierarchy.ts +24 -0
- package/registry/nocobase-ai/components/tools/echarts-runtime.ts +138 -0
- package/registry/nocobase-ai/components/tools/sub-agent-renderer.tsx +67 -0
- package/registry/nocobase-ai/components/tools/suggestions-renderer.tsx +59 -0
- package/registry/nocobase-ai/components/tools/tool-renderer-provider.tsx +55 -0
- package/registry/nocobase-ai/components/tools/tool-renderer-utils.ts +18 -0
- package/registry/nocobase-ai/components/tools/workflow-renderer.tsx +108 -0
- package/registry/nocobase-ai/components/triggers/ai-chat-floating-trigger.tsx +73 -0
- package/registry/nocobase-ai/components/triggers/ai-employee-shortcut.tsx +149 -0
- package/registry/nocobase-ai/demo/configuration-gate.tsx +49 -0
- package/registry/nocobase-ai/demo/container-showcase.tsx +210 -0
- package/registry/nocobase-ai/demo/floating.tsx +134 -0
- package/registry/nocobase-ai/demo/index.tsx +346 -0
- package/registry/nocobase-ai/demo/interaction-showcase.tsx +151 -0
- package/registry/nocobase-ai/demo/page-context-prompt-generator.tsx +355 -0
- package/registry/nocobase-ai/demo/page-context.tsx +884 -0
- package/registry/nocobase-ai/demo/page-element-showcase.tsx +197 -0
- package/registry/nocobase-ai/demo/prompt-card.tsx +41 -0
- package/registry/nocobase-ai/demo/prompt-generator.tsx +452 -0
- package/registry/nocobase-ai/demo/shortcut.tsx +1207 -0
- package/registry/nocobase-ai/demo/tool-cards.tsx +530 -0
- package/registry/nocobase-ai/extension.tsx +158 -0
- package/registry/nocobase-ai/global-ai-chat.tsx +213 -0
- package/registry/nocobase-ai/index.ts +3 -0
- package/registry/nocobase-ai/providers/ai-provider.tsx +323 -0
- package/registry/nocobase-ai/providers/avatars.ts +817 -0
- package/registry/nocobase-ai/providers/chat-context.tsx +109 -0
- package/registry/nocobase-ai/providers/chat-controller.ts +84 -0
- package/registry/nocobase-ai/providers/chat-message-utils.ts +198 -0
- package/registry/nocobase-ai/providers/chat-provider.tsx +871 -0
- package/registry/nocobase-ai/providers/chat-reducer.ts +147 -0
- package/registry/nocobase-ai/providers/chat-task-utils.ts +105 -0
- package/registry/nocobase-ai/providers/chat-transport.ts +184 -0
- package/registry/nocobase-ai/providers/form-registry.tsx +271 -0
- package/registry/nocobase-ai/providers/frontend-tool-registry.tsx +231 -0
- package/registry/nocobase-ai/providers/index.ts +61 -0
- package/registry/nocobase-ai/providers/model.ts +32 -0
- package/registry/nocobase-ai/providers/page-context.tsx +136 -0
- package/registry/nocobase-ai/providers/stream-coalescer.ts +60 -0
- package/registry/nocobase-ai/providers/stream-event-utils.ts +82 -0
- package/registry/nocobase-ai/providers/stream-parser.ts +61 -0
- package/registry/nocobase-ai/providers/sub-agent-stream.ts +316 -0
- package/registry/nocobase-ai/providers/types.ts +183 -0
- package/registry/nocobase-ai/providers/ui-message-stream.ts +463 -0
- package/registry/nocobase-ai/providers/use-automatic-tool-approval.ts +94 -0
- package/registry/nocobase-ai/providers/use-chat-attachments.ts +206 -0
- package/registry/nocobase-ai/providers/use-chat-message-actions.ts +554 -0
- package/registry/nocobase-ai/providers/use-chat-runtime.ts +210 -0
- package/registry/nocobase-ai/providers/use-chat-work-context.ts +93 -0
- package/registry/nocobase-ai/providers/use-conversation-catalog.ts +108 -0
- package/registry/nocobase-ai/providers/use-conversation-history.ts +178 -0
- package/registry/nocobase-ai/services/index.ts +7 -0
- package/registry/nocobase-ai/services/nocobase-ai-service.ts +454 -0
- package/registry/nocobase-ai/services/types.ts +82 -0
- package/registry/nocobase-ai/tests/business-report-regression.mjs +70 -0
- package/registry/nocobase-ai/tests/form-filler-regression.mjs +129 -0
- package/registry/nocobase-ai/tests/frontend-tool-regression.mjs +128 -0
- package/registry/nocobase-ai/tests/page-context-regression.mjs +48 -0
- package/registry/nocobase-ai/tests/stream-regression.mjs +207 -0
- package/registry/nocobase-i18n/README.md +53 -0
- package/registry/nocobase-i18n/components/index.ts +1 -0
- package/registry/nocobase-i18n/components/language-switcher.tsx +138 -0
- package/registry/nocobase-i18n/demo/index.tsx +209 -0
- package/registry/nocobase-i18n/demo/prompt-generator.tsx +155 -0
- package/registry/nocobase-i18n/extension.tsx +46 -0
- package/registry/nocobase-i18n/i18n-provider.ts +9 -0
- package/registry/nocobase-i18n/index.ts +6 -0
- package/registry/nocobase-i18n/locale-store.ts +134 -0
- package/registry/nocobase-i18n/locales/en-US.ts +89 -0
- package/registry/nocobase-i18n/locales/zh-CN.ts +85 -0
- package/registry/nocobase-i18n/provider.tsx +32 -0
- package/registry/nocobase-i18n/runtime.ts +118 -0
- package/registry/nocobase-i18n/server-resources.ts +184 -0
- package/registry/nocobase-i18n/tests/i18n-regression.mjs +42 -0
- package/registry.config.json +163 -0
- package/scripts/registry.mjs +164 -0
- package/src/App.css +95 -0
- package/src/App.tsx +87 -69
- package/src/app/extension.ts +11 -0
- package/src/app/extensions.tsx +49 -0
- package/src/components/access-control/access-denied.tsx +30 -0
- package/src/components/access-control/acl-bootstrap.tsx +9 -0
- package/src/components/access-control/acl-gate.tsx +36 -0
- package/src/components/access-control/navigate-to-accessible-resource.tsx +17 -0
- package/src/components/access-control/resource-access-guard.tsx +21 -0
- package/src/components/access-control/use-acl-runtime.ts +28 -0
- package/src/components/app-shell/breadcrumb.tsx +39 -3
- package/src/components/app-shell/document-title-handler.tsx +11 -20
- package/src/components/app-shell/header.tsx +22 -4
- package/src/components/app-shell/loading-overlay.tsx +2 -4
- package/src/components/app-shell/loading-state.tsx +15 -0
- package/src/components/app-shell/sidebar.tsx +103 -45
- package/src/components/data-table/data-table-pagination.tsx +27 -7
- package/src/components/data-table/data-table-styles.ts +57 -0
- package/src/components/data-table/data-table.tsx +14 -59
- package/src/components/resources/resource-label.ts +101 -0
- package/src/components/resources/views/create-view.tsx +17 -14
- package/src/components/resources/views/edit-view.tsx +17 -15
- package/src/components/resources/views/list-view.tsx +20 -10
- package/src/components/resources/views/show-view.tsx +20 -18
- package/src/index.tsx +2 -3
- package/src/lib/i18n.ts +161 -0
- package/src/lib/nocobase/acl/action.ts +147 -0
- package/src/lib/nocobase/acl/data-source.ts +24 -0
- package/src/lib/nocobase/acl/index.ts +6 -0
- package/src/lib/nocobase/acl/menu.ts +41 -0
- package/src/lib/nocobase/acl/record-permissions.ts +111 -0
- package/src/lib/nocobase/acl/store.ts +182 -0
- package/src/lib/nocobase/acl/types.ts +96 -0
- package/src/lib/nocobase/client.ts +279 -0
- package/src/lib/nocobase/error.ts +32 -0
- package/src/locales/en-US.ts +82 -0
- package/src/locales/index.ts +13 -0
- package/src/locales/zh-CN.ts +79 -0
- package/src/pages/users/create.tsx +98 -0
- package/src/pages/users/edit.tsx +92 -0
- package/src/pages/users/form-context.ts +57 -0
- package/src/pages/users/form-fields.tsx +211 -0
- package/src/pages/users/list.tsx +205 -0
- package/src/pages/users/show.tsx +187 -0
- package/src/pages/users/types.ts +20 -0
- package/src/providers/access-control.ts +34 -0
- package/src/providers/auth.ts +74 -60
- package/src/providers/constants.ts +3 -37
- package/src/providers/data.ts +242 -85
- package/vite.config.ts +17 -0
- package/src/pages/blog-posts/create.tsx +0 -177
- package/src/pages/blog-posts/edit.tsx +0 -197
- package/src/pages/blog-posts/list.tsx +0 -131
- package/src/pages/blog-posts/show.tsx +0 -81
- package/src/pages/categories/create.tsx +0 -73
- package/src/pages/categories/edit.tsx +0 -73
- package/src/pages/categories/index.ts +0 -4
- package/src/pages/categories/list.tsx +0 -86
- package/src/pages/categories/show.tsx +0 -37
- /package/src/pages/{blog-posts → users}/index.ts +0 -0
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
const zhCN = {
|
|
2
|
+
starter: {
|
|
3
|
+
"shell.workspace": "AI 应用工作区",
|
|
4
|
+
"shell.footer.freedom": "AI 自由构建。",
|
|
5
|
+
"shell.footer.reliability": "NocoBase 保障可靠运行。",
|
|
6
|
+
"auth.signOut": "退出登录",
|
|
7
|
+
"auth.signingOut": "正在退出...",
|
|
8
|
+
"buttons.apply": "应用",
|
|
9
|
+
"buttons.clear": "清除",
|
|
10
|
+
"buttons.undo": "撤销",
|
|
11
|
+
"pages.error.title": "页面不存在",
|
|
12
|
+
"pages.error.description": "你访问的页面不存在。",
|
|
13
|
+
"pages.error.backHome": "返回首页",
|
|
14
|
+
"table.filter.text.placeholder": "输入筛选内容...",
|
|
15
|
+
"table.filter.numeric.placeholder": "输入筛选数值...",
|
|
16
|
+
"table.filter.combobox.placeholder": "请选择...",
|
|
17
|
+
"table.filter.combobox.search": "搜索...",
|
|
18
|
+
"table.filter.combobox.noResults": "没有找到结果。",
|
|
19
|
+
"table.filter.operator.placeholder": "搜索操作符...",
|
|
20
|
+
"table.filter.operator.noResults": "没有找到操作符。",
|
|
21
|
+
"table.filter.operator.eq": "等于",
|
|
22
|
+
"table.filter.operator.ne": "不等于",
|
|
23
|
+
"table.filter.operator.lt": "小于",
|
|
24
|
+
"table.filter.operator.gt": "大于",
|
|
25
|
+
"table.filter.operator.lte": "小于或等于",
|
|
26
|
+
"table.filter.operator.gte": "大于或等于",
|
|
27
|
+
"table.filter.operator.in": "包含于数组",
|
|
28
|
+
"table.filter.operator.nin": "不包含于数组",
|
|
29
|
+
"table.filter.operator.ina": "包含于数组(区分大小写)",
|
|
30
|
+
"table.filter.operator.nina": "不包含于数组(区分大小写)",
|
|
31
|
+
"table.filter.operator.contains": "包含",
|
|
32
|
+
"table.filter.operator.ncontains": "不包含",
|
|
33
|
+
"table.filter.operator.containss": "包含(区分大小写)",
|
|
34
|
+
"table.filter.operator.ncontainss": "不包含(区分大小写)",
|
|
35
|
+
"table.filter.operator.between": "介于",
|
|
36
|
+
"table.filter.operator.nbetween": "不介于",
|
|
37
|
+
"table.filter.operator.null": "为空",
|
|
38
|
+
"table.filter.operator.nnull": "不为空",
|
|
39
|
+
"table.filter.operator.startswith": "开头是",
|
|
40
|
+
"table.filter.operator.nstartswith": "开头不是",
|
|
41
|
+
"table.filter.operator.startswiths": "开头是(区分大小写)",
|
|
42
|
+
"table.filter.operator.nstartswiths": "开头不是(区分大小写)",
|
|
43
|
+
"table.filter.operator.endswith": "结尾是",
|
|
44
|
+
"table.filter.operator.nendswith": "结尾不是",
|
|
45
|
+
"table.filter.operator.endswiths": "结尾是(区分大小写)",
|
|
46
|
+
"table.filter.operator.nendswiths": "结尾不是(区分大小写)",
|
|
47
|
+
"table.filter.operator.eqs": "等于(区分大小写)",
|
|
48
|
+
"table.filter.operator.nes": "不等于(区分大小写)",
|
|
49
|
+
},
|
|
50
|
+
"nocobase-i18n": {
|
|
51
|
+
"language.label": "语言",
|
|
52
|
+
"language.current": "当前语言",
|
|
53
|
+
"language.switchError": "无法切换语言。",
|
|
54
|
+
"navigation.group": "本地化",
|
|
55
|
+
"navigation.demo": "国际化",
|
|
56
|
+
"demo.title": "国际化",
|
|
57
|
+
"demo.description":
|
|
58
|
+
"结合前端 UI 资源和指定 NocoBase 动态 namespace 的可选混合语言运行时。",
|
|
59
|
+
"demo.switcher.title": "语言切换器",
|
|
60
|
+
"demo.switcher.description": "同一个可复用组件也会集成到登录用户菜单中。",
|
|
61
|
+
"demo.switcher.unavailable.title": "当前仅启用一种语言",
|
|
62
|
+
"demo.switcher.unavailable.description":
|
|
63
|
+
"请在 NocoBase 系统设置中启用其他语言,语言切换器才会显示。",
|
|
64
|
+
"demo.frontend.title": "应用 UI 文案翻译",
|
|
65
|
+
"demo.frontend.description":
|
|
66
|
+
"Starter 和 Registry 组件的界面文案保存在各自的本地语言资源中。",
|
|
67
|
+
"demo.frontend.actions": "常用操作",
|
|
68
|
+
"demo.server.title": "NocoBase 元数据翻译",
|
|
69
|
+
"demo.server.description":
|
|
70
|
+
"数据表和字段名称从 lm-collections 等服务端动态 namespace 加载。",
|
|
71
|
+
"demo.currentLocale": "当前语言:{{locale}}",
|
|
72
|
+
"demo.prompt.title": "提示词生成器",
|
|
73
|
+
"demo.prompt.description": "描述完整的多语言应用场景并生成实现提示词。",
|
|
74
|
+
"demo.prompt.settings": "场景设置",
|
|
75
|
+
"demo.prompt.scene": "应用场景",
|
|
76
|
+
"demo.prompt.languages": "语言",
|
|
77
|
+
"demo.prompt.copy": "复制提示词",
|
|
78
|
+
"demo.prompt.copied": "已复制",
|
|
79
|
+
"demo.prompt.copyError": "无法访问剪贴板,请手动选中并复制提示词。",
|
|
80
|
+
"demo.prompt.output": "生成的提示词",
|
|
81
|
+
"demo.prompt.outputDescription": "修改场景和语言要求时会实时更新。",
|
|
82
|
+
},
|
|
83
|
+
} as const;
|
|
84
|
+
|
|
85
|
+
export default zhCN;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { useEffect, useState, type PropsWithChildren } from "react";
|
|
2
|
+
|
|
3
|
+
import { LoadingState } from "@/components/app-shell/loading-state";
|
|
4
|
+
import { setTranslationResolver } from "@/lib/i18n";
|
|
5
|
+
import { loadServerLocaleResources } from "./server-resources";
|
|
6
|
+
import { translate } from "./runtime";
|
|
7
|
+
|
|
8
|
+
export function LocalePreferenceProvider({ children }: PropsWithChildren) {
|
|
9
|
+
const [ready, setReady] = useState(false);
|
|
10
|
+
|
|
11
|
+
useEffect(() => setTranslationResolver(translate), []);
|
|
12
|
+
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
let active = true;
|
|
15
|
+
void loadServerLocaleResources()
|
|
16
|
+
.catch((error) => {
|
|
17
|
+
console.warn("Unable to load NocoBase locale resources", error);
|
|
18
|
+
})
|
|
19
|
+
.finally(() => {
|
|
20
|
+
if (active) setReady(true);
|
|
21
|
+
});
|
|
22
|
+
return () => {
|
|
23
|
+
active = false;
|
|
24
|
+
};
|
|
25
|
+
}, []);
|
|
26
|
+
|
|
27
|
+
if (!ready) {
|
|
28
|
+
return <LoadingState className="min-h-80" />;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return children;
|
|
32
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { createInstance, type TOptions } from "i18next";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
getTranslationResources,
|
|
5
|
+
registerTranslationResources,
|
|
6
|
+
subscribeTranslationResources,
|
|
7
|
+
type TranslationOptions,
|
|
8
|
+
} from "@/lib/i18n";
|
|
9
|
+
import { nocobaseClient } from "@/lib/nocobase/client";
|
|
10
|
+
import {
|
|
11
|
+
DEFAULT_LOCALE,
|
|
12
|
+
getLocaleDefinition,
|
|
13
|
+
getLocaleDirection,
|
|
14
|
+
getLocaleLabel,
|
|
15
|
+
registerLocale,
|
|
16
|
+
resolveSupportedLocale,
|
|
17
|
+
} from "./locale-store";
|
|
18
|
+
import enUS from "./locales/en-US";
|
|
19
|
+
import zhCN from "./locales/zh-CN";
|
|
20
|
+
|
|
21
|
+
export type LocaleResources = Record<
|
|
22
|
+
string,
|
|
23
|
+
Record<string, string | number | boolean>
|
|
24
|
+
>;
|
|
25
|
+
|
|
26
|
+
export const i18n = createInstance();
|
|
27
|
+
|
|
28
|
+
void i18n.init({
|
|
29
|
+
lng: resolveSupportedLocale(nocobaseClient.getLocale()),
|
|
30
|
+
fallbackLng: DEFAULT_LOCALE,
|
|
31
|
+
defaultNS: "starter",
|
|
32
|
+
fallbackNS: "starter",
|
|
33
|
+
keySeparator: false,
|
|
34
|
+
nsSeparator: false,
|
|
35
|
+
initImmediate: false,
|
|
36
|
+
interpolation: {
|
|
37
|
+
escapeValue: false,
|
|
38
|
+
},
|
|
39
|
+
resources: {
|
|
40
|
+
"en-US": enUS,
|
|
41
|
+
"zh-CN": zhCN,
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
function addLocaleResources(namespace: string, resources: LocaleResources) {
|
|
46
|
+
for (const [locale, resource] of Object.entries(resources)) {
|
|
47
|
+
i18n.addResourceBundle(locale, namespace, resource, true, true);
|
|
48
|
+
registerLocale({
|
|
49
|
+
locale,
|
|
50
|
+
label: getLocaleLabel(locale),
|
|
51
|
+
direction: getLocaleDirection(locale),
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
getTranslationResources().forEach(([namespace, resources]) =>
|
|
57
|
+
addLocaleResources(namespace, resources)
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
subscribeTranslationResources(addLocaleResources);
|
|
61
|
+
|
|
62
|
+
export function getCurrentLocale() {
|
|
63
|
+
return resolveSupportedLocale(i18n.resolvedLanguage ?? i18n.language);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function registerLocaleResources(
|
|
67
|
+
namespace: string,
|
|
68
|
+
resources: LocaleResources
|
|
69
|
+
) {
|
|
70
|
+
registerTranslationResources(namespace, resources);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function translate(
|
|
74
|
+
key: string,
|
|
75
|
+
options?: TranslationOptions | string,
|
|
76
|
+
defaultMessage?: string
|
|
77
|
+
) {
|
|
78
|
+
const normalizedOptions = typeof options === "string" ? undefined : options;
|
|
79
|
+
const normalizedDefault =
|
|
80
|
+
typeof options === "string" ? options : defaultMessage;
|
|
81
|
+
const value = i18n.t(key, {
|
|
82
|
+
...(normalizedOptions as TOptions),
|
|
83
|
+
defaultValue: normalizedDefault ?? normalizedOptions?.defaultValue ?? key,
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
return typeof value === "string" ? value : String(value);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function applyDocumentLocale(locale = getCurrentLocale()) {
|
|
90
|
+
if (typeof document === "undefined") return;
|
|
91
|
+
const direction =
|
|
92
|
+
getLocaleDefinition(locale)?.direction ?? getLocaleDirection(locale);
|
|
93
|
+
document.documentElement.lang = locale;
|
|
94
|
+
document.documentElement.dir = direction;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export async function changeLocale(locale: string) {
|
|
98
|
+
const nextLocale = resolveSupportedLocale(locale);
|
|
99
|
+
|
|
100
|
+
if (nocobaseClient.getToken()) {
|
|
101
|
+
try {
|
|
102
|
+
await nocobaseClient.action("users", "updateLang", {
|
|
103
|
+
method: "POST",
|
|
104
|
+
body: { appLang: nextLocale },
|
|
105
|
+
});
|
|
106
|
+
} catch (error) {
|
|
107
|
+
console.warn("Unable to persist the NocoBase language preference", error);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
nocobaseClient.setLocale(nextLocale);
|
|
112
|
+
await i18n.changeLanguage(nextLocale);
|
|
113
|
+
applyDocumentLocale(nextLocale);
|
|
114
|
+
|
|
115
|
+
if (typeof window !== "undefined") window.location.reload();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
applyDocumentLocale();
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { nocobaseClient } from "@/lib/nocobase/client";
|
|
2
|
+
import {
|
|
3
|
+
getLocaleDefinitions,
|
|
4
|
+
registerLocale,
|
|
5
|
+
setEnabledLocales,
|
|
6
|
+
} from "./locale-store";
|
|
7
|
+
import { applyDocumentLocale, getCurrentLocale, i18n } from "./runtime";
|
|
8
|
+
|
|
9
|
+
type ServerLanguagePayload = {
|
|
10
|
+
lang?: string;
|
|
11
|
+
resources?: Record<string, Record<string, string>>;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
type SystemSettingsPayload = {
|
|
15
|
+
enabledLanguages?: string[];
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const serverResourceNamespaces = new Set(["lm-collections"]);
|
|
19
|
+
const loadedServerResourceNamespaces = new Set<string>();
|
|
20
|
+
const namespaceLoadPromises = new Map<string, Promise<void>>();
|
|
21
|
+
let cachedServerResources: ServerLanguagePayload["resources"] = {};
|
|
22
|
+
let serverResourcesPromise: Promise<void> | undefined;
|
|
23
|
+
let bootstrapStarted = false;
|
|
24
|
+
let bootstrapComplete = false;
|
|
25
|
+
|
|
26
|
+
function mergeNamespace(namespace: string, locale?: string) {
|
|
27
|
+
const resource = cachedServerResources?.[namespace];
|
|
28
|
+
if (!locale || !resource) return false;
|
|
29
|
+
i18n.addResourceBundle(locale, namespace, resource, true, false);
|
|
30
|
+
loadedServerResourceNamespaces.add(namespace);
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function requestServerResources(
|
|
35
|
+
namespaces: string[],
|
|
36
|
+
requestedLocale?: string
|
|
37
|
+
) {
|
|
38
|
+
const payload = await nocobaseClient.action<ServerLanguagePayload>(
|
|
39
|
+
"app",
|
|
40
|
+
"getLang",
|
|
41
|
+
{
|
|
42
|
+
method: "GET",
|
|
43
|
+
query: {
|
|
44
|
+
...(requestedLocale ? { locale: requestedLocale } : {}),
|
|
45
|
+
...(namespaces.length ? { ns: namespaces.join(",") } : {}),
|
|
46
|
+
},
|
|
47
|
+
includeRole: false,
|
|
48
|
+
withAclMeta: false,
|
|
49
|
+
}
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
cachedServerResources = {
|
|
53
|
+
...cachedServerResources,
|
|
54
|
+
...payload.resources,
|
|
55
|
+
};
|
|
56
|
+
for (const namespace of namespaces) {
|
|
57
|
+
mergeNamespace(namespace, payload.lang);
|
|
58
|
+
loadedServerResourceNamespaces.add(namespace);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return payload;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async function ensureServerResourceNamespaces(namespaces: string[]) {
|
|
65
|
+
const pending: Promise<void>[] = [];
|
|
66
|
+
const missing: string[] = [];
|
|
67
|
+
|
|
68
|
+
for (const namespace of namespaces) {
|
|
69
|
+
if (loadedServerResourceNamespaces.has(namespace)) continue;
|
|
70
|
+
if (mergeNamespace(namespace, getCurrentLocale())) continue;
|
|
71
|
+
const existing = namespaceLoadPromises.get(namespace);
|
|
72
|
+
if (existing) pending.push(existing);
|
|
73
|
+
else missing.push(namespace);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (missing.length) {
|
|
77
|
+
const request = requestServerResources(
|
|
78
|
+
missing,
|
|
79
|
+
nocobaseClient.getStoredLocale() ?? getCurrentLocale()
|
|
80
|
+
).then(() => undefined);
|
|
81
|
+
for (const namespace of missing) {
|
|
82
|
+
namespaceLoadPromises.set(namespace, request);
|
|
83
|
+
}
|
|
84
|
+
const cleanup = () => {
|
|
85
|
+
for (const namespace of missing) {
|
|
86
|
+
if (namespaceLoadPromises.get(namespace) === request) {
|
|
87
|
+
namespaceLoadPromises.delete(namespace);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
void request.then(cleanup, cleanup);
|
|
92
|
+
pending.push(request);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
await Promise.all(pending);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function registerServerResourceNamespace(namespace: string) {
|
|
99
|
+
if (!namespace) return;
|
|
100
|
+
serverResourceNamespaces.add(namespace);
|
|
101
|
+
|
|
102
|
+
if (
|
|
103
|
+
mergeNamespace(namespace, getCurrentLocale()) ||
|
|
104
|
+
!bootstrapStarted ||
|
|
105
|
+
!bootstrapComplete
|
|
106
|
+
) {
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
void ensureServerResourceNamespaces([namespace]).catch((error) => {
|
|
111
|
+
console.warn(
|
|
112
|
+
`Unable to load NocoBase locale namespace ${namespace}`,
|
|
113
|
+
error
|
|
114
|
+
);
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function getServerResourceNamespaces() {
|
|
119
|
+
return [...serverResourceNamespaces];
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function loadServerLocaleResources() {
|
|
123
|
+
if (serverResourcesPromise) return serverResourcesPromise;
|
|
124
|
+
|
|
125
|
+
bootstrapStarted = true;
|
|
126
|
+
const requestedLocale = nocobaseClient.getStoredLocale();
|
|
127
|
+
const initialNamespaces = [...serverResourceNamespaces];
|
|
128
|
+
const systemSettingsPromise = nocobaseClient
|
|
129
|
+
.action<SystemSettingsPayload>("systemSettings", "get", {
|
|
130
|
+
method: "GET",
|
|
131
|
+
includeRole: false,
|
|
132
|
+
withAclMeta: false,
|
|
133
|
+
})
|
|
134
|
+
.catch((error) => {
|
|
135
|
+
console.warn("Unable to load enabled NocoBase languages", error);
|
|
136
|
+
return undefined;
|
|
137
|
+
})
|
|
138
|
+
.then((systemSettings) => {
|
|
139
|
+
const configuredLocales = Array.isArray(systemSettings?.enabledLanguages)
|
|
140
|
+
? systemSettings.enabledLanguages
|
|
141
|
+
: [];
|
|
142
|
+
if (configuredLocales.length) setEnabledLocales(configuredLocales);
|
|
143
|
+
return systemSettings;
|
|
144
|
+
});
|
|
145
|
+
const languagePromise = requestServerResources(
|
|
146
|
+
initialNamespaces,
|
|
147
|
+
requestedLocale
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
serverResourcesPromise = Promise.all([systemSettingsPromise, languagePromise])
|
|
151
|
+
.then(async ([systemSettings, payload]) => {
|
|
152
|
+
const configuredLocales = systemSettings?.enabledLanguages ?? [];
|
|
153
|
+
const serverLocale = payload.lang;
|
|
154
|
+
|
|
155
|
+
if (!configuredLocales.length && serverLocale) {
|
|
156
|
+
setEnabledLocales([serverLocale]);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (serverLocale) {
|
|
160
|
+
registerLocale({ locale: serverLocale, label: serverLocale });
|
|
161
|
+
nocobaseClient.setLocale(serverLocale);
|
|
162
|
+
await i18n.changeLanguage(serverLocale);
|
|
163
|
+
applyDocumentLocale(serverLocale);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
bootstrapComplete = true;
|
|
167
|
+
const lateNamespaces = [...serverResourceNamespaces].filter(
|
|
168
|
+
(namespace) => !loadedServerResourceNamespaces.has(namespace)
|
|
169
|
+
);
|
|
170
|
+
if (lateNamespaces.length) {
|
|
171
|
+
await ensureServerResourceNamespaces(lateNamespaces);
|
|
172
|
+
}
|
|
173
|
+
})
|
|
174
|
+
.catch((error) => {
|
|
175
|
+
bootstrapComplete = true;
|
|
176
|
+
if (!getLocaleDefinitions().length) {
|
|
177
|
+
setEnabledLocales([getCurrentLocale()]);
|
|
178
|
+
}
|
|
179
|
+
serverResourcesPromise = undefined;
|
|
180
|
+
throw error;
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
return serverResourcesPromise;
|
|
184
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { createServer } from "vite";
|
|
3
|
+
|
|
4
|
+
const server = await createServer({
|
|
5
|
+
appType: "custom",
|
|
6
|
+
logLevel: "silent",
|
|
7
|
+
server: { middlewareMode: true },
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
try {
|
|
11
|
+
await server.ssrLoadModule("/src/locales/index.ts");
|
|
12
|
+
const { i18n, registerLocaleResources, translate } =
|
|
13
|
+
await server.ssrLoadModule("/registry/nocobase-i18n/runtime.ts");
|
|
14
|
+
const { resolveTranslatableText, setTranslationResolver } =
|
|
15
|
+
await server.ssrLoadModule("/src/lib/i18n.ts");
|
|
16
|
+
|
|
17
|
+
setTranslationResolver(translate);
|
|
18
|
+
|
|
19
|
+
await i18n.changeLanguage("en-US");
|
|
20
|
+
assert.equal(translate("resources.users", { ns: "app" }, "Users"), "Users");
|
|
21
|
+
|
|
22
|
+
await i18n.changeLanguage("zh-CN");
|
|
23
|
+
assert.equal(translate("resources.users", { ns: "app" }, "Users"), "用户");
|
|
24
|
+
assert.equal(resolveTranslatableText('{{t("Admin")}}'), "管理员");
|
|
25
|
+
assert.equal(
|
|
26
|
+
resolveTranslatableText("Full permissions", { ns: "starter" }),
|
|
27
|
+
"全部权限"
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
registerLocaleResources("example-feature", {
|
|
31
|
+
"en-US": { title: "Example" },
|
|
32
|
+
"zh-CN": { title: "示例" },
|
|
33
|
+
});
|
|
34
|
+
assert.equal(
|
|
35
|
+
translate("title", { ns: "example-feature" }, "Example"),
|
|
36
|
+
"示例"
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
console.log("NocoBase i18n regression tests passed");
|
|
40
|
+
} finally {
|
|
41
|
+
await server.close();
|
|
42
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "nocobase-ai",
|
|
3
|
+
"homepage": "https://www.nocobase.com",
|
|
4
|
+
"items": [
|
|
5
|
+
{
|
|
6
|
+
"name": "i18n",
|
|
7
|
+
"type": "registry:block",
|
|
8
|
+
"title": "NocoBase i18n",
|
|
9
|
+
"description": "Optional hybrid internationalization with frontend-owned UI resources, selected NocoBase dynamic namespaces, X-Locale synchronization, a reusable language switcher, and an integrated showcase.",
|
|
10
|
+
"dependencies": [
|
|
11
|
+
"@refinedev/core@^5.0.8",
|
|
12
|
+
"i18next@^25.3.2",
|
|
13
|
+
"lucide-react@^0.487.0",
|
|
14
|
+
"react-router@^7.0.2"
|
|
15
|
+
],
|
|
16
|
+
"registryDependencies": [
|
|
17
|
+
"badge",
|
|
18
|
+
"button",
|
|
19
|
+
"card",
|
|
20
|
+
"dropdown-menu",
|
|
21
|
+
"input",
|
|
22
|
+
"label",
|
|
23
|
+
"select",
|
|
24
|
+
"textarea"
|
|
25
|
+
],
|
|
26
|
+
"docs": "Requires a compatible NocoBase Starter with the generic AppExtension i18nProvider and UserMenuItems host fields. Starter and Registry translations stay local; app:getLang is used only to merge registered dynamic namespaces such as lm-collections. The integrated Demo route documents the complete usage model.",
|
|
27
|
+
"source": {
|
|
28
|
+
"root": "registry/nocobase-i18n",
|
|
29
|
+
"target": "src/extensions/nocobase-i18n",
|
|
30
|
+
"install": false,
|
|
31
|
+
"include": ["."]
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"name": "ai-runtime",
|
|
36
|
+
"type": "registry:lib",
|
|
37
|
+
"title": "NocoBase AI Runtime",
|
|
38
|
+
"description": "NocoBase AI services, providers, transports, controllers, and avatars.",
|
|
39
|
+
"dependencies": [
|
|
40
|
+
"@ai-sdk/react@2.0.0",
|
|
41
|
+
"ai@5.0.0",
|
|
42
|
+
"@dicebear/core@^9.4.3",
|
|
43
|
+
"@nocobase/ai-employee-avatars@^1.0.2",
|
|
44
|
+
"eventsource-parser@3.1.0"
|
|
45
|
+
],
|
|
46
|
+
"source": {
|
|
47
|
+
"root": "registry/nocobase-ai",
|
|
48
|
+
"target": "src/extensions/nocobase-ai",
|
|
49
|
+
"include": ["providers", "services"]
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"name": "ai-chat",
|
|
54
|
+
"type": "registry:block",
|
|
55
|
+
"title": "NocoBase AI Chat Components",
|
|
56
|
+
"description": "Reusable NocoBase AI chat window, containers, floating trigger, and employee shortcut.",
|
|
57
|
+
"dependencies": [
|
|
58
|
+
"echarts@^5.5.0",
|
|
59
|
+
"echarts-for-react@3.0.2",
|
|
60
|
+
"lucide-react@^0.487.0",
|
|
61
|
+
"next-themes@^0.4.6",
|
|
62
|
+
"react-markdown@^10.1.0",
|
|
63
|
+
"remark-gfm@^4.0.1"
|
|
64
|
+
],
|
|
65
|
+
"registryDependencies": [
|
|
66
|
+
"@nocobase/ai-runtime",
|
|
67
|
+
"badge",
|
|
68
|
+
"button",
|
|
69
|
+
"collapsible",
|
|
70
|
+
"dialog",
|
|
71
|
+
"dropdown-menu",
|
|
72
|
+
"hover-card",
|
|
73
|
+
"input",
|
|
74
|
+
"input-group",
|
|
75
|
+
"label",
|
|
76
|
+
"popover",
|
|
77
|
+
"select",
|
|
78
|
+
"tabs",
|
|
79
|
+
"textarea",
|
|
80
|
+
"tooltip"
|
|
81
|
+
],
|
|
82
|
+
"source": {
|
|
83
|
+
"root": "registry/nocobase-ai",
|
|
84
|
+
"target": "src/extensions/nocobase-ai",
|
|
85
|
+
"include": ["components", "assets", "index.ts", "README.md"]
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
"name": "ai-react-hook-form",
|
|
90
|
+
"type": "registry:lib",
|
|
91
|
+
"title": "NocoBase AI React Hook Form Adapter",
|
|
92
|
+
"description": "Optional React Hook Form adapter for the built-in AI Form filler.",
|
|
93
|
+
"dependencies": ["react-hook-form@^7.57.0"],
|
|
94
|
+
"registryDependencies": ["@nocobase/ai-chat"],
|
|
95
|
+
"source": {
|
|
96
|
+
"root": "registry/nocobase-ai",
|
|
97
|
+
"target": "src/extensions/nocobase-ai",
|
|
98
|
+
"include": ["adapters/react-hook-form.ts"]
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
"name": "ai",
|
|
103
|
+
"type": "registry:block",
|
|
104
|
+
"title": "NocoBase AI Components and Demo",
|
|
105
|
+
"description": "Complete NocoBase AI component runtime with application resources, routes, and component showcase pages.",
|
|
106
|
+
"dependencies": ["@refinedev/core@^5.0.8", "react-router@^7.0.2"],
|
|
107
|
+
"registryDependencies": [
|
|
108
|
+
"@nocobase/ai-chat",
|
|
109
|
+
"@nocobase/ai-react-hook-form",
|
|
110
|
+
"accordion",
|
|
111
|
+
"alert",
|
|
112
|
+
"badge",
|
|
113
|
+
"card",
|
|
114
|
+
"checkbox",
|
|
115
|
+
"input",
|
|
116
|
+
"switch",
|
|
117
|
+
"table",
|
|
118
|
+
"textarea"
|
|
119
|
+
],
|
|
120
|
+
"docs": "This item includes the AI extension integration and Demo pages. It is designed for a compatible starter with the AppExtension host enabled. After installation, src/extensions/nocobase-ai/extension.tsx is discovered automatically and contributes its application resources and routes without AI-specific App.tsx edits.",
|
|
121
|
+
"source": {
|
|
122
|
+
"root": "registry/nocobase-ai",
|
|
123
|
+
"target": "src/extensions/nocobase-ai",
|
|
124
|
+
"include": ["demo", "extension.tsx", "global-ai-chat.tsx"]
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
"name": "acl-ui",
|
|
129
|
+
"type": "registry:block",
|
|
130
|
+
"title": "NocoBase ACL UI",
|
|
131
|
+
"description": "Page, region, field, and role-switching components backed by the Starter's NocoBase accessControlProvider.",
|
|
132
|
+
"dependencies": ["@refinedev/core@^5.0.8", "lucide-react@^0.487.0"],
|
|
133
|
+
"registryDependencies": ["badge", "select"],
|
|
134
|
+
"docs": "Requires a compatible NocoBase Starter with the built-in accessControlProvider and ACL store.",
|
|
135
|
+
"source": {
|
|
136
|
+
"root": "registry/nocobase-acl",
|
|
137
|
+
"target": "src/extensions/nocobase-acl",
|
|
138
|
+
"include": ["components", "index.ts", "README.md"]
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
"name": "acl",
|
|
143
|
+
"type": "registry:block",
|
|
144
|
+
"title": "NocoBase ACL Components and Demo",
|
|
145
|
+
"description": "Complete ACL composition demo with live role switching, action matrix, multi-collection regions, and field permissions.",
|
|
146
|
+
"dependencies": ["@refinedev/core@^5.0.8", "react-router@^7.0.2"],
|
|
147
|
+
"registryDependencies": [
|
|
148
|
+
"@nocobase/acl-ui",
|
|
149
|
+
"button",
|
|
150
|
+
"card",
|
|
151
|
+
"input",
|
|
152
|
+
"label",
|
|
153
|
+
"table",
|
|
154
|
+
"textarea"
|
|
155
|
+
],
|
|
156
|
+
"source": {
|
|
157
|
+
"root": "registry/nocobase-acl",
|
|
158
|
+
"target": "src/extensions/nocobase-acl",
|
|
159
|
+
"include": ["demo", "extension.tsx"]
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
]
|
|
163
|
+
}
|