refactor(i18n): route remaining Obsidian UI text through the message catalogue

Continues the source-key migration inside the application boundary introduced
in 1.0.0, where LiveSync owns its catalogue and consumes Commonlib as a
published package.

- Replace hardcoded user-visible strings in the Obsidian UI (Setup Wizard
  dialogues, P2P panes, Customisation Sync panes, Global History, the JSON
  conflict pane and the remote-configuration menu) with `$msg` calls, keeping
  the English source string as the key.
- Wire up strings whose translations already existed in the catalogue but were
  still rendered as literals, for example the whole Intro dialogue.
- Add the new entries to `src/common/messagesYAML/en.yaml` and `es.yaml`, then
  regenerate `messagesJson/` and `combinedMessages.prod.ts` through the
  documented `i18n:bake` pipeline.
- No Commonlib gitlink or catalogue is involved; every change is
  LiveSync-owned.

Regenerating the catalogue also normalises three pre-existing entries each in
`ko.json` and `zh.json`, where the committed JSON kept a trailing space before
a newline that YAML cannot represent.

Verified with tsc-check, tsc-check:apps, svelte-check and lint.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Zeedif
2026-07-30 18:59:19 -06:00
co-authored by Claude Opus 5
parent c385bd7ce7
commit b6746be73e
37 changed files with 3624 additions and 499 deletions
@@ -10,6 +10,7 @@
import InfoNote from "@/modules/services/LiveSyncUI/components/InfoNote.svelte";
import ExtraItems from "@/modules/services/LiveSyncUI/components/ExtraItems.svelte";
import Check from "@/modules/services/LiveSyncUI/components/Check.svelte";
import { $msg as translateMessage } from "@/common/translation";
import {
TYPE_BACKUP_DONE,
TYPE_BACKUP_SKIPPED,
@@ -48,90 +49,110 @@
}
</script>
<DialogHeader title="Reset Synchronisation on This Device" />
<DialogHeader title={translateMessage("Reset Synchronisation on This Device")} />
<Guidance
>This will rebuild the local database on this device using the most recent data from the server. This action is
designed to resolve synchronisation inconsistencies and restore correct functionality.</Guidance
>{translateMessage(
"This will rebuild the local database on this device using the most recent data from the server. This action is designed to resolve synchronisation inconsistencies and restore correct functionality."
)}</Guidance
>
<Guidance important title="⚠️ Important Notice">
<Guidance important title={translateMessage("⚠️ Important Notice")}>
<strong
>If you have unsynchronised changes in your Vault on this device, they will likely diverge from the server's
versions after the reset. This may result in a large number of file conflicts.</strong
>{translateMessage(
"If you have unsynchronised changes in your Vault on this device, they will likely diverge from the server's versions after the reset. This may result in a large number of file conflicts."
)}</strong
><br />
Furthermore, if conflicts are already present in the server data, they will be synchronised to this device as they are,
and you will need to resolve them locally.
{translateMessage(
"Furthermore, if conflicts are already present in the server data, they will be synchronised to this device as they are, and you will need to resolve them locally."
)}
</Guidance>
<hr />
<Instruction>
<Question
><strong>To minimise the creation of new conflicts</strong>, please select the option that best describes the
current state of your Vault. The application will then check your files in the most appropriate way based on
your selection.</Question
><strong>{translateMessage("To minimise the creation of new conflicts")}</strong>{translateMessage(
", please select the option that best describes the current state of your Vault. The application will then check your files in the most appropriate way based on your selection."
)}</Question
>
<Options>
<Option
selectedValue={TYPE_IDENTICAL}
title="The files in this Vault are almost identical to the server's."
title={translateMessage("The files in this Vault are almost identical to the server's.")}
bind:value={vaultType}
>
(e.g., immediately after restoring on another computer, or having recovered from a backup)
{translateMessage(
"(e.g., immediately after restoring on another computer, or having recovered from a backup)"
)}
</Option>
<Option
selectedValue={TYPE_INDEPENDENT}
title="This Vault is empty, or contains only new files that are not on the server."
title={translateMessage("This Vault is empty, or contains only new files that are not on the server.")}
bind:value={vaultType}
>
(e.g., setting up for the first time on a new smartphone, starting from a clean slate)
{translateMessage("(e.g., setting up for the first time on a new smartphone, starting from a clean slate)")}
</Option>
<Option
selectedValue={TYPE_UNBALANCED}
title="There may be differences between the files in this Vault and the server."
title={translateMessage("There may be differences between the files in this Vault and the server.")}
bind:value={vaultType}
>
(e.g., after editing many files whilst offline)
{translateMessage("(e.g., after editing many files whilst offline)")}
<InfoNote info>
In this scenario, Self-hosted LiveSync will recreate metadata for every file and deliberately generate
conflicts. Where the file content is identical, these conflicts will be resolved automatically.
{translateMessage(
"In this scenario, Self-hosted LiveSync will recreate metadata for every file and deliberately generate conflicts. Where the file content is identical, these conflicts will be resolved automatically."
)}
</InfoNote>
</Option>
</Options>
</Instruction>
<hr />
<Instruction>
<Question>Have you created a backup before proceeding?</Question>
<Question>{translateMessage("Have you created a backup before proceeding?")}</Question>
<InfoNote>
We recommend that you copy your Vault folder to a safe location. This will provide a safeguard in case a large
number of conflicts arise, or if you accidentally synchronise with an incorrect destination.
{translateMessage(
"We recommend that you copy your Vault folder to a safe location. This will provide a safeguard in case a large number of conflicts arise, or if you accidentally synchronise with an incorrect destination."
)}
</InfoNote>
<Options>
<Option selectedValue={TYPE_BACKUP_DONE} title="I have created a backup of my Vault." bind:value={backupType} />
<Option
selectedValue={TYPE_BACKUP_DONE}
title={translateMessage("I have created a backup of my Vault.")}
bind:value={backupType}
/>
<Option
selectedValue={TYPE_BACKUP_SKIPPED}
title="I understand the risks and will proceed without a backup."
title={translateMessage("I understand the risks and will proceed without a backup.")}
bind:value={backupType}
/>
<Option
selectedValue={TYPE_UNABLE_TO_BACKUP}
title="I am unable to create a backup of my Vault."
title={translateMessage("I am unable to create a backup of my Vault.")}
bind:value={backupType}
>
<InfoNote error visible={backupType === TYPE_UNABLE_TO_BACKUP}>
<strong
>It is strongly advised to create a backup before proceeding. Continuing without a backup may lead
to data loss.
>{translateMessage(
"It is strongly advised to create a backup before proceeding. Continuing without a backup may lead to data loss."
)}
</strong>
<br />
If you understand the risks and still wish to proceed, select so.
{translateMessage("If you understand the risks and still wish to proceed, select so.")}
</InfoNote>
</Option>
</Options>
</Instruction>
<Instruction>
<ExtraItems title="Advanced">
<Check title="Prevent fetching configuration from server" bind:value={preventFetchingConfig} />
<ExtraItems title={translateMessage("Advanced")}>
<Check
title={translateMessage("Prevent fetching configuration from server")}
bind:value={preventFetchingConfig}
/>
</ExtraItems>
</Instruction>
<UserDecisions>
<Decision title="Reset and Resume Synchronisation" important disabled={!canProceed} commit={() => commit()} />
<Decision title="Cancel" commit={() => setResult(TYPE_CANCEL)} />
<Decision
title={translateMessage("Reset and Resume Synchronisation")}
important
disabled={!canProceed}
commit={() => commit()}
/>
<Decision title={translateMessage("Cancel")} commit={() => setResult(TYPE_CANCEL)} />
</UserDecisions>
@@ -18,11 +18,11 @@
let userType = $state<IntroResultType>(TYPE_CANCELLED);
let proceedTitle = $derived.by(() => {
if (userType === TYPE_NEW_USER) {
return "Yes, I want to set up a new synchronisation";
return translateMessage("Yes, I want to set up a new synchronisation");
} else if (userType === TYPE_EXISTING_USER) {
return "Yes, I want to add this device to my existing synchronisation";
return translateMessage("Yes, I want to add this device to my existing synchronisation");
} else {
return "Please select an option to proceed";
return translateMessage("Please select an option to proceed");
}
});
const canProceed = $derived.by(() => {
@@ -30,31 +30,41 @@
});
</script>
<DialogHeader title="Welcome to Self-hosted LiveSync" />
<Guidance>We will now guide you through a few questions to simplify the synchronisation setup.</Guidance>
<DialogHeader title={translateMessage("Welcome to Self-hosted LiveSync")} />
<Guidance
>{translateMessage(
"We will now guide you through a few questions to simplify the synchronisation setup."
)}</Guidance
>
<InfoNote>
{translateMessage(
"This first setup has several short steps because it confirms encryption, the connection method, and which device provides the initial data. Once it is complete, additional devices can reuse a Setup URI."
)}
</InfoNote>
<Instruction>
<Question>First, please select the option that best describes your current situation.</Question>
<Question>{translateMessage("First, please select the option that best describes your current situation.")}</Question>
<Options>
<Option selectedValue={TYPE_NEW_USER} title="I am setting this up for the first time" bind:value={userType}>
(Select this if you are configuring this device as the first synchronisation device.) This option is
suitable if you are new to LiveSync and want to set it up from scratch.
<Option
selectedValue={TYPE_NEW_USER}
title={translateMessage("I am setting this up for the first time")}
bind:value={userType}
>
{translateMessage(
"(Select this if you are configuring this device as the first synchronisation device.) This option is suitable if you are new to LiveSync and want to set it up from scratch."
)}
</Option>
<Option
selectedValue={TYPE_EXISTING_USER}
title="I am adding a device to an existing synchronisation setup"
title={translateMessage("I am adding a device to an existing synchronisation setup")}
bind:value={userType}
>
(Select this if you are already using synchronisation on another computer or smartphone.) This option is
suitable if you are new to LiveSync and want to set it up from scratch.
{translateMessage(
"(Select this if you are already using synchronisation on another computer or smartphone.) This option is suitable if you are new to LiveSync and want to set it up from scratch."
)}
</Option>
</Options>
</Instruction>
<UserDecisions>
<Decision title={proceedTitle} important={canProceed} disabled={!canProceed} commit={() => setResult(userType)} />
<Decision title="No, please take me back" commit={() => setResult(TYPE_CANCELLED)} />
<Decision title={translateMessage("No, please take me back")} commit={() => setResult(TYPE_CANCELLED)} />
</UserDecisions>
@@ -14,6 +14,7 @@
TYPE_NEW,
TYPE_COMPATIBLE_EXISTING,
} from "./setupDialogTypes";
import { $msg as translateMessage } from "@/common/translation";
type Props = {
setResult: (result: OutroAskUserModeResultType) => void;
@@ -25,58 +26,66 @@
});
const proceedMessage = $derived.by(() => {
if (userType === TYPE_NEW) {
return "Proceed to the next step.";
return translateMessage("Proceed to the next step.");
} else if (userType === TYPE_EXISTING) {
return "Proceed to the next step.";
return translateMessage("Proceed to the next step.");
} else if (userType === TYPE_COMPATIBLE_EXISTING) {
return "Apply the settings";
return translateMessage("Apply the settings");
} else {
return "Please select an option to proceed";
return translateMessage("Please select an option to proceed");
}
});
</script>
<DialogHeader title="Mostly Complete: Decision Required" />
<DialogHeader title={translateMessage("Mostly Complete: Decision Required")} />
<Guidance>
The connection to the server has been configured successfully. As the next step, <strong
>the local database, that is to say the synchronisation information, must be reconstituted.</strong
{translateMessage("The connection to the server has been configured successfully. As the next step,")} <strong
>{translateMessage(
"the local database, that is to say the synchronisation information, must be reconstituted."
)}</strong
>
</Guidance>
<Instruction>
<Question>Please select your situation.</Question>
<Question>{translateMessage("Please select your situation.")}</Question>
<Option
title="I am setting up a new server for the first time / I want to reset my existing server."
title={translateMessage(
"I am setting up a new server for the first time / I want to reset my existing server."
)}
bind:value={userType}
selectedValue={TYPE_NEW}
>
<InfoNote>
Selecting this option will result in the current data on this device being used to initialise the server.
Any existing data on the server will be completely overwritten.
{translateMessage(
"Selecting this option will result in the current data on this device being used to initialise the server. Any existing data on the server will be completely overwritten."
)}
</InfoNote>
</Option>
<Option
title="My remote server is already set up. I want to join this device."
title={translateMessage("My remote server is already set up. I want to join this device.")}
bind:value={userType}
selectedValue={TYPE_EXISTING}
>
<InfoNote>
Selecting this option will result in this device joining the existing server. You need to fetching the
existing synchronisation data from the server to this device.
{translateMessage(
"Selecting this option will result in this device joining the existing server. You need to fetching the existing synchronisation data from the server to this device."
)}
</InfoNote>
</Option>
<Option
title="The remote is already set up, and the configuration is compatible (or got compatible by this operation)."
title={translateMessage(
"The remote is already set up, and the configuration is compatible (or got compatible by this operation)."
)}
bind:value={userType}
selectedValue={TYPE_COMPATIBLE_EXISTING}
>
<InfoNote warning>
Unless you are certain, selecting this options is bit dangerous. It assumes that the server configuration is
compatible with this device. If this is not the case, data loss may occur. Please ensure you know what you
are doing.
{translateMessage(
"Unless you are certain, selecting this options is bit dangerous. It assumes that the server configuration is compatible with this device. If this is not the case, data loss may occur. Please ensure you know what you are doing."
)}
</InfoNote>
</Option>
</Instruction>
<UserDecisions>
<Decision title={proceedMessage} important={true} disabled={!canProceed} commit={() => setResult(userType)} />
<Decision title="No, please take me back" commit={() => setResult(TYPE_CANCELLED)} />
<Decision title={translateMessage("No, please take me back")} commit={() => setResult(TYPE_CANCELLED)} />
</UserDecisions>
@@ -25,7 +25,7 @@
)}
</p>
<p>
<strong>PLEASE NOTE</strong>
<strong>{translateMessage("PLEASE NOTE")}</strong>
<br />
{translateMessage(
"After restarting, select an online source device for the initial Fetch. The local LiveSync database on this device will be rebuilt from that source. Unsynchronised files in this Vault may conflict with the fetched data."
@@ -43,28 +43,40 @@
important={true}
commit={() => setResult(TYPE_APPLY)}
/>
<Decision title="No, please take me back" commit={() => setResult(TYPE_CANCELLED)} />
<Decision title={translateMessage("No, please take me back")} commit={() => setResult(TYPE_CANCELLED)} />
</UserDecisions>
{:else}
<DialogHeader title="Setup Complete: Preparing to Fetch Synchronisation Data" />
<DialogHeader title={translateMessage("Setup Complete: Preparing to Fetch Synchronisation Data")} />
<Guidance>
<p>
The connection to the server has been configured successfully. As the next step, <strong
>the latest synchronisation data will be downloaded from the server to this device.</strong
{translateMessage("The connection to the server has been configured successfully. As the next step,")}
<strong
>{translateMessage(
"the latest synchronisation data will be downloaded from the server to this device."
)}</strong
>
</p>
<p>
<strong>PLEASE NOTE</strong>
<strong>{translateMessage("PLEASE NOTE")}</strong>
<br />
After restarting, the database on this device will be rebuilt using data from the server. If there are any unsynchronised
files in this vault, conflicts may occur with the server data.
{translateMessage(
"After restarting, the database on this device will be rebuilt using data from the server. If there are any unsynchronised files in this vault, conflicts may occur with the server data."
)}
</p>
</Guidance>
<Instruction>
<Question>Please select the button below to restart and proceed to the data fetching confirmation.</Question>
<Question
>{translateMessage(
"Please select the button below to restart and proceed to the data fetching confirmation."
)}</Question
>
</Instruction>
<UserDecisions>
<Decision title="Restart and Fetch Data" important={true} commit={() => setResult(TYPE_APPLY)} />
<Decision title="No, please take me back" commit={() => setResult(TYPE_CANCELLED)} />
<Decision
title={translateMessage("Restart and Fetch Data")}
important={true}
commit={() => setResult(TYPE_APPLY)}
/>
<Decision title={translateMessage("No, please take me back")} commit={() => setResult(TYPE_CANCELLED)} />
</UserDecisions>
{/if}
@@ -36,28 +36,35 @@
important={true}
commit={() => setResult(TYPE_APPLY)}
/>
<Decision title="No, please take me back" commit={() => setResult(TYPE_CANCELLED)} />
<Decision title={msg("No, please take me back")} commit={() => setResult(TYPE_CANCELLED)} />
</UserDecisions>
{:else}
<DialogHeader title="Setup Complete: Preparing to Initialise Server" />
<DialogHeader title={msg("Setup Complete: Preparing to Initialise Server")} />
<Guidance>
<p>
The connection to the server has been configured successfully. As the next step, <strong
>the synchronisation data on the server will be built based on the current data on this device.</strong
{msg("The connection to the server has been configured successfully. As the next step,")} <strong
>{msg(
"the synchronisation data on the server will be built based on the current data on this device."
)}</strong
>
</p>
<p>
<strong>IMPORTANT</strong>
<strong>{msg("IMPORTANT")}</strong>
<br />
After restarting, the data on this device will be uploaded to the server as the 'master copy'. Please be aware
that any unintended data currently on the server will be completely overwritten.
{msg(
"After restarting, the data on this device will be uploaded to the server as the 'master copy'. Please be aware that any unintended data currently on the server will be completely overwritten."
)}
</p>
</Guidance>
<Instruction>
<Question>Please select the button below to restart and proceed to the final confirmation.</Question>
<Question>{msg("Please select the button below to restart and proceed to the final confirmation.")}</Question>
</Instruction>
<UserDecisions>
<Decision title="Restart and Initialise Server" important={true} commit={() => setResult(TYPE_APPLY)} />
<Decision title="No, please take me back" commit={() => setResult(TYPE_CANCELLED)} />
<Decision
title={msg("Restart and Initialise Server")}
important={true}
commit={() => setResult(TYPE_APPLY)}
/>
<Decision title={msg("No, please take me back")} commit={() => setResult(TYPE_CANCELLED)} />
</UserDecisions>
{/if}
@@ -23,7 +23,11 @@
detectedIssues = fixResults;
} catch (e) {
Logger(e, LOG_LEVEL_VERBOSE, "setup-couchdb-check");
detectedIssues.push({ message: `Error during testAndFixSettings: ${e}`, result: "error", classes: [] });
detectedIssues.push({
message: translateMessage("Error during testAndFixSettings: ${reason}", { reason: `${e}` }),
result: "error",
classes: [],
});
}
}
function isErrorResult(result: ConfigCheckResult): result is ResultError<unknown> | ResultErrorMessage {
@@ -71,7 +75,9 @@
</div>
{#if isFixableError(issue)}
<div class="operations">
<button onclick={() => fixIssue(issue)} class="mod-cta" disabled={processing}>Fix</button>
<button onclick={() => fixIssue(issue)} class="mod-cta" disabled={processing}
>{translateMessage("Fix")}</button
>
</div>
{/if}
</div>
@@ -83,15 +89,15 @@
<details open={!isAllSuccess}>
<summary>
{#if detectedIssues.length === 0}
No checks have been performed yet.
{translateMessage("No checks have been performed yet.")}
{:else if isAllSuccess}
All checks passed successfully!
{translateMessage("All checks passed successfully!")}
{:else}
{errorIssueCount} issue(s) detected!
{translateMessage("${count} issue(s) detected!", { count: `${errorIssueCount}` })}
{/if}
</summary>
{#if detectedIssues.length > 0}
<h3>Issue detection log:</h3>
<h3>{translateMessage("Issue detection log:")}</h3>
{#each detectedIssues as issue}
{@render result(issue)}
{/each}
@@ -58,57 +58,69 @@
</Check>
</Guidance>
{:else}
<DialogHeader title="Final Confirmation: Overwrite Server Data with This Device's Files" />
<DialogHeader title={msg("Final Confirmation: Overwrite Server Data with This Device's Files")} />
<Guidance
>This procedure will first delete all existing synchronisation data from the server. Following this, the server
data will be completely rebuilt, using the current state of your Vault on this device (including its local
database) as <strong>the single, authoritative master copy</strong>.</Guidance
>{msg(
"This procedure will first delete all existing synchronisation data from the server. Following this, the server data will be completely rebuilt, using the current state of your Vault on this device (including its local database) as"
)} <strong>{msg("the single, authoritative master copy")}</strong>.</Guidance
>
<InfoNote>
You should perform this operation only in exceptional circumstances, such as when the server data is completely
corrupted, when changes on all other devices are no longer needed, or when the database size has become unusually
large in comparison to the Vault size.
{msg(
"You should perform this operation only in exceptional circumstances, such as when the server data is completely corrupted, when changes on all other devices are no longer needed, or when the database size has become unusually large in comparison to the Vault size."
)}
</InfoNote>
<Guidance important title="⚠️ Please Confirm the Following">
<Guidance important title={msg("⚠️ Please Confirm the Following")}>
<Check
title="I understand that all changes made on other smartphones or computers possibly could be lost."
title={msg("I understand that all changes made on other smartphones or computers possibly could be lost.")}
bind:value={confirmationCheck1}
>
<InfoNote>There is a way to resolve this on other devices.</InfoNote>
<InfoNote>Of course, we can back up the data before proceeding.</InfoNote>
<InfoNote>{msg("There is a way to resolve this on other devices.")}</InfoNote>
<InfoNote>{msg("Of course, we can back up the data before proceeding.")}</InfoNote>
</Check>
<Check
title="I understand that other devices will no longer be able to synchronise, and will need to be reset the synchronisation information."
title={msg(
"I understand that other devices will no longer be able to synchronise, and will need to be reset the synchronisation information."
)}
bind:value={confirmationCheck2}
>
<InfoNote>by resetting the remote, you will be informed on other devices.</InfoNote>
<InfoNote>{msg("by resetting the remote, you will be informed on other devices.")}</InfoNote>
</Check>
<Check title="I understand that this action is irreversible once performed." bind:value={confirmationCheck3} />
<Check
title={msg("I understand that this action is irreversible once performed.")}
bind:value={confirmationCheck3}
/>
</Guidance>
{/if}
<hr />
<Instruction>
<Question>Have you created a backup before proceeding?</Question>
<Question>{msg("Have you created a backup before proceeding?")}</Question>
<InfoNote warning>
This is an extremely powerful operation. We strongly recommend that you copy your Vault folder to a safe
location.
{msg(
"This is an extremely powerful operation. We strongly recommend that you copy your Vault folder to a safe location."
)}
</InfoNote>
<Options>
<Option selectedValue={TYPE_BACKUP_DONE} title="I have created a backup of my Vault." bind:value={backupType} />
<Option
selectedValue={TYPE_BACKUP_DONE}
title={msg("I have created a backup of my Vault.")}
bind:value={backupType}
/>
<Option
selectedValue={TYPE_BACKUP_SKIPPED}
title="I understand the risks and will proceed without a backup."
title={msg("I understand the risks and will proceed without a backup.")}
bind:value={backupType}
/>
<Option
selectedValue={TYPE_UNABLE_TO_BACKUP}
title="I am unable to create a backup of my Vaults."
title={msg("I am unable to create a backup of my Vaults.")}
bind:value={backupType}
>
<InfoNote error visible={backupType === TYPE_UNABLE_TO_BACKUP}>
<strong
>You should create a new synchronisation destination and rebuild your data there. <br /> After that,
synchronise to a brand new vault on each other device with the new remote one by one.</strong
>{msg("You should create a new synchronisation destination and rebuild your data there.")} <br />
{msg(
"After that, synchronise to a brand new vault on each other device with the new remote one by one."
)}</strong
>
</InfoNote>
</Option>
@@ -116,17 +128,19 @@
</Instruction>
{#if !isP2P}
<Instruction>
<ExtraItems title="Advanced">
<Check title="Prevent fetching configuration from server" bind:value={preventFetchingConfig} />
<ExtraItems title={msg("Advanced")}>
<Check title={msg("Prevent fetching configuration from server")} bind:value={preventFetchingConfig} />
</ExtraItems>
</Instruction>
{/if}
<UserDecisions>
<Decision
title={isP2P ? msg("Ui.SetupWizard.RebuildEverythingP2P.Proceed") : "I Understand, Overwrite Server"}
title={isP2P
? msg("Ui.SetupWizard.RebuildEverythingP2P.Proceed")
: msg("I Understand, Overwrite Server")}
important
disabled={!canProceed}
commit={() => commit()}
/>
<Decision title="Cancel" commit={() => setResult(TYPE_CANCEL)} />
<Decision title={msg("Cancel")} commit={() => setResult(TYPE_CANCEL)} />
</UserDecisions>
@@ -5,6 +5,7 @@
import Instruction from "@/modules/services/LiveSyncUI/components/Instruction.svelte";
import UserDecisions from "@/modules/services/LiveSyncUI/components/UserDecisions.svelte";
import { TYPE_CLOSE, type ScanQRCodeResultType } from "./setupDialogTypes";
import { $msg as translateMessage } from "@/common/translation";
type Props = {
setResult: (_result: ScanQRCodeResultType) => void;
@@ -12,17 +13,25 @@
const { setResult }: Props = $props();
</script>
<DialogHeader title="Scan QR Code" />
<Guidance>Please follow the steps below to import settings from your existing device.</Guidance>
<DialogHeader title={translateMessage("Scan QR Code")} />
<Guidance>{translateMessage("Please follow the steps below to import settings from your existing device.")}</Guidance>
<Instruction>
<!-- <Question>How would you like to configure the connection to your server?</Question> -->
<ol>
<li>On this device, please keep this Vault open.</li>
<li>On the source device, open Obsidian.</li>
<li>On the source device, from the command palette, run the 'Show settings as a QR code' command.</li>
<li>On this device, switch to the camera app or use a QR code scanner to scan the displayed QR code.</li>
<li>{translateMessage("On this device, please keep this Vault open.")}</li>
<li>{translateMessage("On the source device, open Obsidian.")}</li>
<li>
{translateMessage(
"On the source device, from the command palette, run the 'Show settings as a QR code' command."
)}
</li>
<li>
{translateMessage(
"On this device, switch to the camera app or use a QR code scanner to scan the displayed QR code."
)}
</li>
</ol>
</Instruction>
<UserDecisions>
<Decision title="Close this dialog" important={true} commit={() => setResult(TYPE_CLOSE)} />
<Decision title={translateMessage("Close this dialog")} important={true} commit={() => setResult(TYPE_CLOSE)} />
</UserDecisions>
@@ -23,13 +23,13 @@
let userType = $state<SelectMethodExistingResultType>(TYPE_CANCELLED);
let proceedTitle = $derived.by(() => {
if (userType === TYPE_USE_SETUP_URI) {
return "Proceed with Setup URI";
return translateMessage("Proceed with Setup URI");
} else if (userType === TYPE_CONFIGURE_MANUALLY) {
return translateMessage("Ui.SetupWizard.SelectExisting.ProceedManual");
} else if (userType === TYPE_SCAN_QR_CODE) {
return "Scan the QR code displayed on an active device using this device's camera.";
return translateMessage("Scan the QR code displayed on an active device using this device's camera.");
} else {
return "Please select an option to proceed";
return translateMessage("Please select an option to proceed");
}
});
const canProceed = $derived.by(() => {
@@ -37,16 +37,24 @@
});
</script>
<DialogHeader title="Device Setup Method" />
<Guidance>You are adding this device to an existing synchronisation setup.</Guidance>
<DialogHeader title={translateMessage("Device Setup Method")} />
<Guidance>{translateMessage("You are adding this device to an existing synchronisation setup.")}</Guidance>
<Instruction>
<Question>Please select a method to import the settings from another device.</Question>
<Question>{translateMessage("Please select a method to import the settings from another device.")}</Question>
<Options>
<Option selectedValue={TYPE_USE_SETUP_URI} title="Use a Setup URI (Recommended)" bind:value={userType}>
Paste the Setup URI generated from one of your active devices.
<Option
selectedValue={TYPE_USE_SETUP_URI}
title={translateMessage("Use a Setup URI (Recommended)")}
bind:value={userType}
>
{translateMessage("Paste the Setup URI generated from one of your active devices.")}
</Option>
<Option selectedValue={TYPE_SCAN_QR_CODE} title="Scan a QR Code (Recommended for mobile)" bind:value={userType}>
Scan the QR code displayed on an active device using this device's camera.
<Option
selectedValue={TYPE_SCAN_QR_CODE}
title={translateMessage("Scan a QR Code (Recommended for mobile)")}
bind:value={userType}
>
{translateMessage("Scan the QR code displayed on an active device using this device's camera.")}
</Option>
<Option
selectedValue={TYPE_CONFIGURE_MANUALLY}
@@ -59,5 +67,5 @@
</Instruction>
<UserDecisions>
<Decision title={proceedTitle} important={canProceed} disabled={!canProceed} commit={() => setResult(userType)} />
<Decision title="Cancel" commit={() => setResult(TYPE_CANCELLED)} />
<Decision title={translateMessage("Cancel")} commit={() => setResult(TYPE_CANCELLED)} />
</UserDecisions>
@@ -22,11 +22,11 @@
let userType = $state<SelectMethodNewUserResultType>(TYPE_CANCELLED);
let proceedTitle = $derived.by(() => {
if (userType === TYPE_USE_SETUP_URI) {
return "Proceed with Setup URI";
return translateMessage("Proceed with Setup URI");
} else if (userType === TYPE_CONFIGURE_MANUALLY) {
return translateMessage("Ui.SetupWizard.SelectNew.ProceedManual");
} else {
return "Please select an option to proceed";
return translateMessage("Please select an option to proceed");
}
});
const canProceed = $derived.by(() => {
@@ -34,12 +34,16 @@
});
</script>
<DialogHeader title="Connection Method" />
<DialogHeader title={translateMessage("Connection Method")} />
<Guidance>{translateMessage("Ui.SetupWizard.SelectNew.Guidance")}</Guidance>
<Instruction>
<Question>{translateMessage("Ui.SetupWizard.SelectNew.Question")}</Question>
<Options>
<Option selectedValue={TYPE_USE_SETUP_URI} title="Use a Setup URI (Recommended)" bind:value={userType}>
<Option
selectedValue={TYPE_USE_SETUP_URI}
title={translateMessage("Use a Setup URI (Recommended)")}
bind:value={userType}
>
{translateMessage("Ui.SetupWizard.SelectNew.SetupUriOptionDesc")}
</Option>
<Option
@@ -56,5 +60,5 @@
</Instruction>
<UserDecisions>
<Decision title={proceedTitle} important={canProceed} disabled={!canProceed} commit={() => setResult(userType)} />
<Decision title="Cancel" commit={() => setResult(TYPE_CANCELLED)} />
<Decision title={translateMessage("Cancel")} commit={() => setResult(TYPE_CANCELLED)} />
</UserDecisions>
@@ -22,13 +22,13 @@
let userType = $state<SetupRemoteResultType>(TYPE_CANCELLED);
let proceedTitle = $derived.by(() => {
if (userType === TYPE_COUCHDB) {
return "Continue to CouchDB setup";
return translateMessage("Continue to CouchDB setup");
} else if (userType === TYPE_BUCKET) {
return translateMessage("Ui.SetupWizard.SetupRemote.ProceedBucket");
} else if (userType === TYPE_P2P) {
return translateMessage("Ui.SetupWizard.SetupRemote.ProceedP2P");
} else {
return "Please select an option to proceed";
return translateMessage("Please select an option to proceed");
}
});
const canProceed = $derived.by(() => {
@@ -63,5 +63,5 @@
</Instruction>
<UserDecisions>
<Decision title={proceedTitle} important={canProceed} disabled={!canProceed} commit={() => setResult(userType)} />
<Decision title="No, please take me back" commit={() => setResult(TYPE_CANCELLED)} />
<Decision title={translateMessage("No, please take me back")} commit={() => setResult(TYPE_CANCELLED)} />
</UserDecisions>
@@ -19,6 +19,7 @@
import { getDialogContext, type GuestDialogProps } from "@/modules/services/LiveSyncUI/svelteDialog";
import { copyTo, pickBucketSyncSettings } from "@vrtmrz/livesync-commonlib/compat/common/utils";
import { TYPE_CANCELLED, type SetupRemoteBucketResultType } from "./setupDialogTypes";
import { $msg as translateMessage } from "@/common/translation";
const default_setting = pickBucketSyncSettings(DEFAULT_SETTINGS);
@@ -82,17 +83,17 @@
const trialRemoteSetting = generateSetting();
const replicator = await context.services.replicator.getNewReplicator(trialRemoteSetting);
if (!replicator) {
return "Failed to create replicator instance.";
return translateMessage("Failed to create replicator instance.");
}
try {
const result = await replicator.tryConnectRemote(trialRemoteSetting, false);
if (result) {
return "";
} else {
return "Failed to connect to the server. Please check your settings.";
return translateMessage("Failed to connect to the server. Please check your settings.");
}
} catch (e) {
return `Failed to connect to the server: ${e}`;
return translateMessage("Failed to connect to the server: ${reason}", { reason: `${e}` });
}
} finally {
processing = false;
@@ -109,7 +110,7 @@
return;
}
} catch (e) {
error = `Error during connection test: ${e}`;
error = translateMessage("Error during connection test: ${reason}", { reason: `${e}` });
return;
}
}
@@ -122,9 +123,13 @@
}
</script>
<DialogHeader title="S3/MinIO/R2 Configuration" />
<Guidance>Please enter the details required to connect to your S3/MinIO/R2 compatible object storage service.</Guidance>
<InputRow label="Endpoint URL">
<DialogHeader title={translateMessage("S3/MinIO/R2 Configuration")} />
<Guidance
>{translateMessage(
"Please enter the details required to connect to your S3/MinIO/R2 compatible object storage service."
)}</Guidance
>
<InputRow label={translateMessage("Endpoint URL")}>
<input
type="text"
name="s3-endpoint"
@@ -137,13 +142,15 @@
bind:value={syncSetting.endpoint}
/>
</InputRow>
<InfoNote warning visible={isEndpointInsecure}>We can use only Secure (HTTPS) connections on Obsidian Mobile.</InfoNote>
<InfoNote warning visible={isEndpointInsecure}
>{translateMessage("We can use only Secure (HTTPS) connections on Obsidian Mobile.")}</InfoNote
>
<InputRow label="Access Key ID">
<InputRow label={translateMessage("Access Key ID")}>
<input
type="text"
name="s3-access-key-id"
placeholder="Enter your Access Key ID"
placeholder={translateMessage("Enter your Access Key ID")}
autocorrect="off"
autocapitalize="off"
spellcheck="false"
@@ -152,19 +159,19 @@
/>
</InputRow>
<InputRow label="Secret Access Key">
<InputRow label={translateMessage("Secret Access Key")}>
<Password
name="s3-secret-access-key"
placeholder="Enter your Secret Access Key"
placeholder={translateMessage("Enter your Secret Access Key")}
required
bind:value={syncSetting.secretKey}
/>
</InputRow>
<InputRow label="Bucket Name">
<InputRow label={translateMessage("Bucket Name")}>
<input
type="text"
name="s3-bucket-name"
placeholder="Enter your Bucket Name"
placeholder={translateMessage("Enter your Bucket Name")}
autocorrect="off"
autocapitalize="off"
spellcheck="false"
@@ -172,26 +179,26 @@
bind:value={syncSetting.bucket}
/></InputRow
>
<InputRow label="Region">
<InputRow label={translateMessage("Region")}>
<input
type="text"
name="s3-region"
placeholder="Enter your Region (e.g., us-east-1, auto for R2)"
placeholder={translateMessage("Enter your Region (e.g., us-east-1, auto for R2)")}
autocorrect="off"
autocapitalize="off"
spellcheck="false"
bind:value={syncSetting.region}
/>
</InputRow>
<InputRow label="Use Path-Style Access">
<InputRow label={translateMessage("Use Path-Style Access")}>
<input type="checkbox" name="s3-use-path-style" bind:checked={syncSetting.forcePathStyle} />
</InputRow>
<InputRow label="Folder Prefix">
<InputRow label={translateMessage("Folder Prefix")}>
<input
type="text"
name="s3-folder-prefix"
placeholder="Enter a folder prefix (optional)"
placeholder={translateMessage("Enter a folder prefix (optional)")}
autocorrect="off"
autocapitalize="off"
spellcheck="false"
@@ -199,20 +206,21 @@
/>
</InputRow>
<InfoNote>
If you want to store the data in a specific folder within the bucket, you can specify a folder prefix here.
Otherwise, leave it blank to store data at the root of the bucket.
{translateMessage(
"If you want to store the data in a specific folder within the bucket, you can specify a folder prefix here. Otherwise, leave it blank to store data at the root of the bucket."
)}
</InfoNote>
<InputRow label="Use internal API">
<InputRow label={translateMessage("Use internal API")}>
<input type="checkbox" name="s3-use-internal-api" bind:checked={syncSetting.useCustomRequestHandler} />
</InputRow>
<InfoNote>
If you cannot avoid CORS issues, you might want to try this option. It uses Obsidian's internal API to communicate
with the S3 server. Not compliant with web standards, but works. Note that this might break in future Obsidian
versions.
{translateMessage(
"If you cannot avoid CORS issues, you might want to try this option. It uses Obsidian's internal API to communicate with the S3 server. Not compliant with web standards, but works. Note that this might break in future Obsidian versions."
)}
</InfoNote>
<ExtraItems title="Advanced Settings">
<InputRow label="Custom Headers">
<ExtraItems title={translateMessage("Advanced Settings")}>
<InputRow label={translateMessage("Custom Headers")}>
<textarea
name="bucket-custom-headers"
placeholder="e.g., x-example-header: value\n another-header: value2"
@@ -229,11 +237,16 @@
</InfoNote>
{#if processing}
Checking connection... Please wait.
{translateMessage("Checking connection... Please wait.")}
{:else}
<UserDecisions>
<Decision title="Test Settings and Continue" important disabled={!canProceed} commit={() => checkAndCommit()} />
<Decision title="Continue anyway" commit={() => commit()} />
<Decision title="Cancel" commit={() => cancel()} />
<Decision
title={translateMessage("Test Settings and Continue")}
important
disabled={!canProceed}
commit={() => checkAndCommit()}
/>
<Decision title={translateMessage("Continue anyway")} commit={() => commit()} />
<Decision title={translateMessage("Cancel")} commit={() => cancel()} />
</UserDecisions>
{/if}
@@ -75,7 +75,7 @@
const trialRemoteSetting = generateSetting();
const replicator = await context.services.replicator.getNewReplicator(trialRemoteSetting);
if (!replicator) {
return "Failed to create replicator instance.";
return translateMessage("Failed to create replicator instance.");
}
try {
const result = await probeCouchDBConnection(
@@ -86,10 +86,12 @@
if (result.ok) {
return "";
} else {
return `Failed to connect to the server: ${result.reason}`;
return translateMessage("Failed to connect to the server: ${reason}", {
reason: `${result.reason}`,
});
}
} catch (e) {
return `Failed to connect to the server: ${e}`;
return translateMessage("Failed to connect to the server: ${reason}", { reason: `${e}` });
}
} finally {
processing = false;
@@ -106,7 +108,7 @@
return;
}
} catch (e) {
error = `Error during connection test: ${e}`;
error = translateMessage("Error during connection test: ${reason}", { reason: `${e}` });
return;
}
}
@@ -159,9 +161,9 @@
});
</script>
<DialogHeader title="CouchDB Configuration" />
<Guidance>Please enter the CouchDB server information below.</Guidance>
<InputRow label="URL">
<DialogHeader title={translateMessage("CouchDB Configuration")} />
<Guidance>{translateMessage("Please enter the CouchDB server information below.")}</Guidance>
<InputRow label={translateMessage("URL")}>
<input
type="text"
name="couchdb-url"
@@ -174,13 +176,15 @@
pattern="^https?://.+"
/>
</InputRow>
<InfoNote warning visible={isURIInsecure}>We can use only Secure (HTTPS) connections on Obsidian Mobile.</InfoNote>
<InfoNote warning visible={isURIInsecure}
>{translateMessage("We can use only Secure (HTTPS) connections on Obsidian Mobile.")}</InfoNote
>
<InfoNote warning visible={isURLInvalid}>{translateMessage("Enter a complete HTTP or HTTPS URL.")}</InfoNote>
<InputRow label="Username">
<InputRow label={translateMessage("Username")}>
<input
type="text"
name="couchdb-username"
placeholder="Enter your username"
placeholder={translateMessage("Enter your username")}
autocorrect="off"
autocapitalize="off"
spellcheck="false"
@@ -188,20 +192,20 @@
bind:value={syncSetting.couchDB_USER}
/>
</InputRow>
<InputRow label="Password">
<InputRow label={translateMessage("Password")}>
<Password
name="couchdb-password"
placeholder="Enter your password"
placeholder={translateMessage("Enter your password")}
bind:value={syncSetting.couchDB_PASSWORD}
required
/>
</InputRow>
<InputRow label="Database Name">
<InputRow label={translateMessage("Database Name")}>
<input
type="text"
name="couchdb-database"
placeholder="Enter your database name"
placeholder={translateMessage("Enter your database name")}
autocorrect="off"
autocapitalize="off"
spellcheck="false"
@@ -212,17 +216,17 @@
<InfoNote>
{translateMessage("CouchDB validates the database name when you connect. The name must not be empty.")}
</InfoNote>
<InputRow label="Use Internal API">
<InputRow label={translateMessage("Use Internal API")}>
<input type="checkbox" name="couchdb-use-internal-api" bind:checked={syncSetting.useRequestAPI} />
</InputRow>
<InfoNote>
If you cannot avoid CORS issues, you might want to try this option. It uses Obsidian's internal API to communicate
with the CouchDB server. Not compliant with web standards, but works. Note that this might break in future Obsidian
versions.
{translateMessage(
"If you cannot avoid CORS issues, you might want to try this option. It uses Obsidian's internal API to communicate with the CouchDB server. Not compliant with web standards, but works. Note that this might break in future Obsidian versions."
)}
</InfoNote>
<ExtraItems title="Advanced Settings">
<InputRow label="Custom Headers">
<ExtraItems title={translateMessage("Advanced Settings")}>
<InputRow label={translateMessage("Custom Headers")}>
<textarea
name="couchdb-custom-headers"
placeholder="e.g., x-example-header: value\n another-header: value2"
@@ -233,11 +237,11 @@
></textarea>
</InputRow>
</ExtraItems>
<ExtraItems title="Experimental Settings">
<InputRow label="Use JWT Authentication">
<ExtraItems title={translateMessage("Experimental Settings")}>
<InputRow label={translateMessage("Use JWT Authentication")}>
<input type="checkbox" name="couchdb-use-jwt" bind:checked={syncSetting.useJWT} />
</InputRow>
<InputRow label="JWT Algorithm">
<InputRow label={translateMessage("JWT Algorithm")}>
<select bind:value={syncSetting.jwtAlgorithm} disabled={!isUseJWT}>
<option value="HS256">HS256</option>
<option value="HS512">HS512</option>
@@ -245,7 +249,7 @@
<option value="ES512">ES512</option>
</select>
</InputRow>
<InputRow label="JWT Expiration Duration (minutes)">
<InputRow label={translateMessage("JWT Expiration Duration (minutes)")}>
<input
type="text"
name="couchdb-jwt-exp-duration"
@@ -254,43 +258,44 @@
disabled={!isUseJWT}
/>
</InputRow>
<InputRow label="JWT Key">
<InputRow label={translateMessage("JWT Key")}>
<textarea
name="couchdb-jwt-key"
rows="5"
autocapitalize="off"
spellcheck="false"
placeholder="Enter your JWT secret or private key"
placeholder={translateMessage("Enter your JWT secret or private key")}
bind:value={syncSetting.jwtKey}
disabled={!isUseJWT}
></textarea>
</InputRow>
<InfoNote>
For HS256/HS512 algorithms, provide the shared secret key. For ES256/ES512 algorithms, provide the pkcs8
PEM-formatted private key.
{translateMessage(
"For HS256/HS512 algorithms, provide the shared secret key. For ES256/ES512 algorithms, provide the pkcs8 PEM-formatted private key."
)}
</InfoNote>
<InputRow label="JWT Key ID (kid)">
<InputRow label={translateMessage("JWT Key ID (kid)")}>
<input
type="text"
name="couchdb-jwt-kid"
placeholder="Enter your JWT Key ID"
placeholder={translateMessage("Enter your JWT Key ID")}
bind:value={syncSetting.jwtKid}
disabled={!isUseJWT}
/>
</InputRow>
<InputRow label="JWT Subject (sub)">
<InputRow label={translateMessage("JWT Subject (sub)")}>
<input
type="text"
name="couchdb-jwt-sub"
placeholder="Enter your JWT Subject (CouchDB Username)"
placeholder={translateMessage("Enter your JWT Subject (CouchDB Username)")}
bind:value={syncSetting.jwtSub}
disabled={!isUseJWT}
/>
</InputRow>
<InfoNote warning>
JWT (JSON Web Token) authentication allows you to securely authenticate with the CouchDB server using tokens.
Ensure that your CouchDB server is configured to accept JWTs and that the provided key and settings match the
server's configuration. Incidentally, I have not verified it very thoroughly.
{translateMessage(
"JWT (JSON Web Token) authentication allows you to securely authenticate with the CouchDB server using tokens. Ensure that your CouchDB server is configured to accept JWTs and that the provided key and settings match the server's configuration. Incidentally, I have not verified it very thoroughly."
)}
</InfoNote>
</ExtraItems>
@@ -307,7 +312,7 @@
</InfoNote>
{#if processing}
Checking connection... Please wait.
{translateMessage("Checking connection... Please wait.")}
{:else}
<UserDecisions>
<Decision title={primaryActionTitle} important disabled={!canProceed} commit={() => checkAndCommit()} />
@@ -323,6 +328,6 @@
commit={() => commit()}
/>
{/if}
<Decision title="Cancel" commit={() => cancel()} />
<Decision title={translateMessage("Cancel")} commit={() => cancel()} />
</UserDecisions>
{/if}
@@ -17,6 +17,7 @@
import type { GuestDialogProps } from "@/modules/services/LiveSyncUI/svelteDialog";
import { copyTo, pickEncryptionSettings } from "@vrtmrz/livesync-commonlib/compat/common/utils";
import { TYPE_CANCELLED, type SetupRemoteE2EEResultType } from "./setupDialogTypes";
import { $msg as translateMessage } from "@/common/translation";
type Props = GuestDialogProps<SetupRemoteE2EEResultType, EncryptionSettings>;
const { setResult, getInitialData }: Props = $props();
@@ -47,30 +48,31 @@
}
</script>
<DialogHeader title="End-to-End Encryption" />
<Guidance>Please configure your end-to-end encryption settings.</Guidance>
<InputRow label="End-to-End Encryption">
<DialogHeader title={translateMessage("End-to-End Encryption")} />
<Guidance>{translateMessage("Please configure your end-to-end encryption settings.")}</Guidance>
<InputRow label={translateMessage("End-to-End Encryption")}>
<input type="checkbox" bind:checked={encryptionSettings.encrypt} />
<Password
name="e2ee-passphrase"
placeholder="Enter your passphrase"
placeholder={translateMessage("Enter your passphrase")}
bind:value={encryptionSettings.passphrase}
disabled={!encryptionSettings.encrypt}
required={encryptionSettings.encrypt}
/>
</InputRow>
<InfoNote title="Strongly Recommended">
Enabling end-to-end encryption ensures that your data is encrypted on your device before being sent to the remote
server. This means that even if someone gains access to the server, they won't be able to read your data without the
passphrase. Make sure to remember your passphrase, as it will be required to decrypt your data on other devices.
<InfoNote title={translateMessage("Strongly Recommended")}>
{translateMessage(
"Enabling end-to-end encryption ensures that your data is encrypted on your device before being sent to the remote server. This means that even if someone gains access to the server, they won't be able to read your data without the passphrase. Make sure to remember your passphrase, as it will be required to decrypt your data on other devices."
)}
<br />
Also, please note that if you are using Peer-to-Peer synchronization, this configuration will be used when you switch
to other methods and connect to a remote server in the future.
{translateMessage(
"Also, please note that if you are using Peer-to-Peer synchronization, this configuration will be used when you switch to other methods and connect to a remote server in the future."
)}
</InfoNote>
<InfoNote warning>
This setting must be the same even when connecting to multiple synchronisation destinations.
{translateMessage("This setting must be the same even when connecting to multiple synchronisation destinations.")}
</InfoNote>
<InputRow label="Obfuscate Properties">
<InputRow label={translateMessage("Obfuscate Properties")}>
<input
type="checkbox"
bind:checked={encryptionSettings.usePathObfuscation}
@@ -79,14 +81,13 @@
</InputRow>
<InfoNote>
Obfuscating properties (e.g., path of file, size, creation and modification dates) adds an additional layer of
security by making it harder to identify the structure and names of your files and folders on the remote server.
This helps protect your privacy and makes it more difficult for unauthorized users to infer information about your
data.
{translateMessage(
"Obfuscating properties (e.g., path of file, size, creation and modification dates) adds an additional layer of security by making it harder to identify the structure and names of your files and folders on the remote server. This helps protect your privacy and makes it more difficult for unauthorized users to infer information about your data."
)}
</InfoNote>
<ExtraItems title="Advanced">
<InputRow label="Encryption Algorithm">
<ExtraItems title={translateMessage("Advanced")}>
<InputRow label={translateMessage("Encryption Algorithm")}>
<select bind:value={encryptionSettings.E2EEAlgorithm} disabled={!encryptionSettings.encrypt}>
{#each Object.values(E2EEAlgorithms) as alg}
<option value={alg}>{E2EEAlgorithmNames[alg] ?? alg}</option>
@@ -94,30 +95,33 @@
</select>
</InputRow>
<InfoNote>
In most cases, you should stick with the default algorithm ({E2EEAlgorithmNames[
DEFAULT_SETTINGS.E2EEAlgorithm
]}), This setting is only required if you have an existing Vault encrypted in a different format.
{translateMessage(
"In most cases, you should stick with the default algorithm (${algorithm}), This setting is only required if you have an existing Vault encrypted in a different format.",
{ algorithm: E2EEAlgorithmNames[DEFAULT_SETTINGS.E2EEAlgorithm] }
)}
</InfoNote>
<InfoNote warning>
Changing the encryption algorithm will prevent access to any data previously encrypted with a different
algorithm. Ensure that all your devices are configured to use the same algorithm to maintain access to your
data.
{translateMessage(
"Changing the encryption algorithm will prevent access to any data previously encrypted with a different algorithm. Ensure that all your devices are configured to use the same algorithm to maintain access to your data."
)}
</InfoNote>
</ExtraItems>
<InfoNote warning>
<p>
Please be aware that the End-to-End Encryption passphrase is not validated until the synchronisation process
actually commences. This is a security measure designed to protect your data.
{translateMessage(
"Please be aware that the End-to-End Encryption passphrase is not validated until the synchronisation process actually commences. This is a security measure designed to protect your data."
)}
</p>
<p>
Therefore, we ask that you exercise extreme caution when configuring server information manually. If an
incorrect passphrase is entered, the data on the server will become corrupted. <br /><br />
Please understand that this is intended behaviour.
{translateMessage(
"Therefore, we ask that you exercise extreme caution when configuring server information manually. If an incorrect passphrase is entered, the data on the server will become corrupted."
)} <br /><br />
{translateMessage("Please understand that this is intended behaviour.")}
</p>
</InfoNote>
<UserDecisions>
<Decision title="Proceed" important disabled={!e2eeValid} commit={() => commit()} />
<Decision title="Cancel" commit={() => setResult(TYPE_CANCELLED)} />
<Decision title={translateMessage("Proceed")} important disabled={!e2eeValid} commit={() => commit()} />
<Decision title={translateMessage("Cancel")} commit={() => setResult(TYPE_CANCELLED)} />
</UserDecisions>
@@ -123,7 +123,9 @@
try {
const result = await probeP2PSetupConnection(replicator);
if (!result.ok) {
return `Failed to connect to the signalling relay: ${result.reason}`;
return translateMessage("Failed to connect to the signalling relay: ${reason}", {
reason: `${result.reason}`,
});
}
return "";
} finally {
@@ -157,7 +159,7 @@
return;
}
} catch (e) {
error = `Error during connection test: ${e}`;
error = translateMessage("Error during connection test: ${reason}", { reason: `${e}` });
return;
}
}
@@ -178,9 +180,9 @@
});
</script>
<DialogHeader title="P2P Configuration" />
<Guidance>Please enter the Peer-to-Peer Synchronisation information below.</Guidance>
<InputRow label="Enabled">
<DialogHeader title={translateMessage("P2P Configuration")} />
<Guidance>{translateMessage("Please enter the Peer-to-Peer Synchronisation information below.")}</Guidance>
<InputRow label={translateMessage("Enabled")}>
<input type="checkbox" name="p2p-enabled" bind:checked={syncSetting.P2P_Enabled} />
</InputRow>
<InputRow label={translateMessage("Signalling relay URLs")}>
@@ -208,7 +210,7 @@
rel="noopener noreferrer">{translateMessage("Learn more about P2P connections")}</a
>.
</InfoNote>
<InputRow label="Group ID">
<InputRow label={translateMessage("Group ID")}>
<input
type="text"
name="p2p-room-id"
@@ -218,17 +220,24 @@
spellcheck="false"
bind:value={syncSetting.P2P_roomID}
/>
<button class="button" onclick={() => generateDefaultGroupId()}>Generate Random ID</button>
<button class="button" onclick={() => generateDefaultGroupId()}>{translateMessage("Generate Random ID")}</button>
</InputRow>
<InputRow label="Passphrase">
<Password name="p2p-password" placeholder="Enter your passphrase" bind:value={syncSetting.P2P_passphrase} />
<InputRow label={translateMessage("Passphrase")}>
<Password
name="p2p-password"
placeholder={translateMessage("Enter your passphrase")}
bind:value={syncSetting.P2P_passphrase}
/>
</InputRow>
<InfoNote>
The Group ID and passphrase are used to identify your group of devices. Make sure to use the same Group ID and
passphrase on all devices you want to synchronise.<br />
Note that the Group ID is not limited to the generated format; you can use any string as the Group ID.
{translateMessage(
"The Group ID and passphrase are used to identify your group of devices. Make sure to use the same Group ID and passphrase on all devices you want to synchronise."
)}<br />
{translateMessage(
"Note that the Group ID is not limited to the generated format; you can use any string as the Group ID."
)}
</InfoNote>
<InputRow label="Device Peer ID">
<InputRow label={translateMessage("Device Peer ID")}>
<input
type="text"
name="p2p-device-peer-id"
@@ -239,12 +248,13 @@
bind:value={syncSetting.P2P_DevicePeerName}
/>
</InputRow>
<InputRow label="Auto Start P2P Connection">
<InputRow label={translateMessage("Auto Start P2P Connection")}>
<input type="checkbox" name="p2p-auto-start" bind:checked={syncSetting.P2P_AutoStart} />
</InputRow>
<InfoNote>
If "Auto Start P2P Connection" is enabled, the P2P connection will be started automatically when the plug-in
launches.
{translateMessage(
'If "Auto Start P2P Connection" is enabled, the P2P connection will be started automatically when the plug-in launches.'
)}
</InfoNote>
<InputRow label={translateMessage("Announce changes automatically after connecting")}>
<input type="checkbox" name="p2p-auto-broadcast" bind:checked={syncSetting.P2P_AutoBroadcast} />
@@ -254,10 +264,11 @@
"When enabled, this device notifies connected peers after a local change. The notification contains no Vault data; a peer which follows this device then fetches the change through the encrypted P2P connection."
)}
</InfoNote>
<ExtraItems title="Advanced Settings">
<ExtraItems title={translateMessage("Advanced Settings")}>
<InfoNote>
TURN server settings are only necessary if you are behind a strict NAT or firewall that prevents direct P2P
connections. In most cases, you can leave these fields blank.
{translateMessage(
"TURN server settings are only necessary if you are behind a strict NAT or firewall that prevents direct P2P connections. In most cases, you can leave these fields blank."
)}
</InfoNote>
<InfoNote warning>
{translateMessage(
@@ -269,7 +280,7 @@
rel="noopener noreferrer">{translateMessage("Learn more about signalling and TURN")}</a
>.
</InfoNote>
<InputRow label="TURN Server URLs (comma-separated)">
<InputRow label={translateMessage("TURN Server URLs (comma-separated)")}>
<textarea
name="p2p-turn-servers"
placeholder="turn:turn.example.com:3478,turn:turn.example.com:443"
@@ -279,21 +290,21 @@
rows="5"
></textarea>
</InputRow>
<InputRow label="TURN Username">
<InputRow label={translateMessage("TURN Username")}>
<input
type="text"
name="p2p-turn-username"
placeholder="Enter TURN username"
placeholder={translateMessage("Enter TURN username")}
autocorrect="off"
autocapitalize="off"
spellcheck="false"
bind:value={syncSetting.P2P_turnUsername}
/>
</InputRow>
<InputRow label="TURN Credential">
<InputRow label={translateMessage("TURN Credential")}>
<Password
name="p2p-turn-credential"
placeholder="Enter TURN credential"
placeholder={translateMessage("Enter TURN credential")}
bind:value={syncSetting.P2P_turnCredential}
/>
</InputRow>
@@ -302,11 +313,16 @@
{error}
</InfoNote>
{#if processing}
Checking connection... Please wait.
{translateMessage("Checking connection... Please wait.")}
{:else}
<UserDecisions>
<Decision title="Test Settings and Continue" important disabled={!canProceed} commit={() => checkAndCommit()} />
<Decision title="Continue anyway" commit={() => commit()} />
<Decision title="Cancel" commit={() => cancel()} />
<Decision
title={translateMessage("Test Settings and Continue")}
important
disabled={!canProceed}
commit={() => checkAndCommit()}
/>
<Decision title={translateMessage("Continue anyway")} commit={() => commit()} />
<Decision title={translateMessage("Cancel")} commit={() => cancel()} />
</UserDecisions>
{/if}
@@ -13,6 +13,7 @@
import { decryptString } from "@vrtmrz/livesync-commonlib/compat/encryption/stringEncryption";
import type { GuestDialogProps } from "@/modules/services/LiveSyncUI/svelteDialog";
import { TYPE_CANCELLED, type UseSetupURIResultType } from "./setupDialogTypes";
import { $msg as translateMessage } from "@/common/translation";
type Props = GuestDialogProps<UseSetupURIResultType, string>;
const { setResult, getInitialData }: Props = $props();
@@ -34,7 +35,7 @@
error = "";
if (!seemsValid) return;
if (!passphrase) {
error = "Passphrase is required.";
error = translateMessage("Passphrase is required.");
return;
}
try {
@@ -47,7 +48,7 @@
// Logger("Settings imported successfully", LOG_LEVEL_NOTICE);
return;
} catch (e) {
error = "Failed to parse Setup-URI.";
error = translateMessage("Failed to parse Setup-URI.");
return;
}
}
@@ -56,14 +57,17 @@
}
</script>
<DialogHeader title="Enter Setup URI" />
<DialogHeader title={translateMessage("Enter Setup URI")} />
<Guidance
>Please enter the Setup URI that was generated during server installation or on another device, along with the vault
passphrase.<br />
Note that you can generate a new Setup URI by running the "Copy settings as a new Setup URI" command in the command palette.</Guidance
>{translateMessage(
"Please enter the Setup URI that was generated during server installation or on another device, along with the vault passphrase."
)}<br />
{translateMessage(
'Note that you can generate a new Setup URI by running the "Copy settings as a new Setup URI" command in the command palette.'
)}</Guidance
>
<InputRow label="Setup-URI">
<InputRow label={translateMessage("Setup-URI")}>
<input
type="text"
placeholder="obsidian://setuplivesync?settings=...."
@@ -74,12 +78,12 @@
required
/>
</InputRow>
<InfoNote visible={seemsValid}>The Setup-URI is valid and ready to use.</InfoNote>
<InfoNote visible={seemsValid}>{translateMessage("The Setup-URI is valid and ready to use.")}</InfoNote>
<InfoNote warning visible={!seemsValid && setupURI.trim() != ""}>
The Setup-URI does not appear to be valid. Please check that you have copied it correctly.
{translateMessage("The Setup-URI does not appear to be valid. Please check that you have copied it correctly.")}
</InfoNote>
<InputRow label="Passphrase">
<Password placeholder="Enter your passphrase" bind:value={passphrase} required />
<InputRow label={translateMessage("Passphrase")}>
<Password placeholder={translateMessage("Enter your passphrase")} bind:value={passphrase} required />
</InputRow>
<InfoNote error visible={error.trim() != ""}>
{error}
@@ -87,10 +91,10 @@
<UserDecisions>
<Decision
title="Test Settings and Continue"
title={translateMessage("Test Settings and Continue")}
important={true}
disabled={!canProceed}
commit={() => processSetupURI()}
/>
<Decision title="Cancel" commit={() => setResult(TYPE_CANCELLED)} />
<Decision title={translateMessage("Cancel")} commit={() => setResult(TYPE_CANCELLED)} />
</UserDecisions>