skill-base 2.0.4 → 2.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +177 -115
- package/bin/skill-base.js +29 -3
- package/package.json +4 -1
- package/src/cappy.js +416 -0
- package/src/database.js +11 -0
- package/src/index.js +125 -25
- package/src/middleware/auth.js +96 -32
- package/src/routes/auth.js +1 -1
- package/src/routes/skills.js +10 -5
- package/src/utils/zip.js +15 -4
- package/static/android-chrome-192x192.png +0 -0
- package/static/android-chrome-512x512.png +0 -0
- package/static/apple-touch-icon.png +0 -0
- package/static/assets/index-BkwByEEp.css +1 -0
- package/static/assets/index-CB4Diul3.js +209 -0
- package/static/favicon-16x16.png +0 -0
- package/static/favicon-32x32.png +0 -0
- package/static/favicon.ico +0 -0
- package/static/favicon.svg +14 -0
- package/static/index.html +18 -248
- package/static/site.webmanifest +1 -0
- package/static/admin/users.html +0 -593
- package/static/cli-code.html +0 -203
- package/static/css/.gitkeep +0 -0
- package/static/css/style.css +0 -1567
- package/static/diff.html +0 -466
- package/static/file.html +0 -443
- package/static/js/.gitkeep +0 -0
- package/static/js/admin/users.js +0 -346
- package/static/js/app.js +0 -508
- package/static/js/auth.js +0 -151
- package/static/js/cli-code.js +0 -184
- package/static/js/collaborators.js +0 -283
- package/static/js/diff.js +0 -540
- package/static/js/file.js +0 -619
- package/static/js/i18n.js +0 -739
- package/static/js/index.js +0 -168
- package/static/js/publish.js +0 -718
- package/static/js/settings.js +0 -124
- package/static/js/setup.js +0 -157
- package/static/js/skill.js +0 -808
- package/static/login.html +0 -82
- package/static/publish.html +0 -459
- package/static/settings.html +0 -163
- package/static/setup.html +0 -101
- package/static/skill.html +0 -851
package/static/js/i18n.js
DELETED
|
@@ -1,739 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Skill Base - i18n Internationalization Module
|
|
3
|
-
* Detects browser language and provides translations.
|
|
4
|
-
* Chinese (zh-*) users get Chinese; all others get English (default).
|
|
5
|
-
*
|
|
6
|
-
* Usage:
|
|
7
|
-
* t('key') - get translated string
|
|
8
|
-
* applyI18n() - apply data-i18n attributes to DOM
|
|
9
|
-
* applyI18nPlaceholders() - apply data-i18n-placeholder attributes
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
(function () {
|
|
13
|
-
'use strict';
|
|
14
|
-
|
|
15
|
-
// ── Language detection ──────────────────────────────────────────────────────
|
|
16
|
-
// Priority: localStorage (user preference) > browser language
|
|
17
|
-
const _storedLang = localStorage.getItem('skb-lang');
|
|
18
|
-
let isChinese;
|
|
19
|
-
if (_storedLang === 'zh' || _storedLang === 'en') {
|
|
20
|
-
isChinese = _storedLang === 'zh';
|
|
21
|
-
} else {
|
|
22
|
-
const _browserLang = (navigator.language || navigator.languages?.[0] || 'en').toLowerCase();
|
|
23
|
-
isChinese = _browserLang.startsWith('zh');
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// ── Translation tables ───────────────────────────────────────────────────────
|
|
27
|
-
const zh = {
|
|
28
|
-
// Navbar
|
|
29
|
-
'nav.home': '首页',
|
|
30
|
-
'nav.publish': '发布',
|
|
31
|
-
'nav.settings': '账户设置',
|
|
32
|
-
'nav.admin': '用户管理',
|
|
33
|
-
'nav.logout': '登出',
|
|
34
|
-
'nav.login': '登录',
|
|
35
|
-
|
|
36
|
-
// Common buttons / labels
|
|
37
|
-
'btn.cancel': '取消',
|
|
38
|
-
'btn.save': '保存修改',
|
|
39
|
-
'btn.add': '添加',
|
|
40
|
-
'btn.edit': '编辑',
|
|
41
|
-
'btn.delete': '删除',
|
|
42
|
-
'btn.confirm': '确认删除',
|
|
43
|
-
'btn.remove': '移除',
|
|
44
|
-
'btn.close': '关闭',
|
|
45
|
-
'btn.reset': '确认重置',
|
|
46
|
-
'btn.back': '返回详情页',
|
|
47
|
-
|
|
48
|
-
// Time (formatDate)
|
|
49
|
-
'time.justNow': '刚刚',
|
|
50
|
-
'time.minutesAgo': '分钟前',
|
|
51
|
-
'time.hoursAgo': '小时前',
|
|
52
|
-
'time.daysAgo': '天前',
|
|
53
|
-
|
|
54
|
-
// Common states
|
|
55
|
-
'state.loading': '加载中...',
|
|
56
|
-
'state.noDesc': '暂无描述',
|
|
57
|
-
'state.unknown': '未知',
|
|
58
|
-
'state.noData': '暂无数据',
|
|
59
|
-
'state.empty': '空目录',
|
|
60
|
-
|
|
61
|
-
// Login page
|
|
62
|
-
'login.title': '登录 - Skill Base',
|
|
63
|
-
'login.subtitle': '// 内网 Skill 管理平台 · 需要凭证',
|
|
64
|
-
'login.submit': '执行登录',
|
|
65
|
-
'login.loading': '登录中...',
|
|
66
|
-
'login.errUsername': '请输入用户名',
|
|
67
|
-
'login.errPassword': '请输入密码',
|
|
68
|
-
'login.errFailed': '登录失败,请重试',
|
|
69
|
-
'login.unauthorized': '未授权,请重新登录',
|
|
70
|
-
|
|
71
|
-
// Setup page (initial admin setup)
|
|
72
|
-
'setup.title': '初始化 - Skill Base',
|
|
73
|
-
'setup.subtitle': '// 首次启动 · 请设置管理员账号',
|
|
74
|
-
'setup.usernameHint': '3-50 个字符',
|
|
75
|
-
'setup.passwordHint': '至少 6 个字符',
|
|
76
|
-
'setup.submit': '创建管理员账号',
|
|
77
|
-
'setup.loading': '创建中...',
|
|
78
|
-
'setup.success': '管理员账号创建成功,即将跳转到登录页...',
|
|
79
|
-
'setup.errUsername': '请输入用户名',
|
|
80
|
-
'setup.errUsernameLength': '用户名需要 3-50 个字符',
|
|
81
|
-
'setup.errPassword': '请输入密码',
|
|
82
|
-
'setup.errPasswordLength': '密码至少需要 6 个字符',
|
|
83
|
-
'setup.errPasswordMismatch': '两次输入的密码不一致',
|
|
84
|
-
'setup.errFailed': '创建失败,请重试',
|
|
85
|
-
|
|
86
|
-
// Index page
|
|
87
|
-
'index.searchPlaceholder': '搜索 Skill...',
|
|
88
|
-
'index.fabTitle': '发布新版本',
|
|
89
|
-
'index.noSkills': '暂无 Skill,去发布第一个吧',
|
|
90
|
-
'index.noResults': '未找到包含 "{q}" 的 Skill',
|
|
91
|
-
'index.noResultsHint': '试试其他关键词?',
|
|
92
|
-
'index.publishBtn': '发布 Skill',
|
|
93
|
-
'index.loadFailed': '加载失败: ',
|
|
94
|
-
|
|
95
|
-
// Skill detail page
|
|
96
|
-
'skill.title': 'Skill 详情 - Skill Base',
|
|
97
|
-
'skill.breadcrumbHome': '首页',
|
|
98
|
-
'skill.loading': '加载中...',
|
|
99
|
-
'skill.fileDir': '文件目录',
|
|
100
|
-
'skill.selectFile': '选择文件以预览',
|
|
101
|
-
'skill.clickFile': '点击左侧文件查看内容',
|
|
102
|
-
'skill.htmlPreview': 'HTML 预览',
|
|
103
|
-
'skill.mdSource': 'Markdown 源码',
|
|
104
|
-
'skill.team': '管理团队',
|
|
105
|
-
'skill.addCollaborator': '+ 添加协作者',
|
|
106
|
-
'skill.deleteSkill': '删除 Skill',
|
|
107
|
-
'skill.versionHistory': '版本历史',
|
|
108
|
-
'skill.noVersions': '暂无版本记录',
|
|
109
|
-
'skill.owner': '负责人: ',
|
|
110
|
-
'skill.createdAt': '创建时间: ',
|
|
111
|
-
'skill.uploadedBy': '上传者: ',
|
|
112
|
-
'skill.noChangelog': '无更新说明',
|
|
113
|
-
'skill.selectVersion': '选择版本...',
|
|
114
|
-
'skill.latestTag': '(最新)',
|
|
115
|
-
'skill.download': '下载当前版本',
|
|
116
|
-
'skill.compare': '对比版本',
|
|
117
|
-
'skill.selectVersionFirst': '请先选择版本',
|
|
118
|
-
'skill.fileNotFound': '文件不存在',
|
|
119
|
-
'skill.binaryFile': '二进制文件,无法预览',
|
|
120
|
-
'skill.binaryHint': '请下载 zip 包后在本地查看',
|
|
121
|
-
'skill.previewFailed': '预览失败: ',
|
|
122
|
-
'skill.loadFailed': '加载 Skill 详情失败',
|
|
123
|
-
'skill.missingId': '缺少 Skill ID 参数',
|
|
124
|
-
'skill.loadVersionsFailed': '加载版本列表失败',
|
|
125
|
-
'skill.zipFailed': '下载 zip 失败',
|
|
126
|
-
'skill.versionFailed': '加载版本失败: ',
|
|
127
|
-
'skill.infoLoading': 'Skill 信息加载中',
|
|
128
|
-
'skill.needTwoVersions': '至少需要两个版本才能对比',
|
|
129
|
-
'skill.loadInfoFailed': '加载 Skill 信息失败',
|
|
130
|
-
|
|
131
|
-
// Collaborators
|
|
132
|
-
'collab.modal': '添加协作者',
|
|
133
|
-
'collab.usernameLabel': '用户名',
|
|
134
|
-
'collab.usernamePlaceholder': '输入用户名',
|
|
135
|
-
'collab.owner': '所有者',
|
|
136
|
-
'collab.collaborator': '协作者',
|
|
137
|
-
'collab.disabled': ' (已禁用)',
|
|
138
|
-
'collab.noUsers': '未找到用户',
|
|
139
|
-
'collab.enterUsername': '请输入用户名',
|
|
140
|
-
'collab.addSuccess': '协作者添加成功',
|
|
141
|
-
'collab.addFailed': '添加失败',
|
|
142
|
-
'collab.removeConfirm': '确定要移除该协作者吗?',
|
|
143
|
-
'collab.removeSuccess': '协作者已移除',
|
|
144
|
-
'collab.removeFailed': '移除失败',
|
|
145
|
-
'collab.deleteModal': '确认删除',
|
|
146
|
-
'collab.deleteWarning': '⚠️ 此操作不可恢复!将删除该 Skill 的所有版本和数据。',
|
|
147
|
-
'collab.deleteLabel': '请输入 Skill ID 确认删除:',
|
|
148
|
-
'collab.deleteConfirmBtn': '确认删除',
|
|
149
|
-
'collab.deleteWrongId': '请输入正确的 Skill ID 确认删除',
|
|
150
|
-
'collab.deleteSuccess': 'Skill 已删除',
|
|
151
|
-
'collab.deleteFailed': '删除失败',
|
|
152
|
-
|
|
153
|
-
// Publish page
|
|
154
|
-
'publish.title': '发布新版本 - Skill Base',
|
|
155
|
-
'publish.breadcrumbCurrent': '发布新版本',
|
|
156
|
-
'publish.heading': '发布新版本',
|
|
157
|
-
'publish.uploadLabel': '上传文件',
|
|
158
|
-
'publish.dropText': '拖拽 Skill 文件夹或 zip 到此处',
|
|
159
|
-
'publish.dropSubtitle': '或点击选择文件夹',
|
|
160
|
-
'publish.dropHint': 'Skill ID、名称、描述均从包内读取(文件夹名 / zip 文件名 + SKILL.md),不可手改',
|
|
161
|
-
'publish.or': '或',
|
|
162
|
-
'publish.selectZip': '选择 zip 文件...',
|
|
163
|
-
'publish.selectedFiles': '已选择文件',
|
|
164
|
-
'publish.clearFiles': '清除',
|
|
165
|
-
'publish.selectSkill': '选择 Skill',
|
|
166
|
-
'publish.createNew': '-- 创建新 Skill --',
|
|
167
|
-
'publish.skillSelectHint': '更新已有 Skill 时请选择与包内 Skill ID 一致的一项;新建则保持「创建新 Skill」',
|
|
168
|
-
'publish.idLabel': 'Skill ID',
|
|
169
|
-
'publish.idPlaceholder': '上传文件夹或 zip 后自动填入',
|
|
170
|
-
'publish.idHint': '由文件夹名或 zip 文件名推导(小写字母、数字、连字符、下划线)',
|
|
171
|
-
'publish.nameLabel': 'Skill 名称',
|
|
172
|
-
'publish.namePlaceholder': '上传后从 SKILL.md 读取',
|
|
173
|
-
'publish.nameHint': '来自 SKILL.md(frontmatter 或首行标题)',
|
|
174
|
-
'publish.descLabel': '描述',
|
|
175
|
-
'publish.descPlaceholder': '上传后从 SKILL.md 读取',
|
|
176
|
-
'publish.descHint': ' / 500 字(与包内一致,截断至 500 字)',
|
|
177
|
-
'publish.changelogLabel': '更新说明',
|
|
178
|
-
'publish.changelogPlaceholder': '描述本次更新的内容...\n\n例如:\n- 新增 xxx 功能\n- 修复 xxx 问题',
|
|
179
|
-
'publish.cancelBtn': '取消',
|
|
180
|
-
'publish.submitBtn': '发布新版本',
|
|
181
|
-
'publish.uploading': '正在上传...',
|
|
182
|
-
'publish.successTitle': '发布成功!',
|
|
183
|
-
'publish.viewDetail': '查看详情',
|
|
184
|
-
'publish.publishAnother': '继续发布',
|
|
185
|
-
'publish.filesCount': ' 个文件',
|
|
186
|
-
'publish.totalSize': '总大小: ',
|
|
187
|
-
|
|
188
|
-
// Settings page
|
|
189
|
-
'settings.title': '账户设置 - Skill Base',
|
|
190
|
-
'settings.heading': '账户设置',
|
|
191
|
-
'settings.subtitle': '管理您的个人信息和凭证',
|
|
192
|
-
'settings.basicInfo': '基本信息',
|
|
193
|
-
'settings.usernameLabel': '用户名',
|
|
194
|
-
'settings.usernameHint': '至少 1 个字符,最多 50 个字符',
|
|
195
|
-
'settings.nameLabel': '姓名',
|
|
196
|
-
'settings.namePlaceholder': '请输入您的姓名',
|
|
197
|
-
'settings.nameHint': '可选,用于显示在技能作者信息中',
|
|
198
|
-
'settings.saveBtn': '保存修改',
|
|
199
|
-
'settings.saving': '保存中...',
|
|
200
|
-
'settings.cliSection': 'CLI 访问凭证',
|
|
201
|
-
'settings.cliDesc': '在本地终端使用 <code>skb</code> 命令行工具时,需要获取验证码进行登录。',
|
|
202
|
-
'settings.cliLink': '获取 CLI 验证码',
|
|
203
|
-
'settings.passwordSection': '修改密码',
|
|
204
|
-
'settings.oldPassword': '当前密码',
|
|
205
|
-
'settings.newPassword': '新密码',
|
|
206
|
-
'settings.newPasswordHint': '至少 6 个字符',
|
|
207
|
-
'settings.confirmPassword': '确认新密码',
|
|
208
|
-
'settings.changePasswordBtn': '修改密码',
|
|
209
|
-
'settings.changing': '修改中...',
|
|
210
|
-
'settings.loadFailed': '加载用户信息失败',
|
|
211
|
-
'settings.usernameLenError': '用户名长度必须在 1-50 个字符之间',
|
|
212
|
-
'settings.updateSuccess': '个人信息已更新',
|
|
213
|
-
'settings.updateFailed': '更新失败',
|
|
214
|
-
'settings.noOldPassword': '请输入当前密码',
|
|
215
|
-
'settings.newPassTooShort': '新密码长度至少 6 位',
|
|
216
|
-
'settings.passMismatch': '两次输入的新密码不一致',
|
|
217
|
-
'settings.changeSuccess': '密码修改成功',
|
|
218
|
-
'settings.changeFailed': '修改失败',
|
|
219
|
-
|
|
220
|
-
// Diff page
|
|
221
|
-
'diff.title': '版本对比 - Skill Base',
|
|
222
|
-
'diff.breadcrumbCurrent': '版本对比',
|
|
223
|
-
'diff.versionA': '版本 A (旧):',
|
|
224
|
-
'diff.versionB': '版本 B (新):',
|
|
225
|
-
'diff.selectVersion': '选择版本...',
|
|
226
|
-
'diff.recalculate': '重新计算',
|
|
227
|
-
'diff.file': '文件:',
|
|
228
|
-
'diff.allFiles': '全部文件',
|
|
229
|
-
'diff.sideBySide': '左右对比',
|
|
230
|
-
'diff.unified': '统一视图',
|
|
231
|
-
'diff.changedFiles': '变更文件列表',
|
|
232
|
-
'diff.back': '返回详情页',
|
|
233
|
-
'diff.missingId': '缺少 Skill ID 参数',
|
|
234
|
-
'diff.loadInfoFailed': '加载 Skill 信息失败',
|
|
235
|
-
'diff.selectBoth': '请选择两个版本进行对比',
|
|
236
|
-
'diff.loadVersionsFailed': '加载版本列表失败',
|
|
237
|
-
'diff.computing': '正在计算差异...',
|
|
238
|
-
'diff.computeFailed': '计算差异失败',
|
|
239
|
-
'diff.empty': '选择两个版本后点击"重新计算"查看差异',
|
|
240
|
-
'diff.linesAdded': '行',
|
|
241
|
-
'diff.linesRemoved': '行',
|
|
242
|
-
'diff.filesChanged': ' 个文件变更',
|
|
243
|
-
'diff.added': '新增',
|
|
244
|
-
'diff.deleted': '删除',
|
|
245
|
-
'diff.modified': '修改',
|
|
246
|
-
'diff.binaryDiff': '二进制文件,无法显示差异',
|
|
247
|
-
'diff.pageTitle': '版本对比 - ',
|
|
248
|
-
|
|
249
|
-
// File page
|
|
250
|
-
'file.title': '文件预览 - Skill Base',
|
|
251
|
-
'file.breadcrumbFile': '文件',
|
|
252
|
-
'file.currentVersion': '当前版本:',
|
|
253
|
-
'file.fileDir': '文件目录',
|
|
254
|
-
'file.selectFile': '选择文件以预览',
|
|
255
|
-
'file.clickFile': '点击左侧文件查看内容',
|
|
256
|
-
'file.download': '下载当前版本',
|
|
257
|
-
'file.selectOtherVersion': '选择其他版本...',
|
|
258
|
-
'file.compare': '对比版本',
|
|
259
|
-
'file.back': '返回详情页',
|
|
260
|
-
'file.missingId': '缺少 Skill ID 参数',
|
|
261
|
-
'file.loadFailed': '加载失败',
|
|
262
|
-
'file.binaryFile': '二进制文件,无法预览',
|
|
263
|
-
'file.binaryHint': '请下载 zip 包后在本地查看',
|
|
264
|
-
'file.largeFile': '文件较大,仅显示前 100KB',
|
|
265
|
-
'file.noFile': '无法读取文件',
|
|
266
|
-
|
|
267
|
-
// CLI Code page
|
|
268
|
-
'cliCode.title': 'CLI 验证码 - Skill Base',
|
|
269
|
-
'cliCode.heading': 'CLI 验证码',
|
|
270
|
-
'cliCode.subtitle': '在命令行工具中输入此验证码完成登录',
|
|
271
|
-
'cliCode.generating': '正在生成验证码...',
|
|
272
|
-
'cliCode.expires': ' 后过期',
|
|
273
|
-
'cliCode.expired': '已过期',
|
|
274
|
-
'cliCode.copyBtn': '复制验证码',
|
|
275
|
-
'cliCode.copied': '已复制',
|
|
276
|
-
'cliCode.regenerate': '重新生成',
|
|
277
|
-
'cliCode.hintTitle': '使用方法:',
|
|
278
|
-
'cliCode.hintText': '在终端运行 <code>skb login</code>,然后输入上方验证码即可完成 CLI 登录。',
|
|
279
|
-
'cliCode.fromCli': '请登录成功后,在下方输入验证码',
|
|
280
|
-
'cliCode.generateFailed': '生成验证码失败',
|
|
281
|
-
'cliCode.genFailed': '生成失败',
|
|
282
|
-
'cliCode.noCopy': '没有可复制的验证码',
|
|
283
|
-
'cliCode.copiedToast': '验证码已复制到剪贴板',
|
|
284
|
-
'cliCode.pressCopy': '请按 Ctrl+C 复制验证码',
|
|
285
|
-
'cliCode.newGenerated': '新验证码已生成',
|
|
286
|
-
|
|
287
|
-
// Admin - User Management
|
|
288
|
-
'admin.title': '用户管理 - Skill Base',
|
|
289
|
-
'admin.heading': '用户管理',
|
|
290
|
-
'admin.addUser': '添加用户',
|
|
291
|
-
'admin.searchPlaceholder': '搜索用户名...',
|
|
292
|
-
'admin.allStatus': '全部状态',
|
|
293
|
-
'admin.active': '启用',
|
|
294
|
-
'admin.disabled': '禁用',
|
|
295
|
-
'admin.thUsername': '用户名',
|
|
296
|
-
'admin.thName': '姓名',
|
|
297
|
-
'admin.thRole': '角色',
|
|
298
|
-
'admin.thStatus': '状态',
|
|
299
|
-
'admin.thCreatedAt': '创建时间',
|
|
300
|
-
'admin.thActions': '操作',
|
|
301
|
-
'admin.prevPage': '上一页',
|
|
302
|
-
'admin.nextPage': '下一页',
|
|
303
|
-
'admin.noUsers': '暂无用户',
|
|
304
|
-
'admin.loadFailed': '加载失败,请刷新重试',
|
|
305
|
-
'admin.roleAdmin': '管理员',
|
|
306
|
-
'admin.roleDeveloper': '开发者',
|
|
307
|
-
'admin.statusActive': '✅ 启用',
|
|
308
|
-
'admin.statusDisabled': '⛔ 禁用',
|
|
309
|
-
'admin.editBtn': '编辑',
|
|
310
|
-
'admin.addModal': '添加用户',
|
|
311
|
-
'admin.addUsernameLabel': '用户名',
|
|
312
|
-
'admin.addUsernamePlaceholder': '请输入用户名',
|
|
313
|
-
'admin.addPasswordLabel': '初始密码',
|
|
314
|
-
'admin.addPasswordPlaceholder': '请输入初始密码',
|
|
315
|
-
'admin.addRoleLabel': '角色',
|
|
316
|
-
'admin.roleDeveloperLabel': '开发者',
|
|
317
|
-
'admin.roleAdminLabel': '管理员',
|
|
318
|
-
'admin.editModal': '编辑用户',
|
|
319
|
-
'admin.editUsernameLabel': '用户名',
|
|
320
|
-
'admin.editNameLabel': '姓名',
|
|
321
|
-
'admin.editNamePlaceholder': '请输入姓名',
|
|
322
|
-
'admin.editRoleLabel': '角色',
|
|
323
|
-
'admin.editStatusLabel': '状态',
|
|
324
|
-
'admin.resetPasswordSection': '重置用户密码',
|
|
325
|
-
'admin.resetPasswordBtn': '重置密码',
|
|
326
|
-
'admin.resetModal': '重置密码',
|
|
327
|
-
'admin.resetDesc': '为用户 ',
|
|
328
|
-
'admin.resetDescEnd': ' 设置新密码:',
|
|
329
|
-
'admin.resetPasswordLabel': '新密码',
|
|
330
|
-
'admin.resetPasswordPlaceholder': '请输入新密码',
|
|
331
|
-
'admin.adding': '添加中...',
|
|
332
|
-
'admin.addSuccess': '用户添加成功',
|
|
333
|
-
'admin.addFailed': '添加用户失败',
|
|
334
|
-
'admin.getUserFailed': '获取用户信息失败',
|
|
335
|
-
'admin.saving': '保存中...',
|
|
336
|
-
'admin.saveSuccess': '用户信息已更新',
|
|
337
|
-
'admin.saveFailed': '更新用户失败',
|
|
338
|
-
'admin.resetting': '重置中...',
|
|
339
|
-
'admin.resetSuccess': '密码重置成功',
|
|
340
|
-
'admin.resetFailed': '重置密码失败',
|
|
341
|
-
'admin.enterUsername': '请输入用户名',
|
|
342
|
-
'admin.enterPassword': '请输入初始密码',
|
|
343
|
-
'admin.enterNewPassword': '请输入新密码',
|
|
344
|
-
'admin.loadUsersFailed': '加载用户列表失败',
|
|
345
|
-
};
|
|
346
|
-
|
|
347
|
-
const en = {
|
|
348
|
-
// Navbar
|
|
349
|
-
'nav.home': 'Home',
|
|
350
|
-
'nav.publish': 'Publish',
|
|
351
|
-
'nav.settings': 'Account Settings',
|
|
352
|
-
'nav.admin': 'User Management',
|
|
353
|
-
'nav.logout': 'Sign Out',
|
|
354
|
-
'nav.login': 'Sign In',
|
|
355
|
-
|
|
356
|
-
// Common buttons / labels
|
|
357
|
-
'btn.cancel': 'Cancel',
|
|
358
|
-
'btn.save': 'Save Changes',
|
|
359
|
-
'btn.add': 'Add',
|
|
360
|
-
'btn.edit': 'Edit',
|
|
361
|
-
'btn.delete': 'Delete',
|
|
362
|
-
'btn.confirm': 'Confirm Delete',
|
|
363
|
-
'btn.remove': 'Remove',
|
|
364
|
-
'btn.close': 'Close',
|
|
365
|
-
'btn.reset': 'Confirm Reset',
|
|
366
|
-
'btn.back': 'Back to Detail',
|
|
367
|
-
|
|
368
|
-
// Time (formatDate)
|
|
369
|
-
'time.justNow': 'just now',
|
|
370
|
-
'time.minutesAgo': ' min ago',
|
|
371
|
-
'time.hoursAgo': ' hr ago',
|
|
372
|
-
'time.daysAgo': ' days ago',
|
|
373
|
-
|
|
374
|
-
// Common states
|
|
375
|
-
'state.loading': 'Loading...',
|
|
376
|
-
'state.noDesc': 'No description',
|
|
377
|
-
'state.unknown': 'Unknown',
|
|
378
|
-
'state.noData': 'No data',
|
|
379
|
-
'state.empty': 'Empty directory',
|
|
380
|
-
|
|
381
|
-
// Login page
|
|
382
|
-
'login.title': 'Sign In - Skill Base',
|
|
383
|
-
'login.subtitle': '// Internal Skill Platform · Credentials Required',
|
|
384
|
-
'login.submit': 'Sign In',
|
|
385
|
-
'login.loading': 'Signing in...',
|
|
386
|
-
'login.errUsername': 'Please enter your username',
|
|
387
|
-
'login.errPassword': 'Please enter your password',
|
|
388
|
-
'login.errFailed': 'Login failed, please try again',
|
|
389
|
-
'login.unauthorized': 'Unauthorized, please sign in again',
|
|
390
|
-
|
|
391
|
-
// Setup page (initial admin setup)
|
|
392
|
-
'setup.title': 'Initial Setup - Skill Base',
|
|
393
|
-
'setup.subtitle': '// First Launch · Create Admin Account',
|
|
394
|
-
'setup.usernameHint': '3-50 characters',
|
|
395
|
-
'setup.passwordHint': 'At least 6 characters',
|
|
396
|
-
'setup.submit': 'Create Admin Account',
|
|
397
|
-
'setup.loading': 'Creating...',
|
|
398
|
-
'setup.success': 'Admin account created successfully, redirecting to login...',
|
|
399
|
-
'setup.errUsername': 'Please enter a username',
|
|
400
|
-
'setup.errUsernameLength': 'Username must be 3-50 characters',
|
|
401
|
-
'setup.errPassword': 'Please enter a password',
|
|
402
|
-
'setup.errPasswordLength': 'Password must be at least 6 characters',
|
|
403
|
-
'setup.errPasswordMismatch': 'Passwords do not match',
|
|
404
|
-
'setup.errFailed': 'Setup failed, please try again',
|
|
405
|
-
|
|
406
|
-
// Index page
|
|
407
|
-
'index.searchPlaceholder': 'Search skills...',
|
|
408
|
-
'index.fabTitle': 'Publish New Version',
|
|
409
|
-
'index.noSkills': 'No skills yet, publish the first one',
|
|
410
|
-
'index.noResults': 'No skills found for "{q}"',
|
|
411
|
-
'index.noResultsHint': 'Try a different keyword?',
|
|
412
|
-
'index.publishBtn': 'Publish Skill',
|
|
413
|
-
'index.loadFailed': 'Load failed: ',
|
|
414
|
-
|
|
415
|
-
// Skill detail page
|
|
416
|
-
'skill.title': 'Skill Detail - Skill Base',
|
|
417
|
-
'skill.breadcrumbHome': 'Home',
|
|
418
|
-
'skill.loading': 'Loading...',
|
|
419
|
-
'skill.fileDir': 'Files',
|
|
420
|
-
'skill.selectFile': 'Select a file to preview',
|
|
421
|
-
'skill.clickFile': 'Click a file on the left to preview',
|
|
422
|
-
'skill.htmlPreview': 'HTML Preview',
|
|
423
|
-
'skill.mdSource': 'Markdown Source',
|
|
424
|
-
'skill.team': 'Team',
|
|
425
|
-
'skill.addCollaborator': '+ Add Collaborator',
|
|
426
|
-
'skill.deleteSkill': 'Delete Skill',
|
|
427
|
-
'skill.versionHistory': 'Version History',
|
|
428
|
-
'skill.noVersions': 'No version history',
|
|
429
|
-
'skill.owner': 'Owner: ',
|
|
430
|
-
'skill.createdAt': 'Created: ',
|
|
431
|
-
'skill.uploadedBy': 'Uploaded by: ',
|
|
432
|
-
'skill.noChangelog': 'No changelog',
|
|
433
|
-
'skill.selectVersion': 'Select version...',
|
|
434
|
-
'skill.latestTag': '(latest)',
|
|
435
|
-
'skill.download': 'Download',
|
|
436
|
-
'skill.compare': 'Compare',
|
|
437
|
-
'skill.selectVersionFirst': 'Please select a version first',
|
|
438
|
-
'skill.fileNotFound': 'File not found',
|
|
439
|
-
'skill.binaryFile': 'Binary file, cannot preview',
|
|
440
|
-
'skill.binaryHint': 'Download the zip package to view locally',
|
|
441
|
-
'skill.previewFailed': 'Preview failed: ',
|
|
442
|
-
'skill.loadFailed': 'Failed to load Skill details',
|
|
443
|
-
'skill.missingId': 'Missing Skill ID parameter',
|
|
444
|
-
'skill.loadVersionsFailed': 'Failed to load version list',
|
|
445
|
-
'skill.zipFailed': 'Failed to download zip',
|
|
446
|
-
'skill.versionFailed': 'Failed to load version: ',
|
|
447
|
-
'skill.infoLoading': 'Skill info is loading',
|
|
448
|
-
'skill.needTwoVersions': 'At least 2 versions are needed to compare',
|
|
449
|
-
'skill.loadInfoFailed': 'Failed to load Skill info',
|
|
450
|
-
|
|
451
|
-
// Collaborators
|
|
452
|
-
'collab.modal': 'Add Collaborator',
|
|
453
|
-
'collab.usernameLabel': 'Username',
|
|
454
|
-
'collab.usernamePlaceholder': 'Enter username',
|
|
455
|
-
'collab.owner': 'Owner',
|
|
456
|
-
'collab.collaborator': 'Collaborator',
|
|
457
|
-
'collab.disabled': ' (disabled)',
|
|
458
|
-
'collab.noUsers': 'No users found',
|
|
459
|
-
'collab.enterUsername': 'Please enter a username',
|
|
460
|
-
'collab.addSuccess': 'Collaborator added',
|
|
461
|
-
'collab.addFailed': 'Failed to add',
|
|
462
|
-
'collab.removeConfirm': 'Are you sure you want to remove this collaborator?',
|
|
463
|
-
'collab.removeSuccess': 'Collaborator removed',
|
|
464
|
-
'collab.removeFailed': 'Failed to remove',
|
|
465
|
-
'collab.deleteModal': 'Confirm Delete',
|
|
466
|
-
'collab.deleteWarning': '⚠️ This action cannot be undone! All versions and data of this Skill will be deleted.',
|
|
467
|
-
'collab.deleteLabel': 'Enter Skill ID to confirm deletion:',
|
|
468
|
-
'collab.deleteConfirmBtn': 'Confirm Delete',
|
|
469
|
-
'collab.deleteWrongId': 'Please enter the correct Skill ID to confirm deletion',
|
|
470
|
-
'collab.deleteSuccess': 'Skill deleted',
|
|
471
|
-
'collab.deleteFailed': 'Failed to delete',
|
|
472
|
-
|
|
473
|
-
// Publish page
|
|
474
|
-
'publish.title': 'Publish New Version - Skill Base',
|
|
475
|
-
'publish.breadcrumbCurrent': 'Publish New Version',
|
|
476
|
-
'publish.heading': 'Publish New Version',
|
|
477
|
-
'publish.uploadLabel': 'Upload Files',
|
|
478
|
-
'publish.dropText': 'Drop Skill folder or zip here',
|
|
479
|
-
'publish.dropSubtitle': 'or click to select folder',
|
|
480
|
-
'publish.dropHint': 'Skill ID, name and description are read from the package (folder/zip name + SKILL.md) and cannot be edited manually',
|
|
481
|
-
'publish.or': 'or',
|
|
482
|
-
'publish.selectZip': 'Select zip file...',
|
|
483
|
-
'publish.selectedFiles': 'Selected files',
|
|
484
|
-
'publish.clearFiles': 'Clear',
|
|
485
|
-
'publish.selectSkill': 'Select Skill',
|
|
486
|
-
'publish.createNew': '-- Create New Skill --',
|
|
487
|
-
'publish.skillSelectHint': 'Select a Skill with matching ID when updating; keep "Create New Skill" for new ones',
|
|
488
|
-
'publish.idLabel': 'Skill ID',
|
|
489
|
-
'publish.idPlaceholder': 'Auto-filled after upload',
|
|
490
|
-
'publish.idHint': 'Derived from folder or zip filename (lowercase letters, numbers, hyphens, underscores)',
|
|
491
|
-
'publish.nameLabel': 'Skill Name',
|
|
492
|
-
'publish.namePlaceholder': 'Read from SKILL.md after upload',
|
|
493
|
-
'publish.nameHint': 'From SKILL.md (frontmatter or first heading)',
|
|
494
|
-
'publish.descLabel': 'Description',
|
|
495
|
-
'publish.descPlaceholder': 'Read from SKILL.md after upload',
|
|
496
|
-
'publish.descHint': ' / 500 chars (truncated to match package)',
|
|
497
|
-
'publish.changelogLabel': 'Changelog',
|
|
498
|
-
'publish.changelogPlaceholder': 'Describe the changes in this release...\n\ne.g.:\n- Added xxx feature\n- Fixed xxx issue',
|
|
499
|
-
'publish.cancelBtn': 'Cancel',
|
|
500
|
-
'publish.submitBtn': 'Publish New Version',
|
|
501
|
-
'publish.uploading': 'Uploading...',
|
|
502
|
-
'publish.successTitle': 'Published successfully!',
|
|
503
|
-
'publish.viewDetail': 'View Details',
|
|
504
|
-
'publish.publishAnother': 'Publish Another',
|
|
505
|
-
'publish.filesCount': ' files',
|
|
506
|
-
'publish.totalSize': 'Total size: ',
|
|
507
|
-
|
|
508
|
-
// Settings page
|
|
509
|
-
'settings.title': 'Account Settings - Skill Base',
|
|
510
|
-
'settings.heading': 'Account Settings',
|
|
511
|
-
'settings.subtitle': 'Manage your personal information and credentials',
|
|
512
|
-
'settings.basicInfo': 'Basic Information',
|
|
513
|
-
'settings.usernameLabel': 'Username',
|
|
514
|
-
'settings.usernameHint': '1-50 characters',
|
|
515
|
-
'settings.nameLabel': 'Name',
|
|
516
|
-
'settings.namePlaceholder': 'Enter your name',
|
|
517
|
-
'settings.nameHint': 'Optional, shown as skill author info',
|
|
518
|
-
'settings.saveBtn': 'Save Changes',
|
|
519
|
-
'settings.saving': 'Saving...',
|
|
520
|
-
'settings.cliSection': 'CLI Access Credentials',
|
|
521
|
-
'settings.cliDesc': 'To use the <code>skb</code> CLI tool locally, get a verification code to sign in.',
|
|
522
|
-
'settings.cliLink': 'Get CLI Verification Code',
|
|
523
|
-
'settings.passwordSection': 'Change Password',
|
|
524
|
-
'settings.oldPassword': 'Current Password',
|
|
525
|
-
'settings.newPassword': 'New Password',
|
|
526
|
-
'settings.newPasswordHint': 'At least 6 characters',
|
|
527
|
-
'settings.confirmPassword': 'Confirm New Password',
|
|
528
|
-
'settings.changePasswordBtn': 'Change Password',
|
|
529
|
-
'settings.changing': 'Changing...',
|
|
530
|
-
'settings.loadFailed': 'Failed to load user info',
|
|
531
|
-
'settings.usernameLenError': 'Username must be 1-50 characters',
|
|
532
|
-
'settings.updateSuccess': 'Profile updated',
|
|
533
|
-
'settings.updateFailed': 'Update failed',
|
|
534
|
-
'settings.noOldPassword': 'Please enter your current password',
|
|
535
|
-
'settings.newPassTooShort': 'New password must be at least 6 characters',
|
|
536
|
-
'settings.passMismatch': 'Passwords do not match',
|
|
537
|
-
'settings.changeSuccess': 'Password changed successfully',
|
|
538
|
-
'settings.changeFailed': 'Failed to change password',
|
|
539
|
-
|
|
540
|
-
// Diff page
|
|
541
|
-
'diff.title': 'Version Diff - Skill Base',
|
|
542
|
-
'diff.breadcrumbCurrent': 'Version Diff',
|
|
543
|
-
'diff.versionA': 'Version A (old):',
|
|
544
|
-
'diff.versionB': 'Version B (new):',
|
|
545
|
-
'diff.selectVersion': 'Select version...',
|
|
546
|
-
'diff.recalculate': 'Recalculate',
|
|
547
|
-
'diff.file': 'File:',
|
|
548
|
-
'diff.allFiles': 'All files',
|
|
549
|
-
'diff.sideBySide': 'Side by Side',
|
|
550
|
-
'diff.unified': 'Unified',
|
|
551
|
-
'diff.changedFiles': 'Changed Files',
|
|
552
|
-
'diff.back': 'Back to Detail',
|
|
553
|
-
'diff.missingId': 'Missing Skill ID parameter',
|
|
554
|
-
'diff.loadInfoFailed': 'Failed to load Skill info',
|
|
555
|
-
'diff.selectBoth': 'Please select two versions to compare',
|
|
556
|
-
'diff.loadVersionsFailed': 'Failed to load version list',
|
|
557
|
-
'diff.computing': 'Computing diff...',
|
|
558
|
-
'diff.computeFailed': 'Failed to compute diff',
|
|
559
|
-
'diff.empty': 'Select two versions and click "Recalculate" to view differences',
|
|
560
|
-
'diff.linesAdded': ' lines',
|
|
561
|
-
'diff.linesRemoved': ' lines',
|
|
562
|
-
'diff.filesChanged': ' files changed',
|
|
563
|
-
'diff.added': 'added',
|
|
564
|
-
'diff.deleted': 'deleted',
|
|
565
|
-
'diff.modified': 'modified',
|
|
566
|
-
'diff.binaryDiff': 'Binary file, cannot show diff',
|
|
567
|
-
'diff.pageTitle': 'Version Diff - ',
|
|
568
|
-
|
|
569
|
-
// File page
|
|
570
|
-
'file.title': 'File Preview - Skill Base',
|
|
571
|
-
'file.breadcrumbFile': 'File',
|
|
572
|
-
'file.currentVersion': 'Current Version:',
|
|
573
|
-
'file.fileDir': 'Files',
|
|
574
|
-
'file.selectFile': 'Select a file to preview',
|
|
575
|
-
'file.clickFile': 'Click a file on the left to preview',
|
|
576
|
-
'file.download': 'Download Current Version',
|
|
577
|
-
'file.selectOtherVersion': 'Select another version...',
|
|
578
|
-
'file.compare': 'Compare Versions',
|
|
579
|
-
'file.back': 'Back to Detail',
|
|
580
|
-
'file.missingId': 'Missing Skill ID parameter',
|
|
581
|
-
'file.loadFailed': 'Load failed',
|
|
582
|
-
'file.binaryFile': 'Binary file, cannot preview',
|
|
583
|
-
'file.binaryHint': 'Download the zip package to view locally',
|
|
584
|
-
'file.largeFile': 'Large file, showing first 100KB only',
|
|
585
|
-
'file.noFile': 'Cannot read file',
|
|
586
|
-
|
|
587
|
-
// CLI Code page
|
|
588
|
-
'cliCode.title': 'CLI Verification Code - Skill Base',
|
|
589
|
-
'cliCode.heading': 'CLI Verification Code',
|
|
590
|
-
'cliCode.subtitle': 'Enter this code in the CLI to complete sign in',
|
|
591
|
-
'cliCode.generating': 'Generating code...',
|
|
592
|
-
'cliCode.expires': ' remaining',
|
|
593
|
-
'cliCode.expired': 'Expired',
|
|
594
|
-
'cliCode.copyBtn': 'Copy Code',
|
|
595
|
-
'cliCode.copied': 'Copied',
|
|
596
|
-
'cliCode.regenerate': 'Regenerate',
|
|
597
|
-
'cliCode.hintTitle': 'How to use:',
|
|
598
|
-
'cliCode.hintText': 'Run <code>skb login</code> in your terminal, then enter the code above to complete CLI sign in.',
|
|
599
|
-
'cliCode.fromCli': 'After signing in, enter the verification code below',
|
|
600
|
-
'cliCode.generateFailed': 'Failed to generate code',
|
|
601
|
-
'cliCode.genFailed': 'Generation failed',
|
|
602
|
-
'cliCode.noCopy': 'No verification code to copy',
|
|
603
|
-
'cliCode.copiedToast': 'Code copied to clipboard',
|
|
604
|
-
'cliCode.pressCopy': 'Press Ctrl+C to copy the code',
|
|
605
|
-
'cliCode.newGenerated': 'New code generated',
|
|
606
|
-
|
|
607
|
-
// Admin - User Management
|
|
608
|
-
'admin.title': 'User Management - Skill Base',
|
|
609
|
-
'admin.heading': 'User Management',
|
|
610
|
-
'admin.addUser': 'Add User',
|
|
611
|
-
'admin.searchPlaceholder': 'Search username...',
|
|
612
|
-
'admin.allStatus': 'All Status',
|
|
613
|
-
'admin.active': 'Active',
|
|
614
|
-
'admin.disabled': 'Disabled',
|
|
615
|
-
'admin.thUsername': 'Username',
|
|
616
|
-
'admin.thName': 'Name',
|
|
617
|
-
'admin.thRole': 'Role',
|
|
618
|
-
'admin.thStatus': 'Status',
|
|
619
|
-
'admin.thCreatedAt': 'Created At',
|
|
620
|
-
'admin.thActions': 'Actions',
|
|
621
|
-
'admin.prevPage': 'Previous',
|
|
622
|
-
'admin.nextPage': 'Next',
|
|
623
|
-
'admin.noUsers': 'No users found',
|
|
624
|
-
'admin.loadFailed': 'Failed to load, please refresh',
|
|
625
|
-
'admin.roleAdmin': 'Admin',
|
|
626
|
-
'admin.roleDeveloper': 'Developer',
|
|
627
|
-
'admin.statusActive': '✅ Active',
|
|
628
|
-
'admin.statusDisabled': '⛔ Disabled',
|
|
629
|
-
'admin.editBtn': 'Edit',
|
|
630
|
-
'admin.addModal': 'Add User',
|
|
631
|
-
'admin.addUsernameLabel': 'Username',
|
|
632
|
-
'admin.addUsernamePlaceholder': 'Enter username',
|
|
633
|
-
'admin.addPasswordLabel': 'Initial Password',
|
|
634
|
-
'admin.addPasswordPlaceholder': 'Enter initial password',
|
|
635
|
-
'admin.addRoleLabel': 'Role',
|
|
636
|
-
'admin.roleDeveloperLabel': 'Developer',
|
|
637
|
-
'admin.roleAdminLabel': 'Admin',
|
|
638
|
-
'admin.editModal': 'Edit User',
|
|
639
|
-
'admin.editUsernameLabel': 'Username',
|
|
640
|
-
'admin.editNameLabel': 'Name',
|
|
641
|
-
'admin.editNamePlaceholder': 'Enter name',
|
|
642
|
-
'admin.editRoleLabel': 'Role',
|
|
643
|
-
'admin.editStatusLabel': 'Status',
|
|
644
|
-
'admin.resetPasswordSection': 'Reset User Password',
|
|
645
|
-
'admin.resetPasswordBtn': 'Reset Password',
|
|
646
|
-
'admin.resetModal': 'Reset Password',
|
|
647
|
-
'admin.resetDesc': 'Set new password for user ',
|
|
648
|
-
'admin.resetDescEnd': ':',
|
|
649
|
-
'admin.resetPasswordLabel': 'New Password',
|
|
650
|
-
'admin.resetPasswordPlaceholder': 'Enter new password',
|
|
651
|
-
'admin.adding': 'Adding...',
|
|
652
|
-
'admin.addSuccess': 'User added successfully',
|
|
653
|
-
'admin.addFailed': 'Failed to add user',
|
|
654
|
-
'admin.getUserFailed': 'Failed to get user info',
|
|
655
|
-
'admin.saving': 'Saving...',
|
|
656
|
-
'admin.saveSuccess': 'User info updated',
|
|
657
|
-
'admin.saveFailed': 'Failed to update user',
|
|
658
|
-
'admin.resetting': 'Resetting...',
|
|
659
|
-
'admin.resetSuccess': 'Password reset successfully',
|
|
660
|
-
'admin.resetFailed': 'Failed to reset password',
|
|
661
|
-
'admin.enterUsername': 'Please enter a username',
|
|
662
|
-
'admin.enterPassword': 'Please enter an initial password',
|
|
663
|
-
'admin.enterNewPassword': 'Please enter a new password',
|
|
664
|
-
'admin.loadUsersFailed': 'Failed to load user list',
|
|
665
|
-
};
|
|
666
|
-
|
|
667
|
-
const translations = isChinese ? zh : en;
|
|
668
|
-
|
|
669
|
-
/**
|
|
670
|
-
* Get translated string by key.
|
|
671
|
-
* @param {string} key
|
|
672
|
-
* @param {object} [params] - optional interpolation params, e.g. {q: 'foo'}
|
|
673
|
-
* @returns {string}
|
|
674
|
-
*/
|
|
675
|
-
function t(key, params) {
|
|
676
|
-
let str = translations[key] ?? key;
|
|
677
|
-
if (params) {
|
|
678
|
-
Object.keys(params).forEach(k => {
|
|
679
|
-
str = str.replace(new RegExp('\\{' + k + '\\}', 'g'), params[k]);
|
|
680
|
-
});
|
|
681
|
-
}
|
|
682
|
-
return str;
|
|
683
|
-
}
|
|
684
|
-
|
|
685
|
-
/**
|
|
686
|
-
* Apply data-i18n attribute translations to all matching DOM elements.
|
|
687
|
-
* Elements with data-i18n will have their textContent replaced.
|
|
688
|
-
* Elements with data-i18n-html will have their innerHTML replaced.
|
|
689
|
-
* Elements with data-i18n-placeholder will have their placeholder replaced.
|
|
690
|
-
* Elements with data-i18n-title will have their title replaced.
|
|
691
|
-
*/
|
|
692
|
-
function applyI18n() {
|
|
693
|
-
// textContent
|
|
694
|
-
document.querySelectorAll('[data-i18n]').forEach(el => {
|
|
695
|
-
const key = el.getAttribute('data-i18n');
|
|
696
|
-
el.textContent = t(key);
|
|
697
|
-
});
|
|
698
|
-
// innerHTML (for content with HTML tags)
|
|
699
|
-
document.querySelectorAll('[data-i18n-html]').forEach(el => {
|
|
700
|
-
const key = el.getAttribute('data-i18n-html');
|
|
701
|
-
el.innerHTML = t(key);
|
|
702
|
-
});
|
|
703
|
-
// placeholder
|
|
704
|
-
document.querySelectorAll('[data-i18n-placeholder]').forEach(el => {
|
|
705
|
-
const key = el.getAttribute('data-i18n-placeholder');
|
|
706
|
-
el.placeholder = t(key);
|
|
707
|
-
});
|
|
708
|
-
// title attribute
|
|
709
|
-
document.querySelectorAll('[data-i18n-title]').forEach(el => {
|
|
710
|
-
const key = el.getAttribute('data-i18n-title');
|
|
711
|
-
el.title = t(key);
|
|
712
|
-
});
|
|
713
|
-
// Set html lang attribute
|
|
714
|
-
document.documentElement.lang = isChinese ? 'zh-CN' : 'en';
|
|
715
|
-
}
|
|
716
|
-
|
|
717
|
-
/**
|
|
718
|
-
* Switch the UI language and persist the choice to localStorage.
|
|
719
|
-
* Reloads the page so all JS-rendered content is re-translated.
|
|
720
|
-
* @param {'zh'|'en'} newLang
|
|
721
|
-
*/
|
|
722
|
-
function setLang(newLang) {
|
|
723
|
-
localStorage.setItem('skb-lang', newLang);
|
|
724
|
-
window.location.reload();
|
|
725
|
-
}
|
|
726
|
-
|
|
727
|
-
// Expose globals
|
|
728
|
-
window.t = t;
|
|
729
|
-
window.applyI18n = applyI18n;
|
|
730
|
-
window.setLang = setLang;
|
|
731
|
-
window.I18N_LANG = isChinese ? 'zh' : 'en';
|
|
732
|
-
|
|
733
|
-
// Auto-apply on DOMContentLoaded
|
|
734
|
-
if (document.readyState === 'loading') {
|
|
735
|
-
document.addEventListener('DOMContentLoaded', applyI18n);
|
|
736
|
-
} else {
|
|
737
|
-
applyI18n();
|
|
738
|
-
}
|
|
739
|
-
})();
|