create-cundi-app 1.0.13 → 1.0.16

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/index.js CHANGED
@@ -44,6 +44,14 @@ async function main() {
44
44
  projectName = response.projectName;
45
45
  }
46
46
 
47
+ // Ask if samples should be included
48
+ const { includeSamples } = await prompts({
49
+ type: "confirm",
50
+ name: "includeSamples",
51
+ message: "Include sample pages? (Article, Product, BasicTypeTest, TiptapTest, DrawioTest)",
52
+ initial: false,
53
+ });
54
+
47
55
  const targetDir = path.resolve(process.cwd(), projectName);
48
56
 
49
57
  // Check if directory already exists
@@ -65,6 +73,9 @@ async function main() {
65
73
 
66
74
  console.log();
67
75
  console.log(kleur.blue(`Creating project in ${kleur.bold(targetDir)}...`));
76
+ if (includeSamples) {
77
+ console.log(kleur.gray("Including sample pages..."));
78
+ }
68
79
  console.log();
69
80
 
70
81
  // Copy template files
@@ -73,12 +84,39 @@ async function main() {
73
84
  try {
74
85
  await fs.copy(templateDir, targetDir, {
75
86
  filter: (src) => {
76
- // Skip node_modules and dist if they exist
77
87
  const relativePath = path.relative(templateDir, src);
78
- return !relativePath.includes("node_modules") && !relativePath.includes("dist");
88
+ // Skip node_modules and dist if they exist
89
+ if (relativePath.includes("node_modules") || relativePath.includes("dist")) {
90
+ return false;
91
+ }
92
+ // Skip samples if not requested
93
+ if (!includeSamples && relativePath.includes("pages/samples")) {
94
+ return false;
95
+ }
96
+ // Skip the alternative version files (they will be handled separately)
97
+ if (relativePath.endsWith(".no-samples.tsx") || relativePath.endsWith(".no-samples.ts")) {
98
+ return false;
99
+ }
100
+ return true;
79
101
  },
80
102
  });
81
103
 
104
+ // Handle App.tsx and i18n.ts based on sample choice
105
+ if (!includeSamples) {
106
+ // If not including samples, use the no-samples versions
107
+ const appNoSamplesPath = path.join(templateDir, "src/App.no-samples.tsx");
108
+ const appPath = path.join(targetDir, "src/App.tsx");
109
+ if (fs.existsSync(appNoSamplesPath)) {
110
+ await fs.copy(appNoSamplesPath, appPath, { overwrite: true });
111
+ }
112
+
113
+ const i18nNoSamplesPath = path.join(templateDir, "src/i18n.no-samples.ts");
114
+ const i18nPath = path.join(targetDir, "src/i18n.ts");
115
+ if (fs.existsSync(i18nNoSamplesPath)) {
116
+ await fs.copy(i18nNoSamplesPath, i18nPath, { overwrite: true });
117
+ }
118
+ }
119
+
82
120
  // Generate package.json with project name
83
121
  const pkgTemplatePath = path.join(targetDir, "package.json.template");
84
122
  const pkgTargetPath = path.join(targetDir, "package.json");
@@ -96,7 +134,7 @@ async function main() {
96
134
  await fs.writeFile(
97
135
  envExamplePath,
98
136
  `# API Configuration
99
- VITE_API_URL=http://localhost:5000/api
137
+ VITE_API_URL=http://localhost:5000
100
138
 
101
139
  # Keycloak Configuration (Optional)
102
140
  VITE_KEYCLOAK_URL=http://localhost:8080
@@ -129,6 +167,10 @@ VITE_REDIRECT_URI=http://localhost:5173/auth/callback
129
167
  // console.log(` ${kleur.gray("$")} cp .env.example .env ${kleur.gray("# Configure your environment")}`);
130
168
  console.log(` ${kleur.gray("$")} npm run dev`);
131
169
  console.log();
170
+ if (includeSamples) {
171
+ console.log(kleur.yellow("Note: Sample pages are included. Make sure your backend has the corresponding Business Objects."));
172
+ console.log();
173
+ }
132
174
  console.log(kleur.gray("────────────────────────────────────"));
133
175
  console.log(kleur.blue("Happy coding! 🎉"));
134
176
  console.log();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-cundi-app",
3
- "version": "1.0.13",
3
+ "version": "1.0.16",
4
4
  "description": "Create a new Cundi app with React + Refine + Ant Design",
5
5
  "type": "module",
6
6
  "bin": {
@@ -11,7 +11,7 @@
11
11
  "template"
12
12
  ],
13
13
  "scripts": {
14
- "pack": "npm pack --pack-destination ../../output/npm"
14
+ "pack": "node sync-version.js && npm pack --pack-destination ../../output/npm"
15
15
  },
16
16
  "keywords": [
17
17
  "create",
@@ -1,5 +1,5 @@
1
1
  # API Configuration
2
- VITE_API_URL=http://localhost:5000/api
2
+ VITE_API_URL=http://localhost:5000
3
3
 
4
4
  # Keycloak Configuration (Optional)
5
5
  VITE_KEYCLOAK_URL=http://localhost:8080
@@ -24,7 +24,7 @@
24
24
  "dependencies": {
25
25
  "@ant-design/icons": "^5.5.1",
26
26
  "@ant-design/v5-patch-for-react-19": "^1.0.3",
27
- "@cundi/refine-xaf": "^1.0.11",
27
+ "@cundi/refine-xaf": "^1.0.14",
28
28
  "@refinedev/antd": "^6.0.3",
29
29
  "@refinedev/cli": "^2.16.50",
30
30
  "@refinedev/core": "^5.0.6",
@@ -0,0 +1,249 @@
1
+ import {
2
+ Refine,
3
+ Authenticated,
4
+ } from "@refinedev/core";
5
+ import {
6
+ useNotificationProvider,
7
+ ThemedLayout,
8
+ ErrorComponent,
9
+ } from "@refinedev/antd";
10
+ import {
11
+ DashboardOutlined,
12
+ UserOutlined,
13
+ SettingOutlined,
14
+ TeamOutlined,
15
+ FieldTimeOutlined,
16
+ ThunderboltOutlined,
17
+ CopyOutlined,
18
+ } from "@ant-design/icons";
19
+ import routerProvider, {
20
+ NavigateToResource,
21
+ CatchAllNavigate,
22
+ UnsavedChangesNotifier,
23
+ DocumentTitleHandler,
24
+ } from "@refinedev/react-router";
25
+ import { BrowserRouter, Routes, Route, Outlet } from "react-router";
26
+ import { App as AntdApp } from "antd";
27
+ import { useTranslation } from "react-i18next";
28
+ import "./i18n";
29
+
30
+ import "@ant-design/v5-patch-for-react-19";
31
+ import "@refinedev/antd/dist/reset.css";
32
+
33
+ import { DashboardPage } from "./pages/dashboard";
34
+ import {
35
+ authProvider,
36
+ dataProvider,
37
+ Header,
38
+ LoginPage,
39
+ KeycloakLoginPage,
40
+ AuthCallback,
41
+ ApplicationUserList,
42
+ ApplicationUserCreate,
43
+ ApplicationUserEdit,
44
+ RoleList,
45
+ RoleCreate,
46
+ RoleEdit,
47
+ ColorModeContextProvider,
48
+ BackgroundJobList,
49
+ TriggerRuleList,
50
+ TriggerRuleCreate,
51
+ TriggerRuleEdit,
52
+ TriggerLogList,
53
+ MirrorTypeMappingConfigList,
54
+ MirrorTypeMappingConfigCreate,
55
+ MirrorTypeMappingConfigEdit,
56
+ } from "@cundi/refine-xaf";
57
+
58
+ import { accessControlProvider } from "./accessControlProvider";
59
+
60
+ const API_URL = import.meta.env.VITE_API_URL + "/api/odata";
61
+
62
+ const InnerApp: React.FC = () => {
63
+ const { t, i18n } = useTranslation();
64
+
65
+ const i18nProvider = {
66
+ translate: (key: string, params: any) => t(key, params) as string,
67
+ changeLocale: (lang: string) => i18n.changeLanguage(lang),
68
+ getLocale: () => i18n.language,
69
+ };
70
+
71
+ return (
72
+ <BrowserRouter>
73
+ <AntdApp>
74
+ <Refine
75
+ authProvider={authProvider}
76
+ accessControlProvider={accessControlProvider}
77
+ dataProvider={{ default: dataProvider(API_URL) }}
78
+ i18nProvider={i18nProvider}
79
+ routerProvider={routerProvider}
80
+ resources={[
81
+ {
82
+ name: "dashboard",
83
+ list: "/",
84
+ meta: {
85
+ label: t("sider.dashboard"),
86
+ icon: <DashboardOutlined />,
87
+ },
88
+ },
89
+ {
90
+ name: "ApplicationUser",
91
+ list: "/ApplicationUsers",
92
+ create: "/ApplicationUsers/create",
93
+ edit: "/ApplicationUsers/edit/:id",
94
+ meta: {
95
+ label: t("sider.users"),
96
+ icon: <UserOutlined />,
97
+ parent: "Settings",
98
+ },
99
+ },
100
+ {
101
+ name: "PermissionPolicyRole",
102
+ list: "/PermissionPolicyRoles",
103
+ create: "/PermissionPolicyRoles/create",
104
+ edit: "/PermissionPolicyRoles/edit/:id",
105
+ meta: {
106
+ label: t("sider.roles"),
107
+ parent: "Settings",
108
+ icon: <TeamOutlined />,
109
+ },
110
+ },
111
+ {
112
+ name: "BackgroundJobs",
113
+ list: "/background-jobs",
114
+ meta: {
115
+ label: t("sider.backgroundJobs"),
116
+ icon: <FieldTimeOutlined />,
117
+ parent: "Settings",
118
+ },
119
+ },
120
+ {
121
+ name: "TriggerRule",
122
+ list: "/TriggerRules",
123
+ create: "/TriggerRules/create",
124
+ edit: "/TriggerRules/edit/:id",
125
+ meta: {
126
+ label: t("sider.triggerRules"),
127
+ parent: "Settings",
128
+ icon: <ThunderboltOutlined />,
129
+ },
130
+ },
131
+ {
132
+ name: "TriggerLog",
133
+ list: "/TriggerLogs",
134
+ meta: {
135
+ label: t("sider.triggerLogs"),
136
+ parent: "Settings",
137
+ icon: <ThunderboltOutlined />,
138
+ },
139
+ },
140
+ {
141
+ name: "MirrorTypeMappingConfig",
142
+ list: "/MirrorTypeMappingConfigs",
143
+ create: "/MirrorTypeMappingConfigs/create",
144
+ edit: "/MirrorTypeMappingConfigs/edit/:id",
145
+ meta: {
146
+ label: t("sider.mirrorConfigs"),
147
+ parent: "Settings",
148
+ icon: <CopyOutlined />,
149
+ },
150
+ },
151
+ {
152
+ name: "Settings",
153
+ meta: {
154
+ label: t("sider.settings"),
155
+ icon: <SettingOutlined />,
156
+ },
157
+ },
158
+ ]}
159
+ notificationProvider={useNotificationProvider}
160
+ options={{
161
+ syncWithLocation: true,
162
+ warnWhenUnsavedChanges: true,
163
+ }}
164
+ >
165
+ <Routes>
166
+ <Route
167
+ element={
168
+ <Authenticated
169
+ key="authenticated-routes"
170
+ fallback={<CatchAllNavigate to="/login" />}
171
+ >
172
+ <ThemedLayout Header={Header}>
173
+ <Outlet />
174
+ </ThemedLayout>
175
+ </Authenticated>
176
+ }
177
+ >
178
+ <Route index element={<DashboardPage />} />
179
+
180
+ <Route path="/ApplicationUsers">
181
+ <Route index element={<ApplicationUserList />} />
182
+ <Route path="create" element={<ApplicationUserCreate />} />
183
+ <Route path="edit/:id" element={<ApplicationUserEdit />} />
184
+ </Route>
185
+
186
+ <Route path="/PermissionPolicyRoles">
187
+ <Route index element={<RoleList />} />
188
+ <Route path="create" element={<RoleCreate />} />
189
+ <Route path="edit/:id" element={<RoleEdit />} />
190
+ </Route>
191
+
192
+ <Route path="/background-jobs" element={<BackgroundJobList title={t("sider.backgroundJobs")} />} />
193
+
194
+ <Route path="/TriggerRules">
195
+ <Route index element={<TriggerRuleList />} />
196
+ <Route path="create" element={<TriggerRuleCreate />} />
197
+ <Route path="edit/:id" element={<TriggerRuleEdit />} />
198
+ </Route>
199
+ <Route path="/TriggerLogs">
200
+ <Route index element={<TriggerLogList />} />
201
+ </Route>
202
+ <Route path="/MirrorTypeMappingConfigs">
203
+ <Route index element={<MirrorTypeMappingConfigList />} />
204
+ <Route path="create" element={<MirrorTypeMappingConfigCreate />} />
205
+ <Route path="edit/:id" element={<MirrorTypeMappingConfigEdit />} />
206
+ </Route>
207
+ </Route>
208
+
209
+ <Route
210
+ element={
211
+ <Authenticated key="auth-pages" fallback={<Outlet />}>
212
+ <NavigateToResource resource="dashboard" />
213
+ </Authenticated>
214
+ }
215
+ >
216
+ <Route path="/login" element={<KeycloakLoginPage />} />
217
+ <Route path="/login/api" element={<LoginPage />} />
218
+ <Route path="/auth/callback" element={<AuthCallback />} />
219
+ </Route>
220
+
221
+ <Route
222
+ element={
223
+ <Authenticated key="catch-all">
224
+ <ThemedLayout Header={Header}>
225
+ <Outlet />
226
+ </ThemedLayout>
227
+ </Authenticated>
228
+ }
229
+ >
230
+ <Route path="*" element={<ErrorComponent />} />
231
+ </Route>
232
+ </Routes>
233
+ <UnsavedChangesNotifier />
234
+ <DocumentTitleHandler />
235
+ </Refine>
236
+ </AntdApp>
237
+ </BrowserRouter>
238
+ );
239
+ };
240
+
241
+ const App: React.FC = () => {
242
+ return (
243
+ <ColorModeContextProvider>
244
+ <InnerApp />
245
+ </ColorModeContextProvider>
246
+ );
247
+ };
248
+
249
+ export default App;
@@ -15,6 +15,7 @@ import {
15
15
  FieldTimeOutlined,
16
16
  ThunderboltOutlined,
17
17
  CopyOutlined,
18
+ AppstoreOutlined,
18
19
  } from "@ant-design/icons";
19
20
  import routerProvider, {
20
21
  NavigateToResource,
@@ -57,7 +58,26 @@ import {
57
58
 
58
59
  import { accessControlProvider } from "./accessControlProvider";
59
60
 
60
- const API_URL = import.meta.env.VITE_API_URL + "/odata";
61
+ // Sample pages
62
+ import { ArticleList } from "./pages/samples/article/list";
63
+ import { ArticleCreate } from "./pages/samples/article/create";
64
+ import { ArticleEdit } from "./pages/samples/article/edit";
65
+ import { ProductList } from "./pages/samples/product/list";
66
+ import { ProductCreate } from "./pages/samples/product/create";
67
+ import { ProductEdit } from "./pages/samples/product/edit";
68
+ import { BasicTypeTestList } from "./pages/samples/basic-type-test/list";
69
+ import { BasicTypeTestCreate } from "./pages/samples/basic-type-test/create";
70
+ import { BasicTypeTestEdit } from "./pages/samples/basic-type-test/edit";
71
+ import { TiptapTestList } from "./pages/samples/tiptap-test/list";
72
+ import { TiptapTestCreate } from "./pages/samples/tiptap-test/create";
73
+ import { TiptapTestEdit } from "./pages/samples/tiptap-test/edit";
74
+ import { TiptapTestShow } from "./pages/samples/tiptap-test/show";
75
+ import { DrawioTestList } from "./pages/samples/drawio-test/list";
76
+ import { DrawioTestCreate } from "./pages/samples/drawio-test/create";
77
+ import { DrawioTestEdit } from "./pages/samples/drawio-test/edit";
78
+ import { DrawioTestShow } from "./pages/samples/drawio-test/show";
79
+
80
+ const API_URL = import.meta.env.VITE_API_URL + "/api/odata";
61
81
 
62
82
  const InnerApp: React.FC = () => {
63
83
  const { t, i18n } = useTranslation();
@@ -74,7 +94,7 @@ const InnerApp: React.FC = () => {
74
94
  <Refine
75
95
  authProvider={authProvider}
76
96
  accessControlProvider={accessControlProvider}
77
- dataProvider={dataProvider(API_URL)}
97
+ dataProvider={{ default: dataProvider(API_URL) }}
78
98
  i18nProvider={i18nProvider}
79
99
  routerProvider={routerProvider}
80
100
  resources={[
@@ -86,6 +106,81 @@ const InnerApp: React.FC = () => {
86
106
  icon: <DashboardOutlined />,
87
107
  },
88
108
  },
109
+ // Sample resources
110
+ {
111
+ name: "Samples",
112
+ meta: {
113
+ label: t("sider.samples"),
114
+ icon: <AppstoreOutlined />,
115
+ },
116
+ },
117
+ {
118
+ name: "Model",
119
+ meta: {
120
+ label: t("sider.model"),
121
+ parent: "Samples",
122
+ },
123
+ },
124
+ {
125
+ name: "BasicTypeTest",
126
+ list: "/BasicTypeTests",
127
+ create: "/BasicTypeTests/create",
128
+ edit: "/BasicTypeTests/edit/:id",
129
+ meta: {
130
+ label: t("sider.basicTypeTest"),
131
+ parent: "Model",
132
+ },
133
+ },
134
+ {
135
+ name: "TiptapTest",
136
+ list: "/TiptapTests",
137
+ create: "/TiptapTests/create",
138
+ edit: "/TiptapTests/edit/:id",
139
+ show: "/TiptapTests/show/:id",
140
+ meta: {
141
+ label: t("sider.tiptapTest"),
142
+ parent: "Model",
143
+ },
144
+ },
145
+ {
146
+ name: "DrawioTest",
147
+ list: "/DrawioTests",
148
+ create: "/DrawioTests/create",
149
+ edit: "/DrawioTests/edit/:id",
150
+ show: "/DrawioTests/show/:id",
151
+ meta: {
152
+ label: t("sider.drawioTest"),
153
+ parent: "Model",
154
+ },
155
+ },
156
+ {
157
+ name: "FullTextSearch",
158
+ meta: {
159
+ label: t("sider.fullTextSearch"),
160
+ parent: "Samples",
161
+ },
162
+ },
163
+ {
164
+ name: "Article",
165
+ list: "/Articles",
166
+ create: "/Articles/create",
167
+ edit: "/Articles/edit/:id",
168
+ meta: {
169
+ label: t("types.Article"),
170
+ parent: "FullTextSearch",
171
+ },
172
+ },
173
+ {
174
+ name: "Product",
175
+ list: "/Products",
176
+ create: "/Products/create",
177
+ edit: "/Products/edit/:id",
178
+ meta: {
179
+ label: t("types.Product"),
180
+ parent: "FullTextSearch",
181
+ },
182
+ },
183
+ // Core resources
89
184
  {
90
185
  name: "ApplicationUser",
91
186
  list: "/ApplicationUsers",
@@ -94,7 +189,7 @@ const InnerApp: React.FC = () => {
94
189
  meta: {
95
190
  label: t("sider.users"),
96
191
  icon: <UserOutlined />,
97
- parent: t("sider.settings"),
192
+ parent: "Settings",
98
193
  },
99
194
  },
100
195
  {
@@ -104,7 +199,7 @@ const InnerApp: React.FC = () => {
104
199
  edit: "/PermissionPolicyRoles/edit/:id",
105
200
  meta: {
106
201
  label: t("sider.roles"),
107
- parent: t("sider.settings"),
202
+ parent: "Settings",
108
203
  icon: <TeamOutlined />,
109
204
  },
110
205
  },
@@ -114,7 +209,7 @@ const InnerApp: React.FC = () => {
114
209
  meta: {
115
210
  label: t("sider.backgroundJobs"),
116
211
  icon: <FieldTimeOutlined />,
117
- parent: t("sider.settings"),
212
+ parent: "Settings",
118
213
  },
119
214
  },
120
215
  {
@@ -124,7 +219,7 @@ const InnerApp: React.FC = () => {
124
219
  edit: "/TriggerRules/edit/:id",
125
220
  meta: {
126
221
  label: t("sider.triggerRules"),
127
- parent: t("sider.settings"),
222
+ parent: "Settings",
128
223
  icon: <ThunderboltOutlined />,
129
224
  },
130
225
  },
@@ -133,7 +228,7 @@ const InnerApp: React.FC = () => {
133
228
  list: "/TriggerLogs",
134
229
  meta: {
135
230
  label: t("sider.triggerLogs"),
136
- parent: t("sider.settings"),
231
+ parent: "Settings",
137
232
  icon: <ThunderboltOutlined />,
138
233
  },
139
234
  },
@@ -144,7 +239,7 @@ const InnerApp: React.FC = () => {
144
239
  edit: "/MirrorTypeMappingConfigs/edit/:id",
145
240
  meta: {
146
241
  label: t("sider.mirrorConfigs"),
147
- parent: t("sider.settings"),
242
+ parent: "Settings",
148
243
  icon: <CopyOutlined />,
149
244
  },
150
245
  },
@@ -177,6 +272,40 @@ const InnerApp: React.FC = () => {
177
272
  >
178
273
  <Route index element={<DashboardPage />} />
179
274
 
275
+ {/* Sample routes */}
276
+ <Route path="/Articles">
277
+ <Route index element={<ArticleList />} />
278
+ <Route path="create" element={<ArticleCreate />} />
279
+ <Route path="edit/:id" element={<ArticleEdit />} />
280
+ </Route>
281
+
282
+ <Route path="/Products">
283
+ <Route index element={<ProductList />} />
284
+ <Route path="create" element={<ProductCreate />} />
285
+ <Route path="edit/:id" element={<ProductEdit />} />
286
+ </Route>
287
+
288
+ <Route path="/BasicTypeTests">
289
+ <Route index element={<BasicTypeTestList />} />
290
+ <Route path="create" element={<BasicTypeTestCreate />} />
291
+ <Route path="edit/:id" element={<BasicTypeTestEdit />} />
292
+ </Route>
293
+
294
+ <Route path="/TiptapTests">
295
+ <Route index element={<TiptapTestList />} />
296
+ <Route path="create" element={<TiptapTestCreate />} />
297
+ <Route path="edit/:id" element={<TiptapTestEdit />} />
298
+ <Route path="show/:id" element={<TiptapTestShow />} />
299
+ </Route>
300
+
301
+ <Route path="/DrawioTests">
302
+ <Route index element={<DrawioTestList />} />
303
+ <Route path="create" element={<DrawioTestCreate />} />
304
+ <Route path="edit/:id" element={<DrawioTestEdit />} />
305
+ <Route path="show/:id" element={<DrawioTestShow />} />
306
+ </Route>
307
+
308
+ {/* Core routes */}
180
309
  <Route path="/ApplicationUsers">
181
310
  <Route index element={<ApplicationUserList />} />
182
311
  <Route path="create" element={<ApplicationUserCreate />} />
@@ -11,8 +11,8 @@ const ADMIN_ONLY_RESOURCES = ["ApplicationUser", "PermissionPolicyRole", "Settin
11
11
  */
12
12
  export const accessControlProvider: AccessControlProvider = {
13
13
  can: async ({ resource, action, params }) => {
14
- const isAdmin = localStorage.getItem("user_is_admin") === "true";
15
- const roles = JSON.parse(localStorage.getItem("user_roles") || "[]") as string[];
14
+ const isAdmin = (localStorage.getItem("user_is_admin") || sessionStorage.getItem("user_is_admin")) === "true";
15
+ const roles = JSON.parse(localStorage.getItem("user_roles") || sessionStorage.getItem("user_roles") || "[]") as string[];
16
16
 
17
17
  // Admin-only resources check
18
18
  if (resource && ADMIN_ONLY_RESOURCES.includes(resource) && !isAdmin) {
@@ -0,0 +1,49 @@
1
+ import i18n from "i18next";
2
+ import { initReactI18next } from "react-i18next";
3
+ import LanguageDetector from "i18next-browser-languagedetector";
4
+ import { refineXafTranslations } from "@cundi/refine-xaf";
5
+
6
+ // Use translations from the SDK
7
+ const { en, "zh-TW": zhTW } = refineXafTranslations;
8
+
9
+ const enExtended = {
10
+ ...en,
11
+ sider: {
12
+ ...((en as any).sider || {}),
13
+ triggerRules: "Trigger Rules",
14
+ triggerLogs: "Trigger Logs",
15
+ mirrorConfigs: "Mirror Configs",
16
+ },
17
+ };
18
+
19
+ const zhTWExtended = {
20
+ ...zhTW,
21
+ sider: {
22
+ ...((zhTW as any).sider || {}),
23
+ triggerRules: "觸發規則",
24
+ triggerLogs: "觸發紀錄",
25
+ mirrorConfigs: "鏡射設定",
26
+ },
27
+ };
28
+
29
+ i18n
30
+ .use(LanguageDetector)
31
+ .use(initReactI18next)
32
+ .init({
33
+ resources: {
34
+ en: {
35
+ translation: enExtended,
36
+ },
37
+ "zh-TW": {
38
+ translation: zhTWExtended,
39
+ },
40
+ },
41
+ lng: "zh-TW", // Default language
42
+ fallbackLng: "en",
43
+ debug: false,
44
+ interpolation: {
45
+ escapeValue: false,
46
+ },
47
+ });
48
+
49
+ export default i18n;