@zhang_libo/resource-hub 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 (118) hide show
  1. package/LICENSE +21 -0
  2. package/README.en.md +80 -0
  3. package/README.ja.md +80 -0
  4. package/README.md +79 -0
  5. package/README.zh-TW.md +80 -0
  6. package/bin/cli.js +10 -0
  7. package/dist/app.d.ts +2 -0
  8. package/dist/app.d.ts.map +1 -0
  9. package/dist/app.js +59 -0
  10. package/dist/app.js.map +1 -0
  11. package/dist/db/index.js +12 -0
  12. package/dist/db/index.js.map +1 -0
  13. package/dist/db/migrate.d.ts +3 -0
  14. package/dist/db/migrate.d.ts.map +1 -0
  15. package/dist/db/migrate.js +169 -0
  16. package/dist/db/migrate.js.map +1 -0
  17. package/dist/db/schema.d.ts +743 -0
  18. package/dist/db/schema.d.ts.map +1 -0
  19. package/dist/db/schema.js +88 -0
  20. package/dist/db/schema.js.map +1 -0
  21. package/dist/i18n.js +309 -0
  22. package/dist/i18n.js.map +1 -0
  23. package/dist/plugins/admin.d.ts +4 -0
  24. package/dist/plugins/admin.d.ts.map +1 -0
  25. package/dist/plugins/admin.js +19 -0
  26. package/dist/plugins/admin.js.map +1 -0
  27. package/dist/plugins/auth.d.ts +4 -0
  28. package/dist/plugins/auth.d.ts.map +1 -0
  29. package/dist/plugins/auth.js +35 -0
  30. package/dist/plugins/auth.js.map +1 -0
  31. package/dist/routes/auth.d.ts +4 -0
  32. package/dist/routes/auth.d.ts.map +1 -0
  33. package/dist/routes/auth.js +352 -0
  34. package/dist/routes/auth.js.map +1 -0
  35. package/dist/routes/categories.d.ts +4 -0
  36. package/dist/routes/categories.d.ts.map +1 -0
  37. package/dist/routes/categories.js +112 -0
  38. package/dist/routes/categories.js.map +1 -0
  39. package/dist/routes/config.d.ts +4 -0
  40. package/dist/routes/config.d.ts.map +1 -0
  41. package/dist/routes/config.js +227 -0
  42. package/dist/routes/config.js.map +1 -0
  43. package/dist/routes/resources.d.ts +4 -0
  44. package/dist/routes/resources.d.ts.map +1 -0
  45. package/dist/routes/resources.js +474 -0
  46. package/dist/routes/resources.js.map +1 -0
  47. package/dist/routes/tags.d.ts +4 -0
  48. package/dist/routes/tags.d.ts.map +1 -0
  49. package/dist/routes/tags.js +37 -0
  50. package/dist/routes/tags.js.map +1 -0
  51. package/dist/routes/users.d.ts +4 -0
  52. package/dist/routes/users.d.ts.map +1 -0
  53. package/dist/routes/users.js +181 -0
  54. package/dist/routes/users.js.map +1 -0
  55. package/dist/services/crypto.js +49 -0
  56. package/dist/services/crypto.js.map +1 -0
  57. package/dist/services/mail.d.ts +16 -0
  58. package/dist/services/mail.d.ts.map +1 -0
  59. package/dist/services/mail.js +33 -0
  60. package/dist/services/mail.js.map +1 -0
  61. package/dist/services/rsa.js +49 -0
  62. package/dist/services/rsa.js.map +1 -0
  63. package/dist/services/token.d.ts +9 -0
  64. package/dist/services/token.d.ts.map +1 -0
  65. package/dist/services/token.js +29 -0
  66. package/dist/services/token.js.map +1 -0
  67. package/dist/types.d.ts +80 -0
  68. package/dist/types.d.ts.map +1 -0
  69. package/dist/types.js +2 -0
  70. package/dist/types.js.map +1 -0
  71. package/package.json +73 -0
  72. package/public/admin/AdminCategories.jsx +310 -0
  73. package/public/admin/AdminConfig.jsx +254 -0
  74. package/public/admin/AdminEmail.jsx +279 -0
  75. package/public/admin/AdminTags.jsx +263 -0
  76. package/public/admin/AdminUsers.jsx +452 -0
  77. package/public/app.jsx +186 -0
  78. package/public/components/ConfirmDialog.jsx +78 -0
  79. package/public/components/DropdownSelect.jsx +281 -0
  80. package/public/components/EmailPreviewModal.jsx +104 -0
  81. package/public/components/EmptyState.jsx +50 -0
  82. package/public/components/Modal.jsx +127 -0
  83. package/public/components/PasswordStrength.jsx +45 -0
  84. package/public/components/Skeleton.jsx +68 -0
  85. package/public/components/Toast.jsx +80 -0
  86. package/public/components/TooltipIconButton.jsx +55 -0
  87. package/public/context/AppContext.jsx +314 -0
  88. package/public/features/BatchResourceModal.jsx +606 -0
  89. package/public/features/ChangePasswordModal.jsx +187 -0
  90. package/public/features/ProfileModal.jsx +170 -0
  91. package/public/features/ResourceCard.jsx +422 -0
  92. package/public/features/ResourceFormModal.jsx +915 -0
  93. package/public/features/ResourceRow.jsx +287 -0
  94. package/public/features/ResourceTimeline.jsx +472 -0
  95. package/public/hooks/useApi.jsx +26 -0
  96. package/public/hooks/useRouter.jsx +35 -0
  97. package/public/index.html +258 -0
  98. package/public/layout/AdminLayout.jsx +167 -0
  99. package/public/layout/AppLayout.jsx +119 -0
  100. package/public/layout/AuthLayout.jsx +503 -0
  101. package/public/layout/Header.jsx +543 -0
  102. package/public/layout/Sidebar.jsx +175 -0
  103. package/public/pages/AdminPage.jsx +30 -0
  104. package/public/pages/ForgotPasswordPage.jsx +93 -0
  105. package/public/pages/HomePage.jsx +2297 -0
  106. package/public/pages/LoginPage.jsx +191 -0
  107. package/public/pages/RegisterPage.jsx +137 -0
  108. package/public/pages/ResetPasswordPage.jsx +169 -0
  109. package/public/pages/SetupPage.jsx +157 -0
  110. package/public/utils/helpers.jsx +152 -0
  111. package/public/utils/i18n.jsx +1374 -0
  112. package/public/utils/preferences.jsx +220 -0
  113. package/public/utils/security.jsx +88 -0
  114. package/public/utils/theme.jsx +24 -0
  115. package/public/vendor/babel.min.js +2 -0
  116. package/public/vendor/lucide-react.min.js +9 -0
  117. package/public/vendor/react-dom.development.js +29869 -0
  118. package/public/vendor/react.development.js +3342 -0
@@ -0,0 +1,1374 @@
1
+ const RH_SUPPORTED_LOCALES = Object.freeze(['zh-Hans', 'zh-Hant', 'en', 'ja']);
2
+ const RH_DEFAULT_LOCALE = 'zh-Hans';
3
+ const RH_INTL_LOCALE_MAP = Object.freeze({
4
+ 'zh-Hans': 'zh-CN',
5
+ 'zh-Hant': 'zh-TW',
6
+ en: 'en-US',
7
+ ja: 'ja-JP',
8
+ });
9
+ const RH_LOCALE_NATIVE_LABELS = Object.freeze({
10
+ 'zh-Hans': '简体中文',
11
+ 'zh-Hant': '繁體中文',
12
+ en: 'English',
13
+ ja: '日本語',
14
+ });
15
+
16
+ const RH_STATIC_TRANSLATIONS = {
17
+ 'zh-Hant': {},
18
+ en: {},
19
+ ja: {},
20
+ };
21
+
22
+ const RH_PATTERN_TRANSLATORS = [
23
+ {
24
+ test: /^成功添加 (\d+) 条资源$/,
25
+ render(locale, match) {
26
+ const count = match[1];
27
+ if (locale === 'zh-Hant') return `成功新增 ${count} 筆資源`;
28
+ if (locale === 'en') return `Successfully added ${count} resources`;
29
+ if (locale === 'ja') return `${count} 件のリソースを追加しました`;
30
+ return match[0];
31
+ },
32
+ },
33
+ {
34
+ test: /^成功 (\d+) 条,失败 (\d+) 条$/,
35
+ render(locale, match) {
36
+ const successCount = match[1];
37
+ const failCount = match[2];
38
+ if (locale === 'zh-Hant') return `成功 ${successCount} 筆,失敗 ${failCount} 筆`;
39
+ if (locale === 'en') return `${successCount} succeeded, ${failCount} failed`;
40
+ if (locale === 'ja') return `${successCount} 件成功、${failCount} 件失敗`;
41
+ return match[0];
42
+ },
43
+ },
44
+ {
45
+ test: /^已删除 (\d+) 个标签$/,
46
+ render(locale, match) {
47
+ const count = match[1];
48
+ if (locale === 'zh-Hant') return `已刪除 ${count} 個標籤`;
49
+ if (locale === 'en') return `Deleted ${count} tags`;
50
+ if (locale === 'ja') return `${count} 個のタグを削除しました`;
51
+ return match[0];
52
+ },
53
+ },
54
+ {
55
+ test: /^标签「(.+)」已删除$/,
56
+ render(locale, match) {
57
+ const tag = match[1];
58
+ if (locale === 'zh-Hant') return `標籤「${tag}」已刪除`;
59
+ if (locale === 'en') return `Tag "${tag}" deleted`;
60
+ if (locale === 'ja') return `タグ「${tag}」を削除しました`;
61
+ return match[0];
62
+ },
63
+ },
64
+ {
65
+ test: /^重置密码 — (.+)$/,
66
+ render(locale, match) {
67
+ const name = match[1];
68
+ if (locale === 'zh-Hant') return `重設密碼 — ${name}`;
69
+ if (locale === 'en') return `Reset Password — ${name}`;
70
+ if (locale === 'ja') return `パスワード再設定 — ${name}`;
71
+ return match[0];
72
+ },
73
+ },
74
+ {
75
+ test: /^已重置 (.+) 的密码,临时密码已发送至邮箱$/,
76
+ render(locale, match) {
77
+ const name = match[1];
78
+ if (locale === 'zh-Hant') return `已重設 ${name} 的密碼,臨時密碼已寄送至信箱`;
79
+ if (locale === 'en') return `${name}'s password was reset and a temporary password was emailed`;
80
+ if (locale === 'ja') return `${name} のパスワードを再設定し、一時パスワードをメール送信しました`;
81
+ return match[0];
82
+ },
83
+ },
84
+ {
85
+ test: /^确认删除类别「(.+)」?关联的 (\d+) 个资源将失去类别归属。删除后不可恢复。$/,
86
+ render(locale, match) {
87
+ const name = match[1];
88
+ const count = match[2];
89
+ if (locale === 'zh-Hant') return `確認刪除類別「${name}」?關聯的 ${count} 個資源將失去類別歸屬。刪除後無法復原。`;
90
+ if (locale === 'en') return `Delete category "${name}"? ${count} linked resources will lose their category. This cannot be undone.`;
91
+ if (locale === 'ja') return `カテゴリ「${name}」を削除しますか?関連する ${count} 件のリソースからカテゴリが外れます。この操作は元に戻せません。`;
92
+ return match[0];
93
+ },
94
+ },
95
+ {
96
+ test: /^确认删除类别「(.+)」?删除后不可恢复。$/,
97
+ render(locale, match) {
98
+ const name = match[1];
99
+ if (locale === 'zh-Hant') return `確認刪除類別「${name}」?刪除後無法復原。`;
100
+ if (locale === 'en') return `Delete category "${name}"? This cannot be undone.`;
101
+ if (locale === 'ja') return `カテゴリ「${name}」を削除しますか?この操作は元に戻せません。`;
102
+ return match[0];
103
+ },
104
+ },
105
+ {
106
+ test: /^确认删除标签「(.+)」?该标签将从所有资源中移除。$/,
107
+ render(locale, match) {
108
+ const tag = match[1];
109
+ if (locale === 'zh-Hant') return `確認刪除標籤「${tag}」?該標籤將從所有資源中移除。`;
110
+ if (locale === 'en') return `Delete tag "${tag}"? It will be removed from all resources.`;
111
+ if (locale === 'ja') return `タグ「${tag}」を削除しますか?すべてのリソースから外れます。`;
112
+ return match[0];
113
+ },
114
+ },
115
+ {
116
+ test: /^确认删除选中的 (\d+) 个标签?这些标签将从所有资源中移除。$/,
117
+ render(locale, match) {
118
+ const count = match[1];
119
+ if (locale === 'zh-Hant') return `確認刪除選中的 ${count} 個標籤?這些標籤將從所有資源中移除。`;
120
+ if (locale === 'en') return `Delete ${count} selected tags? They will be removed from all resources.`;
121
+ if (locale === 'ja') return `選択した ${count} 個のタグを削除しますか?すべてのリソースから外れます。`;
122
+ return match[0];
123
+ },
124
+ },
125
+ {
126
+ test: /^确认删除用户「(.+)」?该操作不可撤销,其名下资源将转移给当前管理员。$/,
127
+ render(locale, match) {
128
+ const name = match[1];
129
+ if (locale === 'zh-Hant') return `確認刪除使用者「${name}」?此操作無法撤銷,其名下資源將轉移給目前管理員。`;
130
+ if (locale === 'en') return `Delete user "${name}"? This cannot be undone, and their resources will be transferred to the current admin.`;
131
+ if (locale === 'ja') return `ユーザー「${name}」を削除しますか?この操作は元に戻せず、所有リソースは現在の管理者へ移管されます。`;
132
+ return match[0];
133
+ },
134
+ },
135
+ {
136
+ test: /^(\d+) 个结果$/,
137
+ render(locale, match) {
138
+ const count = match[1];
139
+ if (locale === 'zh-Hant') return `${count} 個結果`;
140
+ if (locale === 'en') return `${count} results`;
141
+ if (locale === 'ja') return `${count} 件`;
142
+ return match[0];
143
+ },
144
+ },
145
+ {
146
+ test: /^(\d+) 条记录$/,
147
+ render(locale, match) {
148
+ const count = match[1];
149
+ if (locale === 'zh-Hant') return `${count} 筆紀錄`;
150
+ if (locale === 'en') return `${count} records`;
151
+ if (locale === 'ja') return `${count} 件の記録`;
152
+ return match[0];
153
+ },
154
+ },
155
+ {
156
+ test: /^访问 (\d+)$/,
157
+ render(locale, match) {
158
+ const count = match[1];
159
+ if (locale === 'zh-Hant') return `訪問 ${count}`;
160
+ if (locale === 'en') return `${count} visits`;
161
+ if (locale === 'ja') return `アクセス ${count}`;
162
+ return match[0];
163
+ },
164
+ },
165
+ {
166
+ test: /^范围 · (.+)$/,
167
+ render(locale, match) {
168
+ const label = match[1];
169
+ if (locale === 'zh-Hant') return `範圍 · ${label}`;
170
+ if (locale === 'en') return `Scope · ${label}`;
171
+ if (locale === 'ja') return `範囲 · ${label}`;
172
+ return match[0];
173
+ },
174
+ },
175
+ {
176
+ test: /^类别 · (.+)$/,
177
+ render(locale, match) {
178
+ const label = match[1];
179
+ if (locale === 'zh-Hant') return `類別 · ${label}`;
180
+ if (locale === 'en') return `Category · ${label}`;
181
+ if (locale === 'ja') return `カテゴリ · ${label}`;
182
+ return match[0];
183
+ },
184
+ },
185
+ {
186
+ test: /^搜索 · (.+)$/,
187
+ render(locale, match) {
188
+ const label = match[1];
189
+ if (locale === 'zh-Hant') return `搜尋 · ${label}`;
190
+ if (locale === 'en') return `Search · ${label}`;
191
+ if (locale === 'ja') return `検索 · ${label}`;
192
+ return match[0];
193
+ },
194
+ },
195
+ {
196
+ test: /^(.+) · 已应用 (\d+) 组条件$/,
197
+ render(locale, match) {
198
+ const label = translateText(locale, match[1]);
199
+ const count = match[2];
200
+ if (locale === 'zh-Hant') return `${label} · 已套用 ${count} 組條件`;
201
+ if (locale === 'en') return `${label} · ${count} filter groups applied`;
202
+ if (locale === 'ja') return `${label} · ${count} 件の条件を適用中`;
203
+ return match[0];
204
+ },
205
+ },
206
+ {
207
+ test: /^(\d+) 个资源可直接访问,覆盖 (\d+) 个分类$/,
208
+ render(locale, match) {
209
+ const resourceCount = match[1];
210
+ const categoryCount = match[2];
211
+ if (locale === 'zh-Hant') return `${resourceCount} 個資源可直接存取,涵蓋 ${categoryCount} 個分類`;
212
+ if (locale === 'en') return `${resourceCount} resources ready to open across ${categoryCount} categories`;
213
+ if (locale === 'ja') return `${resourceCount} 件のリソースに直接アクセス可能、${categoryCount} カテゴリをカバー`;
214
+ return match[0];
215
+ },
216
+ },
217
+ {
218
+ test: /^(\d+) 个公开资源,覆盖 (\d+) 个分类$/,
219
+ render(locale, match) {
220
+ const resourceCount = match[1];
221
+ const categoryCount = match[2];
222
+ if (locale === 'zh-Hant') return `${resourceCount} 個公開資源,涵蓋 ${categoryCount} 個分類`;
223
+ if (locale === 'en') return `${resourceCount} public resources across ${categoryCount} categories`;
224
+ if (locale === 'ja') return `${resourceCount} 件の公開リソース、${categoryCount} カテゴリをカバー`;
225
+ return match[0];
226
+ },
227
+ },
228
+ {
229
+ test: /^(\d+) 项$/,
230
+ render(locale, match) {
231
+ const count = match[1];
232
+ if (locale === 'zh-Hant') return `${count} 項`;
233
+ if (locale === 'en') return `${count} items`;
234
+ if (locale === 'ja') return `${count} 件`;
235
+ return match[0];
236
+ },
237
+ },
238
+ {
239
+ test: /^(\d+) 个分类$/,
240
+ render(locale, match) {
241
+ const count = match[1];
242
+ if (locale === 'zh-Hant') return `${count} 個分類`;
243
+ if (locale === 'en') return `${count} categories`;
244
+ if (locale === 'ja') return `${count} カテゴリ`;
245
+ return match[0];
246
+ },
247
+ },
248
+ {
249
+ test: /^展开更多 ?\((\d+)\)$/,
250
+ render(locale, match) {
251
+ const count = match[1];
252
+ if (locale === 'zh-Hant') return `展開更多(${count})`;
253
+ if (locale === 'en') return `Show More (${count})`;
254
+ if (locale === 'ja') return `さらに表示 (${count})`;
255
+ return match[0];
256
+ },
257
+ },
258
+ {
259
+ test: /^批量删除 ?\((\d+)\)$/,
260
+ render(locale, match) {
261
+ const count = match[1];
262
+ if (locale === 'zh-Hant') return `批量刪除(${count})`;
263
+ if (locale === 'en') return `Batch Delete (${count})`;
264
+ if (locale === 'ja') return `一括削除 (${count})`;
265
+ return match[0];
266
+ },
267
+ },
268
+ ];
269
+
270
+ Object.assign(RH_STATIC_TRANSLATIONS['zh-Hant'], {
271
+ '浅色': '淺色',
272
+ '跟随系统': '跟隨系統',
273
+ '登录': '登入',
274
+ '个人信息': '個人資訊',
275
+ '修改密码': '修改密碼',
276
+ '后台管理': '後台管理',
277
+ '注销登录': '登出',
278
+ '搜索资源...': '搜尋資源...',
279
+ '搜索资源名称、描述、标签... (Ctrl+K)': '搜尋資源名稱、描述、標籤... (Ctrl+K)',
280
+ '加载中…': '載入中…',
281
+ '确认删除': '確認刪除',
282
+ '模拟邮件(开发预览)': '模擬郵件(開發預覽)',
283
+ '主题:': '主旨:',
284
+ '忘记密码': '忘記密碼',
285
+ '登录账号': '登入帳號',
286
+ '用户名': '使用者名稱',
287
+ '请输入用户名': '請輸入使用者名稱',
288
+ '密码': '密碼',
289
+ '请输入密码': '請輸入密碼',
290
+ '隐藏密码': '隱藏密碼',
291
+ '显示密码': '顯示密碼',
292
+ '登录中...': '登入中...',
293
+ '如无法登录或注册,请联系管理员确认账号与注册策略。': '如無法登入或註冊,請聯絡管理員確認帳號與註冊策略。',
294
+ '忘记密码?': '忘記密碼?',
295
+ '没有账号?': '沒有帳號?',
296
+ '注册账号': '註冊帳號',
297
+ '注册成功!初始密码已发送至邮箱': '註冊成功!初始密碼已寄送至信箱',
298
+ '注册失败': '註冊失敗',
299
+ '注册功能暂未开放': '註冊功能尚未開放',
300
+ '去登录': '前往登入',
301
+ '邀请与注册': '邀請與註冊',
302
+ '已有账号?': '已有帳號?',
303
+ '返回登录': '返回登入',
304
+ '发送重置链接': '發送重設連結',
305
+ '账号恢复': '帳號恢復',
306
+ '输入注册邮箱,系统将发送密码重置链接': '輸入註冊信箱,系統將發送密碼重設連結',
307
+ '若该邮箱已注册,重置链接已发送,请查收': '若此信箱已註冊,重設連結已寄出,請查收',
308
+ '密码已重置,请登录': '密碼已重設,請登入',
309
+ '重置失败': '重設失敗',
310
+ '链接已失效或过期': '連結已失效或過期',
311
+ '请重新发送密码重置链接': '請重新發送密碼重設連結',
312
+ '重新发送重置链接': '重新發送重設連結',
313
+ '重置密码': '重設密碼',
314
+ '设置新密码': '設定新密碼',
315
+ '新密码': '新密碼',
316
+ '确认新密码': '確認新密碼',
317
+ '至少8位,含字母和数字': '至少 8 位,含字母與數字',
318
+ '再次输入新密码': '再次輸入新密碼',
319
+ '再次输入密码': '再次輸入密碼',
320
+ '初始化成功!请登录': '初始化成功!請登入',
321
+ '初始化失败': '初始化失敗',
322
+ '首次初始化': '首次初始化',
323
+ '欢迎使用资源导航系统': '歡迎使用資源導航系統',
324
+ '请先完成初始化,创建管理员账号': '請先完成初始化,建立管理員帳號',
325
+ '管理员用户名': '管理員使用者名稱',
326
+ '管理员显示名称': '管理員顯示名稱',
327
+ '管理员邮箱': '管理員信箱',
328
+ '管理员密码': '管理員密碼',
329
+ '确认密码': '確認密碼',
330
+ '显示给其他用户的名称': '顯示給其他使用者的名稱',
331
+ '完成初始化': '完成初始化',
332
+ '更顺手地进入你的': '更順手地進入你的',
333
+ '统一入口与常用访问能力,从这里开始。': '統一入口與常用存取能力,從這裡開始。',
334
+ '资源管理': '資源管理',
335
+ '公开与私有资源统一进入,常用内容不再分散。': '公開與私有資源統一進入,常用內容不再分散。',
336
+ '快速切换': '快速切換',
337
+ '收藏、最近访问与我创建的内容都能快速回到手边。': '收藏、最近訪問與我建立的內容都能快速回到手邊。',
338
+ '后台配置': '後台配置',
339
+ '管理员可直接进入系统配置与邮件服务管理。': '管理員可直接進入系統配置與郵件服務管理。',
340
+ '常用入口能力': '常用入口能力',
341
+ '登录后可直接访问的核心能力': '登入後可直接存取的核心能力',
342
+ '资源导航': '資源導航',
343
+ '我的收藏': '我的收藏',
344
+ '最近访问': '最近訪問',
345
+ '我创建的': '我建立的',
346
+ '按热度': '按熱度',
347
+ '按创建时间': '按建立時間',
348
+ '按更新时间': '按更新時間',
349
+ '卡片视图': '卡片視圖',
350
+ '列表视图': '列表視圖',
351
+ '时间轴视图': '時間軸視圖',
352
+ '全部资源': '全部資源',
353
+ '公开资源': '公開資源',
354
+ '总访问量': '總訪問量',
355
+ '近30日访问': '近 30 日訪問',
356
+ '近24小时访问': '近 24 小時訪問',
357
+ '浏览全部资源': '瀏覽全部資源',
358
+ '可见资源': '可見資源',
359
+ '当前可访问入口': '目前可存取入口',
360
+ '资源分类': '資源分類',
361
+ '按主题浏览入口': '依主題瀏覽入口',
362
+ '我的资源': '我的資源',
363
+ '我创建与维护的内容': '我建立與維護的內容',
364
+ '页面累计访问量': '頁面累計訪問量',
365
+ '近 30 天记录': '近 30 天紀錄',
366
+ '最近一天活跃': '最近一天活躍',
367
+ '无需登录即可访问': '無需登入即可存取',
368
+ '常用标签': '常用標籤',
369
+ '作为补充过滤条件': '作為補充篩選條件',
370
+ '快速回到常用资源': '快速回到常用資源',
371
+ '还没有收藏资源,先去逛逛并添加常用入口': '還沒有收藏資源,先去逛逛並加入常用入口',
372
+ '继续上次浏览路径': '延續上次瀏覽路徑',
373
+ '还没有访问记录,先随便逛一逛': '還沒有訪問紀錄,先隨意逛一逛',
374
+ '维护自己的内容': '維護自己的內容',
375
+ '还没有创建内容,可以尝试新增一个资源': '還沒有建立內容,可以先新增一個資源',
376
+ '快捷入口': '快捷入口',
377
+ '从收藏、历史或我的资源继续浏览和维护内容。': '從收藏、歷史或我的資源繼續瀏覽與維護內容。',
378
+ '分类入口': '分類入口',
379
+ '按主题快速浏览全部资源。': '按主題快速瀏覽全部資源。',
380
+ '查看更多': '查看更多',
381
+ '全部': '全部',
382
+ '全部可见资源': '全部可見資源',
383
+ '公开资源浏览': '公開資源瀏覽',
384
+ '返回首页概览': '返回首頁總覽',
385
+ '暂无数据': '暫無資料',
386
+ '点击进入该分类结果页': '點擊進入此分類結果頁',
387
+ '按访问热度挑选常用入口。': '按訪問熱度挑選常用入口。',
388
+ '快速查看最近维护或新增的内容。': '快速查看最近維護或新增的內容。',
389
+ '继续维护你创建和拥有的资源。': '繼續維護你建立與擁有的資源。',
390
+ '热门资源': '熱門資源',
391
+ '最近更新': '最近更新',
392
+ '新增资源': '新增資源',
393
+ '筛选': '篩選',
394
+ '按创建时间归档': '按建立時間歸檔',
395
+ '清空': '清空',
396
+ '清空筛选': '清空篩選',
397
+ '类别筛选结果': '類別篩選結果',
398
+ '范围筛选结果': '範圍篩選結果',
399
+ '搜索结果': '搜尋結果',
400
+ '标签筛选结果': '標籤篩選結果',
401
+ '筛选结果': '篩選結果',
402
+ '类别过滤': '類別篩選',
403
+ '标签筛选': '標籤篩選',
404
+ '收起标签': '收起標籤',
405
+ '快捷访问': '快捷訪問',
406
+ '清空标签': '清空標籤',
407
+ '没有匹配的资源结果': '沒有符合條件的資源結果',
408
+ '当前没有可展示的资源': '目前沒有可顯示的資源',
409
+ '可以放宽关键词、切换类别,或直接清空筛选。': '可以放寬關鍵字、切換分類,或直接清空篩選。',
410
+ '从资源区右上角新增资源后,这里会立即出现结果。': '從資源區右上角新增資源後,這裡會立即出現結果。',
411
+ '当前仅显示公开资源。若需要完整内容,请使用 Header 中的登录入口。': '目前僅顯示公開資源。若需完整內容,請使用 Header 中的登入入口。',
412
+ '快捷访问、类别和标签筛选已收进左侧导航。': '快捷訪問、分類與標籤篩選已收進左側導覽。',
413
+ '关闭导航遮罩': '關閉導覽遮罩',
414
+ '私有': '私有',
415
+ '公开': '公開',
416
+ '暂无描述': '暫無描述',
417
+ '取消收藏': '取消收藏',
418
+ '收藏资源': '收藏資源',
419
+ '编辑资源': '編輯資源',
420
+ '更新于': '更新於',
421
+ '创建于': '建立於',
422
+ '访问': '訪問',
423
+ '刚刚更新': '剛剛更新',
424
+ '请登录后操作': '請登入後操作',
425
+ '用户名或密码错误,请重新输入': '使用者名稱或密碼錯誤,請重新輸入',
426
+ '类别': '類別',
427
+ '标签': '標籤',
428
+ '返回首页': '返回首頁',
429
+ '类别管理': '類別管理',
430
+ '标签管理': '標籤管理',
431
+ '用户管理': '使用者管理',
432
+ '系统配置': '系統配置',
433
+ '邮件服务': '郵件服務',
434
+ '保存配置': '儲存設定',
435
+ '站点标题': '站點標題',
436
+ '站点副标题': '站點副標題',
437
+ '站点 Logo URL': '站點 Logo URL',
438
+ '留空则使用默认图标': '留空則使用預設圖示',
439
+ 'Token 有效期(分钟)': 'Token 有效期(分鐘)',
440
+ '重置链接有效期(分钟)': '重設連結有效期(分鐘)',
441
+ '开放注册': '開放註冊',
442
+ '限制注册邮箱域名': '限制註冊信箱網域',
443
+ '已开启': '已開啟',
444
+ '已关闭': '已關閉',
445
+ '邮箱域名白名单': '信箱網域白名單',
446
+ '测试连接': '測試連線',
447
+ '加密方式': '加密方式',
448
+ '无加密': '無加密',
449
+ '发件人信息': '寄件者資訊',
450
+ '发件人名称': '寄件者名稱',
451
+ '发件人邮箱': '寄件者信箱',
452
+ '显示名称': '顯示名稱',
453
+ '角色': '角色',
454
+ '状态': '狀態',
455
+ '管理员': '管理員',
456
+ '普通用户': '一般使用者',
457
+ '启用': '啟用',
458
+ '禁用': '停用',
459
+ '编辑': '編輯',
460
+ '删除': '刪除',
461
+ '编辑用户': '編輯使用者',
462
+ '新增用户': '新增使用者',
463
+ '初始密码': '初始密碼',
464
+ '账号状态': '帳號狀態',
465
+ '密码强度:弱': '密碼強度:弱',
466
+ '密码强度:中': '密碼強度:中',
467
+ '密码强度:强': '密碼強度:強',
468
+ '保存成功': '儲存成功',
469
+ '请输入有效的邮箱地址': '請輸入有效的信箱地址',
470
+ '请输入当前密码': '請輸入目前密碼',
471
+ '新密码至少8位,需包含字母和数字': '新密碼至少 8 位,需包含字母與數字',
472
+ '新密码不能与当前密码相同': '新密碼不能與目前密碼相同',
473
+ '两次密码不一致': '兩次密碼不一致',
474
+ '当前密码错误': '目前密碼錯誤',
475
+ '修改失败': '修改失敗',
476
+ '确认修改': '確認修改',
477
+ '保存修改': '儲存修改',
478
+ '注册时间': '註冊時間',
479
+ '今天': '今天',
480
+ '当前密码': '目前密碼',
481
+ '取消': '取消',
482
+ '关闭': '關閉',
483
+ '保存': '儲存',
484
+ '保存失败': '儲存失敗',
485
+ '密码已修改': '密碼已修改',
486
+ '资源名称': '資源名稱',
487
+ '请输入资源名称': '請輸入資源名稱',
488
+ '访问权限': '存取權限',
489
+ 'Logo URL(可选)': 'Logo URL(可選)',
490
+ '选择类别': '選擇分類',
491
+ '描述': '描述',
492
+ '描述(可选,最多200字符)': '描述(可選,最多 200 字)',
493
+ 'URL需以 http:// 或 https:// 开头': 'URL 需以 http:// 或 https:// 開頭',
494
+ 'Logo URL格式不正确': 'Logo URL 格式不正確',
495
+ '描述最多200字符': '描述最多 200 字元',
496
+ '简短描述这个资源…': '簡短描述這個資源…',
497
+ '标签(最多10个,每个最多20字符)': '標籤(最多 10 個,每個最多 20 字)',
498
+ '输入标签后回车添加': '輸入標籤後按 Enter 新增',
499
+ '启用状态': '啟用狀態',
500
+ '资源可正常展示与访问': '資源可正常顯示與存取',
501
+ '资源将被隐藏并停止对外展示': '資源將被隱藏並停止對外顯示',
502
+ '批量录入': '批量錄入',
503
+ '批量录入资源': '批量錄入資源',
504
+ '每行一条资源,名称与 URL 必填;类别、访问权限、启用状态可选;标签可输入并从已有标签联想选择,最多 10 个。': '每行一筆資源,名稱與 URL 必填;分類、存取權限、啟用狀態可選;標籤可輸入並從既有標籤聯想選擇,最多 10 個。',
505
+ '名称': '名稱',
506
+ '操作': '操作',
507
+ '输入或选择类别': '輸入或選擇分類',
508
+ '可选': '可選',
509
+ '输入选择标签': '輸入或選擇標籤',
510
+ '添加一行': '新增一行',
511
+ '批量保存': '批量儲存',
512
+ '删除此行': '刪除此行',
513
+ '匹配类别': '符合的分類',
514
+ '已有类别': '已有分類',
515
+ '匹配标签': '符合的標籤',
516
+ '已有标签': '已有標籤',
517
+ '收起类别列表': '收起分類列表',
518
+ '展开类别列表': '展開分類列表',
519
+ '统一访问入口': '統一存取入口',
520
+ '统一管理与访问你的资源': '統一管理與存取你的資源',
521
+ '登录以访问全部资源导航': '登入以存取完整資源導航',
522
+ '资源工作台': '資源工作臺',
523
+ '工作台': '工作臺',
524
+ '请输入显示名称': '請輸入顯示名稱',
525
+ '请输入邮箱地址': '請輸入信箱地址',
526
+ '资源名称不能为空(最多50字符)': '資源名稱不能為空(最多 50 字)',
527
+ '资源导航系统': '資源導航系統',
528
+ '管理资源分类名称与配色,保持后台和结果页的类别语义一致。': '管理資源分類名稱與配色,保持後台與結果頁的分類語意一致。',
529
+ '新增类别': '新增分類',
530
+ '颜色': '顏色',
531
+ '资源数': '資源數',
532
+ '暂无类别': '暫無分類',
533
+ '编辑类别': '編輯分類',
534
+ '类别名称': '分類名稱',
535
+ '输入类别名称': '輸入分類名稱',
536
+ '类别名称不能为空': '分類名稱不能為空',
537
+ '类别已更新': '分類已更新',
538
+ '类别已创建': '分類已建立',
539
+ '类别已删除': '分類已刪除',
540
+ '删除类别': '刪除分類',
541
+ '集中管理资源标签,保持筛选体系简洁、可读且一致。': '集中管理資源標籤,保持篩選體系簡潔、可讀且一致。',
542
+ '刷新': '重新整理',
543
+ '标签名': '標籤名',
544
+ '使用次数': '使用次數',
545
+ '暂无标签': '暫無標籤',
546
+ '批量删除': '批量刪除',
547
+ '批量删除标签': '批量刪除標籤',
548
+ '删除标签': '刪除標籤',
549
+ '管理账号、角色与状态,确保后台操作语义清晰而克制。': '管理帳號、角色與狀態,確保後台操作語意清晰且克制。',
550
+ '用户已更新': '使用者已更新',
551
+ '用户已创建': '使用者已建立',
552
+ '用户已删除': '使用者已刪除',
553
+ '删除用户': '刪除使用者',
554
+ '用户的显示名称': '使用者的顯示名稱',
555
+ '仅字母数字下划线,3-20位': '僅限字母、數字與底線,3-20 位',
556
+ '显示名称不能为空(最多30字符)': '顯示名稱不能為空(最多 30 字)',
557
+ '系统将自动生成临时密码并发送至用户邮箱,用户可凭临时密码登录后自行修改。': '系統將自動產生臨時密碼並寄送至使用者信箱,使用者可憑臨時密碼登入後自行修改。',
558
+ '确认重置': '確認重設',
559
+ '用户角色': '使用者角色',
560
+ '暂无用户': '暫無使用者',
561
+ '维护站点基础信息、注册策略与安全有效期配置。': '維護站點基礎資訊、註冊策略與安全有效期設定。',
562
+ '留空时显示品牌色首字母图标': '留空時顯示品牌色首字母圖示',
563
+ '多个域名用英文逗号分隔,留空则不限制': '多個網域請以英文逗號分隔,留空則不限制',
564
+ '系统配置已保存': '系統設定已儲存',
565
+ '管理 SMTP 连接与发件人信息,保持后台配置区的层级和交互一致。': '管理 SMTP 連線與寄件者資訊,保持後台設定區的層級與互動一致。',
566
+ 'SMTP 主机为空时,系统使用模拟邮件模式:邮件不会真实发送,仅在前端弹窗预览。': 'SMTP 主機為空時,系統會使用模擬郵件模式:郵件不會真正送出,只會在前端彈窗預覽。',
567
+ 'SMTP 主机': 'SMTP 主機',
568
+ 'SMTP 端口': 'SMTP 埠號',
569
+ 'SMTP 用户名': 'SMTP 使用者名稱',
570
+ 'SMTP 密码': 'SMTP 密碼',
571
+ 'smtp.example.com(留空使用模拟模式)': 'smtp.example.com(留空使用模擬模式)',
572
+ '保持 *** 则不更改当前密码': '保留 *** 則不變更目前密碼',
573
+ '填写 `***` 表示不修改当前已保存的密码': '填寫 `***` 表示不修改目前已儲存的密碼',
574
+ '邮件配置已保存': '郵件設定已儲存',
575
+ '发送失败': '發送失敗',
576
+ '更新失败': '更新失敗',
577
+ '创建失败': '建立失敗',
578
+ });
579
+
580
+ Object.assign(RH_STATIC_TRANSLATIONS.en, {
581
+ '浅色': 'Light',
582
+ '深色': 'Dark',
583
+ '跟随系统': 'System',
584
+ '登录': 'Sign In',
585
+ '个人信息': 'Profile',
586
+ '修改密码': 'Change Password',
587
+ '后台管理': 'Admin',
588
+ '注销登录': 'Sign Out',
589
+ '搜索资源...': 'Search resources...',
590
+ '搜索资源名称、描述、标签... (Ctrl+K)': 'Search names, descriptions, tags... (Ctrl+K)',
591
+ '加载中…': 'Loading…',
592
+ '确认删除': 'Delete',
593
+ '模拟邮件(开发预览)': 'Mock Email (Preview)',
594
+ '收件人:': 'To:',
595
+ '主题:': 'Subject:',
596
+ '正文:': 'Body:',
597
+ '忘记密码': 'Forgot Password',
598
+ '登录账号': 'Sign In',
599
+ '用户名': 'Username',
600
+ '请输入用户名': 'Enter username',
601
+ '密码': 'Password',
602
+ '请输入密码': 'Enter password',
603
+ '隐藏密码': 'Hide password',
604
+ '显示密码': 'Show password',
605
+ '登录中...': 'Signing in...',
606
+ '如无法登录或注册,请联系管理员确认账号与注册策略。': 'If sign-in or sign-up is unavailable, contact your administrator.',
607
+ '忘记密码?': 'Forgot password?',
608
+ '没有账号?': 'No account?',
609
+ '注册账号': 'Create Account',
610
+ '注册成功!初始密码已发送至邮箱': 'Registration succeeded. The initial password has been emailed.',
611
+ '注册失败': 'Registration failed',
612
+ '注册功能暂未开放': 'Registration is currently closed',
613
+ '去登录': 'Go to Sign In',
614
+ '邀请与注册': 'Invitation & Registration',
615
+ '已有账号?': 'Already have an account?',
616
+ '返回登录': 'Back to Sign In',
617
+ '发送重置链接': 'Send Reset Link',
618
+ '账号恢复': 'Account Recovery',
619
+ '输入注册邮箱,系统将发送密码重置链接': 'Enter your registered email to receive a reset link.',
620
+ '若该邮箱已注册,重置链接已发送,请查收': 'If the email is registered, a reset link has been sent.',
621
+ '密码已重置,请登录': 'Password reset. Please sign in.',
622
+ '重置失败': 'Reset failed',
623
+ '链接已失效或过期': 'The link is invalid or expired',
624
+ '请重新发送密码重置链接': 'Please request a new password reset link',
625
+ '重新发送重置链接': 'Send another reset link',
626
+ '重置密码': 'Reset Password',
627
+ '设置新密码': 'Set New Password',
628
+ '新密码': 'New Password',
629
+ '确认新密码': 'Confirm Password',
630
+ '至少8位,含字母和数字': 'At least 8 characters, including letters and numbers',
631
+ '再次输入新密码': 'Enter the new password again',
632
+ '再次输入密码': 'Enter the password again',
633
+ '初始化成功!请登录': 'Setup completed. Please sign in.',
634
+ '初始化失败': 'Setup failed',
635
+ '首次初始化': 'Initial Setup',
636
+ '欢迎使用资源导航系统': 'Welcome to ResourceHub',
637
+ '请先完成初始化,创建管理员账号': 'Complete setup and create the first admin account.',
638
+ '管理员用户名': 'Admin Username',
639
+ '管理员显示名称': 'Admin Display Name',
640
+ '管理员邮箱': 'Admin Email',
641
+ '管理员密码': 'Admin Password',
642
+ '确认密码': 'Confirm Password',
643
+ '显示给其他用户的名称': 'Name shown to other users',
644
+ '完成初始化': 'Finish Setup',
645
+ '统一入口与常用访问能力,从这里开始。': 'A unified entry point for your everyday resources starts here.',
646
+ '资源管理': 'Resource Management',
647
+ '快速切换': 'Quick Access',
648
+ '后台配置': 'Admin Configuration',
649
+ '资源导航': 'Resource Hub',
650
+ '我的收藏': 'Favorites',
651
+ '最近访问': 'Recent',
652
+ '我创建的': 'Mine',
653
+ '按热度': 'By Popularity',
654
+ '按创建时间': 'By Created Time',
655
+ '按更新时间': 'By Updated Time',
656
+ '卡片视图': 'Card View',
657
+ '列表视图': 'List View',
658
+ '时间轴视图': 'Timeline View',
659
+ '全部资源': 'All Resources',
660
+ '公开资源': 'Public Resources',
661
+ '总访问量': 'Total Visits',
662
+ '近30日访问': 'Visits in 30 Days',
663
+ '近24小时访问': 'Visits in 24 Hours',
664
+ '浏览全部资源': 'Browse All Resources',
665
+ '可见资源': 'Accessible Resources',
666
+ '当前可访问入口': 'Available right now',
667
+ '资源分类': 'Categories',
668
+ '按主题浏览入口': 'Browse by topic',
669
+ '我的资源': 'My Resources',
670
+ '我创建与维护的内容': 'Items I manage',
671
+ '页面累计访问量': 'Page visits to date',
672
+ '近 30 天记录': 'Last 30 days',
673
+ '最近一天活跃': 'Most recent 24 hours',
674
+ '无需登录即可访问': 'Available without signing in',
675
+ '常用标签': 'Common Tags',
676
+ '作为补充过滤条件': 'Use as extra filters',
677
+ '快速回到常用资源': 'Jump back to favorites',
678
+ '还没有收藏资源,先去逛逛并添加常用入口': 'No favorites yet. Browse around and pin your usual entries first.',
679
+ '继续上次浏览路径': 'Continue where you left off',
680
+ '还没有访问记录,先随便逛一逛': 'No visit history yet. Explore a few resources first.',
681
+ '维护自己的内容': 'Keep your own content up to date',
682
+ '还没有创建内容,可以尝试新增一个资源': 'Nothing created yet. Try adding a resource first.',
683
+ '快捷入口': 'Quick Access',
684
+ '从收藏、历史或我的资源继续浏览和维护内容。': 'Keep browsing and maintaining content from favorites, history, or your own resources.',
685
+ '分类入口': 'Category Entry Points',
686
+ '按主题快速浏览全部资源。': 'Browse all resources quickly by topic.',
687
+ '查看更多': 'View More',
688
+ '全部': 'All',
689
+ '全部可见资源': 'All Accessible Resources',
690
+ '公开资源浏览': 'Public Resource View',
691
+ '返回首页概览': 'Back to Overview',
692
+ '暂无数据': 'No data yet',
693
+ '点击进入该分类结果页': 'Open this category result view',
694
+ '按访问热度挑选常用入口。': 'Pick everyday entries by visit popularity.',
695
+ '快速查看最近维护或新增的内容。': 'Quickly scan what was recently updated or newly added.',
696
+ '继续维护你创建和拥有的资源。': 'Keep maintaining the resources you created or own.',
697
+ '热门资源': 'Popular Resources',
698
+ '最近更新': 'Recently Updated',
699
+ '新增资源': 'Add Resource',
700
+ '筛选': 'Filters',
701
+ '按创建时间归档': 'Archived by created time',
702
+ '清空': 'Clear',
703
+ '清空筛选': 'Clear Filters',
704
+ '类别筛选结果': 'Category Results',
705
+ '范围筛选结果': 'Scope Results',
706
+ '搜索结果': 'Search Results',
707
+ '标签筛选结果': 'Tag Results',
708
+ '筛选结果': 'Filtered Results',
709
+ '类别过滤': 'Category Filters',
710
+ '标签筛选': 'Tag Filters',
711
+ '收起标签': 'Collapse Tags',
712
+ '快捷访问': 'Quick Access',
713
+ '清空标签': 'Clear Tags',
714
+ '没有匹配的资源结果': 'No matching resources found',
715
+ '当前没有可展示的资源': 'No resources are available to show right now',
716
+ '可以放宽关键词、切换类别,或直接清空筛选。': 'Try broader keywords, switch categories, or clear the filters.',
717
+ '从资源区右上角新增资源后,这里会立即出现结果。': 'Once you add a resource from the upper-right action, it will appear here right away.',
718
+ '当前仅显示公开资源。若需要完整内容,请使用 Header 中的登录入口。': 'Only public resources are shown right now. Use the sign-in entry in the header for the full view.',
719
+ '快捷访问、类别和标签筛选已收进左侧导航。': 'Quick access, category filters, and tag filters now live in the left sidebar.',
720
+ '关闭导航遮罩': 'Close navigation overlay',
721
+ '私有': 'Private',
722
+ '公开': 'Public',
723
+ '暂无描述': 'No description',
724
+ '取消收藏': 'Remove Favorite',
725
+ '收藏资源': 'Favorite',
726
+ '编辑资源': 'Edit Resource',
727
+ '更新于': 'Updated',
728
+ '创建于': 'Created',
729
+ '访问': 'Visits',
730
+ '刚刚更新': 'Just now',
731
+ '请登录后操作': 'Please sign in first',
732
+ '用户名或密码错误,请重新输入': 'Username or password is incorrect. Please try again.',
733
+ '类别': 'Categories',
734
+ '标签': 'Tags',
735
+ '返回首页': 'Back to Home',
736
+ '类别管理': 'Category Management',
737
+ '标签管理': 'Tag Management',
738
+ '用户管理': 'User Management',
739
+ '系统配置': 'System Settings',
740
+ '邮件服务': 'Email Service',
741
+ '保存配置': 'Save Settings',
742
+ '站点标题': 'Site Title',
743
+ '站点副标题': 'Site Subtitle',
744
+ '站点 Logo URL': 'Site Logo URL',
745
+ '留空则使用默认图标': 'Leave empty to use the default icon',
746
+ 'Token 有效期(分钟)': 'Token Expiry (minutes)',
747
+ '重置链接有效期(分钟)': 'Reset Link Expiry (minutes)',
748
+ '开放注册': 'Open Registration',
749
+ '限制注册邮箱域名': 'Restrict Email Domain',
750
+ '已开启': 'Enabled',
751
+ '已关闭': 'Disabled',
752
+ '邮箱域名白名单': 'Email Domain Allowlist',
753
+ '测试连接': 'Send Test',
754
+ '加密方式': 'Encryption',
755
+ '无加密': 'No Encryption',
756
+ '发件人信息': 'Sender',
757
+ '发件人名称': 'Sender Name',
758
+ '发件人邮箱': 'Sender Email',
759
+ '显示名称': 'Display Name',
760
+ '邮箱': 'Email',
761
+ '角色': 'Role',
762
+ '状态': 'Status',
763
+ '管理员': 'Admin',
764
+ '普通用户': 'User',
765
+ '启用': 'Active',
766
+ '禁用': 'Disabled',
767
+ '编辑': 'Edit',
768
+ '删除': 'Delete',
769
+ '编辑用户': 'Edit User',
770
+ '新增用户': 'Add User',
771
+ '初始密码': 'Initial Password',
772
+ '账号状态': 'Account Status',
773
+ '密码强度:弱': 'Password Strength: Weak',
774
+ '密码强度:中': 'Password Strength: Medium',
775
+ '密码强度:强': 'Password Strength: Strong',
776
+ '保存成功': 'Saved successfully',
777
+ '请输入有效的邮箱地址': 'Enter a valid email address',
778
+ '请输入当前密码': 'Enter current password',
779
+ '新密码至少8位,需包含字母和数字': 'Password must be at least 8 characters and include letters and numbers',
780
+ '新密码不能与当前密码相同': 'New password must differ from the current password',
781
+ '两次密码不一致': 'Passwords do not match',
782
+ '当前密码错误': 'Current password is incorrect',
783
+ '修改失败': 'Update failed',
784
+ '确认修改': 'Confirm Changes',
785
+ '保存修改': 'Save Changes',
786
+ '注册时间': 'Created At',
787
+ '今天': 'Today',
788
+ '更顺手地进入你的': 'A smoother way into your',
789
+ '常用入口能力': 'Core Access Capabilities',
790
+ '登录后可直接访问的核心能力': 'Core capabilities available right after sign-in',
791
+ '公开与私有资源统一进入,常用内容不再分散。': 'Public and private resources live in one place, so your usual content stays together.',
792
+ '收藏、最近访问与我创建的内容都能快速回到手边。': 'Jump back quickly to favorites, recent visits, and the content you created.',
793
+ '管理员可直接进入系统配置与邮件服务管理。': 'Admins can go straight to system settings and email service management.',
794
+ '当前密码': 'Current Password',
795
+ '取消': 'Cancel',
796
+ '关闭': 'Close',
797
+ '保存': 'Save',
798
+ '保存失败': 'Save failed',
799
+ '密码已修改': 'Password updated',
800
+ '资源名称': 'Resource Name',
801
+ '请输入资源名称': 'Enter resource name',
802
+ '访问权限': 'Access',
803
+ 'Logo URL(可选)': 'Logo URL (Optional)',
804
+ '选择类别': 'Select category',
805
+ '描述': 'Description',
806
+ '描述(可选,最多200字符)': 'Description (Optional, up to 200 characters)',
807
+ 'URL需以 http:// 或 https:// 开头': 'URL must start with http:// or https://',
808
+ 'Logo URL格式不正确': 'Logo URL is invalid',
809
+ '描述最多200字符': 'Description can be at most 200 characters',
810
+ '简短描述这个资源…': 'Add a short description for this resource...',
811
+ '标签(最多10个,每个最多20字符)': 'Tags (up to 10, 20 characters each)',
812
+ '输入标签后回车添加': 'Type a tag and press Enter',
813
+ '启用状态': 'Enabled Status',
814
+ '资源可正常展示与访问': 'This resource can be shown and visited normally',
815
+ '资源将被隐藏并停止对外展示': 'This resource will be hidden from public view',
816
+ '批量录入': 'Bulk Entry',
817
+ '批量录入资源': 'Bulk Resource Entry',
818
+ '每行一条资源,名称与 URL 必填;类别、访问权限、启用状态可选;标签可输入并从已有标签联想选择,最多 10 个。': 'One resource per row. Name and URL are required; category, access, and enabled state are optional; tags support suggestions from existing tags, up to 10 total.',
819
+ '名称': 'Name',
820
+ '操作': 'Actions',
821
+ '输入或选择类别': 'Type or select a category',
822
+ '可选': 'Optional',
823
+ '输入选择标签': 'Type or select tags',
824
+ '添加一行': 'Add Row',
825
+ '批量保存': 'Save All',
826
+ '删除此行': 'Delete row',
827
+ '匹配类别': 'Matching Categories',
828
+ '已有类别': 'Existing Categories',
829
+ '匹配标签': 'Matching Tags',
830
+ '已有标签': 'Existing Tags',
831
+ '收起类别列表': 'Collapse category list',
832
+ '展开类别列表': 'Expand category list',
833
+ '统一访问入口': 'Unified Access Point',
834
+ '统一管理与访问你的资源': 'Manage and access your resources in one place',
835
+ '登录以访问全部资源导航': 'Sign in to access the full resource hub',
836
+ '资源工作台': 'Resource Workspace',
837
+ '工作台': 'Workspace',
838
+ '请输入显示名称': 'Enter display name',
839
+ '请输入邮箱地址': 'Enter email address',
840
+ '资源名称不能为空(最多50字符)': 'Resource name is required (up to 50 characters)',
841
+ '资源导航系统': 'ResourceHub',
842
+ '管理资源分类名称与配色,保持后台和结果页的类别语义一致。': 'Manage category names and colors so admin and results views stay aligned.',
843
+ '新增类别': 'Add Category',
844
+ '颜色': 'Color',
845
+ '资源数': 'Resource Count',
846
+ '暂无类别': 'No categories yet',
847
+ '编辑类别': 'Edit Category',
848
+ '类别名称': 'Category Name',
849
+ '输入类别名称': 'Enter category name',
850
+ '类别名称不能为空': 'Category name is required',
851
+ '类别已更新': 'Category updated',
852
+ '类别已创建': 'Category created',
853
+ '类别已删除': 'Category deleted',
854
+ '删除类别': 'Delete Category',
855
+ '集中管理资源标签,保持筛选体系简洁、可读且一致。': 'Manage resource tags in one place so filtering stays concise, readable, and consistent.',
856
+ '刷新': 'Refresh',
857
+ '标签名': 'Tag Name',
858
+ '使用次数': 'Usage Count',
859
+ '暂无标签': 'No tags yet',
860
+ '批量删除': 'Batch Delete',
861
+ '批量删除标签': 'Batch Delete Tags',
862
+ '删除标签': 'Delete Tag',
863
+ '管理账号、角色与状态,确保后台操作语义清晰而克制。': 'Manage accounts, roles, and statuses with clear, restrained admin controls.',
864
+ '用户已更新': 'User updated',
865
+ '用户已创建': 'User created',
866
+ '用户已删除': 'User deleted',
867
+ '删除用户': 'Delete User',
868
+ '用户的显示名称': 'User display name',
869
+ '仅字母数字下划线,3-20位': 'Letters, numbers, and underscores only, 3-20 characters',
870
+ '显示名称不能为空(最多30字符)': 'Display name is required (up to 30 characters)',
871
+ '系统将自动生成临时密码并发送至用户邮箱,用户可凭临时密码登录后自行修改。': 'The system will generate a temporary password and email it to the user. They can sign in with it and change it afterward.',
872
+ '确认重置': 'Confirm Reset',
873
+ '用户角色': 'User Role',
874
+ '暂无用户': 'No users yet',
875
+ '维护站点基础信息、注册策略与安全有效期配置。': 'Manage site identity, registration rules, and security expiry settings.',
876
+ '留空时显示品牌色首字母图标': 'Leave empty to show a brand-colored initial icon',
877
+ '多个域名用英文逗号分隔,留空则不限制': 'Use commas to separate multiple domains. Leave empty for no restriction.',
878
+ '系统配置已保存': 'System settings saved',
879
+ '管理 SMTP 连接与发件人信息,保持后台配置区的层级和交互一致。': 'Manage SMTP connectivity and sender details while keeping the admin settings area consistent.',
880
+ 'SMTP 主机为空时,系统使用模拟邮件模式:邮件不会真实发送,仅在前端弹窗预览。': 'When SMTP host is empty, the system uses mock email mode. Messages are not sent for real and are only previewed in a frontend modal.',
881
+ 'SMTP 主机': 'SMTP Host',
882
+ 'SMTP 端口': 'SMTP Port',
883
+ 'SMTP 用户名': 'SMTP Username',
884
+ 'SMTP 密码': 'SMTP Password',
885
+ 'smtp.example.com(留空使用模拟模式)': 'smtp.example.com (leave empty for mock mode)',
886
+ '保持 *** 则不更改当前密码': 'Keep *** to retain the current password',
887
+ '填写 `***` 表示不修改当前已保存的密码': 'Enter `***` to keep the currently saved password',
888
+ '邮件配置已保存': 'Email settings saved',
889
+ '发送失败': 'Send failed',
890
+ '更新失败': 'Update failed',
891
+ '创建失败': 'Create failed',
892
+ });
893
+
894
+ Object.assign(RH_STATIC_TRANSLATIONS.ja, {
895
+ '浅色': 'ライト',
896
+ '深色': 'ダーク',
897
+ '跟随系统': 'システム',
898
+ '登录': 'ログイン',
899
+ '个人信息': 'プロフィール',
900
+ '修改密码': 'パスワード変更',
901
+ '后台管理': '管理画面',
902
+ '注销登录': 'ログアウト',
903
+ '搜索资源...': 'リソースを検索...',
904
+ '搜索资源名称、描述、标签... (Ctrl+K)': '名前・説明・タグを検索... (Ctrl+K)',
905
+ '加载中…': '読み込み中…',
906
+ '确认删除': '削除する',
907
+ '模拟邮件(开发预览)': 'モックメール(プレビュー)',
908
+ '收件人:': '宛先:',
909
+ '主题:': '件名:',
910
+ '正文:': '本文:',
911
+ '忘记密码': 'パスワードを忘れた場合',
912
+ '登录账号': 'ログイン',
913
+ '用户名': 'ユーザー名',
914
+ '请输入用户名': 'ユーザー名を入力',
915
+ '密码': 'パスワード',
916
+ '请输入密码': 'パスワードを入力',
917
+ '隐藏密码': 'パスワードを隠す',
918
+ '显示密码': 'パスワードを表示',
919
+ '登录中...': 'ログイン中...',
920
+ '忘记密码?': 'パスワードを忘れましたか?',
921
+ '没有账号?': 'アカウントがありませんか?',
922
+ '注册账号': 'アカウント登録',
923
+ '注册成功!初始密码已发送至邮箱': '登録に成功しました。初期パスワードをメール送信しました。',
924
+ '注册失败': '登録に失敗しました',
925
+ '注册功能暂未开放': '登録は現在利用できません',
926
+ '去登录': 'ログインへ',
927
+ '邀请与注册': '招待と登録',
928
+ '已有账号?': 'すでにアカウントをお持ちですか?',
929
+ '返回登录': 'ログインに戻る',
930
+ '发送重置链接': '再設定リンクを送信',
931
+ '账号恢复': 'アカウント復旧',
932
+ '若该邮箱已注册,重置链接已发送,请查收': '登録済みの場合、再設定リンクを送信しました。',
933
+ '密码已重置,请登录': 'パスワードを再設定しました。ログインしてください。',
934
+ '重置失败': '再設定に失敗しました',
935
+ '链接已失效或过期': 'リンクは無効または期限切れです',
936
+ '请重新发送密码重置链接': '新しい再設定リンクを送信してください',
937
+ '重新发送重置链接': '再設定リンクを再送信',
938
+ '重置密码': 'パスワード再設定',
939
+ '设置新密码': '新しいパスワードを設定',
940
+ '新密码': '新しいパスワード',
941
+ '确认新密码': '新しいパスワードの確認',
942
+ '至少8位,含字母和数字': '8文字以上、英字と数字を含めてください',
943
+ '再次输入新密码': '新しいパスワードをもう一度入力',
944
+ '再次输入密码': 'パスワードをもう一度入力',
945
+ '初始化成功!请登录': '初期設定が完了しました。ログインしてください。',
946
+ '初始化失败': '初期設定に失敗しました',
947
+ '首次初始化': '初回セットアップ',
948
+ '欢迎使用资源导航系统': 'ResourceHub へようこそ',
949
+ '请先完成初始化,创建管理员账号': 'まず初期設定を完了し、管理者アカウントを作成してください。',
950
+ '管理员用户名': '管理者ユーザー名',
951
+ '管理员显示名称': '管理者表示名',
952
+ '管理员邮箱': '管理者メール',
953
+ '管理员密码': '管理者パスワード',
954
+ '确认密码': 'パスワード確認',
955
+ '显示给其他用户的名称': '他のユーザーに表示される名前',
956
+ '完成初始化': 'セットアップ完了',
957
+ '资源导航': 'リソースハブ',
958
+ '我的收藏': 'お気に入り',
959
+ '最近访问': '最近のアクセス',
960
+ '我创建的': '自分のリソース',
961
+ '按热度': '人気順',
962
+ '按创建时间': '作成日時順',
963
+ '按更新时间': '更新日時順',
964
+ '卡片视图': 'カード表示',
965
+ '列表视图': 'リスト表示',
966
+ '时间轴视图': 'タイムライン表示',
967
+ '全部资源': 'すべてのリソース',
968
+ '公开资源': '公開リソース',
969
+ '总访问量': '総アクセス数',
970
+ '近30日访问': '直近30日のアクセス',
971
+ '近24小时访问': '直近24時間のアクセス',
972
+ '浏览全部资源': 'すべてのリソースを見る',
973
+ '可见资源': '表示可能なリソース',
974
+ '当前可访问入口': '今すぐ使える入口',
975
+ '资源分类': 'リソース分類',
976
+ '按主题浏览入口': 'テーマ別に見る',
977
+ '我的资源': '自分のリソース',
978
+ '我创建与维护的内容': '自分で作成・管理している内容',
979
+ '页面累计访问量': 'ページ累計アクセス数',
980
+ '近 30 天记录': '過去 30 日の記録',
981
+ '最近一天活跃': '直近 1 日のアクティブ',
982
+ '无需登录即可访问': 'ログインなしでアクセス可能',
983
+ '常用标签': 'よく使うタグ',
984
+ '作为补充过滤条件': '追加フィルターとして利用',
985
+ '快速回到常用资源': 'よく使うリソースへすぐ戻る',
986
+ '还没有收藏资源,先去逛逛并添加常用入口': 'お気に入りはまだありません。先にいくつか見て、よく使う入口を追加してください。',
987
+ '继续上次浏览路径': '前回の続きから確認',
988
+ '还没有访问记录,先随便逛一逛': '閲覧履歴はまだありません。まずはいくつか見てみましょう。',
989
+ '维护自己的内容': '自分の内容を管理する',
990
+ '还没有创建内容,可以尝试新增一个资源': 'まだ作成した内容がありません。まずはリソースを追加してみましょう。',
991
+ '快捷入口': 'クイック入口',
992
+ '从收藏、历史或我的资源继续浏览和维护内容。': 'お気に入り、履歴、自分のリソースから続けて閲覧・管理できます。',
993
+ '分类入口': 'カテゴリ入口',
994
+ '按主题快速浏览全部资源。': 'テーマごとにすべてのリソースをすばやく確認できます。',
995
+ '查看更多': 'もっと見る',
996
+ '全部': 'すべて',
997
+ '全部可见资源': '表示可能なすべてのリソース',
998
+ '公开资源浏览': '公開リソース一覧',
999
+ '返回首页概览': '概要ページに戻る',
1000
+ '暂无数据': 'データはまだありません',
1001
+ '点击进入该分类结果页': 'このカテゴリの結果ページを開く',
1002
+ '按访问热度挑选常用入口。': 'アクセス数の多い入口からよく使うものを選べます。',
1003
+ '快速查看最近维护或新增的内容。': '最近更新・追加された内容をすばやく確認できます。',
1004
+ '继续维护你创建和拥有的资源。': '自分で作成・所有しているリソースを引き続き管理できます。',
1005
+ '热门资源': '人気のリソース',
1006
+ '最近更新': '最近更新',
1007
+ '新增资源': 'リソースを追加',
1008
+ '筛选': '絞り込み',
1009
+ '按创建时间归档': '作成日時でアーカイブ',
1010
+ '清空': 'クリア',
1011
+ '清空筛选': '絞り込みを解除',
1012
+ '类别筛选结果': 'カテゴリ絞り込み結果',
1013
+ '范围筛选结果': '範囲の絞り込み結果',
1014
+ '搜索结果': '検索結果',
1015
+ '标签筛选结果': 'タグの絞り込み結果',
1016
+ '筛选结果': '絞り込み結果',
1017
+ '类别过滤': 'カテゴリ絞り込み',
1018
+ '标签筛选': 'タグ絞り込み',
1019
+ '收起标签': 'タグを折りたたむ',
1020
+ '快捷访问': 'クイックアクセス',
1021
+ '清空标签': 'タグをクリア',
1022
+ '没有匹配的资源结果': '一致するリソースが見つかりません',
1023
+ '当前没有可展示的资源': '現在表示できるリソースはありません',
1024
+ '可以放宽关键词、切换类别,或直接清空筛选。': 'キーワードを広げるか、カテゴリを切り替えるか、絞り込みをクリアしてください。',
1025
+ '从资源区右上角新增资源后,这里会立即出现结果。': '右上の追加操作からリソースを作成すると、ここにすぐ表示されます。',
1026
+ '当前仅显示公开资源。若需要完整内容,请使用 Header 中的登录入口。': '現在は公開リソースのみ表示しています。すべてを見るにはヘッダーのログイン入口を使ってください。',
1027
+ '快捷访问、类别和标签筛选已收进左侧导航。': 'クイックアクセス、カテゴリ、タグの絞り込みは左側ナビにまとめています。',
1028
+ '关闭导航遮罩': 'ナビゲーションのオーバーレイを閉じる',
1029
+ '私有': '非公開',
1030
+ '公开': '公開',
1031
+ '暂无描述': '説明なし',
1032
+ '取消收藏': 'お気に入り解除',
1033
+ '收藏资源': 'お気に入り',
1034
+ '编辑资源': 'リソース編集',
1035
+ '更新于': '更新:',
1036
+ '创建于': '作成:',
1037
+ '访问': 'アクセス',
1038
+ '刚刚更新': 'たった今',
1039
+ '请登录后操作': 'ログイン後に操作してください',
1040
+ '用户名或密码错误,请重新输入': 'ユーザー名またはパスワードが正しくありません。もう一度お試しください。',
1041
+ '返回首页': 'ホームへ戻る',
1042
+ '类别管理': 'カテゴリ管理',
1043
+ '标签管理': 'タグ管理',
1044
+ '用户管理': 'ユーザー管理',
1045
+ '系统配置': 'システム設定',
1046
+ '邮件服务': 'メール設定',
1047
+ '保存配置': '設定を保存',
1048
+ '站点标题': 'サイトタイトル',
1049
+ '站点副标题': 'サイトサブタイトル',
1050
+ 'Token 有效期(分钟)': 'Token 有効期限(分)',
1051
+ '重置链接有效期(分钟)': '再設定リンク有効期限(分)',
1052
+ '开放注册': '登録を開放',
1053
+ '限制注册邮箱域名': '登録メールドメイン制限',
1054
+ '已开启': '有効',
1055
+ '已关闭': '無効',
1056
+ '邮箱域名白名单': 'メールドメイン許可リスト',
1057
+ '测试连接': '接続テスト',
1058
+ '加密方式': '暗号化方式',
1059
+ '无加密': '暗号化なし',
1060
+ '发件人信息': '送信者情報',
1061
+ '发件人名称': '送信者名',
1062
+ '发件人邮箱': '送信者メール',
1063
+ '显示名称': '表示名',
1064
+ '邮箱': 'メール',
1065
+ '角色': '権限',
1066
+ '状态': '状態',
1067
+ '管理员': '管理者',
1068
+ '普通用户': '一般ユーザー',
1069
+ '启用': '有効',
1070
+ '禁用': '無効',
1071
+ '编辑': '編集',
1072
+ '删除': '削除',
1073
+ '编辑用户': 'ユーザー編集',
1074
+ '新增用户': 'ユーザー追加',
1075
+ '初始密码': '初期パスワード',
1076
+ '账号状态': 'アカウント状態',
1077
+ '密码强度:弱': 'パスワード強度:弱',
1078
+ '密码强度:中': 'パスワード強度:中',
1079
+ '密码强度:强': 'パスワード強度:強',
1080
+ '保存成功': '保存しました',
1081
+ '请输入有效的邮箱地址': '有効なメールアドレスを入力してください',
1082
+ '请输入当前密码': '現在のパスワードを入力してください',
1083
+ '新密码至少8位,需包含字母和数字': '新しいパスワードは8文字以上で、英字と数字を含めてください',
1084
+ '新密码不能与当前密码相同': '新しいパスワードは現在のものと異なる必要があります',
1085
+ '两次密码不一致': '確認用パスワードが一致しません',
1086
+ '当前密码错误': '現在のパスワードが正しくありません',
1087
+ '修改失败': '更新に失敗しました',
1088
+ '确认修改': '変更を確定',
1089
+ '保存修改': '変更を保存',
1090
+ '注册时间': '登録日時',
1091
+ '今天': '今日',
1092
+ '更顺手地进入你的': 'あなたの',
1093
+ '常用入口能力': 'よく使う入口機能',
1094
+ '登录后可直接访问的核心能力': 'ログイン後すぐ使える主要機能',
1095
+ '公开与私有资源统一进入,常用内容不再分散。': '公開・非公開リソースをまとめて扱え、よく使う内容が分散しません。',
1096
+ '收藏、最近访问与我创建的内容都能快速回到手边。': 'お気に入り、最近のアクセス、自分で作成した内容へすぐ戻れます。',
1097
+ '管理员可直接进入系统配置与邮件服务管理。': '管理者はシステム設定とメールサービス管理へ直接入れます。',
1098
+ '当前密码': '現在のパスワード',
1099
+ '取消': 'キャンセル',
1100
+ '关闭': '閉じる',
1101
+ '保存': '保存',
1102
+ '保存失败': '保存に失敗しました',
1103
+ '密码已修改': 'パスワードを変更しました',
1104
+ '资源名称': 'リソース名',
1105
+ '请输入资源名称': 'リソース名を入力',
1106
+ '访问权限': 'アクセス権限',
1107
+ 'Logo URL(可选)': 'ロゴ URL(任意)',
1108
+ '选择类别': 'カテゴリを選択',
1109
+ '描述': '説明',
1110
+ '描述(可选,最多200字符)': '説明(任意、200文字まで)',
1111
+ 'URL需以 http:// 或 https:// 开头': 'URL は http:// または https:// で始めてください',
1112
+ 'Logo URL格式不正确': 'Logo URL の形式が正しくありません',
1113
+ '描述最多200字符': '説明は最大 200 文字までです',
1114
+ '简短描述这个资源…': 'このリソースを簡単に説明...',
1115
+ '标签(最多10个,每个最多20字符)': 'タグ(最大10件、各20文字まで)',
1116
+ '输入标签后回车添加': 'タグを入力して Enter で追加',
1117
+ '启用状态': '有効状態',
1118
+ '资源可正常展示与访问': 'このリソースは通常どおり表示・アクセスできます',
1119
+ '资源将被隐藏并停止对外展示': 'このリソースは非表示になり、外部には表示されません',
1120
+ '批量录入': '一括入力',
1121
+ '批量录入资源': 'リソース一括入力',
1122
+ '每行一条资源,名称与 URL 必填;类别、访问权限、启用状态可选;标签可输入并从已有标签联想选择,最多 10 个。': '1 行につき 1 件のリソースです。名前と URL は必須、カテゴリ・アクセス権限・有効状態は任意、タグは既存タグから候補選択でき、最大 10 件までです。',
1123
+ '名称': '名前',
1124
+ '操作': '操作',
1125
+ '输入或选择类别': '入力またはカテゴリを選択',
1126
+ '可选': '任意',
1127
+ '输入选择标签': '入力またはタグを選択',
1128
+ '添加一行': '行を追加',
1129
+ '批量保存': 'まとめて保存',
1130
+ '删除此行': 'この行を削除',
1131
+ '匹配类别': '一致するカテゴリ',
1132
+ '已有类别': '既存のカテゴリ',
1133
+ '匹配标签': '一致するタグ',
1134
+ '已有标签': '既存のタグ',
1135
+ '收起类别列表': 'カテゴリ一覧を閉じる',
1136
+ '展开类别列表': 'カテゴリ一覧を開く',
1137
+ '统一访问入口': '統一アクセス入口',
1138
+ '统一管理与访问你的资源': 'リソースを一元管理してアクセス',
1139
+ '登录以访问全部资源导航': 'ログインしてすべてのリソースナビにアクセス',
1140
+ '资源工作台': 'リソースワークスペース',
1141
+ '工作台': 'ワークスペース',
1142
+ '请输入显示名称': '表示名を入力',
1143
+ '请输入邮箱地址': 'メールアドレスを入力',
1144
+ '资源名称不能为空(最多50字符)': 'リソース名は必須です(50文字以内)',
1145
+ '资源导航系统': 'ResourceHub',
1146
+ '管理资源分类名称与配色,保持后台和结果页的类别语义一致。': 'リソース分類の名称と配色を管理し、管理画面と結果ページの分類表現を揃えます。',
1147
+ '新增类别': 'カテゴリを追加',
1148
+ '颜色': '色',
1149
+ '资源数': 'リソース数',
1150
+ '暂无类别': 'カテゴリはまだありません',
1151
+ '编辑类别': 'カテゴリ編集',
1152
+ '类别名称': 'カテゴリ名',
1153
+ '输入类别名称': 'カテゴリ名を入力',
1154
+ '类别名称不能为空': 'カテゴリ名は必須です',
1155
+ '类别已更新': 'カテゴリを更新しました',
1156
+ '类别已创建': 'カテゴリを作成しました',
1157
+ '类别已删除': 'カテゴリを削除しました',
1158
+ '删除类别': 'カテゴリを削除',
1159
+ '集中管理资源标签,保持筛选体系简洁、可读且一致。': 'リソースタグを一元管理し、絞り込み体系を簡潔で読みやすく一貫したものに保ちます。',
1160
+ '刷新': '更新',
1161
+ '标签名': 'タグ名',
1162
+ '使用次数': '使用回数',
1163
+ '暂无标签': 'タグはまだありません',
1164
+ '批量删除': '一括削除',
1165
+ '批量删除标签': 'タグを一括削除',
1166
+ '删除标签': 'タグを削除',
1167
+ '管理账号、角色与状态,确保后台操作语义清晰而克制。': 'アカウント、権限、状態を管理し、管理操作の意味が明確で過度にならないようにします。',
1168
+ '用户已更新': 'ユーザーを更新しました',
1169
+ '用户已创建': 'ユーザーを作成しました',
1170
+ '用户已删除': 'ユーザーを削除しました',
1171
+ '删除用户': 'ユーザーを削除',
1172
+ '用户的显示名称': 'ユーザーの表示名',
1173
+ '仅字母数字下划线,3-20位': '英数字とアンダースコアのみ、3〜20 文字',
1174
+ '显示名称不能为空(最多30字符)': '表示名は必須です(30文字以内)',
1175
+ '系统将自动生成临时密码并发送至用户邮箱,用户可凭临时密码登录后自行修改。': 'システムが一時パスワードを自動生成してユーザーのメールへ送信します。ユーザーはそのパスワードでログイン後、自分で変更できます。',
1176
+ '确认重置': 'リセットを確認',
1177
+ '用户角色': 'ユーザー権限',
1178
+ '暂无用户': 'ユーザーはまだありません',
1179
+ '维护站点基础信息、注册策略与安全有效期配置。': 'サイトの基本情報、登録ポリシー、安全関連の有効期限設定を管理します。',
1180
+ '留空时显示品牌色首字母图标': '空欄の場合はブランドカラーの頭文字アイコンを表示します',
1181
+ '多个域名用英文逗号分隔,留空则不限制': '複数ドメインは半角カンマで区切ってください。空欄なら制限しません',
1182
+ '系统配置已保存': 'システム設定を保存しました',
1183
+ '管理 SMTP 连接与发件人信息,保持后台配置区的层级和交互一致。': 'SMTP 接続と送信者情報を管理し、管理設定エリアの階層と操作感を揃えます。',
1184
+ 'SMTP 主机为空时,系统使用模拟邮件模式:邮件不会真实发送,仅在前端弹窗预览。': 'SMTP ホストが空の場合、システムはモックメールモードを使用します。メールは実際には送信されず、フロントエンドのモーダルでのみプレビューされます。',
1185
+ 'SMTP 主机': 'SMTP ホスト',
1186
+ 'SMTP 端口': 'SMTP ポート',
1187
+ 'SMTP 用户名': 'SMTP ユーザー名',
1188
+ 'SMTP 密码': 'SMTP パスワード',
1189
+ 'smtp.example.com(留空使用模拟模式)': 'smtp.example.com(空欄でモックモード)',
1190
+ '保持 *** 则不更改当前密码': '*** のままなら現在のパスワードを変更しません',
1191
+ '填写 `***` 表示不修改当前已保存的密码': '`***` と入力すると現在保存されているパスワードを変更しません',
1192
+ '邮件配置已保存': 'メール設定を保存しました',
1193
+ '发送失败': '送信に失敗しました',
1194
+ '更新失败': '更新に失敗しました',
1195
+ '创建失败': '作成に失敗しました',
1196
+ });
1197
+
1198
+ function normalizeLocale(candidate) {
1199
+ if (!candidate) return RH_DEFAULT_LOCALE;
1200
+ const normalized = String(candidate).trim();
1201
+ if (!normalized) return RH_DEFAULT_LOCALE;
1202
+
1203
+ const firstToken = normalized.split(',')[0].trim();
1204
+ const languageToken = firstToken.split(';')[0].trim().toLowerCase();
1205
+
1206
+ if (RH_SUPPORTED_LOCALES.includes(firstToken)) return firstToken;
1207
+ if (RH_SUPPORTED_LOCALES.includes(normalized)) return normalized;
1208
+ if (languageToken === 'zh-hant' || languageToken.startsWith('zh-tw') || languageToken.startsWith('zh-hk') || languageToken.startsWith('zh-mo')) {
1209
+ return 'zh-Hant';
1210
+ }
1211
+ if (languageToken === 'zh-hans' || languageToken.startsWith('zh-cn') || languageToken.startsWith('zh-sg') || languageToken === 'zh') {
1212
+ return 'zh-Hans';
1213
+ }
1214
+ if (languageToken.startsWith('en')) return 'en';
1215
+ if (languageToken.startsWith('ja')) return 'ja';
1216
+ return RH_DEFAULT_LOCALE;
1217
+ }
1218
+
1219
+ function detectBrowserLocale() {
1220
+ const browserLocales = [];
1221
+ if (Array.isArray(window.navigator?.languages)) browserLocales.push(...window.navigator.languages);
1222
+ if (window.navigator?.language) browserLocales.push(window.navigator.language);
1223
+
1224
+ for (const candidate of browserLocales) {
1225
+ const locale = normalizeLocale(candidate);
1226
+ if (locale) return locale;
1227
+ }
1228
+
1229
+ return RH_DEFAULT_LOCALE;
1230
+ }
1231
+
1232
+ let currentLocale = detectBrowserLocale();
1233
+
1234
+ function withTrimmedString(value, mapper) {
1235
+ if (typeof value !== 'string') return value;
1236
+ const match = value.match(/^(\s*)(.*?)(\s*)$/s);
1237
+ const leading = match?.[1] || '';
1238
+ const core = match?.[2] || '';
1239
+ const trailing = match?.[3] || '';
1240
+ if (!core) return value;
1241
+ return `${leading}${mapper(core)}${trailing}`;
1242
+ }
1243
+
1244
+ function translateFromStaticMap(locale, coreText) {
1245
+ return RH_STATIC_TRANSLATIONS[locale]?.[coreText] || null;
1246
+ }
1247
+
1248
+ function translateFromPatterns(locale, coreText) {
1249
+ for (const translator of RH_PATTERN_TRANSLATORS) {
1250
+ const match = coreText.match(translator.test);
1251
+ if (match) return translator.render(locale, match);
1252
+ }
1253
+ return null;
1254
+ }
1255
+
1256
+ function translateText(localeOrText, maybeText) {
1257
+ const locale = maybeText === undefined ? currentLocale : normalizeLocale(localeOrText);
1258
+ const sourceText = maybeText === undefined ? localeOrText : maybeText;
1259
+ if (typeof sourceText !== 'string') return sourceText;
1260
+ if (locale === 'zh-Hans') return sourceText;
1261
+
1262
+ return withTrimmedString(sourceText, (coreText) => {
1263
+ const staticTranslation = translateFromStaticMap(locale, coreText);
1264
+ if (staticTranslation) return staticTranslation;
1265
+ const patternTranslation = translateFromPatterns(locale, coreText);
1266
+ if (patternTranslation) return patternTranslation;
1267
+ return coreText;
1268
+ });
1269
+ }
1270
+
1271
+ function transformDomProps(props) {
1272
+ if (!props) return props;
1273
+
1274
+ let nextProps = props;
1275
+ ['placeholder', 'title', 'aria-label', 'ariaLabel'].forEach((propName) => {
1276
+ if (typeof props[propName] !== 'string') return;
1277
+ const translated = translateText(props[propName]);
1278
+ if (translated === props[propName]) return;
1279
+ if (nextProps === props) nextProps = { ...props };
1280
+ nextProps[propName] = translated;
1281
+ });
1282
+
1283
+ if (props.readOnly && typeof props.value === 'string') {
1284
+ const translatedValue = translateText(props.value);
1285
+ if (translatedValue !== props.value) {
1286
+ if (nextProps === props) nextProps = { ...props };
1287
+ nextProps.value = translatedValue;
1288
+ }
1289
+ }
1290
+
1291
+ return nextProps;
1292
+ }
1293
+
1294
+ function transformChild(child) {
1295
+ if (typeof child === 'string') return translateText(child);
1296
+ if (Array.isArray(child)) return child.map(transformChild);
1297
+ return child;
1298
+ }
1299
+
1300
+ if (!window.__rhPatchedCreateElement) {
1301
+ const originalCreateElement = React.createElement.bind(React);
1302
+ React.createElement = function patchedCreateElement(type, props, ...children) {
1303
+ const nextProps = typeof type === 'string' ? transformDomProps(props) : props;
1304
+ const nextChildren = children.map(transformChild);
1305
+ return originalCreateElement(type, nextProps, ...nextChildren);
1306
+ };
1307
+ window.__rhPatchedCreateElement = true;
1308
+ }
1309
+
1310
+ function applyLocale(locale) {
1311
+ currentLocale = normalizeLocale(locale);
1312
+ document.documentElement.lang = currentLocale;
1313
+ document.documentElement.dataset.rhLocale = currentLocale;
1314
+ return currentLocale;
1315
+ }
1316
+
1317
+ function getCurrentLocale() {
1318
+ return currentLocale;
1319
+ }
1320
+
1321
+ function getIntlLocale(locale = currentLocale) {
1322
+ return RH_INTL_LOCALE_MAP[normalizeLocale(locale)] || RH_INTL_LOCALE_MAP[RH_DEFAULT_LOCALE];
1323
+ }
1324
+
1325
+ function getLocaleNativeLabel(locale) {
1326
+ return RH_LOCALE_NATIVE_LABELS[normalizeLocale(locale)] || RH_LOCALE_NATIVE_LABELS[RH_DEFAULT_LOCALE];
1327
+ }
1328
+
1329
+ function useI18n() {
1330
+ const state = window.useAppState?.();
1331
+ const dispatch = window.useAppDispatch?.();
1332
+ const locale = normalizeLocale(state?.locale || currentLocale);
1333
+
1334
+ React.useEffect(() => {
1335
+ applyLocale(locale);
1336
+ }, [locale]);
1337
+
1338
+ const setLocale = React.useCallback((nextLocale) => {
1339
+ const resolved = normalizeLocale(nextLocale);
1340
+ if (dispatch) {
1341
+ dispatch({ type: 'SET_LOCALE', locale: resolved });
1342
+ return;
1343
+ }
1344
+ applyLocale(resolved);
1345
+ }, [dispatch]);
1346
+
1347
+ const t = React.useCallback((text) => translateText(locale, text), [locale]);
1348
+
1349
+ return {
1350
+ locale,
1351
+ setLocale,
1352
+ t,
1353
+ locales: RH_SUPPORTED_LOCALES,
1354
+ defaultLocale: RH_DEFAULT_LOCALE,
1355
+ getNativeLabel: getLocaleNativeLabel,
1356
+ intlLocale: getIntlLocale(locale),
1357
+ };
1358
+ }
1359
+
1360
+ window.i18n = {
1361
+ SUPPORTED_LOCALES: RH_SUPPORTED_LOCALES,
1362
+ DEFAULT_LOCALE: RH_DEFAULT_LOCALE,
1363
+ LOCALE_NATIVE_LABELS: RH_LOCALE_NATIVE_LABELS,
1364
+ normalizeLocale,
1365
+ detectBrowserLocale,
1366
+ applyLocale,
1367
+ translateText,
1368
+ getCurrentLocale,
1369
+ getIntlLocale,
1370
+ getLocaleNativeLabel,
1371
+ useI18n,
1372
+ };
1373
+
1374
+ applyLocale(currentLocale);