Merge reviewed translations and synchronisation fixes for Spanish catalogue validation

This commit is contained in:
vorotamoroz
2026-10-01 00:54:55 +00:00
13 changed files with 3355 additions and 401 deletions
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,57 @@
import { afterEach, describe, expect, it } from "vitest";
import { allMessages } from "./messages/combinedMessages.prod";
import { $msg, setLang } from "./translation";
function interpolationNames(message: string): string[] {
return [...message.matchAll(/\$\{([^}]+)\}/g)].map((match) => match[1]).sort();
}
describe("Russian catalogue interpolation", () => {
afterEach(() => setLang("def"));
it("retains the English interpolation values in each translated message", () => {
const mismatches = Object.entries(allMessages).flatMap(([key, messages]) => {
if (!messages.def || !messages.ru) return [];
const expected = interpolationNames(messages.def);
const actual = interpolationNames(messages.ru);
return JSON.stringify(actual) === JSON.stringify(expected) ? [] : [{ key, expected, actual }];
});
expect(mismatches).toEqual([]);
});
it("renders the generated QR image inside its display container", () => {
setLang("ru");
const qrImage = '<img src="data:image/png;base64,cXI=" alt="Fixture QR code">';
const message = $msg("Setup.QRCode", { qr_image: qrImage });
expect(message).toContain(`<div class="sls-qr">${qrImage}</div>`);
expect(message).not.toContain("${qr_image}");
});
it("shows both generated keys in their individual and combined copy areas", () => {
setLang("ru");
const publicKey = "PUBLIC-KEY-FIXTURE";
const privateKey = "PRIVATE-KEY-FIXTURE";
const message = $msg("Setting.GenerateKeyPair.Desc", { public_key: publicKey, private_key: privateKey });
expect(message.split(publicKey)).toHaveLength(3);
expect(message.split(privateKey)).toHaveLength(3);
expect(message).toContain(`${publicKey}\n${privateKey}`);
expect(message).not.toMatch(/\$\{(?:public_key|private_key)\}/);
});
it("keeps the measured and configured database sizes in the comparison table", () => {
setLang("ru");
const message = $msg("moduleCheckRemoteSize.msgDatabaseGrowing", {
estimatedSize: "123 MB",
maxSize: "456 MB",
});
expect(message).toContain("| 123 MB | 456 MB |");
expect(message).not.toMatch(/\$\{(?:estimatedSize|maxSize)\}/);
});
});
@@ -52,7 +52,7 @@ vi.mock("@/common/obsidianCommunityPlugins.ts", () => ({
}));
import { cancelTask } from "@/common/utils.ts";
import { ConfigSync } from "./CmdConfigSync";
import { ConfigSync, PluginDataExDisplayV2, type IPluginDataExDisplay } from "./CmdConfigSync";
describe("ConfigSync commands", () => {
it("shows the Customisation Sync command only whilst the feature is enabled", () => {
@@ -111,3 +111,25 @@ describe("ConfigSync commands", () => {
expect(periodicPluginSweepProcessor.disable).toHaveBeenCalledOnce();
});
});
describe("PluginDataExDisplayV2", () => {
const entryWithModifiedTimes = (mtimes: number[]) =>
new PluginDataExDisplayV2({
documentPath: "ix:device/plugin_main/example",
category: "PLUGIN_MAIN",
name: "example",
term: "device",
files: mtimes.map((mtime, index) => ({ filename: `file-${index}`, mtime, data: [] })),
} as unknown as IPluginDataExDisplay);
it("averages millisecond modification times without truncating them to 32 bits", () => {
const mtime = Date.UTC(2026, 8, 15);
expect(entryWithModifiedTimes([mtime, mtime, mtime]).mtime).toBe(mtime);
});
it("keeps a newer copy newer than an older one", () => {
const newer = entryWithModifiedTimes([Date.UTC(2026, 8, 15)]);
const older = entryWithModifiedTimes([Date.UTC(2026, 8, 2)]);
expect(newer.mtime).toBeGreaterThan(older.mtime);
});
});
+2 -1
View File
@@ -368,7 +368,8 @@ export class PluginDataExDisplayV2 {
return this._version;
}
get mtime(): number {
return ~~this.files.reduce((a, b) => a + b.mtime, 0) / this.files.length;
// Millisecond timestamps exceed 32 bits, so `~~` would wrap them.
return Math.floor(this.files.reduce((a, b) => a + b.mtime, 0) / this.files.length);
}
}
export type PluginDataEx = {
+14 -17
View File
@@ -12,6 +12,7 @@
// import { askString } from "../../common/utils";
import { Menu } from "@/deps.ts";
import { $msg as translateMessage } from "@/common/translation";
import { selectSourceTerms } from "./PluginTerms.ts";
export let list: IPluginDataExDisplay[] = [];
export let thisTerm = "";
@@ -182,24 +183,20 @@
}
}
async function updateTerms(list: IPluginDataExDisplay[], selectNewest: boolean, isMaintenanceMode: boolean) {
async function updateTerms(
list: IPluginDataExDisplay[],
selectNewest: boolean,
isMaintenanceMode: boolean,
hideNotApplicable: boolean
) {
const local = list.find((e) => e.term == thisTerm);
// selected = "";
if (isMaintenanceMode) {
terms = [...new Set(list.map((e) => e.term))];
} else if (hideNotApplicable) {
const termsTmp = [];
const wk = [...new Set(list.map((e) => e.term))];
for (const termName of wk) {
const remote = list.find((e) => e.term == termName);
if ((await comparePlugin(local, remote)).canApply) {
termsTmp.push(termName);
}
}
terms = [...termsTmp];
} else {
terms = [...new Set(list.map((e) => e.term))].filter((e) => e != thisTerm);
}
terms = await selectSourceTerms(
list,
thisTerm,
{ isMaintenanceMode, hideNotApplicable },
async (local, remote) => (await comparePlugin(local, remote)).canApply
);
let newest: IPluginDataExDisplay | undefined = local;
if (selectNewest) {
for (const term of terms) {
@@ -230,7 +227,7 @@
}
// currentSelectNewest = selectNewest;
}
updateTerms(list, doSelectNewest, isMaintenanceMode);
updateTerms(list, doSelectNewest, isMaintenanceMode, hideNotApplicable);
currentSelectNewest = selectNewest;
}
$: {
+2 -1
View File
@@ -32,7 +32,8 @@
export let core :LiveSyncBaseCore;
// $: core = plugin.core;
$: hideNotApplicable = false;
// Comparing every copy is only worth it while the user asks to hide the unchanged ones.
$: hideNotApplicable = hideEven;
$: thisTerm = core.services.setting.getDeviceAndVaultName();
const addOn = core.getAddOn<ConfigSync>(ConfigSync.name)!;
+32
View File
@@ -0,0 +1,32 @@
import type { IPluginDataExDisplay } from "./CmdConfigSync.ts";
export type CanApplyFrom = (
local: IPluginDataExDisplay | undefined,
remote: IPluginDataExDisplay | undefined
) => Promise<boolean>;
/**
* Devices offered as sources for one item in the Customisation Sync dialogue.
*
* Maintenance mode lists every device, including this one. Otherwise every other
* device that has the item is listed; with `hideNotApplicable`, only devices whose
* copy can actually be applied remain, so an item that is the same everywhere ends
* up with no source and is shown as "All the same or non-existent".
*/
export async function selectSourceTerms(
list: IPluginDataExDisplay[],
thisTerm: string,
options: { isMaintenanceMode: boolean; hideNotApplicable: boolean },
canApplyFrom: CanApplyFrom
): Promise<string[]> {
const terms = [...new Set(list.map((e) => e.term))];
if (options.isMaintenanceMode) return terms;
if (!options.hideNotApplicable) return terms.filter((term) => term != thisTerm);
const local = list.find((e) => e.term == thisTerm);
const applicable: string[] = [];
for (const term of terms) {
const remote = list.find((e) => e.term == term);
if (await canApplyFrom(local, remote)) applicable.push(term);
}
return applicable;
}
@@ -0,0 +1,54 @@
import { describe, expect, it, vi } from "vitest";
import type { IPluginDataExDisplay } from "./CmdConfigSync.ts";
import { selectSourceTerms } from "./PluginTerms.ts";
const copyOn = (term: string) => ({ term, files: [] }) as unknown as IPluginDataExDisplay;
const list = [copyOn("desktop"), copyOn("phone"), copyOn("tablet")];
const differsOn =
(...terms: string[]) =>
(_local: IPluginDataExDisplay | undefined, remote: IPluginDataExDisplay | undefined) =>
Promise.resolve(terms.includes(remote?.term ?? ""));
describe("selectSourceTerms", () => {
it("offers every other device without comparing copies by default", async () => {
const canApplyFrom = vi.fn(differsOn());
const terms = await selectSourceTerms(
list,
"desktop",
{ isMaintenanceMode: false, hideNotApplicable: false },
canApplyFrom
);
expect(terms).toEqual(["phone", "tablet"]);
expect(canApplyFrom).not.toHaveBeenCalled();
});
it("offers every device, including this one, in maintenance mode", async () => {
const terms = await selectSourceTerms(
list,
"desktop",
{ isMaintenanceMode: true, hideNotApplicable: true },
differsOn()
);
expect(terms).toEqual(["desktop", "phone", "tablet"]);
});
it("leaves out devices whose copy is the same when hiding items that are not applicable", async () => {
const terms = await selectSourceTerms(
list,
"desktop",
{ isMaintenanceMode: false, hideNotApplicable: true },
differsOn("tablet")
);
expect(terms).toEqual(["tablet"]);
});
it("offers no source for an item that is the same on every device", async () => {
const terms = await selectSourceTerms(
list,
"desktop",
{ isMaintenanceMode: false, hideNotApplicable: true },
differsOn()
);
expect(terms).toEqual([]);
});
});