Files
verdande/web/src/routes/etiket/[navn]/+page.svelte
T
Kristian WindandClaude Opus 5 4d8b70918b 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>
2026-08-18 09:46:08 +02:00

76 lines
1.6 KiB
Svelte

<script>
/**
* Everything carrying one label.
*
* This runs the filter language rather than a bespoke endpoint: "@indkøb" is
* already exactly this query, and having one code path means a label page and
* a saved filter can never disagree about what a label selects.
*/
import { page } from '$app/stores';
import { api } from '$lib/api.js';
import { app } from '$lib/stores.svelte.js';
import TaskRow from '$lib/components/TaskRow.svelte';
import { t } from '$lib/i18n.svelte.js';
let loaded = $state(false);
let name = $derived(decodeURIComponent($page.params.navn ?? ''));
$effect(() => {
const label = name;
if (!label) return;
loaded = false;
api
.previewFilter(`@${label}`)
.then((result) => {
app.tasks = result?.tasks ?? [];
})
.catch(() => {
app.tasks = [];
})
.finally(() => (loaded = true));
});
let open = $derived(app.tasks.filter((t) => !t.completed));
</script>
<div class="view">
<header><h1>@{name}</h1></header>
{#each open as task (task.id)}
<TaskRow {task} />
{/each}
{#if loaded && !open.length}
<p class="clear">
<span class="rune" aria-hidden="true">ᚹ</span>
{t('view.labelEmpty')}
</p>
{/if}
</div>
<style>
.view {
max-width: var(--content-max);
margin: 0 auto;
padding: var(--s6) var(--s4) var(--s8);
}
h1 {
font-size: var(--text-2xl);
margin-bottom: var(--s5);
}
.clear {
display: flex;
flex-direction: column;
align-items: center;
gap: var(--s3);
padding: var(--s8) var(--s4);
color: var(--ink-faint);
font-size: var(--text-sm);
}
.rune {
font-size: var(--text-2xl);
color: var(--accent);
opacity: 0.5;
}
</style>