feat(i18n): the interface speaks both languages
The account has had a `locale` since the beginning and it only chose which grammar quick add read a line with — so picking English changed nothing anybody could see, and the field said so honestly: "Sprog i hurtig tilføjelse". It says "Sprog" now, because it means it. Every component goes through `t()`. Danish is the source: a key missing from English falls through to Danish rather than to the key, because a Danish sentence in an English screen can be read and `settings.notifications.title` cannot. `tag()` is the single place `Intl` is asked. Before, `'da-DK'` was written into a dozen components, and that is the half of a translation that gets left behind — an English interface still saying "24. aug." is exactly the shape of a job half done. There is a smoke test that switches to English and refuses to see a Danish month name in the calendar's heading. Colour and theme names are keys now. `colors.js` keeps its ids untouched — they are what the database stores, and `internal/store` reads them out of that file — but `name` is `'color.tomato'`, resolved where it is drawn. The Inbox keeps its name. It is a project row the server creates at signup, not a string in the interface, and it can be renamed like any other project. Four copies of "3 hours ago" became one, in `web/src/lib/when.js`. They had already drifted: one said "aldrig logget ind", another "aldrig", and two stopped at days where the others carried on to a date. Routes stay Danish. `/indstillinger`, `/projekt`, `/uddelegeret` are bookmarks and they are in every smoke test; translating them buys nothing and costs both. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
e3c7fc792a
commit
4d8b70918b
@@ -2,6 +2,7 @@
|
||||
/** The AI provider: which one, which model, and the key. */
|
||||
import { api, humanMessage } from '$lib/api.js';
|
||||
import { app } from '$lib/stores.svelte.js';
|
||||
import { t } from '$lib/i18n.svelte.js';
|
||||
|
||||
let settings = $state(null);
|
||||
let apiKey = $state('');
|
||||
@@ -13,7 +14,7 @@
|
||||
let summarising = $state(false);
|
||||
|
||||
const PROVIDERS = [
|
||||
{ value: '', label: 'Slået fra' },
|
||||
{ value: '', label: t('ai.off') },
|
||||
{ value: 'anthropic', label: 'Anthropic' },
|
||||
{ value: 'openai', label: 'OpenAI' },
|
||||
{ value: 'google', label: 'Google' },
|
||||
@@ -76,11 +77,9 @@
|
||||
|
||||
<section class="panel">
|
||||
<header>
|
||||
<h2>AI</h2>
|
||||
<h2>{t('ai.title')}</h2>
|
||||
<p class="hint">
|
||||
Bruges to steder: at dele en opgave op i undertasks, og at skrive et kort
|
||||
overblik over det, der er udestående. Nøglen ligger på din server og sendes
|
||||
kun til den udbyder, du vælger her.
|
||||
{t('ai.hint')}
|
||||
</p>
|
||||
</header>
|
||||
|
||||
@@ -89,7 +88,7 @@
|
||||
{:else}
|
||||
<form onsubmit={save}>
|
||||
<div class="field">
|
||||
<label for="provider">Udbyder</label>
|
||||
<label for="provider">{t('ai.provider')}</label>
|
||||
<select id="provider" bind:value={settings.provider}>
|
||||
{#each PROVIDERS as provider (provider.value)}
|
||||
<option value={provider.value}>{provider.label}</option>
|
||||
@@ -100,7 +99,7 @@
|
||||
|
||||
{#if settings.provider}
|
||||
<div class="field">
|
||||
<label for="model">Model</label>
|
||||
<label for="model">{t('ai.model')}</label>
|
||||
<input
|
||||
id="model"
|
||||
bind:value={settings.model}
|
||||
@@ -110,7 +109,7 @@
|
||||
|
||||
{#if settings.provider === 'compatible'}
|
||||
<div class="field">
|
||||
<label for="base">Adresse</label>
|
||||
<label for="base">{t('ai.baseURL')}</label>
|
||||
<input
|
||||
id="base"
|
||||
class="mono"
|
||||
@@ -121,29 +120,27 @@
|
||||
{/if}
|
||||
|
||||
<div class="field">
|
||||
<label for="key">API-nøgle</label>
|
||||
<label for="key">{t('ai.apiKey')}</label>
|
||||
<input
|
||||
id="key"
|
||||
type="password"
|
||||
autocomplete="off"
|
||||
bind:value={apiKey}
|
||||
placeholder={settings.has_key ? 'Der er gemt en nøgle — lad feltet stå tomt' : ''}
|
||||
placeholder={settings.has_key ? t('ai.keyStored') : ''}
|
||||
/>
|
||||
<p class="hint">
|
||||
{#if settings.has_key}
|
||||
Feltet er tomt med vilje. Nøglen sendes aldrig tilbage til browseren
|
||||
— en indstillingsside, der fylder et kodeordsfelt ud igen, er en side,
|
||||
der en dag lækker en nøgle ud i et skærmbillede.
|
||||
{t('ai.keyBlankHint')}
|
||||
{:else}
|
||||
Gemmes på serveren og vises aldrig igen.
|
||||
{t('ai.keyNewHint')}
|
||||
{/if}
|
||||
</p>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="row">
|
||||
<button class="primary" type="submit" disabled={saving}>Gem</button>
|
||||
{#if saved}<span class="saved">Gemt.</span>{/if}
|
||||
<button class="primary" type="submit" disabled={saving}>{t('ai.save')}</button>
|
||||
{#if saved}<span class="saved">{t('ai.saved')}</span>{/if}
|
||||
</div>
|
||||
</form>
|
||||
{/if}
|
||||
@@ -152,8 +149,8 @@
|
||||
{#if settings?.provider && settings?.has_key}
|
||||
<section class="panel">
|
||||
<header>
|
||||
<h2>Ugentligt overblik</h2>
|
||||
<p class="hint">En kort note om, hvad der ser vigtigst ud lige nu.</p>
|
||||
<h2>{t('ai.weekly')}</h2>
|
||||
<p class="hint">{t('ai.weeklyHint')}</p>
|
||||
</header>
|
||||
|
||||
<div class="row">
|
||||
|
||||
Reference in New Issue
Block a user