mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-11-15 04:30:12 +00:00
fix(i18n): overhaul Japanese translations, localise pending UI labels, and update documentation
- Overhaul Japanese translation catalogue and synchronize with current documentation - Localise pending UI labels across dialogues, panels, and settings - Add Japanese translation mapping for provisional LiveSync messages - Update and refine all Japanese documentation files - Strip weak ETag prefix in ObsHttpHandler for object storage compatibility
This commit is contained in:
1 parent
0fe7d82f46
commit
5ffa3b76ed
32 files changed
+6154
-1549
No files matched your search
@@ -2,6 +2,7 @@ import { ButtonComponent } from "@/deps.ts";
|
||||
import { App, FuzzySuggestModal, MarkdownRenderer, Modal, Plugin, Setting, Component } from "@/deps.ts";
|
||||
import { EVENT_PLUGIN_UNLOADED, eventHub } from "@/common/events.ts";
|
||||
import { compatGlobal, type CompatIntervalHandle } from "@vrtmrz/livesync-commonlib/compat/common/coreEnvFunctions";
|
||||
import { $msg } from "@/common/translation";
|
||||
|
||||
class AutoClosableModal extends Modal {
|
||||
_closeByUnload = () => {
|
||||
@@ -58,7 +59,7 @@ export class InputStringDialog extends AutoClosableModal {
|
||||
new Setting(formEl)
|
||||
.addButton((btn) =>
|
||||
btn
|
||||
.setButtonText("Ok")
|
||||
.setButtonText($msg("Ok"))
|
||||
.setCta()
|
||||
.onClick(() => {
|
||||
this.isManuallyClosed = true;
|
||||
@@ -67,7 +68,7 @@ export class InputStringDialog extends AutoClosableModal {
|
||||
)
|
||||
.addButton((btn) =>
|
||||
btn
|
||||
.setButtonText("Cancel")
|
||||
.setButtonText($msg("Cancel"))
|
||||
.setCta()
|
||||
.onClick(() => {
|
||||
this.close();
|
||||
@@ -199,7 +200,9 @@ export class MessageBox<T extends readonly string[]> extends AutoClosableModal {
|
||||
const buttonSetting = new Setting(contentEl);
|
||||
const labelWrapper = contentEl.createDiv();
|
||||
labelWrapper.addClass("sls-dialogue-note-wrapper");
|
||||
const labelEl = labelWrapper.createEl("label", { text: "To stop the countdown, tap anywhere on the dialogue" });
|
||||
const labelEl = labelWrapper.createEl("label", {
|
||||
text: $msg("To stop the countdown, tap anywhere on the dialogue"),
|
||||
});
|
||||
labelEl.addClass("sls-dialogue-note-countdown");
|
||||
if (!this.timeout || !this.timer) {
|
||||
labelWrapper.empty();
|
||||
|
||||
@@ -39,6 +39,14 @@ function normaliseRequestBody(body: unknown): string | ArrayBuffer | undefined {
|
||||
throw new TypeError(`Obsidian requestUrl does not support the request body type ${bodyType}`);
|
||||
}
|
||||
|
||||
function stripWeakEtag(value: string): string {
|
||||
const trimmed = value.trim();
|
||||
if (trimmed.startsWith("W/")) {
|
||||
return trimmed.slice(2).trim();
|
||||
}
|
||||
return trimmed;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is close to origin implementation of FetchHttpHandler
|
||||
* https://github.com/aws/aws-sdk-js-v3/blob/main/packages/fetch-http-handler/src/fetch-http-handler.ts
|
||||
@@ -86,7 +94,11 @@ export class ObsHttpHandler extends FetchHttpHandler {
|
||||
if (keyLower === "host" || keyLower === "content-length") {
|
||||
continue;
|
||||
}
|
||||
transformedHeaders[keyLower] = request.headers[key];
|
||||
let value = request.headers[key];
|
||||
if (keyLower === "if-match" && typeof value === "string") {
|
||||
value = stripWeakEtag(value);
|
||||
}
|
||||
transformedHeaders[keyLower] = value;
|
||||
}
|
||||
|
||||
let contentType: string | undefined = undefined;
|
||||
@@ -110,7 +122,12 @@ export class ObsHttpHandler extends FetchHttpHandler {
|
||||
const headers = rsp.headers;
|
||||
const headersLower: Record<string, string> = {};
|
||||
for (const key of Object.keys(headers)) {
|
||||
headersLower[key.toLowerCase()] = headers[key];
|
||||
let value = headers[key];
|
||||
const lowerKey = key.toLowerCase();
|
||||
if (lowerKey === "etag" && typeof value === "string") {
|
||||
value = stripWeakEtag(value);
|
||||
}
|
||||
headersLower[lowerKey] = value;
|
||||
}
|
||||
const stream = new ReadableStream<Uint8Array>({
|
||||
start(controller) {
|
||||
|
||||
@@ -147,4 +147,48 @@ describe("ObsHttpHandler response handling", () => {
|
||||
|
||||
await expect(new ObsHttpHandler().handle(requestWithBody(new ArrayBuffer(0)))).rejects.toBe(failure);
|
||||
});
|
||||
|
||||
it("strips weak ETag prefix from incoming etag response headers", async () => {
|
||||
requestUrlMock.mockResolvedValue({
|
||||
headers: { etag: 'W/"weak-revision-123"' },
|
||||
status: 200,
|
||||
arrayBuffer: new ArrayBuffer(0),
|
||||
});
|
||||
const request = new HttpRequest({
|
||||
protocol: "https:",
|
||||
hostname: "objects.example.com",
|
||||
method: "GET",
|
||||
path: "/bucket/milestone.json",
|
||||
headers: {},
|
||||
});
|
||||
|
||||
const result = await new ObsHttpHandler().handle(request);
|
||||
expect(result.response.headers.etag).toBe('"weak-revision-123"');
|
||||
});
|
||||
|
||||
it("strips weak ETag prefix from outgoing if-match request headers", async () => {
|
||||
requestUrlMock.mockResolvedValue({
|
||||
headers: {},
|
||||
status: 200,
|
||||
arrayBuffer: new ArrayBuffer(0),
|
||||
});
|
||||
const request = new HttpRequest({
|
||||
protocol: "https:",
|
||||
hostname: "objects.example.com",
|
||||
method: "PUT",
|
||||
path: "/bucket/milestone.json",
|
||||
headers: {
|
||||
"if-match": 'W/"weak-revision-123"',
|
||||
},
|
||||
});
|
||||
|
||||
await new ObsHttpHandler().handle(request);
|
||||
expect(requestUrlMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
headers: expect.objectContaining({
|
||||
"if-match": '"weak-revision-123"',
|
||||
}),
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -200,14 +200,14 @@
|
||||
</script>
|
||||
|
||||
<div class="globalhistory">
|
||||
<h1>Vault history</h1>
|
||||
<h1>{translateMessage("Vault history")}</h1>
|
||||
<div class="control">
|
||||
<div class="row">
|
||||
<label for="">From:</label><input type="date" bind:value={dispDateFrom} disabled={loading} />
|
||||
<label for="">{translateMessage("From:")}</label><input type="date" bind:value={dispDateFrom} disabled={loading} />
|
||||
</div>
|
||||
<div class="row"><label for="">To:</label><input type="date" bind:value={dispDateTo} disabled={loading} /></div>
|
||||
<div class="row"><label for="">{translateMessage("To:")}</label><input type="date" bind:value={dispDateTo} disabled={loading} /></div>
|
||||
<div class="row">
|
||||
<label for="">Info:</label>
|
||||
<label for="">{translateMessage("Info:")}</label>
|
||||
<label
|
||||
><input type="checkbox" bind:checked={showDiffInfo} disabled={loading} /><span
|
||||
>{translateMessage("Diff")}</span
|
||||
|
||||
@@ -58,7 +58,7 @@
|
||||
<span>{msg("logPane.pause", {}, lang)}</span>
|
||||
</label>
|
||||
<span class="spacer"></span>
|
||||
<button onclick={() => closeDialogue()}>Close</button>
|
||||
<button onclick={() => closeDialogue()}>{msg("Close", {}, lang)}</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="log" bind:this={scroll}>
|
||||
|
||||
@@ -39,19 +39,19 @@
|
||||
{/each}
|
||||
<li>
|
||||
<label>
|
||||
<button on:click={() => add()}>Add</button>
|
||||
<button on:click={() => add()}>{translateMessage("Add")}</button>
|
||||
</label>
|
||||
</li>
|
||||
<li class="buttons">
|
||||
<button
|
||||
on:click={() => apply(patterns)}
|
||||
disabled={statusName.some((e) => e === CHECK_NG) || modified.every((e) => e === "")}
|
||||
>Apply
|
||||
>{translateMessage("Apply")}
|
||||
</button>
|
||||
<button
|
||||
on:click={() => revert()}
|
||||
disabled={statusName.some((e) => e === CHECK_NG) || modified.every((e) => e === "")}
|
||||
>Revert
|
||||
>{translateMessage("Revert")}
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -585,7 +585,7 @@ describe("ObsidianLiveSyncSettingTab pending-setting initialisation", () => {
|
||||
);
|
||||
expect(loggerMocks.Logger).toHaveBeenCalledWith(
|
||||
expect.stringContaining(
|
||||
"Rebuild both databases from a complete authoritative Vault and Fetch on the other devices."
|
||||
"then Rebuild both databases from a complete authoritative Vault and Fetch on the other devices."
|
||||
),
|
||||
LOG_LEVEL_NOTICE
|
||||
);
|
||||
|
||||
@@ -98,7 +98,7 @@ export function panePatches(this: ObsidianLiveSyncSettingTab, paneEl: HTMLElemen
|
||||
el.setText(`Current adapter: ${useIndexedDBAdapter ? "IndexedDB" : "IDB"}`);
|
||||
if (!useIndexedDBAdapter) {
|
||||
setting.addButton((button) => {
|
||||
button.setButtonText("Switch to IndexedDB").onClick(async () => {
|
||||
button.setButtonText($msg("Switch to IndexedDB")).onClick(async () => {
|
||||
Logger("Migrating all data to IndexedDB...", LOG_LEVEL_NOTICE);
|
||||
await migrateAllToIndexedDB();
|
||||
Logger(
|
||||
@@ -109,7 +109,7 @@ export function panePatches(this: ObsidianLiveSyncSettingTab, paneEl: HTMLElemen
|
||||
});
|
||||
} else {
|
||||
setting.addButton((button) => {
|
||||
button.setButtonText("Switch to IDB").onClick(async () => {
|
||||
button.setButtonText($msg("Switch to IDB")).onClick(async () => {
|
||||
Logger("Migrating all data to IDB...", LOG_LEVEL_NOTICE);
|
||||
await migrateAllToIDB();
|
||||
Logger(
|
||||
|
||||
@@ -253,21 +253,21 @@ export function paneSyncSettings(
|
||||
new Setting(paneEl)
|
||||
.setName($msg("obsidianLiveSyncSettingTab.nameEnableHiddenFileSync"))
|
||||
.addButton((button) => {
|
||||
button.setButtonText("Merge").onClick(async () => {
|
||||
button.setButtonText($msg("Merge")).onClick(async () => {
|
||||
this.closeSetting();
|
||||
// this.resetEditingSettings();
|
||||
await this.services.setting.enableOptionalFeature("MERGE");
|
||||
});
|
||||
})
|
||||
.addButton((button) => {
|
||||
button.setButtonText("Fetch").onClick(async () => {
|
||||
button.setButtonText($msg("Fetch")).onClick(async () => {
|
||||
this.closeSetting();
|
||||
// this.resetEditingSettings();
|
||||
await this.services.setting.enableOptionalFeature("FETCH");
|
||||
});
|
||||
})
|
||||
.addButton((button) => {
|
||||
button.setButtonText("Overwrite").onClick(async () => {
|
||||
button.setButtonText($msg("Overwrite")).onClick(async () => {
|
||||
this.closeSetting();
|
||||
// this.resetEditingSettings();
|
||||
await this.services.setting.enableOptionalFeature("OVERWRITE");
|
||||
|
||||
@@ -13,7 +13,8 @@ import {
|
||||
type AllBooleanItemKey as CommonlibAllBooleanItemKey,
|
||||
} from "@vrtmrz/livesync-commonlib/compat/common/settingConstants";
|
||||
import type { ConfigurationItem } from "@vrtmrz/livesync-commonlib/compat/common/types";
|
||||
import type { MessageTranslator } from "@vrtmrz/livesync-commonlib/context";
|
||||
import type { AllMessageKeys } from "@/common/rosetta";
|
||||
import type { LiveSyncMessageTranslator } from "@/common/translation";
|
||||
import { translateLiveSyncMessage } from "@/common/translation";
|
||||
import {
|
||||
DEFAULT_SYNC_GUARD_SETTINGS,
|
||||
@@ -52,13 +53,19 @@ export const SettingInformation: Partial<Record<AllSettingItemKey, Configuration
|
||||
// site has to remember the second argument.
|
||||
|
||||
/** `getConfig` with the LiveSync catalogue applied by default. */
|
||||
export function getConfig(key: AllSettingItemKey, translate: MessageTranslator = translateLiveSyncMessage) {
|
||||
if (isSyncGuardSettingKey(key)) return syncGuardSettingInformation[key];
|
||||
export function getConfig(key: AllSettingItemKey, translate: LiveSyncMessageTranslator = translateLiveSyncMessage) {
|
||||
if (isSyncGuardSettingKey(key)) {
|
||||
const info = syncGuardSettingInformation[key];
|
||||
return {
|
||||
name: translate(info.name as AllMessageKeys),
|
||||
desc: info.desc ? translate(info.desc as AllMessageKeys) : undefined,
|
||||
};
|
||||
}
|
||||
return getCommonlibConfig(key, translate);
|
||||
}
|
||||
|
||||
/** `getConfName` with the LiveSync catalogue applied by default. See `getConfig`. */
|
||||
export function getConfName(key: AllSettingItemKey, translate: MessageTranslator = translateLiveSyncMessage) {
|
||||
if (isSyncGuardSettingKey(key)) return syncGuardSettingInformation[key].name;
|
||||
export function getConfName(key: AllSettingItemKey, translate: LiveSyncMessageTranslator = translateLiveSyncMessage) {
|
||||
if (isSyncGuardSettingKey(key)) return translate(syncGuardSettingInformation[key].name as AllMessageKeys);
|
||||
return getCommonlibConfName(key, translate);
|
||||
}
|
||||
@@ -25,6 +25,13 @@ describe("setting manifest labels", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("renders LiveSync provisional settings in Japanese", () => {
|
||||
setLang("ja");
|
||||
|
||||
expect(getConfName("batchSaveInLiveSync")).toBe("LiveSync モードでデータベース更新をバッチ処理");
|
||||
expect(getConfName("syncGuardEnabled")).toBe("SyncGuard を有効化");
|
||||
});
|
||||
|
||||
it("passes through labels which Commonlib owns but the catalogue does not carry", () => {
|
||||
setLang("zh-tw");
|
||||
|
||||
|
||||
Reference in new issue
Block a user