readshell 0.1.0 → 0.2.1

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,263 @@ 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
+ "cli.update.desc": "\u68C0\u67E5\u6700\u65B0\u7248\u672C\u5E76\u81EA\u52A8\u66F4\u65B0",
522
+ "cli.update.checking": "\u6B63\u5728\u68C0\u67E5\u66F4\u65B0...",
523
+ "cli.update.latest": "\u2713 \u5F53\u524D\u5DF2\u662F\u6700\u65B0\u7248\u672C (v{0})",
524
+ "cli.update.updating": "\u53D1\u73B0\u65B0\u7248\u672C v{0} (\u5F53\u524D v{1})\uFF0C\u6B63\u5728\u66F4\u65B0...",
525
+ "cli.update.success": "\u2713 \u5347\u7EA7\u6210\u529F\uFF01\u8BF7\u91CD\u65B0\u8FD0\u884C novel \u547D\u4EE4\u3002",
526
+ "cli.update.fail": "\u2717 \u5347\u7EA7\u5931\u8D25: {0}",
527
+ // TUI - Library
528
+ "tui.lib.loading": "\u{1F4DA} \u52A0\u8F7D\u4E66\u67B6...",
529
+ "tui.lib.empty.title": "\u{1F4DA} \u4E66\u67B6",
530
+ "tui.lib.empty.desc": "\u4E66\u67B6\u4E3A\u7A7A\u3002\u4F7F\u7528 novel import <file> \u5BFC\u5165\u4F60\u7684\u7B2C\u4E00\u672C\u4E66\u3002",
531
+ "tui.lib.title": "\u{1F4DA} \u4E66\u67B6 ({0} \u672C)",
532
+ "tui.lib.tips": " \u2191\u2193/jk \u9009\u62E9 \xB7 Enter \u6253\u5F00 \xB7 d/x \u5220\u9664 \xB7 q \u9000\u51FA",
533
+ // TUI - Reader
534
+ "tui.reader.loading": "\u8BFB\u53D6\u4E2D...",
535
+ "tui.reader.bookmark_add": "\u2713 \u589E\u52A0\u4E66\u7B7E: {0}",
536
+ "tui.reader.status.remaining": "\u9884\u8BA1\u5269\u4F59 {0}",
537
+ // TUI - ChapterNav
538
+ "tui.nav.tab.chapters": "[\u5168\u90E8\u7AE0\u8282]",
539
+ "tui.nav.tab.bookmarks": "[\u6211\u7684\u4E66\u7B7E]",
540
+ "tui.nav.tips": "Enter \u8DF3\u8F6C \xB7 Tab \u5207\u6362 \xB7 Esc/q \u5173\u95ED",
541
+ "tui.nav.empty": "\u6CA1\u6709\u8BB0\u5F55",
542
+ "tui.nav.page": "\u7B2C {0} / {1} \u9875"
543
+ };
544
+
545
+ // src/locales/en.ts
546
+ var en = {
547
+ // Common
548
+ "common.yes": "Yes",
549
+ "common.no": "No",
550
+ "common.confirm": "Confirm",
551
+ "common.cancel": "Cancel",
552
+ "common.quit": "Press q to quit",
553
+ // CLI
554
+ "cli.import.desc": "Import local file or directory to library",
555
+ "cli.import.help": "File or directory path (supports .txt / .epub)",
556
+ "cli.import.success": "\u2713 Imported:",
557
+ "cli.import.fail": "Import failed:",
558
+ "cli.import.not_found": "Path not found:",
559
+ "cli.import.unsupported": "Unsupported file format. Currently supports: .txt, .epub",
560
+ "cli.import.scan_dir": "Scanning directory",
561
+ "cli.import.found_files": "Found following books:",
562
+ "cli.import.confirm_batch": "Confirm importing these {0} books? (y/N)",
563
+ "cli.import.canceled": "\u2713 Import canceled",
564
+ "cli.resume.desc": "Resume last reading",
565
+ "cli.resume.none": "\u2717 No recent reading record found. Please use `novel import <file>` or `novel open <book-id>` first.",
566
+ "cli.open.desc": "Open specific book",
567
+ "cli.open.help": "Book ID or title (fuzzy match)",
568
+ "cli.open.not_found": "\u2717 Book not found:",
569
+ "cli.library.desc": "View library",
570
+ "cli.library.help": "Optional keyword search",
571
+ "cli.library.none": "\u2717 Library is empty. Use `novel import <file>` to import your first book.",
572
+ "cli.library.search_none": '\u{1F4DA} No books found matching "{0}".',
573
+ "cli.library.search_result": "\u{1F4DA} Search results ({0} books):\n",
574
+ "cli.remove.desc": "Remove book from library (only deletes records, not source file)",
575
+ "cli.remove.help": "Book ID or title (fuzzy match)",
576
+ "cli.remove.not_found": "\u2717 Book not found:",
577
+ "cli.remove.success": "\u2713 Book removed:",
578
+ "cli.remove.fail": "Failed to remove book:",
579
+ "cli.lang.desc": "Switch language",
580
+ "cli.lang.help": "Language code: zh | en",
581
+ "cli.lang.success": "\u2713 Language switched to: {0}",
582
+ "cli.lang.unsupported": "\u2717 Unsupported language: {0}",
583
+ "cli.update.desc": "Check for the latest version and update automatically",
584
+ "cli.update.checking": "Checking for updates...",
585
+ "cli.update.latest": "\u2713 Already up to date (v{0})",
586
+ "cli.update.updating": "New version v{0} found (current v{1}), updating...",
587
+ "cli.update.success": "\u2713 Successfully updated! Please restart novel command.",
588
+ "cli.update.fail": "\u2717 Update failed: {0}",
589
+ // TUI - Library
590
+ "tui.lib.loading": "\u{1F4DA} Loading library...",
591
+ "tui.lib.empty.title": "\u{1F4DA} Library",
592
+ "tui.lib.empty.desc": "Library is empty. Use `novel import <file>` to import your first book.",
593
+ "tui.lib.title": "\u{1F4DA} Library ({0} books)",
594
+ "tui.lib.tips": " \u2191\u2193/jk select \xB7 Enter open \xB7 d/x delete \xB7 q quit",
595
+ // TUI - Reader
596
+ "tui.reader.loading": "Loading...",
597
+ "tui.reader.bookmark_add": "\u2713 Bookmark added: {0}",
598
+ "tui.reader.status.remaining": "Est. remaining {0}",
599
+ // TUI - ChapterNav
600
+ "tui.nav.tab.chapters": "[All Chapters]",
601
+ "tui.nav.tab.bookmarks": "[My Bookmarks]",
602
+ "tui.nav.tips": "Enter jump \xB7 Tab switch \xB7 Esc/q close",
603
+ "tui.nav.empty": "No records",
604
+ "tui.nav.page": "Page {0} / {1}"
605
+ };
606
+ var en_default = en;
607
+
608
+ // src/config/AppConfig.ts
609
+ import Conf from "conf";
610
+ var defaults = {
611
+ linesPerPage: 0,
612
+ showStatusBar: true,
613
+ readingMode: "page",
614
+ language: "zh"
615
+ };
616
+ var config = new Conf({
617
+ projectName: "readshell",
618
+ defaults
619
+ });
620
+ function getConfig() {
621
+ return {
622
+ linesPerPage: config.get("linesPerPage"),
623
+ showStatusBar: config.get("showStatusBar"),
624
+ readingMode: config.get("readingMode"),
625
+ language: config.get("language")
626
+ };
627
+ }
628
+ function setConfig(key, value) {
629
+ config.set(key, value);
630
+ }
631
+
632
+ // src/locales/index.ts
633
+ var dictionaries = {
634
+ zh: zh_default,
635
+ en: en_default
636
+ };
637
+ var currentLang = "zh";
638
+ var currentDict = dictionaries.zh;
639
+ function initI18n() {
640
+ const config2 = getConfig();
641
+ currentLang = config2.language || "zh";
642
+ currentDict = dictionaries[currentLang] || dictionaries.zh;
643
+ }
644
+ function setLanguage(lang) {
645
+ currentLang = lang;
646
+ currentDict = dictionaries[lang] || dictionaries.zh;
647
+ }
648
+ function t(key, ...args) {
649
+ let template = currentDict[key];
650
+ if (!template) {
651
+ return key;
652
+ }
653
+ if (args.length > 0) {
654
+ args.forEach((arg, index) => {
655
+ template = template.replace(`{${index}}`, String(arg));
656
+ });
657
+ }
658
+ return template;
659
+ }
660
+
483
661
  // src/cli/commands/import.ts
662
+ import { statSync as statSync2, readdirSync } from "fs";
663
+ import { resolve as resolve2, join as join2, extname } from "path";
664
+ import * as readline from "readline/promises";
665
+ function scanDirectory(dir) {
666
+ let results = [];
667
+ try {
668
+ const list = readdirSync(dir);
669
+ for (const file of list) {
670
+ const fullPath = join2(dir, file);
671
+ const stat = statSync2(fullPath);
672
+ if (stat.isDirectory()) {
673
+ results = results.concat(scanDirectory(fullPath));
674
+ } else {
675
+ const ext = extname(fullPath).toLowerCase();
676
+ if (ext === ".txt" || ext === ".epub") {
677
+ results.push(fullPath);
678
+ }
679
+ }
680
+ }
681
+ } catch (err) {
682
+ logger.error("Scan error:", err);
683
+ }
684
+ return results;
685
+ }
484
686
  var importCommand = {
485
687
  command: "import <file>",
486
- describe: "\u5BFC\u5165\u672C\u5730\u6587\u4EF6\u5230\u4E66\u67B6",
688
+ describe: t("cli.import.desc"),
487
689
  builder: (yargs2) => {
488
690
  return yargs2.positional("file", {
489
- describe: "\u6587\u4EF6\u8DEF\u5F84\uFF08\u652F\u6301 .txt / .epub\uFF09",
691
+ describe: t("cli.import.help"),
490
692
  type: "string",
491
693
  demandOption: true
492
694
  });
493
695
  },
494
696
  handler: async (argv) => {
495
697
  try {
698
+ const targetPath = resolve2(argv.file);
699
+ let stat;
700
+ try {
701
+ stat = statSync2(targetPath);
702
+ } catch (e) {
703
+ console.log(`${t("cli.import.not_found")} ${targetPath}`);
704
+ process.exit(1);
705
+ }
496
706
  const bookService = new BookService();
497
- const book = await bookService.importBook(argv.file);
498
- console.log(`\u2713 \u5DF2\u5BFC\u5165: ${book.title} (${book.id})`);
707
+ if (stat.isDirectory()) {
708
+ console.log(`${t("cli.import.scan_dir")} ${targetPath}...`);
709
+ const files = scanDirectory(targetPath);
710
+ if (files.length === 0) {
711
+ console.log(`\u2717 ` + t("cli.import.unsupported"));
712
+ return;
713
+ }
714
+ console.log(t("cli.import.found_files"));
715
+ files.forEach((f, i) => console.log(` ${i + 1}. ${f}`));
716
+ const rl = readline.createInterface({
717
+ input: process.stdin,
718
+ output: process.stdout
719
+ });
720
+ const answer = await rl.question(t("cli.import.confirm_batch", files.length) + " ");
721
+ rl.close();
722
+ if (answer.toLowerCase() === "y") {
723
+ for (const file of files) {
724
+ try {
725
+ const book = await bookService.importBook(file);
726
+ console.log(`${t("cli.import.success")} ${book.title} (${book.id})`);
727
+ } catch (err) {
728
+ console.log(`${t("cli.import.fail")} ${file} - ${err}`);
729
+ }
730
+ }
731
+ } else {
732
+ console.log(t("cli.import.canceled"));
733
+ }
734
+ } else {
735
+ const book = await bookService.importBook(argv.file);
736
+ console.log(`${t("cli.import.success")} ${book.title} (${book.id})`);
737
+ }
499
738
  } catch (error) {
500
- logger.error("\u5BFC\u5165\u5931\u8D25:", error);
739
+ console.log(`${t("cli.import.fail")} ${error}`);
501
740
  process.exit(1);
502
741
  }
503
742
  }
@@ -659,24 +898,20 @@ function LibraryPage({ onNavigate }) {
659
898
  }
660
899
  }, { isActive: isRawModeSupported });
661
900
  if (loading) {
662
- return /* @__PURE__ */ jsx2(Box2, { padding: 1, children: /* @__PURE__ */ jsx2(Text2, { color: "cyan", children: "\u{1F4DA} \u52A0\u8F7D\u4E66\u67B6..." }) });
901
+ return /* @__PURE__ */ jsx2(Box2, { padding: 1, children: /* @__PURE__ */ jsx2(Text2, { color: "cyan", children: t("tui.lib.loading") }) });
663
902
  }
664
903
  if (books.length === 0) {
665
904
  return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", padding: 1, children: [
666
- /* @__PURE__ */ jsx2(Text2, { bold: true, color: "cyan", children: "\u{1F4DA} \u4E66\u67B6" }),
905
+ /* @__PURE__ */ jsx2(Text2, { bold: true, color: "cyan", children: t("tui.lib.empty.title") }),
667
906
  /* @__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" }) })
907
+ /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: t("tui.lib.empty.desc") }),
908
+ /* @__PURE__ */ jsx2(Box2, { marginTop: 1, children: /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: t("common.quit") }) })
670
909
  ] })
671
910
  ] });
672
911
  }
673
912
  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" }),
913
+ /* @__PURE__ */ jsx2(Text2, { bold: true, color: "cyan", children: t("tui.lib.title", books.length) }),
914
+ /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: t("tui.lib.tips") }),
680
915
  /* @__PURE__ */ jsx2(Box2, { flexDirection: "column", marginTop: 1, children: books.map((book, index) => {
681
916
  const isSelected = index === selectedIndex;
682
917
  return /* @__PURE__ */ jsx2(Box2, { paddingX: 1, justifyContent: "space-between", children: /* @__PURE__ */ jsxs2(Box2, { children: [
@@ -753,8 +988,7 @@ function StatusBar({
753
988
  ] }) }),
754
989
  /* @__PURE__ */ jsxs3(Box4, { children: [
755
990
  remainingTime && /* @__PURE__ */ jsxs3(Text4, { dimColor: true, children: [
756
- "\u9884\u8BA1\u5269\u4F59 ",
757
- remainingTime,
991
+ t("tui.reader.status.remaining", remainingTime),
758
992
  " "
759
993
  ] }),
760
994
  /* @__PURE__ */ jsxs3(Text4, { color: "gray", children: [
@@ -766,7 +1000,7 @@ function StatusBar({
766
1000
  displayPercent,
767
1001
  "%"
768
1002
  ] }),
769
- /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "q\u9000\u51FA" })
1003
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: t("common.quit") })
770
1004
  ] })
771
1005
  ] });
772
1006
  }
@@ -837,12 +1071,15 @@ function ChapterNav({
837
1071
  children: [
838
1072
  /* @__PURE__ */ jsxs4(Box5, { justifyContent: "space-between", marginBottom: 1, children: [
839
1073
  /* @__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]" })
1074
+ /* @__PURE__ */ jsxs4(Text5, { bold: true, color: activeTab === "chapters" ? "green" : "gray", children: [
1075
+ t("tui.nav.tab.chapters"),
1076
+ " "
1077
+ ] }),
1078
+ /* @__PURE__ */ jsx5(Text5, { bold: true, color: activeTab === "bookmarks" ? "green" : "gray", children: t("tui.nav.tab.bookmarks") })
842
1079
  ] }),
843
- /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "Enter \u8DF3\u8F6C \xB7 Tab \u5207\u6362 \xB7 Esc/q \u5173\u95ED" })
1080
+ /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: t("tui.nav.tips") })
844
1081
  ] }),
845
- visibleItems.length === 0 ? /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "\u6CA1\u6709\u8BB0\u5F55" }) : visibleItems.map((item, idx) => {
1082
+ visibleItems.length === 0 ? /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: t("tui.nav.empty") }) : visibleItems.map((item, idx) => {
846
1083
  const actualIndex = windowStart + idx;
847
1084
  const isSelected = actualIndex === selectedIndex;
848
1085
  return /* @__PURE__ */ jsxs4(
@@ -858,13 +1095,7 @@ function ChapterNav({
858
1095
  item.id
859
1096
  );
860
1097
  }),
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
- ] }) })
1098
+ /* @__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
1099
  ]
869
1100
  }
870
1101
  );
@@ -1260,7 +1491,7 @@ function ReaderContent({
1260
1491
  }
1261
1492
  const currentOffset = reader.getCurrentOffset();
1262
1493
  bookmarkServiceRef.current.addBookmark(bookId, markTitle, currentOffset);
1263
- setToastMessage(`\u2713 \u589E\u52A0\u4E66\u7B7E: ${markTitle}`);
1494
+ setToastMessage(t("tui.reader.bookmark_add", markTitle));
1264
1495
  setTimeout(() => setToastMessage(null), 2e3);
1265
1496
  };
1266
1497
  useEffect4(() => {
@@ -1358,11 +1589,14 @@ function ReaderPage({ bookId, initialByteOffset, onNavigate: _onNavigate }) {
1358
1589
  "\u2717 ",
1359
1590
  error
1360
1591
  ] }),
1361
- /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "\u6309 q \u9000\u51FA" })
1592
+ /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: t("common.quit") })
1362
1593
  ] });
1363
1594
  }
1364
1595
  if (!book || !pages) {
1365
- return /* @__PURE__ */ jsx6(Box6, { padding: 1, children: /* @__PURE__ */ jsx6(Text6, { color: "cyan", children: "\u{1F4D6} \u6B63\u5728\u52A0\u8F7D..." }) });
1596
+ return /* @__PURE__ */ jsx6(Box6, { padding: 1, children: /* @__PURE__ */ jsxs5(Text6, { color: "cyan", children: [
1597
+ "\u{1F4D6} ",
1598
+ t("tui.reader.loading")
1599
+ ] }) });
1366
1600
  }
1367
1601
  return /* @__PURE__ */ jsx6(
1368
1602
  ReaderContent,
@@ -1421,19 +1655,19 @@ function renderApp(options = {}) {
1421
1655
  // src/cli/commands/resume.ts
1422
1656
  var resumeCommand = {
1423
1657
  command: "resume",
1424
- describe: "\u6062\u590D\u4E0A\u6B21\u9605\u8BFB",
1658
+ describe: t("cli.resume.desc"),
1425
1659
  handler: async () => {
1426
1660
  try {
1427
1661
  const progressService = new ProgressService();
1428
1662
  const lastProgress = progressService.getLastOpenedBook();
1429
1663
  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");
1664
+ console.log(t("cli.resume.none"));
1431
1665
  return;
1432
1666
  }
1433
1667
  const bookService = new BookService();
1434
1668
  const book = bookService.findBook(lastProgress.book_id);
1435
1669
  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");
1670
+ console.log(t("cli.resume.none"));
1437
1671
  return;
1438
1672
  }
1439
1673
  logger.debug(`\u6062\u590D\u9605\u8BFB: ${book.title}, offset=${lastProgress.byte_offset}`);
@@ -1452,10 +1686,10 @@ var resumeCommand = {
1452
1686
  // src/cli/commands/open.ts
1453
1687
  var openCommand = {
1454
1688
  command: "open <target>",
1455
- describe: "\u6253\u5F00\u6307\u5B9A\u4E66\u7C4D\uFF08ID \u6216\u4E66\u540D\uFF09",
1689
+ describe: t("cli.open.desc"),
1456
1690
  builder: (yargs2) => {
1457
1691
  return yargs2.positional("target", {
1458
- describe: "\u4E66\u7C4D ID \u6216\u4E66\u540D\uFF08\u652F\u6301\u6A21\u7CCA\u5339\u914D\uFF09",
1692
+ describe: t("cli.open.help"),
1459
1693
  type: "string",
1460
1694
  demandOption: true
1461
1695
  });
@@ -1465,8 +1699,7 @@ var openCommand = {
1465
1699
  const bookService = new BookService();
1466
1700
  const book = bookService.findBook(argv.target);
1467
1701
  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");
1702
+ console.log(`${t("cli.open.not_found")} ${argv.target}`);
1470
1703
  process.exit(1);
1471
1704
  }
1472
1705
  const progressService = new ProgressService();
@@ -1488,11 +1721,11 @@ var openCommand = {
1488
1721
  // src/cli/commands/library.ts
1489
1722
  var libraryCommand = {
1490
1723
  command: "library",
1491
- describe: "\u67E5\u770B\u4E66\u67B6\u5217\u8868",
1724
+ describe: t("cli.library.desc"),
1492
1725
  builder: (yargs2) => {
1493
1726
  return yargs2.option("search", {
1494
1727
  alias: "s",
1495
- describe: "\u641C\u7D22\u4E66\u540D",
1728
+ describe: t("cli.library.help"),
1496
1729
  type: "string"
1497
1730
  });
1498
1731
  },
@@ -1502,11 +1735,10 @@ var libraryCommand = {
1502
1735
  const bookService = new BookService();
1503
1736
  const books = bookService.searchBooks(argv.search);
1504
1737
  if (books.length === 0) {
1505
- console.log(`\u{1F4DA} \u672A\u627E\u5230\u5339\u914D\u300C${argv.search}\u300D\u7684\u4E66\u7C4D\u3002`);
1738
+ console.log(t("cli.library.search_none", argv.search));
1506
1739
  return;
1507
1740
  }
1508
- console.log(`\u{1F4DA} \u641C\u7D22\u7ED3\u679C (${books.length} \u672C):
1509
- `);
1741
+ console.log(t("cli.library.search_result", books.length));
1510
1742
  books.forEach((book, index) => {
1511
1743
  console.log(` ${index + 1}. ${book.title} [${book.id}] (${book.format})`);
1512
1744
  });
@@ -1523,10 +1755,10 @@ var libraryCommand = {
1523
1755
  // src/cli/commands/remove.ts
1524
1756
  var removeCommand = {
1525
1757
  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",
1758
+ describe: t("cli.remove.desc"),
1527
1759
  builder: (yargs2) => {
1528
1760
  return yargs2.positional("target", {
1529
- describe: "\u4E66\u7C4D ID \u6216\u4E66\u540D\uFF08\u652F\u6301\u6A21\u7CCA\u5339\u914D\uFF09",
1761
+ describe: t("cli.remove.help"),
1530
1762
  type: "string",
1531
1763
  demandOption: true
1532
1764
  });
@@ -1536,11 +1768,11 @@ var removeCommand = {
1536
1768
  const bookService = new BookService();
1537
1769
  const book = bookService.findBook(argv.target);
1538
1770
  if (!book) {
1539
- console.log(`\u2717 \u672A\u627E\u5230\u5339\u914D\u4E66\u7C4D: ${argv.target}`);
1771
+ console.log(`${t("cli.remove.not_found")} ${argv.target}`);
1540
1772
  process.exit(1);
1541
1773
  }
1542
1774
  bookService.deleteBook(book.id);
1543
- console.log(`\u2713 \u5DF2\u79FB\u9664\u4E66\u7C4D: ${book.title}`);
1775
+ console.log(`${t("cli.remove.success")} ${book.title}`);
1544
1776
  } catch (error) {
1545
1777
  logger.error("\u79FB\u9664\u4E66\u7C4D\u5931\u8D25:", error);
1546
1778
  process.exit(1);
@@ -1548,9 +1780,72 @@ var removeCommand = {
1548
1780
  }
1549
1781
  };
1550
1782
 
1783
+ // src/cli/commands/lang.ts
1784
+ var langCommand = {
1785
+ command: "lang <target>",
1786
+ describe: t("cli.lang.desc"),
1787
+ builder: (yargs2) => {
1788
+ return yargs2.positional("target", {
1789
+ describe: t("cli.lang.help"),
1790
+ type: "string",
1791
+ choices: ["zh", "en"],
1792
+ demandOption: true
1793
+ });
1794
+ },
1795
+ handler: (argv) => {
1796
+ const lang = argv.target;
1797
+ if (lang === "zh" || lang === "en") {
1798
+ setConfig("language", lang);
1799
+ setLanguage(lang);
1800
+ console.log(t("cli.lang.success", lang));
1801
+ } else {
1802
+ console.log(t("cli.lang.unsupported", lang));
1803
+ process.exit(1);
1804
+ }
1805
+ }
1806
+ };
1807
+
1808
+ // src/cli/commands/update.ts
1809
+ import { execSync } from "child_process";
1810
+ import { readFileSync as readFileSync4 } from "fs";
1811
+ var updateCommand = {
1812
+ command: "update",
1813
+ describe: t("cli.update.desc"),
1814
+ handler: async () => {
1815
+ try {
1816
+ console.log(t("cli.update.checking"));
1817
+ let localVersion = "0.0.0";
1818
+ try {
1819
+ const pkgUrl = new URL("../../package.json", import.meta.url);
1820
+ const pkg = JSON.parse(readFileSync4(pkgUrl, "utf-8"));
1821
+ localVersion = pkg.version;
1822
+ } catch (err) {
1823
+ localVersion = "0.2.0";
1824
+ }
1825
+ const npmOutput = execSync("npm view readshell version", { encoding: "utf-8" });
1826
+ const latestVersion = npmOutput.trim();
1827
+ if (!latestVersion) {
1828
+ throw new Error("Could not fetch npm version");
1829
+ }
1830
+ if (latestVersion === localVersion) {
1831
+ console.log(t("cli.update.latest", localVersion));
1832
+ return;
1833
+ }
1834
+ console.log(t("cli.update.updating", latestVersion, localVersion));
1835
+ execSync("npm install -g readshell@latest", { stdio: "inherit" });
1836
+ console.log(t("cli.update.success"));
1837
+ } catch (error) {
1838
+ const msg = error instanceof Error ? error.message : String(error);
1839
+ console.log(t("cli.update.fail", msg));
1840
+ logger.error("\u66F4\u65B0\u5931\u8D25:", error);
1841
+ process.exit(1);
1842
+ }
1843
+ }
1844
+ };
1845
+
1551
1846
  // src/cli/parser.ts
1552
1847
  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");
1848
+ return yargs(hideBin(process.argv)).scriptName("novel").usage("$0 <command> [options]").command(importCommand).command(resumeCommand).command(openCommand).command(libraryCommand).command(removeCommand).command(langCommand).command(updateCommand).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
1849
  }
1555
1850
 
1556
1851
  // src/db/migrate.ts
@@ -1645,6 +1940,7 @@ function migrate(db2, fromVersion) {
1645
1940
  async function main() {
1646
1941
  try {
1647
1942
  initDatabase();
1943
+ initI18n();
1648
1944
  const parser = createParser();
1649
1945
  await parser.parse();
1650
1946
  } catch (error) {