From f8ee3c866249d6e1c69ce7bc9bd26d7934bf7187 Mon Sep 17 00:00:00 2001 From: nimula Date: Fri, 7 Aug 2026 08:01:53 +0000 Subject: [PATCH] i18n: wire the display-language translator into the settings manifest Commonlib's `getConfig(key, translate?)` and `getConfName(key, translate?)` default `translate` to `englishMessageTranslator`, and this plug-in never passed the second argument. Every automatically wired setting therefore rendered its name and description in English, whatever `displayLanguage` was set to. Commonlib's own Config Doctor already threads a translator through `getConfName`, so this only restores the argument which was missing here. `src/modules/features/SettingDialogue/settingConstants.ts` now re-exports the names it supplies explicitly and adds thin `getConfig` and `getConfName` wrappers which default the translator to `translateLiveSyncMessage`. That reaches all three existing call sites, and therefore the 102 `setAuto` and `autoWire*` calls across the setting panes, the setup-wizard configuration summaries, and the externally-modified-setting prompt. Of the 225 distinct name and description strings in the two manifest tables, 160 are already catalogue keys with translations; the remaining 65 are not catalogue keys and pass through unchanged. `ModuleResolveMismatchedTweaks` used `confName()`, which accepts no translator, so it gains a local `localisedConfName()` instead. Swapping in `getConfName()` there would have silently dropped the `statusDisplay()` suffix, replaced the empty-string fallback for an unknown key with `${key} (No info)`, and introduced `SettingInformation` as a second source. English output is unchanged: every catalogue key which contains a space has a value identical to the key itself, so translating under the default language is idempotent. Verified with `npm run check`, `npm run test:unit`, and `npm run build`. Co-Authored-By: Claude Opus 5 (1M context) --- .../ModuleResolveMismatchedTweaks.ts | 19 ++++++-- ...ModuleResolveMismatchedTweaks.unit.spec.ts | 43 ++++++++++++++++++- .../SettingDialogue/settingConstants.ts | 38 +++++++++++++++- .../settingConstants.unit.spec.ts | 33 ++++++++++++++ 4 files changed, 127 insertions(+), 6 deletions(-) create mode 100644 src/modules/features/SettingDialogue/settingConstants.unit.spec.ts diff --git a/src/modules/coreFeatures/ModuleResolveMismatchedTweaks.ts b/src/modules/coreFeatures/ModuleResolveMismatchedTweaks.ts index b93fb3d9..41ad7bc2 100644 --- a/src/modules/coreFeatures/ModuleResolveMismatchedTweaks.ts +++ b/src/modules/coreFeatures/ModuleResolveMismatchedTweaks.ts @@ -4,7 +4,8 @@ import { TweakValuesShouldMatchedTemplate, TweakValuesTemplate, IncompatibleChanges, - confName, + configurationNames, + statusDisplay, type TweakValues, type ObsidianLiveSyncSettings, type RemoteDBSettings, @@ -15,11 +16,21 @@ import { } from "@vrtmrz/livesync-commonlib/compat/common/types"; import { escapeMarkdownValue } from "@vrtmrz/livesync-commonlib/compat/common/utils"; import { AbstractModule } from "@/modules/AbstractModule.ts"; -import { $msg } from "@/common/translation"; +import { $msg, translateIfAvailable } from "@/common/translation"; import type { InjectableServiceHub } from "@vrtmrz/livesync-commonlib/compat/services/implements/injectable/InjectableServiceHub"; import type { LiveSyncCore } from "@/main.ts"; import { REMOTE_P2P } from "@vrtmrz/livesync-commonlib/compat/common/models/setting.const"; +/** + * Localised counterpart of Commonlib's `confName()`, which takes no translator. + * Same shape: label plus status suffix, and an empty string for an unknown key. + */ +function localisedConfName(key: keyof ObsidianLiveSyncSettings): string { + const info = configurationNames[key]; + if (!info) return ""; + return `${translateIfAvailable(info.name)}${statusDisplay(info.status)}`; +} + function valueToString(value: string | number | boolean | object | undefined): string { if (typeof value === "boolean") { return value ? "true" : "false"; @@ -158,7 +169,7 @@ export class ModuleResolvingMismatchedTweaks extends AbstractModule { // table += `| ${confName(key)} | ${valueMine} | ${valuePreferred} | \n`; tableRows.push( $msg("TweakMismatchResolve.Table.Row", { - name: confName(key), + name: localisedConfName(key), self: valueToString(valueMine), remote: valueToString(valuePreferred), }) @@ -342,7 +353,7 @@ export class ModuleResolvingMismatchedTweaks extends AbstractModule { } tableRows.push( $msg("TweakMismatchResolve.Table.Row", { - name: confName(key), + name: localisedConfName(key), self: currentValueForDisplay, remote: remoteValueForDisplay, }) diff --git a/src/modules/coreFeatures/ModuleResolveMismatchedTweaks.unit.spec.ts b/src/modules/coreFeatures/ModuleResolveMismatchedTweaks.unit.spec.ts index 0f394d8b..e0fd1dc8 100644 --- a/src/modules/coreFeatures/ModuleResolveMismatchedTweaks.unit.spec.ts +++ b/src/modules/coreFeatures/ModuleResolveMismatchedTweaks.unit.spec.ts @@ -1,4 +1,4 @@ -import { describe, expect, it, vi } from "vitest"; +import { afterEach, describe, expect, it, vi } from "vitest"; import { DEFAULT_SETTINGS, REMOTE_COUCHDB, @@ -6,6 +6,7 @@ import { type TweakValues, } from "@vrtmrz/livesync-commonlib/compat/common/types"; import { ModuleResolvingMismatchedTweaks } from "./ModuleResolveMismatchedTweaks"; +import { setLang } from "@/common/translation"; function createModule(settingsOverride: Partial = {}) { const askSelectStringDialogue = vi.fn(async (..._args: unknown[]): Promise => undefined); @@ -255,3 +256,43 @@ describe("ModuleResolvingMismatchedTweaks", () => { expect(calls).toEqual(["save", "reinitialise", "set-preferred"]); }); }); + +describe("ModuleResolvingMismatchedTweaks setting labels", () => { + afterEach(() => setLang("def")); + + async function renderMismatchTable() { + const { module, askSelectStringDialogue } = createModule({ + autoAcceptCompatibleTweak: true, + hashAlg: "xxhash64", + encrypt: false, + tweakModified: 100, + }); + const preferred = { + ...(DEFAULT_SETTINGS as unknown as TweakValues), + hashAlg: "xxhash32", + encrypt: true, + tweakModified: 200, + } as Partial; + + await module._checkAndAskResolvingMismatchedTweaks(preferred); + + return String(askSelectStringDialogue.mock.calls[0]?.[0] ?? ""); + } + + it("localises the setting names and keeps the status suffix", async () => { + setLang("zh-tw"); + + const message = await renderMismatchTable(); + + expect(message).toContain("chunk ID 的雜湊演算法 (Experimental)"); + expect(message).toContain("端對端加密"); + expect(message).not.toContain("The Hash algorithm for chunk IDs"); + }); + + it("leaves English unchanged", async () => { + const message = await renderMismatchTable(); + + expect(message).toContain("The Hash algorithm for chunk IDs (Experimental)"); + expect(message).toContain("End-to-End Encryption"); + }); +}); diff --git a/src/modules/features/SettingDialogue/settingConstants.ts b/src/modules/features/SettingDialogue/settingConstants.ts index c24d9a27..495f8f76 100644 --- a/src/modules/features/SettingDialogue/settingConstants.ts +++ b/src/modules/features/SettingDialogue/settingConstants.ts @@ -1 +1,37 @@ -export * from "@vrtmrz/livesync-commonlib/compat/common/settingConstants"; +export { + AllSettingDefault, + OnDialogSettingsDefault, + SettingInformation, +} from "@vrtmrz/livesync-commonlib/compat/common/settingConstants"; +export type { + AllSettings, + AllSettingItemKey, + AllStringItemKey, + AllNumericItemKey, + AllBooleanItemKey, + OnDialogSettings, + ValueOf, +} from "@vrtmrz/livesync-commonlib/compat/common/settingConstants"; + +import { + getConfig as getCommonlibConfig, + getConfName as getCommonlibConfName, + type AllSettingItemKey, +} from "@vrtmrz/livesync-commonlib/compat/common/settingConstants"; +import type { MessageTranslator } from "@vrtmrz/livesync-commonlib/context"; +import { translateLiveSyncMessage } from "@/common/translation"; + +// Commonlib defaults `translate` to its English-only translator, so every caller which omits +// it silently renders English regardless of `displayLanguage`. Default it to the LiveSync +// catalogue instead, and re-export these wrappers under the original names so that no call +// site has to remember the second argument. + +/** `getConfig` with the LiveSync catalogue applied by default. */ +export function getConfig(key: AllSettingItemKey, translate: MessageTranslator = translateLiveSyncMessage) { + return getCommonlibConfig(key, translate); +} + +/** `getConfName` with the LiveSync catalogue applied by default. See `getConfig`. */ +export function getConfName(key: AllSettingItemKey, translate: MessageTranslator = translateLiveSyncMessage) { + return getCommonlibConfName(key, translate); +} diff --git a/src/modules/features/SettingDialogue/settingConstants.unit.spec.ts b/src/modules/features/SettingDialogue/settingConstants.unit.spec.ts new file mode 100644 index 00000000..ca194579 --- /dev/null +++ b/src/modules/features/SettingDialogue/settingConstants.unit.spec.ts @@ -0,0 +1,33 @@ +import { afterEach, describe, expect, it } from "vitest"; + +import { setLang } from "@/common/translation"; +import { getConfig, getConfName } from "./settingConstants"; + +describe("setting manifest labels", () => { + afterEach(() => setLang("def")); + + it("renders names and descriptions in the selected display language", () => { + setLang("zh-tw"); + + expect(getConfName("liveSync")).toBe("同步模式"); + expect(getConfig("couchDB_URI")).toMatchObject({ name: "伺服器 URI" }); + expect(getConfig("encrypt")).toMatchObject({ + name: "端對端加密", + desc: "加密遠端資料庫中的內容。如果你使用外掛的同步功能,建議啟用此選項。", + }); + }); + + it("leaves English untouched, so that the catalogue key and its English value stay interchangeable", () => { + expect(getConfName("liveSync")).toBe("Sync Mode"); + expect(getConfig("encrypt")).toMatchObject({ + name: "End-to-End Encryption", + desc: "Encrypt contents on the remote database. If you use the plugin's synchronization feature, enabling this is recommended.", + }); + }); + + it("passes through labels which Commonlib owns but the catalogue does not carry", () => { + setLang("zh-tw"); + + expect(getConfName("chunkSplitterVersion")).toBe("Chunk Splitter"); + }); +});