@nbtca/prompt 1.0.4 → 1.0.6
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/CHANGELOG.md +48 -0
- package/README.md +45 -3
- package/dist/config/data.d.ts +1 -1
- package/dist/config/data.js +1 -1
- package/dist/core/logo.d.ts +3 -3
- package/dist/core/logo.js +40 -40
- package/dist/core/logo.js.map +1 -1
- package/dist/core/menu.d.ts +3 -3
- package/dist/core/menu.d.ts.map +1 -1
- package/dist/core/menu.js +112 -67
- package/dist/core/menu.js.map +1 -1
- package/dist/core/ui.d.ts +11 -11
- package/dist/core/ui.js +15 -15
- package/dist/core/ui.js.map +1 -1
- package/dist/features/calendar.d.ts.map +1 -1
- package/dist/features/calendar.js +21 -15
- package/dist/features/calendar.js.map +1 -1
- package/dist/features/docs.d.ts.map +1 -1
- package/dist/features/docs.js +104 -44
- package/dist/features/docs.js.map +1 -1
- package/dist/features/repair.d.ts.map +1 -1
- package/dist/features/repair.js +6 -10
- package/dist/features/repair.js.map +1 -1
- package/dist/features/website.d.ts +3 -3
- package/dist/features/website.d.ts.map +1 -1
- package/dist/features/website.js +11 -9
- package/dist/features/website.js.map +1 -1
- package/dist/i18n/index.d.ts +140 -0
- package/dist/i18n/index.d.ts.map +1 -0
- package/dist/i18n/index.js +113 -0
- package/dist/i18n/index.js.map +1 -0
- package/dist/main.d.ts +2 -2
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +11 -10
- package/dist/main.js.map +1 -1
- package/package.json +1 -1
- package/src/config/data.ts +1 -1
- package/src/core/logo.ts +40 -40
- package/src/core/menu.ts +119 -67
- package/src/core/ui.ts +15 -15
- package/src/features/calendar.ts +23 -15
- package/src/features/docs.ts +116 -45
- package/src/features/repair.ts +6 -10
- package/src/features/website.ts +11 -9
- package/src/i18n/index.ts +236 -0
- package/src/i18n/locales/en.json +107 -0
- package/src/i18n/locales/zh.json +107 -0
- package/src/main.ts +11 -10
package/src/features/docs.ts
CHANGED
|
@@ -10,6 +10,8 @@ import chalk from 'chalk';
|
|
|
10
10
|
import open from 'open';
|
|
11
11
|
import inquirer from 'inquirer';
|
|
12
12
|
import { error, info, success, warning } from '../core/ui.js';
|
|
13
|
+
import { spawn } from 'child_process';
|
|
14
|
+
import { t } from '../i18n/index.js';
|
|
13
15
|
|
|
14
16
|
// 配置marked使用终端渲染器
|
|
15
17
|
marked.setOptions({
|
|
@@ -26,6 +28,12 @@ const GITHUB_REPO = {
|
|
|
26
28
|
branch: 'main'
|
|
27
29
|
};
|
|
28
30
|
|
|
31
|
+
/**
|
|
32
|
+
* GitHub Token (可选 - 用于避免 API 速率限制)
|
|
33
|
+
* 从环境变量读取,如果没有则使用未认证请求
|
|
34
|
+
*/
|
|
35
|
+
const GITHUB_TOKEN = process.env['GITHUB_TOKEN'] || process.env['GH_TOKEN'];
|
|
36
|
+
|
|
29
37
|
/**
|
|
30
38
|
* 文档结构类型
|
|
31
39
|
*/
|
|
@@ -56,12 +64,19 @@ async function fetchGitHubDirectory(path: string = ''): Promise<DocItem[]> {
|
|
|
56
64
|
const url = `https://api.github.com/repos/${GITHUB_REPO.owner}/${GITHUB_REPO.repo}/contents/${path}?ref=${GITHUB_REPO.branch}`;
|
|
57
65
|
|
|
58
66
|
try {
|
|
67
|
+
const headers: any = {
|
|
68
|
+
'Accept': 'application/vnd.github.v3+json',
|
|
69
|
+
'User-Agent': 'NBTCA-CLI'
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
// 如果有 GitHub Token,添加认证头以避免速率限制
|
|
73
|
+
if (GITHUB_TOKEN) {
|
|
74
|
+
headers['Authorization'] = `Bearer ${GITHUB_TOKEN}`;
|
|
75
|
+
}
|
|
76
|
+
|
|
59
77
|
const response = await axios.get(url, {
|
|
60
78
|
timeout: 10000,
|
|
61
|
-
headers
|
|
62
|
-
'Accept': 'application/vnd.github.v3+json',
|
|
63
|
-
'User-Agent': 'NBTCA-CLI'
|
|
64
|
-
}
|
|
79
|
+
headers
|
|
65
80
|
});
|
|
66
81
|
|
|
67
82
|
return response.data
|
|
@@ -90,6 +105,17 @@ async function fetchGitHubDirectory(path: string = ''): Promise<DocItem[]> {
|
|
|
90
105
|
return a.name.localeCompare(b.name);
|
|
91
106
|
});
|
|
92
107
|
} catch (err: any) {
|
|
108
|
+
// 提供更详细的错误信息
|
|
109
|
+
if (err.response?.status === 403) {
|
|
110
|
+
const rateLimitRemaining = err.response.headers['x-ratelimit-remaining'];
|
|
111
|
+
const rateLimitReset = err.response.headers['x-ratelimit-reset'];
|
|
112
|
+
|
|
113
|
+
if (rateLimitRemaining === '0') {
|
|
114
|
+
const resetDate = new Date(parseInt(rateLimitReset) * 1000);
|
|
115
|
+
throw new Error(`GitHub API 速率限制已达上限,将在 ${resetDate.toLocaleTimeString()} 重置。\n提示: 设置 GITHUB_TOKEN 环境变量可获得更高的速率限制。`);
|
|
116
|
+
}
|
|
117
|
+
throw new Error(`GitHub API 拒绝访问 (403)。\n提示: 尝试设置 GITHUB_TOKEN 环境变量。`);
|
|
118
|
+
}
|
|
93
119
|
throw new Error(`无法获取目录内容: ${err.message}`);
|
|
94
120
|
}
|
|
95
121
|
}
|
|
@@ -156,22 +182,23 @@ function extractDocTitle(content: string): string | null {
|
|
|
156
182
|
* 浏览目录并选择文档
|
|
157
183
|
*/
|
|
158
184
|
async function browseDirectory(dirPath: string = ''): Promise<void> {
|
|
185
|
+
const trans = t();
|
|
159
186
|
try {
|
|
160
|
-
info(dirPath ?
|
|
187
|
+
info(dirPath ? `${trans.docs.loadingDir}: ${dirPath}` : trans.docs.loading);
|
|
161
188
|
|
|
162
189
|
const items = await fetchGitHubDirectory(dirPath);
|
|
163
190
|
|
|
164
191
|
console.log('\r' + ' '.repeat(60) + '\r'); // 清除加载提示
|
|
165
192
|
|
|
166
193
|
if (items.length === 0) {
|
|
167
|
-
warning(
|
|
194
|
+
warning(trans.docs.emptyDir);
|
|
168
195
|
return;
|
|
169
196
|
}
|
|
170
197
|
|
|
171
198
|
// 构建选择列表
|
|
172
199
|
const choices = [
|
|
173
200
|
...(dirPath ? [
|
|
174
|
-
{ name: chalk.gray(
|
|
201
|
+
{ name: chalk.gray(trans.docs.upToParent), value: { type: 'back' } },
|
|
175
202
|
new inquirer.Separator()
|
|
176
203
|
] : []),
|
|
177
204
|
...items.map(item => ({
|
|
@@ -181,14 +208,14 @@ async function browseDirectory(dirPath: string = ''): Promise<void> {
|
|
|
181
208
|
value: item
|
|
182
209
|
})),
|
|
183
210
|
new inquirer.Separator(),
|
|
184
|
-
{ name: chalk.gray(
|
|
211
|
+
{ name: chalk.gray(trans.docs.returnToMenu), value: { type: 'exit' } }
|
|
185
212
|
];
|
|
186
213
|
|
|
187
214
|
const { selected } = await inquirer.prompt([
|
|
188
215
|
{
|
|
189
216
|
type: 'list',
|
|
190
217
|
name: 'selected',
|
|
191
|
-
message: dirPath ?
|
|
218
|
+
message: dirPath ? `${trans.docs.currentDir}: ${dirPath}` : trans.docs.chooseDoc,
|
|
192
219
|
choices,
|
|
193
220
|
pageSize: 15,
|
|
194
221
|
loop: false
|
|
@@ -213,14 +240,14 @@ async function browseDirectory(dirPath: string = ''): Promise<void> {
|
|
|
213
240
|
}
|
|
214
241
|
|
|
215
242
|
} catch (err: any) {
|
|
216
|
-
error(
|
|
217
|
-
console.log(chalk.gray(`
|
|
243
|
+
error(trans.docs.loadError);
|
|
244
|
+
console.log(chalk.gray(` ${trans.docs.errorHint}: ${err.message}`));
|
|
218
245
|
|
|
219
246
|
const { retry } = await inquirer.prompt([
|
|
220
247
|
{
|
|
221
248
|
type: 'confirm',
|
|
222
249
|
name: 'retry',
|
|
223
|
-
message:
|
|
250
|
+
message: trans.docs.retry,
|
|
224
251
|
default: true
|
|
225
252
|
}
|
|
226
253
|
]);
|
|
@@ -231,12 +258,58 @@ async function browseDirectory(dirPath: string = ''): Promise<void> {
|
|
|
231
258
|
}
|
|
232
259
|
}
|
|
233
260
|
|
|
261
|
+
/**
|
|
262
|
+
* 使用系统pager (less/more) 显示内容
|
|
263
|
+
* 提供类似vim/journalctl的阅读体验
|
|
264
|
+
*/
|
|
265
|
+
async function displayInPager(content: string, title: string): Promise<boolean> {
|
|
266
|
+
const trans = t();
|
|
267
|
+
return new Promise((resolve) => {
|
|
268
|
+
// 检测可用的pager程序
|
|
269
|
+
const pager = process.env['PAGER'] || 'less';
|
|
270
|
+
|
|
271
|
+
// 为内容添加标题
|
|
272
|
+
const fullContent = `${chalk.cyan.bold(`>> ${title}`)}\n${chalk.gray('='.repeat(80))}\n\n${content}\n\n${chalk.gray('='.repeat(80))}\n${chalk.dim(trans.docs.endOfDocument)}\n`;
|
|
273
|
+
|
|
274
|
+
// less的参数: -R (支持颜色), -F (如果内容少于一屏则直接显示), -X (退出时不清屏)
|
|
275
|
+
const lessArgs = ['-R', '-F', '-X'];
|
|
276
|
+
|
|
277
|
+
try {
|
|
278
|
+
const child = spawn(pager, lessArgs, {
|
|
279
|
+
stdio: ['pipe', 'inherit', 'inherit'],
|
|
280
|
+
shell: true
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
// 将内容写入pager的stdin
|
|
284
|
+
child.stdin.write(fullContent);
|
|
285
|
+
child.stdin.end();
|
|
286
|
+
|
|
287
|
+
child.on('exit', (code) => {
|
|
288
|
+
resolve(code === 0);
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
child.on('error', () => {
|
|
292
|
+
// 如果pager失败,回退到直接输出
|
|
293
|
+
console.error(chalk.yellow(trans.docs.pagerNotAvailable));
|
|
294
|
+
console.log(fullContent);
|
|
295
|
+
resolve(false);
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
} catch {
|
|
299
|
+
// 回退方案: 直接输出
|
|
300
|
+
console.log(fullContent);
|
|
301
|
+
resolve(false);
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
|
|
234
306
|
/**
|
|
235
307
|
* 查看Markdown文件
|
|
236
308
|
*/
|
|
237
309
|
async function viewMarkdownFile(path: string): Promise<void> {
|
|
310
|
+
const trans = t();
|
|
238
311
|
try {
|
|
239
|
-
info(
|
|
312
|
+
info(`${trans.docs.loading.replace('...', '')}: ${path}`);
|
|
240
313
|
|
|
241
314
|
// 从GitHub获取原始Markdown内容
|
|
242
315
|
const rawContent = await fetchGitHubRawContent(path);
|
|
@@ -247,24 +320,16 @@ async function viewMarkdownFile(path: string): Promise<void> {
|
|
|
247
320
|
// 提取标题
|
|
248
321
|
const title = extractDocTitle(cleanedContent) || path.split('/').pop() || path;
|
|
249
322
|
|
|
250
|
-
console.clear();
|
|
251
|
-
console.log();
|
|
252
|
-
console.log(chalk.cyan.bold(`>> ${title}`));
|
|
253
|
-
console.log(chalk.gray(` ${path}`));
|
|
254
|
-
console.log();
|
|
255
|
-
console.log(chalk.gray('='.repeat(80)));
|
|
256
|
-
console.log();
|
|
257
|
-
|
|
258
323
|
// 渲染Markdown到终端
|
|
259
|
-
const rendered = marked(cleanedContent);
|
|
260
|
-
|
|
261
|
-
console.log();
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
console.log();
|
|
324
|
+
const rendered = await marked(cleanedContent);
|
|
325
|
+
|
|
326
|
+
console.log('\r' + ' '.repeat(60) + '\r'); // 清除加载提示
|
|
327
|
+
|
|
328
|
+
// 使用pager显示文档 (类似vim/journalctl的阅读体验)
|
|
329
|
+
await displayInPager(rendered, `${title}\n${chalk.gray(` ${path}`)}`);
|
|
266
330
|
|
|
267
|
-
|
|
331
|
+
console.log(); // 添加空行
|
|
332
|
+
success(trans.docs.docCompleted);
|
|
268
333
|
console.log();
|
|
269
334
|
|
|
270
335
|
// 提供后续操作选项
|
|
@@ -272,30 +337,34 @@ async function viewMarkdownFile(path: string): Promise<void> {
|
|
|
272
337
|
{
|
|
273
338
|
type: 'list',
|
|
274
339
|
name: 'action',
|
|
275
|
-
message:
|
|
340
|
+
message: trans.docs.chooseAction,
|
|
276
341
|
choices: [
|
|
277
|
-
{ name:
|
|
278
|
-
{ name:
|
|
342
|
+
{ name: trans.docs.backToList, value: 'back' },
|
|
343
|
+
{ name: trans.docs.reread, value: 'reread' },
|
|
344
|
+
{ name: trans.docs.openBrowser, value: 'browser' }
|
|
279
345
|
]
|
|
280
346
|
}
|
|
281
347
|
]);
|
|
282
348
|
|
|
283
349
|
if (action === 'browser') {
|
|
284
350
|
await openDocsInBrowser(path);
|
|
351
|
+
} else if (action === 'reread') {
|
|
352
|
+
// 重新阅读文档
|
|
353
|
+
await viewMarkdownFile(path);
|
|
285
354
|
}
|
|
286
355
|
|
|
287
356
|
} catch (err: any) {
|
|
288
357
|
console.log('\r' + ' '.repeat(60) + '\r');
|
|
289
|
-
error(
|
|
290
|
-
console.log(chalk.gray(`
|
|
291
|
-
warning('
|
|
358
|
+
error(trans.docs.loadError);
|
|
359
|
+
console.log(chalk.gray(` ${trans.docs.errorHint}: ${err.message}`));
|
|
360
|
+
warning(trans.docs.openBrowserPrompt.replace('是否', '建议'));
|
|
292
361
|
console.log();
|
|
293
362
|
|
|
294
363
|
const { openBrowser } = await inquirer.prompt([
|
|
295
364
|
{
|
|
296
365
|
type: 'confirm',
|
|
297
366
|
name: 'openBrowser',
|
|
298
|
-
message:
|
|
367
|
+
message: trans.docs.openBrowserPrompt,
|
|
299
368
|
default: true
|
|
300
369
|
}
|
|
301
370
|
]);
|
|
@@ -310,16 +379,17 @@ async function viewMarkdownFile(path: string): Promise<void> {
|
|
|
310
379
|
* 在浏览器中打开知识库
|
|
311
380
|
*/
|
|
312
381
|
export async function openDocsInBrowser(path?: string): Promise<void> {
|
|
382
|
+
const trans = t();
|
|
313
383
|
try {
|
|
314
|
-
info(
|
|
384
|
+
info(trans.docs.opening);
|
|
315
385
|
const url = path
|
|
316
386
|
? `https://docs.nbtca.space/${path.replace(/\.md$/, '')}`
|
|
317
387
|
: 'https://docs.nbtca.space';
|
|
318
388
|
await open(url);
|
|
319
|
-
success(
|
|
389
|
+
success(trans.docs.browserOpened);
|
|
320
390
|
} catch (err) {
|
|
321
|
-
error(
|
|
322
|
-
console.log(chalk.gray(
|
|
391
|
+
error(trans.docs.browserError);
|
|
392
|
+
console.log(chalk.gray(` ${trans.docs.browserErrorHint}`));
|
|
323
393
|
}
|
|
324
394
|
console.log();
|
|
325
395
|
}
|
|
@@ -328,9 +398,10 @@ export async function openDocsInBrowser(path?: string): Promise<void> {
|
|
|
328
398
|
* 显示知识库菜单
|
|
329
399
|
*/
|
|
330
400
|
export async function showDocsMenu(): Promise<void> {
|
|
401
|
+
const trans = t();
|
|
331
402
|
console.log();
|
|
332
|
-
console.log(chalk.cyan.bold(
|
|
333
|
-
console.log(chalk.dim(
|
|
403
|
+
console.log(chalk.cyan.bold(` >> ${trans.docs.title}`));
|
|
404
|
+
console.log(chalk.dim(` ${trans.docs.subtitle}`));
|
|
334
405
|
console.log();
|
|
335
406
|
|
|
336
407
|
const choices = [
|
|
@@ -339,15 +410,15 @@ export async function showDocsMenu(): Promise<void> {
|
|
|
339
410
|
value: cat.path
|
|
340
411
|
})),
|
|
341
412
|
new inquirer.Separator(),
|
|
342
|
-
{ name: chalk.gray(
|
|
343
|
-
{ name: chalk.gray(
|
|
413
|
+
{ name: chalk.gray(trans.docs.openBrowser), value: 'browser' },
|
|
414
|
+
{ name: chalk.gray(trans.docs.returnToMenu), value: 'back' }
|
|
344
415
|
];
|
|
345
416
|
|
|
346
417
|
const { action } = await inquirer.prompt([
|
|
347
418
|
{
|
|
348
419
|
type: 'list',
|
|
349
420
|
name: 'action',
|
|
350
|
-
message:
|
|
421
|
+
message: trans.docs.chooseCategory,
|
|
351
422
|
choices,
|
|
352
423
|
pageSize: 15,
|
|
353
424
|
loop: false
|
package/src/features/repair.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import open from 'open';
|
|
7
7
|
import chalk from 'chalk';
|
|
8
8
|
import { error, info, success } from '../core/ui.js';
|
|
9
|
+
import { t } from '../i18n/index.js';
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* 维修服务URL
|
|
@@ -16,25 +17,20 @@ const REPAIR_URL = 'https://nbtca.space/repair';
|
|
|
16
17
|
* 打开维修服务页面
|
|
17
18
|
*/
|
|
18
19
|
export async function openRepairService(): Promise<void> {
|
|
20
|
+
const trans = t();
|
|
19
21
|
try {
|
|
20
22
|
console.log();
|
|
21
|
-
info(
|
|
23
|
+
info(trans.repair.opening);
|
|
22
24
|
console.log();
|
|
23
25
|
|
|
24
26
|
await open(REPAIR_URL);
|
|
25
27
|
|
|
26
|
-
success(
|
|
27
|
-
console.log();
|
|
28
|
-
console.log(chalk.gray(' 服务内容:'));
|
|
29
|
-
console.log(chalk.gray(' • 电脑硬件维修'));
|
|
30
|
-
console.log(chalk.gray(' • 软件安装与配置'));
|
|
31
|
-
console.log(chalk.gray(' • 系统优化与故障排查'));
|
|
32
|
-
console.log(chalk.gray(' • 数据恢复与备份'));
|
|
28
|
+
success(trans.repair.opened);
|
|
33
29
|
console.log();
|
|
34
30
|
} catch (err) {
|
|
35
|
-
error(
|
|
31
|
+
error(trans.repair.error);
|
|
36
32
|
console.log();
|
|
37
|
-
console.log(chalk.yellow('
|
|
33
|
+
console.log(chalk.yellow(' ' + trans.repair.errorHint));
|
|
38
34
|
console.log();
|
|
39
35
|
}
|
|
40
36
|
}
|
package/src/features/website.ts
CHANGED
|
@@ -6,38 +6,40 @@
|
|
|
6
6
|
import open from 'open';
|
|
7
7
|
import chalk from 'chalk';
|
|
8
8
|
import { error, info, success } from '../core/ui.js';
|
|
9
|
+
import { t } from '../i18n/index.js';
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* 打开指定URL
|
|
12
13
|
*/
|
|
13
|
-
export async function openWebsite(url: string
|
|
14
|
+
export async function openWebsite(url: string): Promise<void> {
|
|
15
|
+
const trans = t();
|
|
14
16
|
try {
|
|
15
17
|
console.log();
|
|
16
|
-
info(
|
|
18
|
+
info(trans.website.opening);
|
|
17
19
|
|
|
18
20
|
await open(url);
|
|
19
21
|
|
|
20
|
-
success(
|
|
22
|
+
success(trans.website.opened);
|
|
21
23
|
console.log(chalk.gray(` ${url}`));
|
|
22
24
|
console.log();
|
|
23
25
|
} catch (err) {
|
|
24
|
-
error(
|
|
26
|
+
error(trans.website.error);
|
|
25
27
|
console.log();
|
|
26
|
-
console.log(chalk.yellow('
|
|
28
|
+
console.log(chalk.yellow(' ' + trans.website.errorHint + ': ') + chalk.cyan(url));
|
|
27
29
|
console.log();
|
|
28
30
|
}
|
|
29
31
|
}
|
|
30
32
|
|
|
31
33
|
/**
|
|
32
|
-
*
|
|
34
|
+
* Open NBTCA homepage
|
|
33
35
|
*/
|
|
34
36
|
export async function openHomepage(): Promise<void> {
|
|
35
|
-
await openWebsite('https://nbtca.space'
|
|
37
|
+
await openWebsite('https://nbtca.space');
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
/**
|
|
39
|
-
*
|
|
41
|
+
* Open GitHub page
|
|
40
42
|
*/
|
|
41
43
|
export async function openGithub(): Promise<void> {
|
|
42
|
-
await openWebsite('https://github.com/nbtca'
|
|
44
|
+
await openWebsite('https://github.com/nbtca');
|
|
43
45
|
}
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internationalization (i18n) System
|
|
3
|
+
* Multi-language support for the application
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import fs from 'fs';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
import { fileURLToPath } from 'url';
|
|
9
|
+
import { dirname } from 'path';
|
|
10
|
+
|
|
11
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
12
|
+
const __dirname = dirname(__filename);
|
|
13
|
+
|
|
14
|
+
export type Language = 'zh' | 'en';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Translation structure
|
|
18
|
+
*/
|
|
19
|
+
export interface Translations {
|
|
20
|
+
common: {
|
|
21
|
+
back: string;
|
|
22
|
+
exit: string;
|
|
23
|
+
cancel: string;
|
|
24
|
+
confirm: string;
|
|
25
|
+
loading: string;
|
|
26
|
+
error: string;
|
|
27
|
+
success: string;
|
|
28
|
+
goodbye: string;
|
|
29
|
+
};
|
|
30
|
+
menu: {
|
|
31
|
+
title: string;
|
|
32
|
+
events: string;
|
|
33
|
+
eventsDesc: string;
|
|
34
|
+
repair: string;
|
|
35
|
+
repairDesc: string;
|
|
36
|
+
docs: string;
|
|
37
|
+
docsDesc: string;
|
|
38
|
+
website: string;
|
|
39
|
+
websiteDesc: string;
|
|
40
|
+
github: string;
|
|
41
|
+
githubDesc: string;
|
|
42
|
+
about: string;
|
|
43
|
+
aboutDesc: string;
|
|
44
|
+
language: string;
|
|
45
|
+
languageDesc: string;
|
|
46
|
+
navigationHint: string;
|
|
47
|
+
chooseAction: string;
|
|
48
|
+
};
|
|
49
|
+
about: {
|
|
50
|
+
title: string;
|
|
51
|
+
project: string;
|
|
52
|
+
version: string;
|
|
53
|
+
description: string;
|
|
54
|
+
github: string;
|
|
55
|
+
website: string;
|
|
56
|
+
email: string;
|
|
57
|
+
features: string;
|
|
58
|
+
feature1: string;
|
|
59
|
+
feature2: string;
|
|
60
|
+
feature3: string;
|
|
61
|
+
feature4: string;
|
|
62
|
+
license: string;
|
|
63
|
+
author: string;
|
|
64
|
+
};
|
|
65
|
+
calendar: {
|
|
66
|
+
title: string;
|
|
67
|
+
subtitle: string;
|
|
68
|
+
loading: string;
|
|
69
|
+
noEvents: string;
|
|
70
|
+
error: string;
|
|
71
|
+
errorHint: string;
|
|
72
|
+
dateTime: string;
|
|
73
|
+
eventName: string;
|
|
74
|
+
location: string;
|
|
75
|
+
};
|
|
76
|
+
docs: {
|
|
77
|
+
title: string;
|
|
78
|
+
subtitle: string;
|
|
79
|
+
loading: string;
|
|
80
|
+
loadingDir: string;
|
|
81
|
+
chooseCategory: string;
|
|
82
|
+
currentDir: string;
|
|
83
|
+
chooseDoc: string;
|
|
84
|
+
emptyDir: string;
|
|
85
|
+
upToParent: string;
|
|
86
|
+
returnToMenu: string;
|
|
87
|
+
backToList: string;
|
|
88
|
+
reread: string;
|
|
89
|
+
openBrowser: string;
|
|
90
|
+
loadError: string;
|
|
91
|
+
errorHint: string;
|
|
92
|
+
openBrowserPrompt: string;
|
|
93
|
+
docCompleted: string;
|
|
94
|
+
chooseAction: string;
|
|
95
|
+
opening: string;
|
|
96
|
+
browserOpened: string;
|
|
97
|
+
browserError: string;
|
|
98
|
+
browserErrorHint: string;
|
|
99
|
+
retry: string;
|
|
100
|
+
pagerNotAvailable: string;
|
|
101
|
+
endOfDocument: string;
|
|
102
|
+
};
|
|
103
|
+
repair: {
|
|
104
|
+
title: string;
|
|
105
|
+
subtitle: string;
|
|
106
|
+
opening: string;
|
|
107
|
+
opened: string;
|
|
108
|
+
error: string;
|
|
109
|
+
errorHint: string;
|
|
110
|
+
};
|
|
111
|
+
website: {
|
|
112
|
+
opening: string;
|
|
113
|
+
opened: string;
|
|
114
|
+
error: string;
|
|
115
|
+
errorHint: string;
|
|
116
|
+
};
|
|
117
|
+
language: {
|
|
118
|
+
title: string;
|
|
119
|
+
currentLanguage: string;
|
|
120
|
+
selectLanguage: string;
|
|
121
|
+
zh: string;
|
|
122
|
+
en: string;
|
|
123
|
+
changed: string;
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Language configuration
|
|
129
|
+
*/
|
|
130
|
+
let currentLanguage: Language = 'zh'; // Default to Chinese
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Get configuration directory path
|
|
134
|
+
*/
|
|
135
|
+
function getConfigDir(): string {
|
|
136
|
+
const homeDir = process.env['HOME'] || process.env['USERPROFILE'] || '';
|
|
137
|
+
return path.join(homeDir, '.nbtca');
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Get language configuration file path
|
|
142
|
+
*/
|
|
143
|
+
function getLanguageConfigPath(): string {
|
|
144
|
+
return path.join(getConfigDir(), 'language.json');
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Load language preference from config file
|
|
149
|
+
*/
|
|
150
|
+
export function loadLanguagePreference(): Language {
|
|
151
|
+
try {
|
|
152
|
+
const configPath = getLanguageConfigPath();
|
|
153
|
+
if (fs.existsSync(configPath)) {
|
|
154
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
155
|
+
if (config.language === 'zh' || config.language === 'en') {
|
|
156
|
+
currentLanguage = config.language;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
} catch (err) {
|
|
160
|
+
// If loading fails, use default (Chinese)
|
|
161
|
+
}
|
|
162
|
+
return currentLanguage;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Save language preference to config file
|
|
167
|
+
*/
|
|
168
|
+
export function saveLanguagePreference(language: Language): void {
|
|
169
|
+
try {
|
|
170
|
+
const configDir = getConfigDir();
|
|
171
|
+
if (!fs.existsSync(configDir)) {
|
|
172
|
+
fs.mkdirSync(configDir, { recursive: true });
|
|
173
|
+
}
|
|
174
|
+
const configPath = getLanguageConfigPath();
|
|
175
|
+
fs.writeFileSync(configPath, JSON.stringify({ language }, null, 2));
|
|
176
|
+
currentLanguage = language;
|
|
177
|
+
} catch (err) {
|
|
178
|
+
// Silently fail if we can't save preference
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Get current language
|
|
184
|
+
*/
|
|
185
|
+
export function getCurrentLanguage(): Language {
|
|
186
|
+
return currentLanguage;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Set current language
|
|
191
|
+
*/
|
|
192
|
+
export function setLanguage(language: Language): void {
|
|
193
|
+
currentLanguage = language;
|
|
194
|
+
saveLanguagePreference(language);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Load translation file
|
|
199
|
+
*/
|
|
200
|
+
function loadTranslations(language: Language): Translations {
|
|
201
|
+
try {
|
|
202
|
+
const translationPath = path.join(__dirname, 'locales', `${language}.json`);
|
|
203
|
+
const content = fs.readFileSync(translationPath, 'utf-8');
|
|
204
|
+
return JSON.parse(content);
|
|
205
|
+
} catch (err) {
|
|
206
|
+
// Fallback to Chinese if loading fails
|
|
207
|
+
const fallbackPath = path.join(__dirname, 'locales', 'zh.json');
|
|
208
|
+
const content = fs.readFileSync(fallbackPath, 'utf-8');
|
|
209
|
+
return JSON.parse(content);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Translation cache
|
|
215
|
+
*/
|
|
216
|
+
let translationsCache: Map<Language, Translations> = new Map();
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Get translations for current language
|
|
220
|
+
*/
|
|
221
|
+
export function t(): Translations {
|
|
222
|
+
if (!translationsCache.has(currentLanguage)) {
|
|
223
|
+
translationsCache.set(currentLanguage, loadTranslations(currentLanguage));
|
|
224
|
+
}
|
|
225
|
+
return translationsCache.get(currentLanguage)!;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Clear translation cache (useful when switching languages)
|
|
230
|
+
*/
|
|
231
|
+
export function clearTranslationCache(): void {
|
|
232
|
+
translationsCache.clear();
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Initialize language preference on module load
|
|
236
|
+
loadLanguagePreference();
|