@open-file-viewer/core 0.1.21 → 0.1.23

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
@@ -602,7 +602,36 @@ var defaultMessages = {
602
602
  officeUnsupportedIntro: "\u5DF2\u8FDB\u5165 Office \u63D2\u4EF6\u3002{message}",
603
603
  officeUnsupportedSupportedFormats: "\u5F53\u524D\u7248\u672C\u4F18\u5148\u652F\u6301 docx\u3001rtf\u3001odt/fodt\u3001xlsx/xls/csv/ods\u3001pptx/ppsx\u3001odp/fodp \u7684\u57FA\u7840\u5185\u5BB9\u9884\u89C8\u3002",
604
604
  officeErrorWithMessage: "\u89E3\u6790\u5668\u8FD4\u56DE\uFF1A{message}",
605
- officeErrorWithoutMessage: "\u89E3\u6790\u5668\u672A\u8FD4\u56DE\u5177\u4F53\u9519\u8BEF\u4FE1\u606F\u3002"
605
+ officeErrorWithoutMessage: "\u89E3\u6790\u5668\u672A\u8FD4\u56DE\u5177\u4F53\u9519\u8BEF\u4FE1\u606F\u3002",
606
+ officeConvertedTitle: "Office \u9AD8\u4FDD\u771F\u8F6C\u6362\u9884\u89C8",
607
+ officeConvertedPdfFailed: "Office \u8F6C\u6362\u540E\u7684 PDF \u65E0\u6CD5\u9884\u89C8",
608
+ pdfEncryptedTitle: "PDF \u5DF2\u52A0\u5BC6\uFF0C\u65E0\u6CD5\u5728\u7EBF\u9884\u89C8",
609
+ pdfEncryptedMessage: "\u8BF7\u4E0B\u8F7D\u540E\u4F7F\u7528\u5BC6\u7801\u6253\u5F00\uFF0C\u6216\u4E0A\u4F20\u89E3\u5BC6\u540E\u7684 PDF \u6587\u4EF6\u3002",
610
+ pdfPreviewFailedTitle: "PDF \u9884\u89C8\u5931\u8D25",
611
+ pdfCorruptedMessage: "\u8BE5 PDF \u6587\u4EF6\u53EF\u80FD\u5DF2\u635F\u574F\u6216\u683C\u5F0F\u65E0\u6548\u3002",
612
+ pdfCannotLoadMessage: "\u5F53\u524D\u6D4F\u89C8\u5668\u65E0\u6CD5\u52A0\u8F7D\u8BE5 PDF\u3002",
613
+ pdfDownload: "\u4E0B\u8F7D PDF",
614
+ pdfPageLoading: "\u9875\u9762 {page} \u52A0\u8F7D\u4E2D...",
615
+ pdfPageEmpty: "\u8BE5\u9875\u6CA1\u6709\u68C0\u6D4B\u5230\u53EF\u663E\u793A\u7684 PDF \u517C\u5BB9\u5185\u5BB9\u3002\u82E5\u8FD9\u662F Illustrator/AI \u6587\u4EF6\uFF0C\u53EF\u80FD\u53EA\u5305\u542B\u79C1\u6709\u7F16\u8F91\u6570\u636E\uFF0C\u5EFA\u8BAE\u5BFC\u51FA\u4E3A PDF/SVG/PNG \u540E\u9884\u89C8\u3002",
616
+ pdfPageRenderFailed: "\u65E0\u6CD5\u6E32\u67D3\u8BE5\u9875\u9762\u3002\u8BE5\u9875\u53EF\u80FD\u5305\u542B\u6D4F\u89C8\u5668 PDF \u5F15\u64CE\u6682\u4E0D\u652F\u6301\u7684\u56FE\u5F62\u3001\u5B57\u4F53\u6216\u538B\u7F29\u7279\u6027\u3002",
617
+ pdfPreviousPage: "\u4E0A\u4E00\u9875",
618
+ pdfNextPage: "\u4E0B\u4E00\u9875",
619
+ pdfPageInput: "\u5F53\u524D\u9875\u7801",
620
+ pdfPagePosition: "/ {total}",
621
+ pdfPageLabel: "\u7B2C {page} \u9875",
622
+ pdfSummaryPages: "\u9875\u6570",
623
+ pdfSummaryPageSizes: "\u9875\u9762\u5C3A\u5BF8",
624
+ pdfSummaryFit: "\u9002\u914D",
625
+ pdfSummaryActualSize: "\u539F\u59CB\u5927\u5C0F",
626
+ pdfSummaryFitWidth: "\u9002\u5408\u5BBD\u5EA6",
627
+ pdfSummaryZoom: "\u7F29\u653E",
628
+ imagePreviewFailedTitle: "\u56FE\u7247\u9884\u89C8\u5931\u8D25",
629
+ imagePreviewFailedMessage: "\u5F53\u524D\u6D4F\u89C8\u5668\u65E0\u6CD5\u76F4\u63A5\u663E\u793A\u8BE5\u56FE\u7247\uFF0C\u6587\u4EF6\u53EF\u80FD\u5DF2\u635F\u574F\u6216\u7F16\u7801\u6682\u4E0D\u53D7\u652F\u6301\u3002",
630
+ imageDownload: "\u4E0B\u8F7D\u56FE\u7247",
631
+ imageZoomOut: "\u7F29\u5C0F",
632
+ imageZoomIn: "\u653E\u5927",
633
+ imageRotate: "\u65CB\u8F6C\u56FE\u7247",
634
+ imageReset: "\u91CD\u7F6E\u56FE\u7247\u89C6\u56FE"
606
635
  },
607
636
  "en-US": {
608
637
  loading: "Loading preview...",
@@ -651,7 +680,36 @@ var defaultMessages = {
651
680
  officeUnsupportedIntro: "is handled by the Office plugin. {message}",
652
681
  officeUnsupportedSupportedFormats: "This version prioritizes basic previews for docx, rtf, odt/fodt, xlsx/xls/csv/ods, pptx/ppsx, and odp/fodp files.",
653
682
  officeErrorWithMessage: "Parser returned: {message}",
654
- officeErrorWithoutMessage: "Parser did not return a specific error."
683
+ officeErrorWithoutMessage: "Parser did not return a specific error.",
684
+ officeConvertedTitle: "High-fidelity Office conversion preview",
685
+ officeConvertedPdfFailed: "The converted Office PDF could not be previewed",
686
+ pdfEncryptedTitle: "This PDF is encrypted and cannot be previewed online",
687
+ pdfEncryptedMessage: "Download and open it with the password, or upload a decrypted PDF file.",
688
+ pdfPreviewFailedTitle: "PDF preview failed",
689
+ pdfCorruptedMessage: "The PDF may be corrupted or use an invalid format.",
690
+ pdfCannotLoadMessage: "This browser could not load the PDF.",
691
+ pdfDownload: "Download PDF",
692
+ pdfPageLoading: "Loading page {page}...",
693
+ pdfPageEmpty: "No displayable PDF-compatible content was detected on this page. If this is an Illustrator/AI file, export it as PDF, SVG, or PNG before previewing.",
694
+ pdfPageRenderFailed: "This page could not be rendered. It may contain graphics, fonts, or compression features that the browser PDF engine does not support.",
695
+ pdfPreviousPage: "Previous page",
696
+ pdfNextPage: "Next page",
697
+ pdfPageInput: "Current page",
698
+ pdfPagePosition: "/ {total}",
699
+ pdfPageLabel: "Page {page}",
700
+ pdfSummaryPages: "Pages",
701
+ pdfSummaryPageSizes: "Page sizes",
702
+ pdfSummaryFit: "Fit",
703
+ pdfSummaryActualSize: "Actual size",
704
+ pdfSummaryFitWidth: "Fit width",
705
+ pdfSummaryZoom: "Zoom",
706
+ imagePreviewFailedTitle: "Image preview failed",
707
+ imagePreviewFailedMessage: "This browser cannot display the image. The file may be corrupted or use an unsupported encoding.",
708
+ imageDownload: "Download image",
709
+ imageZoomOut: "Zoom out",
710
+ imageZoomIn: "Zoom in",
711
+ imageRotate: "Rotate image",
712
+ imageReset: "Reset image view"
655
713
  }
656
714
  };
657
715
  function resolveMessages(options) {
@@ -756,12 +814,17 @@ function createViewer(options) {
756
814
  currentIndex = clampIndex(index, queue.length);
757
815
  await renderQueueItem(currentIndex);
758
816
  };
759
- const toolbar = createToolbar(options.toolbar, viewport, {
760
- getLength: () => queue.length,
761
- next: () => goTo(currentIndex + 1),
762
- previous: () => goTo(currentIndex - 1),
763
- command: (command) => currentInstance?.command?.(command)
764
- });
817
+ const toolbar = createToolbar(
818
+ options.toolbar,
819
+ viewport,
820
+ {
821
+ getLength: () => queue.length,
822
+ next: () => goTo(currentIndex + 1),
823
+ previous: () => goTo(currentIndex - 1),
824
+ command: (command) => currentInstance?.command?.(command)
825
+ },
826
+ options.locale || "en-US"
827
+ );
765
828
  if (toolbar) {
766
829
  host.append(toolbar.element);
767
830
  }
@@ -998,7 +1061,7 @@ function removeMediaListener(media, listener) {
998
1061
  }
999
1062
  media.removeListener?.(listener);
1000
1063
  }
1001
- function createToolbar(toolbar, viewport, queue) {
1064
+ function createToolbar(toolbar, viewport, queue, locale) {
1002
1065
  if (!toolbar) {
1003
1066
  return void 0;
1004
1067
  }
@@ -1006,7 +1069,7 @@ function createToolbar(toolbar, viewport, queue) {
1006
1069
  const element = document.createElement("div");
1007
1070
  element.className = "ofv-toolbar";
1008
1071
  element.setAttribute("role", "toolbar");
1009
- element.setAttribute("aria-label", "File preview toolbar");
1072
+ element.setAttribute("aria-label", defaultToolbarText[locale].ariaLabel);
1010
1073
  let file;
1011
1074
  let currentIndex = 0;
1012
1075
  let currentLength = queue.getLength();
@@ -1067,8 +1130,8 @@ function createToolbar(toolbar, viewport, queue) {
1067
1130
  }
1068
1131
  if (id === "previous" && queue.getLength() > 1) {
1069
1132
  previousButton = addButton(
1070
- getToolbarLabel(options, "previous"),
1071
- getToolbarTitle(options, "previous"),
1133
+ getToolbarLabel(options, locale, "previous"),
1134
+ getToolbarTitle(options, locale, "previous"),
1072
1135
  () => void queue.previous(),
1073
1136
  void 0,
1074
1137
  options.icons?.previous
@@ -1077,8 +1140,8 @@ function createToolbar(toolbar, viewport, queue) {
1077
1140
  }
1078
1141
  if (id === "next" && queue.getLength() > 1) {
1079
1142
  nextButton = addButton(
1080
- getToolbarLabel(options, "next"),
1081
- getToolbarTitle(options, "next"),
1143
+ getToolbarLabel(options, locale, "next"),
1144
+ getToolbarTitle(options, locale, "next"),
1082
1145
  () => void queue.next(),
1083
1146
  void 0,
1084
1147
  options.icons?.next
@@ -1092,27 +1155,27 @@ function createToolbar(toolbar, viewport, queue) {
1092
1155
  return;
1093
1156
  }
1094
1157
  if (id === "zoom-out" && options.zoom) {
1095
- addCommandButton(id, getToolbarLabel(options, id), getToolbarTitle(options, id), "zoom-out");
1158
+ addCommandButton(id, getToolbarLabel(options, locale, id), getToolbarTitle(options, locale, id), "zoom-out");
1096
1159
  return;
1097
1160
  }
1098
1161
  if (id === "zoom-in" && options.zoom) {
1099
- addCommandButton(id, getToolbarLabel(options, id), getToolbarTitle(options, id), "zoom-in");
1162
+ addCommandButton(id, getToolbarLabel(options, locale, id), getToolbarTitle(options, locale, id), "zoom-in");
1100
1163
  return;
1101
1164
  }
1102
1165
  if (id === "zoom-reset" && options.zoom) {
1103
- addCommandButton(id, getToolbarLabel(options, id), getToolbarTitle(options, id), "zoom-reset");
1166
+ addCommandButton(id, getToolbarLabel(options, locale, id), getToolbarTitle(options, locale, id), "zoom-reset");
1104
1167
  zoomResetButton = commandButtons[commandButtons.length - 1]?.button;
1105
1168
  updateZoomLabel();
1106
1169
  return;
1107
1170
  }
1108
1171
  if (id === "rotate-right" && options.rotate) {
1109
- addCommandButton(id, getToolbarLabel(options, id), getToolbarTitle(options, id), "rotate-right");
1172
+ addCommandButton(id, getToolbarLabel(options, locale, id), getToolbarTitle(options, locale, id), "rotate-right");
1110
1173
  return;
1111
1174
  }
1112
1175
  if (id === "download" && options.download !== false) {
1113
1176
  addButton(
1114
- getToolbarLabel(options, id),
1115
- getToolbarTitle(options, id),
1177
+ getToolbarLabel(options, locale, id),
1178
+ getToolbarTitle(options, locale, id),
1116
1179
  () => getContext().download(),
1117
1180
  void 0,
1118
1181
  options.icons?.download
@@ -1121,8 +1184,8 @@ function createToolbar(toolbar, viewport, queue) {
1121
1184
  }
1122
1185
  if (id === "fullscreen" && options.fullscreen !== false) {
1123
1186
  fullscreenButton = addButton(
1124
- getToolbarLabel(options, id),
1125
- getToolbarTitle(options, id),
1187
+ getToolbarLabel(options, locale, id),
1188
+ getToolbarTitle(options, locale, id),
1126
1189
  () => getContext().fullscreen(),
1127
1190
  void 0,
1128
1191
  options.icons?.fullscreen
@@ -1132,8 +1195,8 @@ function createToolbar(toolbar, viewport, queue) {
1132
1195
  }
1133
1196
  if (id === "print" && options.print) {
1134
1197
  addButton(
1135
- getToolbarLabel(options, id),
1136
- getToolbarTitle(options, id),
1198
+ getToolbarLabel(options, locale, id),
1199
+ getToolbarTitle(options, locale, id),
1137
1200
  () => getContext().print(),
1138
1201
  void 0,
1139
1202
  options.icons?.print
@@ -1159,11 +1222,11 @@ function createToolbar(toolbar, viewport, queue) {
1159
1222
  const renderSearchControl = () => {
1160
1223
  const searchGroup = document.createElement("div");
1161
1224
  searchGroup.className = "ofv-toolbar-search";
1162
- searchGroup.title = getToolbarTitle(options, "search");
1225
+ searchGroup.title = getToolbarTitle(options, locale, "search");
1163
1226
  const nextSearchInput = document.createElement("input");
1164
1227
  nextSearchInput.type = "search";
1165
- nextSearchInput.placeholder = getToolbarLabel(options, "search");
1166
- nextSearchInput.setAttribute("aria-label", getToolbarTitle(options, "search"));
1228
+ nextSearchInput.placeholder = getToolbarLabel(options, locale, "search");
1229
+ nextSearchInput.setAttribute("aria-label", getToolbarTitle(options, locale, "search"));
1167
1230
  const nextSearchCount = document.createElement("span");
1168
1231
  nextSearchCount.className = "ofv-toolbar-search-count";
1169
1232
  searchInput = nextSearchInput;
@@ -1218,7 +1281,7 @@ function createToolbar(toolbar, viewport, queue) {
1218
1281
  }
1219
1282
  setToolbarButtonContent(
1220
1283
  zoomResetButton,
1221
- currentZoom === void 0 ? getToolbarLabel(options, "zoom-reset") : formatToolbarZoom(currentZoom),
1284
+ currentZoom === void 0 ? getToolbarLabel(options, locale, "zoom-reset") : formatToolbarZoom(currentZoom),
1222
1285
  options.icons?.["zoom-reset"]
1223
1286
  );
1224
1287
  }
@@ -1241,8 +1304,8 @@ function createToolbar(toolbar, viewport, queue) {
1241
1304
  const active = isFullscreenActive();
1242
1305
  const id = active ? "exit-fullscreen" : "fullscreen";
1243
1306
  const icon = active ? options.icons?.["exit-fullscreen"] ?? options.icons?.fullscreen : options.icons?.fullscreen;
1244
- setToolbarButtonContent(fullscreenButton, getToolbarLabel(options, id), icon);
1245
- const title = getToolbarTitle(options, id);
1307
+ setToolbarButtonContent(fullscreenButton, getToolbarLabel(options, locale, id), icon);
1308
+ const title = getToolbarTitle(options, locale, id);
1246
1309
  fullscreenButton.title = title;
1247
1310
  fullscreenButton.setAttribute("aria-label", title);
1248
1311
  fullscreenButton.setAttribute("aria-pressed", String(active));
@@ -1366,39 +1429,75 @@ function createToolbarContext({
1366
1429
  clearSearch: search.clear
1367
1430
  };
1368
1431
  }
1369
- var defaultToolbarLabels = {
1370
- previous: "Prev",
1371
- next: "Next",
1372
- queue: "",
1373
- "zoom-out": "-",
1374
- "zoom-in": "+",
1375
- "zoom-reset": "100%",
1376
- "rotate-right": "Rotate",
1377
- download: "Download",
1378
- fullscreen: "Fullscreen",
1379
- "exit-fullscreen": "Exit fullscreen",
1380
- print: "Print",
1381
- search: "Search"
1382
- };
1383
- var defaultToolbarTitles = {
1384
- previous: "Previous file",
1385
- next: "Next file",
1386
- queue: "Current file position",
1387
- "zoom-out": "Zoom out",
1388
- "zoom-in": "Zoom in",
1389
- "zoom-reset": "Reset zoom",
1390
- "rotate-right": "Rotate right",
1391
- download: "Download file",
1392
- fullscreen: "Open preview fullscreen",
1393
- "exit-fullscreen": "Exit fullscreen",
1394
- print: "Print preview",
1395
- search: "Search preview text"
1432
+ var defaultToolbarText = {
1433
+ "zh-CN": {
1434
+ ariaLabel: "\u6587\u4EF6\u9884\u89C8\u5DE5\u5177\u680F",
1435
+ labels: {
1436
+ previous: "\u4E0A\u4E00\u4E2A",
1437
+ next: "\u4E0B\u4E00\u4E2A",
1438
+ queue: "",
1439
+ "zoom-out": "-",
1440
+ "zoom-in": "+",
1441
+ "zoom-reset": "100%",
1442
+ "rotate-right": "\u65CB\u8F6C",
1443
+ download: "\u4E0B\u8F7D",
1444
+ fullscreen: "\u5168\u5C4F",
1445
+ "exit-fullscreen": "\u9000\u51FA\u5168\u5C4F",
1446
+ print: "\u6253\u5370",
1447
+ search: "\u641C\u7D22"
1448
+ },
1449
+ titles: {
1450
+ previous: "\u4E0A\u4E00\u4E2A\u6587\u4EF6",
1451
+ next: "\u4E0B\u4E00\u4E2A\u6587\u4EF6",
1452
+ queue: "\u5F53\u524D\u6587\u4EF6\u4F4D\u7F6E",
1453
+ "zoom-out": "\u7F29\u5C0F",
1454
+ "zoom-in": "\u653E\u5927",
1455
+ "zoom-reset": "\u91CD\u7F6E\u7F29\u653E",
1456
+ "rotate-right": "\u5411\u53F3\u65CB\u8F6C",
1457
+ download: "\u4E0B\u8F7D\u6587\u4EF6",
1458
+ fullscreen: "\u5168\u5C4F\u67E5\u770B\u9884\u89C8",
1459
+ "exit-fullscreen": "\u9000\u51FA\u5168\u5C4F",
1460
+ print: "\u6253\u5370\u9884\u89C8",
1461
+ search: "\u641C\u7D22\u9884\u89C8\u6587\u672C"
1462
+ }
1463
+ },
1464
+ "en-US": {
1465
+ ariaLabel: "File preview toolbar",
1466
+ labels: {
1467
+ previous: "Prev",
1468
+ next: "Next",
1469
+ queue: "",
1470
+ "zoom-out": "-",
1471
+ "zoom-in": "+",
1472
+ "zoom-reset": "100%",
1473
+ "rotate-right": "Rotate",
1474
+ download: "Download",
1475
+ fullscreen: "Fullscreen",
1476
+ "exit-fullscreen": "Exit fullscreen",
1477
+ print: "Print",
1478
+ search: "Search"
1479
+ },
1480
+ titles: {
1481
+ previous: "Previous file",
1482
+ next: "Next file",
1483
+ queue: "Current file position",
1484
+ "zoom-out": "Zoom out",
1485
+ "zoom-in": "Zoom in",
1486
+ "zoom-reset": "Reset zoom",
1487
+ "rotate-right": "Rotate right",
1488
+ download: "Download file",
1489
+ fullscreen: "Open preview fullscreen",
1490
+ "exit-fullscreen": "Exit fullscreen",
1491
+ print: "Print preview",
1492
+ search: "Search preview text"
1493
+ }
1494
+ }
1396
1495
  };
1397
- function getToolbarLabel(options, id) {
1398
- return options.labels?.[id] ?? defaultToolbarLabels[id];
1496
+ function getToolbarLabel(options, locale, id) {
1497
+ return options.labels?.[id] ?? defaultToolbarText[locale].labels[id];
1399
1498
  }
1400
- function getToolbarTitle(options, id) {
1401
- return options.titles?.[id] ?? options.labels?.[id] ?? defaultToolbarTitles[id];
1499
+ function getToolbarTitle(options, locale, id) {
1500
+ return options.titles?.[id] ?? options.labels?.[id] ?? defaultToolbarText[locale].titles[id];
1402
1501
  }
1403
1502
  function formatToolbarZoom(zoom) {
1404
1503
  return `${Math.round(zoom * 100)}%`;
@@ -1550,7 +1649,7 @@ function isSafeToolbarIconAttribute(name, value) {
1550
1649
  return !/^\s*(?:javascript|data:text\/html|vbscript):/i.test(value);
1551
1650
  }
1552
1651
  function isBuiltInToolbarAction(id) {
1553
- return id in defaultToolbarLabels;
1652
+ return id in defaultToolbarText["en-US"].labels;
1554
1653
  }
1555
1654
  function createSearchController(root) {
1556
1655
  const markerClass = "ofv-search-match";
@@ -2121,7 +2220,7 @@ function imagePlugin() {
2121
2220
  infoBar.hidden = false;
2122
2221
  infoBar.removeAttribute("aria-hidden");
2123
2222
  infoBar.style.removeProperty("display");
2124
- stage.replaceChildren(createImageFallback(ctx.file.name, url));
2223
+ stage.replaceChildren(createImageFallback(ctx.file.name, url, ctx.options.messages));
2125
2224
  ctx.toolbar?.refreshCommandSupport();
2126
2225
  };
2127
2226
  const setScale = (nextScale) => {
@@ -2146,13 +2245,13 @@ function imagePlugin() {
2146
2245
  return () => button.removeEventListener("click", action);
2147
2246
  };
2148
2247
  const disposers = showInlineControls ? [
2149
- addButton("-", "Zoom out", () => setScale(scale - 0.25)),
2150
- addButton("+", "Zoom in", () => setScale(scale + 0.25)),
2151
- addButton("Rotate", "Rotate image", () => {
2248
+ addButton("-", ctx.options.messages.imageZoomOut, () => setScale(scale - 0.25)),
2249
+ addButton("+", ctx.options.messages.imageZoomIn, () => setScale(scale + 0.25)),
2250
+ addButton(ctx.options.messages.imageRotate, ctx.options.messages.imageRotate, () => {
2152
2251
  rotation += 90;
2153
2252
  updateTransform();
2154
2253
  }),
2155
- addButton("Reset", "Reset image view", reset)
2254
+ addButton(ctx.options.messages.imageReset, ctx.options.messages.imageReset, reset)
2156
2255
  ] : [];
2157
2256
  if (showInlineControls) {
2158
2257
  controls.append(zoomLabel);
@@ -2367,17 +2466,17 @@ function getTiffDimensions(ifd) {
2367
2466
  height: Number(ifd.height || ifd.t257 || 0)
2368
2467
  };
2369
2468
  }
2370
- function createImageFallback(fileName, url) {
2469
+ function createImageFallback(fileName, url, messages) {
2371
2470
  const fallback = document.createElement("div");
2372
2471
  fallback.className = "ofv-fallback";
2373
2472
  const title = document.createElement("strong");
2374
- title.textContent = "\u56FE\u7247\u9884\u89C8\u5931\u8D25";
2473
+ title.textContent = messages.imagePreviewFailedTitle;
2375
2474
  const meta = document.createElement("span");
2376
- meta.textContent = "\u5F53\u524D\u6D4F\u89C8\u5668\u65E0\u6CD5\u76F4\u63A5\u663E\u793A\u8BE5\u56FE\u7247\uFF0C\u6587\u4EF6\u53EF\u80FD\u5DF2\u635F\u574F\u6216\u7F16\u7801\u6682\u4E0D\u53D7\u652F\u6301\u3002";
2475
+ meta.textContent = messages.imagePreviewFailedMessage;
2377
2476
  const download = document.createElement("a");
2378
2477
  download.href = url;
2379
2478
  download.download = fileName;
2380
- download.textContent = "\u4E0B\u8F7D\u56FE\u7247";
2479
+ download.textContent = messages.imageDownload;
2381
2480
  fallback.append(title, meta, download);
2382
2481
  return fallback;
2383
2482
  }
@@ -4738,17 +4837,20 @@ function pdfPlugin(options = {}) {
4738
4837
  fit: ctx.options.fit,
4739
4838
  zoom: ctx.options.zoom,
4740
4839
  toolbar: ctx.toolbar,
4741
- encryptedTitle: "PDF \u5DF2\u52A0\u5BC6\uFF0C\u65E0\u6CD5\u5728\u7EBF\u9884\u89C8",
4742
- encryptedMessage: "\u8BF7\u4E0B\u8F7D\u540E\u4F7F\u7528\u5BC6\u7801\u6253\u5F00\uFF0C\u6216\u4E0A\u4F20\u89E3\u5BC6\u540E\u7684 PDF \u6587\u4EF6\u3002",
4743
- encryptedAction: "\u4E0B\u8F7D PDF",
4840
+ messages: ctx.options.messages,
4744
4841
  revokeUrlOnDestroy: true
4745
4842
  });
4746
4843
  }
4747
4844
  };
4748
4845
  }
4749
4846
  async function renderPdfDocumentPreview(options) {
4847
+ const useLegacyCompatibility = shouldUseLegacyPdfCompatibility(options.compatibilityMode);
4848
+ if (useLegacyCompatibility) {
4849
+ installPromiseWithResolversPolyfill();
4850
+ }
4750
4851
  const pdf = options.pdfjs || await import("pdfjs-dist");
4751
- configurePdfWorker(pdf, options.workerSrc);
4852
+ const messages = { ...defaultMessages["en-US"], ...options.messages };
4853
+ configurePdfWorker(pdf, options.workerSrc, useLegacyCompatibility);
4752
4854
  const viewer = document.createElement("div");
4753
4855
  viewer.className = "ofv-pdf-viewer";
4754
4856
  if (options.title) {
@@ -4777,10 +4879,10 @@ async function renderPdfDocumentPreview(options) {
4777
4879
  url: options.fileUrl
4778
4880
  };
4779
4881
  const fallback = isEncryptedError(error) ? createEncryptedFallback(fileLike, options.fileUrl, {
4780
- title: options.encryptedTitle || "PDF \u5DF2\u52A0\u5BC6\uFF0C\u65E0\u6CD5\u5728\u7EBF\u9884\u89C8",
4781
- message: options.encryptedMessage || "\u8BF7\u4E0B\u8F7D\u540E\u4F7F\u7528\u5BC6\u7801\u6253\u5F00\uFF0C\u6216\u4E0A\u4F20\u89E3\u5BC6\u540E\u7684 PDF \u6587\u4EF6\u3002",
4782
- action: options.encryptedAction || "\u4E0B\u8F7D PDF"
4783
- }) : createPdfFallback(options.fileName, options.fileUrl, normalizePdfError(error), options.fallbackTitle);
4882
+ title: options.encryptedTitle || messages.pdfEncryptedTitle,
4883
+ message: options.encryptedMessage || messages.pdfEncryptedMessage,
4884
+ action: options.encryptedAction || messages.pdfDownload
4885
+ }) : createPdfFallback(options.fileName, options.fileUrl, normalizePdfError(error, messages), messages, options.fallbackTitle);
4784
4886
  if (!fallback.classList.contains("ofv-pdf-web-fallback")) {
4785
4887
  options.viewport.classList.add("ofv-center");
4786
4888
  }
@@ -4847,8 +4949,43 @@ async function renderPdfDocumentPreview(options) {
4847
4949
  let currentSize = options.size;
4848
4950
  let zoomFactor = getInitialZoom({ options: { zoom: options.zoom ?? 1 } }, 0.25, 4);
4849
4951
  let rotation = 0;
4952
+ let currentPage = 1;
4953
+ const goToPage = (page, scroll = true) => {
4954
+ currentPage = Math.min(pdfDocument.numPages, Math.max(1, Math.round(page) || 1));
4955
+ pageNavigator.setCurrent(currentPage);
4956
+ const wrapper = pageStates[currentPage - 1]?.wrapper;
4957
+ if (!scroll || !wrapper) {
4958
+ return;
4959
+ }
4960
+ const top = Math.max(0, wrapper.offsetTop - 16);
4961
+ if (typeof scroller.scrollTo === "function") {
4962
+ scroller.scrollTo({ top, behavior: "smooth" });
4963
+ } else {
4964
+ scroller.scrollTop = top;
4965
+ }
4966
+ };
4967
+ const pageNavigator = createPdfPageNavigator(pdfDocument.numPages, messages, (page) => goToPage(page));
4968
+ viewer.insertBefore(pageNavigator.element, summary);
4969
+ const handleScrollerScroll = () => {
4970
+ const scrollerRect = scroller.getBoundingClientRect();
4971
+ const targetY = scrollerRect.top + Math.min(Math.max(scroller.clientHeight * 0.25, 40), 180);
4972
+ let nearestPage = currentPage;
4973
+ let nearestDistance = Number.POSITIVE_INFINITY;
4974
+ pageStates.forEach((state, index) => {
4975
+ const distance = Math.abs(state.wrapper.getBoundingClientRect().top - targetY);
4976
+ if (distance < nearestDistance) {
4977
+ nearestDistance = distance;
4978
+ nearestPage = index + 1;
4979
+ }
4980
+ });
4981
+ if (nearestPage !== currentPage) {
4982
+ currentPage = nearestPage;
4983
+ pageNavigator.setCurrent(currentPage);
4984
+ }
4985
+ };
4986
+ scroller.addEventListener("scroll", handleScrollerScroll, { passive: true });
4850
4987
  const updateSummary = () => {
4851
- renderPdfSummary(summary, pdfDocument.numPages, pagesMeta, options.fit, zoomFactor);
4988
+ renderPdfSummary(summary, pdfDocument.numPages, pagesMeta, options.fit, zoomFactor, messages);
4852
4989
  options.toolbar?.setZoom(zoomFactor);
4853
4990
  };
4854
4991
  const clearPage = (pageIdx) => {
@@ -4864,7 +5001,9 @@ async function renderPdfDocumentPreview(options) {
4864
5001
  state.canvas = null;
4865
5002
  state.rendered = false;
4866
5003
  state.wrapper.replaceChildren();
4867
- state.wrapper.append(createPageStatus("ofv-pdf-skeleton", `\u9875\u9762 ${pageIdx + 1} \u52A0\u8F7D\u4E2D...`));
5004
+ state.wrapper.append(
5005
+ createPageStatus("ofv-pdf-skeleton", formatPreviewMessage(messages.pdfPageLoading, { page: pageIdx + 1 }))
5006
+ );
4868
5007
  };
4869
5008
  const renderPage = async (pageIdx, size) => {
4870
5009
  const state = pageStates[pageIdx];
@@ -4932,7 +5071,7 @@ async function renderPdfDocumentPreview(options) {
4932
5071
  state.wrapper.appendChild(
4933
5072
  createPageStatus(
4934
5073
  "ofv-pdf-empty",
4935
- "\u8BE5\u9875\u6CA1\u6709\u68C0\u6D4B\u5230\u53EF\u663E\u793A\u7684 PDF \u517C\u5BB9\u5185\u5BB9\u3002\u82E5\u8FD9\u662F Illustrator/AI \u6587\u4EF6\uFF0C\u53EF\u80FD\u53EA\u5305\u542B\u79C1\u6709\u7F16\u8F91\u6570\u636E\uFF0C\u5EFA\u8BAE\u5BFC\u51FA\u4E3A PDF/SVG/PNG \u540E\u9884\u89C8\u3002"
5074
+ messages.pdfPageEmpty
4936
5075
  )
4937
5076
  );
4938
5077
  }
@@ -4940,7 +5079,7 @@ async function renderPdfDocumentPreview(options) {
4940
5079
  console.error(`Failed to render PDF page ${pageIdx + 1}:`, err);
4941
5080
  state.rendered = false;
4942
5081
  state.wrapper.replaceChildren(
4943
- createPageStatus("ofv-pdf-error", "\u65E0\u6CD5\u6E32\u67D3\u8BE5\u9875\u9762\u3002\u8BE5\u9875\u53EF\u80FD\u5305\u542B\u6D4F\u89C8\u5668 PDF \u5F15\u64CE\u6682\u4E0D\u652F\u6301\u7684\u56FE\u5F62\u3001\u5B57\u4F53\u6216\u538B\u7F29\u7279\u6027\u3002")
5082
+ createPageStatus("ofv-pdf-error", messages.pdfPageRenderFailed)
4944
5083
  );
4945
5084
  }
4946
5085
  };
@@ -4981,9 +5120,10 @@ async function renderPdfDocumentPreview(options) {
4981
5120
  const wrapper = document.createElement("div");
4982
5121
  wrapper.className = "ofv-pdf-page-wrapper";
4983
5122
  wrapper.setAttribute("data-page-index", String(i));
5123
+ wrapper.setAttribute("aria-label", formatPreviewMessage(messages.pdfPageLabel, { page: i + 1 }));
4984
5124
  wrapper.style.width = `${w}px`;
4985
5125
  wrapper.style.height = `${h}px`;
4986
- wrapper.append(createPageStatus("ofv-pdf-skeleton", `\u9875\u9762 ${i + 1} \u52A0\u8F7D\u4E2D...`));
5126
+ wrapper.append(createPageStatus("ofv-pdf-skeleton", formatPreviewMessage(messages.pdfPageLoading, { page: i + 1 })));
4987
5127
  scroller.appendChild(wrapper);
4988
5128
  pageStates.push({
4989
5129
  wrapper,
@@ -5005,6 +5145,7 @@ async function renderPdfDocumentPreview(options) {
5005
5145
  }
5006
5146
  }, 0);
5007
5147
  }
5148
+ goToPage(currentPage, false);
5008
5149
  };
5009
5150
  renderLayout(options.size);
5010
5151
  let resizeTimer;
@@ -5045,6 +5186,8 @@ async function renderPdfDocumentPreview(options) {
5045
5186
  },
5046
5187
  destroy() {
5047
5188
  options.toolbar?.setZoom(void 0);
5189
+ pageNavigator.destroy();
5190
+ scroller.removeEventListener("scroll", handleScrollerScroll);
5048
5191
  window.clearTimeout(resizeTimer);
5049
5192
  observer?.disconnect();
5050
5193
  pageStates.forEach((state) => {
@@ -5090,15 +5233,15 @@ function getPdfAvailableWidth(width) {
5090
5233
  const gutter = width < 160 ? 16 : 32;
5091
5234
  return Math.max(1, width - gutter);
5092
5235
  }
5093
- function renderPdfSummary(summary, pages, pagesMeta, fit, zoomFactor) {
5236
+ function renderPdfSummary(summary, pages, pagesMeta, fit, zoomFactor, messages) {
5094
5237
  summary.replaceChildren();
5095
- appendPdfSummary(summary, "\u9875\u6570", String(pages));
5238
+ appendPdfSummary(summary, messages.pdfSummaryPages, String(pages));
5096
5239
  const pageSizes = formatPdfPageSizes(pagesMeta);
5097
5240
  if (pageSizes) {
5098
- appendPdfSummary(summary, "\u9875\u9762\u5C3A\u5BF8", pageSizes);
5241
+ appendPdfSummary(summary, messages.pdfSummaryPageSizes, pageSizes);
5099
5242
  }
5100
- appendPdfSummary(summary, "\u9002\u914D", fit === "actual" ? "\u539F\u59CB\u5927\u5C0F" : "\u9002\u5408\u5BBD\u5EA6");
5101
- appendPdfSummary(summary, "\u7F29\u653E", `${Math.round(zoomFactor * 100)}%`);
5243
+ appendPdfSummary(summary, messages.pdfSummaryFit, fit === "actual" ? messages.pdfSummaryActualSize : messages.pdfSummaryFitWidth);
5244
+ appendPdfSummary(summary, messages.pdfSummaryZoom, `${Math.round(zoomFactor * 100)}%`);
5102
5245
  }
5103
5246
  function appendPdfSummary(parent, label, value) {
5104
5247
  const item = document.createElement("span");
@@ -5188,7 +5331,7 @@ function createPageStatus(className, text) {
5188
5331
  status.textContent = text;
5189
5332
  return status;
5190
5333
  }
5191
- function createPdfFallback(fileName, url, message, titleText = "PDF \u9884\u89C8\u5931\u8D25") {
5334
+ function createPdfFallback(fileName, url, message, messages, titleText = messages.pdfPreviewFailedTitle) {
5192
5335
  if (isEmbeddableRemoteUrl(url)) {
5193
5336
  return createPdfWebFallback(fileName, url);
5194
5337
  }
@@ -5201,7 +5344,7 @@ function createPdfFallback(fileName, url, message, titleText = "PDF \u9884\u89C8
5201
5344
  const download = document.createElement("a");
5202
5345
  download.href = url;
5203
5346
  download.download = fileName;
5204
- download.textContent = "\u4E0B\u8F7D PDF";
5347
+ download.textContent = messages.pdfDownload;
5205
5348
  fallback.append(title, meta, download);
5206
5349
  return fallback;
5207
5350
  }
@@ -5220,23 +5363,109 @@ function createPdfWebFallback(fileName, url) {
5220
5363
  function isEmbeddableRemoteUrl(url) {
5221
5364
  return /^https?:\/\//i.test(url);
5222
5365
  }
5223
- function normalizePdfError(error) {
5366
+ function normalizePdfError(error, messages) {
5224
5367
  const message = error instanceof Error ? error.message : String(error || "");
5225
5368
  const name = typeof error === "object" && error !== null && "name" in error ? String(error.name) : "";
5226
5369
  const lower = `${name} ${message}`.toLowerCase();
5227
5370
  if (lower.includes("invalid") || lower.includes("missing") || lower.includes("corrupt")) {
5228
- return "\u8BE5 PDF \u6587\u4EF6\u53EF\u80FD\u5DF2\u635F\u574F\u6216\u683C\u5F0F\u65E0\u6548\u3002";
5371
+ return messages.pdfCorruptedMessage;
5229
5372
  }
5230
- return "\u5F53\u524D\u6D4F\u89C8\u5668\u65E0\u6CD5\u52A0\u8F7D\u8BE5 PDF\u3002";
5373
+ return messages.pdfCannotLoadMessage;
5374
+ }
5375
+ function createPdfPageNavigator(total, messages, onChange) {
5376
+ const element = document.createElement("div");
5377
+ element.className = "ofv-pdf-page-navigator";
5378
+ const previous = document.createElement("button");
5379
+ previous.type = "button";
5380
+ previous.textContent = "\u2039";
5381
+ previous.title = messages.pdfPreviousPage;
5382
+ previous.setAttribute("aria-label", messages.pdfPreviousPage);
5383
+ const input = document.createElement("input");
5384
+ input.type = "number";
5385
+ input.min = "1";
5386
+ input.max = String(total);
5387
+ input.value = "1";
5388
+ input.inputMode = "numeric";
5389
+ input.setAttribute("aria-label", messages.pdfPageInput);
5390
+ const position = document.createElement("span");
5391
+ position.textContent = formatPreviewMessage(messages.pdfPagePosition, { total });
5392
+ const next = document.createElement("button");
5393
+ next.type = "button";
5394
+ next.textContent = "\u203A";
5395
+ next.title = messages.pdfNextPage;
5396
+ next.setAttribute("aria-label", messages.pdfNextPage);
5397
+ const commit = () => onChange(Number(input.value));
5398
+ const handleKeydown = (event) => {
5399
+ if (event.key === "Enter") {
5400
+ commit();
5401
+ }
5402
+ };
5403
+ const handlePrevious = () => onChange(Number(input.value) - 1);
5404
+ const handleNext = () => onChange(Number(input.value) + 1);
5405
+ input.addEventListener("change", commit);
5406
+ input.addEventListener("keydown", handleKeydown);
5407
+ previous.addEventListener("click", handlePrevious);
5408
+ next.addEventListener("click", handleNext);
5409
+ element.append(previous, input, position, next);
5410
+ return {
5411
+ element,
5412
+ setCurrent(page) {
5413
+ input.value = String(page);
5414
+ previous.disabled = page <= 1;
5415
+ next.disabled = page >= total;
5416
+ },
5417
+ destroy() {
5418
+ input.removeEventListener("change", commit);
5419
+ input.removeEventListener("keydown", handleKeydown);
5420
+ previous.removeEventListener("click", handlePrevious);
5421
+ next.removeEventListener("click", handleNext);
5422
+ }
5423
+ };
5231
5424
  }
5232
- function configurePdfWorker(pdf, workerSrc) {
5425
+ function configurePdfWorker(pdf, workerSrc, legacy = false) {
5233
5426
  if (workerSrc) {
5234
5427
  pdf.GlobalWorkerOptions.workerSrc = workerSrc;
5235
5428
  return;
5236
5429
  }
5237
5430
  if (!pdf.GlobalWorkerOptions.workerSrc && typeof window !== "undefined") {
5238
- pdf.GlobalWorkerOptions.workerSrc = `https://cdn.jsdelivr.net/npm/pdfjs-dist@${pdf.version}/build/pdf.worker.mjs`;
5431
+ const build = legacy ? "legacy/build" : "build";
5432
+ pdf.GlobalWorkerOptions.workerSrc = `https://cdn.jsdelivr.net/npm/pdfjs-dist@${pdf.version}/${build}/pdf.worker.mjs`;
5433
+ }
5434
+ }
5435
+ function shouldUseLegacyPdfCompatibility(mode = "auto") {
5436
+ if (mode === "legacy") {
5437
+ return true;
5438
+ }
5439
+ if (mode === "modern") {
5440
+ return false;
5441
+ }
5442
+ const promise = Promise;
5443
+ if (typeof promise.withResolvers !== "function") {
5444
+ return true;
5445
+ }
5446
+ if (typeof navigator === "undefined") {
5447
+ return false;
5239
5448
  }
5449
+ return /(?:QIHU|360SE|360EE)/i.test(navigator.userAgent);
5450
+ }
5451
+ function installPromiseWithResolversPolyfill() {
5452
+ const promise = Promise;
5453
+ if (typeof promise.withResolvers === "function") {
5454
+ return;
5455
+ }
5456
+ Object.defineProperty(promise, "withResolvers", {
5457
+ configurable: true,
5458
+ writable: true,
5459
+ value: () => {
5460
+ let resolve;
5461
+ let reject;
5462
+ const pending = new Promise((resolvePromise, rejectPromise) => {
5463
+ resolve = resolvePromise;
5464
+ reject = rejectPromise;
5465
+ });
5466
+ return { promise: pending, resolve, reject };
5467
+ }
5468
+ });
5240
5469
  }
5241
5470
 
5242
5471
  // src/plugins/epub.ts
@@ -7631,8 +7860,9 @@ async function renderConvertedOfficePreview(panel, ctx, options, conversionConte
7631
7860
  fit: ctx.options.fit,
7632
7861
  zoom: ctx.options.zoom,
7633
7862
  toolbar: ctx.toolbar,
7634
- title: "Office \u9AD8\u4FDD\u771F\u8F6C\u6362\u9884\u89C8",
7635
- fallbackTitle: "Office \u8F6C\u6362\u540E\u7684 PDF \u65E0\u6CD5\u9884\u89C8",
7863
+ messages: ctx.options.messages,
7864
+ title: ctx.options.messages.officeConvertedTitle,
7865
+ fallbackTitle: ctx.options.messages.officeConvertedPdfFailed,
7636
7866
  revokeUrlOnDestroy: converted.revokeUrlOnDestroy
7637
7867
  });
7638
7868
  }
@@ -9414,6 +9644,9 @@ function renderChartCard(chart) {
9414
9644
  function renderChartSvg(chart) {
9415
9645
  const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
9416
9646
  svg.setAttribute("viewBox", "0 0 640 380");
9647
+ svg.setAttribute("preserveAspectRatio", "xMidYMid meet");
9648
+ svg.setAttribute("text-rendering", "geometricPrecision");
9649
+ svg.setAttribute("shape-rendering", "geometricPrecision");
9417
9650
  svg.setAttribute("role", "img");
9418
9651
  svg.setAttribute("aria-label", chart.title);
9419
9652
  svg.classList.add("ofv-chart-svg");
@@ -9459,16 +9692,7 @@ function renderChartSvg(chart) {
9459
9692
  const clusterWidth = groupWidth * 0.58;
9460
9693
  const barWidth = Math.max(5, Math.min(28, clusterWidth / Math.max(1, chart.series.length)));
9461
9694
  const zeroY = plot.y + plot.height - (0 - axisMin) / (axisMax - axisMin || 1) * plot.height;
9462
- categories.slice(0, categoryCount).forEach((category, index) => {
9463
- const x = plot.x + groupWidth * (index + 0.5);
9464
- const label = appendSvg(svg, "text", {
9465
- x: Number(x.toFixed(1)),
9466
- y: plot.y + plot.height + 22,
9467
- class: "ofv-chart-label",
9468
- "text-anchor": "middle"
9469
- });
9470
- label.textContent = truncateChartLabel(category);
9471
- });
9695
+ appendChartCategoryLabels(svg, categories, plot, (index) => plot.x + groupWidth * (index + 0.5));
9472
9696
  chart.series.forEach((series, seriesIndex) => {
9473
9697
  const color = series.color || colors[seriesIndex % colors.length];
9474
9698
  series.values.forEach((value, index) => {
@@ -9486,6 +9710,8 @@ function renderChartSvg(chart) {
9486
9710
  });
9487
9711
  });
9488
9712
  } else {
9713
+ const categoryStep = categories.length > 1 ? plot.width / (categories.length - 1) : plot.width;
9714
+ appendChartCategoryLabels(svg, categories, plot, (index) => plot.x + index * categoryStep);
9489
9715
  chart.series.forEach((series, seriesIndex) => {
9490
9716
  const color = series.color || colors[seriesIndex % colors.length];
9491
9717
  const step = series.values.length > 1 ? plot.width / (series.values.length - 1) : plot.width;
@@ -9510,6 +9736,22 @@ function renderChartSvg(chart) {
9510
9736
  appendChartLegend(svg, chart, colors, 348);
9511
9737
  return svg;
9512
9738
  }
9739
+ function appendChartCategoryLabels(svg, categories, plot, getX) {
9740
+ const interval = Math.max(1, Math.ceil(categories.length / 14));
9741
+ categories.forEach((category, index) => {
9742
+ if (index % interval !== 0 && index !== categories.length - 1) {
9743
+ return;
9744
+ }
9745
+ const label = appendSvg(svg, "text", {
9746
+ x: Number(getX(index).toFixed(1)),
9747
+ y: plot.y + plot.height + 22,
9748
+ class: "ofv-chart-label ofv-chart-category-label",
9749
+ "data-axis": "category",
9750
+ "text-anchor": "middle"
9751
+ });
9752
+ label.textContent = truncateChartLabel(category);
9753
+ });
9754
+ }
9513
9755
  function appendChartLegend(svg, chart, colors, y) {
9514
9756
  const itemWidth = 86;
9515
9757
  const startX = 320 - chart.series.length * itemWidth / 2;