fix(dev): veralteten PWA-Cache bereinigen, damit i18n-Labels laden

Stale Service-Worker-Precache konnte Vite-Module und Locale-Bundles
überlagern, sodass Kompass-Dial-Texte als Roh-i18n-Keys erschienen.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-05-31 11:25:10 +02:00
parent 3a7d244433
commit ee60d5fda3
5 changed files with 65 additions and 15 deletions
+1 -1
View File
@@ -48,7 +48,7 @@ export function usePwaUpdate() {
needRefresh: [needRefresh, setNeedRefresh],
updateServiceWorker
} = useRegisterSW({
immediate: true,
immediate: !import.meta.env.DEV,
onNeedReload() {
clearUpdateSuppression()
setNeedRefresh(false)
+25
View File
@@ -0,0 +1,25 @@
import { describe, expect, it } from 'vitest'
import deJson from './locales/de.json'
import enJson from './locales/en.json'
const resources = {
de: { translation: deJson.translation },
en: { translation: enJson.translation }
}
describe('course dial i18n keys', () => {
it.each([
'logs.event_course_section',
'logs.course_tab_mgk',
'logs.course_tab_rwk',
'logs.course_dial_hint',
'logs.course_step_fine',
'logs.wind_mode_cardinal'
])('resolves %s in de and en bundles', async (key) => {
const { default: i18n } = await import('i18next')
await i18n.init({ lng: 'de', resources, defaultNS: 'translation' })
expect(i18n.t(key)).not.toBe(key)
await i18n.changeLanguage('en')
expect(i18n.t(key)).not.toBe(key)
})
})
+13 -6
View File
@@ -1,19 +1,26 @@
import i18n from 'i18next'
import { initReactI18next } from 'react-i18next'
import LanguageDetector from 'i18next-browser-languagedetector'
import enTranslation from './locales/en.json'
import deTranslation from './locales/de.json'
import enJson from './locales/en.json'
import deJson from './locales/de.json'
import { initSeo } from '../utils/seo.js'
/** JSON files wrap strings in `translation` — register that namespace explicitly. */
const resources = {
en: { translation: enJson.translation },
de: { translation: deJson.translation }
}
i18n
.use(LanguageDetector)
.use(initReactI18next)
.init({
resources: {
en: enTranslation,
de: deTranslation
},
resources,
defaultNS: 'translation',
fallbackLng: 'en',
supportedLngs: ['de', 'en'],
nonExplicitSupportedLngs: true,
load: 'languageOnly',
interpolation: {
escapeValue: false // React already escapes values (prevents XSS)
},
+20 -5
View File
@@ -3,14 +3,29 @@ import { createRoot } from 'react-dom/client'
import 'leaflet/dist/leaflet.css'
import './themes.css'
import './index.css'
import App from './App.tsx'
import './i18n'
import { applyAppearanceToDocument } from './services/appearance.ts'
applyAppearanceToDocument()
/** Stale PWA precache on localhost can shadow Vite dev modules and old locale bundles. */
async function clearDevServiceWorkerCaches(): Promise<void> {
if (!import.meta.env.DEV || !('serviceWorker' in navigator)) return
const regs = await navigator.serviceWorker.getRegistrations()
await Promise.all(regs.map((r) => r.unregister()))
if ('caches' in window) {
const keys = await caches.keys()
await Promise.all(keys.map((k) => caches.delete(k)))
}
}
createRoot(document.getElementById('root')!).render(
async function bootstrap(): Promise<void> {
await clearDevServiceWorkerCaches()
await import('./i18n')
const { default: App } = await import('./App.tsx')
applyAppearanceToDocument()
createRoot(document.getElementById('root')!).render(
<StrictMode>
<App />
</StrictMode>,
)
)
}
void bootstrap()
+3
View File
@@ -47,6 +47,9 @@ export default defineConfig({
srcDir: 'src',
filename: 'sw.ts',
registerType: 'prompt',
devOptions: {
enabled: false
},
includeAssets: ['favicon.ico', 'logo.png'],
injectManifest: {
globPatterns: ['**/*.{js,css,html,ico,png,svg,woff2,webmanifest}']