@nocobase/portal-template-default 1.0.1 → 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.
Files changed (78) hide show
  1. package/README.MD +22 -3
  2. package/package.json +4 -2
  3. package/registry/nocobase-acl/components/role-options.ts +7 -0
  4. package/registry/nocobase-acl/components/role-switcher.tsx +9 -4
  5. package/registry/nocobase-acl/demo/boundary-api.tsx +87 -16
  6. package/registry/nocobase-acl/demo/components.tsx +32 -11
  7. package/registry/nocobase-acl/demo/index.tsx +45 -14
  8. package/registry/nocobase-acl/demo/lazy-route.tsx +2 -7
  9. package/registry/nocobase-acl/demo/prompt-generator.tsx +52 -33
  10. package/registry/nocobase-acl/demo/role-switcher-prompt-generator.tsx +28 -9
  11. package/registry/nocobase-acl/extension.tsx +1 -1
  12. package/registry/nocobase-acl/tests/acl-regression.mjs +7 -6
  13. package/registry/nocobase-ai/components/chat/chat-messages.tsx +3 -4
  14. package/registry/nocobase-ai/components/chat/conversation-list.tsx +2 -4
  15. package/registry/nocobase-ai/components/tools/business-report-dialog.tsx +16 -19
  16. package/registry/nocobase-ai/components/tools/chart-renderer.tsx +3 -14
  17. package/registry/nocobase-ai/components/tools/echarts-preview.tsx +2 -5
  18. package/registry/nocobase-ai/demo/configuration-gate.tsx +9 -12
  19. package/registry/nocobase-ai/extension.tsx +2 -7
  20. package/registry/nocobase-i18n/README.md +53 -0
  21. package/registry/nocobase-i18n/components/index.ts +1 -0
  22. package/registry/nocobase-i18n/components/language-switcher.tsx +138 -0
  23. package/registry/nocobase-i18n/demo/index.tsx +209 -0
  24. package/registry/nocobase-i18n/demo/prompt-generator.tsx +155 -0
  25. package/registry/nocobase-i18n/extension.tsx +46 -0
  26. package/registry/nocobase-i18n/i18n-provider.ts +9 -0
  27. package/registry/nocobase-i18n/index.ts +6 -0
  28. package/registry/nocobase-i18n/locale-store.ts +134 -0
  29. package/registry/nocobase-i18n/locales/en-US.ts +89 -0
  30. package/registry/nocobase-i18n/locales/zh-CN.ts +85 -0
  31. package/registry/nocobase-i18n/provider.tsx +32 -0
  32. package/registry/nocobase-i18n/runtime.ts +118 -0
  33. package/registry/nocobase-i18n/server-resources.ts +184 -0
  34. package/registry/nocobase-i18n/tests/i18n-regression.mjs +42 -0
  35. package/registry.config.json +34 -14
  36. package/scripts/registry.mjs +3 -0
  37. package/src/App.tsx +27 -86
  38. package/src/app/extension.ts +3 -1
  39. package/src/app/extensions.tsx +21 -0
  40. package/src/components/access-control/acl-gate.tsx +4 -10
  41. package/src/components/app-shell/breadcrumb.tsx +39 -3
  42. package/src/components/app-shell/document-title-handler.tsx +11 -20
  43. package/src/components/app-shell/header.tsx +18 -3
  44. package/src/components/app-shell/loading-overlay.tsx +2 -4
  45. package/src/components/app-shell/loading-state.tsx +15 -0
  46. package/src/components/app-shell/sidebar.tsx +75 -47
  47. package/src/components/data-table/data-table-pagination.tsx +27 -7
  48. package/src/components/data-table/data-table-styles.ts +57 -0
  49. package/src/components/data-table/data-table.tsx +14 -59
  50. package/src/components/resources/resource-label.ts +101 -0
  51. package/src/components/resources/views/create-view.tsx +17 -14
  52. package/src/components/resources/views/edit-view.tsx +17 -15
  53. package/src/components/resources/views/list-view.tsx +20 -10
  54. package/src/components/resources/views/show-view.tsx +20 -18
  55. package/src/index.tsx +2 -3
  56. package/src/lib/i18n.ts +161 -0
  57. package/src/lib/nocobase/client.ts +19 -2
  58. package/src/locales/en-US.ts +82 -0
  59. package/src/locales/index.ts +13 -0
  60. package/src/locales/zh-CN.ts +79 -0
  61. package/src/pages/users/create.tsx +98 -0
  62. package/src/pages/users/edit.tsx +92 -0
  63. package/src/pages/users/form-context.ts +57 -0
  64. package/src/pages/users/form-fields.tsx +211 -0
  65. package/src/pages/users/list.tsx +205 -0
  66. package/src/pages/users/show.tsx +187 -0
  67. package/src/pages/users/types.ts +20 -0
  68. package/src/providers/constants.ts +1 -0
  69. package/src/pages/blog-posts/create.tsx +0 -177
  70. package/src/pages/blog-posts/edit.tsx +0 -197
  71. package/src/pages/blog-posts/list.tsx +0 -131
  72. package/src/pages/blog-posts/show.tsx +0 -81
  73. package/src/pages/categories/create.tsx +0 -73
  74. package/src/pages/categories/edit.tsx +0 -73
  75. package/src/pages/categories/index.ts +0 -4
  76. package/src/pages/categories/list.tsx +0 -86
  77. package/src/pages/categories/show.tsx +0 -37
  78. /package/src/pages/{blog-posts → users}/index.ts +0 -0
@@ -1,16 +1,13 @@
1
1
  "use client";
2
2
 
3
3
  import { cn } from "@/lib/utils";
4
- import {
5
- useBack,
6
- useResourceParams,
7
- useUserFriendlyName,
8
- } from "@refinedev/core";
4
+ import { useBack, useResourceParams, useTranslate } from "@refinedev/core";
9
5
  import type { PropsWithChildren } from "react";
10
6
  import { Breadcrumb } from "@/components/app-shell/breadcrumb";
11
7
  import { Button } from "@/components/ui/button";
12
8
  import { RefreshButton } from "@/components/resources/buttons/refresh";
13
9
  import { ArrowLeftIcon } from "lucide-react";
10
+ import { useResourceLabel } from "@/components/resources/resource-label";
14
11
 
15
12
  type EditViewProps = PropsWithChildren<{
16
13
  className?: string;
@@ -41,8 +38,7 @@ export const EditViewHeader = ({
41
38
  headerClassName,
42
39
  }: EditViewHeaderProps) => {
43
40
  const back = useBack();
44
-
45
- const getUserFriendlyName = useUserFriendlyName();
41
+ const translate = useTranslate();
46
42
 
47
43
  const { resource, identifier } = useResourceParams({
48
44
  resource: resourceFromProps,
@@ -51,11 +47,19 @@ export const EditViewHeader = ({
51
47
 
52
48
  const resourceName = resource?.name ?? identifier;
53
49
 
54
- const resourceTitle = getUserFriendlyName(
55
- resource?.meta?.label ?? identifier ?? resource?.name,
56
- "singular"
50
+ const resourceTitle = useResourceLabel(resource, "singular", identifier);
51
+ const title =
52
+ titleFromProps ??
53
+ translate(
54
+ "views.edit.title",
55
+ { resource: resourceTitle },
56
+ `Edit ${resourceTitle}`
57
+ );
58
+ const description = translate(
59
+ "views.edit.description",
60
+ { resource: resourceTitle.toLocaleLowerCase() },
61
+ `Update this ${resourceTitle.toLocaleLowerCase()} while NocoBase keeps the data consistent.`
57
62
  );
58
- const title = titleFromProps ?? `Edit ${resourceTitle}`;
59
63
 
60
64
  return (
61
65
  <div className={cn("flex flex-col", "gap-3", wrapperClassName)}>
@@ -74,6 +78,7 @@ export const EditViewHeader = ({
74
78
  size="icon"
75
79
  className="mt-0.5 rounded-lg"
76
80
  onClick={back}
81
+ aria-label={translate("buttons.cancel", "Cancel")}
77
82
  >
78
83
  <ArrowLeftIcon className="h-4 w-4" />
79
84
  </Button>
@@ -81,10 +86,7 @@ export const EditViewHeader = ({
81
86
  <h2 className="text-3xl font-semibold tracking-[-0.035em]">
82
87
  {title}
83
88
  </h2>
84
- <p className="mt-2 text-sm text-muted-foreground">
85
- Update this {resourceTitle.toLowerCase()} while NocoBase keeps
86
- the data consistent.
87
- </p>
89
+ <p className="mt-2 text-sm text-muted-foreground">{description}</p>
88
90
  </div>
89
91
  </div>
90
92
 
@@ -2,9 +2,11 @@
2
2
 
3
3
  import type { PropsWithChildren } from "react";
4
4
 
5
- import { useResourceParams, useUserFriendlyName } from "@refinedev/core";
5
+ import { useResourceParams, useTranslate } from "@refinedev/core";
6
6
  import { Breadcrumb } from "@/components/app-shell/breadcrumb";
7
7
  import { CreateButton } from "@/components/resources/buttons/create";
8
+ import { useResourceLabel } from "@/components/resources/resource-label";
9
+ import { resolveTranslatableText, type TranslationOptions } from "@/lib/i18n";
8
10
  import { cn } from "@/lib/utils";
9
11
 
10
12
  type ListViewProps = PropsWithChildren<{
@@ -35,22 +37,30 @@ export const ListViewHeader = ({
35
37
  wrapperClassName,
36
38
  headerClassName,
37
39
  }: ListHeaderProps) => {
38
- const getUserFriendlyName = useUserFriendlyName();
39
-
40
40
  const { resource, identifier } = useResourceParams({
41
41
  resource: resourceFromProps,
42
42
  });
43
+ const translate = useTranslate();
43
44
  const resourceName = identifier ?? resource?.name;
44
45
 
45
46
  const isCreateButtonVisible = canCreate ?? !!resource?.create;
46
47
 
47
- const title =
48
- titleFromProps ??
49
- getUserFriendlyName(
50
- resource?.meta?.label ?? identifier ?? resource?.name,
51
- "plural"
52
- );
53
- const description = resource?.meta?.description as string | undefined;
48
+ const resourceTitle = useResourceLabel(resource, "plural", identifier);
49
+ const title = titleFromProps ?? resourceTitle;
50
+ const meta = resource?.meta as
51
+ | {
52
+ description?: string;
53
+ descriptionI18nKey?: string;
54
+ i18nOptions?: TranslationOptions;
55
+ }
56
+ | undefined;
57
+ const description = meta?.descriptionI18nKey
58
+ ? translate(
59
+ meta.descriptionI18nKey,
60
+ meta.i18nOptions,
61
+ meta.description ?? meta.descriptionI18nKey
62
+ )
63
+ : resolveTranslatableText(meta?.description);
54
64
 
55
65
  return (
56
66
  <div className={cn("flex flex-col", "gap-3", wrapperClassName)}>
@@ -3,25 +3,29 @@
3
3
  import type { PropsWithChildren } from "react";
4
4
 
5
5
  import { ArrowLeftIcon } from "lucide-react";
6
- import {
7
- useBack,
8
- useResourceParams,
9
- useUserFriendlyName,
10
- } from "@refinedev/core";
6
+ import { useBack, useResourceParams, useTranslate } from "@refinedev/core";
11
7
  import { Breadcrumb } from "@/components/app-shell/breadcrumb";
12
8
  import { Button } from "@/components/ui/button";
13
9
  import { RefreshButton } from "@/components/resources/buttons/refresh";
14
10
  import { cn } from "@/lib/utils";
15
11
  import { EditButton } from "../buttons/edit";
12
+ import { useResourceLabel } from "@/components/resources/resource-label";
16
13
 
17
14
  type ShowViewProps = PropsWithChildren<{
18
15
  className?: string;
16
+ resource?: string;
17
+ title?: string;
19
18
  }>;
20
19
 
21
- export function ShowView({ children, className }: ShowViewProps) {
20
+ export function ShowView({
21
+ children,
22
+ className,
23
+ resource,
24
+ title,
25
+ }: ShowViewProps) {
22
26
  return (
23
27
  <div className={cn("flex flex-col", "gap-6", className)}>
24
- <ShowViewHeader />
28
+ <ShowViewHeader resource={resource} title={title} />
25
29
  {children}
26
30
  </div>
27
31
  );
@@ -41,8 +45,7 @@ export const ShowViewHeader = ({
41
45
  headerClassName,
42
46
  }: ShowViewHeaderProps) => {
43
47
  const back = useBack();
44
-
45
- const getUserFriendlyName = useUserFriendlyName();
48
+ const translate = useTranslate();
46
49
 
47
50
  const { resource, identifier } = useResourceParams({
48
51
  resource: resourceFromProps,
@@ -51,12 +54,12 @@ export const ShowViewHeader = ({
51
54
 
52
55
  const resourceName = resource?.name ?? identifier;
53
56
 
54
- const title =
55
- titleFromProps ??
56
- getUserFriendlyName(
57
- resource?.meta?.label ?? identifier ?? resource?.name,
58
- "singular"
59
- );
57
+ const resourceTitle = useResourceLabel(resource, "singular", identifier);
58
+ const title = titleFromProps ?? resourceTitle;
59
+ const description = translate(
60
+ "views.show.description",
61
+ "Review the record and its NocoBase-managed data."
62
+ );
60
63
 
61
64
  return (
62
65
  <div className={cn("flex flex-col", "gap-3", wrapperClassName)}>
@@ -75,6 +78,7 @@ export const ShowViewHeader = ({
75
78
  size="icon"
76
79
  className="mt-0.5 rounded-lg"
77
80
  onClick={back}
81
+ aria-label={translate("buttons.cancel", "Cancel")}
78
82
  >
79
83
  <ArrowLeftIcon className="h-4 w-4" />
80
84
  </Button>
@@ -82,9 +86,7 @@ export const ShowViewHeader = ({
82
86
  <h2 className="text-3xl font-semibold tracking-[-0.035em]">
83
87
  {title}
84
88
  </h2>
85
- <p className="mt-2 text-sm text-muted-foreground">
86
- Review the record and its NocoBase-managed data.
87
- </p>
89
+ <p className="mt-2 text-sm text-muted-foreground">{description}</p>
88
90
  </div>
89
91
  </div>
90
92
 
package/src/index.tsx CHANGED
@@ -1,6 +1,7 @@
1
1
  import React from "react";
2
2
  import { createRoot } from "react-dom/client";
3
3
 
4
+ import "./locales";
4
5
  import App from "./App";
5
6
 
6
7
  const container = document.getElementById("root") as HTMLElement;
@@ -12,6 +13,4 @@ const root = createRoot(container);
12
13
  // </React.StrictMode>
13
14
  // );
14
15
 
15
- root.render(
16
- <App />
17
- );
16
+ root.render(<App />);
@@ -0,0 +1,161 @@
1
+ export type TranslationOptions = Record<string, unknown> & {
2
+ defaultValue?: string;
3
+ ns?: string | string[];
4
+ };
5
+
6
+ export type TranslationResolver = (
7
+ key: string,
8
+ options?: TranslationOptions,
9
+ defaultMessage?: string
10
+ ) => string;
11
+
12
+ export type TranslationExpression = {
13
+ key: string;
14
+ options?: TranslationOptions;
15
+ };
16
+
17
+ export type TranslationResource = Record<string, string | number | boolean>;
18
+
19
+ export type TranslationResourceBundle = Record<string, TranslationResource>;
20
+
21
+ type TranslationResourceListener = (
22
+ namespace: string,
23
+ resources: TranslationResourceBundle
24
+ ) => void;
25
+
26
+ let translationResolver: TranslationResolver | undefined;
27
+ const translationResources = new Map<string, TranslationResourceBundle>();
28
+ const translationResourceListeners = new Set<TranslationResourceListener>();
29
+
30
+ const quotedValuePattern = `(?:"(?:\\\\.|[^"\\\\])*"|'(?:\\\\.|[^'\\\\])*')`;
31
+ const translationExpressionPattern = new RegExp(
32
+ `^\\s*\\{\\{\\s*t\\(\\s*(${quotedValuePattern})\\s*(?:,\\s*(\\{[\\s\\S]*\\}))?\\s*\\)\\s*\\}\\}\\s*$`
33
+ );
34
+
35
+ function decodeQuotedValue(value: string) {
36
+ if (value.startsWith('"')) {
37
+ try {
38
+ return JSON.parse(value) as string;
39
+ } catch {
40
+ return value.slice(1, -1);
41
+ }
42
+ }
43
+
44
+ return value
45
+ .slice(1, -1)
46
+ .replace(/\\'/g, "'")
47
+ .replace(/\\"/g, '"')
48
+ .replace(/\\n/g, "\n")
49
+ .replace(/\\r/g, "\r")
50
+ .replace(/\\t/g, "\t")
51
+ .replace(/\\\\/g, "\\");
52
+ }
53
+
54
+ function parsePrimitive(value: string): unknown {
55
+ const normalized = value.trim();
56
+ if (normalized.startsWith('"') || normalized.startsWith("'")) {
57
+ return decodeQuotedValue(normalized);
58
+ }
59
+ if (normalized === "true") return true;
60
+ if (normalized === "false") return false;
61
+ if (normalized === "null") return null;
62
+ if (/^-?\d+(?:\.\d+)?$/.test(normalized)) return Number(normalized);
63
+ return undefined;
64
+ }
65
+
66
+ function parseOptions(source?: string): TranslationOptions | undefined {
67
+ if (!source) return undefined;
68
+
69
+ const options: TranslationOptions = {};
70
+ const propertyPattern = new RegExp(
71
+ `([A-Za-z_$][\\w$]*)\\s*:\\s*(${quotedValuePattern}|-?\\d+(?:\\.\\d+)?|true|false|null)`,
72
+ "g"
73
+ );
74
+
75
+ for (const match of source.matchAll(propertyPattern)) {
76
+ const value = parsePrimitive(match[2]);
77
+ if (typeof value !== "undefined") options[match[1]] = value;
78
+ }
79
+
80
+ const namespaceArray = source.match(/\bns\s*:\s*\[([^\]]*)\]/);
81
+ if (namespaceArray) {
82
+ const namespaces = [
83
+ ...namespaceArray[1].matchAll(new RegExp(quotedValuePattern, "g")),
84
+ ].map((match) => decodeQuotedValue(match[0]));
85
+ if (namespaces.length) options.ns = namespaces;
86
+ }
87
+
88
+ return Object.keys(options).length ? options : undefined;
89
+ }
90
+
91
+ export function parseTranslationExpression(
92
+ value: string
93
+ ): TranslationExpression | undefined {
94
+ const match = translationExpressionPattern.exec(value);
95
+ if (!match) return undefined;
96
+
97
+ return {
98
+ key: decodeQuotedValue(match[1]),
99
+ options: parseOptions(match[2]),
100
+ };
101
+ }
102
+
103
+ export function setTranslationResolver(resolver?: TranslationResolver) {
104
+ translationResolver = resolver;
105
+ return () => {
106
+ if (translationResolver === resolver) translationResolver = undefined;
107
+ };
108
+ }
109
+
110
+ export function registerTranslationResources(
111
+ namespace: string,
112
+ resources: TranslationResourceBundle
113
+ ) {
114
+ const current = translationResources.get(namespace) ?? {};
115
+ const merged = Object.fromEntries(
116
+ [...new Set([...Object.keys(current), ...Object.keys(resources)])].map(
117
+ (locale) => [
118
+ locale,
119
+ {
120
+ ...(current[locale] ?? {}),
121
+ ...(resources[locale] ?? {}),
122
+ },
123
+ ]
124
+ )
125
+ );
126
+
127
+ translationResources.set(namespace, merged);
128
+ translationResourceListeners.forEach((listener) =>
129
+ listener(namespace, resources)
130
+ );
131
+ }
132
+
133
+ export function getTranslationResources() {
134
+ return [...translationResources.entries()];
135
+ }
136
+
137
+ export function subscribeTranslationResources(
138
+ listener: TranslationResourceListener
139
+ ) {
140
+ translationResourceListeners.add(listener);
141
+ return () => translationResourceListeners.delete(listener);
142
+ }
143
+
144
+ export function resolveTranslatableText(
145
+ value: unknown,
146
+ options?: TranslationOptions
147
+ ): string {
148
+ if (typeof value !== "string") return value == null ? "" : String(value);
149
+
150
+ const expression = parseTranslationExpression(value);
151
+ if (!expression) {
152
+ return translationResolver && options
153
+ ? translationResolver(value, options, value)
154
+ : value;
155
+ }
156
+
157
+ const defaultMessage = expression.options?.defaultValue ?? expression.key;
158
+ return translationResolver
159
+ ? translationResolver(expression.key, expression.options, defaultMessage)
160
+ : defaultMessage;
161
+ }
@@ -2,6 +2,7 @@ import {
2
2
  API_ORIGIN,
3
3
  API_URL,
4
4
  NOCOBASE_AUTHENTICATOR,
5
+ NOCOBASE_LOCALE_KEY,
5
6
  NOCOBASE_ROLE_KEY,
6
7
  NOCOBASE_TOKEN_KEY,
7
8
  } from "@/providers/constants";
@@ -30,7 +31,7 @@ export type NocoBaseRequestOptions = {
30
31
  unwrap?: "data" | "deep-data" | "none";
31
32
  };
32
33
 
33
- const getClientLocale = () =>
34
+ const getBrowserLocale = () =>
34
35
  typeof navigator === "undefined" ? undefined : navigator.language;
35
36
 
36
37
  const getClientTimezone = () => {
@@ -109,6 +110,22 @@ export class NocoBaseClient {
109
110
  else localStorage.removeItem(NOCOBASE_ROLE_KEY);
110
111
  }
111
112
 
113
+ getStoredLocale() {
114
+ return typeof localStorage === "undefined"
115
+ ? undefined
116
+ : localStorage.getItem(NOCOBASE_LOCALE_KEY) ?? undefined;
117
+ }
118
+
119
+ getLocale() {
120
+ return this.getStoredLocale() ?? getBrowserLocale();
121
+ }
122
+
123
+ setLocale(locale?: string | null) {
124
+ if (typeof localStorage === "undefined") return;
125
+ if (locale) localStorage.setItem(NOCOBASE_LOCALE_KEY, locale);
126
+ else localStorage.removeItem(NOCOBASE_LOCALE_KEY);
127
+ }
128
+
112
129
  buildUrl(endpoint: string, query?: Record<string, QueryValue>) {
113
130
  const base = `${this.apiUrl.replace(/\/$/, "")}/${endpoint.replace(
114
131
  /^\//,
@@ -149,7 +166,7 @@ export class NocoBaseClient {
149
166
  | "accept"
150
167
  | "body"
151
168
  > = {}) {
152
- const locale = getClientLocale();
169
+ const locale = this.getLocale();
153
170
  const formData =
154
171
  typeof FormData !== "undefined" && body instanceof FormData;
155
172
  return {
@@ -0,0 +1,82 @@
1
+ export const starter = {
2
+ "buttons.create": "Create",
3
+ "buttons.edit": "Edit",
4
+ "buttons.show": "View",
5
+ "buttons.delete": "Delete",
6
+ "buttons.refresh": "Refresh",
7
+ "buttons.cancel": "Cancel",
8
+ "buttons.confirm": "Are you sure?",
9
+ "views.create.title": "Create {{resource}}",
10
+ "views.create.description":
11
+ "Add a new {{resource}} to your NocoBase workspace.",
12
+ "views.edit.title": "Edit {{resource}}",
13
+ "views.edit.description":
14
+ "Update this {{resource}} while NocoBase keeps the data consistent.",
15
+ "views.show.description": "Review the record and its NocoBase-managed data.",
16
+ "table.empty.title": "No data to display",
17
+ "table.empty.description": "This table is empty for the time being.",
18
+ "table.pagination.total": "{{count}} row(s)",
19
+ "table.pagination.pageSize": "Rows per page",
20
+ "table.pagination.page": "Page {{current}} of {{total}}",
21
+ "table.pagination.first": "Go to first page",
22
+ "table.pagination.previous": "Go to previous page",
23
+ "table.pagination.next": "Go to next page",
24
+ "table.pagination.last": "Go to last page",
25
+ "notifications.success": "Success",
26
+ "notifications.createSuccess": "Successfully created {{resource}}",
27
+ "notifications.editSuccess": "Successfully updated {{resource}}",
28
+ "notifications.deleteSuccess": "Successfully deleted {{resource}}",
29
+ "users.users": "User",
30
+ Admin: "Admin",
31
+ Root: "Root",
32
+ Member: "Member",
33
+ Anonymous: "Anonymous",
34
+ "Full permissions": "Full permissions",
35
+ } as const;
36
+
37
+ export const app = {
38
+ "resources.users": "Users",
39
+ "resources.user": "User",
40
+ "resources.users.description":
41
+ "Manage the people who can sign in and work in this NocoBase application.",
42
+ "users.fields.id": "ID",
43
+ "users.fields.nickname": "Nickname",
44
+ "users.fields.username": "Username",
45
+ "users.fields.email": "Email",
46
+ "users.fields.phone": "Phone",
47
+ "users.fields.password": "Password",
48
+ "users.fields.roles": "Roles",
49
+ "users.fields.createdAt": "Created at",
50
+ "users.fields.updatedAt": "Updated at",
51
+ "users.fields.actions": "Actions",
52
+ "users.actions.view": "View user",
53
+ "users.actions.edit": "Edit user",
54
+ "users.actions.delete": "Delete user",
55
+ "users.form.create.submit": "Create user",
56
+ "users.form.create.submitting": "Creating...",
57
+ "users.form.edit.submit": "Save changes",
58
+ "users.form.edit.submitting": "Saving...",
59
+ "users.form.cancel": "Cancel",
60
+ "users.form.nickname.placeholder": "Enter a display name",
61
+ "users.form.username.placeholder": "Enter a unique username",
62
+ "users.form.email.placeholder": "Enter an email address",
63
+ "users.form.phone.placeholder": "Enter a phone number",
64
+ "users.form.password.placeholder": "Set an initial password",
65
+ "users.form.password.aiNotice":
66
+ "Passwords are never included in AI page context or Form filler.",
67
+ "users.validation.username": "Username is required",
68
+ "users.validation.email": "Email is required",
69
+ "users.validation.emailFormat": "Enter a valid email address",
70
+ "users.validation.phone": "Phone is required",
71
+ "users.validation.password": "Password is required",
72
+ "users.detail.identity": "Identity",
73
+ "users.detail.contact": "Contact",
74
+ "users.detail.access": "Access",
75
+ "users.detail.timestamps": "Timestamps",
76
+ "users.detail.noRoles": "No assigned roles",
77
+ "users.detail.unnamed": "Unnamed user",
78
+ "users.ai.table": "Users table",
79
+ "users.ai.detail": "User details",
80
+ "users.ai.createForm": "Create user form",
81
+ "users.ai.editForm": "Edit user form",
82
+ } as const;
@@ -0,0 +1,13 @@
1
+ import { registerTranslationResources } from "@/lib/i18n";
2
+ import { app as enUSApp, starter as enUSStarter } from "./en-US";
3
+ import { app as zhCNApp, starter as zhCNStarter } from "./zh-CN";
4
+
5
+ registerTranslationResources("starter", {
6
+ "en-US": enUSStarter,
7
+ "zh-CN": zhCNStarter,
8
+ });
9
+
10
+ registerTranslationResources("app", {
11
+ "en-US": enUSApp,
12
+ "zh-CN": zhCNApp,
13
+ });
@@ -0,0 +1,79 @@
1
+ export const starter = {
2
+ "buttons.create": "新建",
3
+ "buttons.edit": "编辑",
4
+ "buttons.show": "查看",
5
+ "buttons.delete": "删除",
6
+ "buttons.refresh": "刷新",
7
+ "buttons.cancel": "取消",
8
+ "buttons.confirm": "确定要执行此操作吗?",
9
+ "views.create.title": "新建{{resource}}",
10
+ "views.create.description": "在 NocoBase 工作区中添加新的{{resource}}。",
11
+ "views.edit.title": "编辑{{resource}}",
12
+ "views.edit.description": "更新此{{resource}},数据一致性由 NocoBase 保障。",
13
+ "views.show.description": "查看记录及其由 NocoBase 管理的数据。",
14
+ "table.empty.title": "暂无数据",
15
+ "table.empty.description": "当前表格中没有可显示的数据。",
16
+ "table.pagination.total": "共 {{count}} 条",
17
+ "table.pagination.pageSize": "每页行数",
18
+ "table.pagination.page": "第 {{current}} 页,共 {{total}} 页",
19
+ "table.pagination.first": "前往第一页",
20
+ "table.pagination.previous": "前往上一页",
21
+ "table.pagination.next": "前往下一页",
22
+ "table.pagination.last": "前往最后一页",
23
+ "notifications.success": "操作成功",
24
+ "notifications.createSuccess": "已成功创建{{resource}}",
25
+ "notifications.editSuccess": "已成功更新{{resource}}",
26
+ "notifications.deleteSuccess": "已成功删除{{resource}}",
27
+ "users.users": "用户",
28
+ Admin: "管理员",
29
+ Root: "超级管理员",
30
+ Member: "成员",
31
+ Anonymous: "匿名用户",
32
+ "Full permissions": "全部权限",
33
+ } as const;
34
+
35
+ export const app = {
36
+ "resources.users": "用户",
37
+ "resources.user": "用户",
38
+ "resources.users.description": "管理可以登录并使用此 NocoBase 应用的用户。",
39
+ "users.fields.id": "ID",
40
+ "users.fields.nickname": "昵称",
41
+ "users.fields.username": "用户名",
42
+ "users.fields.email": "邮箱",
43
+ "users.fields.phone": "手机号",
44
+ "users.fields.password": "密码",
45
+ "users.fields.roles": "角色",
46
+ "users.fields.createdAt": "创建时间",
47
+ "users.fields.updatedAt": "更新时间",
48
+ "users.fields.actions": "操作",
49
+ "users.actions.view": "查看用户",
50
+ "users.actions.edit": "编辑用户",
51
+ "users.actions.delete": "删除用户",
52
+ "users.form.create.submit": "创建用户",
53
+ "users.form.create.submitting": "正在创建...",
54
+ "users.form.edit.submit": "保存修改",
55
+ "users.form.edit.submitting": "正在保存...",
56
+ "users.form.cancel": "取消",
57
+ "users.form.nickname.placeholder": "输入显示名称",
58
+ "users.form.username.placeholder": "输入唯一用户名",
59
+ "users.form.email.placeholder": "输入邮箱地址",
60
+ "users.form.phone.placeholder": "输入手机号",
61
+ "users.form.password.placeholder": "设置初始密码",
62
+ "users.form.password.aiNotice":
63
+ "密码不会包含在 AI 页面上下文中,Form filler 也无法填写密码。",
64
+ "users.validation.username": "请输入用户名",
65
+ "users.validation.email": "请输入邮箱",
66
+ "users.validation.emailFormat": "请输入有效的邮箱地址",
67
+ "users.validation.phone": "请输入手机号",
68
+ "users.validation.password": "请输入密码",
69
+ "users.detail.identity": "身份信息",
70
+ "users.detail.contact": "联系方式",
71
+ "users.detail.access": "访问权限",
72
+ "users.detail.timestamps": "时间信息",
73
+ "users.detail.noRoles": "尚未分配角色",
74
+ "users.detail.unnamed": "未命名用户",
75
+ "users.ai.table": "用户表格",
76
+ "users.ai.detail": "用户详情",
77
+ "users.ai.createForm": "新建用户表单",
78
+ "users.ai.editForm": "编辑用户表单",
79
+ } as const;