feat(confirm): centralize confirmation dialogs and add i18n
Introduce a generic ConfirmDialog and a promise-based requestConfirmation API in App.svelte so callers can await user responses instead of using window.confirm. Provide helper builders (branchDeleteConfirmRequest, discardConfirmRequest) to create dialog content for common cases. Many call sites were switched to use requestConfirmation and now render the in-app ConfirmDialog; the previous specialized confirm components (BranchDeleteConfirmDialog, DiscardConfirmDialog) were removed. Add lightweight i18n support (setLanguage, t()) and new messages/i18n modules, and replace hardcoded English strings in several components (e.g. AiSettingsPage, BlameDialog and many confirmation prompts) with translated keys. Summary of effects: - Replaces native window.confirm with awaitable in-app ConfirmDialog dialogs. - Centralizes confirmation UI and content construction in App.svelte. - Adds i18n plumbing and updates UI text to use t(). - Removes two specialized confirm dialog components and adds src/lib/components/ConfirmDialog.svelte.
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
import { Bot, Eye, EyeOff, Globe, Key } from "@lucide/svelte";
|
||||
import { credDelete, credLoad, credSave } from "../git";
|
||||
import type { AiSettings, CommitAiProvider } from "../types";
|
||||
import { t } from "../i18n.svelte";
|
||||
|
||||
interface Props {
|
||||
settings: AiSettings;
|
||||
@@ -81,7 +82,7 @@
|
||||
|
||||
async function persistKey(target: CloudProvider, value: string) {
|
||||
if (value === originalKeys[target]) return;
|
||||
if (!keysLoaded) throw new Error("API keys could not be loaded. Existing credentials have been preserved.");
|
||||
if (!keysLoaded) throw new Error(t("ai.keysNotLoaded"));
|
||||
const key = CRED_KEYS[target];
|
||||
const trimmed = value.trim();
|
||||
if (trimmed) {
|
||||
@@ -92,7 +93,7 @@
|
||||
}
|
||||
|
||||
export async function saveSettings(): Promise<AiSettings> {
|
||||
if (loadingKeys) throw new Error("Please wait for AI settings to load.");
|
||||
if (loadingKeys) throw new Error(t("ai.waitForSettings"));
|
||||
saving = true;
|
||||
error = "";
|
||||
try {
|
||||
@@ -119,7 +120,7 @@
|
||||
</script>
|
||||
|
||||
<div class="ai-settings-form">
|
||||
<div class="ai-provider-options" role="radiogroup" aria-label="AI provider">
|
||||
<div class="ai-provider-options" role="radiogroup" aria-label={t("ai.providerLabel")}>
|
||||
<button type="button" class="ai-provider-option" class:active={provider === "openai"} onclick={() => { provider = "openai"; }}>
|
||||
<Bot size={16} aria-hidden="true" />
|
||||
OpenAI
|
||||
@@ -130,17 +131,17 @@
|
||||
</button>
|
||||
<button type="button" class="ai-provider-option" class:active={provider === "custom"} onclick={() => { provider = "custom"; }}>
|
||||
<Globe size={16} aria-hidden="true" />
|
||||
Custom endpoint
|
||||
{t("ai.custom")}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{#if provider === "openai"}
|
||||
<label class="cred-field">
|
||||
<span class="cred-field-label">Model</span>
|
||||
<span class="cred-field-label">{t("ai.model")}</span>
|
||||
<input type="text" bind:value={openaiModel} placeholder="gpt-4o-mini" autocomplete="off" spellcheck="false" />
|
||||
</label>
|
||||
<div class="cred-field">
|
||||
<span class="cred-field-label">API key</span>
|
||||
<span class="cred-field-label">{t("ai.apiKey")}</span>
|
||||
<div class="cred-input">
|
||||
<Key size={15} class="cred-field-icon" aria-hidden="true" />
|
||||
<input
|
||||
@@ -158,11 +159,11 @@
|
||||
</div>
|
||||
{:else if provider === "anthropic"}
|
||||
<label class="cred-field">
|
||||
<span class="cred-field-label">Model</span>
|
||||
<span class="cred-field-label">{t("ai.model")}</span>
|
||||
<input type="text" bind:value={anthropicModel} placeholder="claude-3-5-haiku-latest" autocomplete="off" spellcheck="false" />
|
||||
</label>
|
||||
<div class="cred-field">
|
||||
<span class="cred-field-label">API key</span>
|
||||
<span class="cred-field-label">{t("ai.apiKey")}</span>
|
||||
<div class="cred-input">
|
||||
<Key size={15} class="cred-field-icon" aria-hidden="true" />
|
||||
<input
|
||||
@@ -180,21 +181,21 @@
|
||||
</div>
|
||||
{:else}
|
||||
<label class="cred-field">
|
||||
<span class="cred-field-label">Endpoint URL</span>
|
||||
<span class="cred-field-label">{t("ai.endpointUrl")}</span>
|
||||
<input type="text" bind:value={customBaseUrl} placeholder="http://localhost:11434/v1" autocomplete="off" spellcheck="false" />
|
||||
</label>
|
||||
<label class="cred-field">
|
||||
<span class="cred-field-label">Model</span>
|
||||
<span class="cred-field-label">{t("ai.model")}</span>
|
||||
<input type="text" bind:value={customModel} placeholder="llama3.1" autocomplete="off" spellcheck="false" />
|
||||
</label>
|
||||
<div class="cred-field">
|
||||
<span class="cred-field-label">API key (optional)</span>
|
||||
<span class="cred-field-label">{t("ai.apiKeyOptional")}</span>
|
||||
<div class="cred-input">
|
||||
<Key size={15} class="cred-field-icon" aria-hidden="true" />
|
||||
<input
|
||||
type={showKey ? "text" : "password"}
|
||||
bind:value={customApiKey}
|
||||
placeholder="Optional"
|
||||
placeholder={t("ai.optional")}
|
||||
autocomplete="off"
|
||||
spellcheck="false"
|
||||
disabled={loadingKeys}
|
||||
@@ -206,7 +207,7 @@
|
||||
</div>
|
||||
<div class="cred-token-hint">
|
||||
<Globe size={13} aria-hidden="true" />
|
||||
<span>For local OpenAI-compatible servers like Ollama or LM Studio. The base URL should end in /v1.</span>
|
||||
<span>{t("ai.customHint")}</span>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user