feat(i18n): convert Settings AI section to t() (Batch E)

This commit is contained in:
iamdoubz committed 2026-07-12 12:35:42 -05:00
1 parent 377a91d1b8
commit a0ad1564a8
1 file changed
+58 -49
+58 -49
View File
@@ -1442,52 +1442,59 @@
</section> </section>
{:else if section === "ai"} {:else if section === "ai"}
<section> <section>
<h3>AI summary provider</h3> <h3>{t("settings.ai.title")}</h3>
<p class="muted"> <p class="muted">
Summaries run on a local LLM by default. Point this at Ollama on this PC or another {t("settings.ai.intro_1")} <code>192.168.0.x</code>) {t("settings.ai.intro_2")}
machine on your LAN (e.g. <code>192.168.0.x</code>) — both count as local, so nothing
leaves your network. Hosted providers (Anthropic) are optional, off by default, and send
the transcript to a third party once you turn one on.
</p> </p>
<label <label
>Provider >{t("settings.ai.provider")}
<select bind:value={llmProvider}> <select bind:value={llmProvider}>
<option value="off">Off</option> <option value="off">{t("settings.ai.off")}</option>
<option value="ollama">Ollama (local / LAN)</option> <option value="ollama">{t("settings.ai.provider_ollama")}</option>
<option value="custom">Custom (OpenAI-compatible)</option> <option value="custom">{t("settings.ai.provider_custom")}</option>
<option value="anthropic">Anthropic (Claude) — hosted, leaves this device</option> <option value="anthropic">{t("settings.ai.provider_anthropic")}</option>
</select> </select>
</label> </label>
{#if llmProvider !== "off"} {#if llmProvider !== "off"}
<div class="grid"> <div class="grid">
{#if llmProvider !== "anthropic"} {#if llmProvider !== "anthropic"}
<label class="wide" <label class="wide"
>Endpoint<input >{t("settings.ai.endpoint")}<input
bind:value={llmEndpoint} bind:value={llmEndpoint}
placeholder="http://192.168.0.42:11434" placeholder="http://192.168.0.42:11434"
/></label /></label
> >
<label>Model<input bind:value={llmModel} placeholder="llama3.1" /></label> <label
>{t("settings.ai.model")}<input
bind:value={llmModel}
placeholder="llama3.1"
/></label
>
{:else} {:else}
<label class="wide" <label class="wide"
>Model<input bind:value={llmModel} placeholder="claude-3-5-sonnet-latest" /></label >{t("settings.ai.model")}<input
bind:value={llmModel}
placeholder="claude-3-5-sonnet-latest"
/></label
> >
<label class="wide" <label class="wide"
>API key >{t("settings.ai.api_key")}
<div class="key-input"> <div class="key-input">
<input <input
type={showApiKey ? "text" : "password"} type={showApiKey ? "text" : "password"}
autocomplete="off" autocomplete="off"
bind:value={llmApiKey} bind:value={llmApiKey}
placeholder={settings.llmStatus?.provider === "anthropic" placeholder={settings.llmStatus?.provider === "anthropic"
? "•••••••••••••••• (already set — leave blank to keep it)" ? t("settings.ai.api_key_set_placeholder")
: "sk-ant-…"} : "sk-ant-…"}
/> />
<button <button
type="button" type="button"
class="icon-toggle" class="icon-toggle"
onclick={() => (showApiKey = !showApiKey)} onclick={() => (showApiKey = !showApiKey)}
aria-label={showApiKey ? "Hide API key" : "Show API key"} aria-label={showApiKey
? t("settings.ai.hide_api_key")
: t("settings.ai.show_api_key")}
> >
{#if showApiKey}<EyeOff size={14} aria-hidden="true" />{:else}<Eye {#if showApiKey}<EyeOff size={14} aria-hidden="true" />{:else}<Eye
size={14} size={14}
@@ -1501,26 +1508,27 @@
{#if llmProvider === "anthropic"} {#if llmProvider === "anthropic"}
<div class="banner hosted"> <div class="banner hosted">
<Globe size={14} aria-hidden="true" /> <Globe size={14} aria-hidden="true" />
Anthropic is a hosted, third-party service — this meeting's transcript leaves your device {t("settings.ai.anthropic_banner")}
when you generate a summary.
</div> </div>
{:else if llmEndpoint && !endpointIsLocalOrLan(llmEndpoint)} {:else if llmEndpoint && !endpointIsLocalOrLan(llmEndpoint)}
<div class="banner"> <div class="banner">
<AlertTriangle size={14} aria-hidden="true" /> <AlertTriangle size={14} aria-hidden="true" />
This endpoint isn't on your machine or LAN — your transcript would leave your network. {t("settings.ai.endpoint_banner")}
</div> </div>
{/if} {/if}
<div class="actions"> <div class="actions">
<button class="primary" onclick={onSaveLlmClick} disabled={settings.llmSaving}> <button class="primary" onclick={onSaveLlmClick} disabled={settings.llmSaving}>
{settings.llmSaving ? "Saving…" : "Save"} {settings.llmSaving ? t("settings.ai.saving") : t("settings.ai.save")}
</button> </button>
<button class="ghost" onclick={() => settings.loadLlmStatus()}> <button class="ghost" onclick={() => settings.loadLlmStatus()}>
<RefreshCw size={14} aria-hidden="true" /> Test connection <RefreshCw size={14} aria-hidden="true" />
{t("settings.ai.test_connection")}
</button> </button>
</div> </div>
{#if showHostedBanner} {#if showHostedBanner}
<HostedAiBanner <HostedAiBanner
providerLabel={HOSTED_PROVIDER_LABELS[llmProvider] ?? "this hosted provider"} providerLabel={HOSTED_PROVIDER_LABELS[llmProvider] ??
t("settings.ai.this_hosted_provider")}
onAccept={acceptHostedBannerAndSave} onAccept={acceptHostedBannerAndSave}
onCancel={() => (showHostedBanner = false)} onCancel={() => (showHostedBanner = false)}
/> />
@@ -1529,13 +1537,17 @@
{@const s = settings.llmStatus} {@const s = settings.llmStatus}
<div class="status-card"> <div class="status-card">
<span class="status-dot" class:ok={s.reachable} class:bad={!s.reachable}></span> <span class="status-dot" class:ok={s.reachable} class:bad={!s.reachable}></span>
<span class="status-text">{s.reachable ? "Reachable" : "Unreachable"}</span> <span class="status-text"
>{s.reachable ? t("settings.ai.reachable") : t("settings.ai.unreachable")}</span
>
{#if s.reachable} {#if s.reachable}
<span class="muted" <span class="muted"
>· {s.models.length} model{s.models.length === 1 ? "" : "s"}</span >{s.models.length === 1
? t("settings.ai.model_count_one", { n: s.models.length })
: t("settings.ai.model_count_many", { n: s.models.length })}</span
> >
<span class="muted" class:warn={!s.isLocal} <span class="muted" class:warn={!s.isLocal}
>· {s.isLocal ? "on your machine / LAN" : "leaves your network"}</span >{s.isLocal ? t("settings.ai.on_lan") : t("settings.ai.leaves_network")}</span
> >
{/if} {/if}
</div> </div>
@@ -1554,7 +1566,7 @@
<span class="param-name">{opt.label}</span> <span class="param-name">{opt.label}</span>
<span class="param-help" <span class="param-help"
>{opt.help}{#if opt.default !== undefined} >{opt.help}{#if opt.default !== undefined}
Default {opt.default}.{/if}</span {t("settings.ai.default_value", { value: String(opt.default) })}{/if}</span
> >
</div> </div>
<div class="param-control"> <div class="param-control">
@@ -1594,8 +1606,8 @@
<button <button
class="reset-field" class="reset-field"
class:hidden={!isOverridden(opt)} class:hidden={!isOverridden(opt)}
title="Reset to default" title={t("settings.ai.reset_default")}
aria-label="Reset {opt.label}" aria-label={t("settings.ai.reset_field", { name: opt.label })}
onclick={() => commit(opt.key, undefined)} onclick={() => commit(opt.key, undefined)}
> >
<RotateCcw size={13} aria-hidden="true" /> <RotateCcw size={13} aria-hidden="true" />
@@ -1607,29 +1619,27 @@
<details class="advanced"> <details class="advanced">
<summary> <summary>
<ChevronRight size={15} aria-hidden="true" class="chevron" /> <ChevronRight size={15} aria-hidden="true" class="chevron" />
<span>Advanced Ollama configuration</span> <span>{t("settings.ai.advanced_title")}</span>
{#if advChangedCount}<span class="changed-badge">{advChangedCount} changed</span {#if advChangedCount}<span class="changed-badge"
>{t("settings.ai.changed", { n: advChangedCount })}</span
>{/if} >{/if}
</summary> </summary>
<div class="adv-body"> <div class="adv-body">
{#if advChangedCount} {#if advChangedCount}
<button class="link reset-all" onclick={resetAllAdvanced} <button class="link reset-all" onclick={resetAllAdvanced}
>Reset all to defaults</button >{t("settings.ai.reset_all")}</button
> >
{/if} {/if}
<label class="wide adv-system" <label class="wide adv-system"
>System prompt >{t("settings.ai.system_prompt")}
<textarea <textarea
rows="3" rows="3"
bind:value={advSystem} bind:value={advSystem}
placeholder="e.g. You are a concise meeting summarizer." placeholder={t("settings.ai.system_prompt_placeholder")}
></textarea> ></textarea>
</label> </label>
<p class="muted small"> <p class="muted small">{t("settings.ai.system_prompt_hint")}</p>
Added before WhispAssist's required output format, so your instructions can't
break summary/action-item parsing.
</p>
{#each OPTION_GROUPS as g (g)} {#each OPTION_GROUPS as g (g)}
<fieldset class="adv-group"> <fieldset class="adv-group">
@@ -1637,21 +1647,19 @@
{#if g === "context"} {#if g === "context"}
<div class="param"> <div class="param">
<div class="param-label"> <div class="param-label">
<span class="param-name">Think</span> <span class="param-name">{t("settings.ai.think")}</span>
<span class="param-help">Reasoning effort (reasoning models only).</span> <span class="param-help">{t("settings.ai.think_help")}</span>
</div> </div>
<div class="param-control"> <div class="param-control">
<select bind:value={advThink}> <select bind:value={advThink}>
{#each THINK_OPTIONS as t (t)}<option value={t}>{t}</option>{/each} {#each THINK_OPTIONS as th (th)}<option value={th}>{th}</option>{/each}
</select> </select>
</div> </div>
</div> </div>
<div class="param"> <div class="param">
<div class="param-label"> <div class="param-label">
<span class="param-name">Keep alive</span> <span class="param-name">{t("settings.ai.keep_alive")}</span>
<span class="param-help" <span class="param-help">{t("settings.ai.keep_alive_help")}</span>
>How long the model stays in RAM. e.g. 5m, 1h, 0 (unload), -1 (forever).</span
>
</div> </div>
<div class="param-control"> <div class="param-control">
<input bind:value={advKeepAlive} placeholder="5m" /> <input bind:value={advKeepAlive} placeholder="5m" />
@@ -1667,8 +1675,8 @@
<details class="adv-nested"> <details class="adv-nested">
<summary> <summary>
<ChevronRight size={14} aria-hidden="true" class="chevron" /> <ChevronRight size={14} aria-hidden="true" class="chevron" />
<span>Runtime &amp; hardware</span> <span>{t("settings.ai.runtime_hardware")}</span>
<span class="muted small">— rarely needed</span> <span class="muted small">{t("settings.ai.rarely_needed")}</span>
</summary> </summary>
<fieldset class="adv-group"> <fieldset class="adv-group">
{#each OLLAMA_OPTIONS.filter((o) => o.group === "hardware") as opt (opt.key)} {#each OLLAMA_OPTIONS.filter((o) => o.group === "hardware") as opt (opt.key)}
@@ -1679,10 +1687,10 @@
<div class="actions"> <div class="actions">
<button class="primary" onclick={saveAdvanced} disabled={advSaving}> <button class="primary" onclick={saveAdvanced} disabled={advSaving}>
{advSaving ? "Saving…" : "Save advanced"} {advSaving ? t("settings.ai.saving") : t("settings.ai.save_advanced")}
</button> </button>
{#if advSaved}<span class="test ok" {#if advSaved}<span class="test ok"
><Check size={14} aria-hidden="true" /> Saved</span ><Check size={14} aria-hidden="true" /> {t("settings.ai.saved")}</span
>{/if} >{/if}
</div> </div>
</div> </div>
@@ -1690,7 +1698,8 @@
{/if} {/if}
{:else} {:else}
<div class="actions"> <div class="actions">
<button class="primary" onclick={saveLlm} disabled={settings.llmSaving}>Turn off</button <button class="primary" onclick={saveLlm} disabled={settings.llmSaving}
>{t("settings.ai.turn_off")}</button
> >
</div> </div>
{/if} {/if}