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:
@@ -48,7 +48,7 @@ export function usePwaUpdate() {
|
|||||||
needRefresh: [needRefresh, setNeedRefresh],
|
needRefresh: [needRefresh, setNeedRefresh],
|
||||||
updateServiceWorker
|
updateServiceWorker
|
||||||
} = useRegisterSW({
|
} = useRegisterSW({
|
||||||
immediate: true,
|
immediate: !import.meta.env.DEV,
|
||||||
onNeedReload() {
|
onNeedReload() {
|
||||||
clearUpdateSuppression()
|
clearUpdateSuppression()
|
||||||
setNeedRefresh(false)
|
setNeedRefresh(false)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -1,19 +1,26 @@
|
|||||||
import i18n from 'i18next'
|
import i18n from 'i18next'
|
||||||
import { initReactI18next } from 'react-i18next'
|
import { initReactI18next } from 'react-i18next'
|
||||||
import LanguageDetector from 'i18next-browser-languagedetector'
|
import LanguageDetector from 'i18next-browser-languagedetector'
|
||||||
import enTranslation from './locales/en.json'
|
import enJson from './locales/en.json'
|
||||||
import deTranslation from './locales/de.json'
|
import deJson from './locales/de.json'
|
||||||
import { initSeo } from '../utils/seo.js'
|
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
|
i18n
|
||||||
.use(LanguageDetector)
|
.use(LanguageDetector)
|
||||||
.use(initReactI18next)
|
.use(initReactI18next)
|
||||||
.init({
|
.init({
|
||||||
resources: {
|
resources,
|
||||||
en: enTranslation,
|
defaultNS: 'translation',
|
||||||
de: deTranslation
|
|
||||||
},
|
|
||||||
fallbackLng: 'en',
|
fallbackLng: 'en',
|
||||||
|
supportedLngs: ['de', 'en'],
|
||||||
|
nonExplicitSupportedLngs: true,
|
||||||
|
load: 'languageOnly',
|
||||||
interpolation: {
|
interpolation: {
|
||||||
escapeValue: false // React already escapes values (prevents XSS)
|
escapeValue: false // React already escapes values (prevents XSS)
|
||||||
},
|
},
|
||||||
|
|||||||
+18
-3
@@ -3,14 +3,29 @@ import { createRoot } from 'react-dom/client'
|
|||||||
import 'leaflet/dist/leaflet.css'
|
import 'leaflet/dist/leaflet.css'
|
||||||
import './themes.css'
|
import './themes.css'
|
||||||
import './index.css'
|
import './index.css'
|
||||||
import App from './App.tsx'
|
|
||||||
import './i18n'
|
|
||||||
import { applyAppearanceToDocument } from './services/appearance.ts'
|
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)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function bootstrap(): Promise<void> {
|
||||||
|
await clearDevServiceWorkerCaches()
|
||||||
|
await import('./i18n')
|
||||||
|
const { default: App } = await import('./App.tsx')
|
||||||
|
applyAppearanceToDocument()
|
||||||
createRoot(document.getElementById('root')!).render(
|
createRoot(document.getElementById('root')!).render(
|
||||||
<StrictMode>
|
<StrictMode>
|
||||||
<App />
|
<App />
|
||||||
</StrictMode>,
|
</StrictMode>,
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
void bootstrap()
|
||||||
|
|||||||
@@ -47,6 +47,9 @@ export default defineConfig({
|
|||||||
srcDir: 'src',
|
srcDir: 'src',
|
||||||
filename: 'sw.ts',
|
filename: 'sw.ts',
|
||||||
registerType: 'prompt',
|
registerType: 'prompt',
|
||||||
|
devOptions: {
|
||||||
|
enabled: false
|
||||||
|
},
|
||||||
includeAssets: ['favicon.ico', 'logo.png'],
|
includeAssets: ['favicon.ico', 'logo.png'],
|
||||||
injectManifest: {
|
injectManifest: {
|
||||||
globPatterns: ['**/*.{js,css,html,ico,png,svg,woff2,webmanifest}']
|
globPatterns: ['**/*.{js,css,html,ico,png,svg,woff2,webmanifest}']
|
||||||
|
|||||||
Reference in New Issue
Block a user