itismyskillmarket 1.3.17 → 1.3.18
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/gui/app.js +689 -189
- package/gui/index.html +8 -2
- package/gui/style.css +38 -0
- package/package.json +1 -1
package/gui/app.js
CHANGED
|
@@ -15,6 +15,491 @@ const state = {
|
|
|
15
15
|
previousView: 'skills',
|
|
16
16
|
};
|
|
17
17
|
|
|
18
|
+
// -----------------------------------------------------------------------------
|
|
19
|
+
// i18n 国际化系统
|
|
20
|
+
// -----------------------------------------------------------------------------
|
|
21
|
+
|
|
22
|
+
const translations = {
|
|
23
|
+
en: {
|
|
24
|
+
// 导航
|
|
25
|
+
'nav.skills': 'Skills',
|
|
26
|
+
'nav.installed': 'Installed',
|
|
27
|
+
'nav.platforms': 'Platforms',
|
|
28
|
+
'nav.admin': 'Admin',
|
|
29
|
+
'nav.help': 'Help',
|
|
30
|
+
'nav.back': 'Back',
|
|
31
|
+
|
|
32
|
+
// 视图标题
|
|
33
|
+
'title.availableSkills': 'Available Skills',
|
|
34
|
+
'title.installedSkills': 'Installed Skills',
|
|
35
|
+
'title.platforms': 'Available Platforms',
|
|
36
|
+
'title.help': 'Help & Configuration',
|
|
37
|
+
'title.admin': 'Admin Dashboard',
|
|
38
|
+
|
|
39
|
+
// 按钮
|
|
40
|
+
'btn.install': 'Install',
|
|
41
|
+
'btn.uninstall': 'Uninstall',
|
|
42
|
+
'btn.update': 'Update',
|
|
43
|
+
'btn.refresh': '🔄 Refresh',
|
|
44
|
+
'btn.updateAll': '🔄 Update All',
|
|
45
|
+
'btn.info': 'Info',
|
|
46
|
+
'btn.confirm': 'Confirm',
|
|
47
|
+
'btn.cancel': 'Cancel',
|
|
48
|
+
|
|
49
|
+
// 加载状态
|
|
50
|
+
'loading.skills': 'Loading skills...',
|
|
51
|
+
'loading.platforms': 'Loading platforms...',
|
|
52
|
+
'loading.details': 'Loading skill details...',
|
|
53
|
+
'loading.stats': 'Loading stats...',
|
|
54
|
+
'loading.published': 'Loading published skills...',
|
|
55
|
+
'loading.generic': 'Loading...',
|
|
56
|
+
|
|
57
|
+
// 空状态
|
|
58
|
+
'empty.noSkills': 'No skills found',
|
|
59
|
+
'empty.noPlatforms': 'No platforms found',
|
|
60
|
+
'empty.noPublishedSkills': 'No published skills found',
|
|
61
|
+
'empty.noTags': 'No dist-tags found.',
|
|
62
|
+
'empty.couldNotLoadTags': 'Could not load tags.',
|
|
63
|
+
|
|
64
|
+
// 搜索
|
|
65
|
+
'search.placeholder': '🔍 Search skills...',
|
|
66
|
+
|
|
67
|
+
// 分页
|
|
68
|
+
'pagination.prev': '← Prev',
|
|
69
|
+
'pagination.next': 'Next →',
|
|
70
|
+
'pagination.pageInfo': 'Page {page} of {totalPages}',
|
|
71
|
+
|
|
72
|
+
// 每页数量
|
|
73
|
+
'pageSize.10': '10 per page',
|
|
74
|
+
'pageSize.20': '20 per page',
|
|
75
|
+
'pageSize.50': '50 per page',
|
|
76
|
+
|
|
77
|
+
// 状态
|
|
78
|
+
'status.available': '✅ Available',
|
|
79
|
+
'status.unavailable': '❌ Not detected',
|
|
80
|
+
'status.skillsInstalled': '{count} skills installed',
|
|
81
|
+
|
|
82
|
+
// 详情视图
|
|
83
|
+
'detail.description': 'Description',
|
|
84
|
+
'detail.details': 'Details',
|
|
85
|
+
'detail.id': 'ID',
|
|
86
|
+
'detail.version': 'Version',
|
|
87
|
+
'detail.license': 'License',
|
|
88
|
+
'detail.author': 'Author',
|
|
89
|
+
'detail.homepage': 'Homepage',
|
|
90
|
+
'detail.repository': 'Repository',
|
|
91
|
+
'detail.platforms': 'Platforms',
|
|
92
|
+
'detail.versions': 'Versions (last {count})',
|
|
93
|
+
'detail.latest': 'latest',
|
|
94
|
+
'detail.noDescription': 'No description',
|
|
95
|
+
'detail.nA': 'N/A',
|
|
96
|
+
|
|
97
|
+
// Toast 消息
|
|
98
|
+
'toast.installing': 'Installing {skillId}...',
|
|
99
|
+
'toast.installSuccess': '{skillId} installed successfully!',
|
|
100
|
+
'toast.uninstalling': 'Uninstalling {skillId}...',
|
|
101
|
+
'toast.uninstallSuccess': '{skillId} uninstalled!',
|
|
102
|
+
'toast.updating': 'Updating {skillId}...',
|
|
103
|
+
'toast.updateSuccess': '{skillId} updated!',
|
|
104
|
+
'toast.updateAll': 'Updating all skills...',
|
|
105
|
+
'toast.updateAllSuccess': 'All skills updated!',
|
|
106
|
+
|
|
107
|
+
// 确认对话框
|
|
108
|
+
'confirm.uninstall': 'Are you sure you want to uninstall {skillId}?',
|
|
109
|
+
'confirm.updateAll': 'Update all installed skills?',
|
|
110
|
+
|
|
111
|
+
// 警告
|
|
112
|
+
'warning.fetchErrors': '⚠ {count} skill(s) failed to load details from npm registry. Refresh to retry.',
|
|
113
|
+
|
|
114
|
+
// Admin 视图
|
|
115
|
+
'admin.publishedSkillsCount': 'Published Skills ({count})',
|
|
116
|
+
'admin.stats.totalSkills': 'Published Skills',
|
|
117
|
+
'admin.stats.totalVersions': 'Total Versions',
|
|
118
|
+
'admin.stats.avgVersions': 'Avg Versions/Skill',
|
|
119
|
+
'admin.stats.withMetadata': 'With Metadata',
|
|
120
|
+
'admin.stats.totalSize': '{value} MB',
|
|
121
|
+
'admin.stats.platformsCovered': 'Platforms Covered',
|
|
122
|
+
|
|
123
|
+
// Admin Modal - Deprecate
|
|
124
|
+
'admin.deprecating': 'Deprecating {skillId}...',
|
|
125
|
+
'admin.deprecateTitle': 'Deprecate: {skillId}',
|
|
126
|
+
'admin.deprecateVersion': 'Version',
|
|
127
|
+
'admin.deprecateVersionPlaceholder': '(leave empty for all versions)',
|
|
128
|
+
'admin.deprecateMessage': 'Message',
|
|
129
|
+
'admin.deprecateDefaultMsg': 'This skill is deprecated. Please use an alternative.',
|
|
130
|
+
'admin.deprecateWarning': '⚠ Deprecating will mark this skill as deprecated in the npm registry.',
|
|
131
|
+
'admin.confirmDeprecate': 'Confirm Deprecate',
|
|
132
|
+
|
|
133
|
+
// Admin Modal - Unpublish
|
|
134
|
+
'admin.unpublishing': 'Unpublishing {skillId}...',
|
|
135
|
+
'admin.unpublishTitle': 'Unpublish: {skillId}',
|
|
136
|
+
'admin.unpublishVersion': 'Version',
|
|
137
|
+
'admin.unpublishVersionPlaceholder': '(leave empty for entire package)',
|
|
138
|
+
'admin.unpublishForce': 'Force unpublish entire package (required if no version specified)',
|
|
139
|
+
'admin.unpublishDanger': '⚠ This action cannot be undone! Packages can be restored within 72 hours.',
|
|
140
|
+
'admin.confirmUnpublish': 'Confirm Unpublish',
|
|
141
|
+
|
|
142
|
+
// Admin Modal - Tags
|
|
143
|
+
'admin.settingTag': 'Setting tag {tag}...',
|
|
144
|
+
'admin.removingTag': 'Removing tag {tag}...',
|
|
145
|
+
'admin.tagsTitle': 'Tags: {skillId}',
|
|
146
|
+
'admin.currentTags': 'Current Tags',
|
|
147
|
+
'admin.setTag': 'Set Tag',
|
|
148
|
+
'admin.removeTag': 'Remove Tag',
|
|
149
|
+
'admin.tagName': 'Tag',
|
|
150
|
+
'admin.tagNamePlaceholder': 'e.g. beta, latest',
|
|
151
|
+
'admin.tagVersion': 'Version',
|
|
152
|
+
'admin.tagVersionPlaceholder': 'e.g. 1.0.1',
|
|
153
|
+
'admin.tagNameRemovePlaceholder': 'e.g. beta',
|
|
154
|
+
'admin.tag.default': ' (default)',
|
|
155
|
+
'admin.tagRequired': 'Tag and version are required',
|
|
156
|
+
'admin.tagNameRequired': 'Tag name is required',
|
|
157
|
+
|
|
158
|
+
// Admin Modal - Owners
|
|
159
|
+
'admin.addingOwner': 'Adding owner {user}...',
|
|
160
|
+
'admin.removingOwner': 'Removing owner {user}...',
|
|
161
|
+
'admin.ownersTitle': 'Owners: {skillId}',
|
|
162
|
+
'admin.addOwner': 'Add Owner',
|
|
163
|
+
'admin.removeOwner': 'Remove Owner',
|
|
164
|
+
'admin.npmUser': 'npm User',
|
|
165
|
+
'admin.npmUserPlaceholder': 'npm username',
|
|
166
|
+
'admin.usernameRequired': 'Username is required',
|
|
167
|
+
|
|
168
|
+
// Admin Modal - Access
|
|
169
|
+
'admin.settingAccess': 'Setting access to {level}...',
|
|
170
|
+
'admin.accessTitle': 'Access: {skillId}',
|
|
171
|
+
'admin.accessDesc': 'Set the package access level. Public packages are visible to everyone. Restricted packages require authentication to install.',
|
|
172
|
+
'admin.accessPublic': 'Public',
|
|
173
|
+
'admin.accessRestricted': 'Restricted',
|
|
174
|
+
'admin.setAccess': 'Set Access',
|
|
175
|
+
|
|
176
|
+
// Help 视图
|
|
177
|
+
'help.envVars': 'Environment Variables',
|
|
178
|
+
'help.setEnvVars': 'Set the following environment variables to override defaults:',
|
|
179
|
+
'help.envVar.primaryScope': 'Primary npm scope for publishing/lookup',
|
|
180
|
+
'help.envVar.fallbackScope': 'Fallback scope (backward compatibility)',
|
|
181
|
+
'help.envVar.scopeList': 'Comma-separated list of scopes to search',
|
|
182
|
+
'help.envVar.registryUrl': 'npm registry URL',
|
|
183
|
+
'help.envVar.personalLink': 'Personal link prefix (for publish output)',
|
|
184
|
+
'help.commands': 'Common Commands',
|
|
185
|
+
|
|
186
|
+
// 语言
|
|
187
|
+
'lang.en': 'EN',
|
|
188
|
+
'lang.zh': '中',
|
|
189
|
+
|
|
190
|
+
// 通用错误
|
|
191
|
+
'error.generic': 'Error',
|
|
192
|
+
},
|
|
193
|
+
|
|
194
|
+
zh: {
|
|
195
|
+
// 导航
|
|
196
|
+
'nav.skills': '技能',
|
|
197
|
+
'nav.installed': '已安装',
|
|
198
|
+
'nav.platforms': '平台',
|
|
199
|
+
'nav.admin': '管理',
|
|
200
|
+
'nav.help': '帮助',
|
|
201
|
+
'nav.back': '返回',
|
|
202
|
+
|
|
203
|
+
// 视图标题
|
|
204
|
+
'title.availableSkills': '可用技能',
|
|
205
|
+
'title.installedSkills': '已安装技能',
|
|
206
|
+
'title.platforms': '可用平台',
|
|
207
|
+
'title.help': '帮助与配置',
|
|
208
|
+
'title.admin': '管理面板',
|
|
209
|
+
|
|
210
|
+
// 按钮
|
|
211
|
+
'btn.install': '安装',
|
|
212
|
+
'btn.uninstall': '卸载',
|
|
213
|
+
'btn.update': '更新',
|
|
214
|
+
'btn.refresh': '🔄 刷新',
|
|
215
|
+
'btn.updateAll': '🔄 全部更新',
|
|
216
|
+
'btn.info': '详情',
|
|
217
|
+
'btn.confirm': '确认',
|
|
218
|
+
'btn.cancel': '取消',
|
|
219
|
+
|
|
220
|
+
// 加载状态
|
|
221
|
+
'loading.skills': '加载技能中...',
|
|
222
|
+
'loading.platforms': '加载平台中...',
|
|
223
|
+
'loading.details': '加载技能详情中...',
|
|
224
|
+
'loading.stats': '加载统计中...',
|
|
225
|
+
'loading.published': '加载已发布技能中...',
|
|
226
|
+
'loading.generic': '加载中...',
|
|
227
|
+
|
|
228
|
+
// 空状态
|
|
229
|
+
'empty.noSkills': '未找到技能',
|
|
230
|
+
'empty.noPlatforms': '未找到平台',
|
|
231
|
+
'empty.noPublishedSkills': '未找到已发布的技能',
|
|
232
|
+
'empty.noTags': '暂无 dist-tags。',
|
|
233
|
+
'empty.couldNotLoadTags': '无法加载 tags。',
|
|
234
|
+
|
|
235
|
+
// 搜索
|
|
236
|
+
'search.placeholder': '🔍 搜索技能...',
|
|
237
|
+
|
|
238
|
+
// 分页
|
|
239
|
+
'pagination.prev': '← 上一页',
|
|
240
|
+
'pagination.next': '下一页 →',
|
|
241
|
+
'pagination.pageInfo': '第 {page} 页 / 共 {totalPages} 页',
|
|
242
|
+
|
|
243
|
+
// 每页数量
|
|
244
|
+
'pageSize.10': '每页 10 条',
|
|
245
|
+
'pageSize.20': '每页 20 条',
|
|
246
|
+
'pageSize.50': '每页 50 条',
|
|
247
|
+
|
|
248
|
+
// 状态
|
|
249
|
+
'status.available': '✅ 可用',
|
|
250
|
+
'status.unavailable': '❌ 未检测到',
|
|
251
|
+
'status.skillsInstalled': '已安装 {count} 个技能',
|
|
252
|
+
|
|
253
|
+
// 详情视图
|
|
254
|
+
'detail.description': '描述',
|
|
255
|
+
'detail.details': '详细信息',
|
|
256
|
+
'detail.id': 'ID',
|
|
257
|
+
'detail.version': '版本',
|
|
258
|
+
'detail.license': '许可证',
|
|
259
|
+
'detail.author': '作者',
|
|
260
|
+
'detail.homepage': '主页',
|
|
261
|
+
'detail.repository': '代码仓库',
|
|
262
|
+
'detail.platforms': '支持平台',
|
|
263
|
+
'detail.versions': '版本记录(最近 {count} 个)',
|
|
264
|
+
'detail.latest': '最新',
|
|
265
|
+
'detail.noDescription': '暂无描述',
|
|
266
|
+
'detail.nA': 'N/A',
|
|
267
|
+
|
|
268
|
+
// Toast 消息
|
|
269
|
+
'toast.installing': '正在安装 {skillId}...',
|
|
270
|
+
'toast.installSuccess': '{skillId} 安装成功!',
|
|
271
|
+
'toast.uninstalling': '正在卸载 {skillId}...',
|
|
272
|
+
'toast.uninstallSuccess': '{skillId} 已卸载!',
|
|
273
|
+
'toast.updating': '正在更新 {skillId}...',
|
|
274
|
+
'toast.updateSuccess': '{skillId} 已更新!',
|
|
275
|
+
'toast.updateAll': '正在更新所有技能...',
|
|
276
|
+
'toast.updateAllSuccess': '所有技能已更新!',
|
|
277
|
+
|
|
278
|
+
// 确认对话框
|
|
279
|
+
'confirm.uninstall': '确定要卸载 {skillId} 吗?',
|
|
280
|
+
'confirm.updateAll': '更新所有已安装的技能?',
|
|
281
|
+
|
|
282
|
+
// 警告
|
|
283
|
+
'warning.fetchErrors': '⚠ {count} 个技能从 npm registry 加载详情失败。刷新重试。',
|
|
284
|
+
|
|
285
|
+
// Admin 视图
|
|
286
|
+
'admin.publishedSkillsCount': '已发布技能({count} 个)',
|
|
287
|
+
'admin.stats.totalSkills': '已发布技能',
|
|
288
|
+
'admin.stats.totalVersions': '总版本数',
|
|
289
|
+
'admin.stats.avgVersions': '平均版本数',
|
|
290
|
+
'admin.stats.withMetadata': '含元数据',
|
|
291
|
+
'admin.stats.totalSize': '{value} MB',
|
|
292
|
+
'admin.stats.platformsCovered': '覆盖平台',
|
|
293
|
+
|
|
294
|
+
// Admin Modal - Deprecate
|
|
295
|
+
'admin.deprecating': '正在废弃 {skillId}...',
|
|
296
|
+
'admin.deprecateTitle': '废弃: {skillId}',
|
|
297
|
+
'admin.deprecateVersion': '版本',
|
|
298
|
+
'admin.deprecateVersionPlaceholder': '(留空表示所有版本)',
|
|
299
|
+
'admin.deprecateMessage': '消息',
|
|
300
|
+
'admin.deprecateDefaultMsg': '此技能已废弃,请使用其他替代方案。',
|
|
301
|
+
'admin.deprecateWarning': '⚠ 废弃操作将在 npm registry 中标记此技能为已废弃。',
|
|
302
|
+
'admin.confirmDeprecate': '确认废弃',
|
|
303
|
+
|
|
304
|
+
// Admin Modal - Unpublish
|
|
305
|
+
'admin.unpublishing': '正在取消发布 {skillId}...',
|
|
306
|
+
'admin.unpublishTitle': '取消发布: {skillId}',
|
|
307
|
+
'admin.unpublishVersion': '版本',
|
|
308
|
+
'admin.unpublishVersionPlaceholder': '(留空表示整个包)',
|
|
309
|
+
'admin.unpublishForce': '强制取消发布整个包(未指定版本时必须勾选)',
|
|
310
|
+
'admin.unpublishDanger': '⚠ 此操作不可撤销!包可在 72 小时内恢复。',
|
|
311
|
+
'admin.confirmUnpublish': '确认取消发布',
|
|
312
|
+
|
|
313
|
+
// Admin Modal - Tags
|
|
314
|
+
'admin.settingTag': '正在设置标签 {tag}...',
|
|
315
|
+
'admin.removingTag': '正在移除标签 {tag}...',
|
|
316
|
+
'admin.tagsTitle': '标签: {skillId}',
|
|
317
|
+
'admin.currentTags': '当前标签',
|
|
318
|
+
'admin.setTag': '设置标签',
|
|
319
|
+
'admin.removeTag': '移除标签',
|
|
320
|
+
'admin.tagName': '标签名',
|
|
321
|
+
'admin.tagNamePlaceholder': '例如:beta, latest',
|
|
322
|
+
'admin.tagVersion': '版本',
|
|
323
|
+
'admin.tagVersionPlaceholder': '例如:1.0.1',
|
|
324
|
+
'admin.tagNameRemovePlaceholder': '例如:beta',
|
|
325
|
+
'admin.tag.default': '(默认)',
|
|
326
|
+
'admin.tagRequired': '标签和版本不能为空',
|
|
327
|
+
'admin.tagNameRequired': '标签名不能为空',
|
|
328
|
+
|
|
329
|
+
// Admin Modal - Owners
|
|
330
|
+
'admin.addingOwner': '正在添加维护者 {user}...',
|
|
331
|
+
'admin.removingOwner': '正在移除维护者 {user}...',
|
|
332
|
+
'admin.ownersTitle': '维护者: {skillId}',
|
|
333
|
+
'admin.addOwner': '添加维护者',
|
|
334
|
+
'admin.removeOwner': '移除维护者',
|
|
335
|
+
'admin.npmUser': 'npm 用户名',
|
|
336
|
+
'admin.npmUserPlaceholder': 'npm 用户名',
|
|
337
|
+
'admin.usernameRequired': '用户名不能为空',
|
|
338
|
+
|
|
339
|
+
// Admin Modal - Access
|
|
340
|
+
'admin.settingAccess': '正在设置访问权限为 {level}...',
|
|
341
|
+
'admin.accessTitle': '访问权限: {skillId}',
|
|
342
|
+
'admin.accessDesc': '设置包的访问级别。公开包对所有人可见。受限包需要认证才能安装。',
|
|
343
|
+
'admin.accessPublic': '公开',
|
|
344
|
+
'admin.accessRestricted': '受限',
|
|
345
|
+
'admin.setAccess': '设置权限',
|
|
346
|
+
|
|
347
|
+
// Help 视图
|
|
348
|
+
'help.envVars': '环境变量配置',
|
|
349
|
+
'help.setEnvVars': '设置以下环境变量可覆盖默认配置:',
|
|
350
|
+
'help.envVar.primaryScope': '主要 npm scope,用于发布/查找 skill',
|
|
351
|
+
'help.envVar.fallbackScope': '回退 scope(兼容旧安装)',
|
|
352
|
+
'help.envVar.scopeList': '搜索时尝试的 scope 列表(逗号分隔)',
|
|
353
|
+
'help.envVar.registryUrl': 'npm registry 地址',
|
|
354
|
+
'help.envVar.personalLink': '个人链接前缀(publish 输出用)',
|
|
355
|
+
'help.commands': '常用命令',
|
|
356
|
+
|
|
357
|
+
// 语言
|
|
358
|
+
'lang.en': 'EN',
|
|
359
|
+
'lang.zh': '中',
|
|
360
|
+
|
|
361
|
+
// 通用错误
|
|
362
|
+
'error.generic': '错误',
|
|
363
|
+
}
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
let currentLanguage = 'en';
|
|
367
|
+
|
|
368
|
+
// 翻译函数 - 支持变量替换
|
|
369
|
+
function t(key, params = {}) {
|
|
370
|
+
let text = translations[currentLanguage]?.[key] || translations['en'][key] || key;
|
|
371
|
+
// 替换变量 {varName}
|
|
372
|
+
Object.keys(params).forEach(k => {
|
|
373
|
+
text = text.replace(new RegExp(`\\{${k}\\}`, 'g'), params[k]);
|
|
374
|
+
});
|
|
375
|
+
return text;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// 检测语言(localStorage + 浏览器语言)
|
|
379
|
+
function detectLanguage() {
|
|
380
|
+
const saved = localStorage.getItem('skm-language');
|
|
381
|
+
if (saved && ['en', 'zh'].includes(saved)) {
|
|
382
|
+
return saved;
|
|
383
|
+
}
|
|
384
|
+
// 浏览器语言检测
|
|
385
|
+
const browserLang = navigator.language || navigator.userLanguage || '';
|
|
386
|
+
return browserLang.startsWith('zh') ? 'zh' : 'en';
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// 设置语言并重新渲染
|
|
390
|
+
function setLanguage(lang) {
|
|
391
|
+
if (!['en', 'zh'].includes(lang)) return;
|
|
392
|
+
currentLanguage = lang;
|
|
393
|
+
localStorage.setItem('skm-language', lang);
|
|
394
|
+
|
|
395
|
+
// 更新 select 显示
|
|
396
|
+
const langSelect = document.getElementById('lang-select');
|
|
397
|
+
if (langSelect) langSelect.value = lang;
|
|
398
|
+
|
|
399
|
+
// 更新语言选项显示
|
|
400
|
+
updateLanguageOptions();
|
|
401
|
+
|
|
402
|
+
// 重新渲染当前视图
|
|
403
|
+
reRenderCurrentView();
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// 更新语言选项显示(根据当前语言显示选项文本)
|
|
407
|
+
function updateLanguageOptions() {
|
|
408
|
+
const langSelect = document.getElementById('lang-select');
|
|
409
|
+
if (!langSelect) return;
|
|
410
|
+
|
|
411
|
+
langSelect.innerHTML = `
|
|
412
|
+
<option value="en"${currentLanguage === 'en' ? ' selected' : ''}>${t('lang.en')}</option>
|
|
413
|
+
<option value="zh"${currentLanguage === 'zh' ? ' selected' : ''}>${t('lang.zh')}</option>
|
|
414
|
+
`;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// 应用翻译到静态 HTML 元素
|
|
418
|
+
function applyI18nToStaticElements() {
|
|
419
|
+
// 导航按钮
|
|
420
|
+
const navSkills = document.querySelector('.nav-btn[data-view="skills"]');
|
|
421
|
+
const navInstalled = document.querySelector('.nav-btn[data-view="installed"]');
|
|
422
|
+
const navPlatforms = document.querySelector('.nav-btn[data-view="platforms"]');
|
|
423
|
+
const navAdmin = document.querySelector('.nav-btn[data-view="admin"]');
|
|
424
|
+
const navHelp = document.querySelector('.nav-btn[data-view="help"]');
|
|
425
|
+
|
|
426
|
+
if (navSkills) navSkills.innerHTML = `📋 ${t('nav.skills')}`;
|
|
427
|
+
if (navInstalled) navInstalled.innerHTML = `✅ ${t('nav.installed')}`;
|
|
428
|
+
if (navPlatforms) navPlatforms.innerHTML = `💻 ${t('nav.platforms')}`;
|
|
429
|
+
if (navAdmin) navAdmin.innerHTML = `⚙️ ${t('nav.admin')}`;
|
|
430
|
+
if (navHelp) navHelp.innerHTML = `📖 ${t('nav.help')}`;
|
|
431
|
+
|
|
432
|
+
// 视图标题
|
|
433
|
+
const titles = [
|
|
434
|
+
{ selector: '#view-skills .view-header h2', key: 'title.availableSkills' },
|
|
435
|
+
{ selector: '#view-installed .view-header h2', key: 'title.installedSkills' },
|
|
436
|
+
{ selector: '#view-platforms .view-header h2', key: 'title.platforms' },
|
|
437
|
+
{ selector: '#view-help .view-header h2', key: 'title.help' },
|
|
438
|
+
{ selector: '#view-admin .view-header h2', key: 'title.admin' },
|
|
439
|
+
];
|
|
440
|
+
|
|
441
|
+
titles.forEach(({ selector, key }) => {
|
|
442
|
+
const el = document.querySelector(selector);
|
|
443
|
+
if (el) el.textContent = t(key);
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
// 搜索框 placeholder
|
|
447
|
+
const searchInput = document.getElementById('search-input');
|
|
448
|
+
if (searchInput) searchInput.placeholder = t('search.placeholder');
|
|
449
|
+
|
|
450
|
+
// page-size select 选项
|
|
451
|
+
const pageSizeSelect = document.getElementById('page-size');
|
|
452
|
+
if (pageSizeSelect) {
|
|
453
|
+
const currentValue = pageSizeSelect.value;
|
|
454
|
+
pageSizeSelect.innerHTML = `
|
|
455
|
+
<option value="10"${currentValue === '10' ? ' selected' : ''}>${t('pageSize.10')}</option>
|
|
456
|
+
<option value="20"${currentValue === '20' ? ' selected' : ''}>${t('pageSize.20')}</option>
|
|
457
|
+
<option value="50"${currentValue === '50' ? ' selected' : ''}>${t('pageSize.50')}</option>
|
|
458
|
+
`;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// 按钮文本
|
|
462
|
+
const refreshSkills = document.getElementById('refresh-skills');
|
|
463
|
+
const refreshAdmin = document.getElementById('refresh-admin');
|
|
464
|
+
const updateAll = document.getElementById('update-all');
|
|
465
|
+
const backBtn = document.querySelector('#view-skill-detail .btn-secondary');
|
|
466
|
+
|
|
467
|
+
if (refreshSkills) refreshSkills.innerHTML = `🔄 ${t('btn.refresh')}`;
|
|
468
|
+
if (refreshAdmin) refreshAdmin.innerHTML = `🔄 ${t('btn.refresh')}`;
|
|
469
|
+
if (updateAll) updateAll.innerHTML = `🔄 ${t('btn.updateAll')}`;
|
|
470
|
+
if (backBtn) backBtn.innerHTML = `← ${t('nav.back')}`;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// 重新渲染当前视图
|
|
474
|
+
function reRenderCurrentView() {
|
|
475
|
+
// 更新语言选项
|
|
476
|
+
updateLanguageOptions();
|
|
477
|
+
|
|
478
|
+
// 应用翻译到静态元素
|
|
479
|
+
applyI18nToStaticElements();
|
|
480
|
+
|
|
481
|
+
switch(state.currentView) {
|
|
482
|
+
case 'skills':
|
|
483
|
+
loadSkills();
|
|
484
|
+
break;
|
|
485
|
+
case 'installed':
|
|
486
|
+
loadInstalled();
|
|
487
|
+
break;
|
|
488
|
+
case 'platforms':
|
|
489
|
+
loadPlatforms();
|
|
490
|
+
break;
|
|
491
|
+
case 'help':
|
|
492
|
+
loadHelp();
|
|
493
|
+
break;
|
|
494
|
+
case 'admin':
|
|
495
|
+
loadAdminDashboard();
|
|
496
|
+
break;
|
|
497
|
+
case 'skill-detail':
|
|
498
|
+
// 详情视图不需要特殊处理
|
|
499
|
+
break;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
18
503
|
// -----------------------------------------------------------------------------
|
|
19
504
|
// 初始化
|
|
20
505
|
// -----------------------------------------------------------------------------
|
|
@@ -129,14 +614,30 @@ function initializeControls() {
|
|
|
129
614
|
const refreshAdmin = document.getElementById('refresh-admin');
|
|
130
615
|
if (refreshAdmin) refreshAdmin.addEventListener('click', () => loadAdminDashboard());
|
|
131
616
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
617
|
+
// Admin 模态框 - 点击外部关闭
|
|
618
|
+
const modalEl = document.getElementById('modal');
|
|
619
|
+
if (modalEl) {
|
|
620
|
+
modalEl.addEventListener('click', (e) => {
|
|
621
|
+
if (e.target === modalEl) closeModal();
|
|
622
|
+
});
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
// 语言切换
|
|
626
|
+
const langSelect = document.getElementById('lang-select');
|
|
627
|
+
if (langSelect) {
|
|
628
|
+
// 初始化当前语言
|
|
629
|
+
currentLanguage = detectLanguage();
|
|
630
|
+
langSelect.value = currentLanguage;
|
|
631
|
+
updateLanguageOptions();
|
|
632
|
+
|
|
633
|
+
// 应用翻译到静态元素
|
|
634
|
+
applyI18nToStaticElements();
|
|
635
|
+
|
|
636
|
+
langSelect.addEventListener('change', () => {
|
|
637
|
+
setLanguage(langSelect.value);
|
|
638
|
+
});
|
|
639
|
+
}
|
|
640
|
+
}
|
|
140
641
|
|
|
141
642
|
// -----------------------------------------------------------------------------
|
|
142
643
|
// Skills 列表
|
|
@@ -144,79 +645,79 @@ function initializeControls() {
|
|
|
144
645
|
|
|
145
646
|
async function loadSkills() {
|
|
146
647
|
const container = document.getElementById('skills-list');
|
|
147
|
-
container.innerHTML =
|
|
148
|
-
|
|
648
|
+
container.innerHTML = `<div class="loading">${t('loading.skills')}</div>`;
|
|
649
|
+
|
|
149
650
|
try {
|
|
150
651
|
const params = new URLSearchParams({
|
|
151
652
|
page: state.currentPage.toString(),
|
|
152
653
|
limit: state.pageSize.toString(),
|
|
153
654
|
});
|
|
154
|
-
|
|
655
|
+
|
|
155
656
|
if (state.searchQuery) {
|
|
156
657
|
params.append('search', state.searchQuery);
|
|
157
658
|
}
|
|
158
|
-
|
|
659
|
+
|
|
159
660
|
const response = await fetch(`/api/skills?${params}`);
|
|
160
661
|
const data = await response.json();
|
|
161
|
-
|
|
662
|
+
|
|
162
663
|
if (data.error) {
|
|
163
|
-
container.innerHTML = `<div class="loading"
|
|
664
|
+
container.innerHTML = `<div class="loading">${t('error.generic')}: ${data.error}</div>`;
|
|
164
665
|
return;
|
|
165
666
|
}
|
|
166
|
-
|
|
667
|
+
|
|
167
668
|
renderSkills(data.skills || data, container);
|
|
168
669
|
renderPagination(data.page, data.totalPages || 1);
|
|
169
670
|
renderFetchWarning(data.fetchErrors);
|
|
170
671
|
} catch (err) {
|
|
171
|
-
container.innerHTML = `<div class="loading"
|
|
672
|
+
container.innerHTML = `<div class="loading">${t('error.generic')}: ${err.message}</div>`;
|
|
172
673
|
}
|
|
173
674
|
}
|
|
174
675
|
|
|
175
676
|
async function loadInstalled() {
|
|
176
677
|
const container = document.getElementById('installed-list');
|
|
177
|
-
container.innerHTML =
|
|
178
|
-
|
|
678
|
+
container.innerHTML = `<div class="loading">${t('loading.generic')}</div>`;
|
|
679
|
+
|
|
179
680
|
try {
|
|
180
681
|
const response = await fetch('/api/installed');
|
|
181
682
|
const skills = await response.json();
|
|
182
|
-
|
|
683
|
+
|
|
183
684
|
if (skills.error) {
|
|
184
|
-
container.innerHTML = `<div class="loading"
|
|
685
|
+
container.innerHTML = `<div class="loading">${t('error.generic')}: ${skills.error}</div>`;
|
|
185
686
|
return;
|
|
186
687
|
}
|
|
187
|
-
|
|
688
|
+
|
|
188
689
|
renderSkills(skills, container, true);
|
|
189
690
|
} catch (err) {
|
|
190
|
-
container.innerHTML = `<div class="loading"
|
|
691
|
+
container.innerHTML = `<div class="loading">${t('error.generic')}: ${err.message}</div>`;
|
|
191
692
|
}
|
|
192
693
|
}
|
|
193
694
|
|
|
194
695
|
function renderSkills(skills, container, isInstalled = false) {
|
|
195
696
|
if (!skills || skills.length === 0) {
|
|
196
|
-
container.innerHTML =
|
|
697
|
+
container.innerHTML = `<div class="loading">${t('empty.noSkills')}</div>`;
|
|
197
698
|
return;
|
|
198
699
|
}
|
|
199
|
-
|
|
700
|
+
|
|
200
701
|
container.innerHTML = skills.map(skill => createSkillCard(skill, isInstalled)).join('');
|
|
201
702
|
}
|
|
202
703
|
|
|
203
704
|
function createSkillCard(skill, isInstalled) {
|
|
204
705
|
const platforms = skill.platforms || [];
|
|
205
706
|
const platformTags = platforms.map(p => `<span class="platform-tag">${p}</span>`).join('');
|
|
206
|
-
|
|
707
|
+
|
|
207
708
|
return `
|
|
208
709
|
<div class="skill-card" onclick="showSkillDetail('${skill.id}')">
|
|
209
710
|
<h3>${skill.displayName || skill.id}</h3>
|
|
210
711
|
<div class="skill-id">${skill.id}@${skill.version || 'latest'}</div>
|
|
211
|
-
<p>${skill.description || '
|
|
712
|
+
<p>${skill.description || t('detail.noDescription')}</p>
|
|
212
713
|
<div class="platforms">${platformTags}</div>
|
|
213
714
|
<div class="actions">
|
|
214
715
|
${isInstalled ? `
|
|
215
|
-
<button class="btn btn-danger btn-sm" onclick="event.stopPropagation(); uninstallSkill('${skill.id}')"
|
|
216
|
-
<button class="btn btn-primary btn-sm" onclick="event.stopPropagation(); updateSkill('${skill.id}')"
|
|
217
|
-
<button class="btn btn-secondary btn-sm" onclick="event.stopPropagation(); showSkillDetail('${skill.id}')"
|
|
716
|
+
<button class="btn btn-danger btn-sm" onclick="event.stopPropagation(); uninstallSkill('${skill.id}')">${t('btn.uninstall')}</button>
|
|
717
|
+
<button class="btn btn-primary btn-sm" onclick="event.stopPropagation(); updateSkill('${skill.id}')">${t('btn.update')}</button>
|
|
718
|
+
<button class="btn btn-secondary btn-sm" onclick="event.stopPropagation(); showSkillDetail('${skill.id}')">${t('btn.info')}</button>
|
|
218
719
|
` : `
|
|
219
|
-
<button class="btn btn-success btn-sm" onclick="event.stopPropagation(); installSkill('${skill.id}')"
|
|
720
|
+
<button class="btn btn-success btn-sm" onclick="event.stopPropagation(); installSkill('${skill.id}')">${t('btn.install')}</button>
|
|
220
721
|
`}
|
|
221
722
|
</div>
|
|
222
723
|
</div>
|
|
@@ -236,7 +737,7 @@ function renderFetchWarning(fetchErrors) {
|
|
|
236
737
|
const warning = document.createElement('div');
|
|
237
738
|
warning.id = 'fetch-warning';
|
|
238
739
|
warning.style.cssText = 'background: #664400; color: #ffcc00; padding: 8px 16px; border-radius: 6px; margin-bottom: 16px; font-size: 0.9rem;';
|
|
239
|
-
warning.textContent =
|
|
740
|
+
warning.textContent = t('warning.fetchErrors', { count: fetchErrors });
|
|
240
741
|
document.querySelector('.view-header').after(warning);
|
|
241
742
|
}
|
|
242
743
|
|
|
@@ -247,20 +748,20 @@ function renderFetchWarning(fetchErrors) {
|
|
|
247
748
|
function renderPagination(currentPage, totalPages) {
|
|
248
749
|
state.currentPage = currentPage;
|
|
249
750
|
state.totalPages = totalPages;
|
|
250
|
-
|
|
751
|
+
|
|
251
752
|
const container = document.getElementById('pagination');
|
|
252
|
-
|
|
753
|
+
|
|
253
754
|
if (totalPages <= 1) {
|
|
254
755
|
container.innerHTML = '';
|
|
255
756
|
return;
|
|
256
757
|
}
|
|
257
|
-
|
|
758
|
+
|
|
258
759
|
let html = `
|
|
259
|
-
<button ${currentPage <= 1 ? 'disabled' : ''} onclick="changePage(${currentPage - 1})"
|
|
260
|
-
<span class="page-info"
|
|
261
|
-
<button ${currentPage >= totalPages ? 'disabled' : ''} onclick="changePage(${currentPage + 1})"
|
|
760
|
+
<button ${currentPage <= 1 ? 'disabled' : ''} onclick="changePage(${currentPage - 1})">${t('pagination.prev')}</button>
|
|
761
|
+
<span class="page-info">${t('pagination.pageInfo', { page: currentPage, totalPages: totalPages })}</span>
|
|
762
|
+
<button ${currentPage >= totalPages ? 'disabled' : ''} onclick="changePage(${currentPage + 1})">${t('pagination.next')}</button>
|
|
262
763
|
`;
|
|
263
|
-
|
|
764
|
+
|
|
264
765
|
container.innerHTML = html;
|
|
265
766
|
}
|
|
266
767
|
|
|
@@ -276,39 +777,39 @@ function changePage(page) {
|
|
|
276
777
|
|
|
277
778
|
async function loadPlatforms() {
|
|
278
779
|
const container = document.getElementById('platforms-list');
|
|
279
|
-
container.innerHTML =
|
|
280
|
-
|
|
780
|
+
container.innerHTML = `<div class="loading">${t('loading.platforms')}</div>`;
|
|
781
|
+
|
|
281
782
|
try {
|
|
282
783
|
const response = await fetch('/api/platforms');
|
|
283
784
|
const platforms = await response.json();
|
|
284
|
-
|
|
785
|
+
|
|
285
786
|
if (platforms.error) {
|
|
286
|
-
container.innerHTML = `<div class="loading"
|
|
787
|
+
container.innerHTML = `<div class="loading">${t('error.generic')}: ${platforms.error}</div>`;
|
|
287
788
|
return;
|
|
288
789
|
}
|
|
289
|
-
|
|
790
|
+
|
|
290
791
|
renderPlatforms(platforms, container);
|
|
291
792
|
} catch (err) {
|
|
292
|
-
container.innerHTML = `<div class="loading"
|
|
793
|
+
container.innerHTML = `<div class="loading">${t('error.generic')}: ${err.message}</div>`;
|
|
293
794
|
}
|
|
294
795
|
}
|
|
295
796
|
|
|
296
797
|
function renderPlatforms(platforms, container) {
|
|
297
798
|
if (!platforms || platforms.length === 0) {
|
|
298
|
-
container.innerHTML =
|
|
799
|
+
container.innerHTML = `<div class="loading">${t('empty.noPlatforms')}</div>`;
|
|
299
800
|
return;
|
|
300
801
|
}
|
|
301
|
-
|
|
802
|
+
|
|
302
803
|
container.innerHTML = platforms.map(platform => `
|
|
303
804
|
<div class="platform-card">
|
|
304
805
|
<div>
|
|
305
806
|
<h3>${platform.name}</h3>
|
|
306
807
|
<div class="status ${platform.available ? 'status-available' : 'status-unavailable'}">
|
|
307
|
-
${platform.available ? '
|
|
808
|
+
${platform.available ? t('status.available') : t('status.unavailable')}
|
|
308
809
|
</div>
|
|
309
810
|
</div>
|
|
310
811
|
<div>
|
|
311
|
-
${platform.installedCount ? `<span>${platform.installedCount}
|
|
812
|
+
${platform.installedCount ? `<span>${t('status.skillsInstalled', { count: platform.installedCount })}</span>` : ''}
|
|
312
813
|
</div>
|
|
313
814
|
</div>
|
|
314
815
|
`).join('');
|
|
@@ -333,11 +834,11 @@ async function loadHelp() {
|
|
|
333
834
|
|
|
334
835
|
function renderHelp(config, container) {
|
|
335
836
|
const envVars = [
|
|
336
|
-
{ var: 'SKM_NPM_SCOPE', default: config.npmScope, desc: '
|
|
337
|
-
{ var: 'SKM_NPM_SCOPE_FALLBACK', default: config.npmScopeFallback, desc: '
|
|
338
|
-
{ var: 'SKM_NPM_SCOPES', default: config.skillScopes.join(', '), desc: '
|
|
339
|
-
{ var: 'SKM_NPM_REGISTRY', default: config.npmRegistry, desc: '
|
|
340
|
-
{ var: 'SKM_URL', default: config.skmUrl, desc: '
|
|
837
|
+
{ var: 'SKM_NPM_SCOPE', default: config.npmScope, desc: t('help.envVar.primaryScope') },
|
|
838
|
+
{ var: 'SKM_NPM_SCOPE_FALLBACK', default: config.npmScopeFallback, desc: t('help.envVar.fallbackScope') },
|
|
839
|
+
{ var: 'SKM_NPM_SCOPES', default: config.skillScopes.join(', '), desc: t('help.envVar.scopeList') },
|
|
840
|
+
{ var: 'SKM_NPM_REGISTRY', default: config.npmRegistry, desc: t('help.envVar.registryUrl') },
|
|
841
|
+
{ var: 'SKM_URL', default: config.skmUrl, desc: t('help.envVar.personalLink') },
|
|
341
842
|
];
|
|
342
843
|
|
|
343
844
|
container.innerHTML = `
|
|
@@ -412,77 +913,77 @@ skm publish <skill-name> --version 1.0.1</pre>
|
|
|
412
913
|
|
|
413
914
|
async function installSkill(skillId) {
|
|
414
915
|
try {
|
|
415
|
-
showToast(
|
|
916
|
+
showToast(t('toast.installing', { skillId: skillId }), 'info');
|
|
416
917
|
const response = await fetch('/api/install', {
|
|
417
918
|
method: 'POST',
|
|
418
919
|
headers: { 'Content-Type': 'application/json' },
|
|
419
920
|
body: JSON.stringify({ skillId })
|
|
420
921
|
});
|
|
421
|
-
|
|
922
|
+
|
|
422
923
|
const result = await response.json();
|
|
423
|
-
|
|
924
|
+
|
|
424
925
|
if (result.error) {
|
|
425
|
-
showToast(
|
|
926
|
+
showToast(`${t('error.generic')}: ${result.error}`, 'error');
|
|
426
927
|
} else {
|
|
427
|
-
showToast(
|
|
928
|
+
showToast(t('toast.installSuccess', { skillId: skillId }), 'success');
|
|
428
929
|
if (state.currentView === 'installed') loadInstalled();
|
|
429
930
|
}
|
|
430
931
|
} catch (err) {
|
|
431
|
-
showToast(
|
|
932
|
+
showToast(`${t('error.generic')}: ${err.message}`, 'error');
|
|
432
933
|
}
|
|
433
934
|
}
|
|
434
935
|
|
|
435
936
|
async function uninstallSkill(skillId) {
|
|
436
|
-
if (!confirm(
|
|
437
|
-
|
|
937
|
+
if (!confirm(t('confirm.uninstall', { skillId: skillId }))) return;
|
|
938
|
+
|
|
438
939
|
try {
|
|
439
|
-
showToast(
|
|
940
|
+
showToast(t('toast.uninstalling', { skillId: skillId }), 'info');
|
|
440
941
|
const response = await fetch('/api/uninstall', {
|
|
441
942
|
method: 'POST',
|
|
442
943
|
headers: { 'Content-Type': 'application/json' },
|
|
443
944
|
body: JSON.stringify({ skillId })
|
|
444
945
|
});
|
|
445
|
-
|
|
946
|
+
|
|
446
947
|
const result = await response.json();
|
|
447
|
-
|
|
948
|
+
|
|
448
949
|
if (result.error) {
|
|
449
|
-
showToast(
|
|
950
|
+
showToast(`${t('error.generic')}: ${result.error}`, 'error');
|
|
450
951
|
} else {
|
|
451
|
-
showToast(
|
|
952
|
+
showToast(t('toast.uninstallSuccess', { skillId: skillId }), 'success');
|
|
452
953
|
loadInstalled();
|
|
453
954
|
}
|
|
454
955
|
} catch (err) {
|
|
455
|
-
showToast(
|
|
956
|
+
showToast(`${t('error.generic')}: ${err.message}`, 'error');
|
|
456
957
|
}
|
|
457
958
|
}
|
|
458
959
|
|
|
459
960
|
async function updateSkill(skillId) {
|
|
460
961
|
try {
|
|
461
|
-
showToast(
|
|
962
|
+
showToast(t('toast.updating', { skillId: skillId }), 'info');
|
|
462
963
|
const response = await fetch('/api/update', {
|
|
463
964
|
method: 'POST',
|
|
464
965
|
headers: { 'Content-Type': 'application/json' },
|
|
465
966
|
body: JSON.stringify({ skillId })
|
|
466
967
|
});
|
|
467
|
-
|
|
968
|
+
|
|
468
969
|
const result = await response.json();
|
|
469
|
-
|
|
970
|
+
|
|
470
971
|
if (result.error) {
|
|
471
|
-
showToast(
|
|
972
|
+
showToast(`${t('error.generic')}: ${result.error}`, 'error');
|
|
472
973
|
} else {
|
|
473
|
-
showToast(
|
|
974
|
+
showToast(t('toast.updateSuccess', { skillId: skillId }), 'success');
|
|
474
975
|
if (state.currentView === 'installed') loadInstalled();
|
|
475
976
|
}
|
|
476
977
|
} catch (err) {
|
|
477
|
-
showToast(
|
|
978
|
+
showToast(`${t('error.generic')}: ${err.message}`, 'error');
|
|
478
979
|
}
|
|
479
980
|
}
|
|
480
981
|
|
|
481
982
|
async function updateAllSkills() {
|
|
482
|
-
if (!confirm('
|
|
983
|
+
if (!confirm(t('confirm.updateAll'))) return;
|
|
483
984
|
|
|
484
985
|
try {
|
|
485
|
-
showToast('
|
|
986
|
+
showToast(t('toast.updateAll'), 'info');
|
|
486
987
|
const response = await fetch('/api/update', {
|
|
487
988
|
method: 'POST',
|
|
488
989
|
headers: { 'Content-Type': 'application/json' },
|
|
@@ -492,13 +993,13 @@ async function updateAllSkills() {
|
|
|
492
993
|
const result = await response.json();
|
|
493
994
|
|
|
494
995
|
if (result.error) {
|
|
495
|
-
showToast(
|
|
996
|
+
showToast(`${t('error.generic')}: ${result.error}`, 'error');
|
|
496
997
|
} else {
|
|
497
|
-
showToast('
|
|
998
|
+
showToast(t('toast.updateAllSuccess'), 'success');
|
|
498
999
|
loadInstalled();
|
|
499
1000
|
}
|
|
500
1001
|
} catch (err) {
|
|
501
|
-
showToast(
|
|
1002
|
+
showToast(`${t('error.generic')}: ${err.message}`, 'error');
|
|
502
1003
|
}
|
|
503
1004
|
}
|
|
504
1005
|
|
|
@@ -508,7 +1009,7 @@ async function updateAllSkills() {
|
|
|
508
1009
|
|
|
509
1010
|
async function showSkillDetail(skillId) {
|
|
510
1011
|
const content = document.getElementById('skill-detail-content');
|
|
511
|
-
content.innerHTML =
|
|
1012
|
+
content.innerHTML = `<div class="loading">${t('loading.details')}</div>`;
|
|
512
1013
|
|
|
513
1014
|
// 切换到详情视图
|
|
514
1015
|
const btn = document.querySelector(`.nav-btn[data-view="skill-detail"]`);
|
|
@@ -526,7 +1027,7 @@ async function showSkillDetail(skillId) {
|
|
|
526
1027
|
const data = await response.json();
|
|
527
1028
|
|
|
528
1029
|
if (data.error) {
|
|
529
|
-
content.innerHTML = `<div class="loading"
|
|
1030
|
+
content.innerHTML = `<div class="loading">${t('error.generic')}: ${data.error}</div>`;
|
|
530
1031
|
return;
|
|
531
1032
|
}
|
|
532
1033
|
|
|
@@ -540,34 +1041,34 @@ async function showSkillDetail(skillId) {
|
|
|
540
1041
|
<div class="detail-name">${data.name}@${data.version}</div>
|
|
541
1042
|
|
|
542
1043
|
<div class="detail-section">
|
|
543
|
-
<h3
|
|
544
|
-
<div class="description-text">${data.description || '
|
|
1044
|
+
<h3>${t('detail.description')}</h3>
|
|
1045
|
+
<div class="description-text">${data.description || t('detail.noDescription')}</div>
|
|
545
1046
|
</div>
|
|
546
1047
|
|
|
547
1048
|
<div class="detail-section">
|
|
548
|
-
<h3
|
|
549
|
-
<div class="detail-row"><strong
|
|
550
|
-
<div class="detail-row"><strong
|
|
551
|
-
${data.license ? `<div class="detail-row"><strong
|
|
552
|
-
${data.author ? `<div class="detail-row"><strong
|
|
553
|
-
${data.homepage ? `<div class="detail-row"><strong
|
|
554
|
-
${data.repository ? `<div class="detail-row"><strong
|
|
1049
|
+
<h3>${t('detail.details')}</h3>
|
|
1050
|
+
<div class="detail-row"><strong>${t('detail.id')}:</strong> ${data.id}</div>
|
|
1051
|
+
<div class="detail-row"><strong>${t('detail.version')}:</strong> ${data.version}</div>
|
|
1052
|
+
${data.license ? `<div class="detail-row"><strong>${t('detail.license')}:</strong> ${data.license}</div>` : ''}
|
|
1053
|
+
${data.author ? `<div class="detail-row"><strong>${t('detail.author')}:</strong> ${data.author}</div>` : ''}
|
|
1054
|
+
${data.homepage ? `<div class="detail-row"><strong>${t('detail.homepage')}:</strong> <a href="${data.homepage}" target="_blank">${data.homepage}</a></div>` : ''}
|
|
1055
|
+
${data.repository ? `<div class="detail-row"><strong>${t('detail.repository')}:</strong> <a href="${data.repository}" target="_blank">${data.repository}</a></div>` : ''}
|
|
555
1056
|
</div>
|
|
556
1057
|
|
|
557
1058
|
<div class="detail-section">
|
|
558
|
-
<h3
|
|
1059
|
+
<h3>${t('detail.platforms')}</h3>
|
|
559
1060
|
<div class="platform-tags">
|
|
560
|
-
${platforms.length ? platforms.map(p => `<span class="platform-tag">${p}</span>`).join('') :
|
|
1061
|
+
${platforms.length ? platforms.map(p => `<span class="platform-tag">${p}</span>`).join('') : `<span class="platform-tag">${t('detail.nA')}</span>`}
|
|
561
1062
|
</div>
|
|
562
1063
|
</div>
|
|
563
1064
|
|
|
564
1065
|
${versions.length ? `
|
|
565
1066
|
<div class="detail-section">
|
|
566
|
-
<h3
|
|
1067
|
+
<h3>${t('detail.versions', { count: versions.length })}</h3>
|
|
567
1068
|
<div class="version-list">
|
|
568
1069
|
${versions.slice().reverse().map(v => `
|
|
569
1070
|
<div class="version-item">
|
|
570
|
-
<span>${v} ${v === data.version ?
|
|
1071
|
+
<span>${v} ${v === data.version ? `<span class="version-latest">${t('detail.latest')}</span>` : ''}</span>
|
|
571
1072
|
</div>
|
|
572
1073
|
`).join('')}
|
|
573
1074
|
</div>
|
|
@@ -575,12 +1076,12 @@ async function showSkillDetail(skillId) {
|
|
|
575
1076
|
` : ''}
|
|
576
1077
|
|
|
577
1078
|
<div class="detail-actions">
|
|
578
|
-
<button class="btn btn-success" onclick="installSkill('${escapedId}')"
|
|
1079
|
+
<button class="btn btn-success" onclick="installSkill('${escapedId}')">${t('btn.install')}</button>
|
|
579
1080
|
</div>
|
|
580
1081
|
</div>
|
|
581
1082
|
`;
|
|
582
1083
|
} catch (err) {
|
|
583
|
-
content.innerHTML = `<div class="loading"
|
|
1084
|
+
content.innerHTML = `<div class="loading">${t('error.generic')}: ${err.message}</div>`;
|
|
584
1085
|
}
|
|
585
1086
|
}
|
|
586
1087
|
|
|
@@ -622,24 +1123,24 @@ async function loadAdminDashboard() {
|
|
|
622
1123
|
|
|
623
1124
|
async function loadAdminStats() {
|
|
624
1125
|
const container = document.getElementById('admin-stats');
|
|
625
|
-
container.innerHTML =
|
|
1126
|
+
container.innerHTML = `<div class="loading">${t('loading.stats')}</div>`;
|
|
626
1127
|
|
|
627
1128
|
try {
|
|
628
1129
|
const response = await fetch('/api/admin/stats');
|
|
629
1130
|
const data = await response.json();
|
|
630
1131
|
|
|
631
1132
|
if (data.error) {
|
|
632
|
-
container.innerHTML = `<div class="loading"
|
|
1133
|
+
container.innerHTML = `<div class="loading">${t('error.generic')}: ${data.error}</div>`;
|
|
633
1134
|
return;
|
|
634
1135
|
}
|
|
635
1136
|
|
|
636
1137
|
const cards = [
|
|
637
|
-
{ value: data.totalSkills, label: '
|
|
638
|
-
{ value: data.totalVersions, label: '
|
|
639
|
-
{ value: data.averageVersions, label: '
|
|
640
|
-
{ value: data.withMetadata, label: '
|
|
641
|
-
{ value:
|
|
642
|
-
{ value: data.platformCount, label: '
|
|
1138
|
+
{ value: data.totalSkills, label: t('admin.stats.totalSkills') },
|
|
1139
|
+
{ value: data.totalVersions, label: t('admin.stats.totalVersions') },
|
|
1140
|
+
{ value: data.averageVersions, label: t('admin.stats.avgVersions') },
|
|
1141
|
+
{ value: data.withMetadata, label: t('admin.stats.withMetadata') },
|
|
1142
|
+
{ value: t('admin.stats.totalSize', { value: data.totalSizeMB }), label: t('admin.stats.totalSize', { value: '' }).replace(': ', '') },
|
|
1143
|
+
{ value: data.platformCount, label: t('admin.stats.platformsCovered') },
|
|
643
1144
|
];
|
|
644
1145
|
|
|
645
1146
|
container.innerHTML = cards.map(c => `
|
|
@@ -649,13 +1150,13 @@ async function loadAdminStats() {
|
|
|
649
1150
|
</div>
|
|
650
1151
|
`).join('');
|
|
651
1152
|
} catch (err) {
|
|
652
|
-
container.innerHTML = `<div class="loading"
|
|
1153
|
+
container.innerHTML = `<div class="loading">${t('error.generic')}: ${err.message}</div>`;
|
|
653
1154
|
}
|
|
654
1155
|
}
|
|
655
1156
|
|
|
656
1157
|
async function loadAdminSkills() {
|
|
657
1158
|
const container = document.getElementById('admin-skills-list');
|
|
658
|
-
container.innerHTML =
|
|
1159
|
+
container.innerHTML = `<div class="loading">${t('loading.published')}</div>`;
|
|
659
1160
|
|
|
660
1161
|
try {
|
|
661
1162
|
const params = new URLSearchParams({ limit: '100' });
|
|
@@ -663,25 +1164,25 @@ async function loadAdminSkills() {
|
|
|
663
1164
|
const data = await response.json();
|
|
664
1165
|
|
|
665
1166
|
if (data.error) {
|
|
666
|
-
container.innerHTML = `<div class="loading"
|
|
1167
|
+
container.innerHTML = `<div class="loading">${t('error.generic')}: ${data.error}</div>`;
|
|
667
1168
|
return;
|
|
668
1169
|
}
|
|
669
1170
|
|
|
670
1171
|
const skills = data.skills || [];
|
|
671
1172
|
renderAdminSkills(skills, container);
|
|
672
1173
|
} catch (err) {
|
|
673
|
-
container.innerHTML = `<div class="loading"
|
|
1174
|
+
container.innerHTML = `<div class="loading">${t('error.generic')}: ${err.message}</div>`;
|
|
674
1175
|
}
|
|
675
1176
|
}
|
|
676
1177
|
|
|
677
1178
|
function renderAdminSkills(skills, container) {
|
|
678
1179
|
if (!skills || skills.length === 0) {
|
|
679
|
-
container.innerHTML =
|
|
1180
|
+
container.innerHTML = `<div class="loading">${t('empty.noPublishedSkills')}</div>`;
|
|
680
1181
|
return;
|
|
681
1182
|
}
|
|
682
1183
|
|
|
683
1184
|
container.innerHTML = `
|
|
684
|
-
<h3 style="color: var(--text-secondary); margin-bottom: 12px; font-size: 1rem;"
|
|
1185
|
+
<h3 style="color: var(--text-secondary); margin-bottom: 12px; font-size: 1rem;">${t('admin.publishedSkillsCount', { count: skills.length })}</h3>
|
|
685
1186
|
${skills.map(skill => {
|
|
686
1187
|
const id = skill.id || skill.name;
|
|
687
1188
|
const desc = (skill.description || '').slice(0, 80);
|
|
@@ -693,11 +1194,11 @@ function renderAdminSkills(skills, container) {
|
|
|
693
1194
|
${desc ? `<div class="admin-skill-desc">${desc}</div>` : ''}
|
|
694
1195
|
</div>
|
|
695
1196
|
<div class="admin-actions">
|
|
696
|
-
<button class="admin-btn admin-btn-deprecate" onclick="showAdminDeprecateModal('${id}')"
|
|
697
|
-
<button class="admin-btn admin-btn-unpublish" onclick="showAdminUnpublishModal('${id}')"
|
|
698
|
-
<button class="admin-btn admin-btn-tag" onclick="showAdminTagModal('${id}')"
|
|
699
|
-
<button class="admin-btn admin-btn-owner" onclick="showAdminOwnerModal('${id}')"
|
|
700
|
-
<button class="admin-btn admin-btn-access" onclick="showAdminAccessModal('${id}')"
|
|
1197
|
+
<button class="admin-btn admin-btn-deprecate" onclick="showAdminDeprecateModal('${id}')">${t('admin.deprecateTitle', { skillId: '' }).replace(': ', '')}</button>
|
|
1198
|
+
<button class="admin-btn admin-btn-unpublish" onclick="showAdminUnpublishModal('${id}')">${t('admin.unpublishTitle', { skillId: '' }).replace(': ', '')}</button>
|
|
1199
|
+
<button class="admin-btn admin-btn-tag" onclick="showAdminTagModal('${id}')">${t('admin.tagsTitle', { skillId: '' }).replace(': ', '')}</button>
|
|
1200
|
+
<button class="admin-btn admin-btn-owner" onclick="showAdminOwnerModal('${id}')">${t('admin.ownersTitle', { skillId: '' }).replace(': ', '')}</button>
|
|
1201
|
+
<button class="admin-btn admin-btn-access" onclick="showAdminAccessModal('${id}')">${t('admin.accessTitle', { skillId: '' }).replace(': ', '')}</button>
|
|
701
1202
|
</div>
|
|
702
1203
|
</div>
|
|
703
1204
|
`;
|
|
@@ -714,21 +1215,21 @@ function showAdminDeprecateModal(skillId) {
|
|
|
714
1215
|
const modalBody = document.getElementById('modal-body');
|
|
715
1216
|
|
|
716
1217
|
modalBody.innerHTML = `
|
|
717
|
-
<h2
|
|
1218
|
+
<h2>${t('admin.deprecateTitle', { skillId: skillId })}</h2>
|
|
718
1219
|
<div class="admin-modal-section">
|
|
719
1220
|
<div class="admin-input-group">
|
|
720
|
-
<label
|
|
721
|
-
<input type="text" id="deprecate-version" placeholder="(
|
|
1221
|
+
<label>${t('admin.deprecateVersion')}</label>
|
|
1222
|
+
<input type="text" id="deprecate-version" placeholder="${t('admin.deprecateVersionPlaceholder')}">
|
|
722
1223
|
</div>
|
|
723
1224
|
<div class="admin-input-group">
|
|
724
|
-
<label
|
|
725
|
-
<input type="text" id="deprecate-msg" value="
|
|
1225
|
+
<label>${t('admin.deprecateMessage')}</label>
|
|
1226
|
+
<input type="text" id="deprecate-msg" value="${t('admin.deprecateDefaultMsg')}">
|
|
726
1227
|
</div>
|
|
727
|
-
<p class="admin-warning-text"
|
|
1228
|
+
<p class="admin-warning-text">${t('admin.deprecateWarning')}</p>
|
|
728
1229
|
</div>
|
|
729
1230
|
<div class="actions">
|
|
730
|
-
<button class="btn btn-danger" onclick="execAdminDeprecate('${skillId}')"
|
|
731
|
-
<button class="btn btn-secondary" onclick="closeModal()"
|
|
1231
|
+
<button class="btn btn-danger" onclick="execAdminDeprecate('${skillId}')">${t('admin.confirmDeprecate')}</button>
|
|
1232
|
+
<button class="btn btn-secondary" onclick="closeModal()">${t('btn.cancel')}</button>
|
|
732
1233
|
</div>
|
|
733
1234
|
`;
|
|
734
1235
|
modal.classList.remove('hidden');
|
|
@@ -739,7 +1240,7 @@ async function execAdminDeprecate(skillId) {
|
|
|
739
1240
|
const message = document.getElementById('deprecate-msg').value;
|
|
740
1241
|
|
|
741
1242
|
try {
|
|
742
|
-
showToast(
|
|
1243
|
+
showToast(t('admin.deprecating', { skillId: skillId }), 'info');
|
|
743
1244
|
const response = await fetch('/api/admin/deprecate', {
|
|
744
1245
|
method: 'POST',
|
|
745
1246
|
headers: { 'Content-Type': 'application/json' },
|
|
@@ -747,14 +1248,14 @@ async function execAdminDeprecate(skillId) {
|
|
|
747
1248
|
});
|
|
748
1249
|
const result = await response.json();
|
|
749
1250
|
if (result.error) {
|
|
750
|
-
showToast(
|
|
1251
|
+
showToast(`${t('error.generic')}: ${result.error}`, 'error');
|
|
751
1252
|
} else {
|
|
752
1253
|
showToast(`✅ ${result.message}`, 'success');
|
|
753
1254
|
closeModal();
|
|
754
1255
|
loadAdminDashboard();
|
|
755
1256
|
}
|
|
756
1257
|
} catch (err) {
|
|
757
|
-
showToast(
|
|
1258
|
+
showToast(`${t('error.generic')}: ${err.message}`, 'error');
|
|
758
1259
|
}
|
|
759
1260
|
}
|
|
760
1261
|
|
|
@@ -763,21 +1264,21 @@ function showAdminUnpublishModal(skillId) {
|
|
|
763
1264
|
const modalBody = document.getElementById('modal-body');
|
|
764
1265
|
|
|
765
1266
|
modalBody.innerHTML = `
|
|
766
|
-
<h2
|
|
1267
|
+
<h2>${t('admin.unpublishTitle', { skillId: skillId })}</h2>
|
|
767
1268
|
<div class="admin-modal-section">
|
|
768
1269
|
<div class="admin-input-group">
|
|
769
|
-
<label
|
|
770
|
-
<input type="text" id="unpublish-version" placeholder="(
|
|
1270
|
+
<label>${t('admin.unpublishVersion')}</label>
|
|
1271
|
+
<input type="text" id="unpublish-version" placeholder="${t('admin.unpublishVersionPlaceholder')}">
|
|
771
1272
|
</div>
|
|
772
1273
|
<div class="admin-checkbox-group">
|
|
773
1274
|
<input type="checkbox" id="unpublish-force">
|
|
774
|
-
<label for="unpublish-force"
|
|
1275
|
+
<label for="unpublish-force">${t('admin.unpublishForce')}</label>
|
|
775
1276
|
</div>
|
|
776
|
-
<p class="admin-danger-text"
|
|
1277
|
+
<p class="admin-danger-text">${t('admin.unpublishDanger')}</p>
|
|
777
1278
|
</div>
|
|
778
1279
|
<div class="actions">
|
|
779
|
-
<button class="btn btn-danger" onclick="execAdminUnpublish('${skillId}')"
|
|
780
|
-
<button class="btn btn-secondary" onclick="closeModal()"
|
|
1280
|
+
<button class="btn btn-danger" onclick="execAdminUnpublish('${skillId}')">${t('admin.confirmUnpublish')}</button>
|
|
1281
|
+
<button class="btn btn-secondary" onclick="closeModal()">${t('btn.cancel')}</button>
|
|
781
1282
|
</div>
|
|
782
1283
|
`;
|
|
783
1284
|
modal.classList.remove('hidden');
|
|
@@ -788,7 +1289,7 @@ async function execAdminUnpublish(skillId) {
|
|
|
788
1289
|
const force = document.getElementById('unpublish-force').checked;
|
|
789
1290
|
|
|
790
1291
|
try {
|
|
791
|
-
showToast(
|
|
1292
|
+
showToast(t('admin.unpublishing', { skillId: skillId }), 'info');
|
|
792
1293
|
const response = await fetch('/api/admin/unpublish', {
|
|
793
1294
|
method: 'POST',
|
|
794
1295
|
headers: { 'Content-Type': 'application/json' },
|
|
@@ -796,14 +1297,14 @@ async function execAdminUnpublish(skillId) {
|
|
|
796
1297
|
});
|
|
797
1298
|
const result = await response.json();
|
|
798
1299
|
if (result.error) {
|
|
799
|
-
showToast(
|
|
1300
|
+
showToast(`${t('error.generic')}: ${result.error}`, 'error');
|
|
800
1301
|
} else {
|
|
801
1302
|
showToast(`✅ ${result.message}`, 'success');
|
|
802
1303
|
closeModal();
|
|
803
1304
|
loadAdminDashboard();
|
|
804
1305
|
}
|
|
805
1306
|
} catch (err) {
|
|
806
|
-
showToast(
|
|
1307
|
+
showToast(`${t('error.generic')}: ${err.message}`, 'error');
|
|
807
1308
|
}
|
|
808
1309
|
}
|
|
809
1310
|
|
|
@@ -812,32 +1313,32 @@ function showAdminTagModal(skillId) {
|
|
|
812
1313
|
const modalBody = document.getElementById('modal-body');
|
|
813
1314
|
|
|
814
1315
|
modalBody.innerHTML = `
|
|
815
|
-
<h2
|
|
1316
|
+
<h2>${t('admin.tagsTitle', { skillId: skillId })}</h2>
|
|
816
1317
|
<div id="admin-tag-current">
|
|
817
|
-
<div class="loading"
|
|
1318
|
+
<div class="loading">${t('loading.generic')}</div>
|
|
818
1319
|
</div>
|
|
819
1320
|
<div class="admin-modal-section" style="margin-top: 16px;">
|
|
820
|
-
<h3
|
|
1321
|
+
<h3>${t('admin.setTag')}</h3>
|
|
821
1322
|
<div class="admin-input-group">
|
|
822
|
-
<label
|
|
823
|
-
<input type="text" id="tag-set-name" placeholder="
|
|
1323
|
+
<label>${t('admin.tagName')}</label>
|
|
1324
|
+
<input type="text" id="tag-set-name" placeholder="${t('admin.tagNamePlaceholder')}">
|
|
824
1325
|
</div>
|
|
825
1326
|
<div class="admin-input-group">
|
|
826
|
-
<label
|
|
827
|
-
<input type="text" id="tag-set-version" placeholder="
|
|
1327
|
+
<label>${t('admin.tagVersion')}</label>
|
|
1328
|
+
<input type="text" id="tag-set-version" placeholder="${t('admin.tagVersionPlaceholder')}">
|
|
828
1329
|
</div>
|
|
829
|
-
<button class="btn btn-primary btn-sm" onclick="execAdminTagSet('${skillId}')"
|
|
1330
|
+
<button class="btn btn-primary btn-sm" onclick="execAdminTagSet('${skillId}')">${t('admin.setTag')}</button>
|
|
830
1331
|
</div>
|
|
831
1332
|
<div class="admin-modal-section">
|
|
832
|
-
<h3
|
|
1333
|
+
<h3>${t('admin.removeTag')}</h3>
|
|
833
1334
|
<div class="admin-input-group">
|
|
834
|
-
<label
|
|
835
|
-
<input type="text" id="tag-rm-name" placeholder="
|
|
1335
|
+
<label>${t('admin.tagName')}</label>
|
|
1336
|
+
<input type="text" id="tag-rm-name" placeholder="${t('admin.tagNameRemovePlaceholder')}">
|
|
836
1337
|
</div>
|
|
837
|
-
<button class="btn btn-danger btn-sm" onclick="execAdminTagRemove('${skillId}')"
|
|
1338
|
+
<button class="btn btn-danger btn-sm" onclick="execAdminTagRemove('${skillId}')">${t('admin.removeTag')}</button>
|
|
838
1339
|
</div>
|
|
839
1340
|
<div class="actions" style="margin-top: 16px;">
|
|
840
|
-
<button class="btn btn-secondary" onclick="closeModal()"
|
|
1341
|
+
<button class="btn btn-secondary" onclick="closeModal()">${t('btn.cancel')}</button>
|
|
841
1342
|
</div>
|
|
842
1343
|
`;
|
|
843
1344
|
modal.classList.remove('hidden');
|
|
@@ -858,25 +1359,25 @@ async function loadAdminTags(skillId) {
|
|
|
858
1359
|
if (result.success && result.tags) {
|
|
859
1360
|
const entries = Object.entries(result.tags);
|
|
860
1361
|
if (entries.length === 0) {
|
|
861
|
-
container.innerHTML =
|
|
1362
|
+
container.innerHTML = `<p style="color: var(--text-muted); font-size: 0.85rem;">${t('empty.noTags')}</p>`;
|
|
862
1363
|
} else {
|
|
863
1364
|
container.innerHTML = `
|
|
864
|
-
<h3
|
|
1365
|
+
<h3>${t('admin.currentTags')}</h3>
|
|
865
1366
|
<div class="admin-tag-list">
|
|
866
1367
|
${entries.map(([tag, ver]) => `
|
|
867
1368
|
<span class="admin-tag-item">
|
|
868
1369
|
${tag} <span class="tag-version">→ ${ver}</span>
|
|
869
|
-
${tag === 'latest' ?
|
|
1370
|
+
${tag === 'latest' ? `<span style="color: var(--accent); font-size: 0.75rem;">${t('admin.tag.default')}</span>` : ''}
|
|
870
1371
|
</span>
|
|
871
1372
|
`).join('')}
|
|
872
1373
|
</div>
|
|
873
1374
|
`;
|
|
874
1375
|
}
|
|
875
1376
|
} else {
|
|
876
|
-
container.innerHTML =
|
|
1377
|
+
container.innerHTML = `<p style="color: var(--text-muted); font-size: 0.85rem;">${t('empty.couldNotLoadTags')}</p>`;
|
|
877
1378
|
}
|
|
878
1379
|
} catch (err) {
|
|
879
|
-
container.innerHTML = `<p style="color: #ff6666; font-size: 0.85rem;"
|
|
1380
|
+
container.innerHTML = `<p style="color: #ff6666; font-size: 0.85rem;">${t('error.generic')}: ${err.message}</p>`;
|
|
880
1381
|
}
|
|
881
1382
|
}
|
|
882
1383
|
|
|
@@ -885,12 +1386,12 @@ async function execAdminTagSet(skillId) {
|
|
|
885
1386
|
const version = document.getElementById('tag-set-version').value;
|
|
886
1387
|
|
|
887
1388
|
if (!tag || !version) {
|
|
888
|
-
showToast('
|
|
1389
|
+
showToast(t('admin.tagRequired'), 'error');
|
|
889
1390
|
return;
|
|
890
1391
|
}
|
|
891
1392
|
|
|
892
1393
|
try {
|
|
893
|
-
showToast(
|
|
1394
|
+
showToast(t('admin.settingTag', { tag: tag }), 'info');
|
|
894
1395
|
const response = await fetch('/api/admin/tag', {
|
|
895
1396
|
method: 'POST',
|
|
896
1397
|
headers: { 'Content-Type': 'application/json' },
|
|
@@ -898,7 +1399,7 @@ async function execAdminTagSet(skillId) {
|
|
|
898
1399
|
});
|
|
899
1400
|
const result = await response.json();
|
|
900
1401
|
if (result.error) {
|
|
901
|
-
showToast(
|
|
1402
|
+
showToast(`${t('error.generic')}: ${result.error}`, 'error');
|
|
902
1403
|
} else {
|
|
903
1404
|
showToast(`✅ ${result.message}`, 'success');
|
|
904
1405
|
loadAdminTags(skillId);
|
|
@@ -906,7 +1407,7 @@ async function execAdminTagSet(skillId) {
|
|
|
906
1407
|
document.getElementById('tag-set-version').value = '';
|
|
907
1408
|
}
|
|
908
1409
|
} catch (err) {
|
|
909
|
-
showToast(
|
|
1410
|
+
showToast(`${t('error.generic')}: ${err.message}`, 'error');
|
|
910
1411
|
}
|
|
911
1412
|
}
|
|
912
1413
|
|
|
@@ -914,12 +1415,12 @@ async function execAdminTagRemove(skillId) {
|
|
|
914
1415
|
const tag = document.getElementById('tag-rm-name').value;
|
|
915
1416
|
|
|
916
1417
|
if (!tag) {
|
|
917
|
-
showToast('
|
|
1418
|
+
showToast(t('admin.tagNameRequired'), 'error');
|
|
918
1419
|
return;
|
|
919
1420
|
}
|
|
920
1421
|
|
|
921
1422
|
try {
|
|
922
|
-
showToast(
|
|
1423
|
+
showToast(t('admin.removingTag', { tag: tag }), 'info');
|
|
923
1424
|
const response = await fetch('/api/admin/tag', {
|
|
924
1425
|
method: 'POST',
|
|
925
1426
|
headers: { 'Content-Type': 'application/json' },
|
|
@@ -927,14 +1428,14 @@ async function execAdminTagRemove(skillId) {
|
|
|
927
1428
|
});
|
|
928
1429
|
const result = await response.json();
|
|
929
1430
|
if (result.error) {
|
|
930
|
-
showToast(
|
|
1431
|
+
showToast(`${t('error.generic')}: ${result.error}`, 'error');
|
|
931
1432
|
} else {
|
|
932
1433
|
showToast(`✅ ${result.message}`, 'success');
|
|
933
1434
|
loadAdminTags(skillId);
|
|
934
1435
|
document.getElementById('tag-rm-name').value = '';
|
|
935
1436
|
}
|
|
936
1437
|
} catch (err) {
|
|
937
|
-
showToast(
|
|
1438
|
+
showToast(`${t('error.generic')}: ${err.message}`, 'error');
|
|
938
1439
|
}
|
|
939
1440
|
}
|
|
940
1441
|
|
|
@@ -943,25 +1444,25 @@ function showAdminOwnerModal(skillId) {
|
|
|
943
1444
|
const modalBody = document.getElementById('modal-body');
|
|
944
1445
|
|
|
945
1446
|
modalBody.innerHTML = `
|
|
946
|
-
<h2
|
|
1447
|
+
<h2>${t('admin.ownersTitle', { skillId: skillId })}</h2>
|
|
947
1448
|
<div class="admin-modal-section">
|
|
948
|
-
<h3
|
|
1449
|
+
<h3>${t('admin.addOwner')}</h3>
|
|
949
1450
|
<div class="admin-input-group">
|
|
950
|
-
<label
|
|
951
|
-
<input type="text" id="owner-add-name" placeholder="
|
|
1451
|
+
<label>${t('admin.npmUser')}</label>
|
|
1452
|
+
<input type="text" id="owner-add-name" placeholder="${t('admin.npmUserPlaceholder')}">
|
|
952
1453
|
</div>
|
|
953
|
-
<button class="btn btn-success btn-sm" onclick="execAdminOwnerAdd('${skillId}')"
|
|
1454
|
+
<button class="btn btn-success btn-sm" onclick="execAdminOwnerAdd('${skillId}')">${t('admin.addOwner')}</button>
|
|
954
1455
|
</div>
|
|
955
1456
|
<div class="admin-modal-section">
|
|
956
|
-
<h3
|
|
1457
|
+
<h3>${t('admin.removeOwner')}</h3>
|
|
957
1458
|
<div class="admin-input-group">
|
|
958
|
-
<label
|
|
959
|
-
<input type="text" id="owner-rm-name" placeholder="
|
|
1459
|
+
<label>${t('admin.npmUser')}</label>
|
|
1460
|
+
<input type="text" id="owner-rm-name" placeholder="${t('admin.npmUserPlaceholder')}">
|
|
960
1461
|
</div>
|
|
961
|
-
<button class="btn btn-danger btn-sm" onclick="execAdminOwnerRemove('${skillId}')"
|
|
1462
|
+
<button class="btn btn-danger btn-sm" onclick="execAdminOwnerRemove('${skillId}')">${t('admin.removeOwner')}</button>
|
|
962
1463
|
</div>
|
|
963
1464
|
<div class="actions">
|
|
964
|
-
<button class="btn btn-secondary" onclick="closeModal()"
|
|
1465
|
+
<button class="btn btn-secondary" onclick="closeModal()">${t('btn.cancel')}</button>
|
|
965
1466
|
</div>
|
|
966
1467
|
`;
|
|
967
1468
|
modal.classList.remove('hidden');
|
|
@@ -969,10 +1470,10 @@ function showAdminOwnerModal(skillId) {
|
|
|
969
1470
|
|
|
970
1471
|
async function execAdminOwnerAdd(skillId) {
|
|
971
1472
|
const user = document.getElementById('owner-add-name').value;
|
|
972
|
-
if (!user) { showToast('
|
|
1473
|
+
if (!user) { showToast(t('admin.usernameRequired'), 'error'); return; }
|
|
973
1474
|
|
|
974
1475
|
try {
|
|
975
|
-
showToast(
|
|
1476
|
+
showToast(t('admin.addingOwner', { user: user }), 'info');
|
|
976
1477
|
const response = await fetch('/api/admin/owner', {
|
|
977
1478
|
method: 'POST',
|
|
978
1479
|
headers: { 'Content-Type': 'application/json' },
|
|
@@ -980,22 +1481,22 @@ async function execAdminOwnerAdd(skillId) {
|
|
|
980
1481
|
});
|
|
981
1482
|
const result = await response.json();
|
|
982
1483
|
if (result.error) {
|
|
983
|
-
showToast(
|
|
1484
|
+
showToast(`${t('error.generic')}: ${result.error}`, 'error');
|
|
984
1485
|
} else {
|
|
985
1486
|
showToast(`✅ ${result.message}`, 'success');
|
|
986
1487
|
document.getElementById('owner-add-name').value = '';
|
|
987
1488
|
}
|
|
988
1489
|
} catch (err) {
|
|
989
|
-
showToast(
|
|
1490
|
+
showToast(`${t('error.generic')}: ${err.message}`, 'error');
|
|
990
1491
|
}
|
|
991
1492
|
}
|
|
992
1493
|
|
|
993
1494
|
async function execAdminOwnerRemove(skillId) {
|
|
994
1495
|
const user = document.getElementById('owner-rm-name').value;
|
|
995
|
-
if (!user) { showToast('
|
|
1496
|
+
if (!user) { showToast(t('admin.usernameRequired'), 'error'); return; }
|
|
996
1497
|
|
|
997
1498
|
try {
|
|
998
|
-
showToast(
|
|
1499
|
+
showToast(t('admin.removingOwner', { user: user }), 'info');
|
|
999
1500
|
const response = await fetch('/api/admin/owner', {
|
|
1000
1501
|
method: 'POST',
|
|
1001
1502
|
headers: { 'Content-Type': 'application/json' },
|
|
@@ -1003,13 +1504,13 @@ async function execAdminOwnerRemove(skillId) {
|
|
|
1003
1504
|
});
|
|
1004
1505
|
const result = await response.json();
|
|
1005
1506
|
if (result.error) {
|
|
1006
|
-
showToast(
|
|
1507
|
+
showToast(`${t('error.generic')}: ${result.error}`, 'error');
|
|
1007
1508
|
} else {
|
|
1008
1509
|
showToast(`✅ ${result.message}`, 'success');
|
|
1009
1510
|
document.getElementById('owner-rm-name').value = '';
|
|
1010
1511
|
}
|
|
1011
1512
|
} catch (err) {
|
|
1012
|
-
showToast(
|
|
1513
|
+
showToast(`${t('error.generic')}: ${err.message}`, 'error');
|
|
1013
1514
|
}
|
|
1014
1515
|
}
|
|
1015
1516
|
|
|
@@ -1018,20 +1519,19 @@ function showAdminAccessModal(skillId) {
|
|
|
1018
1519
|
const modalBody = document.getElementById('modal-body');
|
|
1019
1520
|
|
|
1020
1521
|
modalBody.innerHTML = `
|
|
1021
|
-
<h2
|
|
1522
|
+
<h2>${t('admin.accessTitle', { skillId: skillId })}</h2>
|
|
1022
1523
|
<div class="admin-modal-section">
|
|
1023
1524
|
<p style="color: var(--text-muted); font-size: 0.85rem; margin-bottom: 12px;">
|
|
1024
|
-
|
|
1025
|
-
Restricted packages require authentication to install.
|
|
1525
|
+
${t('admin.accessDesc')}
|
|
1026
1526
|
</p>
|
|
1027
1527
|
<div class="admin-radio-group">
|
|
1028
|
-
<label><input type="radio" name="access-level" value="public" checked>
|
|
1029
|
-
<label><input type="radio" name="access-level" value="restricted">
|
|
1528
|
+
<label><input type="radio" name="access-level" value="public" checked> ${t('admin.accessPublic')}</label>
|
|
1529
|
+
<label><input type="radio" name="access-level" value="restricted"> ${t('admin.accessRestricted')}</label>
|
|
1030
1530
|
</div>
|
|
1031
1531
|
</div>
|
|
1032
1532
|
<div class="actions">
|
|
1033
|
-
<button class="btn btn-primary" onclick="execAdminAccess('${skillId}')"
|
|
1034
|
-
<button class="btn btn-secondary" onclick="closeModal()"
|
|
1533
|
+
<button class="btn btn-primary" onclick="execAdminAccess('${skillId}')">${t('admin.setAccess')}</button>
|
|
1534
|
+
<button class="btn btn-secondary" onclick="closeModal()">${t('btn.cancel')}</button>
|
|
1035
1535
|
</div>
|
|
1036
1536
|
`;
|
|
1037
1537
|
modal.classList.remove('hidden');
|
|
@@ -1041,7 +1541,7 @@ async function execAdminAccess(skillId) {
|
|
|
1041
1541
|
const level = document.querySelector('input[name="access-level"]:checked').value;
|
|
1042
1542
|
|
|
1043
1543
|
try {
|
|
1044
|
-
showToast(
|
|
1544
|
+
showToast(t('admin.settingAccess', { level: level }), 'info');
|
|
1045
1545
|
const response = await fetch('/api/admin/access', {
|
|
1046
1546
|
method: 'POST',
|
|
1047
1547
|
headers: { 'Content-Type': 'application/json' },
|
|
@@ -1049,12 +1549,12 @@ async function execAdminAccess(skillId) {
|
|
|
1049
1549
|
});
|
|
1050
1550
|
const result = await response.json();
|
|
1051
1551
|
if (result.error) {
|
|
1052
|
-
showToast(
|
|
1552
|
+
showToast(`${t('error.generic')}: ${result.error}`, 'error');
|
|
1053
1553
|
} else {
|
|
1054
1554
|
showToast(`✅ ${result.message}`, 'success');
|
|
1055
1555
|
closeModal();
|
|
1056
1556
|
}
|
|
1057
1557
|
} catch (err) {
|
|
1058
|
-
showToast(
|
|
1558
|
+
showToast(`${t('error.generic')}: ${err.message}`, 'error');
|
|
1059
1559
|
}
|
|
1060
1560
|
}
|