readshell 0.1.0 → 0.2.0

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/dist/index.js CHANGED
@@ -351,18 +351,18 @@ ${plainText}
351
351
  };
352
352
  }
353
353
  function openEpub(filePath) {
354
- return new Promise((resolve2, reject) => {
354
+ return new Promise((resolve3, reject) => {
355
355
  const epub = new EPub(filePath);
356
356
  epub.on("error", (err) => reject(err));
357
- epub.on("end", () => resolve2(epub));
357
+ epub.on("end", () => resolve3(epub));
358
358
  epub.parse();
359
359
  });
360
360
  }
361
361
  function getChapterText(epub, chapterId) {
362
- return new Promise((resolve2, reject) => {
362
+ return new Promise((resolve3, reject) => {
363
363
  epub.getChapter(chapterId, (err, text) => {
364
364
  if (err) return reject(err);
365
- resolve2(text);
365
+ resolve3(text);
366
366
  });
367
367
  });
368
368
  }
@@ -480,24 +480,251 @@ var BookService = class {
480
480
  }
481
481
  };
482
482
 
483
+ // src/locales/zh.ts
484
+ var zh_default = {
485
+ // Common
486
+ "common.yes": "\u662F",
487
+ "common.no": "\u5426",
488
+ "common.confirm": "\u786E\u8BA4",
489
+ "common.cancel": "\u53D6\u6D88",
490
+ "common.quit": "\u6309 q \u9000\u51FA",
491
+ // CLI
492
+ "cli.import.desc": "\u5BFC\u5165\u672C\u5730\u6587\u4EF6\u6216\u76EE\u5F55\u5230\u4E66\u67B6",
493
+ "cli.import.help": "\u6587\u4EF6\u6216\u76EE\u5F55\u8DEF\u5F84\uFF08\u652F\u6301 .txt / .epub\uFF09",
494
+ "cli.import.success": "\u2713 \u5DF2\u5BFC\u5165:",
495
+ "cli.import.fail": "\u5BFC\u5165\u5931\u8D25:",
496
+ "cli.import.not_found": "\u8DEF\u5F84\u4E0D\u5B58\u5728:",
497
+ "cli.import.unsupported": "\u4E0D\u652F\u6301\u7684\u6587\u4EF6\u683C\u5F0F\u3002\u76EE\u524D\u652F\u6301: .txt, .epub",
498
+ "cli.import.scan_dir": "\u626B\u63CF\u76EE\u5F55",
499
+ "cli.import.found_files": "\u627E\u5230\u4EE5\u4E0B\u4E66\u7C4D\uFF1A",
500
+ "cli.import.confirm_batch": "\u662F\u5426\u786E\u8BA4\u5BFC\u5165\u4E0A\u8FF0 {0} \u672C\u4E66\uFF1F(y/N)",
501
+ "cli.import.canceled": "\u2713 \u5BFC\u5165\u5DF2\u53D6\u6D88",
502
+ "cli.resume.desc": "\u6062\u590D\u4E0A\u6B21\u9605\u8BFB",
503
+ "cli.resume.none": "\u2717 \u6CA1\u6709\u627E\u5230\u6700\u8FD1\u9605\u8BFB\u8BB0\u5F55\uFF0C\u8BF7\u5148\u4F7F\u7528 novel import <file> \u6216 novel open <book-id> \u6253\u5F00\u4E00\u672C\u4E66\u3002",
504
+ "cli.open.desc": "\u6253\u5F00\u6307\u5B9A\u4E66\u7C4D",
505
+ "cli.open.help": "\u4E66\u7C4D ID \u6216\u4E66\u540D\uFF08\u652F\u6301\u6A21\u7CCA\u5339\u914D\uFF09",
506
+ "cli.open.not_found": "\u2717 \u672A\u627E\u5230\u5339\u914D\u4E66\u7C4D:",
507
+ "cli.library.desc": "\u67E5\u770B\u4E66\u67B6\u5217\u8868",
508
+ "cli.library.help": "\u53EF\u9009\u5173\u952E\u5B57\u641C\u7D22",
509
+ "cli.library.none": "\u2717 \u4E66\u67B6\u4E3A\u7A7A\u3002\u4F7F\u7528 novel import <file> \u5BFC\u5165\u4F60\u7684\u7B2C\u4E00\u672C\u4E66\u3002",
510
+ "cli.library.search_none": "\u{1F4DA} \u672A\u627E\u5230\u5339\u914D\u300C{0}\u300D\u7684\u4E66\u7C4D\u3002",
511
+ "cli.library.search_result": "\u{1F4DA} \u641C\u7D22\u7ED3\u679C ({0} \u672C):\n",
512
+ "cli.remove.desc": "\u4ECE\u4E66\u67B6\u79FB\u9664\u4E66\u7C4D\uFF08\u4EC5\u5220\u9664\u8BB0\u5F55\uFF0C\u4E0D\u5220\u6E90\u6587\u4EF6\uFF09",
513
+ "cli.remove.help": "\u4E66\u7C4D ID \u6216\u4E66\u540D\uFF08\u652F\u6301\u6A21\u7CCA\u5339\u914D\uFF09",
514
+ "cli.remove.not_found": "\u2717 \u672A\u627E\u5230\u5339\u914D\u4E66\u7C4D:",
515
+ "cli.remove.success": "\u2713 \u5DF2\u79FB\u9664\u4E66\u7C4D:",
516
+ "cli.remove.fail": "\u79FB\u9664\u4E66\u7C4D\u5931\u8D25:",
517
+ "cli.lang.desc": "\u5207\u6362\u8BED\u8A00",
518
+ "cli.lang.help": "\u8BED\u8A00\u4EE3\u7801: zh | en",
519
+ "cli.lang.success": "\u2713 \u8BED\u8A00\u5DF2\u5207\u6362\u4E3A: {0}",
520
+ "cli.lang.unsupported": "\u2717 \u4E0D\u652F\u6301\u7684\u8BED\u8A00: {0}",
521
+ // TUI - Library
522
+ "tui.lib.loading": "\u{1F4DA} \u52A0\u8F7D\u4E66\u67B6...",
523
+ "tui.lib.empty.title": "\u{1F4DA} \u4E66\u67B6",
524
+ "tui.lib.empty.desc": "\u4E66\u67B6\u4E3A\u7A7A\u3002\u4F7F\u7528 novel import <file> \u5BFC\u5165\u4F60\u7684\u7B2C\u4E00\u672C\u4E66\u3002",
525
+ "tui.lib.title": "\u{1F4DA} \u4E66\u67B6 ({0} \u672C)",
526
+ "tui.lib.tips": " \u2191\u2193/jk \u9009\u62E9 \xB7 Enter \u6253\u5F00 \xB7 d/x \u5220\u9664 \xB7 q \u9000\u51FA",
527
+ // TUI - Reader
528
+ "tui.reader.loading": "\u8BFB\u53D6\u4E2D...",
529
+ "tui.reader.bookmark_add": "\u2713 \u589E\u52A0\u4E66\u7B7E: {0}",
530
+ "tui.reader.status.remaining": "\u9884\u8BA1\u5269\u4F59 {0}",
531
+ // TUI - ChapterNav
532
+ "tui.nav.tab.chapters": "[\u5168\u90E8\u7AE0\u8282]",
533
+ "tui.nav.tab.bookmarks": "[\u6211\u7684\u4E66\u7B7E]",
534
+ "tui.nav.tips": "Enter \u8DF3\u8F6C \xB7 Tab \u5207\u6362 \xB7 Esc/q \u5173\u95ED",
535
+ "tui.nav.empty": "\u6CA1\u6709\u8BB0\u5F55",
536
+ "tui.nav.page": "\u7B2C {0} / {1} \u9875"
537
+ };
538
+
539
+ // src/locales/en.ts
540
+ var en = {
541
+ // Common
542
+ "common.yes": "Yes",
543
+ "common.no": "No",
544
+ "common.confirm": "Confirm",
545
+ "common.cancel": "Cancel",
546
+ "common.quit": "Press q to quit",
547
+ // CLI
548
+ "cli.import.desc": "Import local file or directory to library",
549
+ "cli.import.help": "File or directory path (supports .txt / .epub)",
550
+ "cli.import.success": "\u2713 Imported:",
551
+ "cli.import.fail": "Import failed:",
552
+ "cli.import.not_found": "Path not found:",
553
+ "cli.import.unsupported": "Unsupported file format. Currently supports: .txt, .epub",
554
+ "cli.import.scan_dir": "Scanning directory",
555
+ "cli.import.found_files": "Found following books:",
556
+ "cli.import.confirm_batch": "Confirm importing these {0} books? (y/N)",
557
+ "cli.import.canceled": "\u2713 Import canceled",
558
+ "cli.resume.desc": "Resume last reading",
559
+ "cli.resume.none": "\u2717 No recent reading record found. Please use `novel import <file>` or `novel open <book-id>` first.",
560
+ "cli.open.desc": "Open specific book",
561
+ "cli.open.help": "Book ID or title (fuzzy match)",
562
+ "cli.open.not_found": "\u2717 Book not found:",
563
+ "cli.library.desc": "View library",
564
+ "cli.library.help": "Optional keyword search",
565
+ "cli.library.none": "\u2717 Library is empty. Use `novel import <file>` to import your first book.",
566
+ "cli.library.search_none": '\u{1F4DA} No books found matching "{0}".',
567
+ "cli.library.search_result": "\u{1F4DA} Search results ({0} books):\n",
568
+ "cli.remove.desc": "Remove book from library (only deletes records, not source file)",
569
+ "cli.remove.help": "Book ID or title (fuzzy match)",
570
+ "cli.remove.not_found": "\u2717 Book not found:",
571
+ "cli.remove.success": "\u2713 Book removed:",
572
+ "cli.remove.fail": "Failed to remove book:",
573
+ "cli.lang.desc": "Switch language",
574
+ "cli.lang.help": "Language code: zh | en",
575
+ "cli.lang.success": "\u2713 Language switched to: {0}",
576
+ "cli.lang.unsupported": "\u2717 Unsupported language: {0}",
577
+ // TUI - Library
578
+ "tui.lib.loading": "\u{1F4DA} Loading library...",
579
+ "tui.lib.empty.title": "\u{1F4DA} Library",
580
+ "tui.lib.empty.desc": "Library is empty. Use `novel import <file>` to import your first book.",
581
+ "tui.lib.title": "\u{1F4DA} Library ({0} books)",
582
+ "tui.lib.tips": " \u2191\u2193/jk select \xB7 Enter open \xB7 d/x delete \xB7 q quit",
583
+ // TUI - Reader
584
+ "tui.reader.loading": "Loading...",
585
+ "tui.reader.bookmark_add": "\u2713 Bookmark added: {0}",
586
+ "tui.reader.status.remaining": "Est. remaining {0}",
587
+ // TUI - ChapterNav
588
+ "tui.nav.tab.chapters": "[All Chapters]",
589
+ "tui.nav.tab.bookmarks": "[My Bookmarks]",
590
+ "tui.nav.tips": "Enter jump \xB7 Tab switch \xB7 Esc/q close",
591
+ "tui.nav.empty": "No records",
592
+ "tui.nav.page": "Page {0} / {1}"
593
+ };
594
+ var en_default = en;
595
+
596
+ // src/config/AppConfig.ts
597
+ import Conf from "conf";
598
+ var defaults = {
599
+ linesPerPage: 0,
600
+ showStatusBar: true,
601
+ readingMode: "page",
602
+ language: "zh"
603
+ };
604
+ var config = new Conf({
605
+ projectName: "readshell",
606
+ defaults
607
+ });
608
+ function getConfig() {
609
+ return {
610
+ linesPerPage: config.get("linesPerPage"),
611
+ showStatusBar: config.get("showStatusBar"),
612
+ readingMode: config.get("readingMode"),
613
+ language: config.get("language")
614
+ };
615
+ }
616
+ function setConfig(key, value) {
617
+ config.set(key, value);
618
+ }
619
+
620
+ // src/locales/index.ts
621
+ var dictionaries = {
622
+ zh: zh_default,
623
+ en: en_default
624
+ };
625
+ var currentLang = "zh";
626
+ var currentDict = dictionaries.zh;
627
+ function initI18n() {
628
+ const config2 = getConfig();
629
+ currentLang = config2.language || "zh";
630
+ currentDict = dictionaries[currentLang] || dictionaries.zh;
631
+ }
632
+ function setLanguage(lang) {
633
+ currentLang = lang;
634
+ currentDict = dictionaries[lang] || dictionaries.zh;
635
+ }
636
+ function t(key, ...args) {
637
+ let template = currentDict[key];
638
+ if (!template) {
639
+ return key;
640
+ }
641
+ if (args.length > 0) {
642
+ args.forEach((arg, index) => {
643
+ template = template.replace(`{${index}}`, String(arg));
644
+ });
645
+ }
646
+ return template;
647
+ }
648
+
483
649
  // src/cli/commands/import.ts
650
+ import { statSync as statSync2, readdirSync } from "fs";
651
+ import { resolve as resolve2, join as join2, extname } from "path";
652
+ import * as readline from "readline/promises";
653
+ function scanDirectory(dir) {
654
+ let results = [];
655
+ try {
656
+ const list = readdirSync(dir);
657
+ for (const file of list) {
658
+ const fullPath = join2(dir, file);
659
+ const stat = statSync2(fullPath);
660
+ if (stat.isDirectory()) {
661
+ results = results.concat(scanDirectory(fullPath));
662
+ } else {
663
+ const ext = extname(fullPath).toLowerCase();
664
+ if (ext === ".txt" || ext === ".epub") {
665
+ results.push(fullPath);
666
+ }
667
+ }
668
+ }
669
+ } catch (err) {
670
+ logger.error("Scan error:", err);
671
+ }
672
+ return results;
673
+ }
484
674
  var importCommand = {
485
675
  command: "import <file>",
486
- describe: "\u5BFC\u5165\u672C\u5730\u6587\u4EF6\u5230\u4E66\u67B6",
676
+ describe: t("cli.import.desc"),
487
677
  builder: (yargs2) => {
488
678
  return yargs2.positional("file", {
489
- describe: "\u6587\u4EF6\u8DEF\u5F84\uFF08\u652F\u6301 .txt / .epub\uFF09",
679
+ describe: t("cli.import.help"),
490
680
  type: "string",
491
681
  demandOption: true
492
682
  });
493
683
  },
494
684
  handler: async (argv) => {
495
685
  try {
686
+ const targetPath = resolve2(argv.file);
687
+ let stat;
688
+ try {
689
+ stat = statSync2(targetPath);
690
+ } catch (e) {
691
+ console.log(`${t("cli.import.not_found")} ${targetPath}`);
692
+ process.exit(1);
693
+ }
496
694
  const bookService = new BookService();
497
- const book = await bookService.importBook(argv.file);
498
- console.log(`\u2713 \u5DF2\u5BFC\u5165: ${book.title} (${book.id})`);
695
+ if (stat.isDirectory()) {
696
+ console.log(`${t("cli.import.scan_dir")} ${targetPath}...`);
697
+ const files = scanDirectory(targetPath);
698
+ if (files.length === 0) {
699
+ console.log(`\u2717 ` + t("cli.import.unsupported"));
700
+ return;
701
+ }
702
+ console.log(t("cli.import.found_files"));
703
+ files.forEach((f, i) => console.log(` ${i + 1}. ${f}`));
704
+ const rl = readline.createInterface({
705
+ input: process.stdin,
706
+ output: process.stdout
707
+ });
708
+ const answer = await rl.question(t("cli.import.confirm_batch", files.length) + " ");
709
+ rl.close();
710
+ if (answer.toLowerCase() === "y") {
711
+ for (const file of files) {
712
+ try {
713
+ const book = await bookService.importBook(file);
714
+ console.log(`${t("cli.import.success")} ${book.title} (${book.id})`);
715
+ } catch (err) {
716
+ console.log(`${t("cli.import.fail")} ${file} - ${err}`);
717
+ }
718
+ }
719
+ } else {
720
+ console.log(t("cli.import.canceled"));
721
+ }
722
+ } else {
723
+ const book = await bookService.importBook(argv.file);
724
+ console.log(`${t("cli.import.success")} ${book.title} (${book.id})`);
725
+ }
499
726
  } catch (error) {
500
- logger.error("\u5BFC\u5165\u5931\u8D25:", error);
727
+ console.log(`${t("cli.import.fail")} ${error}`);
501
728
  process.exit(1);
502
729
  }
503
730
  }
@@ -659,24 +886,20 @@ function LibraryPage({ onNavigate }) {
659
886
  }
660
887
  }, { isActive: isRawModeSupported });
661
888
  if (loading) {
662
- return /* @__PURE__ */ jsx2(Box2, { padding: 1, children: /* @__PURE__ */ jsx2(Text2, { color: "cyan", children: "\u{1F4DA} \u52A0\u8F7D\u4E66\u67B6..." }) });
889
+ return /* @__PURE__ */ jsx2(Box2, { padding: 1, children: /* @__PURE__ */ jsx2(Text2, { color: "cyan", children: t("tui.lib.loading") }) });
663
890
  }
664
891
  if (books.length === 0) {
665
892
  return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", padding: 1, children: [
666
- /* @__PURE__ */ jsx2(Text2, { bold: true, color: "cyan", children: "\u{1F4DA} \u4E66\u67B6" }),
893
+ /* @__PURE__ */ jsx2(Text2, { bold: true, color: "cyan", children: t("tui.lib.empty.title") }),
667
894
  /* @__PURE__ */ jsxs2(Box2, { marginTop: 1, flexDirection: "column", children: [
668
- /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "\u4E66\u67B6\u4E3A\u7A7A\u3002\u4F7F\u7528 novel import <file> \u5BFC\u5165\u4F60\u7684\u7B2C\u4E00\u672C\u4E66\u3002" }),
669
- /* @__PURE__ */ jsx2(Box2, { marginTop: 1, children: /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "\u6309 q \u9000\u51FA" }) })
895
+ /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: t("tui.lib.empty.desc") }),
896
+ /* @__PURE__ */ jsx2(Box2, { marginTop: 1, children: /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: t("common.quit") }) })
670
897
  ] })
671
898
  ] });
672
899
  }
673
900
  return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", padding: 1, children: [
674
- /* @__PURE__ */ jsxs2(Text2, { bold: true, color: "cyan", children: [
675
- "\u{1F4DA} \u4E66\u67B6 (",
676
- books.length,
677
- " \u672C)"
678
- ] }),
679
- /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: " \u2191\u2193/jk \u9009\u62E9 \xB7 Enter \u6253\u5F00 \xB7 d/x \u5220\u9664 \xB7 q \u9000\u51FA" }),
901
+ /* @__PURE__ */ jsx2(Text2, { bold: true, color: "cyan", children: t("tui.lib.title", books.length) }),
902
+ /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: t("tui.lib.tips") }),
680
903
  /* @__PURE__ */ jsx2(Box2, { flexDirection: "column", marginTop: 1, children: books.map((book, index) => {
681
904
  const isSelected = index === selectedIndex;
682
905
  return /* @__PURE__ */ jsx2(Box2, { paddingX: 1, justifyContent: "space-between", children: /* @__PURE__ */ jsxs2(Box2, { children: [
@@ -753,8 +976,7 @@ function StatusBar({
753
976
  ] }) }),
754
977
  /* @__PURE__ */ jsxs3(Box4, { children: [
755
978
  remainingTime && /* @__PURE__ */ jsxs3(Text4, { dimColor: true, children: [
756
- "\u9884\u8BA1\u5269\u4F59 ",
757
- remainingTime,
979
+ t("tui.reader.status.remaining", remainingTime),
758
980
  " "
759
981
  ] }),
760
982
  /* @__PURE__ */ jsxs3(Text4, { color: "gray", children: [
@@ -766,7 +988,7 @@ function StatusBar({
766
988
  displayPercent,
767
989
  "%"
768
990
  ] }),
769
- /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "q\u9000\u51FA" })
991
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: t("common.quit") })
770
992
  ] })
771
993
  ] });
772
994
  }
@@ -837,12 +1059,15 @@ function ChapterNav({
837
1059
  children: [
838
1060
  /* @__PURE__ */ jsxs4(Box5, { justifyContent: "space-between", marginBottom: 1, children: [
839
1061
  /* @__PURE__ */ jsxs4(Box5, { children: [
840
- /* @__PURE__ */ jsx5(Text5, { bold: true, color: activeTab === "chapters" ? "green" : "gray", children: "[\u5168\u90E8\u7AE0\u8282] " }),
841
- /* @__PURE__ */ jsx5(Text5, { bold: true, color: activeTab === "bookmarks" ? "green" : "gray", children: "[\u6211\u7684\u4E66\u7B7E]" })
1062
+ /* @__PURE__ */ jsxs4(Text5, { bold: true, color: activeTab === "chapters" ? "green" : "gray", children: [
1063
+ t("tui.nav.tab.chapters"),
1064
+ " "
1065
+ ] }),
1066
+ /* @__PURE__ */ jsx5(Text5, { bold: true, color: activeTab === "bookmarks" ? "green" : "gray", children: t("tui.nav.tab.bookmarks") })
842
1067
  ] }),
843
- /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "Enter \u8DF3\u8F6C \xB7 Tab \u5207\u6362 \xB7 Esc/q \u5173\u95ED" })
1068
+ /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: t("tui.nav.tips") })
844
1069
  ] }),
845
- visibleItems.length === 0 ? /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "\u6CA1\u6709\u8BB0\u5F55" }) : visibleItems.map((item, idx) => {
1070
+ visibleItems.length === 0 ? /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: t("tui.nav.empty") }) : visibleItems.map((item, idx) => {
846
1071
  const actualIndex = windowStart + idx;
847
1072
  const isSelected = actualIndex === selectedIndex;
848
1073
  return /* @__PURE__ */ jsxs4(
@@ -858,13 +1083,7 @@ function ChapterNav({
858
1083
  item.id
859
1084
  );
860
1085
  }),
861
- /* @__PURE__ */ jsx5(Box5, { marginTop: 1, justifyContent: "flex-end", children: /* @__PURE__ */ jsxs4(Text5, { dimColor: true, children: [
862
- "\u7B2C ",
863
- Math.floor(selectedIndex / pageSize) + 1,
864
- " / ",
865
- Math.ceil(currentList.length / pageSize) || 1,
866
- " \u9875"
867
- ] }) })
1086
+ /* @__PURE__ */ jsx5(Box5, { marginTop: 1, justifyContent: "flex-end", children: /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: t("tui.nav.page", Math.floor(selectedIndex / pageSize) + 1, Math.ceil(currentList.length / pageSize) || 1) }) })
868
1087
  ]
869
1088
  }
870
1089
  );
@@ -1260,7 +1479,7 @@ function ReaderContent({
1260
1479
  }
1261
1480
  const currentOffset = reader.getCurrentOffset();
1262
1481
  bookmarkServiceRef.current.addBookmark(bookId, markTitle, currentOffset);
1263
- setToastMessage(`\u2713 \u589E\u52A0\u4E66\u7B7E: ${markTitle}`);
1482
+ setToastMessage(t("tui.reader.bookmark_add", markTitle));
1264
1483
  setTimeout(() => setToastMessage(null), 2e3);
1265
1484
  };
1266
1485
  useEffect4(() => {
@@ -1358,11 +1577,14 @@ function ReaderPage({ bookId, initialByteOffset, onNavigate: _onNavigate }) {
1358
1577
  "\u2717 ",
1359
1578
  error
1360
1579
  ] }),
1361
- /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "\u6309 q \u9000\u51FA" })
1580
+ /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: t("common.quit") })
1362
1581
  ] });
1363
1582
  }
1364
1583
  if (!book || !pages) {
1365
- return /* @__PURE__ */ jsx6(Box6, { padding: 1, children: /* @__PURE__ */ jsx6(Text6, { color: "cyan", children: "\u{1F4D6} \u6B63\u5728\u52A0\u8F7D..." }) });
1584
+ return /* @__PURE__ */ jsx6(Box6, { padding: 1, children: /* @__PURE__ */ jsxs5(Text6, { color: "cyan", children: [
1585
+ "\u{1F4D6} ",
1586
+ t("tui.reader.loading")
1587
+ ] }) });
1366
1588
  }
1367
1589
  return /* @__PURE__ */ jsx6(
1368
1590
  ReaderContent,
@@ -1421,19 +1643,19 @@ function renderApp(options = {}) {
1421
1643
  // src/cli/commands/resume.ts
1422
1644
  var resumeCommand = {
1423
1645
  command: "resume",
1424
- describe: "\u6062\u590D\u4E0A\u6B21\u9605\u8BFB",
1646
+ describe: t("cli.resume.desc"),
1425
1647
  handler: async () => {
1426
1648
  try {
1427
1649
  const progressService = new ProgressService();
1428
1650
  const lastProgress = progressService.getLastOpenedBook();
1429
1651
  if (!lastProgress) {
1430
- console.log("\u{1F4DA} \u8FD8\u6CA1\u6709\u9605\u8BFB\u8BB0\u5F55\u3002\u4F7F\u7528 novel import <file> \u5BFC\u5165\u4E00\u672C\u4E66\u5F00\u59CB\u9605\u8BFB\u3002");
1652
+ console.log(t("cli.resume.none"));
1431
1653
  return;
1432
1654
  }
1433
1655
  const bookService = new BookService();
1434
1656
  const book = bookService.findBook(lastProgress.book_id);
1435
1657
  if (!book) {
1436
- console.log("\u{1F4DA} \u4E0A\u6B21\u9605\u8BFB\u7684\u4E66\u7C4D\u5DF2\u88AB\u5220\u9664\u3002\u4F7F\u7528 novel library \u67E5\u770B\u4E66\u67B6\u3002");
1658
+ console.log(t("cli.resume.none"));
1437
1659
  return;
1438
1660
  }
1439
1661
  logger.debug(`\u6062\u590D\u9605\u8BFB: ${book.title}, offset=${lastProgress.byte_offset}`);
@@ -1452,10 +1674,10 @@ var resumeCommand = {
1452
1674
  // src/cli/commands/open.ts
1453
1675
  var openCommand = {
1454
1676
  command: "open <target>",
1455
- describe: "\u6253\u5F00\u6307\u5B9A\u4E66\u7C4D\uFF08ID \u6216\u4E66\u540D\uFF09",
1677
+ describe: t("cli.open.desc"),
1456
1678
  builder: (yargs2) => {
1457
1679
  return yargs2.positional("target", {
1458
- describe: "\u4E66\u7C4D ID \u6216\u4E66\u540D\uFF08\u652F\u6301\u6A21\u7CCA\u5339\u914D\uFF09",
1680
+ describe: t("cli.open.help"),
1459
1681
  type: "string",
1460
1682
  demandOption: true
1461
1683
  });
@@ -1465,8 +1687,7 @@ var openCommand = {
1465
1687
  const bookService = new BookService();
1466
1688
  const book = bookService.findBook(argv.target);
1467
1689
  if (!book) {
1468
- console.log(`\u2717 \u672A\u627E\u5230\u4E66\u7C4D: ${argv.target}`);
1469
- console.log(" \u4F7F\u7528 novel library \u67E5\u770B\u4E66\u67B6\u4E2D\u7684\u6240\u6709\u4E66\u7C4D\u3002");
1690
+ console.log(`${t("cli.open.not_found")} ${argv.target}`);
1470
1691
  process.exit(1);
1471
1692
  }
1472
1693
  const progressService = new ProgressService();
@@ -1488,11 +1709,11 @@ var openCommand = {
1488
1709
  // src/cli/commands/library.ts
1489
1710
  var libraryCommand = {
1490
1711
  command: "library",
1491
- describe: "\u67E5\u770B\u4E66\u67B6\u5217\u8868",
1712
+ describe: t("cli.library.desc"),
1492
1713
  builder: (yargs2) => {
1493
1714
  return yargs2.option("search", {
1494
1715
  alias: "s",
1495
- describe: "\u641C\u7D22\u4E66\u540D",
1716
+ describe: t("cli.library.help"),
1496
1717
  type: "string"
1497
1718
  });
1498
1719
  },
@@ -1502,11 +1723,10 @@ var libraryCommand = {
1502
1723
  const bookService = new BookService();
1503
1724
  const books = bookService.searchBooks(argv.search);
1504
1725
  if (books.length === 0) {
1505
- console.log(`\u{1F4DA} \u672A\u627E\u5230\u5339\u914D\u300C${argv.search}\u300D\u7684\u4E66\u7C4D\u3002`);
1726
+ console.log(t("cli.library.search_none", argv.search));
1506
1727
  return;
1507
1728
  }
1508
- console.log(`\u{1F4DA} \u641C\u7D22\u7ED3\u679C (${books.length} \u672C):
1509
- `);
1729
+ console.log(t("cli.library.search_result", books.length));
1510
1730
  books.forEach((book, index) => {
1511
1731
  console.log(` ${index + 1}. ${book.title} [${book.id}] (${book.format})`);
1512
1732
  });
@@ -1523,10 +1743,10 @@ var libraryCommand = {
1523
1743
  // src/cli/commands/remove.ts
1524
1744
  var removeCommand = {
1525
1745
  command: "remove <target>",
1526
- describe: "\u4ECE\u4E66\u67B6\u79FB\u9664\u4E66\u7C4D\uFF08\u4EC5\u5220\u9664\u8BB0\u5F55\uFF0C\u4E0D\u5220\u6E90\u6587\u4EF6\uFF09",
1746
+ describe: t("cli.remove.desc"),
1527
1747
  builder: (yargs2) => {
1528
1748
  return yargs2.positional("target", {
1529
- describe: "\u4E66\u7C4D ID \u6216\u4E66\u540D\uFF08\u652F\u6301\u6A21\u7CCA\u5339\u914D\uFF09",
1749
+ describe: t("cli.remove.help"),
1530
1750
  type: "string",
1531
1751
  demandOption: true
1532
1752
  });
@@ -1536,11 +1756,11 @@ var removeCommand = {
1536
1756
  const bookService = new BookService();
1537
1757
  const book = bookService.findBook(argv.target);
1538
1758
  if (!book) {
1539
- console.log(`\u2717 \u672A\u627E\u5230\u5339\u914D\u4E66\u7C4D: ${argv.target}`);
1759
+ console.log(`${t("cli.remove.not_found")} ${argv.target}`);
1540
1760
  process.exit(1);
1541
1761
  }
1542
1762
  bookService.deleteBook(book.id);
1543
- console.log(`\u2713 \u5DF2\u79FB\u9664\u4E66\u7C4D: ${book.title}`);
1763
+ console.log(`${t("cli.remove.success")} ${book.title}`);
1544
1764
  } catch (error) {
1545
1765
  logger.error("\u79FB\u9664\u4E66\u7C4D\u5931\u8D25:", error);
1546
1766
  process.exit(1);
@@ -1548,9 +1768,34 @@ var removeCommand = {
1548
1768
  }
1549
1769
  };
1550
1770
 
1771
+ // src/cli/commands/lang.ts
1772
+ var langCommand = {
1773
+ command: "lang <target>",
1774
+ describe: t("cli.lang.desc"),
1775
+ builder: (yargs2) => {
1776
+ return yargs2.positional("target", {
1777
+ describe: t("cli.lang.help"),
1778
+ type: "string",
1779
+ choices: ["zh", "en"],
1780
+ demandOption: true
1781
+ });
1782
+ },
1783
+ handler: (argv) => {
1784
+ const lang = argv.target;
1785
+ if (lang === "zh" || lang === "en") {
1786
+ setConfig("language", lang);
1787
+ setLanguage(lang);
1788
+ console.log(t("cli.lang.success", lang));
1789
+ } else {
1790
+ console.log(t("cli.lang.unsupported", lang));
1791
+ process.exit(1);
1792
+ }
1793
+ }
1794
+ };
1795
+
1551
1796
  // src/cli/parser.ts
1552
1797
  function createParser() {
1553
- return yargs(hideBin(process.argv)).scriptName("novel").usage("$0 <command> [options]").command(importCommand).command(resumeCommand).command(openCommand).command(libraryCommand).command(removeCommand).demandCommand(1, "\u8BF7\u6307\u5B9A\u4E00\u4E2A\u547D\u4EE4\u3002\u4F7F\u7528 --help \u67E5\u770B\u53EF\u7528\u547D\u4EE4\u3002").strict().alias("h", "help").alias("v", "version").version("0.1.0").epilogue("ReadShell \u2014 \u7EC8\u7AEF\u5185\u4F4E\u6253\u65AD\u8F7B\u9605\u8BFB\u5DE5\u5177");
1798
+ return yargs(hideBin(process.argv)).scriptName("novel").usage("$0 <command> [options]").command(importCommand).command(resumeCommand).command(openCommand).command(libraryCommand).command(removeCommand).command(langCommand).demandCommand(1, "\u8BF7\u6307\u5B9A\u4E00\u4E2A\u547D\u4EE4\u3002\u4F7F\u7528 --help \u67E5\u770B\u53EF\u7528\u547D\u4EE4\u3002").strict().alias("h", "help").alias("v", "version").version("0.1.0").epilogue("ReadShell \u2014 \u7EC8\u7AEF\u5185\u4F4E\u6253\u65AD\u8F7B\u9605\u8BFB\u5DE5\u5177");
1554
1799
  }
1555
1800
 
1556
1801
  // src/db/migrate.ts
@@ -1645,6 +1890,7 @@ function migrate(db2, fromVersion) {
1645
1890
  async function main() {
1646
1891
  try {
1647
1892
  initDatabase();
1893
+ initI18n();
1648
1894
  const parser = createParser();
1649
1895
  await parser.parse();
1650
1896
  } catch (error) {