Files
mc-webui/app/static/js/sw.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

88 lines
3.3 KiB
JavaScript

const CACHE_NAME = 'mc-webui-v10';
const ASSETS_TO_CACHE = [
'/',
'/static/css/style.css',
'/static/js/app.js',
'/static/js/dm.js',
'/static/js/contacts.js',
'/static/js/message-utils.js',
'/static/js/filter-utils.js',
'/static/js/i18n-runtime.js',
'/static/js/datetime-utils.js',
'/static/js/console.js',
'/static/images/android-chrome-192x192.png',
'/static/images/android-chrome-512x512.png',
// Bootstrap 5.3.2 (local)
'/static/vendor/bootstrap/css/bootstrap.min.css',
'/static/vendor/bootstrap/js/bootstrap.bundle.min.js',
// Bootstrap Icons 1.11.2 (local)
'/static/vendor/bootstrap-icons/bootstrap-icons.css',
'/static/vendor/bootstrap-icons/fonts/bootstrap-icons.woff2',
'/static/vendor/bootstrap-icons/fonts/bootstrap-icons.woff',
// Emoji Picker Element 1.28.1 (local)
'/static/vendor/emoji-picker-element/index.js',
'/static/vendor/emoji-picker-element/picker.js',
'/static/vendor/emoji-picker-element/database.js',
'/static/vendor/emoji-picker-element-data/en/emojibase/data.json',
// Socket.IO client 4.x (local)
'/static/vendor/socket.io/socket.io.min.js',
// Console page
'/console'
];
// Install event - cache core assets
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME)
.then((cache) => cache.addAll(ASSETS_TO_CACHE))
.then(() => self.skipWaiting())
);
});
// Activate event - clean old caches
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then((cacheNames) => {
return Promise.all(
cacheNames
.filter((name) => name !== CACHE_NAME)
.map((name) => caches.delete(name))
);
}).then(() => self.clients.claim())
);
});
// Fetch event - hybrid strategy:
// - Cache-first for vendor libraries (static, unchanging)
// - Cache-first for translation catalogs (URL is content-hashed, so it cannot go stale)
// - Network-first for app content (dynamic, needs updates)
//
// Note: /i18n/<lang>.<hash>.js is deliberately NOT in ASSETS_TO_CACHE — the hashed
// filename isn't known when this file is written. It gets cached on first fetch instead.
self.addEventListener('fetch', (event) => {
const url = new URL(event.request.url);
// Cache-first for vendor libraries (Bootstrap, Icons) and translation catalogs
if (url.pathname.includes('/static/vendor/') || url.pathname.startsWith('/i18n/')) {
event.respondWith(
caches.match(event.request)
.then((cachedResponse) => {
return cachedResponse || fetch(event.request)
.then((response) => {
// Cache the fetched vendor file for future use
return caches.open(CACHE_NAME).then((cache) => {
cache.put(event.request, response.clone());
return response;
});
});
})
);
} else {
// Network-first for everything else (app code, API calls, dynamic content)
event.respondWith(
fetch(event.request)
.catch(() => caches.match(event.request))
);
}
});