From 6a725dde58c7e3231fe9f3e3093072eed75f1608 Mon Sep 17 00:00:00 2001 From: Linus Rath <139418639+rathlinus@users.noreply.github.com> Date: Wed, 8 Apr 2026 22:42:15 +0200 Subject: [PATCH] feat: add Latvian (lv) locale support --- components/providers/intl-provider.tsx | 2 + components/service-worker-registration.tsx | 34 ++++++++--- components/ui/language-switcher.tsx | 1 + i18n/request.ts | 3 + i18n/routing.ts | 2 +- public/sw.js | 68 +++++----------------- 6 files changed, 48 insertions(+), 62 deletions(-) diff --git a/components/providers/intl-provider.tsx b/components/providers/intl-provider.tsx index a8dbc449..a6c80b20 100644 --- a/components/providers/intl-provider.tsx +++ b/components/providers/intl-provider.tsx @@ -10,6 +10,7 @@ import koMessages from '@/locales/ko/common.json'; import esMessages from '@/locales/es/common.json'; import itMessages from '@/locales/it/common.json'; import deMessages from '@/locales/de/common.json'; +import lvMessages from '@/locales/lv/common.json'; import nlMessages from '@/locales/nl/common.json'; import plMessages from '@/locales/pl/common.json'; import ptMessages from '@/locales/pt/common.json'; @@ -25,6 +26,7 @@ const ALL_MESSAGES = { es: esMessages, it: itMessages, de: deMessages, + lv: lvMessages, nl: nlMessages, pl: plMessages, pt: ptMessages, diff --git a/components/service-worker-registration.tsx b/components/service-worker-registration.tsx index 92724ddb..0258a019 100644 --- a/components/service-worker-registration.tsx +++ b/components/service-worker-registration.tsx @@ -4,16 +4,32 @@ import { useEffect } from "react"; export function ServiceWorkerRegistration() { useEffect(() => { - if (typeof window !== "undefined" && "serviceWorker" in navigator) { - navigator.serviceWorker - .register("/sw.js") - .then((registration) => { - console.log("Service Worker registered successfully:", registration); - }) - .catch((error) => { - console.error("Service Worker registration failed:", error); - }); + if (typeof window === "undefined" || !("serviceWorker" in navigator)) { + return; } + + if (process.env.NODE_ENV !== "production") { + // In development, unregister any previously installed service worker + // and clear its caches so hot reload always serves fresh assets. + navigator.serviceWorker.getRegistrations().then((registrations) => { + registrations.forEach((registration) => registration.unregister()); + }); + if ("caches" in window) { + caches.keys().then((keys) => { + keys.forEach((key) => caches.delete(key)); + }); + } + return; + } + + navigator.serviceWorker + .register("/sw.js") + .then((registration) => { + console.log("Service Worker registered successfully:", registration); + }) + .catch((error) => { + console.error("Service Worker registration failed:", error); + }); }, []); return null; diff --git a/components/ui/language-switcher.tsx b/components/ui/language-switcher.tsx index 449bd36b..34c1811f 100644 --- a/components/ui/language-switcher.tsx +++ b/components/ui/language-switcher.tsx @@ -16,6 +16,7 @@ export function LanguageSwitcher({ className }: { className?: string }) { { value: 'es', label: 'Español' }, { value: 'it', label: 'Italiano' }, { value: 'de', label: 'Deutsch' }, + { value: 'lv', label: 'Latviešu' }, { value: 'nl', label: 'Nederlands' }, { value: 'pl', label: 'Polski' }, { value: 'pt', label: 'Português' }, diff --git a/i18n/request.ts b/i18n/request.ts index 61d71f41..cd761ff8 100644 --- a/i18n/request.ts +++ b/i18n/request.ts @@ -29,6 +29,9 @@ export default getRequestConfig(async ({ requestLocale }) => { case 'ko': messages = (await import('../locales/ko/common.json')).default; break; + case 'lv': + messages = (await import('../locales/lv/common.json')).default; + break; case 'nl': messages = (await import('../locales/nl/common.json')).default; break; diff --git a/i18n/routing.ts b/i18n/routing.ts index 84ea1712..e872638d 100644 --- a/i18n/routing.ts +++ b/i18n/routing.ts @@ -1,7 +1,7 @@ import { defineRouting } from 'next-intl/routing'; export const routing = defineRouting({ - locales: ['en', 'fr', 'de', 'es', 'it', 'ja', 'ko', 'nl', 'pl', 'pt', 'ru', 'zh'], + locales: ['en', 'fr', 'de', 'es', 'it', 'ja', 'ko', 'lv', 'nl', 'pl', 'pt', 'ru', 'zh'], defaultLocale: 'en', localePrefix: 'never' }); diff --git a/public/sw.js b/public/sw.js index f338bd02..fdfe53d8 100644 --- a/public/sw.js +++ b/public/sw.js @@ -1,62 +1,26 @@ /* eslint-disable no-undef */ -const CACHE_NAME = "bulwark-v1"; -const STATIC_ASSETS = ["/", "/manifest.json"]; +// Self-destructing service worker. +// +// The previous version of this file used a cache-first strategy with no +// dev-mode guard, which pinned stale JS/HTML chunks until a hard reload. +// This replacement unregisters itself and wipes all caches as soon as the +// browser picks it up. Browsers re-fetch sw.js on every navigation to check +// for updates, so any client running the old worker will swap to this one +// on their next page load and then lose the worker entirely. -self.addEventListener("install", (event) => { - event.waitUntil( - caches.open(CACHE_NAME).then((cache) => { - return cache.addAll(STATIC_ASSETS); - }) - ); +self.addEventListener("install", () => { self.skipWaiting(); }); self.addEventListener("activate", (event) => { event.waitUntil( - caches.keys().then((cacheNames) => { - return Promise.all( - cacheNames - .filter((cacheName) => cacheName !== CACHE_NAME) - .map((cacheName) => caches.delete(cacheName)) - ); - }) - ); - self.clients.claim(); -}); - -self.addEventListener("fetch", (event) => { - // Skip non-GET requests - if (event.request.method !== "GET") { - return; - } - - // Skip API requests and external requests - if ( - event.request.url.includes("/api/") || - !event.request.url.startsWith(self.location.origin) - ) { - return; - } - - event.respondWith( - caches.match(event.request).then((response) => { - if (response) { - return response; - } - - return fetch(event.request).then((response) => { - if (!response || response.status !== 200 || response.type === "error") { - return response; - } - - const responseToCache = response.clone(); - caches.open(CACHE_NAME).then((cache) => { - cache.put(event.request, responseToCache); - }); - - return response; - }); - }) + (async () => { + const cacheNames = await caches.keys(); + await Promise.all(cacheNames.map((name) => caches.delete(name))); + await self.registration.unregister(); + const clients = await self.clients.matchAll({ type: "window" }); + clients.forEach((client) => client.navigate(client.url)); + })() ); });