"use strict"; var TabLocalFiles; (function (TabLocalFiles) { function formatShortcut(spec) { if (shortcutPlatform() === "mac") { return `${spec.alt ? "⌥" : ""}${spec.shift ? "⇧" : ""}${spec.mod ? "⌘" : ""}${spec.key}`; } const parts = []; if (spec.mod) { parts.push("Ctrl"); } if (spec.alt) { parts.push("Alt"); } if (spec.shift) { parts.push("Shift"); } parts.push(spec.key); return parts.join("+"); } TabLocalFiles.formatShortcut = formatShortcut; function shortcutPlatform() { const navigatorInfo = globalThis.navigator; const platform = (navigatorInfo?.userAgentData?.platform || navigatorInfo?.platform || "").toLowerCase(); if (platform.includes("mac")) { return "mac"; } if (platform.includes("win")) { return "windows"; } return "other"; } TabLocalFiles.shortcutPlatform = shortcutPlatform; })(TabLocalFiles || (TabLocalFiles = {})); var TabLocalFiles; (function (TabLocalFiles) { TabLocalFiles.RUNTIME_VERSION = "0.1.7"; })(TabLocalFiles || (TabLocalFiles = {})); var TabLocalFiles; (function (TabLocalFiles) { const DEFAULT_LOCALE = "zh-CN"; const I18N_MESSAGES = { "zh-CN": { "app.description": "本地文件渲染", "viewer.openingTitle": "正在打开文件", "viewer.openingDescription": "Previewer 正在准备文件预览。", "common.fileName": "文件名", "common.fileSize": "大小", "common.fileCreated": "创建时间", "common.fileModified": "修改时间", "common.today": "今天", "common.yesterday": "昨天", "common.filePath": "文件路径", "common.pathEllipsis": "...", "common.showCollapsedPath": "显示折叠的中间路径", "common.collapsedPathSegments": "折叠的中间路径", "common.fileActions": "文件操作", "common.fileInfo": "文件信息", "common.moreActions": "更多操作", "common.renameFile": "重命名文件", "common.expandExplorer": "展开资源管理器", "common.collapseExplorer": "收起资源管理器", "common.expandHeader": "展开文件头", "common.collapseHeader": "收起文件头", "common.rawFile": "原始文件", "common.openInNewTab": "在新标签页打开", "common.save": "保存", "common.saveAs": "另存为……", "common.downloadRemoteFile": "下载到本地", "common.print": "打印", "print.preparing": "正在准备打印内容…", "print.failed": "无法准备打印内容", "print.notReady": "内容尚未加载完成,请稍后再试", "print.pageAlt": "打印第 {page} 页", "print.namedPageAlt": "{fileName} 第 {page} 页", "print.preparingPage": "正在准备第 {page}/{total} 页…", "print.preparingSlide": "正在准备第 {slide}/{total} 张幻灯片…", "print.namedSlideAlt": "{fileName} 第 {slide} 张幻灯片", "common.edit": "编辑", "common.preview": "预览", "common.copy": "复制", "common.copyAllText": "复制全部", "common.export": "导出", "common.revealInFileManager": "在文件管理器中显示", "common.revealInFinder": "在 Finder 中显示", "common.revealInExplorer": "在文件资源管理器中显示", "common.revealUnavailableRemote": "在线页面无法显示", "common.editUnavailableRemote": "在线页面无法编辑", "common.fileUnavailable": "该文件可能已被移至别处、修改或删除。", "common.unknown": "未知", "common.unknownError": "未知错误", "common.untitled": "Untitled", "common.localFile": "Local file", "common.loading": "正在加载...", "common.notModified": "未修改", "common.unsavedChanges": "有未保存的修改", "common.unexportedChanges": "有未导出的修改", "common.copiedToClipboard": "已复制到剪贴板", "common.copiedSelectedText": "已复制选中文本", "common.copyFailed": "复制失败", "common.saving": "正在保存...", "common.exporting": "正在导出...", "common.savingAs": "正在另存为...", "common.downloadingRemoteFile": "正在下载...", "common.savedToOriginal": "已保存到原文件", "common.saved": "已保存", "common.savedAs": "已另存为", "common.downloadedRemoteFile": "已下载到本地", "common.exportedToDownloads": "已导出到下载文件夹", "common.cancelledExport": "已取消导出", "common.cancelledSaveAs": "已取消另存为", "common.cancelledDownload": "已取消下载", "common.cancel": "取消", "common.saveFailedWorkspace": "保存失败:无法写入当前工作区文件", "common.exportFailed": "导出失败", "common.saveAsFailed": "另存失败", "common.downloadRemoteFailed": "下载失败:无法读取在线文件", "common.downloadUnavailable": "当前文件无法下载", "common.fileContent": "文件内容", "common.textFile": "Text file", "common.filePickerLocalFile": "Local file", "editor.clean": "已同步", "editor.external": "外部更新中", "editor.externalHint": "其他程序正在修改此文件,改动已自动同步到这里", "changes.unreadAbove": "上方 {count} 处未读改动", "changes.unreadBelow": "下方 {count} 处未读改动", "changes.markAllRead": "全部标为已读", "changes.jump": "跳到这处改动", "changes.added": "外部新增", "changes.modified": "外部修改", "changes.deleted": "外部删除了这里的内容", "editor.readonly": "只读", "editor.dirty": "未保存", "editor.saving": "保存中", "editor.saved": "已保存", "editor.autoSavePending": "待保存", "editor.autoSaved": "已自动保存", "editor.savedAsUtf8": "文件原为 {encoding} 编码,内容无法用该编码表示,已改为 UTF-8 保存", "editor.error": "保存失败", "editor.conflict": "文件冲突", "editor.draftRestored": "已恢复未保存草稿", "editor.draftOutOfSpace": "浏览器存储空间不足,本文档的未保存草稿无法留存。文字一个字也没被删减,建议尽快保存到文件。", "editor.unsavedTitle": "保存修改?", "editor.unsavedDescription": "“{fileName}”有未保存的修改。", "editor.saveBeforeLeave": "离开前保存“{fileName}”的修改?", "editor.discardBeforeLeave": "放弃“{fileName}”的未保存修改?", "editor.discard": "放弃修改", "editor.beforeUnload": "当前文件有未保存的修改。", "editor.revertTitle": "还原到打开文件时的内容?", "editor.revertDescription": "将丢弃“{fileName}”自本次打开以来的全部修改,包括已经自动保存到磁盘的部分。", "editor.revertConfirm": "还原", "editor.conflictTitle": "文件已在其他位置修改", "editor.conflictDescription": "“{fileName}”的磁盘内容已变化。为避免覆盖他人的修改,只有明确确认后才会继续保存。也可以取消并使用“另存为”。", "editor.overwrite": "仍然覆盖", "markdown.bold": "粗体", "markdown.italic": "斜体", "markdown.insertLink": "插入链接", "markdown.insertImage": "插入图片", "markdown.widthDecrease": "减小正文宽度", "markdown.contentWidth": "正文宽度", "markdown.widthIncrease": "增加正文宽度", "markdown.taskToggle": "切换任务状态", "markdown.taskReadonlyRemote": "在线 Markdown 只读,请下载到本地后编辑", "large.previewTitle": "大文件安全预览", "large.previewDescription": "已先加载前 {previewSize},滚动到底部继续按需加载;编辑和保存已禁用。", "large.previewAria": "{kind} 大文件预览片段", "large.previewMeta": "{kind} · 大文件预览 · {size}", "large.blockedTitle": "文件过大,已停止自动预览", "large.blockedDescription": "{kind} 文件大小 {size}。为避免浏览器崩溃,Previewer 没有读取完整内容。可以打开原始文件或在文件管理器中定位。", "large.blockedMeta": "{kind} · 文件过大 · {size}", "large.emptyPreview": "无法读取安全预览片段。", "large.virtualStatus": "正在显示 {start} - {end} / {total}", "large.virtualLoading": "正在加载下一段...", "large.virtualError": "加载失败,滚动后将重试。", "large.directoryLimited": "仅显示前 {count} 项,文件夹过大时不会一次渲染全部内容。", "toolbar.reveal": "在文件管理器中显示", "theme.followingBrowser": "跟随浏览器{theme}主题,点击可临时固定{nextTheme}", "theme.fixed": "已固定{theme}主题,点击恢复跟随浏览器", "theme.dark": "深色", "theme.light": "浅色", "rename.inProgress": "正在重命名...", "rename.success": "已重命名", "rename.failed": "重命名失败:{error}", "rename.invalidName": "文件名包含非法字符", "rename.duplicateName": "当前文件夹已存在同名文件", "rename.extensionNone": "无扩展名", "rename.extensionChangeTitle": "确认变更扩展名", "rename.extensionChangeDescription": "「{fileName}」的扩展名即将变更为「{extension}」,这会改变文件的打开方式。确认重命名吗?", "rename.extensionChangeConfirm": "确认重命名", "common.panelMode": "预览 / 编辑模式", "common.previewModeHint": "预览模式(⌘E 切换)", "common.editModeHint": "编辑模式(⌘E 切换)", "common.editUnsupportedType": "该格式不支持编辑", "common.copyAbsolutePath": "复制绝对路径", "common.copiedPath": "已复制绝对路径", "common.duplicateFile": "创建副本", "common.reloadFromDisk": "从磁盘刷新", "common.resizePanes": "拖动调整两栏宽度(双击恢复默认)", "common.reloadedFromDisk": "已从磁盘重新加载", "common.reloadFailed": "读取文件失败", "common.revert": "还原", "common.revertToOpened": "还原到打开文件时的内容", "common.revertUnavailable": "当前内容与打开文件时一致", "common.reverted": "已还原到打开文件时的内容", "common.saveNothingToSave": "没有需要保存的修改", "common.saveHandledByAutoSave": "自动保存已接管,无需手动保存", "common.moreTools": "更多工具", "common.undo": "撤销", "common.quoteFileToChat": "引用文件到 Chat", "common.undoChatEdit": "撤销 AI 修改", "common.chatEditConflict": "内容已被修改,无法安全撤销。", "common.redo": "重做", "common.fit": "适应", "common.zoomPercent": "缩放比例,可输入百分比", "duplicate.readFailed": "读取文件内容失败,无法创建副本", "duplicate.failed": "创建副本失败:{error}", "duplicate.success": "已创建副本 {fileName}", "duplicate.namePattern": "{name}(副本)", "duplicate.namePatternIndexed": "{name}(副本 {index})", "document.translate": "翻译文档并保存为新文件或覆盖原文件", "document.translateShort": "译文…", "document.translateTitle": "导出译文", "document.translateModeLabel": "输出内容", "document.translateModeTranslationFirst": "双语(译文在上)", "document.translateModeOriginalFirst": "双语(译文在下)", "document.translateModeReplace": "仅译文", "document.translateLanguageLabel": "目标语言", "document.translateOutputLabel": "保存方式", "document.translateOutputNew": "导出为新文件", "document.translateOutputOverwrite": "覆盖当前文件", "document.translateSummaryExport": "将翻译 {count} 个段落,结果导出为 {fileName},保存到下载目录。代码块、表格、链接地址保持原样。翻译会消耗账户用量。", "document.translateSummaryOverwrite": "将翻译 {count} 个段落并覆盖当前文件。代码块、表格、链接地址保持原样。翻译会消耗账户用量。", "document.translateNothing": "没有需要翻译的正文段落", "document.translateUnavailable": "翻译服务不可用,请确认翻译扩展已启用", "document.translateStart": "开始翻译", "document.translateCancel": "取消", "document.translateRunning": "正在翻译 {completed}/{total}", "document.translateCancelled": "已取消翻译", "document.translateFailed": "翻译失败:{error}", "document.translateSourceChanged": "文档在翻译期间被修改,已放弃覆盖保存", "document.translateDoneOverwrite": "已翻译 {count} 个段落并保存", "document.translateDoneExported": "已翻译 {count} 个段落,已导出 {fileName} 到下载目录", "document.translateKeptOriginal": ";{count} 个段落未能翻译,保留原文", "document.translateAllRejected": "译文会破坏文档格式,未写入任何内容", "markdown.toc": "目录", "markdown.tocResizeAria": "调整目录宽度", "markdown.tocResizeTitle": "拖拽调整目录宽度(双击恢复默认)", "markdown.readingWidth": "阅读宽度", "markdown.widthFixed": "默认", "markdown.widthFull": "全宽", "markdown.fontSize": "字号", "markdown.fontDecrease": "减小字号", "markdown.fontIncrease": "增大字号", "markdown.fontMedium": "中", "markdown.fontLarge": "大", "markdown.fontXLarge": "超大", "markdown.heading1": "一级标题", "markdown.heading2": "二级标题", "markdown.heading3": "三级标题", "markdown.heading4": "四级标题", "markdown.heading5": "五级标题", "markdown.strikethrough": "删除线", "markdown.inlineCode": "行内代码", "markdown.unorderedList": "无序列表", "markdown.orderedList": "有序列表", "markdown.taskList": "任务列表", "markdown.blockquote": "引用", "markdown.codeBlock": "代码块", "markdown.insertTable": "插入表格", "markdown.horizontalRule": "分割线", "markdown.clearFormat": "清除格式", "common.shortcutHint": "{label}({shortcut})", "markdown.tableHeader": "列", "markdown.tableCell": "内容", "markdown.editModeSwitch": "编辑方式", "markdown.editSource": "源码", "markdown.editSplit": "分屏", "markdown.editInline": "即时编辑", "markdown.inlineLoading": "正在加载即时编辑器...", "markdown.codeCopied": "已复制", "markdown.statLines": "行数", "markdown.statWords": "词数", "markdown.exportHtml": "HTML", "markdown.exportHtmlDone": "已导出 HTML", "markdown.exportHtmlFailed": "导出 HTML 失败", "markdown.exportPdf": "PDF", "markdown.exportPdfWorking": "正在生成 PDF…", "markdown.exportPdfDone": "已导出 PDF", "markdown.exportPdfFailed": "导出 PDF 失败", "markdown.exportImage": "长图 (PNG)", "markdown.exportImageWorking": "正在生成长图…", "markdown.exportImageDone": "已导出长图", "markdown.exportImageFailed": "导出长图失败", "markdown.exportImageTooTall": "文档太长,无法生成单张长图", "markdown.exportUnavailable": "当前环境不支持此导出方式", "markdown.inlineLoadFailed": "即时编辑器加载失败,已切回源码视图", "inline.placeholder": "开始输入…", "inline.codeSearchLanguage": "搜索语言", "inline.codeNoResult": "无结果", "inline.codePreview": "预览", "inline.codePreviewLoading": "正在渲染图表...", "inline.codeHideCode": "隐藏代码", "inline.linkPlaceholder": "粘贴链接...", "inline.imageUrlPlaceholder": "粘贴图片链接...", "markdown.searchPlaceholder": "在文档中搜索", "markdown.searchPrev": "上一个匹配", "markdown.searchNext": "下一个匹配", "markdown.searchClose": "关闭搜索", "markdown.imagePasteFailed": "图片保存失败", "markdown.formatTables": "格式化表格", "markdown.frontmatter": "文档属性", "markdown.frontmatterKey": "属性名", "markdown.frontmatterValue": "属性值", "markdown.frontmatterEmptyValue": "没有值", "markdown.frontmatterAdd": "添加文档属性", "markdown.frontmatterRemove": "移除该属性", "markdown.frontmatterNewKey": "新属性", "markdown.largeSourceOnly": "大文件仅支持源码编辑", "markdown.splitTooNarrow": "窗口太窄,放不下分屏的两栏", "markdown.footnotes": "脚注", "markdown.footnoteBackref": "返回引用处", "markdown.alertNote": "说明", "markdown.alertTip": "提示", "markdown.alertImportant": "重要", "markdown.alertWarning": "警告", "markdown.alertCaution": "危险", "markdown.alertAbstract": "摘要", "markdown.alertInfo": "信息", "markdown.alertTodo": "待办", "markdown.alertSuccess": "成功", "markdown.alertQuestion": "问题", "markdown.alertFailure": "失败", "markdown.alertDanger": "危险", "markdown.alertBug": "缺陷", "markdown.alertExample": "示例", "markdown.alertQuote": "引用", "markdown.searchToggleReplace": "显示替换", "markdown.searchCaseSensitive": "区分大小写", "markdown.searchReplacePlaceholder": "替换为", "markdown.searchReplaceOne": "替换", "markdown.searchReplaceAll": "全部替换", "svg.sourceEditorAria": "SVG 源码编辑器", "code.lineNumbers": "行号", "code.wordWrap": "自动换行", "code.highlightLanguage": "语法高亮", "code.jsonRawStyle": "JSON 显示方式", "code.jsonStyleOriginal": "原始", "code.jsonStylePretty": "格式化", "code.jsonStyleTree": "树形", "code.dataViewMode": "数据视图", "code.dataTreeLoading": "解析中…", "code.dataTreeParseError": "解析失败", "code.jsonStyleCompact": "压缩", "code.jsonFormatPretty2": "格式化(2 空格)", "code.jsonFormatPretty4": "格式化(4 空格)", "code.jsonFormatCompact": "压缩", "code.invalidJson": "JSON 无效,无法执行该操作", "code.compactApplied": "已压缩", "code.jsonValid": "JSON 有效", "code.jsonInvalid": "JSON 无效", "code.jsonExpandNode": "展开", "code.jsonCollapseNode": "折叠", "code.jsonTreeRoot": "根节点", "code.jsonTreeAria": "JSON 结构树", "code.jsonNodeKeys": "{count} 个键", "code.jsonNodeItems": "{count} 项", "code.jsonFoldLimited": "结构超过 {lines} 行,已关闭折叠", "code.lineNumbersUnavailable": "当前显示方式没有行号", "code.csvViewMode": "表格显示方式", "code.csvViewTable": "表格", "code.csvViewRaw": "原始文本", "code.csvDimensions": "{rows} 行 × {columns} 列", "code.jsonlViewMode": "JSON Lines 显示方式", "code.jsonlRecords": "{count} 条记录", "code.jsonlValid": "每一行都是有效 JSON", "code.jsonlInvalidSummary": "{count} 行不是有效 JSON,首个在第 {line} 行", "code.jsonlInvalidLine": "第 {line} 行不是有效 JSON:{text}", "code.jsonlInvalid": "不是有效 JSON", "code.jsonlLineLabel": "第 {line} 行", "code.jsonlLineColumn": "行", "code.jsonlValueColumn": "值", "code.jsonlTreeLimited": "仅显示前 {count} 行;编辑态保留完整原始文本。", "code.errorLinesOnly": "仅显示错误行", "code.errorLinesMatched": "仅显示错误行:{count} 行命中", "code.noErrorLines": "(无匹配的错误行)", "office.richPreviewReadOnly": "当前排版预览暂不支持编辑", "directory.sortBy": "排序方式", "directory.sortByName": "名称", "directory.sortByKind": "类型", "directory.sortDirection": "排序方向", "directory.sortAscending": "升序", "directory.sortDescending": "降序", "directory.viewMode": "视图", "directory.viewList": "列表", "directory.viewGrid": "网格", "directory.kindFolder": "文件夹", "create.inProgress": "正在新建 {fileName}...", "create.success": "已新建", "create.failed": "新建失败:{error}", "create.missingDirectory": "无法确定当前文件夹,不能新建文件。", "create.workspaceUnavailable": "当前环境未开放 chrome.tabLocalWorkspace,无法新建文件。", "create.directoryNotWritable": "当前文件夹未授权写入,无法新建文件。", "create.new": "新建", "create.newFileType": "新建文件类型", "outline.empty": "没有可展示的目录", "outline.aria": "文档大纲", "outline.title": "文档大纲", "outline.collapseAll": "全部折叠", "outline.expandAll": "全部展开", "outline.toggleAria": "折叠或展开子标题", "directory.aria": "文件夹树", "directory.backToRoot": "回到根目录", "directory.backToParent": ".. 上级目录", "directory.empty": "空文件夹", "directory.loading": "正在加载...", "directory.expandFailed": "无法展开文件夹", "directory.readFailed": "无法读取文件夹", "directory.previewDescription": "文件夹内的文件和子文件夹如下所示,也会同步显示在左侧目录树中。", "directory.previewItemCount": "包含条目", "directory.previewPath": "路径", "directory.resizeAria": "调整侧栏宽度", "directory.resizeTitle": "拖拽调整侧栏宽度", "directory.settings": "文件夹选项", "directory.showHiddenFiles": "显示隐藏项目", "directory.foldersFirst": "文件夹优先", "directory.rememberSidebarWidth": "记住侧栏宽度", "directory.openSettingsPage": "设置项", "directory.newFolder": "新建文件夹", "directory.newFolderName": "未命名文件夹", "directory.newFolderFailed": "新建文件夹失败:{error}", "directory.rename": "重命名", "directory.renameAria": "新名称", "directory.moved": "已移动到「{folder}」", "directory.moveFailed": "移动失败:{error}", "directory.moveExists": "「{folder}」中已有同名项目", "directory.extensionLocked": "这类文件不能修改扩展名", "directory.newFolderItem": "文件夹", "directory.favoritesMenu": "收藏的文件夹", "directory.favoritesEmpty": "还没有收藏的文件夹。在文件夹的「⋯」菜单里,或点文件头的星标即可收藏。", "trash.placeTrash": "废纸篓", "trash.placeRecycleBin": "回收站", "trash.action": "移到{place}", "trash.confirmTitle": "移到{place}?", "trash.confirmFile": "「{name}」将被移到{place},之后可以从{place}中恢复。", "trash.confirmFolder": "文件夹「{name}」及其中的全部内容将被移到{place},之后可以从{place}中恢复。", "trash.done": "已移到{place}", "trash.failed": "移到{place}失败:{error}", "favorites.add": "收藏", "favorites.remove": "取消收藏", "favorites.added": "已收藏", "favorites.removed": "已取消收藏", "popup.settings": "设置", "popup.favorites": "收藏", "popup.recents": "最近打开", "popup.favoritesEmpty": "在文件头点击星标,即可把文件或文件夹收藏到这里。", "popup.recentsEmpty": "最近打开的文件和文件夹会显示在这里。", "settings.title": "Previewer 设置", "settings.general": "通用", "settings.showHiddenFilesDescription": "在文件夹树与文件夹概览中显示以点开头的隐藏文件。", "settings.rememberSidebarWidthDescription": "下次打开文件夹时恢复上次调整的侧栏宽度。", "settings.documentTitle": "用文档标题命名标签页", "settings.documentTitleDescription": "预览 Markdown、MDX、HTML 时,用文档自己的标题(frontmatter 标题、一级标题、)命名标签页;文档没有标题时仍用文件名。", "settings.lineNumbers": "显示行号", "settings.lineNumbersDescription": "在代码与文本文件的预览和编辑器中显示行号。", "settings.wordWrap": "自动换行", "settings.wordWrapDescription": "超出宽度的长行自动折行显示,关闭后横向滚动查看。", "settings.syntax": "支持的 Markdown 语法", "settings.syntaxDescription": "Previewer 能识别的 Markdown 写法。基础部分遵循 GitHub Flavored Markdown,另外支持若干常见方言与两种图表围栏。", "settings.syntaxGroupBasic": "基础", "settings.syntaxGroupBlocks": "块级", "settings.syntaxGroupExtended": "扩展", "settings.syntaxHeading": "一到六级标题,自动生成大纲", "settings.syntaxEmphasis": "行内强调与代码", "settings.syntaxLinkImage": "链接与图片,相对路径按文档位置解析", "settings.syntaxList": "无序、有序与可勾选的任务列表", "settings.syntaxQuote": "引用块,可嵌套", "settings.syntaxRule": "水平分隔线", "settings.syntaxCodeFence": "围栏代码块,按语言高亮", "settings.syntaxTable": "表格,支持对齐冒号", "settings.syntaxDefinitionList": "定义列表", "settings.syntaxFootnote": "脚注引用与文末脚注区", "settings.syntaxAlert": "GFM 提示块(NOTE/TIP/IMPORTANT/WARNING/CAUTION)与 Obsidian Callout([!tip] 标题、+/- 折叠)", "settings.syntaxFrontmatter": "YAML frontmatter,可在顶部折叠面板中查看与编辑", "settings.syntaxHighlight": "高亮与插入标记", "settings.syntaxSubSup": "下标与上标", "settings.syntaxMath": "KaTeX 数学公式,也支持 \\( \\) 与 \\[ \\] 分隔符", "settings.syntaxMermaid": "Mermaid 图表;mindmap 语法渲染为脑图", "settings.syntaxSvg": "SVG,经白名单消毒后渲染", "settings.syntaxHtml": "静态展示类标签;脚本、事件属性与危险协议会被移除", "settings.shortcuts": "快捷键", "settings.shortcutsDescription": "编辑 Markdown 时可用的快捷键。按键随平台显示,macOS 上 Ctrl 显示为 ⌘。", "settings.shortcutGroupFormat": "行内格式", "settings.shortcutGroupBlocks": "块级格式", "settings.shortcutGroupDocument": "文档操作", "settings.fileTypes": "文件关联", "settings.fileTypesDescription": "Previewer 支持的文件类型。可查看每个类型当前的系统打开方式,将 Tabbit 设为系统默认,或选择在浏览器内使用 Previewer 还是浏览器自带方式打开。", "settings.currentHandler": "当前系统默认应用", "settings.currentHandlerSelf": "Tabbit(当前浏览器)", "settings.currentHandlerUnknown": "未检测到默认应用", "settings.detecting": "检测中...", "settings.detectUnavailable": "无法检测", "settings.systemDefault": "设为系统默认", "settings.systemDefaultActive": "已是系统默认打开方式", "settings.setAsDefault": "设为默认", "settings.setAllAsDefault": "全部设为默认", "settings.setAllNothingPending": "该分类目前没有可批量设为默认的项", "settings.unsetDefaultHint": "如需取消默认,请在系统设置中修改", "settings.openWith": "Tabbit 内打开方式", "settings.openWithPreviewer": "Tabbit Previewer", "settings.openWithNative": "浏览器自带", "settings.openWithNativeUnavailable": "该格式无法用浏览器自带方式正常显示", "settings.setDefaultRequested": "已请求将 Tabbit 设为 {extension} 的默认打开方式", "settings.openedSystemSettings": "已打开系统设置,请选择 Tabbit 作为 {extension} 的默认应用", "settings.setDefaultUnsupported": "当前系统暂不支持设置 {extension} 的默认应用", "settings.setDefaultFailed": "设置 {extension} 默认打开方式失败", "settings.type.markdown": "Markdown", "settings.type.svg": "SVG", "settings.type.image": "图片", "settings.type.media": "音频与视频", "settings.type.pdf": "PDF", "settings.type.office": "Office 文档", "settings.type.html": "HTML", "settings.type.codeText": "代码与文本", "settings.type.subtitle": "字幕", "settings.type.archive": "压缩包", "editor.sourceLabel": "文件内容", "markdown.contentLabel": "Markdown 内容", "code.contentLabel": "代码内容", "code.delimitedContentLabel": "表格原始文本", "code.tablePreviewAria": "{fileName} 表格预览", "code.unnamedColumn": "列 {index}", "code.tablePreviewLimited": "仅预览前 {rows} 行、前 {columns} 列;编辑态保留完整原始文本。", "code.noFormattingNeeded": "当前内容无需格式化", "code.formatApplied": "已应用格式化", "code.sourceReloaded": "{fileName} 源文件已重新读取", "code.formatJson": "格式化 JSON", "code.expandCode": "展开代码", "code.subtitleViewMode": "字幕视图", "code.subtitleTimeline": "时间轴", "code.subtitleSource": "源码", "code.subtitleInfoFormat": "格式", "code.subtitleInfoCues": "条数", "code.subtitleInfoDuration": "时长", "code.subtitleInfoLanguage": "语言", "code.subtitleInfoEncoding": "编码", "code.subtitleTimelineTruncated": "字幕过多,时间轴仅显示前 {shown} 条(共 {total} 条),完整内容请切换到源码视图", "code.subtitleSearchPlaceholder": "搜索台词,或输入 12:30 跳转", "code.subtitleCopyCue": "复制这条字幕", "code.subtitleTextView": "纯文本", "code.subtitleTextTruncated": "字幕过多,纯文本视图仅显示前 {shown} 条(共 {total} 条);「复制全文」仍包含全部内容", "code.subtitleBilingual": "双语", "code.subtitleEncodingTooltip": "检测到的文件编码", "code.subtitleTranscode": "转存为 UTF-8", "code.subtitleTranscoded": "已转存为 UTF-8,旧播放器不再乱码", "code.subtitleTranscodeFailed": "转存失败,请重试", "code.subtitleBinaryNotice": "这是二进制的位图字幕文件(VobSub 图形字幕),无法作为文本预览或编辑。请将它与同名 .idx 文件放在一起,在视频播放器中加载使用。", "code.subtitleExportMenu": "导出", "code.subtitleExportSrt": "SRT", "code.subtitleExportVtt": "VTT", "code.subtitleExportTxt": "纯文本 (TXT)", "code.subtitleExportUnparseable": "无法解析字幕内容,未导出", "code.subtitleExported": "已导出 {fileName}", "code.subtitleExportFailed": "导出失败,请重试", "code.searchPlaceholder": "查找", "code.searchPrev": "上一个", "code.searchNext": "下一个", "code.searchClose": "关闭", "code.replacePlaceholder": "替换为", "code.replaceOne": "替换", "code.replaceAll": "全部替换", "code.replacedCount": "已替换 {count} 处", "code.subtitleShiftMenu": "时轴平移", "code.subtitleShiftLabel": "偏移量(毫秒)", "code.subtitleShiftHint": "正值延后、负值提前,应用后直接保存。仅修正整体偏移,无法修复帧率漂移。", "code.subtitleShiftApply": "应用并保存", "code.subtitleShiftInvalid": "请输入非零的毫秒偏移量", "code.subtitleShifted": "已平移 {count} 条时间轴并保存", "code.subtitleShiftedClamped": "已平移并保存,部分时间被截止到 00:00", "code.subtitleShiftFailed": "平移保存失败,请重试", "code.subtitleShiftExported": "该格式不支持原位平移,已导出平移后的 {fileName}", "code.subtitleCleanMenu": "清理", "code.subtitleCleanTags": "去除样式标签", "code.subtitleCleanAnnotations": "去除括注说明(SDH)", "code.subtitleCleanMusic": "去除音乐符号行", "code.subtitleCleanDuplicates": "合并重复与空白条目", "code.subtitleCleaned": "已清理 {count} 处并保存", "code.subtitleCleanNothing": "没有需要清理的内容", "code.subtitleCleanFailed": "清理失败,请重试", "code.subtitleCleanExported": "该格式不支持原位清理,已导出清理后的 {fileName}", "code.subtitleTranslate": "翻译字幕并保存为新文件", "code.subtitleTranslateTitle": "翻译字幕", "code.subtitleTranslateModeLabel": "输出内容", "code.subtitleTranslateModeBilingual": "双语(译文在上)", "code.subtitleTranslateModeReplace": "仅译文", "code.subtitleTranslateLanguageLabel": "目标语言", "code.subtitleTranslateOutputLabel": "保存方式", "code.subtitleTranslateOutputNew": "另存为新文件", "code.subtitleTranslateOutputOverwrite": "覆盖当前文件", "code.subtitleTranslateOverwriteUnavailable": "仅本地 SRT 文件支持覆盖保存,当前文件将另存为新文件", "code.subtitleTranslateSummary": "将翻译 {count} 条字幕(约 {characters} 字符),已包含目标语言的条目会自动跳过。翻译会消耗账户用量。", "code.subtitleTranslateNothing": "没有需要翻译的条目(可能已包含目标语言)", "code.subtitleTranslateStart": "开始翻译", "code.subtitleTranslateCancel": "取消", "code.subtitleTranslateRunning": "正在翻译 {completed}/{total} 条…", "code.subtitleTranslateCancelled": "已取消,未修改文件", "code.subtitleTranslateUnavailable": "翻译服务不可用,请确认浏览器版本", "code.subtitleTranslateSourceChanged": "字幕内容已变化,请重新开始翻译", "code.subtitleTranslateFailed": "翻译失败:{error}", "code.subtitleTranslateDoneOverwrite": "已翻译 {count} 条并保存到当前文件", "code.subtitleTranslateDoneExported": "已翻译 {count} 条,导出为 {fileName}", "code.subtitleLangChinese": "中文", "code.subtitleLangJapanese": "日文", "code.subtitleLangKorean": "韩文", "code.subtitleLangLatin": "英文", "code.subtitleLangCyrillic": "西里尔文", "code.subtitleLangArabic": "阿拉伯文", "subtitle.binaryUnknown": "二进制 SUB", "subtitle.vobsubDescription": "检测到 MPEG Program Stream / DVD subpicture 数据;不会按文本解码或写回。", "subtitle.binaryDescription": "该 SUB 文件包含二进制数据,已停止文本解码以避免乱码或误写。", "subtitle.vobsubCompanionHint": "VobSub 画面、语言和时间索引通常由同名 .idx 文件描述;请同时打开配套 IDX 查看时间戳。", "subtitle.scannedBytes": "已检查头部", "subtitle.packHeaders": "MPEG pack 头", "subtitle.privateStreams": "字幕私有流包", "html.sourceLoading": "正在读取源码", "html.sourceEditorAria": "HTML source editor", "html.previewAria": "HTML preview", "html.previewFrameTitle": "{fileName} preview", "html.printFrameTitle": "{fileName} 打印预览", "html.preparing": "正在准备源码与预览", "html.viewMode": "HTML view mode", "html.modeSource": "源码", "html.modeSplit": "分屏", "html.modePreview": "预览", "html.reload": "重新读取", "html.sourceReloaded": "已重新读取源码", "html.sourceRead": "已读取 HTML 源码", "html.readFailed": "读取失败:无法读取 HTML 源文件", "html.readRequiresWorkspace": "读取失败:需要本地写回服务读取 HTML 源码", "html.previewUnavailable": "该文件可能已被移至别处、修改或删除。", "html.previewUnavailableInArchive": "压缩包内的 HTML 暂不支持预览。可在左侧查看源码,解压后打开即可正常预览。", "html.previewUnavailableInArchiveStatus": "暂不支持预览", "image.stageAria": "Image preview stage", "image.loadingTitle": "正在加载图片", "image.loadingMeta": "{extension} · 正在读取图片", "image.readyStatus": "可缩放、拖动、旋转和翻转", "image.zoomOut": "缩小", "image.zoomIn": "放大", "image.fit": "适应窗口", "image.actualSize": "100% 原始尺寸", "image.rotateLeft": "左转", "image.rotateRight": "右转", "image.flipHorizontal": "水平翻转", "image.flipVertical": "垂直翻转", "image.reset": "重置", "image.exportPng": "PNG", "image.loadFailedTitle": "图片加载失败", "image.loadFailedMeta": "{extension} · 加载失败", "image.loadFailedStatus": "无法读取当前图片,请检查链接是否仍然有效", "image.zoomedOut": "已缩小视图", "image.zoomedIn": "已放大视图,可拖动查看细节", "image.fitApplied": "已适应窗口", "image.actualSizeApplied": "已切换到 100% 原始尺寸", "image.resetApplied": "已重置变换并适应窗口", "image.wheelZoomed": "滚轮缩放视图,可拖动查看细节", "image.transformed": "有图片变换", "image.panned": "已移动视图", "image.notLoaded": "图片尚未加载完成", "image.exportedPng": "已导出 PNG", "image.exportFailed": "导出失败,浏览器可能限制了当前本地图片的画布读取", "media.kind.audio": "音频", "media.kind.video": "视频", "media.loading": "正在读取媒体信息", "media.playbackRate": "播放速度", "media.playbackRateValue": "播放速度:{rate}", "media.pictureInPicture": "画中画", "media.pictureInPictureActive": "已进入画中画模式", "media.pictureInPictureFailed": "无法进入画中画模式", "media.fullscreen": "全屏", "media.fullscreenActive": "已进入全屏模式", "media.fullscreenFailed": "无法进入全屏模式", "media.loadFailed": "媒体加载失败", "media.loadFailedDetail": "无法播放此媒体;容器或内部编码可能不受支持", "svg.stageAria": "SVG 预览区域,放大后可使用方向键移动视图", "pdf.loadingMeta": "PDF · 正在读取文件", "pdf.initialStatus": "使用 PDF.js 渲染,原始文件可作为浏览器兜底", "pdf.toggleNavigation": "显示或隐藏 PDF 导航", "pdf.navLabel": "导航", "pdf.hideNavigation": "隐藏 PDF 导航", "pdf.showNavigation": "显示 PDF 导航", "pdf.previousPage": "上一页", "pdf.nextPage": "下一页", "pdf.pageJumpTitle": "跳转到指定页码", "pdf.currentPage": "当前页码", "pdf.pageLabelTitle": "第 {page} 页 · 页码标签 {label}", "pdf.searchTitle": "搜索 PDF 文本", "pdf.searchPlaceholder": "搜索 PDF", "pdf.previousSearchResult": "上一个搜索结果", "pdf.nextSearchResult": "下一个搜索结果", "pdf.fitWidth": "适应宽度", "pdf.fitPage": "适应整页", "pdf.handTool": "拖拽平移", "pdf.handToolOn": "已开启拖拽平移:按住鼠标左键拖动页面", "pdf.handToolOff": "已关闭拖拽平移:拖动可选择文本", "pdf.rotateRight": "右转", "pdf.viewMode": "PDF 视图模式", "pdf.viewContinuous": "连续", "pdf.viewSingle": "单页", "pdf.viewTwoPage": "双页", "pdf.readAloud": "朗读当前页或所选文本", "pdf.readAloudLabel": "朗读", "pdf.rerender": "重新渲染", "pdf.navAria": "PDF 导航", "pdf.navTabsAria": "PDF 导航面板", "pdf.navTitle": "阅读导航", "pdf.tabPages": "页面", "pdf.tabOutline": "目录", "pdf.tabNotes": "备注", "pdf.infoLoading": "正在读取文档信息", "pdf.infoEmpty": "这个 PDF 没有可读取的文档信息", "pdf.propTitle": "标题", "pdf.propAuthor": "作者", "pdf.propSubject": "主题", "pdf.propKeywords": "关键词", "pdf.propCreator": "创建工具", "pdf.propProducer": "PDF 生成器", "pdf.propCreated": "创建时间", "pdf.propVersion": "PDF 版本", "pdf.infoAttachments": "附件", "pdf.attachmentSave": "另存附件 {name}", "pdf.attachmentSaving": "正在保存附件 {name}", "pdf.attachmentSaved": "已保存附件 {name}", "pdf.attachmentSaveFailed": "无法保存附件 {name}", "pdf.infoLayers": "图层", "pdf.layerUntitled": "未命名图层", "pdf.layerShown": "已显示图层「{name}」", "pdf.layerHidden": "已隐藏图层「{name}」", "pdf.infoSignature": "数字签名", "pdf.infoSignatureN": "数字签名 {index}", "pdf.metaSigned": "已签名", "pdf.signatureSigner": "签名人", "pdf.signatureTime": "签名时间", "pdf.signatureReason": "原因", "pdf.signatureLocation": "地点", "pdf.signatureContact": "联系方式", "pdf.signatureField": "签名域", "pdf.permission.print": "打印", "pdf.permission.copy": "复制文本", "pdf.permission.modify": "修改内容", "pdf.permission.annotate": "添加批注", "pdf.permission.forms": "填写表单", "pdf.permissionAllowed": "允许", "pdf.permissionRestricted": "作者已限制", "pdf.printRestricted": "这个 PDF 的作者限制了打印,打印结果可能受限", "pdf.copyRestricted": "这个 PDF 的作者限制了复制文本", "pdf.navLoading": "正在准备导航", "pdf.notePlaceholder": "为当前页记录备注", "pdf.noteHint": "备注只保存在这台设备,不会写入 PDF 原文件。", "pdf.noteAria": "当前 PDF 页面备注", "pdf.stageAria": "{fileName} PDF preview", "pdf.loadingTitle": "正在加载 PDF", "pdf.loadingDescription": "解析文件并准备页面渲染。", "pdf.zoomedOut": "已缩小视图", "pdf.zoomedIn": "已放大视图", "pdf.fitWidthApplied": "已适应宽度", "pdf.fitPageApplied": "已适应整页", "pdf.actualSizeApplied": "已切换到 100% 原始尺寸", "pdf.rotatedRight": "已右转 PDF 视图", "pdf.viewModeApplied": "已切换到 {mode} 视图", "pdf.rerenderingVisible": "正在重新渲染可见页面", "pdf.wheelZoomed": "滚轮缩放 PDF 视图", "pdf.readFailed": "无法读取 PDF 文件。可以尝试打开原始文件。", "pdf.parsing": "正在解析 PDF", "pdf.renderedPages": "PDF.js 渲染 · 共 {pages}", "pdf.pageAria": "第 {page} 页", "pdf.thumbnailTitle": "跳转到第 {page} 页", "pdf.pageHasNote": "这一页有备注", "pdf.waitingRender": "等待渲染", "pdf.rendering": "正在渲染", "pdf.renderFailed": "渲染失败", "pdf.noTranslatableText": "此页未检测到可翻译文本;扫描件需要 OCR 文字层。", "pdf.translationPageAria": "第 {page} 页翻译", "pdf.translatingPage": "正在翻译第 {page} 页…", "pdf.translationProgress": "已完成 {completed}/{total} 段", "pdf.translationFailed": "此页翻译失败", "pdf.translationRetry": "翻译失败,点击重试", "pdf.translationBudgetExceeded": "此页文字过多,已超过翻译上限(64 段或 48K 字符)。", "pdf.ocrTextTooLarge": "此页识别文字超过 64KiB,无法安全翻译。", "pdf.readingProgress": "正在读取 PDF · {percent}%", "pdf.searching": "正在搜索 PDF 文本", "pdf.searchFound": "找到 {count} 个匹配项", "pdf.searchNoResults": "没有找到匹配项", "pdf.searchCleared": "已清除搜索", "pdf.notePage": "第 {page} 页备注", "pdf.noteUnsaved": "备注未保存", "pdf.noteSaved": "备注已保存到本机", "pdf.marksTitle": "本页标注", "pdf.marksHint": "选中正文后可高亮或下划线。标注只保存在本机,不写入 PDF。", "pdf.marksEmpty": "这一页还没有标注", "pdf.markHighlight": "高亮", "pdf.markUnderline": "下划线", "pdf.markCopy": "复制", "pdf.toolHighlight": "高亮", "pdf.toolInk": "画笔", "pdf.toolFreeText": "文字", "pdf.toolStamp": "图片", "pdf.editUnavailable": "当前 PDF.js 版本不支持标注编辑", "pdf.editRestricted": "文档作者已限制添加批注", "pdf.exportAnnotated": "导出一份带标注的副本,原文件不变", "pdf.exportAnnotatedLabel": "带标注的 PDF 副本", "pdf.exportAnnotatedNeedsEdit": "进入编辑模式并添加标注后,可导出带标注的副本", "pdf.exportAnnotatedDone": "已导出 {name}", "pdf.exportAnnotatedFailed": "导出带标注副本失败", "pdf.annotatedCopySuffix": "标注", "pdf.saveAnnotations": "把标注写入原 PDF 文件", "pdf.saveAnnotationsDone": "标注已写入原文件", "pdf.saveAnnotationsFailed": "标注写入原文件失败", "pdf.saveAnnotationsConfirmTitle": "写入原 PDF 文件?", "pdf.saveAnnotationsConfirmDescription": "将把本次编辑的标注写入「{fileName}」本体,覆盖磁盘上的原文件。预览模式的高亮与下划线只存在本机,不会一起写入。", "pdf.saveAnnotationsConfirm": "写入原文件", "pdf.annotationsEmpty": "还没有可保存的标注", "pdf.markAdded": "已添加{kind},只保存在本机", "pdf.markRemoved": "已删除标注", "pdf.markDelete": "删除标注", "pdf.markJump": "定位到这条{kind}", "pdf.copied": "已复制所选文本", "pdf.copyFailed": "复制失败", "pdf.selectionToolbarAria": "所选文本操作", "pdf.outlineLoading": "正在读取目录", "pdf.outlineUnavailable": "当前 PDF 没有可读取目录", "pdf.outlineEmpty": "这个 PDF 没有目录", "pdf.outlineUntitled": "未命名目录项", "pdf.outlineGoTo": "跳转到第 {page} 页", "pdf.outlineExternal": "打开外部链接", "pdf.linkInternal": "跳转到文档内的链接目标", "pdf.linkUnresolved": "这个链接的目标页面无法定位", "pdf.annotationAria": "批注:{summary}", "pdf.readAloudStopped": "已停止朗读", "pdf.readAloudNoText": "当前页没有可朗读文本", "pdf.readAloudFinished": "朗读结束", "pdf.readAloudFailed": "朗读失败", "pdf.readAloudSelection": "正在朗读所选文本", "pdf.readAloudPage": "正在朗读第 {page} 页", "pdf.previewFailedTitle": "PDF 预览失败", "pdf.passwordRequired": "这个 PDF 需要密码,当前预览暂不处理加密 PDF。", "pdf.renderErrorDetail": "PDF.js 无法渲染此文件:{detail}", "pdf.renderError": "PDF.js 无法渲染此文件。", "pdf.canvasUnavailable": "当前浏览器无法创建 PDF canvas。", "office.loadingTitle": "正在读取文件", "office.loadingDescription": "会尽量提取可预览的内容。", "office.kindStatus": "{kind} 文档", "office.parsing": "正在解析文档内容", "office.readFailed": "无法读取当前本地文件。", "office.unsupportedExtension": "暂不支持该 Office 扩展名。", "office.parseFailed": "文档解析失败,可能是加密文件、损坏文件或浏览器不支持 ZIP 解压。", "office.noDocxBody": "没有找到 DOCX 正文内容。", "office.docxMeta": "DOCX · {paragraphs} · 只读文本预览,复杂样式以原文件为准", "office.docxBodyLabel": "DOCX 正文", "office.docxReady": "已读取 DOCX,当前为只读文本预览", "office.docxRendering": "正在渲染 DOCX 预览", "office.docxRendered": "已渲染 DOCX 预览", "office.docxFitWidth": "适应宽度", "office.docxZoomedOut": "已缩小 DOCX 预览", "office.docxZoomedIn": "已放大 DOCX 预览", "office.docxFitWidthApplied": "已适应 DOCX 页面宽度", "office.docxActualSizeApplied": "已切换到 DOCX 100% 原始尺寸", "office.noXlsxSheets": "没有找到 XLSX 工作表。", "office.sheet": "工作表", "office.sheetCount": "共 {count}", "office.currentSheetTsv": "当前工作表 TSV", "office.sheetSwitched": "已切换工作表", "office.noPptxSlides": "没有找到 PPTX 幻灯片。", "office.pptxMissingViewer": "PptxViewJS 未加载,已切换到文本大纲预览", "office.pptxRendering": "正在使用 PptxViewJS 渲染 PPTX", "office.pptxReady": "已读取 PPTX,当前为只读文本大纲预览", "office.pptxTextLabel": "PPTX 幻灯片文本", "office.pptxOutlineTitle": "文本大纲", "office.pptxOutlineDescription": "查看幻灯片文本", "office.pptxBackToSlides": "返回幻灯片预览", "office.pptxViewerLoading": "正在加载 {count}", "office.pptxPreviousPage": "上一页", "office.pptxNextPage": "下一页", "office.pptxZoomOut": "缩小", "office.pptxZoomIn": "放大", "office.pptxFit": "适应", "office.pptxViewerMountFailed": "PPTX 预览器挂载失败,已切换到文本大纲预览", "office.pptxRenderingPage": "正在渲染第 {page} 页", "office.pptxViewerReady": "PptxViewJS 预览", "office.pptxViewerReadyStatus": "已使用 PptxViewJS 渲染 PPTX", "office.pptxRenderFailed": "PptxViewJS 渲染失败,已切换到文本大纲预览", "office.legacyUnsupportedStatus": "旧版 Office 二进制格式暂不支持浏览器内解析", "office.legacyUnsupportedTitle": "{extension} 暂不支持预览", "office.legacyUnsupportedDescription": "轻量方案先支持 .docx / .xlsx / .pptx。旧版格式建议另存为新版 OOXML 后再打开。", "office.parseErrorTitle": "无法解析文档", "office.emptyDocxTitle": "没有提取到正文", "office.emptyDocxDescription": "该文档可能主要由图片、图表或受保护内容组成。", "office.emptyXlsxTitle": "没有可展示的表格内容", "office.emptyXlsxDescription": "可以切换到编辑态填写 TSV 内容后保存。", "office.chart": "图表", "office.chartSeries": "系列 {index}", "office.chartUnresolved": "图表结构已识别,但引用的数据范围暂时无法解析。", "office.embeddedChart": "嵌入图表", "office.emptyPptxTitle": "没有提取到幻灯片文本", "office.emptyPptxDescription": "该演示文稿可能主要由图片或图形组成。", "office.blankSlide": "空白幻灯片", "office.xlsxMeta": "XLSX · {sheetName} · {sheetCount} · 保存当前显示值,复杂公式/样式以轻量兼容为准", "office.pptxMeta": "PPTX · {slides} · {images} · 轻量编辑会保存文本,图片/版式保留原文件结构", "unsupported.displayName": "不支持的文件", "unsupported.description": "当前文件类型无法直接预览,可以在文件管理器中定位。", "unsupported.type": "类型", "unsupported.path": "路径", "unsupported.size": "大小", "unsupported.created": "创建时间", "unsupported.modified": "修改时间", "unsupported.reading": "正在读取...", "unsupported.revealRequested": "已请求文件管理器定位", "unsupported.revealFailed": "无法定位文件", "unsupported.unreadable": "无法读取", "zip.displayName": "压缩包", "zip.kicker": "Archive", "zip.folderKicker": "Archive folder", "zip.entryKicker": "Archive entry", "zip.loading": "正在读取压缩包...", "zip.ready": "已读取压缩包索引", "zip.refresh": "从磁盘重新读取", "zip.description": "在左侧目录树中展开即可浏览压缩包内的文件,无需解压。", "zip.emptyArchive": "这个压缩包里没有文件。", "zip.entries": "文件条目", "zip.childEntries": "当前层级", "zip.uncompressedSize": "原始大小", "zip.compressedSize": "压缩后大小", "zip.ratio": "压缩率", "zip.archiveSize": "压缩包大小", "zip.archive": "所属压缩包", "zip.entryPath": "包内路径", "zip.readOnly": "压缩包内的文件是只读的,无法修改或重命名。", "zip.readFailed": "无法读取这个压缩包。", "zip.tooLarge": "压缩包超过 {size} 的预览上限,未读取内容。", "zip.corrupt": "文件已损坏,或者不是有效的 ZIP 压缩包。", "zip.skippedUnsafe": "已跳过 {count} 个路径越界的条目。", "zip.truncated": "条目过多,仅索引前 {count} 项。", "zip.encryptedEntries": "其中 {count} 个条目已加密,无法预览。", "zip.entryEncrypted": "这个条目已加密,需要密码才能解压,无法预览。", "zip.entryUnsupportedMethod": "这个条目使用了浏览器不支持的压缩算法,无法预览。", "zip.entryTooLarge": "这个条目解压后超过单条目预览上限,未展开。", "zip.entryRatio": "这个条目的压缩比异常,出于安全考虑没有解压。", "zip.entryCorrupt": "这个条目已损坏,无法解压。", "zip.entryNotFound": "压缩包中找不到这个条目。", "fileType.directory": "文件夹", "fileType.markdown": "Markdown", "fileType.svg": "SVG", "fileType.image": "图片", "fileType.pdf": "PDF", "fileType.office": "Office", "fileType.html": "HTML", "fileType.code-text": "代码 / 文本", "fileType.unsupported": "不支持的文件" }, en: { "app.description": "Local file rendering", "viewer.openingTitle": "Opening file", "viewer.openingDescription": "Previewer is preparing the file preview.", "common.fileName": "File Name", "common.fileSize": "Size", "common.fileCreated": "Created", "common.fileModified": "Modified", "common.today": "Today", "common.yesterday": "Yesterday", "common.filePath": "File Path", "common.pathEllipsis": "...", "common.showCollapsedPath": "Show collapsed middle path", "common.collapsedPathSegments": "Collapsed middle path", "common.fileActions": "File Actions", "common.fileInfo": "File Info", "common.moreActions": "More Actions", "common.renameFile": "Rename File", "common.expandExplorer": "Expand Explorer", "common.collapseExplorer": "Collapse Explorer", "common.expandHeader": "Expand File Header", "common.collapseHeader": "Collapse File Header", "common.rawFile": "Raw File", "common.openInNewTab": "Open in New Tab", "common.save": "Save", "common.saveAs": "Save As...", "common.downloadRemoteFile": "Download to Local Device", "common.print": "Print", "print.preparing": "Preparing content for printing…", "print.failed": "Unable to prepare content for printing", "print.notReady": "The content is not ready yet. Please try again shortly.", "print.pageAlt": "Print page {page}", "print.namedPageAlt": "{fileName}, page {page}", "print.preparingPage": "Preparing page {page} of {total}…", "print.preparingSlide": "Preparing slide {slide} of {total}…", "print.namedSlideAlt": "{fileName}, slide {slide}", "common.edit": "Edit", "common.preview": "Preview", "common.copy": "Copy", "common.copyAllText": "Copy All", "common.export": "Export", "common.revealInFileManager": "Show in File Manager", "common.revealInFinder": "Show in Finder", "common.revealInExplorer": "Show in Explorer", "common.revealUnavailableRemote": "Online pages cannot be shown", "common.editUnavailableRemote": "Online pages cannot be edited", "common.fileUnavailable": "This file may have been moved, changed, or deleted.", "common.unknown": "Unknown", "common.unknownError": "Unknown error", "common.untitled": "Untitled", "common.localFile": "Local file", "common.loading": "Loading...", "common.notModified": "Not modified", "common.unsavedChanges": "Unsaved changes", "common.unexportedChanges": "Unexported changes", "common.copiedToClipboard": "Copied to clipboard", "common.copiedSelectedText": "Copied selected text", "common.copyFailed": "Copy failed", "common.saving": "Saving...", "common.exporting": "Exporting...", "common.savingAs": "Saving as...", "common.downloadingRemoteFile": "Downloading...", "common.savedToOriginal": "Saved to original file", "common.saved": "Saved", "common.savedAs": "Saved as", "common.downloadedRemoteFile": "Downloaded to local device", "common.exportedToDownloads": "Exported to Downloads", "common.cancelledExport": "Export canceled", "common.cancelledSaveAs": "Save as canceled", "common.cancelledDownload": "Download canceled", "common.cancel": "Cancel", "common.saveFailedWorkspace": "Save failed: unable to write the current workspace file", "common.exportFailed": "Export failed", "common.saveAsFailed": "Save as failed", "common.downloadRemoteFailed": "Download failed: unable to read the online file", "common.downloadUnavailable": "This file cannot be downloaded", "common.fileContent": "File content", "common.textFile": "Text file", "common.filePickerLocalFile": "Local file", "editor.clean": "Synced", "editor.external": "External edit", "editor.externalHint": "Another program is editing this file; its changes are synced here automatically", "changes.unreadAbove": "{count} unread above", "changes.unreadBelow": "{count} unread below", "changes.markAllRead": "Mark all as read", "changes.jump": "Jump to this change", "changes.added": "Added externally", "changes.modified": "Modified externally", "changes.deleted": "Lines removed externally", "editor.readonly": "Read only", "editor.dirty": "Unsaved", "editor.saving": "Saving", "editor.saved": "Saved", "editor.autoSavePending": "Autosaving", "editor.autoSaved": "Autosaved", "editor.savedAsUtf8": "The file was {encoding}-encoded and cannot hold this content; it was saved as UTF-8", "editor.error": "Save failed", "editor.conflict": "File conflict", "editor.draftRestored": "Recovered unsaved draft", "editor.draftOutOfSpace": "Browser storage is full, so this document has no recovery draft. Nothing was cut from your text — save it to the file soon.", "editor.unsavedTitle": "Save changes?", "editor.unsavedDescription": "“{fileName}” has unsaved changes.", "editor.saveBeforeLeave": "Save changes to “{fileName}” before leaving?", "editor.discardBeforeLeave": "Discard unsaved changes to “{fileName}”?", "editor.discard": "Discard changes", "editor.beforeUnload": "The current file has unsaved changes.", "editor.revertTitle": "Revert to the opened content?", "editor.revertDescription": "This discards every change made to “{fileName}” since it was opened, including changes already autosaved to disk.", "editor.revertConfirm": "Revert", "editor.conflictTitle": "File changed elsewhere", "editor.conflictDescription": "The copy of “{fileName}” on disk has changed. To avoid silently overwriting someone else's work, saving can continue only after explicit confirmation. You can also cancel and use Save As.", "editor.overwrite": "Overwrite anyway", "markdown.bold": "Bold", "markdown.italic": "Italic", "markdown.insertLink": "Insert Link", "markdown.insertImage": "Insert Image", "markdown.widthDecrease": "Decrease Content Width", "markdown.contentWidth": "Content Width", "markdown.widthIncrease": "Increase Content Width", "markdown.taskToggle": "Toggle task status", "markdown.taskReadonlyRemote": "Online Markdown is read-only. Download it locally to edit tasks.", "large.previewTitle": "Large-file safety preview", "large.previewDescription": "Loaded the first {previewSize}. More content loads on demand as you scroll; editing and saving are disabled.", "large.previewAria": "{kind} large-file preview fragment", "large.previewMeta": "{kind} · large-file preview · {size}", "large.blockedTitle": "File too large for automatic preview", "large.blockedDescription": "This {kind} file is {size}. Previewer did not read the full content to avoid crashing the browser. Open the raw file or reveal it in the file manager instead.", "large.blockedMeta": "{kind} · file too large · {size}", "large.emptyPreview": "Unable to read a safe preview fragment.", "large.virtualStatus": "Showing {start} - {end} / {total}", "large.virtualLoading": "Loading the next chunk...", "large.virtualError": "Loading failed. Scroll again to retry.", "large.directoryLimited": "Showing only the first {count} items. Very large folders are not rendered all at once.", "toolbar.reveal": "Show in file manager", "theme.followingBrowser": "Following browser {theme} theme. Click to temporarily pin {nextTheme}.", "theme.fixed": "{theme} theme pinned. Click to follow the browser again.", "theme.dark": "dark", "theme.light": "light", "rename.inProgress": "Renaming...", "rename.success": "Renamed", "rename.failed": "Rename failed: {error}", "rename.invalidName": "File name contains invalid characters", "rename.duplicateName": "A file with this name already exists in the current folder", "rename.extensionNone": "no extension", "rename.extensionChangeTitle": "Confirm extension change", "rename.extensionChangeDescription": "The extension of \"{fileName}\" is about to change to \"{extension}\". This changes how the file opens. Rename anyway?", "rename.extensionChangeConfirm": "Rename", "common.panelMode": "Preview / edit mode", "common.previewModeHint": "Preview mode (⌘E to toggle)", "common.editModeHint": "Edit mode (⌘E to toggle)", "common.editUnsupportedType": "This format does not support editing", "common.copyAbsolutePath": "Copy Absolute Path", "common.copiedPath": "Absolute path copied", "common.duplicateFile": "Create a Copy", "common.reloadFromDisk": "Reload from Disk", "common.resizePanes": "Drag to resize the panes (double-click to reset)", "common.reloadedFromDisk": "Reloaded from disk", "common.reloadFailed": "Failed to read the file", "common.revert": "Revert", "common.revertToOpened": "Revert to the content the file was opened with", "common.revertUnavailable": "Already matches the content the file was opened with", "common.reverted": "Reverted to the content the file was opened with", "common.saveNothingToSave": "No changes to save", "common.saveHandledByAutoSave": "Autosave is handling this file", "common.moreTools": "More Tools", "common.undo": "Undo", "common.quoteFileToChat": "Reference file in Chat", "common.undoChatEdit": "Undo AI edit", "common.chatEditConflict": "The content has changed; this edit cannot be safely undone.", "common.redo": "Redo", "common.fit": "Fit", "common.zoomPercent": "Zoom level, type a percentage", "duplicate.readFailed": "Failed to read the file; cannot create a copy", "duplicate.failed": "Failed to create a copy: {error}", "duplicate.success": "Created copy {fileName}", "duplicate.namePattern": "{name} (copy)", "duplicate.namePatternIndexed": "{name} (copy {index})", "document.translate": "Translate this document into a new file or over the original", "document.translateShort": "Translation…", "document.translateTitle": "Export Translation", "document.translateModeLabel": "Output", "document.translateModeTranslationFirst": "Bilingual (translation first)", "document.translateModeOriginalFirst": "Bilingual (original first)", "document.translateModeReplace": "Translation only", "document.translateLanguageLabel": "Target language", "document.translateOutputLabel": "Save as", "document.translateOutputNew": "Export a new file", "document.translateOutputOverwrite": "Overwrite this file", "document.translateSummaryExport": "Translates {count} blocks and exports {fileName} to your downloads. Code blocks, tables and link targets stay as they are. Translation uses your account quota.", "document.translateSummaryOverwrite": "Translates {count} blocks and overwrites this file. Code blocks, tables and link targets stay as they are. Translation uses your account quota.", "document.translateNothing": "There is no prose to translate", "document.translateUnavailable": "Translation is unavailable; check that the translation extension is enabled", "document.translateStart": "Translate", "document.translateCancel": "Cancel", "document.translateRunning": "Translating {completed}/{total}", "document.translateCancelled": "Translation cancelled", "document.translateFailed": "Translation failed: {error}", "document.translateSourceChanged": "The document changed while translating; the overwrite was abandoned", "document.translateDoneOverwrite": "Translated {count} blocks and saved", "document.translateDoneExported": "Translated {count} blocks and exported {fileName} to your downloads", "document.translateKeptOriginal": "; {count} blocks could not be translated and keep their original text", "document.translateAllRejected": "the translation would have broken the document's syntax, so nothing was written", "markdown.toc": "TOC", "markdown.tocResizeAria": "Resize table of contents width", "markdown.tocResizeTitle": "Drag to resize the table of contents (double-click to reset)", "markdown.readingWidth": "Reading Width", "markdown.widthFixed": "Fixed Width", "markdown.widthFull": "Full Width", "markdown.fontSize": "Font Size", "markdown.fontDecrease": "Decrease Font Size", "markdown.fontIncrease": "Increase Font Size", "markdown.fontMedium": "M", "markdown.fontLarge": "L", "markdown.fontXLarge": "XL", "markdown.heading1": "Heading 1", "markdown.heading2": "Heading 2", "markdown.heading3": "Heading 3", "markdown.heading4": "Heading 4", "markdown.heading5": "Heading 5", "markdown.strikethrough": "Strikethrough", "markdown.inlineCode": "Inline Code", "markdown.unorderedList": "Bulleted List", "markdown.orderedList": "Numbered List", "markdown.taskList": "Task List", "markdown.blockquote": "Blockquote", "markdown.codeBlock": "Code Block", "markdown.insertTable": "Insert Table", "markdown.horizontalRule": "Horizontal Rule", "markdown.clearFormat": "Clear Formatting", "common.shortcutHint": "{label} ({shortcut})", "markdown.tableHeader": "Column", "markdown.tableCell": "Cell", "markdown.editModeSwitch": "Editing Mode", "markdown.editSource": "Source", "markdown.editSplit": "Split", "markdown.editInline": "Inline", "markdown.inlineLoading": "Loading the inline editor...", "markdown.codeCopied": "Copied", "markdown.statLines": "Lines", "markdown.statWords": "Words", "markdown.exportHtml": "HTML", "markdown.exportHtmlDone": "Exported HTML", "markdown.exportHtmlFailed": "HTML export failed", "markdown.exportPdf": "PDF", "markdown.exportPdfWorking": "Generating PDF…", "markdown.exportPdfDone": "Exported PDF", "markdown.exportPdfFailed": "PDF export failed", "markdown.exportImage": "Long image (PNG)", "markdown.exportImageWorking": "Generating long image…", "markdown.exportImageDone": "Exported long image", "markdown.exportImageFailed": "Long image export failed", "markdown.exportImageTooTall": "The document is too long for a single image", "markdown.exportUnavailable": "This export is not available in the current environment", "markdown.inlineLoadFailed": "Failed to load the inline editor; switched back to the source view", "inline.placeholder": "Start writing...", "inline.codeSearchLanguage": "Search language", "inline.codeNoResult": "No result", "inline.codePreview": "Preview", "inline.codePreviewLoading": "Rendering diagram...", "inline.codeHideCode": "Hide code", "inline.linkPlaceholder": "Paste link...", "inline.imageUrlPlaceholder": "Paste image URL...", "markdown.searchPlaceholder": "Search in document", "markdown.searchPrev": "Previous match", "markdown.searchNext": "Next match", "markdown.searchClose": "Close search", "markdown.imagePasteFailed": "Failed to Save Image", "markdown.formatTables": "Format Tables", "markdown.frontmatter": "Frontmatter", "markdown.frontmatterKey": "Property name", "markdown.frontmatterValue": "Value", "markdown.frontmatterEmptyValue": "No value", "markdown.frontmatterAdd": "Add a property", "markdown.frontmatterRemove": "Remove this property", "markdown.frontmatterNewKey": "new-property", "markdown.largeSourceOnly": "Large File: Source Editing Only", "markdown.splitTooNarrow": "Not enough room for two panes", "markdown.footnotes": "Footnotes", "markdown.footnoteBackref": "Back to Reference", "markdown.alertNote": "Note", "markdown.alertTip": "Tip", "markdown.alertImportant": "Important", "markdown.alertWarning": "Warning", "markdown.alertCaution": "Caution", "markdown.alertAbstract": "Abstract", "markdown.alertInfo": "Info", "markdown.alertTodo": "Todo", "markdown.alertSuccess": "Success", "markdown.alertQuestion": "Question", "markdown.alertFailure": "Failure", "markdown.alertDanger": "Danger", "markdown.alertBug": "Bug", "markdown.alertExample": "Example", "markdown.alertQuote": "Quote", "markdown.searchToggleReplace": "Toggle Replace", "markdown.searchCaseSensitive": "Match Case", "markdown.searchReplacePlaceholder": "Replace with", "markdown.searchReplaceOne": "Replace", "markdown.searchReplaceAll": "Replace All", "svg.sourceEditorAria": "SVG source editor", "code.lineNumbers": "Line Numbers", "code.wordWrap": "Word Wrap", "code.highlightLanguage": "Syntax Highlighting", "code.jsonRawStyle": "JSON display style", "code.jsonStyleOriginal": "Original", "code.jsonStylePretty": "Pretty", "code.jsonStyleTree": "Tree", "code.dataViewMode": "Data View", "code.dataTreeLoading": "Parsing…", "code.dataTreeParseError": "Parse Failed", "code.jsonStyleCompact": "Compact", "code.jsonFormatPretty2": "Pretty Format (2 Spaces)", "code.jsonFormatPretty4": "Pretty Format (4 Spaces)", "code.jsonFormatCompact": "Compact Format", "code.invalidJson": "Invalid JSON; cannot run this action", "code.compactApplied": "Compacted", "code.jsonValid": "Valid JSON", "code.jsonInvalid": "Invalid JSON", "code.jsonExpandNode": "Expand", "code.jsonCollapseNode": "Collapse", "code.jsonTreeRoot": "root", "code.jsonTreeAria": "JSON structure tree", "code.jsonNodeKeys": "{count} keys", "code.jsonNodeItems": "{count} items", "code.jsonFoldLimited": "Folding is off above {lines} lines", "code.lineNumbersUnavailable": "This display style has no line numbers", "code.csvViewMode": "Table display mode", "code.csvViewTable": "Table", "code.csvViewRaw": "Raw Text", "code.csvDimensions": "{rows} rows × {columns} columns", "code.jsonlViewMode": "JSON Lines display mode", "code.jsonlRecords": "{count} records", "code.jsonlValid": "Every line is valid JSON", "code.jsonlInvalidSummary": "Lines that are not valid JSON: {count}, first on line {line}", "code.jsonlInvalidLine": "Line {line} is not valid JSON: {text}", "code.jsonlInvalid": "not valid JSON", "code.jsonlLineLabel": "Line {line}", "code.jsonlLineColumn": "Line", "code.jsonlValueColumn": "Value", "code.jsonlTreeLimited": "Showing only the first {count} lines; edit mode keeps the complete source text.", "code.errorLinesOnly": "Error Lines Only", "code.errorLinesMatched": "Error lines only: {count} matched", "code.noErrorLines": "(no matching error lines)", "office.richPreviewReadOnly": "The rich preview does not support editing yet", "directory.sortBy": "Sort By", "directory.sortByName": "Name", "directory.sortByKind": "Type", "directory.sortDirection": "Sort Direction", "directory.sortAscending": "Ascending", "directory.sortDescending": "Descending", "directory.viewMode": "View", "directory.viewList": "List", "directory.viewGrid": "Grid", "directory.kindFolder": "Folder", "create.inProgress": "Creating {fileName}...", "create.success": "Created", "create.failed": "Create failed: {error}", "create.missingDirectory": "Cannot determine the current folder, so a file cannot be created.", "create.workspaceUnavailable": "chrome.tabLocalWorkspace is not available, so a file cannot be created.", "create.directoryNotWritable": "The current folder is not writable, so a file cannot be created.", "create.new": "New", "create.newFileType": "New File Type", "outline.empty": "No TOC available", "outline.aria": "Table of Contents", "outline.title": "Table of Contents", "outline.collapseAll": "Collapse all", "outline.expandAll": "Expand all", "outline.toggleAria": "Collapse or expand subsections", "directory.aria": "Folder tree", "directory.backToRoot": "Back to root folder", "directory.backToParent": ".. Parent directory", "directory.empty": "Empty folder", "directory.loading": "Loading...", "directory.expandFailed": "Unable to expand folder", "directory.readFailed": "Unable to read folder", "directory.previewDescription": "Files and subfolders are listed below and mirrored in the sidebar tree.", "directory.previewItemCount": "Items", "directory.previewPath": "Path", "directory.resizeAria": "Resize sidebar width", "directory.resizeTitle": "Drag to resize sidebar width", "directory.settings": "Folder Options", "directory.showHiddenFiles": "Show Hidden Items", "directory.foldersFirst": "Folders First", "directory.rememberSidebarWidth": "Remember Sidebar Width", "directory.openSettingsPage": "Settings", "directory.newFolder": "New Folder", "directory.newFolderName": "untitled folder", "directory.newFolderFailed": "Couldn't create the folder: {error}", "directory.rename": "Rename", "directory.renameAria": "New name", "directory.moved": "Moved to “{folder}”", "directory.moveFailed": "Couldn't move: {error}", "directory.moveExists": "“{folder}” already has an item with this name", "directory.extensionLocked": "This kind of file can't change its extension", "directory.newFolderItem": "Folder", "directory.favoritesMenu": "Favorite Folders", "directory.favoritesEmpty": "No favorite folders yet. Use a folder's ⋯ menu, or the star in the file header, to add one.", "trash.placeTrash": "Trash", "trash.placeRecycleBin": "Recycle Bin", "trash.action": "Move to {place}", "trash.confirmTitle": "Move to {place}?", "trash.confirmFile": "“{name}” will be moved to the {place}. You can restore it from there.", "trash.confirmFolder": "The folder “{name}” and everything in it will be moved to the {place}. You can restore it from there.", "trash.done": "Moved to the {place}", "trash.failed": "Couldn't move to the {place}: {error}", "favorites.add": "Add to Favorites", "favorites.remove": "Remove from Favorites", "favorites.added": "Added to Favorites", "favorites.removed": "Removed from Favorites", "popup.settings": "Settings", "popup.favorites": "Favorites", "popup.recents": "Recent", "popup.favoritesEmpty": "Star a file or folder from its header to keep it here.", "popup.recentsEmpty": "Files and folders you open will show up here.", "settings.title": "Previewer Settings", "settings.general": "General", "settings.showHiddenFilesDescription": "Show dot-prefixed hidden files in the folder tree and folder overview.", "settings.rememberSidebarWidthDescription": "Restore the last sidebar width the next time a folder opens.", "settings.documentTitle": "Name tabs after the document's own title", "settings.documentTitleDescription": "Title tabs from a Markdown, MDX or HTML document's own title (frontmatter title, first heading, <title>), falling back to the file name when it has none.", "settings.lineNumbers": "Show line numbers", "settings.lineNumbersDescription": "Show the line-number gutter in code and text previews and editors.", "settings.wordWrap": "Word wrap", "settings.wordWrapDescription": "Wrap long lines to the viewport; when off, scroll horizontally instead.", "settings.syntax": "Supported Markdown", "settings.syntaxDescription": "The Markdown this build understands. The basics follow GitHub Flavored Markdown, plus a few common dialects and two diagram fences.", "settings.syntaxGroupBasic": "Basics", "settings.syntaxGroupBlocks": "Blocks", "settings.syntaxGroupExtended": "Extended", "settings.syntaxHeading": "Headings one through six; the outline is built from them", "settings.syntaxEmphasis": "Inline emphasis and code", "settings.syntaxLinkImage": "Links and images; relative paths resolve against the document", "settings.syntaxList": "Bulleted, numbered and checkable task lists", "settings.syntaxQuote": "Blockquotes, nestable", "settings.syntaxRule": "Horizontal rule", "settings.syntaxCodeFence": "Fenced code blocks, highlighted by language", "settings.syntaxTable": "Tables, including alignment colons", "settings.syntaxDefinitionList": "Definition lists", "settings.syntaxFootnote": "Footnote references and a footnote section", "settings.syntaxAlert": "GFM alerts (NOTE/TIP/IMPORTANT/WARNING/CAUTION) and Obsidian callouts ([!tip] Title, +/- folding)", "settings.syntaxFrontmatter": "YAML front matter, viewable in a panel at the top", "settings.syntaxHighlight": "Highlight and insert marks", "settings.syntaxSubSup": "Subscript and superscript", "settings.syntaxMath": "KaTeX math; \\( \\) and \\[ \\] delimiters also work", "settings.syntaxMermaid": "Mermaid diagrams; mindmap syntax renders as a mind map", "settings.syntaxSvg": "SVG, rendered after whitelist sanitising", "settings.syntaxHtml": "Static display tags; scripts, event attributes and unsafe protocols are removed", "settings.shortcuts": "Shortcuts", "settings.shortcutsDescription": "Keyboard shortcuts available while editing Markdown. Keys follow your platform; Ctrl appears as ⌘ on macOS.", "settings.shortcutGroupFormat": "Inline formatting", "settings.shortcutGroupBlocks": "Block formatting", "settings.shortcutGroupDocument": "Document actions", "settings.fileTypes": "File Associations", "settings.fileTypesDescription": "File types supported by Previewer. Check each type's current system handler, make Tabbit the system default, or choose whether the browser opens it with Previewer or its built-in viewer.", "settings.currentHandler": "Current System Default App", "settings.currentHandlerSelf": "Tabbit (this browser)", "settings.currentHandlerUnknown": "No default app detected", "settings.detecting": "Detecting...", "settings.detectUnavailable": "Unavailable", "settings.systemDefault": "Set as System Default", "settings.systemDefaultActive": "Currently the system default", "settings.setAsDefault": "Set as default", "settings.setAllAsDefault": "Set all as default", "settings.setAllNothingPending": "Nothing in this section can be set as default right now", "settings.unsetDefaultHint": "To remove the default, change it in system settings", "settings.openWith": "Open in Tabbit with", "settings.openWithPreviewer": "Tabbit Previewer", "settings.openWithNative": "Browser built‑in", "settings.openWithNativeUnavailable": "This format can't be shown usefully by the browser's built-in viewer", "settings.setDefaultRequested": "Requested to make Tabbit the default app for {extension}", "settings.openedSystemSettings": "System settings opened. Choose Tabbit as the default app for {extension}", "settings.setDefaultUnsupported": "Setting the default app for {extension} is not supported on this system", "settings.setDefaultFailed": "Failed to set the default app for {extension}", "settings.type.markdown": "Markdown", "settings.type.svg": "SVG", "settings.type.image": "Images", "settings.type.media": "Audio & Video", "settings.type.pdf": "PDF", "settings.type.office": "Office Documents", "settings.type.html": "HTML", "settings.type.codeText": "Code & Text", "settings.type.subtitle": "Subtitles", "settings.type.archive": "Archives", "editor.sourceLabel": "File content", "markdown.contentLabel": "Markdown content", "code.contentLabel": "Code content", "code.delimitedContentLabel": "Table source text", "code.tablePreviewAria": "{fileName} table preview", "code.unnamedColumn": "Column {index}", "code.tablePreviewLimited": "Previewing only the first {rows} rows and first {columns} columns; edit mode keeps the complete source text.", "code.noFormattingNeeded": "Current content does not need formatting", "code.formatApplied": "Formatting applied", "code.sourceReloaded": "{fileName} source file reloaded", "code.formatJson": "Format JSON", "code.expandCode": "Expand Code", "code.subtitleViewMode": "Subtitle view", "code.subtitleTimeline": "Timeline", "code.subtitleSource": "Source", "code.subtitleInfoFormat": "Format", "code.subtitleInfoCues": "Cues", "code.subtitleInfoDuration": "Duration", "code.subtitleInfoLanguage": "Language", "code.subtitleInfoEncoding": "Encoding", "code.subtitleTimelineTruncated": "Too many cues: the timeline shows the first {shown} of {total}. Switch to the source view for the full text.", "code.subtitleSearchPlaceholder": "Search dialogue, or a time like 12:30", "code.subtitleCopyCue": "Copy this cue", "code.subtitleTextView": "Plain text", "code.subtitleTextTruncated": "Too many cues: the plain-text view shows the first {shown} of {total}. \"Copy all\" still includes everything.", "code.subtitleBilingual": "Bilingual", "code.subtitleEncodingTooltip": "Detected file encoding", "code.subtitleTranscode": "Re-save as UTF-8", "code.subtitleTranscoded": "Saved as UTF-8", "code.subtitleTranscodeFailed": "Re-save failed, please try again", "code.subtitleBinaryNotice": "This is a binary bitmap subtitle file (VobSub graphic subtitles). It cannot be previewed or edited as text. Keep it next to its matching .idx file and load it in a video player.", "code.subtitleExportMenu": "Export", "code.subtitleExportSrt": "SRT", "code.subtitleExportVtt": "VTT", "code.subtitleExportTxt": "Plain text (TXT)", "code.subtitleExportUnparseable": "Could not parse the subtitles; nothing was exported", "code.subtitleExported": "Exported {fileName}", "code.subtitleExportFailed": "Export failed, please try again", "code.searchPlaceholder": "Find", "code.searchPrev": "Previous", "code.searchNext": "Next", "code.searchClose": "Close", "code.replacePlaceholder": "Replace with", "code.replaceOne": "Replace", "code.replaceAll": "Replace all", "code.replacedCount": "Replaced {count} occurrences", "code.subtitleShiftMenu": "Shift timing", "code.subtitleShiftLabel": "Offset (ms)", "code.subtitleShiftHint": "Positive delays, negative advances; saves immediately. Fixes a constant offset only, not frame-rate drift.", "code.subtitleShiftApply": "Apply and save", "code.subtitleShiftInvalid": "Enter a non-zero offset in milliseconds", "code.subtitleShifted": "Shifted {count} cue timings and saved", "code.subtitleShiftedClamped": "Shifted and saved; some timings were clamped to 00:00", "code.subtitleShiftFailed": "Shift failed, please try again", "code.subtitleShiftExported": "This format cannot be shifted in place; exported the shifted {fileName}", "code.subtitleCleanMenu": "Clean up", "code.subtitleCleanTags": "Remove styling tags", "code.subtitleCleanAnnotations": "Remove bracketed captions (SDH)", "code.subtitleCleanMusic": "Remove music lines", "code.subtitleCleanDuplicates": "Merge duplicate and empty cues", "code.subtitleCleaned": "Cleaned {count} items and saved", "code.subtitleCleanNothing": "Nothing to clean", "code.subtitleCleanFailed": "Clean up failed, please try again", "code.subtitleCleanExported": "This format cannot be cleaned in place; exported the cleaned {fileName}", "code.subtitleTranslate": "Translate the subtitles into a new file", "code.subtitleTranslateTitle": "Translate subtitles", "code.subtitleTranslateModeLabel": "Output", "code.subtitleTranslateModeBilingual": "Bilingual (translation above)", "code.subtitleTranslateModeReplace": "Translation only", "code.subtitleTranslateLanguageLabel": "Target language", "code.subtitleTranslateOutputLabel": "Save as", "code.subtitleTranslateOutputNew": "New file", "code.subtitleTranslateOutputOverwrite": "Overwrite this file", "code.subtitleTranslateOverwriteUnavailable": "Only a writable local SRT can be overwritten; this file will be saved as a new file", "code.subtitleTranslateSummary": "Will translate {count} cues (~{characters} characters); cues already in the target language are skipped. Translation consumes your account quota.", "code.subtitleTranslateNothing": "Nothing to translate (the target language may already be present)", "code.subtitleTranslateStart": "Start translating", "code.subtitleTranslateCancel": "Cancel", "code.subtitleTranslateRunning": "Translating {completed}/{total}…", "code.subtitleTranslateCancelled": "Cancelled; the file was not changed", "code.subtitleTranslateUnavailable": "Translation service unavailable; please make sure the browser is up to date", "code.subtitleTranslateSourceChanged": "Subtitle content changed; please restart translation", "code.subtitleTranslateFailed": "Translation failed: {error}", "code.subtitleTranslateDoneOverwrite": "Translated {count} cues and saved to this file", "code.subtitleTranslateDoneExported": "Translated {count} cues; exported {fileName}", "code.subtitleLangChinese": "Chinese", "code.subtitleLangJapanese": "Japanese", "code.subtitleLangKorean": "Korean", "code.subtitleLangLatin": "English/Latin", "code.subtitleLangCyrillic": "Cyrillic", "code.subtitleLangArabic": "Arabic", "subtitle.binaryUnknown": "Binary SUB", "subtitle.vobsubDescription": "MPEG Program Stream / DVD subpicture data detected; it will not be decoded or written as text.", "subtitle.binaryDescription": "This SUB file contains binary data. Text decoding was stopped to prevent garbled output or accidental writes.", "subtitle.vobsubCompanionHint": "VobSub images, languages, and timing are normally described by a same-name .idx file. Open the companion IDX to inspect timestamps.", "subtitle.scannedBytes": "Header scanned", "subtitle.packHeaders": "MPEG pack headers", "subtitle.privateStreams": "Subtitle private-stream packets", "html.sourceLoading": "Reading source", "html.sourceEditorAria": "HTML source editor", "html.previewAria": "HTML preview", "html.previewFrameTitle": "{fileName} preview", "html.printFrameTitle": "{fileName} print preview", "html.preparing": "Preparing source and preview", "html.viewMode": "HTML view mode", "html.modeSource": "Source", "html.modeSplit": "Split", "html.modePreview": "Preview", "html.reload": "Reload", "html.sourceReloaded": "Source reloaded", "html.sourceRead": "HTML source loaded", "html.readFailed": "Read failed: unable to read the HTML source file", "html.readRequiresWorkspace": "Read failed: local workspace access is required to read HTML source", "html.previewUnavailable": "This file may have been moved, changed, or deleted.", "html.previewUnavailableInArchive": "HTML inside an archive is not supported for preview yet. The source is available on the left; extract the archive to preview it.", "html.previewUnavailableInArchiveStatus": "Preview not supported", "image.stageAria": "Image preview stage", "image.loadingTitle": "Loading image", "image.loadingMeta": "{extension} · Reading image", "image.readyStatus": "Zoom, pan, rotate, and flip", "image.zoomOut": "Zoom Out", "image.zoomIn": "Zoom In", "image.fit": "Fit to Window", "image.actualSize": "100% Actual Size", "image.rotateLeft": "Rotate Left", "image.rotateRight": "Rotate Right", "image.flipHorizontal": "Flip Horizontal", "image.flipVertical": "Flip Vertical", "image.reset": "Reset", "image.exportPng": "PNG", "image.loadFailedTitle": "Image failed to load", "image.loadFailedMeta": "{extension} · Load failed", "image.loadFailedStatus": "Unable to read this image. Check whether the link is still valid.", "image.zoomedOut": "Zoomed out", "image.zoomedIn": "Zoomed in. Drag to inspect details.", "image.fitApplied": "Fit to window", "image.actualSizeApplied": "Switched to 100% actual size", "image.resetApplied": "Reset transforms and fit to window", "image.wheelZoomed": "Wheel zoomed. Drag to inspect details.", "image.transformed": "Image transformed", "image.panned": "View moved", "image.notLoaded": "Image has not finished loading", "image.exportedPng": "PNG exported", "image.exportFailed": "Export failed. The browser may have restricted canvas access for this local image.", "media.kind.audio": "Audio", "media.kind.video": "Video", "media.loading": "Reading media information", "media.playbackRate": "Playback Speed", "media.playbackRateValue": "Playback speed: {rate}", "media.pictureInPicture": "Picture in Picture", "media.pictureInPictureActive": "Picture in Picture active", "media.pictureInPictureFailed": "Unable to enter Picture in Picture", "media.fullscreen": "Full Screen", "media.fullscreenActive": "Full screen active", "media.fullscreenFailed": "Unable to enter full screen", "media.loadFailed": "Media failed to load", "media.loadFailedDetail": "Unable to play this media; its container or codec may not be supported", "svg.stageAria": "SVG preview region. Use the arrow keys to move the zoomed view.", "pdf.loadingMeta": "PDF · Reading file", "pdf.initialStatus": "Rendered with PDF.js. The raw file is available as a browser fallback.", "pdf.toggleNavigation": "Show or hide PDF navigation", "pdf.navLabel": "Navigation", "pdf.hideNavigation": "Hide PDF navigation", "pdf.showNavigation": "Show PDF navigation", "pdf.previousPage": "Previous Page", "pdf.nextPage": "Next Page", "pdf.pageJumpTitle": "Jump to page", "pdf.currentPage": "Current page", "pdf.pageLabelTitle": "Page {page} · label {label}", "pdf.searchTitle": "Search PDF text", "pdf.searchPlaceholder": "Search PDF", "pdf.previousSearchResult": "Previous search result", "pdf.nextSearchResult": "Next search result", "pdf.fitWidth": "Fit Width", "pdf.fitPage": "Fit Page", "pdf.handTool": "Hand tool", "pdf.handToolOn": "Hand tool on: drag with the left button to pan", "pdf.handToolOff": "Hand tool off: drag to select text", "pdf.rotateRight": "Rotate Right", "pdf.viewMode": "PDF view mode", "pdf.viewContinuous": "Continuous", "pdf.viewSingle": "Single", "pdf.viewTwoPage": "Two-Page", "pdf.readAloud": "Read current page or selected text aloud", "pdf.readAloudLabel": "Read Aloud", "pdf.rerender": "Render Again", "pdf.navAria": "PDF navigation", "pdf.navTabsAria": "PDF navigation panels", "pdf.navTitle": "Reading navigation", "pdf.tabPages": "Pages", "pdf.tabOutline": "Outline", "pdf.tabNotes": "Notes", "pdf.infoLoading": "Reading document info", "pdf.infoEmpty": "This PDF carries no readable document info", "pdf.propTitle": "Title", "pdf.propAuthor": "Author", "pdf.propSubject": "Subject", "pdf.propKeywords": "Keywords", "pdf.propCreator": "Application", "pdf.propProducer": "PDF producer", "pdf.propCreated": "Created", "pdf.propVersion": "PDF version", "pdf.infoAttachments": "Attachment(s)", "pdf.attachmentSave": "Save attachment {name}", "pdf.attachmentSaving": "Saving attachment {name}", "pdf.attachmentSaved": "Saved attachment {name}", "pdf.attachmentSaveFailed": "Could not save attachment {name}", "pdf.infoLayers": "Layer(s)", "pdf.layerUntitled": "Untitled layer", "pdf.layerShown": "Layer \u201c{name}\u201d shown", "pdf.layerHidden": "Layer \u201c{name}\u201d hidden", "pdf.infoSignature": "Digital signature(s)", "pdf.infoSignatureN": "Digital signature {index}", "pdf.metaSigned": "Signed", "pdf.signatureSigner": "Signer", "pdf.signatureTime": "Signed at", "pdf.signatureReason": "Reason", "pdf.signatureLocation": "Location", "pdf.signatureContact": "Contact", "pdf.signatureField": "Signature field", "pdf.permission.print": "Print", "pdf.permission.copy": "Copy text", "pdf.permission.modify": "Modify content", "pdf.permission.annotate": "Add annotations", "pdf.permission.forms": "Fill forms", "pdf.permissionAllowed": "Allowed", "pdf.permissionRestricted": "Restricted by author", "pdf.printRestricted": "The author of this PDF restricted printing; the printout may be limited", "pdf.copyRestricted": "The author of this PDF restricted copying text", "pdf.navLoading": "Preparing navigation", "pdf.notePlaceholder": "Write a note for the current page", "pdf.noteHint": "Notes stay on this device and do not modify the PDF file.", "pdf.noteAria": "Current PDF page note", "pdf.stageAria": "{fileName} PDF preview", "pdf.loadingTitle": "Loading PDF", "pdf.loadingDescription": "Parsing the file and preparing page rendering.", "pdf.zoomedOut": "Zoomed out", "pdf.zoomedIn": "Zoomed in", "pdf.fitWidthApplied": "Fit to width", "pdf.fitPageApplied": "Fit to page", "pdf.actualSizeApplied": "Switched to 100% actual size", "pdf.rotatedRight": "Rotated PDF view right", "pdf.viewModeApplied": "Switched to {mode} view", "pdf.rerenderingVisible": "Rendering visible pages again", "pdf.wheelZoomed": "Wheel zoomed PDF view", "pdf.readFailed": "Unable to read the PDF file. Try opening the raw file.", "pdf.parsing": "Parsing PDF", "pdf.renderedPages": "PDF.js rendered · {pages}", "pdf.pageAria": "Page {page}", "pdf.thumbnailTitle": "Jump to page {page}", "pdf.pageHasNote": "This page has a note", "pdf.waitingRender": "Waiting to render", "pdf.rendering": "Rendering", "pdf.renderFailed": "Render failed", "pdf.noTranslatableText": "No translatable text was detected on this page. Scanned pages require an OCR text layer.", "pdf.translationPageAria": "Translation for page {page}", "pdf.translatingPage": "Translating page {page}…", "pdf.translationProgress": "Translated {completed}/{total} blocks", "pdf.translationFailed": "Translation failed for this page", "pdf.translationRetry": "Translation failed. Click to retry", "pdf.translationBudgetExceeded": "This page exceeds the translation limit of 64 blocks or 48K characters.", "pdf.ocrTextTooLarge": "Recognized text on this page exceeds the 64KiB safety limit.", "pdf.readingProgress": "Reading PDF · {percent}%", "pdf.searching": "Searching PDF text", "pdf.searchFound": "Found {count} matches", "pdf.searchNoResults": "No matches found", "pdf.searchCleared": "Search cleared", "pdf.notePage": "Page {page} note", "pdf.noteUnsaved": "Note not saved", "pdf.noteSaved": "Note saved on this device", "pdf.marksTitle": "Marks on this page", "pdf.marksHint": "Select text to highlight or underline it. Marks stay on this device and are never written into the PDF.", "pdf.marksEmpty": "No marks on this page yet", "pdf.markHighlight": "Highlight", "pdf.markUnderline": "Underline", "pdf.markCopy": "Copy", "pdf.toolHighlight": "Highlight", "pdf.toolInk": "Draw", "pdf.toolFreeText": "Text", "pdf.toolStamp": "Image", "pdf.editUnavailable": "This PDF.js build does not support annotation editing", "pdf.editRestricted": "The document's author restricted adding annotations", "pdf.exportAnnotated": "Export a copy with the annotations baked in, leaving the original alone", "pdf.exportAnnotatedLabel": "PDF copy with annotations", "pdf.exportAnnotatedNeedsEdit": "Enter edit mode and add annotations to export a copy", "pdf.exportAnnotatedDone": "Exported {name}", "pdf.exportAnnotatedFailed": "Could not export the annotated copy", "pdf.annotatedCopySuffix": "annotated", "pdf.saveAnnotations": "Write the annotations into the original PDF", "pdf.saveAnnotationsDone": "Annotations written into the original file", "pdf.saveAnnotationsFailed": "Could not write the annotations into the original file", "pdf.saveAnnotationsConfirmTitle": "Write into the original PDF?", "pdf.saveAnnotationsConfirmDescription": "This writes the annotations from this editing session into “{fileName}” itself, overwriting the file on disk. Preview-mode highlights and underlines stay on this device and are not included.", "pdf.saveAnnotationsConfirm": "Write into original", "pdf.annotationsEmpty": "No annotations to save yet", "pdf.markAdded": "{kind} added; kept on this device only", "pdf.markRemoved": "Mark removed", "pdf.markDelete": "Delete mark", "pdf.markJump": "Scroll to this {kind}", "pdf.copied": "Copied the selected text", "pdf.copyFailed": "Copy failed", "pdf.selectionToolbarAria": "Selected text actions", "pdf.outlineLoading": "Reading outline", "pdf.outlineUnavailable": "This PDF has no readable outline", "pdf.outlineEmpty": "This PDF has no outline", "pdf.outlineUntitled": "Untitled outline item", "pdf.outlineGoTo": "Jump to page {page}", "pdf.outlineExternal": "Open external link", "pdf.linkInternal": "Jump to the link target in this document", "pdf.linkUnresolved": "The target page of this link could not be located", "pdf.annotationAria": "Annotation: {summary}", "pdf.readAloudStopped": "Read aloud stopped", "pdf.readAloudNoText": "No readable text on this page", "pdf.readAloudFinished": "Read aloud finished", "pdf.readAloudFailed": "Read aloud failed", "pdf.readAloudSelection": "Reading selected text", "pdf.readAloudPage": "Reading page {page}", "pdf.previewFailedTitle": "PDF preview failed", "pdf.passwordRequired": "This PDF requires a password. Encrypted PDFs are not handled in the current preview.", "pdf.renderErrorDetail": "PDF.js could not render this file: {detail}", "pdf.renderError": "PDF.js could not render this file.", "pdf.canvasUnavailable": "This browser cannot create a PDF canvas.", "office.loadingTitle": "Reading file", "office.loadingDescription": "Previewer will try to extract previewable content.", "office.kindStatus": "{kind} document", "office.parsing": "Parsing document content", "office.readFailed": "Unable to read the current local file.", "office.unsupportedExtension": "This Office extension is not supported yet.", "office.parseFailed": "Document parsing failed. The file may be encrypted, damaged, or ZIP decompression may be unavailable in this browser.", "office.noDocxBody": "No DOCX body content was found.", "office.docxMeta": "DOCX · {paragraphs} · Read-only text preview; complex styling follows the original file", "office.docxBodyLabel": "DOCX body", "office.docxReady": "DOCX loaded in read-only text preview.", "office.docxRendering": "Rendering DOCX preview", "office.docxRendered": "DOCX preview rendered", "office.docxFitWidth": "Fit Width", "office.docxZoomedOut": "DOCX preview zoomed out", "office.docxZoomedIn": "DOCX preview zoomed in", "office.docxFitWidthApplied": "Fit DOCX page to width", "office.docxActualSizeApplied": "Switched DOCX preview to 100% actual size", "office.noXlsxSheets": "No XLSX worksheets were found.", "office.sheet": "Sheet", "office.sheetCount": "{count}", "office.currentSheetTsv": "Current sheet TSV", "office.sheetSwitched": "Sheet switched.", "office.noPptxSlides": "No PPTX slides were found.", "office.pptxMissingViewer": "PptxViewJS is not loaded. Switched to text outline preview.", "office.pptxRendering": "Rendering PPTX with PptxViewJS", "office.pptxReady": "PPTX loaded in read-only text outline preview.", "office.pptxTextLabel": "PPTX slide text", "office.pptxOutlineTitle": "Text Outline", "office.pptxOutlineDescription": "View slide text", "office.pptxBackToSlides": "Back to Slide Preview", "office.pptxViewerLoading": "Loading {count}", "office.pptxPreviousPage": "Previous Page", "office.pptxNextPage": "Next Page", "office.pptxZoomOut": "Zoom Out", "office.pptxZoomIn": "Zoom In", "office.pptxFit": "Fit", "office.pptxViewerMountFailed": "PPTX preview mount failed. Switched to text outline preview.", "office.pptxRenderingPage": "Rendering page {page}", "office.pptxViewerReady": "PptxViewJS preview", "office.pptxViewerReadyStatus": "PPTX rendered with PptxViewJS.", "office.pptxRenderFailed": "PptxViewJS rendering failed. Switched to text outline preview.", "office.legacyUnsupportedStatus": "Legacy Office binary formats cannot be parsed in the browser yet.", "office.legacyUnsupportedTitle": "{extension} preview is not supported yet", "office.legacyUnsupportedDescription": "The lightweight path currently supports .docx, .xlsx, and .pptx. Save legacy formats as modern OOXML and open them again.", "office.parseErrorTitle": "Unable to parse document", "office.emptyDocxTitle": "No body text extracted", "office.emptyDocxDescription": "This document may mainly contain images, charts, or protected content.", "office.emptyXlsxTitle": "No table content to show", "office.emptyXlsxDescription": "Switch to edit mode to fill TSV content, then save.", "office.chart": "Chart", "office.chartSeries": "Series {index}", "office.chartUnresolved": "The chart structure was recognized, but the referenced data range cannot be resolved yet.", "office.embeddedChart": "Embedded chart", "office.emptyPptxTitle": "No slide text extracted", "office.emptyPptxDescription": "This presentation may mainly contain images or shapes.", "office.blankSlide": "Blank slide", "office.xlsxMeta": "XLSX · {sheetName} · {sheetCount} · Saves current displayed values; complex formulas and styles use lightweight compatibility", "office.pptxMeta": "PPTX · {slides} · {images} · Light editing saves text while images/layout keep the original structure", "unsupported.displayName": "Unsupported file", "unsupported.description": "This file type cannot be previewed directly. You can locate it in the file manager.", "unsupported.type": "Type", "unsupported.path": "Path", "unsupported.size": "Size", "unsupported.created": "Created", "unsupported.modified": "Modified", "unsupported.reading": "Reading...", "unsupported.revealRequested": "File manager reveal requested", "unsupported.revealFailed": "Unable to locate file", "unsupported.unreadable": "Unable to read", "zip.displayName": "Archive", "zip.kicker": "Archive", "zip.folderKicker": "Archive folder", "zip.entryKicker": "Archive entry", "zip.loading": "Reading archive...", "zip.ready": "Archive index loaded", "zip.refresh": "Reload from disk", "zip.description": "Expand the archive in the sidebar to browse its files without extracting it.", "zip.emptyArchive": "This archive contains no files.", "zip.entries": "File entries", "zip.childEntries": "In this folder", "zip.uncompressedSize": "Original size", "zip.compressedSize": "Compressed size", "zip.ratio": "Compression ratio", "zip.archiveSize": "Archive size", "zip.archive": "Archive", "zip.entryPath": "Path in archive", "zip.readOnly": "Files inside an archive are read-only and cannot be edited or renamed.", "zip.readFailed": "This archive could not be read.", "zip.tooLarge": "The archive is larger than the {size} preview budget and was not read.", "zip.corrupt": "This file is damaged or is not a valid ZIP archive.", "zip.skippedUnsafe": "Skipped {count} entries whose paths escape the archive root.", "zip.truncated": "Too many entries; only the first {count} were indexed.", "zip.encryptedEntries": "{count} entries are encrypted and cannot be previewed.", "zip.entryEncrypted": "This entry is encrypted and needs a password, so it cannot be previewed.", "zip.entryUnsupportedMethod": "This entry uses a compression method the browser cannot decode.", "zip.entryTooLarge": "This entry expands beyond the per-entry preview budget and was not opened.", "zip.entryRatio": "This entry has an abnormal compression ratio and was not expanded, as a safety measure.", "zip.entryCorrupt": "This entry is damaged and cannot be expanded.", "zip.entryNotFound": "This entry is not present in the archive.", "fileType.directory": "Folder", "fileType.markdown": "Markdown", "fileType.svg": "SVG", "fileType.image": "Image", "fileType.pdf": "PDF", "fileType.office": "Office", "fileType.html": "HTML", "fileType.code-text": "Code / Text", "fileType.unsupported": "Unsupported file" } }; const COUNT_UNITS = { "zh-CN": { line: { one: "行", other: "行" }, page: { one: "页", other: "页" }, sheet: { one: "个工作表", other: "个工作表" }, paragraph: { one: "段正文", other: "段正文" }, image: { one: "张图片", other: "张图片" }, slide: { one: "页幻灯片", other: "页幻灯片" }, entry: { one: "项", other: "项" } }, en: { line: { one: "line", other: "lines" }, page: { one: "page", other: "pages" }, sheet: { one: "sheet", other: "sheets" }, paragraph: { one: "paragraph", other: "paragraphs" }, image: { one: "image", other: "images" }, slide: { one: "slide", other: "slides" }, entry: { one: "item", other: "items" } } }; function currentLocale() { const language = browserUiLanguage(); if (!language) { return DEFAULT_LOCALE; } return normalizeLocale(language); } TabLocalFiles.currentLocale = currentLocale; function t(key, values = {}) { return messageTemplate(key).replace(/\{(\w+)\}/g, (_match, name) => String(values[name] ?? "")); } TabLocalFiles.t = t; function messageTemplate(key) { const locale = currentLocale(); return I18N_MESSAGES[locale][key] || I18N_MESSAGES[DEFAULT_LOCALE][key] || key; } TabLocalFiles.messageTemplate = messageTemplate; function formatCount(count, unit) { const locale = currentLocale(); const unitMessages = COUNT_UNITS[locale][unit]; const label = count === 1 ? unitMessages.one : unitMessages.other; return locale === "zh-CN" ? `${count} ${label}` : `${count} ${label}`; } TabLocalFiles.formatCount = formatCount; function fileTypeDisplayName(definition) { return t(`fileType.${definition.id}`) || definition.displayName; } TabLocalFiles.fileTypeDisplayName = fileTypeDisplayName; function applyDocumentLocale() { if (typeof document === "undefined") { return; } document.documentElement.lang = currentLocale(); document.documentElement.dir = "ltr"; } TabLocalFiles.applyDocumentLocale = applyDocumentLocale; function localizeDocument(root) { const target = root || safeDocument(); if (!target) { return; } applyDocumentLocale(); target.querySelectorAll("[data-tlf-i18n]").forEach((element) => { const key = element.dataset.tlfI18n; if (key) { element.textContent = t(key); } }); target.querySelectorAll("[data-tlf-i18n-title]").forEach((element) => { const key = element.dataset.tlfI18nTitle; if (key) { element.setAttribute("title", t(key)); } }); target.querySelectorAll("[data-tlf-i18n-aria-label]").forEach((element) => { const key = element.dataset.tlfI18nAriaLabel; if (key) { element.setAttribute("aria-label", t(key)); } }); target.querySelectorAll("[data-tlf-i18n-placeholder]").forEach((element) => { const key = element.dataset.tlfI18nPlaceholder; if (key) { element.setAttribute("placeholder", t(key)); } }); } TabLocalFiles.localizeDocument = localizeDocument; function browserUiLanguage() { const chromeI18n = globalThis.chrome?.i18n; try { const language = chromeI18n?.getUILanguage?.(); if (language) { return language; } } catch { } const nav = globalThis.navigator; return nav?.languages?.[0] || nav?.language; } function safeDocument() { return typeof document === "undefined" ? undefined : document; } function normalizeLocale(language) { const normalized = language.toLowerCase().replace("_", "-"); return normalized === "zh" || normalized.startsWith("zh-") ? "zh-CN" : "en"; } })(TabLocalFiles || (TabLocalFiles = {})); var TabLocalFiles; (function (TabLocalFiles) { TabLocalFiles.RENDERER_CONTENT_SCRIPT_ID = "tab-local-files-renderer"; TabLocalFiles.PREFERENCES_STORAGE_KEY = "tab-local-files:preferences"; })(TabLocalFiles || (TabLocalFiles = {})); var TabLocalFiles; (function (TabLocalFiles) { var Preferences; (function (Preferences) { const LOCAL_CACHE_KEY = "tab-local-files:preferences-cache"; let cache; let hydration; let mutationRevision = 0; const changeListeners = []; function read() { if (!cache) { cache = parsePreferences(safeLocalStorageGet(LOCAL_CACHE_KEY)) || {}; } return cache; } Preferences.read = read; function readOpenModes() { return { ...(read().openModes || {}) }; } Preferences.readOpenModes = readOpenModes; function useDocumentTitle() { return (read().preview || {}).documentTitle !== false; } Preferences.useDocumentTitle = useDocumentTitle; function showLineNumbers() { return (read().preview || {}).lineNumbers !== false; } Preferences.showLineNumbers = showLineNumbers; function useWordWrap() { return (read().preview || {}).wordWrap !== false; } Preferences.useWordWrap = useWordWrap; const MARKDOWN_READING_WIDTHS = ["fixed", "full"]; const MARKDOWN_FONT_SIZES = ["medium", "large", "xlarge"]; function markdownReadingWidth() { return oneOf((read().preview || {}).markdownWidth, MARKDOWN_READING_WIDTHS, "fixed"); } Preferences.markdownReadingWidth = markdownReadingWidth; function markdownFontSize() { return oneOf((read().preview || {}).markdownFontSize, MARKDOWN_FONT_SIZES, "medium"); } Preferences.markdownFontSize = markdownFontSize; function oneOf(value, allowed, fallback) { return typeof value === "string" && allowed.includes(value) ? value : fallback; } function update(patch) { const current = read(); const next = { ...current, ...(patch.directory ? { directory: { ...(current.directory || {}), ...patch.directory } } : {}), ...(patch.openModes ? { openModes: { ...(current.openModes || {}), ...patch.openModes } } : {}), ...(patch.preview ? { preview: { ...(current.preview || {}), ...patch.preview } } : {}) }; commit(next); return next; } Preferences.update = update; function writeOpenMode(extension, mode) { const normalized = extension.toLowerCase(); const openModes = readOpenModes(); if (mode) { openModes[normalized] = mode; } else { delete openModes[normalized]; } commit({ ...read(), openModes }); } Preferences.writeOpenMode = writeOpenMode; function init() { if (hydration) { return hydration; } subscribeToExternalChanges(); const hydrationRevision = mutationRevision; hydration = new Promise((resolve) => { const storage = chromeStorageApi()?.local; const get = storage?.get; if (!get) { resolve(); return; } try { get.call(storage, TabLocalFiles.PREFERENCES_STORAGE_KEY, (result) => { if (mutationRevision === hydrationRevision) { applyExternalValue(result?.[TabLocalFiles.PREFERENCES_STORAGE_KEY]); } resolve(); }); } catch { resolve(); } }); return hydration; } Preferences.init = init; function onChange(listener) { changeListeners.push(listener); } Preferences.onChange = onChange; function commit(next) { mutationRevision += 1; cache = next; const serialized = JSON.stringify(next); safeLocalStorageSet(LOCAL_CACHE_KEY, serialized); const storage = chromeStorageApi()?.local; const set = storage?.set; if (!set) { return; } try { set.call(storage, { [TabLocalFiles.PREFERENCES_STORAGE_KEY]: next }); } catch { } } function subscribeToExternalChanges() { const changedEvent = chromeStorageApi()?.onChanged; const addListener = changedEvent?.addListener; if (!addListener) { return; } try { addListener.call(changedEvent, (changes, areaName) => { if (areaName !== "local" || !(TabLocalFiles.PREFERENCES_STORAGE_KEY in changes)) { return; } mutationRevision += 1; applyExternalValue(changes[TabLocalFiles.PREFERENCES_STORAGE_KEY]?.newValue); }); } catch { } } function applyExternalValue(value) { const parsed = normalizePreferences(value); if (!parsed || JSON.stringify(parsed) === JSON.stringify(read())) { return; } cache = parsed; safeLocalStorageSet(LOCAL_CACHE_KEY, JSON.stringify(parsed)); changeListeners.forEach((listener) => { try { listener(); } catch { } }); } function parsePreferences(raw) { if (!raw) { return undefined; } try { return normalizePreferences(JSON.parse(raw)); } catch { return undefined; } } function normalizePreferences(value) { if (!value || typeof value !== "object") { return undefined; } const record = value; const preferences = {}; if (record.directory && typeof record.directory === "object") { preferences.directory = record.directory; } if (record.preview && typeof record.preview === "object") { preferences.preview = record.preview; } if (record.openModes && typeof record.openModes === "object") { const openModes = {}; for (const [extension, mode] of Object.entries(record.openModes)) { if (mode === "native" || mode === "previewer") { openModes[extension.toLowerCase()] = mode; } } preferences.openModes = openModes; } return preferences; } function chromeStorageApi() { return globalThis.chrome?.storage; } function safeLocalStorageGet(key) { try { return globalThis.localStorage?.getItem(key) ?? null; } catch { return null; } } function safeLocalStorageSet(key, value) { try { globalThis.localStorage?.setItem(key, value); } catch { } } })(Preferences = TabLocalFiles.Preferences || (TabLocalFiles.Preferences = {})); })(TabLocalFiles || (TabLocalFiles = {})); var TabLocalFiles; (function (TabLocalFiles) { var Theme; (function (Theme) { const LEGACY_MODE_STORAGE_KEY = "tab-local-files:theme"; const MODE_STORAGE_KEY = "tab-local-files:theme-mode"; const MEDIA_QUERY = "(prefers-color-scheme: dark)"; const PORT_NAME = "tlf-theme"; const PORT_RECONNECT_DELAY_MS = 1000; const CHROMIUM_UNSET_USER_COLOR = -6645094; const TABBIT_DEFAULT_ORANGE = -810932; let activeGeneration = 0; let bindingCleanup; let lastBrowserColors; let lastAppliedTheme; let lastAppliedAccent; let lastAppliedMode; const changeHandlers = new Set(); function start() { stop(); clearLegacyPreference(); const generation = ++activeGeneration; const isActive = () => generation === activeGeneration; applyColors(); void readBrowserThemeColors().then((colors) => { if (isActive() && colors) { lastBrowserColors = colors; applyColors(colors); } }); const mediaQuery = window.matchMedia?.(MEDIA_QUERY); const onSystemThemeChange = () => { if (isActive()) { applyColors(); } }; mediaQuery?.addEventListener("change", onSystemThemeChange); const api = tabThemeColorApi(); const onTabThemeChange = (colors) => { if (!isActive()) { return; } const normalized = normalizeColors(colors); if (typeof normalized?.is_dark_mode === "boolean") { lastBrowserColors = { ...lastBrowserColors, is_dark_mode: normalized.is_dark_mode }; } applyColors(lastBrowserColors); void readBrowserThemeColors().then((refreshed) => { if (isActive() && refreshed) { lastBrowserColors = refreshed; applyColors(refreshed); } }); }; api?.onChanged?.addListener?.(onTabThemeChange); const onUserColorChange = (colors) => { if (!isActive()) { return; } const normalized = normalizeColors(colors); if (!normalized?.user_color) { return; } lastBrowserColors = { ...lastBrowserColors, user_color: normalized.user_color, is_dark_mode: normalized.is_dark_mode ?? lastBrowserColors?.is_dark_mode }; applyColors(lastBrowserColors); }; api?.onUserColorChanged?.addListener?.(onUserColorChange); let port; let reconnectTimer; const connectBackgroundPort = () => { port = connectThemePort((colors) => { if (!isActive()) { return; } lastBrowserColors = colors || undefined; applyColors(lastBrowserColors); }, () => { port = undefined; if (!isActive() || reconnectTimer !== undefined) { return; } reconnectTimer = window.setTimeout(() => { reconnectTimer = undefined; if (isActive()) { connectBackgroundPort(); } }, PORT_RECONNECT_DELAY_MS); }); }; connectBackgroundPort(); bindingCleanup = () => { if (reconnectTimer !== undefined) { window.clearTimeout(reconnectTimer); reconnectTimer = undefined; } mediaQuery?.removeEventListener("change", onSystemThemeChange); api?.onChanged?.removeListener?.(onTabThemeChange); api?.onUserColorChanged?.removeListener?.(onUserColorChange); port?.disconnect?.(); }; } Theme.start = start; function stop() { activeGeneration += 1; bindingCleanup?.(); bindingCleanup = undefined; } Theme.stop = stop; function onChange(handler) { changeHandlers.add(handler); return () => { changeHandlers.delete(handler); }; } Theme.onChange = onChange; function currentState() { const theme = lastAppliedTheme ?? resolveTheme(lastBrowserColors); return { theme, mode: currentMode(), source: browserAccentValue(lastBrowserColors) !== undefined ? "browser" : "fallback" }; } Theme.currentState = currentState; function currentMode() { const storedMode = readStorage(MODE_STORAGE_KEY); return storedMode === "dark" || storedMode === "light" ? storedMode : "browser"; } Theme.currentMode = currentMode; function toggleMode() { const mode = currentMode(); if (mode === "browser") { setMode(resolveTheme(lastBrowserColors) === "light" ? "dark" : "light"); } else { setMode("browser"); } applyColors(); const generation = activeGeneration; void readBrowserThemeColors().then((colors) => { if (generation === activeGeneration && colors) { lastBrowserColors = colors; applyColors(colors); } }); } Theme.toggleMode = toggleMode; function applyColors(colors = lastBrowserColors) { applyResolvedTheme(resolveTheme(colors), colors); } function clearLegacyPreference() { if (readStorage(MODE_STORAGE_KEY)) { return; } removeStorage(LEGACY_MODE_STORAGE_KEY); } function setMode(mode) { if (mode === "browser") { removeStorage(MODE_STORAGE_KEY); return; } writeStorage(MODE_STORAGE_KEY, mode); } function resolveTheme(colors) { const mode = currentMode(); if (mode === "dark" || mode === "light") { return mode; } if (typeof colors?.is_dark_mode === "boolean") { return colors.is_dark_mode ? "dark" : "light"; } return systemTheme(); } function systemTheme() { return window.matchMedia?.(MEDIA_QUERY).matches ? "dark" : "light"; } function applyResolvedTheme(theme, colors) { const seed = browserAccentValue(colors); const mode = currentMode(); const changed = lastAppliedTheme !== theme || lastAppliedAccent !== seed || lastAppliedMode !== mode; lastAppliedTheme = theme; lastAppliedAccent = seed; lastAppliedMode = mode; const root = document.documentElement; root.dataset.tlfTheme = theme; root.dataset.tlfThemeMode = mode; root.dataset.tlfThemeSource = seed !== undefined ? "browser" : "fallback"; root.style.colorScheme = theme; applySeedColor(seed); if (changed) { const state = { theme, mode, source: seed !== undefined ? "browser" : "fallback" }; changeHandlers.forEach((handler) => { try { handler(state); } catch (error) { console.warn("[Previewer] Theme change handler failed", error); } }); } } function applySeedColor(seed) { const style = document.documentElement.style; if (!style?.setProperty || !style.removeProperty) { return; } if (seed === undefined) { style.removeProperty("--primary"); return; } const { r, g, b } = skColorToRgb(seed); style.setProperty("--primary", `rgb(${r}, ${g}, ${b})`); } function browserAccentValue(colors) { return [colors?.user_color, colors?.accent_color, colors?.primary_color, colors?.background_color] .map(seedColorValue) .find((value) => value !== undefined); } function seedColorValue(color) { if (typeof color?.value !== "number" || ((color.value >>> 24) & 255) === 0) { return undefined; } const value = normalizeColorValue(color.value); return isUsableSeed(value) ? value : undefined; } function isUsableSeed(value) { const { r, g, b } = skColorToRgb(value); const brightest = Math.max(r, g, b); const chroma = brightest - Math.min(r, g, b); return chroma > 12 || (brightest > 32 && brightest < 224); } function skColorToRgb(value) { const unsignedValue = value >>> 0; return { r: (unsignedValue >> 16) & 255, g: (unsignedValue >> 8) & 255, b: unsignedValue & 255 }; } async function readBrowserThemeColors() { const directColors = await (readBrowserThemeColorsDirectly() || Promise.resolve(undefined)); if (directColors) { return directColors; } console.info("[Previewer] chrome.tabThemeColor is unavailable or empty in this page context; requesting background theme colors."); return readBrowserThemeColorsFromBackground(); } function readBrowserThemeColorsDirectly() { const api = tabThemeColorApi(); if (!api?.getColors && !api?.getUserColor) { return undefined; } return (async () => { const userColors = api.getUserColor ? await readColorsFromApi(api.getUserColor.bind(api)) : undefined; if (userColors && hasUsableColor(userColors)) { return userColors; } return api.getColors ? readColorsFromApi(api.getColors.bind(api)) : userColors; })(); } function readColorsFromApi(reader) { return new Promise((resolve) => { let settled = false; const finish = (colors) => { if (settled) { return; } settled = true; resolve(normalizeColors(colors) || undefined); }; try { reader(finish); } catch { finish(undefined); } }); } function readBrowserThemeColorsFromBackground() { const runtime = chromeRuntimeApi(); if (!runtime?.sendMessage) { console.warn("[Previewer] chrome.runtime.sendMessage is unavailable; falling back to default orange theme."); return Promise.resolve(undefined); } const sendMessage = runtime.sendMessage.bind(runtime); return new Promise((resolve) => { sendMessage({ type: "tlf-get-tab-theme-colors" }, (response) => { if (runtime.lastError?.message) { console.warn("[Previewer] Failed to request background theme colors; falling back to default orange theme.", runtime.lastError.message); resolve(undefined); return; } if (!response?.ok) { console.warn("[Previewer] Background theme color request failed; falling back to default orange theme.", response); resolve(undefined); return; } const colors = normalizeColors(response.themeColors) || undefined; if (!colors) { console.warn("[Previewer] Background returned no theme colors; falling back to default orange theme."); } resolve(colors); }); }); } function connectThemePort(onColors, onDisconnect) { const runtime = chromeRuntimeApi(); if (!runtime?.connect) { return undefined; } try { const port = runtime.connect({ name: PORT_NAME }); const onMessage = (message) => { if (message?.type !== "tlf-tab-theme-colors") { return; } const colors = normalizeColors(message.colors) || undefined; if (!colors) { console.warn("[Previewer] Background theme port returned no colors; falling back to default orange theme."); } onColors(colors); }; port.onMessage?.addListener(onMessage); const handleDisconnect = () => { port.onMessage?.removeListener?.(onMessage); port.onDisconnect?.removeListener?.(handleDisconnect); onDisconnect(); }; port.onDisconnect?.addListener(handleDisconnect); return { onMessage: port.onMessage, disconnect: () => { port.onMessage?.removeListener?.(onMessage); port.onDisconnect?.removeListener?.(handleDisconnect); port.disconnect?.(); } }; } catch (error) { console.warn("[Previewer] Failed to connect background theme port; falling back to one-shot theme lookup.", error); return undefined; } } function normalizeColors(colors) { if (!colors) { return undefined; } return { ...colors, user_color: normalizeColor(colors.user_color), primary_color: normalizeColor(colors.primary_color), accent_color: normalizeColor(colors.accent_color), background_color: normalizeColor(colors.background_color), text_color: normalizeColor(colors.text_color) }; } function normalizeColor(color) { if (!color) { return undefined; } return { value: normalizeColorValue(color.value) }; } function hasUsableColor(colors) { return browserAccentValue(colors) !== undefined; } function normalizeColorValue(value) { return value === CHROMIUM_UNSET_USER_COLOR ? TABBIT_DEFAULT_ORANGE : value; } function tabThemeColorApi() { return globalThis.chrome?.tabThemeColor; } function chromeRuntimeApi() { return globalThis.chrome?.runtime; } function readStorage(key) { try { return window.localStorage.getItem(key); } catch { return null; } } function writeStorage(key, value) { try { window.localStorage.setItem(key, value); } catch { return; } } function removeStorage(key) { try { window.localStorage.removeItem(key); } catch { return; } } })(Theme = TabLocalFiles.Theme || (TabLocalFiles.Theme = {})); })(TabLocalFiles || (TabLocalFiles = {})); var TabLocalFiles; (function (TabLocalFiles) { const FALLBACK_FILE_TYPE_CATALOG = [ { id: "markdown", labelKey: "settings.type.markdown", extensions: [".md", ".markdown"], defaultOpenMode: "previewer", chromeNativeRenderable: true, osAssociation: "associate" }, { id: "svg", labelKey: "settings.type.svg", extensions: [".svg"], defaultOpenMode: "previewer", chromeNativeRenderable: true, osAssociation: "associate" }, { id: "pdf", labelKey: "settings.type.pdf", extensions: [".pdf"], defaultOpenMode: "native", chromeNativeRenderable: true, osAssociation: "associate" }, { id: "office", labelKey: "settings.type.office", extensions: [".doc", ".docx", ".ppt", ".pptx", ".xls", ".xlsx"], defaultOpenMode: "previewer", chromeNativeRenderable: false, osAssociation: "none" }, { id: "html", labelKey: "settings.type.html", extensions: [".html", ".htm", ".xhtml", ".xht", ".xhtm"], defaultOpenMode: "native", chromeNativeRenderable: true, osAssociation: "associate", osAssociationOverrides: { ".xhtml": "none", ".xht": "none", ".xhtm": "none" } }, { id: "subtitle", labelKey: "settings.type.subtitle", extensions: [".srt", ".vtt", ".ass", ".ssa", ".ttml", ".dfxp", ".smi", ".sbv", ".sub", ".mpl", ".lrc", ".idx"], defaultOpenMode: "previewer", chromeNativeRenderable: true, chromeNativeRenderableOverrides: { ".sub": false }, osAssociation: "none" }, { id: "code-text", labelKey: "settings.type.codeText", extensions: [ ".txt", ".text", ".csv", ".tsv", ".rtf", ".tex", ".bib", ".mdx", ".js", ".cjs", ".mjs", ".ts", ".jsx", ".tsx", ".vue", ".py", ".rb", ".java", ".swift", ".go", ".rs", ".cpp", ".cc", ".cxx", ".c", ".h", ".hh", ".hpp", ".hxx", ".css", ".less", ".scss", ".json", ".jsonc", ".jsonl", ".ndjson", ".xml", ".yaml", ".yml", ".toml", ".ini", ".sh", ".sql", ".log" ], defaultOpenMode: "previewer", chromeNativeRenderable: true, osAssociation: "associate", osAssociationOverrides: { ".text": "none", ".rtf": "none", ".mdx": "none", ".cjs": "none", ".mjs": "none", ".vue": "none", ".rb": "none", ".swift": "none", ".cc": "none", ".cxx": "none", ".hh": "none", ".hxx": "none", ".less": "none", ".scss": "none", ".jsonc": "none", ".jsonl": "none", ".ndjson": "none", ".toml": "none", ".ini": "none" } }, { id: "zip", labelKey: "settings.type.archive", extensions: [ ".zip", ".jar", ".apk", ".crx", ".epub", ".skill", ".xpi", ".vsix", ".whl", ".nupkg", ".odt", ".ods", ".odp" ], defaultOpenMode: "previewer", chromeNativeRenderable: false, osAssociation: "none" }, { id: "image", labelKey: "settings.type.image", extensions: [".png", ".jpg", ".jpeg", ".webp", ".gif", ".bmp", ".avif", ".ico", ".apng"], defaultOpenMode: "previewer", chromeNativeRenderable: true, osAssociation: "associate", osAssociationOverrides: { ".avif": "none", ".ico": "none", ".apng": "none" } }, { id: "media", labelKey: "settings.type.media", extensions: [ ".webm", ".mp4", ".m4v", ".mkv", ".ogv", ".ogm", ".mp3", ".wav", ".flac", ".ogg", ".oga", ".opus", ".weba", ".m4a", ".mka" ], defaultOpenMode: "native", chromeNativeRenderable: true, osAssociation: "none" } ]; function manifestFileTypeCatalog() { return globalThis.chrome?.runtime?.getManifest?.()?.tab_local_file_preview?.fileTypeCatalog; } TabLocalFiles.FILE_TYPE_CATALOG = manifestFileTypeCatalog() ?? FALLBACK_FILE_TYPE_CATALOG; function extensionsForId(id) { return TabLocalFiles.FILE_TYPE_CATALOG.find((entry) => entry.id === id)?.extensions ?? []; } TabLocalFiles.MARKDOWN_EXTENSIONS = extensionsForId("markdown"); TabLocalFiles.IMAGE_EXTENSIONS = [...extensionsForId("image"), ...extensionsForId("svg")]; TabLocalFiles.MEDIA_EXTENSIONS = extensionsForId("media"); TabLocalFiles.HTML_EXTENSIONS = [...extensionsForId("html"), ".text_html"]; TabLocalFiles.OFFICE_EXTENSIONS = extensionsForId("office"); TabLocalFiles.SUBTITLE_EXTENSIONS = extensionsForId("subtitle"); TabLocalFiles.CODE_TEXT_EXTENSIONS = extensionsForId("code-text"); TabLocalFiles.ARCHIVE_CATALOG_EXTENSIONS = extensionsForId("zip"); TabLocalFiles.SIDEBAR_ONLY_EXTENSIONS = TabLocalFiles.ARCHIVE_CATALOG_EXTENSIONS.filter((extension) => extension !== ".zip" && extension !== ".skill"); function catalogEntryForExtension(extension) { const normalized = extension.toLowerCase(); return TabLocalFiles.FILE_TYPE_CATALOG.find((entry) => entry.extensions.includes(normalized)); } TabLocalFiles.catalogEntryForExtension = catalogEntryForExtension; function catalogDefaultOpenMode(extension) { return catalogEntryForExtension(extension)?.defaultOpenMode; } TabLocalFiles.catalogDefaultOpenMode = catalogDefaultOpenMode; function catalogChromeNativeRenderable(extension) { const normalized = extension.toLowerCase(); const entry = catalogEntryForExtension(normalized); return entry?.chromeNativeRenderableOverrides?.[normalized] ?? entry?.chromeNativeRenderable; } TabLocalFiles.catalogChromeNativeRenderable = catalogChromeNativeRenderable; function catalogOsAssociation(extension) { const normalized = extension.toLowerCase(); const entry = catalogEntryForExtension(normalized); if (!entry) { return "none"; } return entry.osAssociationOverrides?.[normalized] ?? entry.osAssociation; } TabLocalFiles.catalogOsAssociation = catalogOsAssociation; function effectiveOpenMode(extension) { const normalized = extension.toLowerCase(); return TabLocalFiles.Preferences.readOpenModes()[normalized] || catalogDefaultOpenMode(normalized); } TabLocalFiles.effectiveOpenMode = effectiveOpenMode; })(TabLocalFiles || (TabLocalFiles = {})); var TabLocalFiles; (function (TabLocalFiles) { var SettingsPage; (function (SettingsPage) { const RE_DETECT_DELAYS_MS = [1500, 8000]; const systemOpenInfoByExtension = {}; function start() { TabLocalFiles.Theme.start(); TabLocalFiles.localizeDocument(); document.title = TabLocalFiles.t("settings.title"); bindNavigation(); renderFileTypeList(); renderShortcutReference(); renderSyntaxReference(); bindGeneralSettings(); syncOpenModeControls(); void TabLocalFiles.Preferences.init().then(() => { syncGeneralSettings(); syncOpenModeControls(); }); TabLocalFiles.Preferences.onChange(() => { syncGeneralSettings(); syncOpenModeControls(); }); void detectSystemOpenInfo(allCatalogExtensions()); } SettingsPage.start = start; function bindNavigation() { window.addEventListener("hashchange", showPageFromHash); showPageFromHash(); } function showPageFromHash() { const pages = Array.from(document.querySelectorAll("[data-tlf-settings-page]")); const requested = window.location.hash.slice(1); const active = pages.find((page) => page.dataset.tlfSettingsPage === requested) || pages[0]; if (!active) { return; } pages.forEach((page) => { page.hidden = page !== active; }); const activeId = active.dataset.tlfSettingsPage; document.querySelectorAll("[data-tlf-settings-nav]").forEach((item) => { const current = item.dataset.tlfSettingsNav === activeId; if (current) { item.setAttribute("aria-current", "page"); const title = document.querySelector("[data-tlf-settings-page-title]"); if (title) { title.textContent = item.textContent?.trim() || ""; } } else { item.removeAttribute("aria-current"); } }); const scroller = document.querySelector("[data-tlf-settings-scroll]"); if (scroller) { scroller.scrollTop = 0; } } const SHORTCUT_GROUPS = [ { titleKey: "settings.shortcutGroupFormat", items: [ { labelKey: "markdown.bold", spec: { mod: true, key: "B" } }, { labelKey: "markdown.italic", spec: { mod: true, key: "I" } }, { labelKey: "markdown.strikethrough", spec: { mod: true, shift: true, key: "X" } }, { labelKey: "markdown.insertLink", spec: { mod: true, key: "K" } }, { labelKey: "markdown.inlineCode", spec: { mod: true, shift: true, key: "C" } }, { labelKey: "markdown.codeBlock", spec: { mod: true, shift: true, key: "K" } } ] }, { titleKey: "settings.shortcutGroupBlocks", items: [ { labelKey: "markdown.heading1", spec: { mod: true, alt: true, key: "1" } }, { labelKey: "markdown.heading2", spec: { mod: true, alt: true, key: "2" } }, { labelKey: "markdown.heading3", spec: { mod: true, alt: true, key: "3" } }, { labelKey: "markdown.orderedList", spec: { mod: true, shift: true, key: "7" } }, { labelKey: "markdown.unorderedList", spec: { mod: true, shift: true, key: "8" } }, { labelKey: "markdown.blockquote", spec: { mod: true, shift: true, key: "9" } } ] }, { titleKey: "settings.shortcutGroupDocument", items: [ { labelKey: "common.save", spec: { mod: true, key: "S" } }, { labelKey: "markdown.searchPlaceholder", spec: { mod: true, key: "F" } }, { labelKey: "markdown.searchNext", spec: { mod: true, key: "G" } }, { labelKey: "markdown.searchPrev", spec: { mod: true, shift: true, key: "G" } } ] } ]; function renderShortcutReference() { const container = document.querySelector("[data-tlf-settings-shortcuts]"); if (!container) { return; } container.innerHTML = SHORTCUT_GROUPS.map((group) => ` <div class="tlf-settings-card tlf-settings-shortcut-group"> <h3 class="tlf-settings-type-title">${escapeHtml(TabLocalFiles.t(group.titleKey))}</h3> <dl class="tlf-settings-shortcut-list"> ${group.items .map((item) => ` <div class="tlf-settings-shortcut-row"> <dt class="tlf-settings-shortcut-label">${escapeHtml(TabLocalFiles.t(item.labelKey))}</dt> <dd class="tlf-settings-shortcut-keys"><kbd>${escapeHtml(TabLocalFiles.formatShortcut(item.spec))}</kbd></dd> </div> `) .join("")} </dl> </div> `).join(""); } const SYNTAX_GROUPS = [ { titleKey: "settings.syntaxGroupBasic", rows: [ ["# 一级标题 … ###### 六级标题", "settings.syntaxHeading"], ["**粗体** *斜体* ~~删除线~~ `行内代码`", "settings.syntaxEmphasis"], ["[文字](地址) ![说明](图片地址)", "settings.syntaxLinkImage"], ["- 无序项 / 1. 有序项 / - [ ] 任务", "settings.syntaxList"], ["> 引用块", "settings.syntaxQuote"], ["--- 分隔线", "settings.syntaxRule"] ] }, { titleKey: "settings.syntaxGroupBlocks", rows: [ ["```语言 … ```", "settings.syntaxCodeFence"], ["| 表头 | 表头 |", "settings.syntaxTable"], ["术语 换行 : 定义", "settings.syntaxDefinitionList"], ["[^1] 与 [^1]: 脚注内容", "settings.syntaxFootnote"], ["> [!NOTE] / [!TIP] / [!WARNING]", "settings.syntaxAlert"], ["--- YAML --- 置于文首", "settings.syntaxFrontmatter"] ] }, { titleKey: "settings.syntaxGroupExtended", rows: [ ["==高亮== ++插入++", "settings.syntaxHighlight"], ["H~2~O x^2^", "settings.syntaxSubSup"], ["$行内公式$ $$块级公式$$", "settings.syntaxMath"], ["```mermaid … ```", "settings.syntaxMermaid"], ["```svg … ``` 或内联 <svg>", "settings.syntaxSvg"], ["安全的原始 HTML 标签", "settings.syntaxHtml"] ] } ]; function renderSyntaxReference() { const container = document.querySelector("[data-tlf-settings-syntax]"); if (!container) { return; } container.innerHTML = SYNTAX_GROUPS.map((group) => ` <div class="tlf-settings-card tlf-settings-shortcut-group"> <h3 class="tlf-settings-type-title">${escapeHtml(TabLocalFiles.t(group.titleKey))}</h3> <dl class="tlf-settings-shortcut-list"> ${group.rows .map(([syntax, descriptionKey]) => ` <div class="tlf-settings-shortcut-row"> <dt class="tlf-settings-syntax-sample"><code>${escapeHtml(syntax)}</code></dt> <dd class="tlf-settings-shortcut-label">${escapeHtml(TabLocalFiles.t(descriptionKey))}</dd> </div> `) .join("")} </dl> </div> `).join(""); } function allCatalogExtensions() { return TabLocalFiles.FILE_TYPE_CATALOG.flatMap((entry) => entry.extensions); } function bindGeneralSettings() { document.querySelectorAll("[data-tlf-settings-directory]").forEach((input) => { input.addEventListener("change", () => { const key = input.dataset.tlfSettingsDirectory; if (!key) { return; } TabLocalFiles.Preferences.update({ directory: { [key]: input.checked } }); }); }); document.querySelectorAll("[data-tlf-settings-preview]").forEach((input) => { input.addEventListener("change", () => { const key = input.dataset.tlfSettingsPreview; if (!key) { return; } TabLocalFiles.Preferences.update({ preview: { [key]: input.checked } }); }); }); syncGeneralSettings(); } function syncGeneralSettings() { const directory = (TabLocalFiles.Preferences.read().directory || {}); document.querySelectorAll("[data-tlf-settings-directory]").forEach((input) => { const key = input.dataset.tlfSettingsDirectory; if (!key) { return; } input.checked = key === "showHiddenFiles" ? directory[key] === true : directory[key] !== false; }); document.querySelectorAll("[data-tlf-settings-preview]").forEach((input) => { if (input.dataset.tlfSettingsPreview === "documentTitle") { input.checked = TabLocalFiles.Preferences.useDocumentTitle(); } else if (input.dataset.tlfSettingsPreview === "lineNumbers") { input.checked = TabLocalFiles.Preferences.showLineNumbers(); } else if (input.dataset.tlfSettingsPreview === "wordWrap") { input.checked = TabLocalFiles.Preferences.useWordWrap(); } }); } function renderFileTypeList() { const container = document.querySelector("[data-tlf-settings-file-types]"); if (!container) { return; } container.innerHTML = TabLocalFiles.FILE_TYPE_CATALOG.map((entry) => renderTypeGroup(entry)).join(""); bindOpenModeControls(container); bindSystemDefaultControls(container); bindSetAllDefaultControls(container); } function renderTypeGroup(entry) { return ` <section class="tlf-settings-type-group"> <div class="tlf-settings-type-header"> <h3 class="tlf-settings-type-title">${escapeHtml(TabLocalFiles.t(entry.labelKey))}</h3> <button class="tlf-settings-set-all-default" type="button" data-tlf-settings-set-all-default="${escapeAttr(entry.id)}">${escapeHtml(TabLocalFiles.t("settings.setAllAsDefault"))}</button> </div> <div class="tlf-settings-card tlf-settings-extension-card"> <div class="tlf-settings-extension-row tlf-settings-extension-head" aria-hidden="true"> <span class="tlf-settings-extension-name"></span> <span class="tlf-settings-extension-handler">${escapeHtml(TabLocalFiles.t("settings.currentHandler"))}</span> <span class="tlf-settings-column-label tlf-settings-default-column">${escapeHtml(TabLocalFiles.t("settings.systemDefault"))}</span> <span class="tlf-settings-column-label tlf-settings-open-mode-column">${escapeHtml(TabLocalFiles.t("settings.openWith"))}</span> </div> ${entry.extensions.map((extension) => renderExtensionRow(extension)).join("")} </div> </section> `; } function renderExtensionRow(extension) { return ` <div class="tlf-settings-extension-row" data-tlf-settings-extension="${escapeAttr(extension)}"> <span class="tlf-settings-extension-name">${escapeHtml(extension)}</span> <span class="tlf-settings-extension-handler" data-tlf-settings-handler>${escapeHtml(TabLocalFiles.t("settings.detecting"))}</span> <span class="tlf-settings-default-cell" data-tlf-settings-default-cell>${renderDefaultCell(extension)}</span> <span class="tlf-settings-open-mode" role="radiogroup" aria-label="${escapeAttr(TabLocalFiles.t("settings.openWith"))}"> <button type="button" class="tlf-settings-open-mode-option" role="radio" aria-checked="false" data-tlf-settings-open-mode="previewer"> ${renderIcon("file-pen-line")}<span>${escapeHtml(TabLocalFiles.t("settings.openWithPreviewer"))}</span> </button> ${renderNativeOpenModeOption(extension)} </span> </div> `; } function renderNativeOpenModeOption(extension) { const label = TabLocalFiles.t("settings.openWithNative"); if (TabLocalFiles.catalogChromeNativeRenderable(extension) === false) { return ` <button type="button" class="tlf-settings-open-mode-option" role="radio" aria-checked="false" aria-disabled="true" disabled title="${escapeAttr(TabLocalFiles.t("settings.openWithNativeUnavailable"))}" data-tlf-settings-open-mode="native"> ${renderIcon("file")}<span>${escapeHtml(label)}</span> </button> `; } return ` <button type="button" class="tlf-settings-open-mode-option" role="radio" aria-checked="false" data-tlf-settings-open-mode="native"> ${renderIcon("file")}<span>${escapeHtml(label)}</span> </button> `; } function renderDefaultCell(extension, info) { if (!info) { return `<span class="tlf-settings-default-status tlf-settings-default-status-muted">${escapeHtml(TabLocalFiles.t("settings.detecting"))}</span>`; } if (info.state === "default") { return ` <span class="tlf-settings-default-status tlf-settings-default-status-active" title="${escapeAttr(TabLocalFiles.t("settings.unsetDefaultHint"))}" aria-label="${escapeAttr(TabLocalFiles.t("settings.systemDefaultActive"))}"> ${renderIcon("circle-check")} </span> `; } if (info.canSetDefault && TabLocalFiles.catalogOsAssociation(extension) !== "none") { return `<button class="tlf-settings-set-default-button" type="button" data-tlf-settings-set-default>${escapeHtml(TabLocalFiles.t("settings.setAsDefault"))}</button>`; } const unsupportedLabel = TabLocalFiles.t("settings.setDefaultUnsupported", { extension }); return `<span class="tlf-settings-default-status tlf-settings-default-status-muted" title="${escapeAttr(unsupportedLabel)}" aria-label="${escapeAttr(unsupportedLabel)}">—</span>`; } function bindOpenModeControls(container) { container.querySelectorAll("[data-tlf-settings-open-mode]").forEach((button) => { button.addEventListener("click", () => { const extension = extensionForControl(button); const mode = button.dataset.tlfSettingsOpenMode; if (!extension || !mode) { return; } const nextMode = mode === TabLocalFiles.catalogDefaultOpenMode(extension) ? undefined : mode; TabLocalFiles.Preferences.writeOpenMode(extension, nextMode); syncOpenModeControls(); }); }); } function syncOpenModeControls() { document.querySelectorAll("[data-tlf-settings-extension]").forEach((row) => { const extension = row.dataset.tlfSettingsExtension; if (!extension) { return; } const activeMode = TabLocalFiles.effectiveOpenMode(extension) || "previewer"; row.querySelectorAll("[data-tlf-settings-open-mode]").forEach((button) => { const active = button.dataset.tlfSettingsOpenMode === activeMode; button.setAttribute("aria-checked", String(active)); button.classList.toggle("tlf-settings-open-mode-option-active", active); }); }); } function bindSystemDefaultControls(container) { container.addEventListener("click", (event) => { const button = event.target.closest("[data-tlf-settings-set-default]"); if (!button) { return; } const extension = extensionForControl(button); if (extension) { void requestSetSystemDefault(extension); } }); } function bindSetAllDefaultControls(container) { container.querySelectorAll("[data-tlf-settings-set-all-default]").forEach((button) => { button.addEventListener("click", () => { const entry = TabLocalFiles.FILE_TYPE_CATALOG.find((candidate) => candidate.id === button.dataset.tlfSettingsSetAllDefault); if (entry) { void requestSetAllSystemDefaults(entry.extensions); } }); }); } function extensionsPendingDefault(extensions, infoByExtension) { return extensions.filter((extension) => { const info = infoByExtension[extension]; return Boolean(info && info.state !== "default" && info.canSetDefault) && TabLocalFiles.catalogOsAssociation(extension) !== "none"; }); } SettingsPage.extensionsPendingDefault = extensionsPendingDefault; async function requestSetAllSystemDefaults(extensions) { const pending = extensionsPendingDefault(extensions, systemOpenInfoByExtension); if (!pending.length) { const detected = extensions.some((extension) => Boolean(systemOpenInfoByExtension[extension])); showStatus(detected ? TabLocalFiles.t("settings.setAllNothingPending") : TabLocalFiles.t("settings.detectUnavailable")); return; } for (const extension of pending) { await requestSetSystemDefault(extension); } } async function requestSetSystemDefault(extension) { const api = tabLocalFilePreviewApi(); const request = api?.requestSetSystemDefault; if (!api || !request) { showStatus(TabLocalFiles.t("settings.detectUnavailable")); return; } const result = await new Promise((resolve) => { try { request.call(api, extension, (value) => { void chromeRuntime()?.lastError; resolve(value); }); } catch { resolve(undefined); } }); if (result === "requested") { showStatus(TabLocalFiles.t("settings.setDefaultRequested", { extension })); } else if (result === "opened_settings") { showStatus(TabLocalFiles.t("settings.openedSystemSettings", { extension })); } else if (result === "unsupported") { showStatus(TabLocalFiles.t("settings.setDefaultUnsupported", { extension })); return; } else { showStatus(TabLocalFiles.t("settings.setDefaultFailed", { extension })); return; } RE_DETECT_DELAYS_MS.forEach((delay) => { window.setTimeout(() => { void detectSystemOpenInfo([extension]); }, delay); }); } async function detectSystemOpenInfo(extensions) { const api = tabLocalFilePreviewApi(); const query = api?.getSystemOpenInfo; if (!api || !query) { markDetectionUnavailable(); return; } const infos = await new Promise((resolve) => { try { query.call(api, extensions, (value) => { resolve(chromeRuntime()?.lastError ? undefined : value); }); } catch { resolve(undefined); } }); if (!infos) { markDetectionUnavailable(); return; } infos.forEach((info) => { systemOpenInfoByExtension[info.extension] = info; }); applySystemOpenInfo(); } function markDetectionUnavailable() { document.querySelectorAll("[data-tlf-settings-extension]").forEach((row) => { const handler = row.querySelector("[data-tlf-settings-handler]"); if (handler) { handler.textContent = TabLocalFiles.t("settings.detectUnavailable"); handler.classList.add("tlf-settings-extension-handler-muted"); } const defaultCell = row.querySelector("[data-tlf-settings-default-cell]"); if (defaultCell) { const label = TabLocalFiles.t("settings.detectUnavailable"); defaultCell.innerHTML = `<span class="tlf-settings-default-status tlf-settings-default-status-muted" title="${escapeAttr(label)}" aria-label="${escapeAttr(label)}">—</span>`; } }); } function applySystemOpenInfo() { document.querySelectorAll("[data-tlf-settings-extension]").forEach((row) => { const extension = row.dataset.tlfSettingsExtension; const info = extension ? systemOpenInfoByExtension[extension] : undefined; if (!extension || !info) { return; } const handler = row.querySelector("[data-tlf-settings-handler]"); if (handler) { handler.classList.remove("tlf-settings-extension-handler-muted"); handler.classList.toggle("tlf-settings-extension-handler-self", info.state === "default"); if (info.state === "default") { handler.textContent = TabLocalFiles.t("settings.currentHandlerSelf"); } else if (info.handlerName) { handler.textContent = info.handlerName; } else { handler.textContent = TabLocalFiles.t("settings.currentHandlerUnknown"); handler.classList.add("tlf-settings-extension-handler-muted"); } } const defaultCell = row.querySelector("[data-tlf-settings-default-cell]"); if (defaultCell) { defaultCell.innerHTML = renderDefaultCell(extension, info); } }); } function extensionForControl(control) { return control.closest("[data-tlf-settings-extension]")?.dataset.tlfSettingsExtension; } let statusTimer; function showStatus(message) { const status = document.querySelector("[data-tlf-settings-status]"); if (!status) { return; } status.textContent = message; status.hidden = false; window.clearTimeout(statusTimer); statusTimer = window.setTimeout(() => { status.hidden = true; }, 6000); } function tabLocalFilePreviewApi() { return globalThis.chrome?.tabLocalFilePreview; } function chromeRuntime() { return globalThis.chrome?.runtime; } function escapeHtml(value) { return value .replace(/&/g, "&") .replace(/</g, "<") .replace(/>/g, ">") .replace(/"/g, """) .replace(/'/g, "'"); } function escapeAttr(value) { return escapeHtml(value); } const ICON_PATHS = { "circle-check": '<circle cx="12" cy="12" r="10"></circle><path d="m9 12 2 2 4-4"></path>', file: '<path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z"></path><path d="M14 2v4a2 2 0 0 0 2 2h4"></path>', "file-pen-line": '<path d="m18 5-2.414-2.414A2 2 0 0 0 14.172 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2"></path><path d="M21.378 12.626a1 1 0 0 0-3.004-3.004l-4.01 4.012a2 2 0 0 0-.506.854l-.837 2.87a.5.5 0 0 0 .62.62l2.87-.837a2 2 0 0 0 .854-.506z"></path><path d="M8 18h1"></path>' }; function renderIcon(name) { return `<svg class="tlf-lucide-icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">${ICON_PATHS[name]}</svg>`; } })(SettingsPage = TabLocalFiles.SettingsPage || (TabLocalFiles.SettingsPage = {})); })(TabLocalFiles || (TabLocalFiles = {})); if (typeof document !== "undefined") { TabLocalFiles.SettingsPage.start(); }