@nocobase/portal-template-default 1.0.2 → 1.0.4

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.
Files changed (62) hide show
  1. package/README.MD +10 -5
  2. package/package.json +1 -1
  3. package/registry/nocobase-ai/components/chat/chat-message.tsx +8 -6
  4. package/registry/nocobase-ai/components/chat/chat-messages.tsx +8 -3
  5. package/registry/nocobase-ai/components/chat/markdown-message.tsx +26 -0
  6. package/registry/nocobase-ai/components/chat/sub-agent-conversation.tsx +3 -3
  7. package/registry/nocobase-ai/demo/configuration-gate.tsx +83 -35
  8. package/registry/nocobase-i18n/README.md +23 -32
  9. package/registry/nocobase-i18n/components/language-switcher.tsx +1 -1
  10. package/registry/nocobase-i18n/demo/index.tsx +2 -2
  11. package/registry/nocobase-i18n/demo/prompt-generator.tsx +3 -2
  12. package/registry/nocobase-i18n/extension.tsx +3 -4
  13. package/registry/nocobase-i18n/index.ts +4 -3
  14. package/registry/nocobase-i18n/locales/en-US.ts +2 -52
  15. package/registry/nocobase-i18n/locales/index.ts +8 -0
  16. package/registry/nocobase-i18n/locales/zh-CN.ts +2 -52
  17. package/registry/nocobase-i18n/provider.tsx +18 -6
  18. package/registry/nocobase-i18n/server-resources.ts +13 -31
  19. package/registry/nocobase-i18n/tests/i18n-regression.mjs +14 -5
  20. package/registry/nocobase-route-surfaces/README.md +21 -0
  21. package/registry/nocobase-route-surfaces/demo/index.tsx +266 -0
  22. package/registry/nocobase-route-surfaces/demo/lazy-route.tsx +11 -0
  23. package/registry/nocobase-route-surfaces/demo/prompt-generator.tsx +90 -0
  24. package/registry/nocobase-route-surfaces/demo/scenarios.ts +95 -0
  25. package/registry/nocobase-route-surfaces/extension.tsx +119 -0
  26. package/registry/nocobase-route-surfaces/index.ts +7 -0
  27. package/registry/nocobase-route-surfaces/route-dialog.tsx +145 -0
  28. package/registry/nocobase-route-surfaces/route-drawer.tsx +186 -0
  29. package/registry/nocobase-route-surfaces/route-page.tsx +29 -0
  30. package/registry/nocobase-route-surfaces/route-surface-context.ts +13 -0
  31. package/registry/nocobase-route-surfaces/use-refine-unsaved-changes.tsx +106 -0
  32. package/registry/nocobase-route-surfaces/use-route-surface-close.ts +15 -0
  33. package/registry/nocobase-route-surfaces/use-route-surface-state.ts +65 -0
  34. package/registry.config.json +30 -3
  35. package/src/App.tsx +65 -35
  36. package/src/app/extension.ts +1 -2
  37. package/src/app/extensions.tsx +0 -14
  38. package/src/components/app-shell/breadcrumb.tsx +67 -2
  39. package/src/components/app-shell/sidebar.tsx +2 -2
  40. package/src/components/resources/views/list-view.tsx +3 -2
  41. package/src/components/ui/sheet.tsx +22 -21
  42. package/src/lib/nocobase/client.ts +6 -1
  43. package/src/locales/en-US.ts +81 -0
  44. package/src/locales/zh-CN.ts +77 -0
  45. package/src/pages/users/create.tsx +76 -53
  46. package/src/pages/users/edit.tsx +87 -56
  47. package/src/pages/users/index.ts +3 -0
  48. package/src/pages/users/layout.tsx +15 -0
  49. package/src/pages/users/list.tsx +19 -21
  50. package/src/pages/users/role-badges.tsx +43 -0
  51. package/src/pages/users/role-detail.tsx +272 -0
  52. package/src/pages/users/role-utils.ts +6 -0
  53. package/src/pages/users/routes.ts +24 -0
  54. package/src/pages/users/show.tsx +195 -104
  55. package/src/pages/users/types.ts +13 -0
  56. package/src/providers/i18n/index.ts +3 -0
  57. package/{registry/nocobase-i18n → src/providers/i18n}/runtime.ts +63 -16
  58. package/src/providers/system-settings/context.ts +26 -0
  59. package/src/providers/system-settings/index.ts +2 -0
  60. package/src/providers/system-settings/provider.tsx +94 -0
  61. /package/{registry/nocobase-i18n → src/providers/i18n}/i18n-provider.ts +0 -0
  62. /package/{registry/nocobase-i18n → src/providers/i18n}/locale-store.ts +0 -0
package/README.MD CHANGED
@@ -68,11 +68,10 @@ pnpm registry:install
68
68
  This repository authors optional NocoBase integration entries. Internationalization
69
69
  is delivered as one cohesive Registry entry:
70
70
 
71
- - `i18n` provides frontend-owned UI resources, selected server-generated
72
- namespaces such as `lm-collections`, the application translation provider,
73
- locale persistence, `X-Locale` synchronization, legacy `{{t(...)}}`
74
- compatibility, the language switcher, and the lazy-loaded showcase with its
75
- prompt generator.
71
+ - `i18n` extends the Starter's built-in translation provider and shared System
72
+ Settings bootstrap with selected server-generated namespaces such as
73
+ `lm-collections`, user locale persistence, the language switcher, and the
74
+ lazy-loaded showcase with its prompt generator.
76
75
 
77
76
  The AI integration is available through four incremental entries:
78
77
 
@@ -109,6 +108,12 @@ complete item through Shadcn:
109
108
  pnpm dlx shadcn@latest add @nocobase/ai
110
109
  ```
111
110
 
111
+ Install the optional NocoBase i18n integration with:
112
+
113
+ ```bash
114
+ pnpm dlx shadcn@latest add @nocobase/i18n
115
+ ```
116
+
112
117
  Shadcn does not keep a Registry item lock or merge local changes. Re-running
113
118
  `add` downloads the current item, skips identical files, and asks before
114
119
  replacing different files. Use `--diff` to inspect an update and `--overwrite`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nocobase/portal-template-default",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "type": "module",
5
5
  "devDependencies": {
6
6
  "@ai-sdk/react": "2.0.0",
@@ -64,8 +64,8 @@ function ChatMessageComponent({
64
64
  const workContext = message.metadata?.workContext ?? [];
65
65
  if (isUser) {
66
66
  return (
67
- <article className="group/message flex flex-col items-end px-4 py-2 sm:px-5">
68
- <div className="flex max-w-[80%] flex-col items-end">
67
+ <article className="group/message flex min-w-0 max-w-full flex-col items-end px-4 py-2 sm:px-5">
68
+ <div className="flex min-w-0 max-w-[80%] flex-col items-end">
69
69
  {workContext.length ? (
70
70
  <div className="mb-1.5 flex max-w-full flex-wrap justify-end gap-1.5">
71
71
  {workContext.map((item, index) => (
@@ -85,8 +85,10 @@ function ChatMessageComponent({
85
85
  </div>
86
86
  ) : null}
87
87
  {text ? (
88
- <div className="rounded-2xl rounded-br-md bg-secondary px-4 py-2.5 text-sm leading-6 text-secondary-foreground">
89
- <div className="whitespace-pre-wrap break-words">{text}</div>
88
+ <div className="min-w-0 max-w-full rounded-2xl rounded-br-md bg-secondary px-4 py-2.5 text-sm leading-6 text-secondary-foreground">
89
+ <div className="whitespace-pre-wrap [overflow-wrap:anywhere]">
90
+ {text}
91
+ </div>
90
92
  </div>
91
93
  ) : null}
92
94
  </div>
@@ -150,7 +152,7 @@ function ChatMessageComponent({
150
152
  );
151
153
 
152
154
  return (
153
- <article className="group/message px-4 py-2 sm:px-5">
155
+ <article className="group/message min-w-0 max-w-full px-4 py-2 sm:px-5">
154
156
  <div className="min-w-0 space-y-4">
155
157
  {assistantParts.map((part, index) => {
156
158
  if (part.type === "reasoning") {
@@ -167,7 +169,7 @@ function ChatMessageComponent({
167
169
  return (
168
170
  <div
169
171
  key={`text-${index}`}
170
- className="ai-markdown text-sm leading-6 text-foreground"
172
+ className="ai-markdown min-w-0 max-w-full [overflow-wrap:anywhere] text-sm leading-6 text-foreground"
171
173
  >
172
174
  <MarkdownMessage>{part.text}</MarkdownMessage>
173
175
  </div>
@@ -94,12 +94,17 @@ export function AIChatMessageList({
94
94
  }, [atBottom, messages, status]);
95
95
 
96
96
  return (
97
- <div className={cn("relative min-h-0 flex-1 bg-background", className)}>
97
+ <div
98
+ className={cn(
99
+ "relative min-h-0 min-w-0 flex-1 overflow-hidden bg-background",
100
+ className
101
+ )}
102
+ >
98
103
  <div
99
104
  ref={viewportRef}
100
105
  role="log"
101
106
  aria-live="polite"
102
- className="absolute inset-0 overflow-y-auto overscroll-contain"
107
+ className="absolute inset-0 overflow-x-hidden overflow-y-auto overscroll-contain"
103
108
  onScroll={(event) => {
104
109
  const element = event.currentTarget;
105
110
  setAtBottom(
@@ -112,7 +117,7 @@ export function AIChatMessageList({
112
117
  ) : (
113
118
  <>
114
119
  {messages.length ? (
115
- <div className="mx-auto w-full max-w-3xl py-2">
120
+ <div className="mx-auto min-w-0 w-full max-w-3xl py-2">
116
121
  {messages.map((message) => (
117
122
  <ChatMessage
118
123
  key={message.id}
@@ -9,6 +9,22 @@ type MarkdownMessageProps = {
9
9
  variant?: "chat" | "document";
10
10
  };
11
11
 
12
+ function formatStandaloneJson(content: string) {
13
+ const trimmed = content.trim();
14
+ const isObject = trimmed.startsWith("{") && trimmed.endsWith("}");
15
+ const isArray = trimmed.startsWith("[") && trimmed.endsWith("]");
16
+
17
+ if (!isObject && !isArray) return null;
18
+
19
+ try {
20
+ const value: unknown = JSON.parse(trimmed);
21
+ if (value === null || typeof value !== "object") return null;
22
+ return JSON.stringify(value, null, 2);
23
+ } catch {
24
+ return null;
25
+ }
26
+ }
27
+
12
28
  function createMarkdownComponents(variant: MarkdownMessageProps["variant"]): Components {
13
29
  const document = variant === "document";
14
30
 
@@ -154,6 +170,16 @@ export function MarkdownMessage({
154
170
  children,
155
171
  variant = "chat",
156
172
  }: MarkdownMessageProps) {
173
+ const formattedJson = formatStandaloneJson(children);
174
+
175
+ if (formattedJson) {
176
+ return (
177
+ <pre className="my-4 max-w-full overflow-x-auto rounded-lg bg-muted p-4 text-sm leading-6">
178
+ <code className="font-mono">{formattedJson}</code>
179
+ </pre>
180
+ );
181
+ }
182
+
157
183
  return (
158
184
  <ReactMarkdown
159
185
  remarkPlugins={[remarkGfm]}
@@ -45,7 +45,7 @@ export function SubAgentConversation({
45
45
  const interactionPending = status === "streaming" || status === "submitted";
46
46
 
47
47
  return (
48
- <section className="rounded-xl border border-dashed bg-muted/15">
48
+ <section className="min-w-0 max-w-full rounded-xl border border-dashed bg-muted/15">
49
49
  <Button
50
50
  type="button"
51
51
  variant="ghost"
@@ -67,7 +67,7 @@ export function SubAgentConversation({
67
67
  </span>
68
68
  </Button>
69
69
  {expanded ? (
70
- <div className="space-y-3 border-t border-dashed px-3 py-3">
70
+ <div className="min-w-0 space-y-3 border-t border-dashed px-3 py-3">
71
71
  {messages.flatMap((message) =>
72
72
  message.parts.map((part, index) => {
73
73
  if (part.type === "reasoning") {
@@ -84,7 +84,7 @@ export function SubAgentConversation({
84
84
  return (
85
85
  <div
86
86
  key={`${message.id}-text-${index}`}
87
- className="ai-markdown text-sm leading-6 text-foreground"
87
+ className="ai-markdown min-w-0 max-w-full [overflow-wrap:anywhere] text-sm leading-6 text-foreground"
88
88
  >
89
89
  <MarkdownMessage>{part.text}</MarkdownMessage>
90
90
  </div>
@@ -1,49 +1,97 @@
1
- import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
2
- import { Badge } from "@/components/ui/badge";
3
- import { LoadingState } from "@/components/app-shell/loading-state";
4
- import { useAI } from "../providers";
5
- import { CircleAlert } from "lucide-react";
6
1
  import type { ReactNode } from "react";
2
+ import { CircleAlert } from "lucide-react";
7
3
 
8
- export function AIConfigurationGate({ children }: { children: ReactNode }) {
9
- const {
10
- configurationStatus,
11
- configurationError,
12
- modelConfigurationError,
13
- hasEnabledModels,
14
- } = useAI();
4
+ import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
5
+ import {
6
+ AIProvider,
7
+ useAI,
8
+ type AIEmployee,
9
+ type AIModel,
10
+ } from "../providers";
11
+ import type { AIService } from "../services";
12
+
13
+ const previewEmployees: AIEmployee[] = [
14
+ {
15
+ username: "atlas",
16
+ nickname: "Atlas",
17
+ position: "General assistant",
18
+ greeting: "How can I help with this page?",
19
+ },
20
+ {
21
+ username: "viz",
22
+ nickname: "Viz",
23
+ position: "Insights analyst",
24
+ greeting: "What would you like to explore?",
25
+ },
26
+ ];
15
27
 
16
- if (configurationStatus === "ready" && hasEnabledModels) return children;
28
+ const previewModels: AIModel[] = [
29
+ {
30
+ value: "__preview__",
31
+ label: "Preview only",
32
+ configured: false,
33
+ },
34
+ ];
17
35
 
18
- const loading = configurationStatus === "loading";
19
- const error = configurationError ?? modelConfigurationError;
36
+ const emptyStream = () =>
37
+ new ReadableStream<Uint8Array>({
38
+ start(controller) {
39
+ controller.close();
40
+ },
41
+ });
20
42
 
21
- if (loading) return <LoadingState className="min-h-80" />;
43
+ const previewService: AIService = {
44
+ listEmployees: async () => previewEmployees,
45
+ listModels: async () => previewModels,
46
+ updateEmployeeUserPrompt: async () => undefined,
47
+ listConversations: async () => [],
48
+ getConversationMessages: async () => [],
49
+ getConversationActiveState: async () => "idle",
50
+ updateConversationTitle: async () => undefined,
51
+ destroyConversation: async () => undefined,
52
+ uploadFile: async (file) => ({
53
+ id: crypto.randomUUID(),
54
+ filename: file.name,
55
+ mimetype: file.type,
56
+ size: file.size,
57
+ }),
58
+ createConversation: async () => `preview-${crypto.randomUUID()}`,
59
+ sendMessagesStream: async () => emptyStream(),
60
+ resendMessagesStream: async () => emptyStream(),
61
+ updateToolCallDecision: async () => ({ updated: 0, toolCalls: [] }),
62
+ resumeToolCallStream: async () => emptyStream(),
63
+ resumeConversationStream: async () => emptyStream(),
64
+ };
65
+
66
+ export function AIConfigurationGate({ children }: { children: ReactNode }) {
67
+ const { configurationStatus, hasEnabledModels, employees } = useAI();
68
+ const configured = configurationStatus === "ready" && hasEnabledModels;
69
+
70
+ if (configured) return children;
71
+
72
+ const content = employees.length ? (
73
+ children
74
+ ) : (
75
+ <AIProvider
76
+ employees={previewEmployees}
77
+ models={previewModels}
78
+ service={previewService}
79
+ >
80
+ {children}
81
+ </AIProvider>
82
+ );
22
83
 
23
84
  return (
24
- <div className="space-y-6 pb-12">
25
- <section className="border-b pb-8">
26
- <div className="flex items-center gap-2">
27
- <Badge variant="secondary">AI Components</Badge>
28
- <Badge variant="outline">Live API</Badge>
29
- </div>
30
- <h1 className="mt-4 text-3xl font-semibold tracking-[-0.035em]">
31
- NocoBase AI
32
- </h1>
33
- <p className="mt-3 max-w-3xl text-sm leading-6 text-muted-foreground">
34
- Conversations use the AI employees and enabled models configured in
35
- the connected NocoBase application.
36
- </p>
37
- </section>
38
-
39
- <Alert variant="destructive">
85
+ <div className="space-y-6">
86
+ <Alert>
40
87
  <CircleAlert />
41
- <AlertTitle>NocoBase AI is not available</AlertTitle>
88
+ <AlertTitle>Preview mode</AlertTitle>
42
89
  <AlertDescription>
43
- {error?.message ??
44
- "Check the NocoBase connection and the AI employees available to the current user."}
90
+ Component examples remain available. Sending messages requires an
91
+ enabled AI model in the connected NocoBase application.
45
92
  </AlertDescription>
46
93
  </Alert>
94
+ {content}
47
95
  </div>
48
96
  );
49
97
  }
@@ -1,43 +1,37 @@
1
1
  # NocoBase i18n
2
2
 
3
- Optional frontend-owned internationalization for the NocoBase Admin Starter.
3
+ Optional NocoBase server-language integration and language controls for the
4
+ NocoBase Admin Starter.
4
5
 
5
- The Registry connects i18next to the application translation provider, persists
6
- the selected locale through the Starter's shared NocoBase client, sends it as
7
- `X-Locale`, and adds a reusable language switcher to the signed-in user menu.
8
- Its integrated Demo route shows the complete configuration model without
9
- requiring a second Registry item.
6
+ The Starter owns the Refine i18n provider, local i18next runtime, application
7
+ resources, locale selection, and the shared `systemSettings:get` bootstrap.
8
+ This Registry reuses those foundations and adds only the NocoBase-specific
9
+ remote capabilities:
10
10
 
11
- Starter and Registry UI resources remain frontend-owned. At startup the runtime
12
- reads `enabledLanguages` from `systemSettings:get` and merges only registered
13
- dynamic NocoBase namespaces from `app:getLang`; `lm-collections` is registered
14
- by default so collection and field translations remain compatible.
11
+ - load registered dynamic namespaces from `app:getLang`;
12
+ - persist a signed-in user's selected language through `users:updateLang`;
13
+ - expose reusable page and user-menu language switchers;
14
+ - provide an integrated Demo and Prompt generator.
15
15
 
16
- Application-owned React translations belong in `src/locales`, outside the
17
- installed Registry directory. Register them through the Starter-level
18
- `registerTranslationResources` helper so the application remains buildable when
19
- the optional i18n Registry is not installed. When this Registry is present, it
20
- consumes both resources registered before startup and resources added later by
21
- lazy features.
22
-
23
- On NocoBase versions that support filtered language resources, the request uses
24
- `app:getLang?ns=lm-collections,...`. Older servers ignore the query parameter;
25
- the client still merges only registered namespaces, so the integration remains
26
- backward compatible.
16
+ When this Registry is not installed, the Starter still uses the system default
17
+ language and all local translations normally. It does not request
18
+ `app:getLang`. When installed, the Registry reads the already-cached system
19
+ settings and requests only registered server namespaces. `lm-collections` is
20
+ registered by default for collection and field metadata.
27
21
 
28
- Other installed components can register their own namespace without changing
29
- the runtime:
22
+ Application-owned React translations belong in `src/locales`, outside the
23
+ installed Registry directory:
30
24
 
31
25
  ```ts
32
- import { registerLocaleResources } from "@/extensions/nocobase-i18n";
26
+ import { registerLocaleResources } from "@/providers/i18n";
33
27
 
34
- registerLocaleResources("my-extension", {
28
+ registerLocaleResources("my-feature", {
35
29
  "en-US": { title: "Orders" },
36
30
  "zh-CN": { title: "订单" },
37
31
  });
38
32
  ```
39
33
 
40
- If a component relies on another server-generated namespace, opt in explicitly:
34
+ Other installed components can opt into another server-generated namespace:
41
35
 
42
36
  ```ts
43
37
  import { registerServerResourceNamespace } from "@/extensions/nocobase-i18n";
@@ -45,9 +39,6 @@ import { registerServerResourceNamespace } from "@/extensions/nocobase-i18n";
45
39
  registerServerResourceNamespace("my-dynamic-namespace");
46
40
  ```
47
41
 
48
- Namespaces registered after application startup are loaded incrementally, so
49
- lazy Registry components do not need to participate in the initial request.
50
-
51
- Use the application's `useTranslate` hook in React components. Existing exact
52
- NocoBase-style expressions such as `{{t("Orders")}}` and expressions with a
53
- string namespace are resolved through the Starter compatibility helper.
42
+ Namespaces registered after startup are loaded incrementally. Existing exact
43
+ NocoBase expressions such as `{{t("Orders")}}` remain supported by the
44
+ Starter's translation compatibility helper.
@@ -18,7 +18,7 @@ import {
18
18
  SelectValue,
19
19
  } from "@/components/ui/select";
20
20
  import { cn } from "@/lib/utils";
21
- import { useEnabledLocales } from "../locale-store";
21
+ import { useEnabledLocales } from "@/providers/i18n";
22
22
 
23
23
  export type LanguageSwitcherProps = {
24
24
  className?: string;
@@ -37,7 +37,7 @@ export function I18nDemoPage() {
37
37
  {t(
38
38
  "demo.description",
39
39
  { ns: "nocobase-i18n" },
40
- "An optional hybrid locale runtime with frontend UI resources and selected NocoBase dynamic namespaces."
40
+ "Optional NocoBase server translations and language controls built on the Starter's internationalization runtime."
41
41
  )}
42
42
  </p>
43
43
  </header>
@@ -98,7 +98,7 @@ export function I18nDemoPage() {
98
98
  description={t(
99
99
  "demo.frontend.description",
100
100
  { ns: "nocobase-i18n" },
101
- "Starter and Registry components keep their interface messages in local resource files."
101
+ "The Starter provides the runtime while application and Registry components keep their messages in local resource files."
102
102
  )}
103
103
  >
104
104
  <div className="divide-y rounded-lg border">
@@ -30,12 +30,13 @@ Application scene
30
30
  - Translate the complete visible user flow, including empty, loading, validation, and error states.
31
31
 
32
32
  Implementation contract
33
- - Use the installed @/extensions/nocobase-i18n runtime and the application's translation and locale hooks.
33
+ - Use the Starter's @/providers/i18n runtime and the application's translation and locale hooks.
34
34
  - Put application-owned React messages in src/locales and register the app namespace through registerTranslationResources from @/lib/i18n.
35
35
  - Keep each Registry component's messages in its own namespace and register them with registerLocaleResources().
36
36
  - Do not place application messages inside src/extensions/nocobase-i18n/locales because installed Registry files may be refreshed independently.
37
37
  - Use the reusable LanguageSwitcher when the page needs an explicit language control; the signed-in user menu already includes one.
38
- - Keep Starter and Registry UI resources local. Use app:getLang only for registered dynamic server namespaces such as lm-collections.
38
+ - Reuse the Starter's shared system settings instead of requesting systemSettings:get again.
39
+ - Keep Starter and Registry UI resources local. Use app:getLang only for registered dynamic server namespaces such as lm-collections when the NocoBase i18n Registry is installed.
39
40
  - Let the shared NocoBase client send the selected locale through X-Locale.
40
41
  - Preserve compatibility when server metadata contains exact {{t("...")}} expressions.
41
42
  - Provide complete React components, locale resource files, route/resource integration, and realistic visible content.`,
@@ -4,8 +4,8 @@ import { Languages } from "lucide-react";
4
4
  import { lazy, Suspense } from "react";
5
5
  import { Route } from "react-router";
6
6
  import { LanguageUserMenuItems } from "./components";
7
- import { i18nProvider } from "./i18n-provider";
8
- import { LocalePreferenceProvider } from "./provider";
7
+ import "./locales";
8
+ import { NocoBaseI18nBootstrap } from "./provider";
9
9
 
10
10
  const I18nDemoPage = lazy(() =>
11
11
  import("./demo").then((module) => ({ default: module.I18nDemoPage }))
@@ -13,8 +13,7 @@ const I18nDemoPage = lazy(() =>
13
13
 
14
14
  const nocobaseI18nExtension: AppExtension = {
15
15
  id: "nocobase-i18n",
16
- i18nProvider,
17
- Provider: LocalePreferenceProvider,
16
+ Provider: NocoBaseI18nBootstrap,
18
17
  UserMenuItems: LanguageUserMenuItems,
19
18
  resources: [
20
19
  {
@@ -1,6 +1,7 @@
1
1
  export * from "./components";
2
- export * from "./i18n-provider";
3
- export * from "./locale-store";
4
2
  export * from "./provider";
5
- export * from "./runtime";
6
3
  export * from "./server-resources";
4
+ export {
5
+ registerLocaleResources,
6
+ useEnabledLocales,
7
+ } from "@/providers/i18n";
@@ -1,53 +1,4 @@
1
1
  const enUS = {
2
- starter: {
3
- "shell.workspace": "AI application workspace",
4
- "shell.footer.freedom": "AI builds freely.",
5
- "shell.footer.reliability": "NocoBase keeps it reliable.",
6
- "auth.signOut": "Sign out",
7
- "auth.signingOut": "Signing out...",
8
- "buttons.apply": "Apply",
9
- "buttons.clear": "Clear",
10
- "buttons.undo": "Undo",
11
- "pages.error.title": "Page not found.",
12
- "pages.error.description": "The page you're looking for does not exist.",
13
- "pages.error.backHome": "Back to homepage",
14
- "table.filter.text.placeholder": "Filter by...",
15
- "table.filter.numeric.placeholder": "Filter by...",
16
- "table.filter.combobox.placeholder": "Select...",
17
- "table.filter.combobox.search": "Search...",
18
- "table.filter.combobox.noResults": "Results not found.",
19
- "table.filter.operator.placeholder": "Search operator...",
20
- "table.filter.operator.noResults": "No operator found.",
21
- "table.filter.operator.eq": "Equals",
22
- "table.filter.operator.ne": "Not equals",
23
- "table.filter.operator.lt": "Less than",
24
- "table.filter.operator.gt": "Greater than",
25
- "table.filter.operator.lte": "Less than or equal",
26
- "table.filter.operator.gte": "Greater than or equal",
27
- "table.filter.operator.in": "Includes in an array",
28
- "table.filter.operator.nin": "Not includes in an array",
29
- "table.filter.operator.ina": "Includes in an array (case sensitive)",
30
- "table.filter.operator.nina": "Not includes in an array (case sensitive)",
31
- "table.filter.operator.contains": "Contains",
32
- "table.filter.operator.ncontains": "Not contains",
33
- "table.filter.operator.containss": "Contains (case sensitive)",
34
- "table.filter.operator.ncontainss": "Not contains (case sensitive)",
35
- "table.filter.operator.between": "Between",
36
- "table.filter.operator.nbetween": "Not between",
37
- "table.filter.operator.null": "Is null",
38
- "table.filter.operator.nnull": "Is not null",
39
- "table.filter.operator.startswith": "Starts with",
40
- "table.filter.operator.nstartswith": "Not starts with",
41
- "table.filter.operator.startswiths": "Starts with (case sensitive)",
42
- "table.filter.operator.nstartswiths": "Not starts with (case sensitive)",
43
- "table.filter.operator.endswith": "Ends with",
44
- "table.filter.operator.nendswith": "Not ends with",
45
- "table.filter.operator.endswiths": "Ends with (case sensitive)",
46
- "table.filter.operator.nendswiths": "Not ends with (case sensitive)",
47
- "table.filter.operator.eqs": "Equals (case sensitive)",
48
- "table.filter.operator.nes": "Not equals (case sensitive)",
49
- },
50
- "nocobase-i18n": {
51
2
  "language.label": "Language",
52
3
  "language.current": "Current language",
53
4
  "language.switchError": "Unable to switch language.",
@@ -55,7 +6,7 @@ const enUS = {
55
6
  "navigation.demo": "Internationalization",
56
7
  "demo.title": "Internationalization",
57
8
  "demo.description":
58
- "An optional hybrid locale runtime with frontend UI resources and selected NocoBase dynamic namespaces.",
9
+ "Optional NocoBase server translations and language controls built on the Starter's internationalization runtime.",
59
10
  "demo.switcher.title": "Language switcher",
60
11
  "demo.switcher.description":
61
12
  "The same reusable component is also integrated into the signed-in user menu.",
@@ -64,7 +15,7 @@ const enUS = {
64
15
  "Enable another language in NocoBase system settings to display the switcher.",
65
16
  "demo.frontend.title": "Application UI translations",
66
17
  "demo.frontend.description":
67
- "Starter and Registry components keep their interface messages in local resource files.",
18
+ "The Starter provides the runtime while application and Registry components keep their messages in local resource files.",
68
19
  "demo.frontend.actions": "Common actions",
69
20
  "demo.server.title": "NocoBase metadata translations",
70
21
  "demo.server.description":
@@ -83,7 +34,6 @@ const enUS = {
83
34
  "demo.prompt.output": "Generated prompt",
84
35
  "demo.prompt.outputDescription":
85
36
  "Updates as you change the scene and language requirements.",
86
- },
87
37
  } as const;
88
38
 
89
39
  export default enUS;
@@ -0,0 +1,8 @@
1
+ import { registerLocaleResources } from "@/providers/i18n";
2
+ import enUS from "./en-US";
3
+ import zhCN from "./zh-CN";
4
+
5
+ registerLocaleResources("nocobase-i18n", {
6
+ "en-US": enUS,
7
+ "zh-CN": zhCN,
8
+ });
@@ -1,53 +1,4 @@
1
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
2
  "language.label": "语言",
52
3
  "language.current": "当前语言",
53
4
  "language.switchError": "无法切换语言。",
@@ -55,7 +6,7 @@ const zhCN = {
55
6
  "navigation.demo": "国际化",
56
7
  "demo.title": "国际化",
57
8
  "demo.description":
58
- "结合前端 UI 资源和指定 NocoBase 动态 namespace 的可选混合语言运行时。",
9
+ "基于 Starter 国际化运行时的可选 NocoBase 服务端翻译与语言切换能力。",
59
10
  "demo.switcher.title": "语言切换器",
60
11
  "demo.switcher.description": "同一个可复用组件也会集成到登录用户菜单中。",
61
12
  "demo.switcher.unavailable.title": "当前仅启用一种语言",
@@ -63,7 +14,7 @@ const zhCN = {
63
14
  "请在 NocoBase 系统设置中启用其他语言,语言切换器才会显示。",
64
15
  "demo.frontend.title": "应用 UI 文案翻译",
65
16
  "demo.frontend.description":
66
- "Starter Registry 组件的界面文案保存在各自的本地语言资源中。",
17
+ "Starter 提供运行时,应用和 Registry 组件分别维护自己的本地语言资源。",
67
18
  "demo.frontend.actions": "常用操作",
68
19
  "demo.server.title": "NocoBase 元数据翻译",
69
20
  "demo.server.description":
@@ -79,7 +30,6 @@ const zhCN = {
79
30
  "demo.prompt.copyError": "无法访问剪贴板,请手动选中并复制提示词。",
80
31
  "demo.prompt.output": "生成的提示词",
81
32
  "demo.prompt.outputDescription": "修改场景和语言要求时会实时更新。",
82
- },
83
33
  } as const;
84
34
 
85
35
  export default zhCN;