Files
verdande/web/src/lib/when.js
T
Kristian WindandClaude Opus 5 dce8800f93 fix(web): otte steder hvor grænsefladen blev ved med at være dansk
Skærmbillederne skulle tages på engelsk, og det var dét, der fandt dem:
en konto sat til engelsk fik stadig "Forsinket" over de forsinkede opgaver,
"Uden sektion" over den første kolonne på tavlen, "Aldrig" i gentagelses-
menuen, "hver dag" på rækken, "til instansen · inviteret af" på invitationen,
"3 projekter" på kontoen, og hele historikken — nitten hændelser, alle på
dansk. En funktion, der kun virker på det ene sprog, er en funktion, der er i
stykker.

Gentagelsen er den eneste, der ikke bare manglede en nøgle. Serveren skriver
`recurrence_text` på dansk og skal blive ved med det: én besked går over
WebSocket til alle i projektet på én gang, og de læser ikke det samme sprog.
Så de regler, der har et navn, får det i when.js — på den side, der ved, hvis
skærm det er — og alt mere specifikt falder tilbage til serverens ord.

Mellemrummet i historikken er skrevet ud. Svelte fjerner det, der åbner en
blok, så "inviterede" og adressen løb sammen til ét ord.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-20 07:23:42 +02:00

81 lines
3.1 KiB
JavaScript

/**
* How long ago something was, in the account's language.
*
* Four pages had a copy of this — the user list, the error log, the history page
* and the backup panel — and they had already drifted: one said "aldrig logget
* ind", another "aldrig", and two of them stopped at days while the others carried
* on to a date. Four copies is three chances for the fifth one to be different
* again.
*
* Written by hand rather than with `Intl.RelativeTimeFormat`, which would say
* "3 hours ago" and "for 3 timer siden" correctly and cannot say "lige nu" — and
* "just now" is the answer for most of what these lists show.
*/
import { t, tag } from './i18n.svelte.js';
export function ago(iso, { never = 'when.never' } = {}) {
if (!iso) return t(never);
const then = new Date(iso);
const seconds = Math.round((Date.now() - then) / 1000);
if (seconds < 60) return t('when.justNow');
if (seconds < 3600) return t('when.minutes', { n: Math.floor(seconds / 60) });
if (seconds < 86400) return t('when.hours', { n: Math.floor(seconds / 3600) });
if (seconds < 604800) return t('when.days', { n: Math.floor(seconds / 86400) });
return then.toLocaleDateString(tag(), { day: 'numeric', month: 'short', year: 'numeric' });
}
/** A date and a time, for a list where the time of day matters. */
export function stamp(iso) {
return new Date(iso).toLocaleString(tag(), {
day: 'numeric',
month: 'short',
hour: '2-digit',
minute: '2-digit'
});
}
/** A day, without the year — for something inside the next few months. */
export function shortDate(iso) {
return new Date(iso).toLocaleDateString(tag(), { day: 'numeric', month: 'short' });
}
/** A day with the year, for something that could be any distance away. */
export function fullDate(iso) {
return new Date(iso).toLocaleDateString(tag(), {
day: 'numeric',
month: 'short',
year: 'numeric'
});
}
/**
* The repeat rules worth a menu entry, and what to call them.
*
* Here rather than in the drawer that draws the menu, because the row draws the
* same rule as a badge and the two had already disagreed: the menu said "Hver
* dag" in both languages, and the badge said whatever the server said.
*
* The server sends `recurrence_text` with every task and sends it in Danish. It
* has to: one payload goes over the WebSocket to everybody in the project at
* once, and they do not all read the same language. So the rules that have a name
* are named here, on the side that knows whose screen this is, and anything more
* specific — "hver anden tirsdag", typed into the title — falls back to what the
* server wrote.
*/
export const REPEATS = [
{ rule: '', label: 'detail.never' },
{ rule: 'FREQ=DAILY', label: 'detail.daily' },
{ rule: 'FREQ=WEEKLY', label: 'detail.weekly' },
{ rule: 'FREQ=WEEKLY;INTERVAL=2', label: 'detail.fortnightly' },
{ rule: 'FREQ=MONTHLY', label: 'detail.monthly' },
{ rule: 'FREQ=YEARLY', label: 'detail.yearly' }
];
/** What a repeating task says on its row. */
export function repeatLabel(rule, fallback = '') {
const known = REPEATS.find((option) => option.rule && option.rule === rule);
return known ? t(known.label) : fallback;
}