Files
mc-webui/app/static/js/datetime-utils.js
T
MarekWoandClaude Opus 5 4e8292bd03 refactor(i18n): consolidate date/time formatting (stage 0b)
Prerequisite for string extraction. formatTime() existed twice with subtly
different behaviour, and both hardcoded 'Yesterday ' — a string that would have
survived a naive extraction pass and stayed English forever.

app/static/js/datetime-utils.js now owns the today/yesterday/older logic, with
options for the two variants that actually differed: app.js needs an absolute
date in archive views, dm.js needs the short "5 Aug" form because DM rows are
narrower. Both keep a thin local formatTime() wrapper, so their ~15 call sites
are untouched. formatTimeAgo() moves over as-is (it only ever existed in app.js).

repeater-manage.js's fmtInt() no longer forces toLocaleString('en-US'), so
thousands separators follow the reader's locale. Kept as a function declaration
rather than a const alias so it stays hoisted, like the code it replaced.

Policy, documented in the module header: numeric formatting follows the BROWSER
locale, only the words are translated. Tying the clock to the UI language would
flip a Polish operator to "09:53 AM" the moment they switched the interface to
English, and mesh operators want 24-hour time whatever language the menus are in.

First five real catalog entries: common.yesterday, just_now, minutes_ago,
hours_ago, days_ago. days_ago exercises the plural machinery — Polish needs
"1 dzień temu" / "3 dni temu" / "5 dni temu" where English has one form.

Verified in the browser in both languages: all four helpers global, both
formatTime wrappers still routing through the shared core, Polish plural
categories correct, and the clock and number formats provably unchanged by the
UI language. Live chat timestamps render identically to before.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 17:42:34 +02:00

82 lines
2.8 KiB
JavaScript

/**
* Shared date/time and number formatting.
*
* Policy: numeric formatting follows the BROWSER locale, only the words are translated.
*
* That split is deliberate. Tying the clock to the UI language would flip a Polish
* operator to "09:53 AM" the moment they switched the interface to English, and mesh
* operators want 24-hour time regardless of what language the menus are in. The browser
* locale already reflects how the user wants dates and numbers written, so leave it
* alone and translate only "Yesterday", "just now" and friends.
*
* Loaded via _head_i18n.html on every page, after the translation runtime.
*/
/** HH:MM in the browser's locale. */
function formatClock(date) {
return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
}
/**
* Format a unix timestamp as today / yesterday / an older date.
*
* @param {number} timestamp - unix seconds
* @param {Object} [opts]
* @param {boolean} [opts.absolute] - always show the full date, never today/yesterday
* @param {'full'|'short'} [opts.dateStyle] - how older dates are written
* @returns {string}
*/
function formatTimestamp(timestamp, opts) {
if (!timestamp) return '';
const { absolute = false, dateStyle = 'full' } = opts || {};
const date = new Date(timestamp * 1000);
const longDate = () => (dateStyle === 'short'
? date.toLocaleDateString([], { month: 'short', day: 'numeric' })
: date.toLocaleDateString());
if (absolute) return `${longDate()} ${formatClock(date)}`;
const now = new Date();
const yesterday = new Date(now);
yesterday.setDate(yesterday.getDate() - 1);
if (date.toDateString() === now.toDateString()) {
return formatClock(date);
}
if (date.toDateString() === yesterday.toDateString()) {
return `${t('common.yesterday')} ${formatClock(date)}`;
}
return `${longDate()} ${formatClock(date)}`;
}
/**
* Format a unix timestamp as relative time ("5 min ago", "2h ago").
* @param {number} timestamp - unix seconds
* @returns {string}
*/
function formatTimeAgo(timestamp) {
const diff = Math.floor(Date.now() / 1000) - timestamp;
if (diff < 60) return t('common.just_now');
if (diff < 3600) return t('common.minutes_ago', { count: Math.floor(diff / 60) });
if (diff < 86400) return t('common.hours_ago', { count: Math.floor(diff / 3600) });
if (diff < 604800) return tn('common.days_ago', Math.floor(diff / 86400));
return new Date(timestamp * 1000).toLocaleDateString();
}
/**
* Thousands-separated integer in the browser's locale, em dash for missing values.
* @param {number|null} n
* @returns {string}
*/
function formatInt(n) {
if (n == null || isNaN(n)) return '—';
return Number(n).toLocaleString();
}
window.formatClock = formatClock;
window.formatTimestamp = formatTimestamp;
window.formatTimeAgo = formatTimeAgo;
window.formatInt = formatInt;