@open-file-viewer/core 0.1.20 → 0.1.22

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.cjs CHANGED
@@ -622,7 +622,40 @@ var defaultMessages = {
622
622
  size: "\u5927\u5C0F",
623
623
  source: "\u6765\u6E90",
624
624
  remoteUrl: "\u8FDC\u7A0B URL",
625
- localFile: "\u672C\u5730/\u5185\u5B58\u6587\u4EF6"
625
+ localFile: "\u672C\u5730/\u5185\u5B58\u6587\u4EF6",
626
+ textPlainLanguage: "\u7EAF\u6587\u672C",
627
+ textLineCount: "{count} \u884C",
628
+ textWrap: "\u6362\u884C",
629
+ textCopy: "\u590D\u5236",
630
+ textCopied: "\u5DF2\u590D\u5236",
631
+ textCopyFailed: "\u590D\u5236\u5931\u8D25",
632
+ textDownload: "\u4E0B\u8F7D",
633
+ textDownloadReady: "\u4E0B\u8F7D\u5DF2\u51C6\u5907",
634
+ textLargeFileNotice: "\u6587\u4EF6\u8F83\u5927\uFF0C\u5F53\u524D\u5C55\u793A\u524D {size}\uFF0C\u590D\u5236\u548C\u4E0B\u8F7D\u4ECD\u4F1A\u4F7F\u7528\u5B8C\u6574\u5185\u5BB9\u3002",
635
+ textHighlightSkipped: "\u5185\u5BB9\u8F83\u5927\uFF0C\u5DF2\u8DF3\u8FC7\u8BED\u6CD5\u9AD8\u4EAE\u4EE5\u4FDD\u6301\u6EDA\u52A8\u6D41\u7545\u3002",
636
+ textPreviewFailedTitle: "\u6587\u672C\u9884\u89C8\u5931\u8D25",
637
+ textPreviewFailedMessage: "\u65E0\u6CD5\u8BFB\u53D6\u8BE5\u6587\u672C\u5185\u5BB9\uFF0C\u53EF\u80FD\u662F\u8FDC\u7A0B\u6587\u4EF6\u4E0D\u53EF\u8BBF\u95EE\u6216\u54CD\u5E94\u72B6\u6001\u5F02\u5E38\u3002",
638
+ textOpenOriginal: "\u6253\u5F00\u539F\u6587\u4EF6",
639
+ officeLegacyConversionTitle: "Office \u8F6C\u6362\u63D0\u793A",
640
+ officeLegacyBinaryNotice: "\u5C5E\u4E8E\u65E7\u7248 Microsoft Office \u4E8C\u8FDB\u5236\u683C\u5F0F\uFF0C\u6D4F\u89C8\u5668\u5185\u65E0\u6CD5\u9AD8\u4FDD\u771F\u89E3\u6790\uFF1B\u5F53\u524D\u4EC5\u5C55\u793A\u53EF\u4FE1\u6587\u672C\u7247\u6BB5\u548C\u7ED3\u6784\u6307\u7EB9\uFF0C\u5B8C\u6574\u6392\u7248\u5EFA\u8BAE\u63A5\u5165 LibreOffice/OnlyOffice \u670D\u52A1\u7AEF\u8F6C\u6362\u4E3A PDF/HTML\u3002",
641
+ officeLegacyMetaFormatType: "\u683C\u5F0F\u7C7B\u578B",
642
+ officeLegacyMetaFileStructure: "\u6587\u4EF6\u7ED3\u6784",
643
+ officeLegacyOleDetected: "\u68C0\u6D4B\u5230 OLE Compound File \u7B7E\u540D",
644
+ officeLegacyOleMissing: "\u672A\u68C0\u6D4B\u5230\u6807\u51C6 OLE \u7B7E\u540D\uFF0C\u6309\u539F\u59CB\u4E8C\u8FDB\u5236\u5C1D\u8BD5\u63D0\u53D6",
645
+ officeLegacyMetaTextFragments: "\u6587\u672C\u7247\u6BB5",
646
+ officeLegacyTextFragmentCount: "{count} \u6BB5",
647
+ officeLegacyMetaParseStatus: "\u89E3\u6790\u72B6\u6001",
648
+ officeLegacyReadableFragments: "\u53EF\u8BFB\u6587\u672C\u7247\u6BB5",
649
+ officeLegacyNoText: "\u672A\u63D0\u53D6\u5230\u7A33\u5B9A\u53EF\u8BFB\u6587\u672C\u3002\u8BE5\u6587\u4EF6\u53EF\u80FD\u7ECF\u8FC7\u538B\u7F29\u3001\u52A0\u5BC6\uFF0C\u6216\u6587\u672C\u7F16\u7801\u65E0\u6CD5\u5728\u6D4F\u89C8\u5668\u7AEF\u53EF\u9760\u8BC6\u522B\uFF1B\u8BF7\u4F7F\u7528\u670D\u52A1\u7AEF LibreOffice/OnlyOffice \u8F6C\u6362\u540E\u9884\u89C8\u3002",
650
+ officeLegacyWordParseFailed: "Word \u4E8C\u8FDB\u5236\u89E3\u6790\u5931\u8D25\uFF1A{message}",
651
+ officeSheetParseFailed: "\u8868\u683C\u89E3\u6790\u5931\u8D25\uFF1A{message}",
652
+ officeUnsupportedTitle: "Office \u57FA\u7840\u9884\u89C8",
653
+ officeUnsupportedLegacyMessage: "\u8BE5\u683C\u5F0F\u5C5E\u4E8E\u8001\u4E8C\u8FDB\u5236\u6216\u4E13\u6709\u683C\u5F0F\uFF0C\u6D4F\u89C8\u5668\u5185\u65E0\u6CD5\u53EF\u9760\u89E3\u6790\uFF1B\u5EFA\u8BAE\u63A5\u5165 LibreOffice/OnlyOffice \u670D\u52A1\u7AEF\u8F6C\u6362\u4E3A PDF/HTML \u540E\u9884\u89C8\u3002",
654
+ officeUnsupportedGenericMessage: "\u8BE5\u683C\u5F0F\u901A\u5E38\u9700\u8981\u670D\u52A1\u7AEF\u8F6C\u6362\u6216\u4E13\u7528\u89E3\u6790\u5668\u624D\u80FD\u9AD8\u4FDD\u771F\u9884\u89C8\u3002",
655
+ officeUnsupportedIntro: "\u5DF2\u8FDB\u5165 Office \u63D2\u4EF6\u3002{message}",
656
+ 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",
657
+ officeErrorWithMessage: "\u89E3\u6790\u5668\u8FD4\u56DE\uFF1A{message}",
658
+ officeErrorWithoutMessage: "\u89E3\u6790\u5668\u672A\u8FD4\u56DE\u5177\u4F53\u9519\u8BEF\u4FE1\u606F\u3002"
626
659
  },
627
660
  "en-US": {
628
661
  loading: "Loading preview...",
@@ -638,7 +671,40 @@ var defaultMessages = {
638
671
  size: "Size",
639
672
  source: "Source",
640
673
  remoteUrl: "Remote URL",
641
- localFile: "Local or in-memory file"
674
+ localFile: "Local or in-memory file",
675
+ textPlainLanguage: "plain text",
676
+ textLineCount: "{count} lines",
677
+ textWrap: "Wrap",
678
+ textCopy: "Copy",
679
+ textCopied: "Copied",
680
+ textCopyFailed: "Copy failed",
681
+ textDownload: "Download",
682
+ textDownloadReady: "Download ready",
683
+ textLargeFileNotice: "Large file, showing the first {size}. Copy and download still use the full content.",
684
+ textHighlightSkipped: "Large content, syntax highlighting was skipped to keep scrolling smooth.",
685
+ textPreviewFailedTitle: "Text preview failed",
686
+ textPreviewFailedMessage: "Unable to read this text content. The remote file may be unreachable or returned an invalid response.",
687
+ textOpenOriginal: "Open original file",
688
+ officeLegacyConversionTitle: "Office conversion guidance",
689
+ officeLegacyBinaryNotice: "belongs to a legacy Microsoft Office binary format that cannot be rendered with high fidelity in the browser. The preview shows trusted text fragments and structural fingerprints; use a LibreOffice/OnlyOffice server conversion to PDF/HTML for complete layout fidelity.",
690
+ officeLegacyMetaFormatType: "Format type",
691
+ officeLegacyMetaFileStructure: "File structure",
692
+ officeLegacyOleDetected: "OLE Compound File signature detected",
693
+ officeLegacyOleMissing: "No standard OLE signature detected; extracting from raw binary data",
694
+ officeLegacyMetaTextFragments: "Text fragments",
695
+ officeLegacyTextFragmentCount: "{count} fragments",
696
+ officeLegacyMetaParseStatus: "Parse status",
697
+ officeLegacyReadableFragments: "Readable text fragments",
698
+ officeLegacyNoText: "No stable readable text was extracted. The file may be compressed, encrypted, or use text encoding that cannot be reliably recognized in the browser; use LibreOffice/OnlyOffice server conversion before previewing.",
699
+ officeLegacyWordParseFailed: "Word binary parse failed: {message}",
700
+ officeSheetParseFailed: "Spreadsheet parse failed: {message}",
701
+ officeUnsupportedTitle: "Office basic preview",
702
+ officeUnsupportedLegacyMessage: "This is a legacy binary or proprietary format that cannot be reliably parsed in the browser; convert it to PDF/HTML through LibreOffice/OnlyOffice on the server before previewing.",
703
+ officeUnsupportedGenericMessage: "This format usually needs server-side conversion or a dedicated parser for high-fidelity preview.",
704
+ officeUnsupportedIntro: "is handled by the Office plugin. {message}",
705
+ officeUnsupportedSupportedFormats: "This version prioritizes basic previews for docx, rtf, odt/fodt, xlsx/xls/csv/ods, pptx/ppsx, and odp/fodp files.",
706
+ officeErrorWithMessage: "Parser returned: {message}",
707
+ officeErrorWithoutMessage: "Parser did not return a specific error."
642
708
  }
643
709
  };
644
710
  function resolveMessages(options) {
@@ -647,6 +713,9 @@ function resolveMessages(options) {
647
713
  ...options.messages
648
714
  };
649
715
  }
716
+ function formatPreviewMessage(template, values) {
717
+ return template.replace(/\{(\w+)\}/g, (match, key) => String(values[key] ?? match));
718
+ }
650
719
 
651
720
  // src/plugins/fallback.ts
652
721
  function fallbackPlugin() {
@@ -717,9 +786,10 @@ function formatFallbackBytes(bytes) {
717
786
  function createViewer(options) {
718
787
  const container = resolveContainer(options.container);
719
788
  applyBoxSize(container, options.width, options.height);
789
+ const customClassNames = parseClassNameTokens(options.className);
720
790
  container.classList.add("ofv-root");
721
- if (options.className) {
722
- container.classList.add(options.className);
791
+ if (customClassNames.length > 0) {
792
+ container.classList.add(...customClassNames);
723
793
  }
724
794
  const theme = applyTheme(container, options.theme || "light");
725
795
  const host = document.createElement("div");
@@ -739,12 +809,17 @@ function createViewer(options) {
739
809
  currentIndex = clampIndex(index, queue.length);
740
810
  await renderQueueItem(currentIndex);
741
811
  };
742
- const toolbar = createToolbar(options.toolbar, viewport, {
743
- getLength: () => queue.length,
744
- next: () => goTo(currentIndex + 1),
745
- previous: () => goTo(currentIndex - 1),
746
- command: (command) => currentInstance?.command?.(command)
747
- });
812
+ const toolbar = createToolbar(
813
+ options.toolbar,
814
+ viewport,
815
+ {
816
+ getLength: () => queue.length,
817
+ next: () => goTo(currentIndex + 1),
818
+ previous: () => goTo(currentIndex - 1),
819
+ command: (command) => currentInstance?.command?.(command)
820
+ },
821
+ options.locale || "en-US"
822
+ );
748
823
  if (toolbar) {
749
824
  host.append(toolbar.element);
750
825
  }
@@ -863,12 +938,15 @@ function createViewer(options) {
863
938
  theme.destroy();
864
939
  container.replaceChildren();
865
940
  container.classList.remove("ofv-root");
866
- if (options.className) {
867
- container.classList.remove(options.className);
941
+ if (customClassNames.length > 0) {
942
+ container.classList.remove(...customClassNames);
868
943
  }
869
944
  }
870
945
  };
871
946
  }
947
+ function parseClassNameTokens(className) {
948
+ return className?.trim().split(/\s+/).filter(Boolean) ?? [];
949
+ }
872
950
  function destroyPreviewInstance(instance) {
873
951
  if (!instance) {
874
952
  return;
@@ -978,7 +1056,7 @@ function removeMediaListener(media, listener) {
978
1056
  }
979
1057
  media.removeListener?.(listener);
980
1058
  }
981
- function createToolbar(toolbar, viewport, queue) {
1059
+ function createToolbar(toolbar, viewport, queue, locale) {
982
1060
  if (!toolbar) {
983
1061
  return void 0;
984
1062
  }
@@ -986,7 +1064,7 @@ function createToolbar(toolbar, viewport, queue) {
986
1064
  const element = document.createElement("div");
987
1065
  element.className = "ofv-toolbar";
988
1066
  element.setAttribute("role", "toolbar");
989
- element.setAttribute("aria-label", "File preview toolbar");
1067
+ element.setAttribute("aria-label", defaultToolbarText[locale].ariaLabel);
990
1068
  let file;
991
1069
  let currentIndex = 0;
992
1070
  let currentLength = queue.getLength();
@@ -994,6 +1072,7 @@ function createToolbar(toolbar, viewport, queue) {
994
1072
  let previousButton;
995
1073
  let nextButton;
996
1074
  let zoomResetButton;
1075
+ let fullscreenButton;
997
1076
  let currentZoom;
998
1077
  const commandButtons = [];
999
1078
  const customButtons = [];
@@ -1046,8 +1125,8 @@ function createToolbar(toolbar, viewport, queue) {
1046
1125
  }
1047
1126
  if (id === "previous" && queue.getLength() > 1) {
1048
1127
  previousButton = addButton(
1049
- getToolbarLabel(options, "previous"),
1050
- getToolbarTitle(options, "previous"),
1128
+ getToolbarLabel(options, locale, "previous"),
1129
+ getToolbarTitle(options, locale, "previous"),
1051
1130
  () => void queue.previous(),
1052
1131
  void 0,
1053
1132
  options.icons?.previous
@@ -1056,8 +1135,8 @@ function createToolbar(toolbar, viewport, queue) {
1056
1135
  }
1057
1136
  if (id === "next" && queue.getLength() > 1) {
1058
1137
  nextButton = addButton(
1059
- getToolbarLabel(options, "next"),
1060
- getToolbarTitle(options, "next"),
1138
+ getToolbarLabel(options, locale, "next"),
1139
+ getToolbarTitle(options, locale, "next"),
1061
1140
  () => void queue.next(),
1062
1141
  void 0,
1063
1142
  options.icons?.next
@@ -1071,27 +1150,27 @@ function createToolbar(toolbar, viewport, queue) {
1071
1150
  return;
1072
1151
  }
1073
1152
  if (id === "zoom-out" && options.zoom) {
1074
- addCommandButton(id, getToolbarLabel(options, id), getToolbarTitle(options, id), "zoom-out");
1153
+ addCommandButton(id, getToolbarLabel(options, locale, id), getToolbarTitle(options, locale, id), "zoom-out");
1075
1154
  return;
1076
1155
  }
1077
1156
  if (id === "zoom-in" && options.zoom) {
1078
- addCommandButton(id, getToolbarLabel(options, id), getToolbarTitle(options, id), "zoom-in");
1157
+ addCommandButton(id, getToolbarLabel(options, locale, id), getToolbarTitle(options, locale, id), "zoom-in");
1079
1158
  return;
1080
1159
  }
1081
1160
  if (id === "zoom-reset" && options.zoom) {
1082
- addCommandButton(id, getToolbarLabel(options, id), getToolbarTitle(options, id), "zoom-reset");
1161
+ addCommandButton(id, getToolbarLabel(options, locale, id), getToolbarTitle(options, locale, id), "zoom-reset");
1083
1162
  zoomResetButton = commandButtons[commandButtons.length - 1]?.button;
1084
1163
  updateZoomLabel();
1085
1164
  return;
1086
1165
  }
1087
1166
  if (id === "rotate-right" && options.rotate) {
1088
- addCommandButton(id, getToolbarLabel(options, id), getToolbarTitle(options, id), "rotate-right");
1167
+ addCommandButton(id, getToolbarLabel(options, locale, id), getToolbarTitle(options, locale, id), "rotate-right");
1089
1168
  return;
1090
1169
  }
1091
1170
  if (id === "download" && options.download !== false) {
1092
1171
  addButton(
1093
- getToolbarLabel(options, id),
1094
- getToolbarTitle(options, id),
1172
+ getToolbarLabel(options, locale, id),
1173
+ getToolbarTitle(options, locale, id),
1095
1174
  () => getContext().download(),
1096
1175
  void 0,
1097
1176
  options.icons?.download
@@ -1099,19 +1178,20 @@ function createToolbar(toolbar, viewport, queue) {
1099
1178
  return;
1100
1179
  }
1101
1180
  if (id === "fullscreen" && options.fullscreen !== false) {
1102
- addButton(
1103
- getToolbarLabel(options, id),
1104
- getToolbarTitle(options, id),
1181
+ fullscreenButton = addButton(
1182
+ getToolbarLabel(options, locale, id),
1183
+ getToolbarTitle(options, locale, id),
1105
1184
  () => getContext().fullscreen(),
1106
1185
  void 0,
1107
1186
  options.icons?.fullscreen
1108
1187
  );
1188
+ updateFullscreenButton();
1109
1189
  return;
1110
1190
  }
1111
1191
  if (id === "print" && options.print) {
1112
1192
  addButton(
1113
- getToolbarLabel(options, id),
1114
- getToolbarTitle(options, id),
1193
+ getToolbarLabel(options, locale, id),
1194
+ getToolbarTitle(options, locale, id),
1115
1195
  () => getContext().print(),
1116
1196
  void 0,
1117
1197
  options.icons?.print
@@ -1137,11 +1217,11 @@ function createToolbar(toolbar, viewport, queue) {
1137
1217
  const renderSearchControl = () => {
1138
1218
  const searchGroup = document.createElement("div");
1139
1219
  searchGroup.className = "ofv-toolbar-search";
1140
- searchGroup.title = getToolbarTitle(options, "search");
1220
+ searchGroup.title = getToolbarTitle(options, locale, "search");
1141
1221
  const nextSearchInput = document.createElement("input");
1142
1222
  nextSearchInput.type = "search";
1143
- nextSearchInput.placeholder = getToolbarLabel(options, "search");
1144
- nextSearchInput.setAttribute("aria-label", getToolbarTitle(options, "search"));
1223
+ nextSearchInput.placeholder = getToolbarLabel(options, locale, "search");
1224
+ nextSearchInput.setAttribute("aria-label", getToolbarTitle(options, locale, "search"));
1145
1225
  const nextSearchCount = document.createElement("span");
1146
1226
  nextSearchCount.className = "ofv-toolbar-search-count";
1147
1227
  searchInput = nextSearchInput;
@@ -1196,7 +1276,7 @@ function createToolbar(toolbar, viewport, queue) {
1196
1276
  }
1197
1277
  setToolbarButtonContent(
1198
1278
  zoomResetButton,
1199
- currentZoom === void 0 ? getToolbarLabel(options, "zoom-reset") : formatToolbarZoom(currentZoom),
1279
+ currentZoom === void 0 ? getToolbarLabel(options, locale, "zoom-reset") : formatToolbarZoom(currentZoom),
1200
1280
  options.icons?.["zoom-reset"]
1201
1281
  );
1202
1282
  }
@@ -1207,6 +1287,32 @@ function createToolbar(toolbar, viewport, queue) {
1207
1287
  updateCustomButtons();
1208
1288
  refreshCustomRender();
1209
1289
  }
1290
+ function isFullscreenActive() {
1291
+ return Boolean(
1292
+ typeof document !== "undefined" && element.parentElement && document.fullscreenElement === element.parentElement
1293
+ );
1294
+ }
1295
+ function updateFullscreenButton() {
1296
+ if (!fullscreenButton) {
1297
+ return;
1298
+ }
1299
+ const active = isFullscreenActive();
1300
+ const id = active ? "exit-fullscreen" : "fullscreen";
1301
+ const icon = active ? options.icons?.["exit-fullscreen"] ?? options.icons?.fullscreen : options.icons?.fullscreen;
1302
+ setToolbarButtonContent(fullscreenButton, getToolbarLabel(options, locale, id), icon);
1303
+ const title = getToolbarTitle(options, locale, id);
1304
+ fullscreenButton.title = title;
1305
+ fullscreenButton.setAttribute("aria-label", title);
1306
+ fullscreenButton.setAttribute("aria-pressed", String(active));
1307
+ }
1308
+ const handleFullscreenChange = () => {
1309
+ updateFullscreenButton();
1310
+ refreshCustomRender();
1311
+ };
1312
+ if (typeof document !== "undefined") {
1313
+ document.addEventListener("fullscreenchange", handleFullscreenChange);
1314
+ disposers.push(() => document.removeEventListener("fullscreenchange", handleFullscreenChange));
1315
+ }
1210
1316
  const refreshCustomRender = () => {
1211
1317
  if (!options.render) {
1212
1318
  return;
@@ -1280,6 +1386,9 @@ function createToolbarContext({
1280
1386
  viewport,
1281
1387
  canPrevious: index > 0,
1282
1388
  canNext: index < length - 1,
1389
+ isFullscreen: Boolean(
1390
+ typeof document !== "undefined" && element.parentElement && document.fullscreenElement === element.parentElement
1391
+ ),
1283
1392
  zoom,
1284
1393
  zoomLabel: zoom === void 0 ? void 0 : formatToolbarZoom(zoom),
1285
1394
  async previous() {
@@ -1298,7 +1407,15 @@ function createToolbarContext({
1298
1407
  }
1299
1408
  },
1300
1409
  fullscreen() {
1301
- void element.parentElement?.requestFullscreen?.();
1410
+ const target = element.parentElement;
1411
+ if (!target) {
1412
+ return;
1413
+ }
1414
+ if (typeof document !== "undefined" && document.fullscreenElement === target) {
1415
+ void document.exitFullscreen?.();
1416
+ } else {
1417
+ void target.requestFullscreen?.();
1418
+ }
1302
1419
  },
1303
1420
  print() {
1304
1421
  printPreview(viewport);
@@ -1307,37 +1424,75 @@ function createToolbarContext({
1307
1424
  clearSearch: search.clear
1308
1425
  };
1309
1426
  }
1310
- var defaultToolbarLabels = {
1311
- previous: "Prev",
1312
- next: "Next",
1313
- queue: "",
1314
- "zoom-out": "-",
1315
- "zoom-in": "+",
1316
- "zoom-reset": "100%",
1317
- "rotate-right": "Rotate",
1318
- download: "Download",
1319
- fullscreen: "Fullscreen",
1320
- print: "Print",
1321
- search: "Search"
1322
- };
1323
- var defaultToolbarTitles = {
1324
- previous: "Previous file",
1325
- next: "Next file",
1326
- queue: "Current file position",
1327
- "zoom-out": "Zoom out",
1328
- "zoom-in": "Zoom in",
1329
- "zoom-reset": "Reset zoom",
1330
- "rotate-right": "Rotate right",
1331
- download: "Download file",
1332
- fullscreen: "Open preview fullscreen",
1333
- print: "Print preview",
1334
- search: "Search preview text"
1427
+ var defaultToolbarText = {
1428
+ "zh-CN": {
1429
+ ariaLabel: "\u6587\u4EF6\u9884\u89C8\u5DE5\u5177\u680F",
1430
+ labels: {
1431
+ previous: "\u4E0A\u4E00\u4E2A",
1432
+ next: "\u4E0B\u4E00\u4E2A",
1433
+ queue: "",
1434
+ "zoom-out": "-",
1435
+ "zoom-in": "+",
1436
+ "zoom-reset": "100%",
1437
+ "rotate-right": "\u65CB\u8F6C",
1438
+ download: "\u4E0B\u8F7D",
1439
+ fullscreen: "\u5168\u5C4F",
1440
+ "exit-fullscreen": "\u9000\u51FA\u5168\u5C4F",
1441
+ print: "\u6253\u5370",
1442
+ search: "\u641C\u7D22"
1443
+ },
1444
+ titles: {
1445
+ previous: "\u4E0A\u4E00\u4E2A\u6587\u4EF6",
1446
+ next: "\u4E0B\u4E00\u4E2A\u6587\u4EF6",
1447
+ queue: "\u5F53\u524D\u6587\u4EF6\u4F4D\u7F6E",
1448
+ "zoom-out": "\u7F29\u5C0F",
1449
+ "zoom-in": "\u653E\u5927",
1450
+ "zoom-reset": "\u91CD\u7F6E\u7F29\u653E",
1451
+ "rotate-right": "\u5411\u53F3\u65CB\u8F6C",
1452
+ download: "\u4E0B\u8F7D\u6587\u4EF6",
1453
+ fullscreen: "\u5168\u5C4F\u67E5\u770B\u9884\u89C8",
1454
+ "exit-fullscreen": "\u9000\u51FA\u5168\u5C4F",
1455
+ print: "\u6253\u5370\u9884\u89C8",
1456
+ search: "\u641C\u7D22\u9884\u89C8\u6587\u672C"
1457
+ }
1458
+ },
1459
+ "en-US": {
1460
+ ariaLabel: "File preview toolbar",
1461
+ labels: {
1462
+ previous: "Prev",
1463
+ next: "Next",
1464
+ queue: "",
1465
+ "zoom-out": "-",
1466
+ "zoom-in": "+",
1467
+ "zoom-reset": "100%",
1468
+ "rotate-right": "Rotate",
1469
+ download: "Download",
1470
+ fullscreen: "Fullscreen",
1471
+ "exit-fullscreen": "Exit fullscreen",
1472
+ print: "Print",
1473
+ search: "Search"
1474
+ },
1475
+ titles: {
1476
+ previous: "Previous file",
1477
+ next: "Next file",
1478
+ queue: "Current file position",
1479
+ "zoom-out": "Zoom out",
1480
+ "zoom-in": "Zoom in",
1481
+ "zoom-reset": "Reset zoom",
1482
+ "rotate-right": "Rotate right",
1483
+ download: "Download file",
1484
+ fullscreen: "Open preview fullscreen",
1485
+ "exit-fullscreen": "Exit fullscreen",
1486
+ print: "Print preview",
1487
+ search: "Search preview text"
1488
+ }
1489
+ }
1335
1490
  };
1336
- function getToolbarLabel(options, id) {
1337
- return options.labels?.[id] ?? defaultToolbarLabels[id];
1491
+ function getToolbarLabel(options, locale, id) {
1492
+ return options.labels?.[id] ?? defaultToolbarText[locale].labels[id];
1338
1493
  }
1339
- function getToolbarTitle(options, id) {
1340
- return options.titles?.[id] ?? options.labels?.[id] ?? defaultToolbarTitles[id];
1494
+ function getToolbarTitle(options, locale, id) {
1495
+ return options.titles?.[id] ?? options.labels?.[id] ?? defaultToolbarText[locale].titles[id];
1341
1496
  }
1342
1497
  function formatToolbarZoom(zoom) {
1343
1498
  return `${Math.round(zoom * 100)}%`;
@@ -1489,7 +1644,7 @@ function isSafeToolbarIconAttribute(name, value) {
1489
1644
  return !/^\s*(?:javascript|data:text\/html|vbscript):/i.test(value);
1490
1645
  }
1491
1646
  function isBuiltInToolbarAction(id) {
1492
- return id in defaultToolbarLabels;
1647
+ return id in defaultToolbarText["en-US"].labels;
1493
1648
  }
1494
1649
  function createSearchController(root) {
1495
1650
  const markerClass = "ofv-search-match";
@@ -1568,6 +1723,9 @@ function collectSearchableTextNodes(root) {
1568
1723
  if (["SCRIPT", "STYLE", "TEXTAREA", "INPUT", "BUTTON"].includes(parent.tagName)) {
1569
1724
  return NodeFilter.FILTER_REJECT;
1570
1725
  }
1726
+ if (hasHiddenSearchAncestor(parent, root)) {
1727
+ return NodeFilter.FILTER_REJECT;
1728
+ }
1571
1729
  return NodeFilter.FILTER_ACCEPT;
1572
1730
  }
1573
1731
  });
@@ -1578,6 +1736,19 @@ function collectSearchableTextNodes(root) {
1578
1736
  }
1579
1737
  return nodes;
1580
1738
  }
1739
+ function hasHiddenSearchAncestor(element, root) {
1740
+ let current = element;
1741
+ while (current) {
1742
+ if (current.hidden || current.getAttribute("aria-hidden") === "true" || current.style.display === "none" || current.style.visibility === "hidden") {
1743
+ return true;
1744
+ }
1745
+ if (current === root) {
1746
+ break;
1747
+ }
1748
+ current = current.parentElement;
1749
+ }
1750
+ return false;
1751
+ }
1581
1752
  function printPreview(viewport) {
1582
1753
  const frame = document.createElement("iframe");
1583
1754
  frame.className = "ofv-print-frame";
@@ -4075,7 +4246,7 @@ function textPlugin() {
4075
4246
  const isMarkdown = lang === "markdown";
4076
4247
  const text = await readText(ctx.file.source).catch((error) => void 0);
4077
4248
  if (text === void 0) {
4078
- const fallback = createTextFallback(ctx.file.name, ctx.file.url);
4249
+ const fallback = createTextFallback(ctx.file.name, ctx.options.messages, ctx.file.url);
4079
4250
  ctx.viewport.classList.add("ofv-center");
4080
4251
  ctx.viewport.append(fallback);
4081
4252
  return {
@@ -4247,10 +4418,11 @@ function textPlugin() {
4247
4418
  title.className = "ofv-code-title";
4248
4419
  const fileName = document.createElement("strong");
4249
4420
  fileName.textContent = ctx.file.name;
4421
+ const messages = ctx.options.messages;
4250
4422
  const meta = document.createElement("span");
4251
4423
  meta.textContent = [
4252
- lang === "none" ? "plain text" : lang,
4253
- `${totalLines.toLocaleString()} lines`,
4424
+ lang === "none" ? messages.textPlainLanguage : lang,
4425
+ formatPreviewMessage(messages.textLineCount, { count: totalLines.toLocaleString() }),
4254
4426
  formatBytes(ctx.file.size ?? (ctx.file.source instanceof Blob ? ctx.file.source.size : text.length))
4255
4427
  ].join(" \xB7 ");
4256
4428
  title.append(fileName, meta);
@@ -4262,7 +4434,7 @@ function textPlugin() {
4262
4434
  const wrapButton = document.createElement("button");
4263
4435
  wrapButton.type = "button";
4264
4436
  wrapButton.className = "ofv-code-action";
4265
- wrapButton.textContent = "Wrap";
4437
+ wrapButton.textContent = messages.textWrap;
4266
4438
  wrapButton.setAttribute("aria-pressed", String(defaultWrapped));
4267
4439
  wrapButton.addEventListener("click", () => {
4268
4440
  const wrapped = wrapper.classList.toggle("is-wrapped");
@@ -4271,14 +4443,14 @@ function textPlugin() {
4271
4443
  const copyButton = document.createElement("button");
4272
4444
  copyButton.type = "button";
4273
4445
  copyButton.className = "ofv-code-action";
4274
- copyButton.textContent = "Copy";
4446
+ copyButton.textContent = messages.textCopy;
4275
4447
  copyButton.addEventListener("click", async () => {
4276
4448
  copyButton.disabled = true;
4277
4449
  try {
4278
4450
  await copyToClipboard(text);
4279
- status.textContent = "Copied";
4451
+ status.textContent = messages.textCopied;
4280
4452
  } catch {
4281
- status.textContent = "Copy failed";
4453
+ status.textContent = messages.textCopyFailed;
4282
4454
  } finally {
4283
4455
  copyButton.disabled = false;
4284
4456
  }
@@ -4286,10 +4458,10 @@ function textPlugin() {
4286
4458
  const downloadButton = document.createElement("button");
4287
4459
  downloadButton.type = "button";
4288
4460
  downloadButton.className = "ofv-code-action";
4289
- downloadButton.textContent = "Download";
4461
+ downloadButton.textContent = messages.textDownload;
4290
4462
  downloadButton.addEventListener("click", () => {
4291
4463
  downloadText(ctx.file.name, text);
4292
- status.textContent = "Download ready";
4464
+ status.textContent = messages.textDownloadReady;
4293
4465
  });
4294
4466
  actions.append(wrapButton, copyButton, downloadButton, status);
4295
4467
  header.append(title, actions);
@@ -4314,13 +4486,13 @@ function textPlugin() {
4314
4486
  if (truncated) {
4315
4487
  const notice = document.createElement("div");
4316
4488
  notice.className = "ofv-code-notice";
4317
- notice.textContent = `\u6587\u4EF6\u8F83\u5927\uFF0C\u5F53\u524D\u5C55\u793A\u524D ${formatBytes(codeText.length)}\uFF0C\u590D\u5236\u548C\u4E0B\u8F7D\u4ECD\u4F1A\u4F7F\u7528\u5B8C\u6574\u5185\u5BB9\u3002`;
4489
+ notice.textContent = formatPreviewMessage(messages.textLargeFileNotice, { size: formatBytes(codeText.length) });
4318
4490
  wrapper.append(notice);
4319
4491
  }
4320
4492
  if (!shouldHighlight) {
4321
4493
  const notice = document.createElement("div");
4322
4494
  notice.className = "ofv-code-notice";
4323
- notice.textContent = "\u5185\u5BB9\u8F83\u5927\uFF0C\u5DF2\u8DF3\u8FC7\u8BED\u6CD5\u9AD8\u4EAE\u4EE5\u4FDD\u6301\u6EDA\u52A8\u6D41\u7545\u3002";
4495
+ notice.textContent = messages.textHighlightSkipped;
4324
4496
  wrapper.append(notice);
4325
4497
  }
4326
4498
  wrapper.appendChild(body);
@@ -4387,19 +4559,19 @@ function normalizeFileName2(name) {
4387
4559
  const baseName = name.split(/[\\/]/).pop() || name;
4388
4560
  return baseName.toLowerCase();
4389
4561
  }
4390
- function createTextFallback(fileName, url) {
4562
+ function createTextFallback(fileName, messages, url) {
4391
4563
  const fallback = document.createElement("div");
4392
4564
  fallback.className = "ofv-fallback";
4393
4565
  const title = document.createElement("strong");
4394
- title.textContent = "\u6587\u672C\u9884\u89C8\u5931\u8D25";
4566
+ title.textContent = messages.textPreviewFailedTitle;
4395
4567
  const meta = document.createElement("span");
4396
- meta.textContent = "\u65E0\u6CD5\u8BFB\u53D6\u8BE5\u6587\u672C\u5185\u5BB9\uFF0C\u53EF\u80FD\u662F\u8FDC\u7A0B\u6587\u4EF6\u4E0D\u53EF\u8BBF\u95EE\u6216\u54CD\u5E94\u72B6\u6001\u5F02\u5E38\u3002";
4568
+ meta.textContent = messages.textPreviewFailedMessage;
4397
4569
  fallback.append(title, meta);
4398
4570
  if (url) {
4399
4571
  const download = document.createElement("a");
4400
4572
  download.href = url;
4401
4573
  download.download = fileName;
4402
- download.textContent = "\u6253\u5F00\u539F\u6587\u4EF6";
4574
+ download.textContent = messages.textOpenOriginal;
4403
4575
  fallback.append(download);
4404
4576
  }
4405
4577
  return fallback;
@@ -7461,7 +7633,7 @@ function officePlugin(options = {}) {
7461
7633
  } else if (packageFormat === "docx" && !fileIsDocx(extension)) {
7462
7634
  disposeDocxFit = await renderDocx(panel, arrayBuffer, ctx.options.fit);
7463
7635
  } else if (packageFormat === "xlsx" && !sheetExtensions.has(extension)) {
7464
- await renderSheet(panel, arrayBuffer, "xlsx");
7636
+ await renderSheet(panel, arrayBuffer, "xlsx", ctx.options.messages);
7465
7637
  } else if (packageFormat === "pptx" && !["pptx", "pptm", "ppsx", "ppsm", "potx", "potm"].includes(extension)) {
7466
7638
  await renderPptx(panel, arrayBuffer);
7467
7639
  } else if (fileIsDocx(extension)) {
@@ -7474,9 +7646,9 @@ function officePlugin(options = {}) {
7474
7646
  renderOpenDocumentXml(panel, "FODT \u6587\u6863", await readTextFromBuffer(arrayBuffer));
7475
7647
  } else if (extension === "fods") {
7476
7648
  renderFlatOds(panel, await readTextFromBuffer(arrayBuffer));
7477
- } else if (packagedOfficeCandidates.has(extension) && await renderPackagedOfficePreview(panel, arrayBuffer, extension, ctx.options.fit)) {
7649
+ } else if (packagedOfficeCandidates.has(extension) && await renderPackagedOfficePreview(panel, arrayBuffer, extension, ctx.options.fit, ctx.options.messages)) {
7478
7650
  } else if (sheetExtensions.has(extension)) {
7479
- await renderSheet(panel, arrayBuffer, extension);
7651
+ await renderSheet(panel, arrayBuffer, extension, ctx.options.messages);
7480
7652
  } else if (["pptx", "pptm", "ppsx", "ppsm", "potx", "potm"].includes(extension)) {
7481
7653
  await renderPptx(panel, arrayBuffer);
7482
7654
  } else if (extension === "odp") {
@@ -7484,11 +7656,11 @@ function officePlugin(options = {}) {
7484
7656
  } else if (extension === "fodp") {
7485
7657
  renderOpenDocumentPresentationXml(panel, await readTextFromBuffer(arrayBuffer));
7486
7658
  } else if (extension === "doc" || extension === "dot") {
7487
- renderLegacyWordBinary(panel, extension, arrayBuffer);
7659
+ renderLegacyWordBinary(panel, extension, arrayBuffer, ctx.options.messages);
7488
7660
  } else if (isLegacyOfficeBinary(extension)) {
7489
- renderLegacyOfficeBinary(panel, extension, arrayBuffer);
7661
+ renderLegacyOfficeBinary(panel, extension, arrayBuffer, ctx.options.messages);
7490
7662
  } else {
7491
- renderUnsupportedOffice(panel, extension || ctx.file.extension || "office");
7663
+ renderUnsupportedOffice(panel, extension || ctx.file.extension || "office", ctx.options.messages);
7492
7664
  }
7493
7665
  const controller = createOfficeZoomController(panel, ctx);
7494
7666
  ctx.toolbar?.refreshCommandSupport();
@@ -8833,7 +9005,7 @@ function renderPlainDocument(panel, title, text) {
8833
9005
  section.append(pre);
8834
9006
  panel.append(section);
8835
9007
  }
8836
- async function renderSheet(panel, arrayBuffer, extension) {
9008
+ async function renderSheet(panel, arrayBuffer, extension, messages) {
8837
9009
  const xlsx = await import("xlsx");
8838
9010
  let workbook;
8839
9011
  try {
@@ -8846,10 +9018,16 @@ async function renderSheet(panel, arrayBuffer, extension) {
8846
9018
  }) : xlsx.read(arrayBuffer, { type: "array", cellDates: true, cellNF: true, cellStyles: true });
8847
9019
  } catch (error) {
8848
9020
  if (isLegacyOfficeBinary(extension)) {
8849
- renderLegacyOfficeBinary(panel, extension, arrayBuffer, `\u8868\u683C\u89E3\u6790\u5931\u8D25\uFF1A${normalizeOfficeError(error)}`);
9021
+ renderLegacyOfficeBinary(
9022
+ panel,
9023
+ extension,
9024
+ arrayBuffer,
9025
+ messages,
9026
+ formatPreviewMessage(messages.officeSheetParseFailed, { message: normalizeOfficeError(error, messages) })
9027
+ );
8850
9028
  return;
8851
9029
  }
8852
- renderSheetFallback(panel, extension, normalizeOfficeError(error));
9030
+ renderSheetFallback(panel, extension, normalizeOfficeError(error, messages));
8853
9031
  return;
8854
9032
  }
8855
9033
  const chartPreviews = await readWorkbookCharts(arrayBuffer).catch(() => []);
@@ -9330,6 +9508,9 @@ function renderChartCard(chart) {
9330
9508
  function renderChartSvg(chart) {
9331
9509
  const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
9332
9510
  svg.setAttribute("viewBox", "0 0 640 380");
9511
+ svg.setAttribute("preserveAspectRatio", "xMidYMid meet");
9512
+ svg.setAttribute("text-rendering", "geometricPrecision");
9513
+ svg.setAttribute("shape-rendering", "geometricPrecision");
9333
9514
  svg.setAttribute("role", "img");
9334
9515
  svg.setAttribute("aria-label", chart.title);
9335
9516
  svg.classList.add("ofv-chart-svg");
@@ -9375,16 +9556,7 @@ function renderChartSvg(chart) {
9375
9556
  const clusterWidth = groupWidth * 0.58;
9376
9557
  const barWidth = Math.max(5, Math.min(28, clusterWidth / Math.max(1, chart.series.length)));
9377
9558
  const zeroY = plot.y + plot.height - (0 - axisMin) / (axisMax - axisMin || 1) * plot.height;
9378
- categories.slice(0, categoryCount).forEach((category, index) => {
9379
- const x = plot.x + groupWidth * (index + 0.5);
9380
- const label = appendSvg(svg, "text", {
9381
- x: Number(x.toFixed(1)),
9382
- y: plot.y + plot.height + 22,
9383
- class: "ofv-chart-label",
9384
- "text-anchor": "middle"
9385
- });
9386
- label.textContent = truncateChartLabel(category);
9387
- });
9559
+ appendChartCategoryLabels(svg, categories, plot, (index) => plot.x + groupWidth * (index + 0.5));
9388
9560
  chart.series.forEach((series, seriesIndex) => {
9389
9561
  const color = series.color || colors[seriesIndex % colors.length];
9390
9562
  series.values.forEach((value, index) => {
@@ -9402,6 +9574,8 @@ function renderChartSvg(chart) {
9402
9574
  });
9403
9575
  });
9404
9576
  } else {
9577
+ const categoryStep = categories.length > 1 ? plot.width / (categories.length - 1) : plot.width;
9578
+ appendChartCategoryLabels(svg, categories, plot, (index) => plot.x + index * categoryStep);
9405
9579
  chart.series.forEach((series, seriesIndex) => {
9406
9580
  const color = series.color || colors[seriesIndex % colors.length];
9407
9581
  const step = series.values.length > 1 ? plot.width / (series.values.length - 1) : plot.width;
@@ -9426,6 +9600,22 @@ function renderChartSvg(chart) {
9426
9600
  appendChartLegend(svg, chart, colors, 348);
9427
9601
  return svg;
9428
9602
  }
9603
+ function appendChartCategoryLabels(svg, categories, plot, getX) {
9604
+ const interval = Math.max(1, Math.ceil(categories.length / 14));
9605
+ categories.forEach((category, index) => {
9606
+ if (index % interval !== 0 && index !== categories.length - 1) {
9607
+ return;
9608
+ }
9609
+ const label = appendSvg(svg, "text", {
9610
+ x: Number(getX(index).toFixed(1)),
9611
+ y: plot.y + plot.height + 22,
9612
+ class: "ofv-chart-label ofv-chart-category-label",
9613
+ "data-axis": "category",
9614
+ "text-anchor": "middle"
9615
+ });
9616
+ label.textContent = truncateChartLabel(category);
9617
+ });
9618
+ }
9429
9619
  function appendChartLegend(svg, chart, colors, y) {
9430
9620
  const itemWidth = 86;
9431
9621
  const startX = 320 - chart.series.length * itemWidth / 2;
@@ -10110,10 +10300,15 @@ function pptxRenderTimeoutMs() {
10110
10300
  function normalizePptxLayout(container) {
10111
10301
  const slideCanvases = findPptxSlideCanvases(container);
10112
10302
  for (const slide of slideCanvases) {
10113
- slide.style.backgroundColor = "#FFFFFF";
10303
+ if (!hasInlineBackground(slide)) {
10304
+ slide.style.backgroundColor = "#FFFFFF";
10305
+ }
10114
10306
  }
10115
10307
  normalizePptxMirroredText(container);
10116
10308
  }
10309
+ function hasInlineBackground(element) {
10310
+ return Boolean(element.style.background || element.style.backgroundColor || element.style.backgroundImage);
10311
+ }
10117
10312
  function normalizePptxMirroredText(container) {
10118
10313
  const mirroredContainers = Array.from(container.querySelectorAll("div")).filter((element) => {
10119
10314
  const text = element.textContent?.trim();
@@ -10205,7 +10400,7 @@ function renderOpenDocumentPresentationXml(panel, xml) {
10205
10400
  renderPresentationInsight(panel, inspectOpenDocumentPresentation("FODP \u6F14\u793A\u6587\u7A3F", xml, 0));
10206
10401
  renderOpenDocumentPresentation(panel, "FODP \u6F14\u793A\u6587\u7A3F", xml, []);
10207
10402
  }
10208
- async function renderPackagedOfficePreview(panel, arrayBuffer, extension, fit) {
10403
+ async function renderPackagedOfficePreview(panel, arrayBuffer, extension, fit, messages) {
10209
10404
  let zip;
10210
10405
  try {
10211
10406
  zip = await import_jszip3.default.loadAsync(arrayBuffer);
@@ -10220,7 +10415,7 @@ async function renderPackagedOfficePreview(panel, arrayBuffer, extension, fit) {
10220
10415
  return true;
10221
10416
  }
10222
10417
  if (hasEntry("xl/workbook.xml")) {
10223
- await renderSheet(panel, arrayBuffer, extension);
10418
+ await renderSheet(panel, arrayBuffer, extension, messages);
10224
10419
  return true;
10225
10420
  }
10226
10421
  if (hasEntry("ppt/presentation.xml")) {
@@ -10651,41 +10846,50 @@ function legacyOfficeFormatLabel(extension) {
10651
10846
  }
10652
10847
  return "PowerPoint Binary File Format";
10653
10848
  }
10654
- function renderLegacyWordBinary(panel, extension, arrayBuffer) {
10849
+ function renderLegacyWordBinary(panel, extension, arrayBuffer, messages) {
10655
10850
  try {
10656
10851
  renderLegacyWordDocument(panel, parseLegacyWordDocument(arrayBuffer));
10657
10852
  } catch (error) {
10658
- renderLegacyOfficeBinary(panel, extension, arrayBuffer, `Word \u4E8C\u8FDB\u5236\u89E3\u6790\u5931\u8D25\uFF1A${normalizeOfficeError(error)}`);
10853
+ renderLegacyOfficeBinary(
10854
+ panel,
10855
+ extension,
10856
+ arrayBuffer,
10857
+ messages,
10858
+ formatPreviewMessage(messages.officeLegacyWordParseFailed, { message: normalizeOfficeError(error, messages) })
10859
+ );
10659
10860
  }
10660
10861
  }
10661
- function renderLegacyOfficeBinary(panel, extension, arrayBuffer, parseError) {
10862
+ function renderLegacyOfficeBinary(panel, extension, arrayBuffer, messages, parseError) {
10662
10863
  const fragments = extractLegacyOfficeText(arrayBuffer);
10663
10864
  panel.replaceChildren();
10664
- const section = createSection("Office \u8F6C\u6362\u63D0\u793A");
10865
+ const section = createSection(messages.officeLegacyConversionTitle);
10665
10866
  section.classList.add("ofv-office-conversion");
10666
10867
  const format = document.createElement("p");
10667
10868
  const strong = document.createElement("strong");
10668
10869
  strong.textContent = `.${extension}`;
10669
- format.append(
10670
- strong,
10671
- document.createTextNode(
10672
- " \u5C5E\u4E8E\u65E7\u7248 Microsoft Office \u4E8C\u8FDB\u5236\u683C\u5F0F\uFF0C\u6D4F\u89C8\u5668\u5185\u65E0\u6CD5\u9AD8\u4FDD\u771F\u89E3\u6790\uFF1B\u5F53\u524D\u4EC5\u5C55\u793A\u53EF\u4FE1\u6587\u672C\u7247\u6BB5\u548C\u7ED3\u6784\u6307\u7EB9\uFF0C\u5B8C\u6574\u6392\u7248\u5EFA\u8BAE\u63A5\u5165 LibreOffice/OnlyOffice \u670D\u52A1\u7AEF\u8F6C\u6362\u4E3A PDF/HTML\u3002"
10673
- )
10674
- );
10870
+ format.append(strong, document.createTextNode(" "), document.createTextNode(messages.officeLegacyBinaryNotice));
10675
10871
  const meta = document.createElement("dl");
10676
10872
  meta.className = "ofv-office-binary-meta";
10677
- appendOfficeBinaryMeta(meta, "\u683C\u5F0F\u7C7B\u578B", legacyOfficeFormatLabel(extension));
10678
- appendOfficeBinaryMeta(meta, "\u6587\u4EF6\u7ED3\u6784", hasOleSignature(arrayBuffer) ? "\u68C0\u6D4B\u5230 OLE Compound File \u7B7E\u540D" : "\u672A\u68C0\u6D4B\u5230\u6807\u51C6 OLE \u7B7E\u540D\uFF0C\u6309\u539F\u59CB\u4E8C\u8FDB\u5236\u5C1D\u8BD5\u63D0\u53D6");
10679
- appendOfficeBinaryMeta(meta, "\u6587\u672C\u7247\u6BB5", `${fragments.length} \u6BB5`);
10873
+ appendOfficeBinaryMeta(meta, messages.officeLegacyMetaFormatType, legacyOfficeFormatLabel(extension));
10874
+ appendOfficeBinaryMeta(
10875
+ meta,
10876
+ messages.officeLegacyMetaFileStructure,
10877
+ hasOleSignature(arrayBuffer) ? messages.officeLegacyOleDetected : messages.officeLegacyOleMissing
10878
+ );
10879
+ appendOfficeBinaryMeta(
10880
+ meta,
10881
+ messages.officeLegacyMetaTextFragments,
10882
+ formatPreviewMessage(messages.officeLegacyTextFragmentCount, { count: fragments.length.toLocaleString() })
10883
+ );
10680
10884
  if (parseError) {
10681
- appendOfficeBinaryMeta(meta, "\u89E3\u6790\u72B6\u6001", parseError);
10885
+ appendOfficeBinaryMeta(meta, messages.officeLegacyMetaParseStatus, parseError);
10682
10886
  }
10683
10887
  section.append(format, meta);
10684
10888
  if (fragments.length > 0) {
10685
10889
  const article = document.createElement("article");
10686
10890
  article.className = "ofv-document ofv-office-binary-fragments";
10687
10891
  const heading = document.createElement("h4");
10688
- heading.textContent = "\u53EF\u8BFB\u6587\u672C\u7247\u6BB5";
10892
+ heading.textContent = messages.officeLegacyReadableFragments;
10689
10893
  article.append(heading);
10690
10894
  for (const fragment of fragments.slice(0, 80)) {
10691
10895
  const paragraph = document.createElement("p");
@@ -10696,7 +10900,7 @@ function renderLegacyOfficeBinary(panel, extension, arrayBuffer, parseError) {
10696
10900
  } else {
10697
10901
  const empty = document.createElement("p");
10698
10902
  empty.className = "ofv-office-binary-empty";
10699
- empty.textContent = "\u672A\u63D0\u53D6\u5230\u7A33\u5B9A\u53EF\u8BFB\u6587\u672C\u3002\u8BE5\u6587\u4EF6\u53EF\u80FD\u7ECF\u8FC7\u538B\u7F29\u3001\u52A0\u5BC6\uFF0C\u6216\u6587\u672C\u7F16\u7801\u65E0\u6CD5\u5728\u6D4F\u89C8\u5668\u7AEF\u53EF\u9760\u8BC6\u522B\uFF1B\u8BF7\u4F7F\u7528\u670D\u52A1\u7AEF LibreOffice/OnlyOffice \u8F6C\u6362\u540E\u9884\u89C8\u3002";
10903
+ empty.textContent = messages.officeLegacyNoText;
10700
10904
  section.append(empty);
10701
10905
  }
10702
10906
  panel.append(section);
@@ -10708,23 +10912,27 @@ function appendOfficeBinaryMeta(list, label, value) {
10708
10912
  detail.textContent = value;
10709
10913
  list.append(term, detail);
10710
10914
  }
10711
- function renderUnsupportedOffice(panel, extension) {
10915
+ function renderUnsupportedOffice(panel, extension, messages) {
10712
10916
  const legacyBinary = /* @__PURE__ */ new Set(["doc", "dot", "wps", "ppt", "pps", "key", "dps"]);
10713
- const message = legacyBinary.has(extension) ? "\u8BE5\u683C\u5F0F\u5C5E\u4E8E\u8001\u4E8C\u8FDB\u5236\u6216\u4E13\u6709\u683C\u5F0F\uFF0C\u6D4F\u89C8\u5668\u5185\u65E0\u6CD5\u53EF\u9760\u89E3\u6790\uFF1B\u5EFA\u8BAE\u63A5\u5165 LibreOffice/OnlyOffice \u670D\u52A1\u7AEF\u8F6C\u6362\u4E3A PDF/HTML \u540E\u9884\u89C8\u3002" : "\u8BE5\u683C\u5F0F\u901A\u5E38\u9700\u8981\u670D\u52A1\u7AEF\u8F6C\u6362\u6216\u4E13\u7528\u89E3\u6790\u5668\u624D\u80FD\u9AD8\u4FDD\u771F\u9884\u89C8\u3002";
10917
+ const message = legacyBinary.has(extension) ? messages.officeUnsupportedLegacyMessage : messages.officeUnsupportedGenericMessage;
10714
10918
  panel.replaceChildren();
10715
- const section = createSection("Office \u57FA\u7840\u9884\u89C8");
10919
+ const section = createSection(messages.officeUnsupportedTitle);
10716
10920
  const format = document.createElement("p");
10717
10921
  const strong = document.createElement("strong");
10718
10922
  strong.textContent = `.${extension}`;
10719
- format.append(strong, document.createTextNode(` \u5DF2\u8FDB\u5165 Office \u63D2\u4EF6\u3002${message}`));
10923
+ format.append(
10924
+ strong,
10925
+ document.createTextNode(" "),
10926
+ document.createTextNode(formatPreviewMessage(messages.officeUnsupportedIntro, { message }))
10927
+ );
10720
10928
  const support = document.createElement("p");
10721
- support.textContent = "\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";
10929
+ support.textContent = messages.officeUnsupportedSupportedFormats;
10722
10930
  section.append(format, support);
10723
10931
  panel.append(section);
10724
10932
  }
10725
- function normalizeOfficeError(error) {
10933
+ function normalizeOfficeError(error, messages) {
10726
10934
  const message = error instanceof Error ? error.message : String(error || "");
10727
- return message ? `\u89E3\u6790\u5668\u8FD4\u56DE\uFF1A${message}` : "\u89E3\u6790\u5668\u672A\u8FD4\u56DE\u5177\u4F53\u9519\u8BEF\u4FE1\u606F\u3002";
10935
+ return message ? formatPreviewMessage(messages.officeErrorWithMessage, { message }) : messages.officeErrorWithoutMessage;
10728
10936
  }
10729
10937
  function extractLegacyOfficeText(arrayBuffer) {
10730
10938
  const bytes = new Uint8Array(arrayBuffer);
@@ -11966,11 +12174,13 @@ function archivePlugin() {
11966
12174
  sidebarToggle.addEventListener("click", () => {
11967
12175
  setSidebarCollapsed(!layout.classList.contains("is-sidebar-collapsed"));
11968
12176
  });
11969
- const showDefaultSummary = () => {
12177
+ const showDefaultSummary = (isSupplemental = true) => {
11970
12178
  mainPanel.replaceChildren();
11971
12179
  const summary = document.createElement("div");
11972
12180
  summary.className = "ofv-archive-info";
11973
- hideSupplementalInfo4(summary);
12181
+ if (isSupplemental) {
12182
+ hideSupplementalInfo4(summary);
12183
+ }
11974
12184
  const heading = document.createElement("h3");
11975
12185
  heading.textContent = ctx.file.name;
11976
12186
  const info = document.createElement("div");
@@ -11980,12 +12190,16 @@ function archivePlugin() {
11980
12190
  appendArchiveInfo(info, "\u683C\u5F0F\u7C7B\u578B", `.${ext.toUpperCase()} \u538B\u7F29\u6587\u4EF6`);
11981
12191
  appendArchiveInfo(info, "\u5305\u542B\u6587\u4EF6\u6570", `${fileCount} \u4E2A`);
11982
12192
  appendArchiveInfo(info, "\u5305\u542B\u76EE\u5F55\u6570", `${dirCount} \u4E2A`);
11983
- appendArchiveInfo(info, "\u64CD\u4F5C\u63D0\u793A", "\u8BF7\u70B9\u51FB\u5DE6\u4FA7\u680F\u4E2D\u7684\u6587\u4EF6\u8FDB\u884C\u8054\u52A8\u9884\u89C8\u3002");
12193
+ appendArchiveInfo(
12194
+ info,
12195
+ "\u64CD\u4F5C\u63D0\u793A",
12196
+ fileCount === 0 ? "\u538B\u7F29\u5305\u5185\u6CA1\u6709\u53EF\u9884\u89C8\u7684\u6587\u4EF6\u3002" : "\u8BF7\u70B9\u51FB\u5DE6\u4FA7\u680F\u4E2D\u7684\u6587\u4EF6\u8FDB\u884C\u8054\u52A8\u9884\u89C8\u3002"
12197
+ );
11984
12198
  summary.append(heading, info, createArchiveSummary(archiveEntries));
11985
12199
  mainPanel.append(summary);
11986
12200
  };
11987
- showDefaultSummary();
11988
12201
  const visibleEntries = archiveEntries.filter((e) => !e.dir).slice(0, 500);
12202
+ showDefaultSummary(visibleEntries.length > 0);
11989
12203
  let destroyed = false;
11990
12204
  let renderToken = 0;
11991
12205
  const openArchiveEntry = async (entry, item) => {