@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.js CHANGED
@@ -569,7 +569,40 @@ var defaultMessages = {
569
569
  size: "\u5927\u5C0F",
570
570
  source: "\u6765\u6E90",
571
571
  remoteUrl: "\u8FDC\u7A0B URL",
572
- localFile: "\u672C\u5730/\u5185\u5B58\u6587\u4EF6"
572
+ localFile: "\u672C\u5730/\u5185\u5B58\u6587\u4EF6",
573
+ textPlainLanguage: "\u7EAF\u6587\u672C",
574
+ textLineCount: "{count} \u884C",
575
+ textWrap: "\u6362\u884C",
576
+ textCopy: "\u590D\u5236",
577
+ textCopied: "\u5DF2\u590D\u5236",
578
+ textCopyFailed: "\u590D\u5236\u5931\u8D25",
579
+ textDownload: "\u4E0B\u8F7D",
580
+ textDownloadReady: "\u4E0B\u8F7D\u5DF2\u51C6\u5907",
581
+ 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",
582
+ textHighlightSkipped: "\u5185\u5BB9\u8F83\u5927\uFF0C\u5DF2\u8DF3\u8FC7\u8BED\u6CD5\u9AD8\u4EAE\u4EE5\u4FDD\u6301\u6EDA\u52A8\u6D41\u7545\u3002",
583
+ textPreviewFailedTitle: "\u6587\u672C\u9884\u89C8\u5931\u8D25",
584
+ 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",
585
+ textOpenOriginal: "\u6253\u5F00\u539F\u6587\u4EF6",
586
+ officeLegacyConversionTitle: "Office \u8F6C\u6362\u63D0\u793A",
587
+ 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",
588
+ officeLegacyMetaFormatType: "\u683C\u5F0F\u7C7B\u578B",
589
+ officeLegacyMetaFileStructure: "\u6587\u4EF6\u7ED3\u6784",
590
+ officeLegacyOleDetected: "\u68C0\u6D4B\u5230 OLE Compound File \u7B7E\u540D",
591
+ officeLegacyOleMissing: "\u672A\u68C0\u6D4B\u5230\u6807\u51C6 OLE \u7B7E\u540D\uFF0C\u6309\u539F\u59CB\u4E8C\u8FDB\u5236\u5C1D\u8BD5\u63D0\u53D6",
592
+ officeLegacyMetaTextFragments: "\u6587\u672C\u7247\u6BB5",
593
+ officeLegacyTextFragmentCount: "{count} \u6BB5",
594
+ officeLegacyMetaParseStatus: "\u89E3\u6790\u72B6\u6001",
595
+ officeLegacyReadableFragments: "\u53EF\u8BFB\u6587\u672C\u7247\u6BB5",
596
+ 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",
597
+ officeLegacyWordParseFailed: "Word \u4E8C\u8FDB\u5236\u89E3\u6790\u5931\u8D25\uFF1A{message}",
598
+ officeSheetParseFailed: "\u8868\u683C\u89E3\u6790\u5931\u8D25\uFF1A{message}",
599
+ officeUnsupportedTitle: "Office \u57FA\u7840\u9884\u89C8",
600
+ 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",
601
+ 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",
602
+ officeUnsupportedIntro: "\u5DF2\u8FDB\u5165 Office \u63D2\u4EF6\u3002{message}",
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
+ officeErrorWithMessage: "\u89E3\u6790\u5668\u8FD4\u56DE\uFF1A{message}",
605
+ officeErrorWithoutMessage: "\u89E3\u6790\u5668\u672A\u8FD4\u56DE\u5177\u4F53\u9519\u8BEF\u4FE1\u606F\u3002"
573
606
  },
574
607
  "en-US": {
575
608
  loading: "Loading preview...",
@@ -585,7 +618,40 @@ var defaultMessages = {
585
618
  size: "Size",
586
619
  source: "Source",
587
620
  remoteUrl: "Remote URL",
588
- localFile: "Local or in-memory file"
621
+ localFile: "Local or in-memory file",
622
+ textPlainLanguage: "plain text",
623
+ textLineCount: "{count} lines",
624
+ textWrap: "Wrap",
625
+ textCopy: "Copy",
626
+ textCopied: "Copied",
627
+ textCopyFailed: "Copy failed",
628
+ textDownload: "Download",
629
+ textDownloadReady: "Download ready",
630
+ textLargeFileNotice: "Large file, showing the first {size}. Copy and download still use the full content.",
631
+ textHighlightSkipped: "Large content, syntax highlighting was skipped to keep scrolling smooth.",
632
+ textPreviewFailedTitle: "Text preview failed",
633
+ textPreviewFailedMessage: "Unable to read this text content. The remote file may be unreachable or returned an invalid response.",
634
+ textOpenOriginal: "Open original file",
635
+ officeLegacyConversionTitle: "Office conversion guidance",
636
+ 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.",
637
+ officeLegacyMetaFormatType: "Format type",
638
+ officeLegacyMetaFileStructure: "File structure",
639
+ officeLegacyOleDetected: "OLE Compound File signature detected",
640
+ officeLegacyOleMissing: "No standard OLE signature detected; extracting from raw binary data",
641
+ officeLegacyMetaTextFragments: "Text fragments",
642
+ officeLegacyTextFragmentCount: "{count} fragments",
643
+ officeLegacyMetaParseStatus: "Parse status",
644
+ officeLegacyReadableFragments: "Readable text fragments",
645
+ 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.",
646
+ officeLegacyWordParseFailed: "Word binary parse failed: {message}",
647
+ officeSheetParseFailed: "Spreadsheet parse failed: {message}",
648
+ officeUnsupportedTitle: "Office basic preview",
649
+ 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.",
650
+ officeUnsupportedGenericMessage: "This format usually needs server-side conversion or a dedicated parser for high-fidelity preview.",
651
+ officeUnsupportedIntro: "is handled by the Office plugin. {message}",
652
+ officeUnsupportedSupportedFormats: "This version prioritizes basic previews for docx, rtf, odt/fodt, xlsx/xls/csv/ods, pptx/ppsx, and odp/fodp files.",
653
+ officeErrorWithMessage: "Parser returned: {message}",
654
+ officeErrorWithoutMessage: "Parser did not return a specific error."
589
655
  }
590
656
  };
591
657
  function resolveMessages(options) {
@@ -594,6 +660,9 @@ function resolveMessages(options) {
594
660
  ...options.messages
595
661
  };
596
662
  }
663
+ function formatPreviewMessage(template, values) {
664
+ return template.replace(/\{(\w+)\}/g, (match, key) => String(values[key] ?? match));
665
+ }
597
666
 
598
667
  // src/plugins/fallback.ts
599
668
  function fallbackPlugin() {
@@ -664,9 +733,10 @@ function formatFallbackBytes(bytes) {
664
733
  function createViewer(options) {
665
734
  const container = resolveContainer(options.container);
666
735
  applyBoxSize(container, options.width, options.height);
736
+ const customClassNames = parseClassNameTokens(options.className);
667
737
  container.classList.add("ofv-root");
668
- if (options.className) {
669
- container.classList.add(options.className);
738
+ if (customClassNames.length > 0) {
739
+ container.classList.add(...customClassNames);
670
740
  }
671
741
  const theme = applyTheme(container, options.theme || "light");
672
742
  const host = document.createElement("div");
@@ -686,12 +756,17 @@ function createViewer(options) {
686
756
  currentIndex = clampIndex(index, queue.length);
687
757
  await renderQueueItem(currentIndex);
688
758
  };
689
- const toolbar = createToolbar(options.toolbar, viewport, {
690
- getLength: () => queue.length,
691
- next: () => goTo(currentIndex + 1),
692
- previous: () => goTo(currentIndex - 1),
693
- command: (command) => currentInstance?.command?.(command)
694
- });
759
+ const toolbar = createToolbar(
760
+ options.toolbar,
761
+ viewport,
762
+ {
763
+ getLength: () => queue.length,
764
+ next: () => goTo(currentIndex + 1),
765
+ previous: () => goTo(currentIndex - 1),
766
+ command: (command) => currentInstance?.command?.(command)
767
+ },
768
+ options.locale || "en-US"
769
+ );
695
770
  if (toolbar) {
696
771
  host.append(toolbar.element);
697
772
  }
@@ -810,12 +885,15 @@ function createViewer(options) {
810
885
  theme.destroy();
811
886
  container.replaceChildren();
812
887
  container.classList.remove("ofv-root");
813
- if (options.className) {
814
- container.classList.remove(options.className);
888
+ if (customClassNames.length > 0) {
889
+ container.classList.remove(...customClassNames);
815
890
  }
816
891
  }
817
892
  };
818
893
  }
894
+ function parseClassNameTokens(className) {
895
+ return className?.trim().split(/\s+/).filter(Boolean) ?? [];
896
+ }
819
897
  function destroyPreviewInstance(instance) {
820
898
  if (!instance) {
821
899
  return;
@@ -925,7 +1003,7 @@ function removeMediaListener(media, listener) {
925
1003
  }
926
1004
  media.removeListener?.(listener);
927
1005
  }
928
- function createToolbar(toolbar, viewport, queue) {
1006
+ function createToolbar(toolbar, viewport, queue, locale) {
929
1007
  if (!toolbar) {
930
1008
  return void 0;
931
1009
  }
@@ -933,7 +1011,7 @@ function createToolbar(toolbar, viewport, queue) {
933
1011
  const element = document.createElement("div");
934
1012
  element.className = "ofv-toolbar";
935
1013
  element.setAttribute("role", "toolbar");
936
- element.setAttribute("aria-label", "File preview toolbar");
1014
+ element.setAttribute("aria-label", defaultToolbarText[locale].ariaLabel);
937
1015
  let file;
938
1016
  let currentIndex = 0;
939
1017
  let currentLength = queue.getLength();
@@ -941,6 +1019,7 @@ function createToolbar(toolbar, viewport, queue) {
941
1019
  let previousButton;
942
1020
  let nextButton;
943
1021
  let zoomResetButton;
1022
+ let fullscreenButton;
944
1023
  let currentZoom;
945
1024
  const commandButtons = [];
946
1025
  const customButtons = [];
@@ -993,8 +1072,8 @@ function createToolbar(toolbar, viewport, queue) {
993
1072
  }
994
1073
  if (id === "previous" && queue.getLength() > 1) {
995
1074
  previousButton = addButton(
996
- getToolbarLabel(options, "previous"),
997
- getToolbarTitle(options, "previous"),
1075
+ getToolbarLabel(options, locale, "previous"),
1076
+ getToolbarTitle(options, locale, "previous"),
998
1077
  () => void queue.previous(),
999
1078
  void 0,
1000
1079
  options.icons?.previous
@@ -1003,8 +1082,8 @@ function createToolbar(toolbar, viewport, queue) {
1003
1082
  }
1004
1083
  if (id === "next" && queue.getLength() > 1) {
1005
1084
  nextButton = addButton(
1006
- getToolbarLabel(options, "next"),
1007
- getToolbarTitle(options, "next"),
1085
+ getToolbarLabel(options, locale, "next"),
1086
+ getToolbarTitle(options, locale, "next"),
1008
1087
  () => void queue.next(),
1009
1088
  void 0,
1010
1089
  options.icons?.next
@@ -1018,27 +1097,27 @@ function createToolbar(toolbar, viewport, queue) {
1018
1097
  return;
1019
1098
  }
1020
1099
  if (id === "zoom-out" && options.zoom) {
1021
- addCommandButton(id, getToolbarLabel(options, id), getToolbarTitle(options, id), "zoom-out");
1100
+ addCommandButton(id, getToolbarLabel(options, locale, id), getToolbarTitle(options, locale, id), "zoom-out");
1022
1101
  return;
1023
1102
  }
1024
1103
  if (id === "zoom-in" && options.zoom) {
1025
- addCommandButton(id, getToolbarLabel(options, id), getToolbarTitle(options, id), "zoom-in");
1104
+ addCommandButton(id, getToolbarLabel(options, locale, id), getToolbarTitle(options, locale, id), "zoom-in");
1026
1105
  return;
1027
1106
  }
1028
1107
  if (id === "zoom-reset" && options.zoom) {
1029
- addCommandButton(id, getToolbarLabel(options, id), getToolbarTitle(options, id), "zoom-reset");
1108
+ addCommandButton(id, getToolbarLabel(options, locale, id), getToolbarTitle(options, locale, id), "zoom-reset");
1030
1109
  zoomResetButton = commandButtons[commandButtons.length - 1]?.button;
1031
1110
  updateZoomLabel();
1032
1111
  return;
1033
1112
  }
1034
1113
  if (id === "rotate-right" && options.rotate) {
1035
- addCommandButton(id, getToolbarLabel(options, id), getToolbarTitle(options, id), "rotate-right");
1114
+ addCommandButton(id, getToolbarLabel(options, locale, id), getToolbarTitle(options, locale, id), "rotate-right");
1036
1115
  return;
1037
1116
  }
1038
1117
  if (id === "download" && options.download !== false) {
1039
1118
  addButton(
1040
- getToolbarLabel(options, id),
1041
- getToolbarTitle(options, id),
1119
+ getToolbarLabel(options, locale, id),
1120
+ getToolbarTitle(options, locale, id),
1042
1121
  () => getContext().download(),
1043
1122
  void 0,
1044
1123
  options.icons?.download
@@ -1046,19 +1125,20 @@ function createToolbar(toolbar, viewport, queue) {
1046
1125
  return;
1047
1126
  }
1048
1127
  if (id === "fullscreen" && options.fullscreen !== false) {
1049
- addButton(
1050
- getToolbarLabel(options, id),
1051
- getToolbarTitle(options, id),
1128
+ fullscreenButton = addButton(
1129
+ getToolbarLabel(options, locale, id),
1130
+ getToolbarTitle(options, locale, id),
1052
1131
  () => getContext().fullscreen(),
1053
1132
  void 0,
1054
1133
  options.icons?.fullscreen
1055
1134
  );
1135
+ updateFullscreenButton();
1056
1136
  return;
1057
1137
  }
1058
1138
  if (id === "print" && options.print) {
1059
1139
  addButton(
1060
- getToolbarLabel(options, id),
1061
- getToolbarTitle(options, id),
1140
+ getToolbarLabel(options, locale, id),
1141
+ getToolbarTitle(options, locale, id),
1062
1142
  () => getContext().print(),
1063
1143
  void 0,
1064
1144
  options.icons?.print
@@ -1084,11 +1164,11 @@ function createToolbar(toolbar, viewport, queue) {
1084
1164
  const renderSearchControl = () => {
1085
1165
  const searchGroup = document.createElement("div");
1086
1166
  searchGroup.className = "ofv-toolbar-search";
1087
- searchGroup.title = getToolbarTitle(options, "search");
1167
+ searchGroup.title = getToolbarTitle(options, locale, "search");
1088
1168
  const nextSearchInput = document.createElement("input");
1089
1169
  nextSearchInput.type = "search";
1090
- nextSearchInput.placeholder = getToolbarLabel(options, "search");
1091
- nextSearchInput.setAttribute("aria-label", getToolbarTitle(options, "search"));
1170
+ nextSearchInput.placeholder = getToolbarLabel(options, locale, "search");
1171
+ nextSearchInput.setAttribute("aria-label", getToolbarTitle(options, locale, "search"));
1092
1172
  const nextSearchCount = document.createElement("span");
1093
1173
  nextSearchCount.className = "ofv-toolbar-search-count";
1094
1174
  searchInput = nextSearchInput;
@@ -1143,7 +1223,7 @@ function createToolbar(toolbar, viewport, queue) {
1143
1223
  }
1144
1224
  setToolbarButtonContent(
1145
1225
  zoomResetButton,
1146
- currentZoom === void 0 ? getToolbarLabel(options, "zoom-reset") : formatToolbarZoom(currentZoom),
1226
+ currentZoom === void 0 ? getToolbarLabel(options, locale, "zoom-reset") : formatToolbarZoom(currentZoom),
1147
1227
  options.icons?.["zoom-reset"]
1148
1228
  );
1149
1229
  }
@@ -1154,6 +1234,32 @@ function createToolbar(toolbar, viewport, queue) {
1154
1234
  updateCustomButtons();
1155
1235
  refreshCustomRender();
1156
1236
  }
1237
+ function isFullscreenActive() {
1238
+ return Boolean(
1239
+ typeof document !== "undefined" && element.parentElement && document.fullscreenElement === element.parentElement
1240
+ );
1241
+ }
1242
+ function updateFullscreenButton() {
1243
+ if (!fullscreenButton) {
1244
+ return;
1245
+ }
1246
+ const active = isFullscreenActive();
1247
+ const id = active ? "exit-fullscreen" : "fullscreen";
1248
+ const icon = active ? options.icons?.["exit-fullscreen"] ?? options.icons?.fullscreen : options.icons?.fullscreen;
1249
+ setToolbarButtonContent(fullscreenButton, getToolbarLabel(options, locale, id), icon);
1250
+ const title = getToolbarTitle(options, locale, id);
1251
+ fullscreenButton.title = title;
1252
+ fullscreenButton.setAttribute("aria-label", title);
1253
+ fullscreenButton.setAttribute("aria-pressed", String(active));
1254
+ }
1255
+ const handleFullscreenChange = () => {
1256
+ updateFullscreenButton();
1257
+ refreshCustomRender();
1258
+ };
1259
+ if (typeof document !== "undefined") {
1260
+ document.addEventListener("fullscreenchange", handleFullscreenChange);
1261
+ disposers.push(() => document.removeEventListener("fullscreenchange", handleFullscreenChange));
1262
+ }
1157
1263
  const refreshCustomRender = () => {
1158
1264
  if (!options.render) {
1159
1265
  return;
@@ -1227,6 +1333,9 @@ function createToolbarContext({
1227
1333
  viewport,
1228
1334
  canPrevious: index > 0,
1229
1335
  canNext: index < length - 1,
1336
+ isFullscreen: Boolean(
1337
+ typeof document !== "undefined" && element.parentElement && document.fullscreenElement === element.parentElement
1338
+ ),
1230
1339
  zoom,
1231
1340
  zoomLabel: zoom === void 0 ? void 0 : formatToolbarZoom(zoom),
1232
1341
  async previous() {
@@ -1245,7 +1354,15 @@ function createToolbarContext({
1245
1354
  }
1246
1355
  },
1247
1356
  fullscreen() {
1248
- void element.parentElement?.requestFullscreen?.();
1357
+ const target = element.parentElement;
1358
+ if (!target) {
1359
+ return;
1360
+ }
1361
+ if (typeof document !== "undefined" && document.fullscreenElement === target) {
1362
+ void document.exitFullscreen?.();
1363
+ } else {
1364
+ void target.requestFullscreen?.();
1365
+ }
1249
1366
  },
1250
1367
  print() {
1251
1368
  printPreview(viewport);
@@ -1254,37 +1371,75 @@ function createToolbarContext({
1254
1371
  clearSearch: search.clear
1255
1372
  };
1256
1373
  }
1257
- var defaultToolbarLabels = {
1258
- previous: "Prev",
1259
- next: "Next",
1260
- queue: "",
1261
- "zoom-out": "-",
1262
- "zoom-in": "+",
1263
- "zoom-reset": "100%",
1264
- "rotate-right": "Rotate",
1265
- download: "Download",
1266
- fullscreen: "Fullscreen",
1267
- print: "Print",
1268
- search: "Search"
1269
- };
1270
- var defaultToolbarTitles = {
1271
- previous: "Previous file",
1272
- next: "Next file",
1273
- queue: "Current file position",
1274
- "zoom-out": "Zoom out",
1275
- "zoom-in": "Zoom in",
1276
- "zoom-reset": "Reset zoom",
1277
- "rotate-right": "Rotate right",
1278
- download: "Download file",
1279
- fullscreen: "Open preview fullscreen",
1280
- print: "Print preview",
1281
- search: "Search preview text"
1374
+ var defaultToolbarText = {
1375
+ "zh-CN": {
1376
+ ariaLabel: "\u6587\u4EF6\u9884\u89C8\u5DE5\u5177\u680F",
1377
+ labels: {
1378
+ previous: "\u4E0A\u4E00\u4E2A",
1379
+ next: "\u4E0B\u4E00\u4E2A",
1380
+ queue: "",
1381
+ "zoom-out": "-",
1382
+ "zoom-in": "+",
1383
+ "zoom-reset": "100%",
1384
+ "rotate-right": "\u65CB\u8F6C",
1385
+ download: "\u4E0B\u8F7D",
1386
+ fullscreen: "\u5168\u5C4F",
1387
+ "exit-fullscreen": "\u9000\u51FA\u5168\u5C4F",
1388
+ print: "\u6253\u5370",
1389
+ search: "\u641C\u7D22"
1390
+ },
1391
+ titles: {
1392
+ previous: "\u4E0A\u4E00\u4E2A\u6587\u4EF6",
1393
+ next: "\u4E0B\u4E00\u4E2A\u6587\u4EF6",
1394
+ queue: "\u5F53\u524D\u6587\u4EF6\u4F4D\u7F6E",
1395
+ "zoom-out": "\u7F29\u5C0F",
1396
+ "zoom-in": "\u653E\u5927",
1397
+ "zoom-reset": "\u91CD\u7F6E\u7F29\u653E",
1398
+ "rotate-right": "\u5411\u53F3\u65CB\u8F6C",
1399
+ download: "\u4E0B\u8F7D\u6587\u4EF6",
1400
+ fullscreen: "\u5168\u5C4F\u67E5\u770B\u9884\u89C8",
1401
+ "exit-fullscreen": "\u9000\u51FA\u5168\u5C4F",
1402
+ print: "\u6253\u5370\u9884\u89C8",
1403
+ search: "\u641C\u7D22\u9884\u89C8\u6587\u672C"
1404
+ }
1405
+ },
1406
+ "en-US": {
1407
+ ariaLabel: "File preview toolbar",
1408
+ labels: {
1409
+ previous: "Prev",
1410
+ next: "Next",
1411
+ queue: "",
1412
+ "zoom-out": "-",
1413
+ "zoom-in": "+",
1414
+ "zoom-reset": "100%",
1415
+ "rotate-right": "Rotate",
1416
+ download: "Download",
1417
+ fullscreen: "Fullscreen",
1418
+ "exit-fullscreen": "Exit fullscreen",
1419
+ print: "Print",
1420
+ search: "Search"
1421
+ },
1422
+ titles: {
1423
+ previous: "Previous file",
1424
+ next: "Next file",
1425
+ queue: "Current file position",
1426
+ "zoom-out": "Zoom out",
1427
+ "zoom-in": "Zoom in",
1428
+ "zoom-reset": "Reset zoom",
1429
+ "rotate-right": "Rotate right",
1430
+ download: "Download file",
1431
+ fullscreen: "Open preview fullscreen",
1432
+ "exit-fullscreen": "Exit fullscreen",
1433
+ print: "Print preview",
1434
+ search: "Search preview text"
1435
+ }
1436
+ }
1282
1437
  };
1283
- function getToolbarLabel(options, id) {
1284
- return options.labels?.[id] ?? defaultToolbarLabels[id];
1438
+ function getToolbarLabel(options, locale, id) {
1439
+ return options.labels?.[id] ?? defaultToolbarText[locale].labels[id];
1285
1440
  }
1286
- function getToolbarTitle(options, id) {
1287
- return options.titles?.[id] ?? options.labels?.[id] ?? defaultToolbarTitles[id];
1441
+ function getToolbarTitle(options, locale, id) {
1442
+ return options.titles?.[id] ?? options.labels?.[id] ?? defaultToolbarText[locale].titles[id];
1288
1443
  }
1289
1444
  function formatToolbarZoom(zoom) {
1290
1445
  return `${Math.round(zoom * 100)}%`;
@@ -1436,7 +1591,7 @@ function isSafeToolbarIconAttribute(name, value) {
1436
1591
  return !/^\s*(?:javascript|data:text\/html|vbscript):/i.test(value);
1437
1592
  }
1438
1593
  function isBuiltInToolbarAction(id) {
1439
- return id in defaultToolbarLabels;
1594
+ return id in defaultToolbarText["en-US"].labels;
1440
1595
  }
1441
1596
  function createSearchController(root) {
1442
1597
  const markerClass = "ofv-search-match";
@@ -1515,6 +1670,9 @@ function collectSearchableTextNodes(root) {
1515
1670
  if (["SCRIPT", "STYLE", "TEXTAREA", "INPUT", "BUTTON"].includes(parent.tagName)) {
1516
1671
  return NodeFilter.FILTER_REJECT;
1517
1672
  }
1673
+ if (hasHiddenSearchAncestor(parent, root)) {
1674
+ return NodeFilter.FILTER_REJECT;
1675
+ }
1518
1676
  return NodeFilter.FILTER_ACCEPT;
1519
1677
  }
1520
1678
  });
@@ -1525,6 +1683,19 @@ function collectSearchableTextNodes(root) {
1525
1683
  }
1526
1684
  return nodes;
1527
1685
  }
1686
+ function hasHiddenSearchAncestor(element, root) {
1687
+ let current = element;
1688
+ while (current) {
1689
+ if (current.hidden || current.getAttribute("aria-hidden") === "true" || current.style.display === "none" || current.style.visibility === "hidden") {
1690
+ return true;
1691
+ }
1692
+ if (current === root) {
1693
+ break;
1694
+ }
1695
+ current = current.parentElement;
1696
+ }
1697
+ return false;
1698
+ }
1528
1699
  function printPreview(viewport) {
1529
1700
  const frame = document.createElement("iframe");
1530
1701
  frame.className = "ofv-print-frame";
@@ -4022,7 +4193,7 @@ function textPlugin() {
4022
4193
  const isMarkdown = lang === "markdown";
4023
4194
  const text = await readText(ctx.file.source).catch((error) => void 0);
4024
4195
  if (text === void 0) {
4025
- const fallback = createTextFallback(ctx.file.name, ctx.file.url);
4196
+ const fallback = createTextFallback(ctx.file.name, ctx.options.messages, ctx.file.url);
4026
4197
  ctx.viewport.classList.add("ofv-center");
4027
4198
  ctx.viewport.append(fallback);
4028
4199
  return {
@@ -4194,10 +4365,11 @@ function textPlugin() {
4194
4365
  title.className = "ofv-code-title";
4195
4366
  const fileName = document.createElement("strong");
4196
4367
  fileName.textContent = ctx.file.name;
4368
+ const messages = ctx.options.messages;
4197
4369
  const meta = document.createElement("span");
4198
4370
  meta.textContent = [
4199
- lang === "none" ? "plain text" : lang,
4200
- `${totalLines.toLocaleString()} lines`,
4371
+ lang === "none" ? messages.textPlainLanguage : lang,
4372
+ formatPreviewMessage(messages.textLineCount, { count: totalLines.toLocaleString() }),
4201
4373
  formatBytes(ctx.file.size ?? (ctx.file.source instanceof Blob ? ctx.file.source.size : text.length))
4202
4374
  ].join(" \xB7 ");
4203
4375
  title.append(fileName, meta);
@@ -4209,7 +4381,7 @@ function textPlugin() {
4209
4381
  const wrapButton = document.createElement("button");
4210
4382
  wrapButton.type = "button";
4211
4383
  wrapButton.className = "ofv-code-action";
4212
- wrapButton.textContent = "Wrap";
4384
+ wrapButton.textContent = messages.textWrap;
4213
4385
  wrapButton.setAttribute("aria-pressed", String(defaultWrapped));
4214
4386
  wrapButton.addEventListener("click", () => {
4215
4387
  const wrapped = wrapper.classList.toggle("is-wrapped");
@@ -4218,14 +4390,14 @@ function textPlugin() {
4218
4390
  const copyButton = document.createElement("button");
4219
4391
  copyButton.type = "button";
4220
4392
  copyButton.className = "ofv-code-action";
4221
- copyButton.textContent = "Copy";
4393
+ copyButton.textContent = messages.textCopy;
4222
4394
  copyButton.addEventListener("click", async () => {
4223
4395
  copyButton.disabled = true;
4224
4396
  try {
4225
4397
  await copyToClipboard(text);
4226
- status.textContent = "Copied";
4398
+ status.textContent = messages.textCopied;
4227
4399
  } catch {
4228
- status.textContent = "Copy failed";
4400
+ status.textContent = messages.textCopyFailed;
4229
4401
  } finally {
4230
4402
  copyButton.disabled = false;
4231
4403
  }
@@ -4233,10 +4405,10 @@ function textPlugin() {
4233
4405
  const downloadButton = document.createElement("button");
4234
4406
  downloadButton.type = "button";
4235
4407
  downloadButton.className = "ofv-code-action";
4236
- downloadButton.textContent = "Download";
4408
+ downloadButton.textContent = messages.textDownload;
4237
4409
  downloadButton.addEventListener("click", () => {
4238
4410
  downloadText(ctx.file.name, text);
4239
- status.textContent = "Download ready";
4411
+ status.textContent = messages.textDownloadReady;
4240
4412
  });
4241
4413
  actions.append(wrapButton, copyButton, downloadButton, status);
4242
4414
  header.append(title, actions);
@@ -4261,13 +4433,13 @@ function textPlugin() {
4261
4433
  if (truncated) {
4262
4434
  const notice = document.createElement("div");
4263
4435
  notice.className = "ofv-code-notice";
4264
- 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`;
4436
+ notice.textContent = formatPreviewMessage(messages.textLargeFileNotice, { size: formatBytes(codeText.length) });
4265
4437
  wrapper.append(notice);
4266
4438
  }
4267
4439
  if (!shouldHighlight) {
4268
4440
  const notice = document.createElement("div");
4269
4441
  notice.className = "ofv-code-notice";
4270
- notice.textContent = "\u5185\u5BB9\u8F83\u5927\uFF0C\u5DF2\u8DF3\u8FC7\u8BED\u6CD5\u9AD8\u4EAE\u4EE5\u4FDD\u6301\u6EDA\u52A8\u6D41\u7545\u3002";
4442
+ notice.textContent = messages.textHighlightSkipped;
4271
4443
  wrapper.append(notice);
4272
4444
  }
4273
4445
  wrapper.appendChild(body);
@@ -4334,19 +4506,19 @@ function normalizeFileName2(name) {
4334
4506
  const baseName = name.split(/[\\/]/).pop() || name;
4335
4507
  return baseName.toLowerCase();
4336
4508
  }
4337
- function createTextFallback(fileName, url) {
4509
+ function createTextFallback(fileName, messages, url) {
4338
4510
  const fallback = document.createElement("div");
4339
4511
  fallback.className = "ofv-fallback";
4340
4512
  const title = document.createElement("strong");
4341
- title.textContent = "\u6587\u672C\u9884\u89C8\u5931\u8D25";
4513
+ title.textContent = messages.textPreviewFailedTitle;
4342
4514
  const meta = document.createElement("span");
4343
- 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";
4515
+ meta.textContent = messages.textPreviewFailedMessage;
4344
4516
  fallback.append(title, meta);
4345
4517
  if (url) {
4346
4518
  const download = document.createElement("a");
4347
4519
  download.href = url;
4348
4520
  download.download = fileName;
4349
- download.textContent = "\u6253\u5F00\u539F\u6587\u4EF6";
4521
+ download.textContent = messages.textOpenOriginal;
4350
4522
  fallback.append(download);
4351
4523
  }
4352
4524
  return fallback;
@@ -7408,7 +7580,7 @@ function officePlugin(options = {}) {
7408
7580
  } else if (packageFormat === "docx" && !fileIsDocx(extension)) {
7409
7581
  disposeDocxFit = await renderDocx(panel, arrayBuffer, ctx.options.fit);
7410
7582
  } else if (packageFormat === "xlsx" && !sheetExtensions.has(extension)) {
7411
- await renderSheet(panel, arrayBuffer, "xlsx");
7583
+ await renderSheet(panel, arrayBuffer, "xlsx", ctx.options.messages);
7412
7584
  } else if (packageFormat === "pptx" && !["pptx", "pptm", "ppsx", "ppsm", "potx", "potm"].includes(extension)) {
7413
7585
  await renderPptx(panel, arrayBuffer);
7414
7586
  } else if (fileIsDocx(extension)) {
@@ -7421,9 +7593,9 @@ function officePlugin(options = {}) {
7421
7593
  renderOpenDocumentXml(panel, "FODT \u6587\u6863", await readTextFromBuffer(arrayBuffer));
7422
7594
  } else if (extension === "fods") {
7423
7595
  renderFlatOds(panel, await readTextFromBuffer(arrayBuffer));
7424
- } else if (packagedOfficeCandidates.has(extension) && await renderPackagedOfficePreview(panel, arrayBuffer, extension, ctx.options.fit)) {
7596
+ } else if (packagedOfficeCandidates.has(extension) && await renderPackagedOfficePreview(panel, arrayBuffer, extension, ctx.options.fit, ctx.options.messages)) {
7425
7597
  } else if (sheetExtensions.has(extension)) {
7426
- await renderSheet(panel, arrayBuffer, extension);
7598
+ await renderSheet(panel, arrayBuffer, extension, ctx.options.messages);
7427
7599
  } else if (["pptx", "pptm", "ppsx", "ppsm", "potx", "potm"].includes(extension)) {
7428
7600
  await renderPptx(panel, arrayBuffer);
7429
7601
  } else if (extension === "odp") {
@@ -7431,11 +7603,11 @@ function officePlugin(options = {}) {
7431
7603
  } else if (extension === "fodp") {
7432
7604
  renderOpenDocumentPresentationXml(panel, await readTextFromBuffer(arrayBuffer));
7433
7605
  } else if (extension === "doc" || extension === "dot") {
7434
- renderLegacyWordBinary(panel, extension, arrayBuffer);
7606
+ renderLegacyWordBinary(panel, extension, arrayBuffer, ctx.options.messages);
7435
7607
  } else if (isLegacyOfficeBinary(extension)) {
7436
- renderLegacyOfficeBinary(panel, extension, arrayBuffer);
7608
+ renderLegacyOfficeBinary(panel, extension, arrayBuffer, ctx.options.messages);
7437
7609
  } else {
7438
- renderUnsupportedOffice(panel, extension || ctx.file.extension || "office");
7610
+ renderUnsupportedOffice(panel, extension || ctx.file.extension || "office", ctx.options.messages);
7439
7611
  }
7440
7612
  const controller = createOfficeZoomController(panel, ctx);
7441
7613
  ctx.toolbar?.refreshCommandSupport();
@@ -8780,7 +8952,7 @@ function renderPlainDocument(panel, title, text) {
8780
8952
  section.append(pre);
8781
8953
  panel.append(section);
8782
8954
  }
8783
- async function renderSheet(panel, arrayBuffer, extension) {
8955
+ async function renderSheet(panel, arrayBuffer, extension, messages) {
8784
8956
  const xlsx = await import("xlsx");
8785
8957
  let workbook;
8786
8958
  try {
@@ -8793,10 +8965,16 @@ async function renderSheet(panel, arrayBuffer, extension) {
8793
8965
  }) : xlsx.read(arrayBuffer, { type: "array", cellDates: true, cellNF: true, cellStyles: true });
8794
8966
  } catch (error) {
8795
8967
  if (isLegacyOfficeBinary(extension)) {
8796
- renderLegacyOfficeBinary(panel, extension, arrayBuffer, `\u8868\u683C\u89E3\u6790\u5931\u8D25\uFF1A${normalizeOfficeError(error)}`);
8968
+ renderLegacyOfficeBinary(
8969
+ panel,
8970
+ extension,
8971
+ arrayBuffer,
8972
+ messages,
8973
+ formatPreviewMessage(messages.officeSheetParseFailed, { message: normalizeOfficeError(error, messages) })
8974
+ );
8797
8975
  return;
8798
8976
  }
8799
- renderSheetFallback(panel, extension, normalizeOfficeError(error));
8977
+ renderSheetFallback(panel, extension, normalizeOfficeError(error, messages));
8800
8978
  return;
8801
8979
  }
8802
8980
  const chartPreviews = await readWorkbookCharts(arrayBuffer).catch(() => []);
@@ -9277,6 +9455,9 @@ function renderChartCard(chart) {
9277
9455
  function renderChartSvg(chart) {
9278
9456
  const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
9279
9457
  svg.setAttribute("viewBox", "0 0 640 380");
9458
+ svg.setAttribute("preserveAspectRatio", "xMidYMid meet");
9459
+ svg.setAttribute("text-rendering", "geometricPrecision");
9460
+ svg.setAttribute("shape-rendering", "geometricPrecision");
9280
9461
  svg.setAttribute("role", "img");
9281
9462
  svg.setAttribute("aria-label", chart.title);
9282
9463
  svg.classList.add("ofv-chart-svg");
@@ -9322,16 +9503,7 @@ function renderChartSvg(chart) {
9322
9503
  const clusterWidth = groupWidth * 0.58;
9323
9504
  const barWidth = Math.max(5, Math.min(28, clusterWidth / Math.max(1, chart.series.length)));
9324
9505
  const zeroY = plot.y + plot.height - (0 - axisMin) / (axisMax - axisMin || 1) * plot.height;
9325
- categories.slice(0, categoryCount).forEach((category, index) => {
9326
- const x = plot.x + groupWidth * (index + 0.5);
9327
- const label = appendSvg(svg, "text", {
9328
- x: Number(x.toFixed(1)),
9329
- y: plot.y + plot.height + 22,
9330
- class: "ofv-chart-label",
9331
- "text-anchor": "middle"
9332
- });
9333
- label.textContent = truncateChartLabel(category);
9334
- });
9506
+ appendChartCategoryLabels(svg, categories, plot, (index) => plot.x + groupWidth * (index + 0.5));
9335
9507
  chart.series.forEach((series, seriesIndex) => {
9336
9508
  const color = series.color || colors[seriesIndex % colors.length];
9337
9509
  series.values.forEach((value, index) => {
@@ -9349,6 +9521,8 @@ function renderChartSvg(chart) {
9349
9521
  });
9350
9522
  });
9351
9523
  } else {
9524
+ const categoryStep = categories.length > 1 ? plot.width / (categories.length - 1) : plot.width;
9525
+ appendChartCategoryLabels(svg, categories, plot, (index) => plot.x + index * categoryStep);
9352
9526
  chart.series.forEach((series, seriesIndex) => {
9353
9527
  const color = series.color || colors[seriesIndex % colors.length];
9354
9528
  const step = series.values.length > 1 ? plot.width / (series.values.length - 1) : plot.width;
@@ -9373,6 +9547,22 @@ function renderChartSvg(chart) {
9373
9547
  appendChartLegend(svg, chart, colors, 348);
9374
9548
  return svg;
9375
9549
  }
9550
+ function appendChartCategoryLabels(svg, categories, plot, getX) {
9551
+ const interval = Math.max(1, Math.ceil(categories.length / 14));
9552
+ categories.forEach((category, index) => {
9553
+ if (index % interval !== 0 && index !== categories.length - 1) {
9554
+ return;
9555
+ }
9556
+ const label = appendSvg(svg, "text", {
9557
+ x: Number(getX(index).toFixed(1)),
9558
+ y: plot.y + plot.height + 22,
9559
+ class: "ofv-chart-label ofv-chart-category-label",
9560
+ "data-axis": "category",
9561
+ "text-anchor": "middle"
9562
+ });
9563
+ label.textContent = truncateChartLabel(category);
9564
+ });
9565
+ }
9376
9566
  function appendChartLegend(svg, chart, colors, y) {
9377
9567
  const itemWidth = 86;
9378
9568
  const startX = 320 - chart.series.length * itemWidth / 2;
@@ -10057,10 +10247,15 @@ function pptxRenderTimeoutMs() {
10057
10247
  function normalizePptxLayout(container) {
10058
10248
  const slideCanvases = findPptxSlideCanvases(container);
10059
10249
  for (const slide of slideCanvases) {
10060
- slide.style.backgroundColor = "#FFFFFF";
10250
+ if (!hasInlineBackground(slide)) {
10251
+ slide.style.backgroundColor = "#FFFFFF";
10252
+ }
10061
10253
  }
10062
10254
  normalizePptxMirroredText(container);
10063
10255
  }
10256
+ function hasInlineBackground(element) {
10257
+ return Boolean(element.style.background || element.style.backgroundColor || element.style.backgroundImage);
10258
+ }
10064
10259
  function normalizePptxMirroredText(container) {
10065
10260
  const mirroredContainers = Array.from(container.querySelectorAll("div")).filter((element) => {
10066
10261
  const text = element.textContent?.trim();
@@ -10152,7 +10347,7 @@ function renderOpenDocumentPresentationXml(panel, xml) {
10152
10347
  renderPresentationInsight(panel, inspectOpenDocumentPresentation("FODP \u6F14\u793A\u6587\u7A3F", xml, 0));
10153
10348
  renderOpenDocumentPresentation(panel, "FODP \u6F14\u793A\u6587\u7A3F", xml, []);
10154
10349
  }
10155
- async function renderPackagedOfficePreview(panel, arrayBuffer, extension, fit) {
10350
+ async function renderPackagedOfficePreview(panel, arrayBuffer, extension, fit, messages) {
10156
10351
  let zip;
10157
10352
  try {
10158
10353
  zip = await JSZip3.loadAsync(arrayBuffer);
@@ -10167,7 +10362,7 @@ async function renderPackagedOfficePreview(panel, arrayBuffer, extension, fit) {
10167
10362
  return true;
10168
10363
  }
10169
10364
  if (hasEntry("xl/workbook.xml")) {
10170
- await renderSheet(panel, arrayBuffer, extension);
10365
+ await renderSheet(panel, arrayBuffer, extension, messages);
10171
10366
  return true;
10172
10367
  }
10173
10368
  if (hasEntry("ppt/presentation.xml")) {
@@ -10598,41 +10793,50 @@ function legacyOfficeFormatLabel(extension) {
10598
10793
  }
10599
10794
  return "PowerPoint Binary File Format";
10600
10795
  }
10601
- function renderLegacyWordBinary(panel, extension, arrayBuffer) {
10796
+ function renderLegacyWordBinary(panel, extension, arrayBuffer, messages) {
10602
10797
  try {
10603
10798
  renderLegacyWordDocument(panel, parseLegacyWordDocument(arrayBuffer));
10604
10799
  } catch (error) {
10605
- renderLegacyOfficeBinary(panel, extension, arrayBuffer, `Word \u4E8C\u8FDB\u5236\u89E3\u6790\u5931\u8D25\uFF1A${normalizeOfficeError(error)}`);
10800
+ renderLegacyOfficeBinary(
10801
+ panel,
10802
+ extension,
10803
+ arrayBuffer,
10804
+ messages,
10805
+ formatPreviewMessage(messages.officeLegacyWordParseFailed, { message: normalizeOfficeError(error, messages) })
10806
+ );
10606
10807
  }
10607
10808
  }
10608
- function renderLegacyOfficeBinary(panel, extension, arrayBuffer, parseError) {
10809
+ function renderLegacyOfficeBinary(panel, extension, arrayBuffer, messages, parseError) {
10609
10810
  const fragments = extractLegacyOfficeText(arrayBuffer);
10610
10811
  panel.replaceChildren();
10611
- const section = createSection("Office \u8F6C\u6362\u63D0\u793A");
10812
+ const section = createSection(messages.officeLegacyConversionTitle);
10612
10813
  section.classList.add("ofv-office-conversion");
10613
10814
  const format = document.createElement("p");
10614
10815
  const strong = document.createElement("strong");
10615
10816
  strong.textContent = `.${extension}`;
10616
- format.append(
10617
- strong,
10618
- document.createTextNode(
10619
- " \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"
10620
- )
10621
- );
10817
+ format.append(strong, document.createTextNode(" "), document.createTextNode(messages.officeLegacyBinaryNotice));
10622
10818
  const meta = document.createElement("dl");
10623
10819
  meta.className = "ofv-office-binary-meta";
10624
- appendOfficeBinaryMeta(meta, "\u683C\u5F0F\u7C7B\u578B", legacyOfficeFormatLabel(extension));
10625
- 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");
10626
- appendOfficeBinaryMeta(meta, "\u6587\u672C\u7247\u6BB5", `${fragments.length} \u6BB5`);
10820
+ appendOfficeBinaryMeta(meta, messages.officeLegacyMetaFormatType, legacyOfficeFormatLabel(extension));
10821
+ appendOfficeBinaryMeta(
10822
+ meta,
10823
+ messages.officeLegacyMetaFileStructure,
10824
+ hasOleSignature(arrayBuffer) ? messages.officeLegacyOleDetected : messages.officeLegacyOleMissing
10825
+ );
10826
+ appendOfficeBinaryMeta(
10827
+ meta,
10828
+ messages.officeLegacyMetaTextFragments,
10829
+ formatPreviewMessage(messages.officeLegacyTextFragmentCount, { count: fragments.length.toLocaleString() })
10830
+ );
10627
10831
  if (parseError) {
10628
- appendOfficeBinaryMeta(meta, "\u89E3\u6790\u72B6\u6001", parseError);
10832
+ appendOfficeBinaryMeta(meta, messages.officeLegacyMetaParseStatus, parseError);
10629
10833
  }
10630
10834
  section.append(format, meta);
10631
10835
  if (fragments.length > 0) {
10632
10836
  const article = document.createElement("article");
10633
10837
  article.className = "ofv-document ofv-office-binary-fragments";
10634
10838
  const heading = document.createElement("h4");
10635
- heading.textContent = "\u53EF\u8BFB\u6587\u672C\u7247\u6BB5";
10839
+ heading.textContent = messages.officeLegacyReadableFragments;
10636
10840
  article.append(heading);
10637
10841
  for (const fragment of fragments.slice(0, 80)) {
10638
10842
  const paragraph = document.createElement("p");
@@ -10643,7 +10847,7 @@ function renderLegacyOfficeBinary(panel, extension, arrayBuffer, parseError) {
10643
10847
  } else {
10644
10848
  const empty = document.createElement("p");
10645
10849
  empty.className = "ofv-office-binary-empty";
10646
- 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";
10850
+ empty.textContent = messages.officeLegacyNoText;
10647
10851
  section.append(empty);
10648
10852
  }
10649
10853
  panel.append(section);
@@ -10655,23 +10859,27 @@ function appendOfficeBinaryMeta(list, label, value) {
10655
10859
  detail.textContent = value;
10656
10860
  list.append(term, detail);
10657
10861
  }
10658
- function renderUnsupportedOffice(panel, extension) {
10862
+ function renderUnsupportedOffice(panel, extension, messages) {
10659
10863
  const legacyBinary = /* @__PURE__ */ new Set(["doc", "dot", "wps", "ppt", "pps", "key", "dps"]);
10660
- 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";
10864
+ const message = legacyBinary.has(extension) ? messages.officeUnsupportedLegacyMessage : messages.officeUnsupportedGenericMessage;
10661
10865
  panel.replaceChildren();
10662
- const section = createSection("Office \u57FA\u7840\u9884\u89C8");
10866
+ const section = createSection(messages.officeUnsupportedTitle);
10663
10867
  const format = document.createElement("p");
10664
10868
  const strong = document.createElement("strong");
10665
10869
  strong.textContent = `.${extension}`;
10666
- format.append(strong, document.createTextNode(` \u5DF2\u8FDB\u5165 Office \u63D2\u4EF6\u3002${message}`));
10870
+ format.append(
10871
+ strong,
10872
+ document.createTextNode(" "),
10873
+ document.createTextNode(formatPreviewMessage(messages.officeUnsupportedIntro, { message }))
10874
+ );
10667
10875
  const support = document.createElement("p");
10668
- 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";
10876
+ support.textContent = messages.officeUnsupportedSupportedFormats;
10669
10877
  section.append(format, support);
10670
10878
  panel.append(section);
10671
10879
  }
10672
- function normalizeOfficeError(error) {
10880
+ function normalizeOfficeError(error, messages) {
10673
10881
  const message = error instanceof Error ? error.message : String(error || "");
10674
- return message ? `\u89E3\u6790\u5668\u8FD4\u56DE\uFF1A${message}` : "\u89E3\u6790\u5668\u672A\u8FD4\u56DE\u5177\u4F53\u9519\u8BEF\u4FE1\u606F\u3002";
10882
+ return message ? formatPreviewMessage(messages.officeErrorWithMessage, { message }) : messages.officeErrorWithoutMessage;
10675
10883
  }
10676
10884
  function extractLegacyOfficeText(arrayBuffer) {
10677
10885
  const bytes = new Uint8Array(arrayBuffer);
@@ -11913,11 +12121,13 @@ function archivePlugin() {
11913
12121
  sidebarToggle.addEventListener("click", () => {
11914
12122
  setSidebarCollapsed(!layout.classList.contains("is-sidebar-collapsed"));
11915
12123
  });
11916
- const showDefaultSummary = () => {
12124
+ const showDefaultSummary = (isSupplemental = true) => {
11917
12125
  mainPanel.replaceChildren();
11918
12126
  const summary = document.createElement("div");
11919
12127
  summary.className = "ofv-archive-info";
11920
- hideSupplementalInfo4(summary);
12128
+ if (isSupplemental) {
12129
+ hideSupplementalInfo4(summary);
12130
+ }
11921
12131
  const heading = document.createElement("h3");
11922
12132
  heading.textContent = ctx.file.name;
11923
12133
  const info = document.createElement("div");
@@ -11927,12 +12137,16 @@ function archivePlugin() {
11927
12137
  appendArchiveInfo(info, "\u683C\u5F0F\u7C7B\u578B", `.${ext.toUpperCase()} \u538B\u7F29\u6587\u4EF6`);
11928
12138
  appendArchiveInfo(info, "\u5305\u542B\u6587\u4EF6\u6570", `${fileCount} \u4E2A`);
11929
12139
  appendArchiveInfo(info, "\u5305\u542B\u76EE\u5F55\u6570", `${dirCount} \u4E2A`);
11930
- appendArchiveInfo(info, "\u64CD\u4F5C\u63D0\u793A", "\u8BF7\u70B9\u51FB\u5DE6\u4FA7\u680F\u4E2D\u7684\u6587\u4EF6\u8FDB\u884C\u8054\u52A8\u9884\u89C8\u3002");
12140
+ appendArchiveInfo(
12141
+ info,
12142
+ "\u64CD\u4F5C\u63D0\u793A",
12143
+ 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"
12144
+ );
11931
12145
  summary.append(heading, info, createArchiveSummary(archiveEntries));
11932
12146
  mainPanel.append(summary);
11933
12147
  };
11934
- showDefaultSummary();
11935
12148
  const visibleEntries = archiveEntries.filter((e) => !e.dir).slice(0, 500);
12149
+ showDefaultSummary(visibleEntries.length > 0);
11936
12150
  let destroyed = false;
11937
12151
  let renderToken = 0;
11938
12152
  const openArchiveEntry = async (entry, item) => {