mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-10-09 02:42:30 +00:00
Merge reviewed translations and synchronisation fixes for Spanish catalogue validation
This commit is contained in:
File diff suppressed because it is too large
Load Diff
+736
-74
File diff suppressed because it is too large
Load Diff
+1460
-225
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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
$: {
|
||||
|
||||
@@ -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)!;
|
||||
|
||||
@@ -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([]);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user