feat(i18n): convert Settings sync section to t() (Batch E)
This commit is contained in:
1 parent
698fb26b0a
commit
7796c5b62d
1 file changed
+82
-63
@@ -706,16 +706,18 @@
|
|||||||
{t("nav.about")}
|
{t("nav.about")}
|
||||||
</button>
|
</button>
|
||||||
</nav>
|
</nav>
|
||||||
<button class="close" onclick={onClose} aria-label="Close" title="Close (Esc)">
|
<button
|
||||||
|
class="close"
|
||||||
|
onclick={onClose}
|
||||||
|
aria-label={t("settings.close")}
|
||||||
|
title={t("settings.close_title")}
|
||||||
|
>
|
||||||
<X size={18} aria-hidden="true" />
|
<X size={18} aria-hidden="true" />
|
||||||
</button>
|
</button>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
{#if settings.backendStub && section === "sync"}
|
{#if settings.backendStub && section === "sync"}
|
||||||
<p class="stub">
|
<p class="stub">{t("settings.sync.stub")}</p>
|
||||||
Preview mode — the sync backend isn't implemented yet (Phase 9). Changes are kept in the UI
|
|
||||||
only.
|
|
||||||
</p>
|
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if section === "recording"}
|
{#if section === "recording"}
|
||||||
@@ -1261,51 +1263,52 @@
|
|||||||
</section>
|
</section>
|
||||||
{:else if section === "sync"}
|
{:else if section === "sync"}
|
||||||
<section>
|
<section>
|
||||||
<h3>Sync & upload</h3>
|
<h3>{t("settings.sync.title")}</h3>
|
||||||
<label class="row">
|
<label class="row">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={settings.settings.sync_enabled}
|
checked={settings.settings.sync_enabled}
|
||||||
onchange={(e) => settings.setSyncEnabled((e.target as HTMLInputElement).checked)}
|
onchange={(e) => settings.setSyncEnabled((e.target as HTMLInputElement).checked)}
|
||||||
/>
|
/>
|
||||||
<span>Enable uploading meeting artifacts to configured targets</span>
|
<span>{t("settings.sync.enable_label")}</span>
|
||||||
</label>
|
</label>
|
||||||
<p class="muted">
|
<p class="muted">{t("settings.sync.enable_hint")}</p>
|
||||||
Off by default. Nothing is uploaded unless this is on and a target is enabled. Self-hosted
|
|
||||||
targets keep data on your own server; third-party clouds are clearly labeled.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<h4>Targets</h4>
|
<h4>{t("settings.sync.targets_title")}</h4>
|
||||||
{#if settings.targets.length === 0}
|
{#if settings.targets.length === 0}
|
||||||
<p class="muted">No targets yet. Add one below.</p>
|
<p class="muted">{t("settings.sync.no_targets")}</p>
|
||||||
{:else}
|
{:else}
|
||||||
<ul class="targets">
|
<ul class="targets">
|
||||||
{#each settings.targets as t (t.id)}
|
{#each settings.targets as target (target.id)}
|
||||||
<li>
|
<li>
|
||||||
<label class="tg">
|
<label class="tg">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={t.enabled}
|
checked={target.enabled}
|
||||||
onchange={() => settings.toggleTargetEnabled(t)}
|
onchange={() => settings.toggleTargetEnabled(target)}
|
||||||
/>
|
/>
|
||||||
<span class="name">{t.name || t.kind}</span>
|
<span class="name">{target.name || target.kind}</span>
|
||||||
</label>
|
</label>
|
||||||
<span class="badge" class:third={t.third_party}
|
<span class="badge" class:third={target.third_party}
|
||||||
>{t.third_party ? "third-party" : "your server"}</span
|
>{target.third_party
|
||||||
|
? t("settings.sync.third_party")
|
||||||
|
: t("settings.sync.your_server")}</span
|
||||||
>
|
>
|
||||||
<span class="host">{t.host ?? t.kind}</span>
|
<span class="host">{target.host ?? target.kind}</span>
|
||||||
{#if !t.third_party}
|
{#if !target.third_party}
|
||||||
<button class="link" onclick={() => startEdit(t)}>Edit</button>
|
<button class="link" onclick={() => startEdit(target)}
|
||||||
|
>{t("settings.sync.edit")}</button
|
||||||
|
>
|
||||||
{/if}
|
{/if}
|
||||||
<button class="link danger" onclick={() => settings.removeTarget(t.id)}
|
<button class="link danger" onclick={() => settings.removeTarget(target.id)}
|
||||||
>Remove</button
|
>{t("settings.sync.remove")}</button
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
{/each}
|
{/each}
|
||||||
</ul>
|
</ul>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<h4>{editingId ? "Edit target" : "Add a target"}</h4>
|
<h4>{editingId ? t("settings.sync.edit_target") : t("settings.sync.add_target")}</h4>
|
||||||
{#if !editingId}
|
{#if !editingId}
|
||||||
<div class="kinds">
|
<div class="kinds">
|
||||||
{#each ["webdav", "onedrive", "dropbox", "box"] as k (k)}
|
{#each ["webdav", "onedrive", "dropbox", "box"] as k (k)}
|
||||||
@@ -1317,20 +1320,22 @@
|
|||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if form.kind === "webdav"}
|
{#if form.kind === "webdav"}
|
||||||
<p class="muted">
|
<p class="muted">{t("settings.sync.webdav_hint")}</p>
|
||||||
WebDAV covers Nextcloud, ownCloud, Cloudreve, Seafile, and Synology. (Seafile: enable
|
|
||||||
SeafDAV server-side.)
|
|
||||||
</p>
|
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
<label>Name<input bind:value={form.name} placeholder="Home Nextcloud" /></label>
|
|
||||||
<label
|
<label
|
||||||
>Provider
|
>{t("settings.sync.name")}<input
|
||||||
|
bind:value={form.name}
|
||||||
|
placeholder={t("settings.sync.name_placeholder")}
|
||||||
|
/></label
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
>{t("settings.sync.provider")}
|
||||||
<select bind:value={form.provider_hint}>
|
<select bind:value={form.provider_hint}>
|
||||||
{#each PROVIDERS as p (p)}<option value={p}>{p}</option>{/each}
|
{#each PROVIDERS as p (p)}<option value={p}>{p}</option>{/each}
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
<label class="wide"
|
<label class="wide"
|
||||||
>Server URL<input
|
>{t("settings.sync.server_url")}<input
|
||||||
bind:value={form.base_url}
|
bind:value={form.base_url}
|
||||||
placeholder={davAutoPath
|
placeholder={davAutoPath
|
||||||
? "https://cloud.example.com"
|
? "https://cloud.example.com"
|
||||||
@@ -1338,36 +1343,46 @@
|
|||||||
/>
|
/>
|
||||||
{#if davAutoPath}
|
{#if davAutoPath}
|
||||||
<small class="muted"
|
<small class="muted"
|
||||||
>Just your server URL — WhispAssist adds
|
>{t("settings.sync.server_url_note_1")}
|
||||||
<code>/remote.php/dav/files/<username>/</code> automatically.</small
|
<code>/remote.php/dav/files/<username>/</code>
|
||||||
|
{t("settings.sync.server_url_note_2")}</small
|
||||||
>
|
>
|
||||||
{/if}
|
{/if}
|
||||||
</label>
|
</label>
|
||||||
<label>Remote folder<input bind:value={form.remote_base_path} /></label>
|
|
||||||
<label>Username<input bind:value={form.username} /></label>
|
|
||||||
<label
|
<label
|
||||||
>App password<input
|
>{t("settings.sync.remote_folder")}<input bind:value={form.remote_base_path} /></label
|
||||||
|
>
|
||||||
|
<label>{t("settings.sync.username")}<input bind:value={form.username} /></label>
|
||||||
|
<label
|
||||||
|
>{t("settings.sync.app_password")}<input
|
||||||
type="password"
|
type="password"
|
||||||
bind:value={form.secret}
|
bind:value={form.secret}
|
||||||
placeholder={editingId
|
placeholder={editingId ? t("settings.sync.pw_keep") : t("settings.sync.pw_store")}
|
||||||
? "leave blank to keep current password"
|
|
||||||
: "stored in OS credential store"}
|
|
||||||
/></label
|
/></label
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<fieldset class="artifacts">
|
<fieldset class="artifacts">
|
||||||
<legend>Upload</legend>
|
<legend>{t("settings.sync.upload_legend")}</legend>
|
||||||
<label><input type="checkbox" bind:checked={form.upload_transcript} /> transcript</label
|
|
||||||
>
|
|
||||||
<label><input type="checkbox" bind:checked={form.upload_notes} /> notes</label>
|
|
||||||
<label><input type="checkbox" bind:checked={form.upload_summary} /> summary</label>
|
|
||||||
<label
|
<label
|
||||||
><input type="checkbox" bind:checked={form.upload_recording} /> recording (.wav, if retained)</label
|
><input type="checkbox" bind:checked={form.upload_transcript} />
|
||||||
|
{t("settings.sync.artifact_transcript")}</label
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
><input type="checkbox" bind:checked={form.upload_notes} />
|
||||||
|
{t("settings.sync.artifact_notes")}</label
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
><input type="checkbox" bind:checked={form.upload_summary} />
|
||||||
|
{t("settings.sync.artifact_summary")}</label
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
><input type="checkbox" bind:checked={form.upload_recording} />
|
||||||
|
{t("settings.sync.artifact_recording")}</label
|
||||||
>
|
>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<label class="row small"
|
<label class="row small"
|
||||||
><input type="checkbox" bind:checked={form.allow_plaintext_lan} /> Allow plaintext http for
|
><input type="checkbox" bind:checked={form.allow_plaintext_lan} />
|
||||||
a LAN address (not recommended)</label
|
{t("settings.sync.allow_plaintext")}</label
|
||||||
>
|
>
|
||||||
<label class="row small">
|
<label class="row small">
|
||||||
<input
|
<input
|
||||||
@@ -1375,16 +1390,19 @@
|
|||||||
bind:checked={form.encrypt_before_upload}
|
bind:checked={form.encrypt_before_upload}
|
||||||
disabled={!vault?.unlocked}
|
disabled={!vault?.unlocked}
|
||||||
/>
|
/>
|
||||||
Encrypt before upload (destination stores only ciphertext)
|
{t("settings.sync.encrypt_before")}
|
||||||
{#if !vault?.unlocked}<span class="muted">— requires an unlocked vault</span>{/if}
|
{#if !vault?.unlocked}<span class="muted">{t("settings.sync.requires_vault")}</span
|
||||||
|
>{/if}
|
||||||
</label>
|
</label>
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<button onclick={testConnection}>Test connection</button>
|
<button onclick={testConnection}>{t("settings.sync.test_connection")}</button>
|
||||||
<button class="primary" onclick={saveTarget} disabled={!form.name || !form.base_url}
|
<button class="primary" onclick={saveTarget} disabled={!form.name || !form.base_url}
|
||||||
>{editingId ? "Save changes" : "Add target"}</button
|
>{editingId
|
||||||
|
? t("settings.sync.save_changes")
|
||||||
|
: t("settings.sync.add_target_btn")}</button
|
||||||
>
|
>
|
||||||
{#if editingId}
|
{#if editingId}
|
||||||
<button class="link" onclick={cancelEdit}>Cancel</button>
|
<button class="link" onclick={cancelEdit}>{t("settings.sync.cancel")}</button>
|
||||||
{/if}
|
{/if}
|
||||||
{#if testResult}
|
{#if testResult}
|
||||||
<span class="test" class:ok={testResult.ok} class:fail={!testResult.ok}>
|
<span class="test" class:ok={testResult.ok} class:fail={!testResult.ok}>
|
||||||
@@ -1399,27 +1417,28 @@
|
|||||||
{:else}
|
{:else}
|
||||||
<div class="banner">
|
<div class="banner">
|
||||||
<AlertTriangle size={14} aria-hidden="true" />
|
<AlertTriangle size={14} aria-hidden="true" />
|
||||||
{form.kind} is a third-party cloud — uploading sends your data off your device to {form.kind}.
|
{t("settings.sync.third_party_banner", { kind: form.kind })}
|
||||||
</div>
|
</div>
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
<label class="wide">Name<input bind:value={form.name} placeholder={form.kind} /></label>
|
<label class="wide"
|
||||||
|
>{t("settings.sync.name")}<input
|
||||||
|
bind:value={form.name}
|
||||||
|
placeholder={form.kind}
|
||||||
|
/></label
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<button class="primary" onclick={linkOAuth}>Link {form.kind} account…</button>
|
<button class="primary" onclick={linkOAuth}
|
||||||
<span class="muted"
|
>{t("settings.sync.link_account", { kind: form.kind })}</button
|
||||||
>Opens an OAuth sign-in (loopback redirect); the token is stored in your OS credential
|
|
||||||
store.</span
|
|
||||||
>
|
>
|
||||||
|
<span class="muted">{t("settings.sync.oauth_hint")}</span>
|
||||||
</div>
|
</div>
|
||||||
{#if settings.linkMessage}
|
{#if settings.linkMessage}
|
||||||
<p class="muted">{settings.linkMessage}</p>
|
<p class="muted">{settings.linkMessage}</p>
|
||||||
{/if}
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<p class="muted footnote">
|
<p class="muted footnote">{t("settings.sync.footnote")}</p>
|
||||||
Credentials are never written to settings or the database — only the OS credential store.
|
|
||||||
TLS is required for non-LAN targets.
|
|
||||||
</p>
|
|
||||||
</section>
|
</section>
|
||||||
{:else if section === "ai"}
|
{:else if section === "ai"}
|
||||||
<section>
|
<section>
|
||||||
|
|||||||
Reference in new issue
Block a user