mirror of
https://github.com/fancive/obsidian-parallel-reader.git
synced 2026-07-22 06:53:43 +00:00
refactor: extract i18n translation strings to dedicated i18n-strings.ts
Moves the STRINGS object (330 lines of zh/en translations) from i18n.ts into a separate i18n-strings.ts file. i18n.ts now contains only the resolveUiLanguage and translate logic (28 lines). Change-Id: I9ddea79bddb4e8313dc16aae2a7c8d4eeff96fe1
This commit is contained in:
parent
f4e0d84a9c
commit
15b471efad
3 changed files with 346 additions and 342 deletions
28
main.js
28
main.js
File diff suppressed because one or more lines are too long
330
src/i18n-strings.ts
Normal file
330
src/i18n-strings.ts
Normal file
|
|
@ -0,0 +1,330 @@
|
|||
'use strict';
|
||||
|
||||
export const STRINGS: Record<string, Record<string, string>> = {
|
||||
zh: {
|
||||
appTitle: '对照阅读笔记',
|
||||
settingsTitle: 'Parallel Reader 设置',
|
||||
emptyOpenNote: '打开一篇笔记,然后运行命令:',
|
||||
emptyNoCache: '该笔记尚无对照笔记缓存。运行命令:',
|
||||
commandGenerate: 'Parallel Reader: 为当前笔记生成对照笔记',
|
||||
displayName: '对照阅读笔记',
|
||||
ribbonOpen: '打开对照笔记面板',
|
||||
cmdRun: '为当前笔记生成对照笔记(缓存优先)',
|
||||
cmdRegen: '强制重新生成(绕过缓存)',
|
||||
cmdOpenView: '打开对照笔记面板',
|
||||
cmdExport: '导出当前对照笔记到 Vault',
|
||||
cmdCopyMarkdown: '复制当前对照笔记 Markdown',
|
||||
cmdCancel: '取消当前对照笔记生成',
|
||||
cmdClearCurrent: '清除当前笔记的缓存',
|
||||
cmdClearAll: '清除所有缓存',
|
||||
cmdCardPrev: '聚焦上一张摘要卡片',
|
||||
cmdCardNext: '聚焦下一张摘要卡片',
|
||||
cmdCardJump: '跳转到当前摘要卡片原文',
|
||||
actionCancel: '取消生成',
|
||||
actionRegenerate: '重新生成',
|
||||
actionCopyAll: '复制全部 Markdown',
|
||||
actionExport: '导出到 Vault',
|
||||
staleBanner: '源笔记或生成配置已修改,当前是旧缓存。',
|
||||
loadingDefault: '正在生成对照笔记...',
|
||||
loadingGenerating: '对照阅读:让 LLM 读全文并自适应切段...',
|
||||
loadingSubtitle: '可以继续阅读原文,生成完成后会自动刷新右侧卡片。',
|
||||
errorTitle: '生成失败',
|
||||
actionCopyError: '复制错误信息',
|
||||
emptyCard: '(未生成)',
|
||||
anchorMismatch: 'anchor 匹配失败,无法滚动联动',
|
||||
menuCopyMarkdown: '复制 Markdown',
|
||||
menuCopyPlain: '复制纯文本',
|
||||
menuCopyAnchor: '复制 anchor 引用',
|
||||
menuJumpSource: '跳转到原文',
|
||||
menuEditCard: '编辑此卡片',
|
||||
menuDeleteCard: '删除此卡片',
|
||||
copiedMarkdown: '已复制 Markdown',
|
||||
copiedPlain: '已复制纯文本',
|
||||
copiedAnchor: '已复制引用原文',
|
||||
copiedAllMarkdown: '已复制全部 Markdown',
|
||||
copyFailed: '复制失败:{error}',
|
||||
actionFailed: '{label}失败:{error}',
|
||||
exported: '已导出 → {path}',
|
||||
noCurrentNote: '没有当前笔记',
|
||||
cacheClearedFile: '已清除缓存:{name}',
|
||||
cacheClearedAll: '已清除 {count} 条缓存',
|
||||
noCancelableJob: '当前没有可取消的生成任务',
|
||||
cancelRequested: '已请求取消生成',
|
||||
cancelRequestedApiInFlight: '已请求取消生成;当前 API 请求无法立即中断,返回后会丢弃结果。',
|
||||
actionGenerate: '生成对照笔记',
|
||||
fileMenuGenerate: '生成对照笔记',
|
||||
fileMenuRegen: '强制重新生成对照笔记',
|
||||
fileMenuClear: '清除对照笔记缓存',
|
||||
noExportContent: '当前没有可导出的对照笔记',
|
||||
noCopyContent: '当前没有可复制的对照笔记',
|
||||
noActiveCard: '当前没有可跳转的摘要卡片',
|
||||
viewOpenFailed: '无法打开对照笔记面板',
|
||||
confirmRegenerateEditedCards: '这篇笔记的对照卡片已被手动编辑。重新生成会覆盖这些修改,是否继续?',
|
||||
regenerateCancelled: '已取消重新生成',
|
||||
cardDeleted: '已删除此卡片',
|
||||
cardSaved: '已保存此卡片',
|
||||
editCardTitle: '编辑摘要卡片',
|
||||
editCardTitleField: '标题',
|
||||
editCardGistField: '领读',
|
||||
editCardBulletsField: '要点(每行一条)',
|
||||
editCardCancel: '取消',
|
||||
editCardSave: '保存',
|
||||
openNoteFirst: '先打开一篇笔记',
|
||||
alreadyGenerating: '该笔记正在生成对照笔记',
|
||||
emptyNote: '笔记为空',
|
||||
longNoteTruncated: '笔记较长:仅发送前 {count} 个字符给模型',
|
||||
generatingNotice: '对照阅读:让 LLM 读全文并自适应切段…',
|
||||
noCardsReturned: 'LLM 未返回任何 card',
|
||||
generationDone: '对照笔记生成完成:{count} 段{suffix}',
|
||||
unanchoredSuffix: '(⚠ {count} 段 anchor 未匹配)',
|
||||
cancelled: '已取消生成',
|
||||
cancelledError: '生成已取消',
|
||||
generationFailed: '生成失败{kind}:{error}',
|
||||
errorCustomProviderBaseUrlRequired: '自定义 provider 需要填写 API Base URL。',
|
||||
errorApiBaseUrlMissing: 'API Base URL 未设置。请在设置里选择 provider 或填写自定义 base URL。',
|
||||
errorModelMissing: 'Model 未设置。请在设置里填写模型 ID。',
|
||||
errorApiKeyMissing: 'API key 未设置。请在设置里填写 API Key{hint}。',
|
||||
errorApiKeyEnvHint: ' 或环境变量 {envVar}',
|
||||
errorLlmNonJson: 'LLM 返回非 JSON:\n{excerpt}',
|
||||
errorCustomHeadersJsonParse: '自定义 headers JSON 解析失败:{error}',
|
||||
errorCustomHeadersJsonObject: '自定义 headers JSON 必须是对象',
|
||||
errorCustomHeadersLineFormat: '自定义 headers 每行格式应为 `Header-Name: value`',
|
||||
errorProviderNonJson: '{label} 返回非 JSON:\n{excerpt}',
|
||||
errorProviderRequestFailed: '{label} 请求失败:{error}',
|
||||
errorProviderApiStatus: '{label} API 返回 HTTP {status}:{excerpt}',
|
||||
noEditor: '找不到源笔记对应的编辑器窗口',
|
||||
settingUiLanguageName: '界面语言',
|
||||
settingUiLanguageDesc: '控制插件界面、命令和提示文案;Auto 跟随 Obsidian/系统语言',
|
||||
settingBackendName: 'Backend',
|
||||
settingBackendDesc: '生成 bullet 的后端:CLI 复用本机登录;API 支持 OpenAI/Anthropic/Gemini 及兼容代理',
|
||||
settingCliPathName: 'CLI 路径(可选)',
|
||||
settingCliPathDesc: '留空则自动探测常见位置;Obsidian GUI 启动时不继承 shell PATH,必要时填绝对路径',
|
||||
settingCliPathPlaceholder: '例:/Users/you/bin/codex',
|
||||
settingCliTimeoutName: 'CLI 超时 (ms)',
|
||||
apiProviderHeader: 'API Provider',
|
||||
settingProviderPresetName: 'Provider preset',
|
||||
settingProviderPresetDesc: '参考 OpenClaw 的 provider/model 思路:preset 只负责协议、base URL 和认证默认值',
|
||||
settingApiFormatName: 'API format',
|
||||
settingApiFormatDesc: '不同 provider 的 wire protocol;OpenAI-compatible 代理通常选 Chat Completions',
|
||||
settingBaseUrlName: 'Base URL',
|
||||
settingBaseUrlDesc: '填 provider 根地址,不要附加 /chat/completions;留空时使用 preset 默认值',
|
||||
settingApiKeyName: 'API Key',
|
||||
settingApiKeyDesc: '本地 Ollama/LM Studio 可留空;其他 provider 通常需要 key',
|
||||
settingApiKeyEnvName: 'API Key 环境变量',
|
||||
settingApiKeyEnvDesc: '可选;Obsidian GUI 不一定继承 shell 环境,直接填 API Key 更稳定',
|
||||
settingAuthTypeName: '认证方式',
|
||||
settingAuthTypeDesc: 'Auto 使用 provider preset;自定义代理可按需要改成 Bearer、x-api-key 或 none',
|
||||
settingHeadersName: '额外 headers',
|
||||
settingHeadersDesc: '可选。支持 JSON 对象或每行 `Header: value`,用于 Cloudflare AI Gateway 等代理',
|
||||
settingMaxTokensName: '最大输出 tokens',
|
||||
settingModelName: 'Model',
|
||||
settingModelDescApi:
|
||||
'API 调用的模型 ID;支持 OpenClaw 风格 provider/model,若 provider 前缀匹配当前 preset 会自动剥离',
|
||||
settingModelDescCli: 'Claude Code 下会传 --model;Codex 下通常忽略(用 Codex 默认配置)',
|
||||
settingMaxInputName: '最大输入字符数',
|
||||
settingMaxInputDesc: '超过该长度会截断后再发送给模型;长上下文模型可适当调大',
|
||||
promptHeader: 'Prompt',
|
||||
settingPromptLanguageName: '输出语言',
|
||||
settingPromptLanguageDesc: '控制 title/gist/bullets 的语言;anchor 始终逐字复制原文',
|
||||
settingCardRangeName: '卡片数量范围',
|
||||
settingCardRangeDesc: '模型会在这个范围内自适应切段',
|
||||
settingCustomPromptName: '自定义 system prompt',
|
||||
settingCustomPromptDesc: '可选。支持变量:{minCards}、{maxCards}、{languageInstruction}、{schema}、{example}',
|
||||
settingCustomPromptPlaceholder: '留空使用内置 prompt',
|
||||
settingTestBackendName: '测试当前后端',
|
||||
settingTestBackendDescApi: '会发起一次最小 LLM 请求验证 API 设置',
|
||||
settingTestBackendDescCli: '调用 `<cli> --version` 验证 spawn 能找到二进制',
|
||||
backendTestFailed: '✗ 后端测试失败:{error}',
|
||||
settingExportFolderName: '导出文件夹',
|
||||
settingExportFolderDesc: '对照笔记生成位置(相对 Vault 根)',
|
||||
cacheHeader: '缓存',
|
||||
settingMaxCacheName: '最大缓存篇数',
|
||||
settingMaxCacheDesc: '超过上限后按最近访问时间淘汰最旧的笔记缓存;缓存保存在插件目录的 cache.json',
|
||||
cachePruned: '已淘汰 {count} 条旧缓存',
|
||||
cachedNotesName: '已缓存笔记:{count} 篇',
|
||||
cachedNotesDesc: '缓存以源笔记 SHA1 + 生成配置指纹作为失效键,源笔记或模型配置修改后会显示 stale 提示',
|
||||
clearAllCacheButton: '清除所有缓存',
|
||||
settingStreamingName: '流式输出',
|
||||
settingStreamingDesc: '启用后生成时实时显示 LLM 输出进度(仅 OpenAI Chat 和 Anthropic 格式支持)',
|
||||
settingStreamingTimeoutName: '流式超时 (ms)',
|
||||
settingStreamingTimeoutDesc: '单次流式请求的超时时间;低于 1000ms 或非法值会恢复默认值',
|
||||
cmdBatchGenerate: '批量生成对照笔记(当前文件夹)',
|
||||
batchSelectFolder: '请输入要批量处理的文件夹路径(留空为 Vault 根目录)',
|
||||
batchFolderPrompt: '文件夹路径',
|
||||
batchAlreadyRunning: '批量生成正在运行',
|
||||
batchCancelRequested: '已请求取消批量生成;当前文件结束后停止',
|
||||
batchInvalidFolderInput: '文件夹路径无效:{path}',
|
||||
batchFolderNotFound: '文件夹不存在:{path}',
|
||||
batchNoMarkdown: '该文件夹下没有 Markdown 文件',
|
||||
batchProgress: '批量生成:{current}/{total}',
|
||||
batchDone: '批量生成完成:共处理 {processed}/{total} 篇,跳过缓存 {skipped} 篇,失败 {errors} 篇',
|
||||
batchCancelled: '批量生成已取消:已处理 {processed}/{total} 篇,跳过缓存 {skipped} 篇,失败 {errors} 篇',
|
||||
},
|
||||
en: {
|
||||
appTitle: 'Parallel Reader',
|
||||
settingsTitle: 'Parallel Reader Settings',
|
||||
emptyOpenNote: 'Open a note, then run:',
|
||||
emptyNoCache: 'This note has no cached parallel notes. Run:',
|
||||
commandGenerate: 'Parallel Reader: Generate notes for current note',
|
||||
displayName: 'Parallel Reader',
|
||||
ribbonOpen: 'Open Parallel Reader pane',
|
||||
cmdRun: 'Generate parallel notes for current note (cache first)',
|
||||
cmdRegen: 'Regenerate parallel notes (bypass cache)',
|
||||
cmdOpenView: 'Open Parallel Reader pane',
|
||||
cmdExport: 'Export current parallel notes to Vault',
|
||||
cmdCopyMarkdown: 'Copy current parallel notes as Markdown',
|
||||
cmdCancel: 'Cancel current parallel-note generation',
|
||||
cmdClearCurrent: 'Clear cache for current note',
|
||||
cmdClearAll: 'Clear all caches',
|
||||
cmdCardPrev: 'Focus previous summary card',
|
||||
cmdCardNext: 'Focus next summary card',
|
||||
cmdCardJump: 'Jump current summary card to source',
|
||||
actionCancel: 'Cancel generation',
|
||||
actionRegenerate: 'Regenerate',
|
||||
actionCopyAll: 'Copy all Markdown',
|
||||
actionExport: 'Export to Vault',
|
||||
staleBanner: 'The source note or generation settings changed. This is stale cache.',
|
||||
loadingDefault: 'Generating parallel notes...',
|
||||
loadingGenerating: 'Parallel Reader: asking the LLM to read and segment the full note...',
|
||||
loadingSubtitle: 'You can keep reading. Cards will refresh when generation finishes.',
|
||||
errorTitle: 'Generation failed',
|
||||
actionCopyError: 'Copy error',
|
||||
emptyCard: '(not generated)',
|
||||
anchorMismatch: 'Anchor did not match; scroll sync is unavailable',
|
||||
menuCopyMarkdown: 'Copy Markdown',
|
||||
menuCopyPlain: 'Copy plain text',
|
||||
menuCopyAnchor: 'Copy anchor quote',
|
||||
menuJumpSource: 'Jump to source',
|
||||
menuEditCard: 'Edit this card',
|
||||
menuDeleteCard: 'Delete this card',
|
||||
copiedMarkdown: 'Copied Markdown',
|
||||
copiedPlain: 'Copied plain text',
|
||||
copiedAnchor: 'Copied anchor quote',
|
||||
copiedAllMarkdown: 'Copied all Markdown',
|
||||
copyFailed: 'Copy failed: {error}',
|
||||
actionFailed: '{label} failed: {error}',
|
||||
exported: 'Exported → {path}',
|
||||
noCurrentNote: 'No current note',
|
||||
cacheClearedFile: 'Cleared cache: {name}',
|
||||
cacheClearedAll: 'Cleared {count} cache entries',
|
||||
noCancelableJob: 'No cancellable generation job',
|
||||
cancelRequested: 'Cancel requested',
|
||||
cancelRequestedApiInFlight:
|
||||
'Cancel requested. The in-flight API request cannot be aborted immediately; its result will be ignored.',
|
||||
actionGenerate: 'Generate parallel notes',
|
||||
fileMenuGenerate: 'Generate parallel notes',
|
||||
fileMenuRegen: 'Regenerate parallel notes',
|
||||
fileMenuClear: 'Clear parallel-note cache',
|
||||
noExportContent: 'No parallel notes to export',
|
||||
noCopyContent: 'No parallel notes to copy',
|
||||
noActiveCard: 'No active summary card to jump',
|
||||
viewOpenFailed: 'Could not open the Parallel Reader pane',
|
||||
confirmRegenerateEditedCards:
|
||||
'These parallel-reader cards were edited manually. Regenerating will overwrite those edits. Continue?',
|
||||
regenerateCancelled: 'Regeneration cancelled',
|
||||
cardDeleted: 'Deleted this card',
|
||||
cardSaved: 'Saved this card',
|
||||
editCardTitle: 'Edit summary card',
|
||||
editCardTitleField: 'Title',
|
||||
editCardGistField: 'Gist',
|
||||
editCardBulletsField: 'Bullets (one per line)',
|
||||
editCardCancel: 'Cancel',
|
||||
editCardSave: 'Save',
|
||||
openNoteFirst: 'Open a note first',
|
||||
alreadyGenerating: 'This note is already being generated',
|
||||
emptyNote: 'The note is empty',
|
||||
longNoteTruncated: 'Long note: only the first {count} characters will be sent to the model',
|
||||
generatingNotice: 'Parallel Reader: asking the LLM to read and segment the full note...',
|
||||
noCardsReturned: 'LLM returned no cards',
|
||||
generationDone: 'Generated {count} sections{suffix}',
|
||||
unanchoredSuffix: ' (⚠ {count} anchors unmatched)',
|
||||
cancelled: 'Generation cancelled',
|
||||
cancelledError: 'Generation cancelled',
|
||||
generationFailed: 'Generation failed{kind}: {error}',
|
||||
errorCustomProviderBaseUrlRequired: 'Custom provider requires an API Base URL.',
|
||||
errorApiBaseUrlMissing: 'API Base URL is not set. Choose a provider or enter a custom base URL in settings.',
|
||||
errorModelMissing: 'Model is not set. Enter a model ID in settings.',
|
||||
errorApiKeyMissing: 'API key is not set. Enter an API Key in settings{hint}.',
|
||||
errorApiKeyEnvHint: ' or set environment variable {envVar}',
|
||||
errorLlmNonJson: 'LLM returned non-JSON:\n{excerpt}',
|
||||
errorCustomHeadersJsonParse: 'Custom headers JSON parse failed: {error}',
|
||||
errorCustomHeadersJsonObject: 'Custom headers JSON must be an object',
|
||||
errorCustomHeadersLineFormat: 'Custom headers lines must use `Header-Name: value`',
|
||||
errorProviderNonJson: '{label} returned non-JSON:\n{excerpt}',
|
||||
errorProviderRequestFailed: '{label} request failed: {error}',
|
||||
errorProviderApiStatus: '{label} API returned HTTP {status}: {excerpt}',
|
||||
noEditor: 'Could not find the source note editor',
|
||||
settingUiLanguageName: 'UI language',
|
||||
settingUiLanguageDesc: 'Controls plugin UI, commands, and notices. Auto follows Obsidian/system language.',
|
||||
settingBackendName: 'Backend',
|
||||
settingBackendDesc:
|
||||
'Backend for generating bullets: CLI reuses local login; API supports OpenAI, Anthropic, Gemini, and compatible proxies.',
|
||||
settingCliPathName: 'CLI path (optional)',
|
||||
settingCliPathDesc:
|
||||
'Leave blank to auto-detect common paths. Obsidian launched from the GUI may not inherit shell PATH.',
|
||||
settingCliPathPlaceholder: 'Example: /Users/you/bin/codex',
|
||||
settingCliTimeoutName: 'CLI timeout (ms)',
|
||||
apiProviderHeader: 'API Provider',
|
||||
settingProviderPresetName: 'Provider preset',
|
||||
settingProviderPresetDesc:
|
||||
'OpenClaw-style provider/model setup: presets define protocol, base URL, and auth defaults.',
|
||||
settingApiFormatName: 'API format',
|
||||
settingApiFormatDesc: 'Wire protocol for the provider. OpenAI-compatible proxies usually use Chat Completions.',
|
||||
settingBaseUrlName: 'Base URL',
|
||||
settingBaseUrlDesc: 'Provider root URL, without /chat/completions. Blank uses the preset default.',
|
||||
settingApiKeyName: 'API Key',
|
||||
settingApiKeyDesc: 'Can be blank for local Ollama/LM Studio; most hosted providers require a key.',
|
||||
settingApiKeyEnvName: 'API key env var',
|
||||
settingApiKeyEnvDesc: 'Optional. Obsidian GUI may not inherit shell env; direct API Key is usually more reliable.',
|
||||
settingAuthTypeName: 'Auth type',
|
||||
settingAuthTypeDesc: 'Auto uses provider preset. Custom proxies can use Bearer, x-api-key, or none.',
|
||||
settingHeadersName: 'Extra headers',
|
||||
settingHeadersDesc: 'Optional. JSON object or one `Header: value` per line, useful for Cloudflare AI Gateway.',
|
||||
settingMaxTokensName: 'Max output tokens',
|
||||
settingModelName: 'Model',
|
||||
settingModelDescApi:
|
||||
'Model ID for API calls. Supports OpenClaw-style provider/model; matching provider prefixes are stripped.',
|
||||
settingModelDescCli: 'Passed as --model for Claude Code. Usually ignored by Codex, which uses its default config.',
|
||||
settingMaxInputName: 'Max input characters',
|
||||
settingMaxInputDesc: 'Longer notes are truncated before sending to the model. Raise this for long-context models.',
|
||||
promptHeader: 'Prompt',
|
||||
settingPromptLanguageName: 'Output language',
|
||||
settingPromptLanguageDesc: 'Controls title/gist/bullets language. Anchor is always copied verbatim from source.',
|
||||
settingCardRangeName: 'Card count range',
|
||||
settingCardRangeDesc: 'The model adapts segmentation within this range.',
|
||||
settingCustomPromptName: 'Custom system prompt',
|
||||
settingCustomPromptDesc: 'Optional. Variables: {minCards}, {maxCards}, {languageInstruction}, {schema}, {example}',
|
||||
settingCustomPromptPlaceholder: 'Leave blank to use built-in prompt',
|
||||
settingTestBackendName: 'Test current backend',
|
||||
settingTestBackendDescApi: 'Sends a minimal LLM request to validate API settings.',
|
||||
settingTestBackendDescCli: 'Runs `<cli> --version` to verify the binary can be spawned.',
|
||||
backendTestFailed: '✗ Backend test failed: {error}',
|
||||
settingExportFolderName: 'Export folder',
|
||||
settingExportFolderDesc: 'Parallel-note output location, relative to the Vault root.',
|
||||
cacheHeader: 'Cache',
|
||||
settingMaxCacheName: 'Max cached notes',
|
||||
settingMaxCacheDesc:
|
||||
'Prunes least-recently accessed note caches above this limit. Cache is stored in plugin cache.json.',
|
||||
cachePruned: 'Pruned {count} old cache entries',
|
||||
cachedNotesName: 'Cached notes: {count}',
|
||||
cachedNotesDesc: 'Cache is invalidated by source SHA1 and generation settings fingerprint.',
|
||||
clearAllCacheButton: 'Clear all caches',
|
||||
settingStreamingName: 'Streaming output',
|
||||
settingStreamingDesc:
|
||||
'Show real-time LLM output progress during generation (OpenAI Chat and Anthropic formats only)',
|
||||
settingStreamingTimeoutName: 'Streaming timeout (ms)',
|
||||
settingStreamingTimeoutDesc:
|
||||
'Timeout for one streaming request; invalid values or values below 1000ms use the default',
|
||||
cmdBatchGenerate: 'Batch generate parallel notes (current folder)',
|
||||
batchSelectFolder: 'Enter the folder path to batch-process (leave blank for Vault root)',
|
||||
batchFolderPrompt: 'Folder path',
|
||||
batchAlreadyRunning: 'Batch generation is already running',
|
||||
batchCancelRequested: 'Batch cancellation requested; it will stop after the current note',
|
||||
batchInvalidFolderInput: 'Invalid folder path: {path}',
|
||||
batchFolderNotFound: 'Folder not found: {path}',
|
||||
batchNoMarkdown: 'No Markdown files found in that folder',
|
||||
batchProgress: 'Batch generating: {current}/{total}',
|
||||
batchDone: 'Batch complete: processed {processed}/{total}, skipped {skipped} (cached), errors {errors}',
|
||||
batchCancelled: 'Batch cancelled: processed {processed}/{total}, skipped {skipped} (cached), errors {errors}',
|
||||
},
|
||||
};
|
||||
330
src/i18n.ts
330
src/i18n.ts
|
|
@ -1,335 +1,9 @@
|
|||
'use strict';
|
||||
|
||||
import { STRINGS } from './i18n-strings';
|
||||
import type { PluginSettings } from './types';
|
||||
|
||||
export const STRINGS: Record<string, Record<string, string>> = {
|
||||
zh: {
|
||||
appTitle: '对照阅读笔记',
|
||||
settingsTitle: 'Parallel Reader 设置',
|
||||
emptyOpenNote: '打开一篇笔记,然后运行命令:',
|
||||
emptyNoCache: '该笔记尚无对照笔记缓存。运行命令:',
|
||||
commandGenerate: 'Parallel Reader: 为当前笔记生成对照笔记',
|
||||
displayName: '对照阅读笔记',
|
||||
ribbonOpen: '打开对照笔记面板',
|
||||
cmdRun: '为当前笔记生成对照笔记(缓存优先)',
|
||||
cmdRegen: '强制重新生成(绕过缓存)',
|
||||
cmdOpenView: '打开对照笔记面板',
|
||||
cmdExport: '导出当前对照笔记到 Vault',
|
||||
cmdCopyMarkdown: '复制当前对照笔记 Markdown',
|
||||
cmdCancel: '取消当前对照笔记生成',
|
||||
cmdClearCurrent: '清除当前笔记的缓存',
|
||||
cmdClearAll: '清除所有缓存',
|
||||
cmdCardPrev: '聚焦上一张摘要卡片',
|
||||
cmdCardNext: '聚焦下一张摘要卡片',
|
||||
cmdCardJump: '跳转到当前摘要卡片原文',
|
||||
actionCancel: '取消生成',
|
||||
actionRegenerate: '重新生成',
|
||||
actionCopyAll: '复制全部 Markdown',
|
||||
actionExport: '导出到 Vault',
|
||||
staleBanner: '源笔记或生成配置已修改,当前是旧缓存。',
|
||||
loadingDefault: '正在生成对照笔记...',
|
||||
loadingGenerating: '对照阅读:让 LLM 读全文并自适应切段...',
|
||||
loadingSubtitle: '可以继续阅读原文,生成完成后会自动刷新右侧卡片。',
|
||||
errorTitle: '生成失败',
|
||||
actionCopyError: '复制错误信息',
|
||||
emptyCard: '(未生成)',
|
||||
anchorMismatch: 'anchor 匹配失败,无法滚动联动',
|
||||
menuCopyMarkdown: '复制 Markdown',
|
||||
menuCopyPlain: '复制纯文本',
|
||||
menuCopyAnchor: '复制 anchor 引用',
|
||||
menuJumpSource: '跳转到原文',
|
||||
menuEditCard: '编辑此卡片',
|
||||
menuDeleteCard: '删除此卡片',
|
||||
copiedMarkdown: '已复制 Markdown',
|
||||
copiedPlain: '已复制纯文本',
|
||||
copiedAnchor: '已复制引用原文',
|
||||
copiedAllMarkdown: '已复制全部 Markdown',
|
||||
copyFailed: '复制失败:{error}',
|
||||
actionFailed: '{label}失败:{error}',
|
||||
exported: '已导出 → {path}',
|
||||
noCurrentNote: '没有当前笔记',
|
||||
cacheClearedFile: '已清除缓存:{name}',
|
||||
cacheClearedAll: '已清除 {count} 条缓存',
|
||||
noCancelableJob: '当前没有可取消的生成任务',
|
||||
cancelRequested: '已请求取消生成',
|
||||
cancelRequestedApiInFlight: '已请求取消生成;当前 API 请求无法立即中断,返回后会丢弃结果。',
|
||||
actionGenerate: '生成对照笔记',
|
||||
fileMenuGenerate: '生成对照笔记',
|
||||
fileMenuRegen: '强制重新生成对照笔记',
|
||||
fileMenuClear: '清除对照笔记缓存',
|
||||
noExportContent: '当前没有可导出的对照笔记',
|
||||
noCopyContent: '当前没有可复制的对照笔记',
|
||||
noActiveCard: '当前没有可跳转的摘要卡片',
|
||||
viewOpenFailed: '无法打开对照笔记面板',
|
||||
confirmRegenerateEditedCards: '这篇笔记的对照卡片已被手动编辑。重新生成会覆盖这些修改,是否继续?',
|
||||
regenerateCancelled: '已取消重新生成',
|
||||
cardDeleted: '已删除此卡片',
|
||||
cardSaved: '已保存此卡片',
|
||||
editCardTitle: '编辑摘要卡片',
|
||||
editCardTitleField: '标题',
|
||||
editCardGistField: '领读',
|
||||
editCardBulletsField: '要点(每行一条)',
|
||||
editCardCancel: '取消',
|
||||
editCardSave: '保存',
|
||||
openNoteFirst: '先打开一篇笔记',
|
||||
alreadyGenerating: '该笔记正在生成对照笔记',
|
||||
emptyNote: '笔记为空',
|
||||
longNoteTruncated: '笔记较长:仅发送前 {count} 个字符给模型',
|
||||
generatingNotice: '对照阅读:让 LLM 读全文并自适应切段…',
|
||||
noCardsReturned: 'LLM 未返回任何 card',
|
||||
generationDone: '对照笔记生成完成:{count} 段{suffix}',
|
||||
unanchoredSuffix: '(⚠ {count} 段 anchor 未匹配)',
|
||||
cancelled: '已取消生成',
|
||||
cancelledError: '生成已取消',
|
||||
generationFailed: '生成失败{kind}:{error}',
|
||||
errorCustomProviderBaseUrlRequired: '自定义 provider 需要填写 API Base URL。',
|
||||
errorApiBaseUrlMissing: 'API Base URL 未设置。请在设置里选择 provider 或填写自定义 base URL。',
|
||||
errorModelMissing: 'Model 未设置。请在设置里填写模型 ID。',
|
||||
errorApiKeyMissing: 'API key 未设置。请在设置里填写 API Key{hint}。',
|
||||
errorApiKeyEnvHint: ' 或环境变量 {envVar}',
|
||||
errorLlmNonJson: 'LLM 返回非 JSON:\n{excerpt}',
|
||||
errorCustomHeadersJsonParse: '自定义 headers JSON 解析失败:{error}',
|
||||
errorCustomHeadersJsonObject: '自定义 headers JSON 必须是对象',
|
||||
errorCustomHeadersLineFormat: '自定义 headers 每行格式应为 `Header-Name: value`',
|
||||
errorProviderNonJson: '{label} 返回非 JSON:\n{excerpt}',
|
||||
errorProviderRequestFailed: '{label} 请求失败:{error}',
|
||||
errorProviderApiStatus: '{label} API 返回 HTTP {status}:{excerpt}',
|
||||
noEditor: '找不到源笔记对应的编辑器窗口',
|
||||
settingUiLanguageName: '界面语言',
|
||||
settingUiLanguageDesc: '控制插件界面、命令和提示文案;Auto 跟随 Obsidian/系统语言',
|
||||
settingBackendName: 'Backend',
|
||||
settingBackendDesc: '生成 bullet 的后端:CLI 复用本机登录;API 支持 OpenAI/Anthropic/Gemini 及兼容代理',
|
||||
settingCliPathName: 'CLI 路径(可选)',
|
||||
settingCliPathDesc: '留空则自动探测常见位置;Obsidian GUI 启动时不继承 shell PATH,必要时填绝对路径',
|
||||
settingCliPathPlaceholder: '例:/Users/you/bin/codex',
|
||||
settingCliTimeoutName: 'CLI 超时 (ms)',
|
||||
apiProviderHeader: 'API Provider',
|
||||
settingProviderPresetName: 'Provider preset',
|
||||
settingProviderPresetDesc: '参考 OpenClaw 的 provider/model 思路:preset 只负责协议、base URL 和认证默认值',
|
||||
settingApiFormatName: 'API format',
|
||||
settingApiFormatDesc: '不同 provider 的 wire protocol;OpenAI-compatible 代理通常选 Chat Completions',
|
||||
settingBaseUrlName: 'Base URL',
|
||||
settingBaseUrlDesc: '填 provider 根地址,不要附加 /chat/completions;留空时使用 preset 默认值',
|
||||
settingApiKeyName: 'API Key',
|
||||
settingApiKeyDesc: '本地 Ollama/LM Studio 可留空;其他 provider 通常需要 key',
|
||||
settingApiKeyEnvName: 'API Key 环境变量',
|
||||
settingApiKeyEnvDesc: '可选;Obsidian GUI 不一定继承 shell 环境,直接填 API Key 更稳定',
|
||||
settingAuthTypeName: '认证方式',
|
||||
settingAuthTypeDesc: 'Auto 使用 provider preset;自定义代理可按需要改成 Bearer、x-api-key 或 none',
|
||||
settingHeadersName: '额外 headers',
|
||||
settingHeadersDesc: '可选。支持 JSON 对象或每行 `Header: value`,用于 Cloudflare AI Gateway 等代理',
|
||||
settingMaxTokensName: '最大输出 tokens',
|
||||
settingModelName: 'Model',
|
||||
settingModelDescApi:
|
||||
'API 调用的模型 ID;支持 OpenClaw 风格 provider/model,若 provider 前缀匹配当前 preset 会自动剥离',
|
||||
settingModelDescCli: 'Claude Code 下会传 --model;Codex 下通常忽略(用 Codex 默认配置)',
|
||||
settingMaxInputName: '最大输入字符数',
|
||||
settingMaxInputDesc: '超过该长度会截断后再发送给模型;长上下文模型可适当调大',
|
||||
promptHeader: 'Prompt',
|
||||
settingPromptLanguageName: '输出语言',
|
||||
settingPromptLanguageDesc: '控制 title/gist/bullets 的语言;anchor 始终逐字复制原文',
|
||||
settingCardRangeName: '卡片数量范围',
|
||||
settingCardRangeDesc: '模型会在这个范围内自适应切段',
|
||||
settingCustomPromptName: '自定义 system prompt',
|
||||
settingCustomPromptDesc: '可选。支持变量:{minCards}、{maxCards}、{languageInstruction}、{schema}、{example}',
|
||||
settingCustomPromptPlaceholder: '留空使用内置 prompt',
|
||||
settingTestBackendName: '测试当前后端',
|
||||
settingTestBackendDescApi: '会发起一次最小 LLM 请求验证 API 设置',
|
||||
settingTestBackendDescCli: '调用 `<cli> --version` 验证 spawn 能找到二进制',
|
||||
backendTestFailed: '✗ 后端测试失败:{error}',
|
||||
settingExportFolderName: '导出文件夹',
|
||||
settingExportFolderDesc: '对照笔记生成位置(相对 Vault 根)',
|
||||
cacheHeader: '缓存',
|
||||
settingMaxCacheName: '最大缓存篇数',
|
||||
settingMaxCacheDesc: '超过上限后按最近访问时间淘汰最旧的笔记缓存;缓存保存在插件目录的 cache.json',
|
||||
cachePruned: '已淘汰 {count} 条旧缓存',
|
||||
cachedNotesName: '已缓存笔记:{count} 篇',
|
||||
cachedNotesDesc: '缓存以源笔记 SHA1 + 生成配置指纹作为失效键,源笔记或模型配置修改后会显示 stale 提示',
|
||||
clearAllCacheButton: '清除所有缓存',
|
||||
settingStreamingName: '流式输出',
|
||||
settingStreamingDesc: '启用后生成时实时显示 LLM 输出进度(仅 OpenAI Chat 和 Anthropic 格式支持)',
|
||||
settingStreamingTimeoutName: '流式超时 (ms)',
|
||||
settingStreamingTimeoutDesc: '单次流式请求的超时时间;低于 1000ms 或非法值会恢复默认值',
|
||||
cmdBatchGenerate: '批量生成对照笔记(当前文件夹)',
|
||||
batchSelectFolder: '请输入要批量处理的文件夹路径(留空为 Vault 根目录)',
|
||||
batchFolderPrompt: '文件夹路径',
|
||||
batchAlreadyRunning: '批量生成正在运行',
|
||||
batchCancelRequested: '已请求取消批量生成;当前文件结束后停止',
|
||||
batchInvalidFolderInput: '文件夹路径无效:{path}',
|
||||
batchFolderNotFound: '文件夹不存在:{path}',
|
||||
batchNoMarkdown: '该文件夹下没有 Markdown 文件',
|
||||
batchProgress: '批量生成:{current}/{total}',
|
||||
batchDone: '批量生成完成:共处理 {processed}/{total} 篇,跳过缓存 {skipped} 篇,失败 {errors} 篇',
|
||||
batchCancelled: '批量生成已取消:已处理 {processed}/{total} 篇,跳过缓存 {skipped} 篇,失败 {errors} 篇',
|
||||
},
|
||||
en: {
|
||||
appTitle: 'Parallel Reader',
|
||||
settingsTitle: 'Parallel Reader Settings',
|
||||
emptyOpenNote: 'Open a note, then run:',
|
||||
emptyNoCache: 'This note has no cached parallel notes. Run:',
|
||||
commandGenerate: 'Parallel Reader: Generate notes for current note',
|
||||
displayName: 'Parallel Reader',
|
||||
ribbonOpen: 'Open Parallel Reader pane',
|
||||
cmdRun: 'Generate parallel notes for current note (cache first)',
|
||||
cmdRegen: 'Regenerate parallel notes (bypass cache)',
|
||||
cmdOpenView: 'Open Parallel Reader pane',
|
||||
cmdExport: 'Export current parallel notes to Vault',
|
||||
cmdCopyMarkdown: 'Copy current parallel notes as Markdown',
|
||||
cmdCancel: 'Cancel current parallel-note generation',
|
||||
cmdClearCurrent: 'Clear cache for current note',
|
||||
cmdClearAll: 'Clear all caches',
|
||||
cmdCardPrev: 'Focus previous summary card',
|
||||
cmdCardNext: 'Focus next summary card',
|
||||
cmdCardJump: 'Jump current summary card to source',
|
||||
actionCancel: 'Cancel generation',
|
||||
actionRegenerate: 'Regenerate',
|
||||
actionCopyAll: 'Copy all Markdown',
|
||||
actionExport: 'Export to Vault',
|
||||
staleBanner: 'The source note or generation settings changed. This is stale cache.',
|
||||
loadingDefault: 'Generating parallel notes...',
|
||||
loadingGenerating: 'Parallel Reader: asking the LLM to read and segment the full note...',
|
||||
loadingSubtitle: 'You can keep reading. Cards will refresh when generation finishes.',
|
||||
errorTitle: 'Generation failed',
|
||||
actionCopyError: 'Copy error',
|
||||
emptyCard: '(not generated)',
|
||||
anchorMismatch: 'Anchor did not match; scroll sync is unavailable',
|
||||
menuCopyMarkdown: 'Copy Markdown',
|
||||
menuCopyPlain: 'Copy plain text',
|
||||
menuCopyAnchor: 'Copy anchor quote',
|
||||
menuJumpSource: 'Jump to source',
|
||||
menuEditCard: 'Edit this card',
|
||||
menuDeleteCard: 'Delete this card',
|
||||
copiedMarkdown: 'Copied Markdown',
|
||||
copiedPlain: 'Copied plain text',
|
||||
copiedAnchor: 'Copied anchor quote',
|
||||
copiedAllMarkdown: 'Copied all Markdown',
|
||||
copyFailed: 'Copy failed: {error}',
|
||||
actionFailed: '{label} failed: {error}',
|
||||
exported: 'Exported → {path}',
|
||||
noCurrentNote: 'No current note',
|
||||
cacheClearedFile: 'Cleared cache: {name}',
|
||||
cacheClearedAll: 'Cleared {count} cache entries',
|
||||
noCancelableJob: 'No cancellable generation job',
|
||||
cancelRequested: 'Cancel requested',
|
||||
cancelRequestedApiInFlight:
|
||||
'Cancel requested. The in-flight API request cannot be aborted immediately; its result will be ignored.',
|
||||
actionGenerate: 'Generate parallel notes',
|
||||
fileMenuGenerate: 'Generate parallel notes',
|
||||
fileMenuRegen: 'Regenerate parallel notes',
|
||||
fileMenuClear: 'Clear parallel-note cache',
|
||||
noExportContent: 'No parallel notes to export',
|
||||
noCopyContent: 'No parallel notes to copy',
|
||||
noActiveCard: 'No active summary card to jump',
|
||||
viewOpenFailed: 'Could not open the Parallel Reader pane',
|
||||
confirmRegenerateEditedCards:
|
||||
'These parallel-reader cards were edited manually. Regenerating will overwrite those edits. Continue?',
|
||||
regenerateCancelled: 'Regeneration cancelled',
|
||||
cardDeleted: 'Deleted this card',
|
||||
cardSaved: 'Saved this card',
|
||||
editCardTitle: 'Edit summary card',
|
||||
editCardTitleField: 'Title',
|
||||
editCardGistField: 'Gist',
|
||||
editCardBulletsField: 'Bullets (one per line)',
|
||||
editCardCancel: 'Cancel',
|
||||
editCardSave: 'Save',
|
||||
openNoteFirst: 'Open a note first',
|
||||
alreadyGenerating: 'This note is already being generated',
|
||||
emptyNote: 'The note is empty',
|
||||
longNoteTruncated: 'Long note: only the first {count} characters will be sent to the model',
|
||||
generatingNotice: 'Parallel Reader: asking the LLM to read and segment the full note...',
|
||||
noCardsReturned: 'LLM returned no cards',
|
||||
generationDone: 'Generated {count} sections{suffix}',
|
||||
unanchoredSuffix: ' (⚠ {count} anchors unmatched)',
|
||||
cancelled: 'Generation cancelled',
|
||||
cancelledError: 'Generation cancelled',
|
||||
generationFailed: 'Generation failed{kind}: {error}',
|
||||
errorCustomProviderBaseUrlRequired: 'Custom provider requires an API Base URL.',
|
||||
errorApiBaseUrlMissing: 'API Base URL is not set. Choose a provider or enter a custom base URL in settings.',
|
||||
errorModelMissing: 'Model is not set. Enter a model ID in settings.',
|
||||
errorApiKeyMissing: 'API key is not set. Enter an API Key in settings{hint}.',
|
||||
errorApiKeyEnvHint: ' or set environment variable {envVar}',
|
||||
errorLlmNonJson: 'LLM returned non-JSON:\n{excerpt}',
|
||||
errorCustomHeadersJsonParse: 'Custom headers JSON parse failed: {error}',
|
||||
errorCustomHeadersJsonObject: 'Custom headers JSON must be an object',
|
||||
errorCustomHeadersLineFormat: 'Custom headers lines must use `Header-Name: value`',
|
||||
errorProviderNonJson: '{label} returned non-JSON:\n{excerpt}',
|
||||
errorProviderRequestFailed: '{label} request failed: {error}',
|
||||
errorProviderApiStatus: '{label} API returned HTTP {status}: {excerpt}',
|
||||
noEditor: 'Could not find the source note editor',
|
||||
settingUiLanguageName: 'UI language',
|
||||
settingUiLanguageDesc: 'Controls plugin UI, commands, and notices. Auto follows Obsidian/system language.',
|
||||
settingBackendName: 'Backend',
|
||||
settingBackendDesc:
|
||||
'Backend for generating bullets: CLI reuses local login; API supports OpenAI, Anthropic, Gemini, and compatible proxies.',
|
||||
settingCliPathName: 'CLI path (optional)',
|
||||
settingCliPathDesc:
|
||||
'Leave blank to auto-detect common paths. Obsidian launched from the GUI may not inherit shell PATH.',
|
||||
settingCliPathPlaceholder: 'Example: /Users/you/bin/codex',
|
||||
settingCliTimeoutName: 'CLI timeout (ms)',
|
||||
apiProviderHeader: 'API Provider',
|
||||
settingProviderPresetName: 'Provider preset',
|
||||
settingProviderPresetDesc:
|
||||
'OpenClaw-style provider/model setup: presets define protocol, base URL, and auth defaults.',
|
||||
settingApiFormatName: 'API format',
|
||||
settingApiFormatDesc: 'Wire protocol for the provider. OpenAI-compatible proxies usually use Chat Completions.',
|
||||
settingBaseUrlName: 'Base URL',
|
||||
settingBaseUrlDesc: 'Provider root URL, without /chat/completions. Blank uses the preset default.',
|
||||
settingApiKeyName: 'API Key',
|
||||
settingApiKeyDesc: 'Can be blank for local Ollama/LM Studio; most hosted providers require a key.',
|
||||
settingApiKeyEnvName: 'API key env var',
|
||||
settingApiKeyEnvDesc: 'Optional. Obsidian GUI may not inherit shell env; direct API Key is usually more reliable.',
|
||||
settingAuthTypeName: 'Auth type',
|
||||
settingAuthTypeDesc: 'Auto uses provider preset. Custom proxies can use Bearer, x-api-key, or none.',
|
||||
settingHeadersName: 'Extra headers',
|
||||
settingHeadersDesc: 'Optional. JSON object or one `Header: value` per line, useful for Cloudflare AI Gateway.',
|
||||
settingMaxTokensName: 'Max output tokens',
|
||||
settingModelName: 'Model',
|
||||
settingModelDescApi:
|
||||
'Model ID for API calls. Supports OpenClaw-style provider/model; matching provider prefixes are stripped.',
|
||||
settingModelDescCli: 'Passed as --model for Claude Code. Usually ignored by Codex, which uses its default config.',
|
||||
settingMaxInputName: 'Max input characters',
|
||||
settingMaxInputDesc: 'Longer notes are truncated before sending to the model. Raise this for long-context models.',
|
||||
promptHeader: 'Prompt',
|
||||
settingPromptLanguageName: 'Output language',
|
||||
settingPromptLanguageDesc: 'Controls title/gist/bullets language. Anchor is always copied verbatim from source.',
|
||||
settingCardRangeName: 'Card count range',
|
||||
settingCardRangeDesc: 'The model adapts segmentation within this range.',
|
||||
settingCustomPromptName: 'Custom system prompt',
|
||||
settingCustomPromptDesc: 'Optional. Variables: {minCards}, {maxCards}, {languageInstruction}, {schema}, {example}',
|
||||
settingCustomPromptPlaceholder: 'Leave blank to use built-in prompt',
|
||||
settingTestBackendName: 'Test current backend',
|
||||
settingTestBackendDescApi: 'Sends a minimal LLM request to validate API settings.',
|
||||
settingTestBackendDescCli: 'Runs `<cli> --version` to verify the binary can be spawned.',
|
||||
backendTestFailed: '✗ Backend test failed: {error}',
|
||||
settingExportFolderName: 'Export folder',
|
||||
settingExportFolderDesc: 'Parallel-note output location, relative to the Vault root.',
|
||||
cacheHeader: 'Cache',
|
||||
settingMaxCacheName: 'Max cached notes',
|
||||
settingMaxCacheDesc:
|
||||
'Prunes least-recently accessed note caches above this limit. Cache is stored in plugin cache.json.',
|
||||
cachePruned: 'Pruned {count} old cache entries',
|
||||
cachedNotesName: 'Cached notes: {count}',
|
||||
cachedNotesDesc: 'Cache is invalidated by source SHA1 and generation settings fingerprint.',
|
||||
clearAllCacheButton: 'Clear all caches',
|
||||
settingStreamingName: 'Streaming output',
|
||||
settingStreamingDesc:
|
||||
'Show real-time LLM output progress during generation (OpenAI Chat and Anthropic formats only)',
|
||||
settingStreamingTimeoutName: 'Streaming timeout (ms)',
|
||||
settingStreamingTimeoutDesc:
|
||||
'Timeout for one streaming request; invalid values or values below 1000ms use the default',
|
||||
cmdBatchGenerate: 'Batch generate parallel notes (current folder)',
|
||||
batchSelectFolder: 'Enter the folder path to batch-process (leave blank for Vault root)',
|
||||
batchFolderPrompt: 'Folder path',
|
||||
batchAlreadyRunning: 'Batch generation is already running',
|
||||
batchCancelRequested: 'Batch cancellation requested; it will stop after the current note',
|
||||
batchInvalidFolderInput: 'Invalid folder path: {path}',
|
||||
batchFolderNotFound: 'Folder not found: {path}',
|
||||
batchNoMarkdown: 'No Markdown files found in that folder',
|
||||
batchProgress: 'Batch generating: {current}/{total}',
|
||||
batchDone: 'Batch complete: processed {processed}/{total}, skipped {skipped} (cached), errors {errors}',
|
||||
batchCancelled: 'Batch cancelled: processed {processed}/{total}, skipped {skipped} (cached), errors {errors}',
|
||||
},
|
||||
};
|
||||
export { STRINGS } from './i18n-strings';
|
||||
|
||||
export function resolveUiLanguage(settings: Pick<PluginSettings, 'uiLanguage'> | null): string {
|
||||
const configured = settings?.uiLanguage;
|
||||
|
|
|
|||
Loading…
Reference in a new issue