Improve postponed conflict resolution handling

Keep unresolved conflicts visible after Not now, reopen them only on explicit requests, and clear stale warning and dialogue state when a replicated resolution arrives. Add typed provisional English messages, revision-tree regressions, specifications, and a focused Real Obsidian E2E.
This commit is contained in:
vorotamoroz
2026-07-23 02:00:24 +00:00
parent 8d82a3a00c
commit d784799969
17 changed files with 728 additions and 44 deletions
@@ -0,0 +1,13 @@
/**
* Canonical English for LiveSync-owned messages whose wording is still being
* exercised. These keys remain application-owned and must not be added to
* Commonlib merely to make them available to the LiveSync translator.
*
* Move a message to the YAML catalogue when it is ready for translation, and
* remove it from this map in the same change.
*/
export const liveSyncProvisionalEnglishMessages = {
"This file has unresolved conflicts.": "This file has unresolved conflicts.",
} as const;
export type LiveSyncProvisionalMessageKey = keyof typeof liveSyncProvisionalEnglishMessages;
+7 -1
View File
@@ -65,7 +65,9 @@ export type I18N_LANGS =
export type MESSAGE = { [key in I18N_LANGS]?: string };
import { Logger } from "octagonal-wheels/common/logger";
import type { CommonlibMessageKey } from "@vrtmrz/livesync-commonlib/context";
import type { MessageKeys } from "./messages/combinedMessages.dev.ts";
import type { LiveSyncProvisionalMessageKey } from "./messages/LiveSyncProvisionalMessages.ts";
export function expandKeywords<T extends Record<string, U>, U extends Record<string, string>>(
message: T,
@@ -114,4 +116,8 @@ export function expandKeywords<T extends Record<string, U>, U extends Record<str
return ret as T;
}
export type AllMessageKeys = MessageKeys;
/** Keys translated by LiveSync itself, including English-only provisional messages. */
export type LiveSyncCatalogueMessageKey = MessageKeys | LiveSyncProvisionalMessageKey;
/** Keys accepted by the composed LiveSync and Commonlib translation boundary. */
export type AllMessageKeys = LiveSyncCatalogueMessageKey | CommonlibMessageKey;
+26 -8
View File
@@ -1,10 +1,19 @@
// Avoid using Obsidian's native function for CLIs.
import { getLanguage } from "@vrtmrz/livesync-commonlib/compat/common/coreEnvFunctions";
import { englishMessageTranslator, type MessageTranslator } from "@vrtmrz/livesync-commonlib/context";
import {
commonlibEnglishMessages,
englishMessageTranslator,
type CommonlibMessageKey,
type MessageTranslator,
} from "@vrtmrz/livesync-commonlib/context";
import { LOG_KIND_WARNING, notice } from "octagonal-wheels/common/logger";
import type { TaggedType } from "octagonal-wheels/common/types";
import type { AllMessageKeys, I18N_LANGS } from "./rosetta";
import type { AllMessageKeys, I18N_LANGS, LiveSyncCatalogueMessageKey } from "./rosetta";
import { allMessages } from "./messages/combinedMessages.prod.ts";
import {
liveSyncProvisionalEnglishMessages,
type LiveSyncProvisionalMessageKey,
} from "./messages/LiveSyncProvisionalMessages.ts";
const obsidianLangMap: Record<string, I18N_LANGS> = {
de: "de",
@@ -58,7 +67,10 @@ export function setLang(lang: I18N_LANGS) {
function _getMessage(key: string, lang: I18N_LANGS) {
if (key.trim() == "") return key;
const msgs = allMessages[key] ?? undefined;
const provisionalEnglish = liveSyncProvisionalEnglishMessages[key as LiveSyncProvisionalMessageKey];
const msgs =
allMessages[key] ?? (provisionalEnglish === undefined ? undefined : ({ def: provisionalEnglish } as const));
if (msgs === undefined && isCommonlibMessageKey(key)) return englishMessageTranslator(key);
const resolvedLang = resolveLanguage(lang);
let msg = msgs?.[resolvedLang];
@@ -87,12 +99,16 @@ export function $t(message: string, lang?: I18N_LANGS) {
}
export function translateIfAvailable(message: string, lang?: I18N_LANGS) {
if (message.trim() == "" || allMessages[message] === undefined) return message;
if (message.trim() == "" || (!isLiveSyncMessageKey(message) && !isCommonlibMessageKey(message))) return message;
return $t(message, lang);
}
function isLiveSyncMessageKey(key: string): key is AllMessageKeys {
return key in allMessages;
function isCommonlibMessageKey(key: string): key is CommonlibMessageKey {
return key in commonlibEnglishMessages;
}
function isLiveSyncMessageKey(key: string): key is LiveSyncCatalogueMessageKey {
return key in allMessages || key in liveSyncProvisionalEnglishMessages;
}
/**
@@ -124,7 +140,9 @@ export function $msg<T extends AllMessageKeys>(
}
/** Supplies the LiveSync-owned language catalogue through the Commonlib host boundary. */
export const translateLiveSyncMessage: MessageTranslator = (key, params) => {
if (!isLiveSyncMessageKey(key)) return englishMessageTranslator(key, params);
export type LiveSyncMessageTranslator = MessageTranslator<AllMessageKeys>;
export const translateLiveSyncMessage: LiveSyncMessageTranslator = (key, params) => {
if (!isLiveSyncMessageKey(key)) return englishMessageTranslator(key as CommonlibMessageKey, params);
return $msg(key, params === undefined ? undefined : { ...params });
};
+7
View File
@@ -25,4 +25,11 @@ describe("LiveSync-owned translation catalogue", () => {
expect(translateLiveSyncMessage("Active Remote Type")).toBe(englishMessageTranslator("Active Remote Type"));
});
it("uses LiveSync-owned provisional English without extending Commonlib's message contract", () => {
expect($msg("This file has unresolved conflicts.")).toBe("This file has unresolved conflicts.");
expect(translateLiveSyncMessage("This file has unresolved conflicts.")).toBe(
"This file has unresolved conflicts."
);
});
});