@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.
Files changed (48) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/README.md +45 -3
  3. package/dist/config/data.d.ts +1 -1
  4. package/dist/config/data.js +1 -1
  5. package/dist/core/logo.d.ts +3 -3
  6. package/dist/core/logo.js +40 -40
  7. package/dist/core/logo.js.map +1 -1
  8. package/dist/core/menu.d.ts +3 -3
  9. package/dist/core/menu.d.ts.map +1 -1
  10. package/dist/core/menu.js +112 -67
  11. package/dist/core/menu.js.map +1 -1
  12. package/dist/core/ui.d.ts +11 -11
  13. package/dist/core/ui.js +15 -15
  14. package/dist/core/ui.js.map +1 -1
  15. package/dist/features/calendar.d.ts.map +1 -1
  16. package/dist/features/calendar.js +21 -15
  17. package/dist/features/calendar.js.map +1 -1
  18. package/dist/features/docs.d.ts.map +1 -1
  19. package/dist/features/docs.js +104 -44
  20. package/dist/features/docs.js.map +1 -1
  21. package/dist/features/repair.d.ts.map +1 -1
  22. package/dist/features/repair.js +6 -10
  23. package/dist/features/repair.js.map +1 -1
  24. package/dist/features/website.d.ts +3 -3
  25. package/dist/features/website.d.ts.map +1 -1
  26. package/dist/features/website.js +11 -9
  27. package/dist/features/website.js.map +1 -1
  28. package/dist/i18n/index.d.ts +140 -0
  29. package/dist/i18n/index.d.ts.map +1 -0
  30. package/dist/i18n/index.js +113 -0
  31. package/dist/i18n/index.js.map +1 -0
  32. package/dist/main.d.ts +2 -2
  33. package/dist/main.d.ts.map +1 -1
  34. package/dist/main.js +11 -10
  35. package/dist/main.js.map +1 -1
  36. package/package.json +1 -1
  37. package/src/config/data.ts +1 -1
  38. package/src/core/logo.ts +40 -40
  39. package/src/core/menu.ts +119 -67
  40. package/src/core/ui.ts +15 -15
  41. package/src/features/calendar.ts +23 -15
  42. package/src/features/docs.ts +116 -45
  43. package/src/features/repair.ts +6 -10
  44. package/src/features/website.ts +11 -9
  45. package/src/i18n/index.ts +236 -0
  46. package/src/i18n/locales/en.json +107 -0
  47. package/src/i18n/locales/zh.json +107 -0
  48. package/src/main.ts +11 -10
@@ -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 ? `正在加载目录: ${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('[..] Up to parent directory'), value: { type: 'back' } },
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('[ ^] Return to main menu'), value: { type: 'exit' } }
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 ? `当前目录: ${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(` 错误: ${err.message}`));
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(`正在加载: ${path}`);
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
- console.log(rendered);
261
- console.log();
262
- console.log(chalk.gray('='.repeat(80)));
263
- console.log();
264
- console.log(chalk.dim(' Note: Some features are only available in browser'));
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
- success('文档加载完成');
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: '[ <] Back to docs list', value: 'back' },
278
- { name: '[ *] Open in browser', value: 'browser' }
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(` 错误: ${err.message}`));
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(' 请手动访问: https://docs.nbtca.space'));
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(' >> Knowledge Base'));
333
- console.log(chalk.dim(' Browse documentation from terminal or open in browser'));
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('[ *] Open in browser'), value: 'browser' },
343
- { name: chalk.gray('[ ^] Back to main menu'), value: 'back' }
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
@@ -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(' 请手动访问: ') + chalk.cyan(REPAIR_URL));
33
+ console.log(chalk.yellow(' ' + trans.repair.errorHint));
38
34
  console.log();
39
35
  }
40
36
  }
@@ -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, name: string = '网站'): Promise<void> {
14
+ export async function openWebsite(url: string): Promise<void> {
15
+ const trans = t();
14
16
  try {
15
17
  console.log();
16
- info(`正在打开${name}...`);
18
+ info(trans.website.opening);
17
19
 
18
20
  await open(url);
19
21
 
20
- success(`已在浏览器中打开${name}`);
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(' 请手动访问: ') + chalk.cyan(url));
28
+ console.log(chalk.yellow(' ' + trans.website.errorHint + ': ') + chalk.cyan(url));
27
29
  console.log();
28
30
  }
29
31
  }
30
32
 
31
33
  /**
32
- * 打开NBTCA主页
34
+ * Open NBTCA homepage
33
35
  */
34
36
  export async function openHomepage(): Promise<void> {
35
- await openWebsite('https://nbtca.space', 'NBTCA官网');
37
+ await openWebsite('https://nbtca.space');
36
38
  }
37
39
 
38
40
  /**
39
- * 打开GitHub页面
41
+ * Open GitHub page
40
42
  */
41
43
  export async function openGithub(): Promise<void> {
42
- await openWebsite('https://github.com/nbtca', 'GitHub组织页');
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();