diff --git a/frontend/src/app.vue b/frontend/src/app.vue
index 169fb113aa7..c635c845073 100644
--- a/frontend/src/app.vue
+++ b/frontend/src/app.vue
@@ -15,6 +15,7 @@ import { useLayout } from "~/composables/use-layout"
import { useDarkMode } from "~/composables/use-dark-mode"
import VSkipToContentButton from "~/components/VSkipToContentButton.vue"
+import LanguageRedirectBanner from '~/components/LanguageRedirectBanner.vue'
const config = useRuntimeConfig()
@@ -85,6 +86,7 @@ onMounted(() => {
+
diff --git a/frontend/src/components/LanguageRedirectBanner.vue b/frontend/src/components/LanguageRedirectBanner.vue
new file mode 100755
index 00000000000..389d74883cb
--- /dev/null
+++ b/frontend/src/components/LanguageRedirectBanner.vue
@@ -0,0 +1,43 @@
+
+
+
+
+
+ We don’t have translations for
+ {{ unsupportedLocale }}.
+ Showing the English version instead.
+
+
+
+
+
+
+
diff --git a/frontend/src/middleware/language-redirect.global.ts b/frontend/src/middleware/language-redirect.global.ts
new file mode 100755
index 00000000000..286e3e13301
--- /dev/null
+++ b/frontend/src/middleware/language-redirect.global.ts
@@ -0,0 +1,30 @@
+import {
+ defineNuxtRouteMiddleware,
+ navigateTo,
+ useCookie,
+ useNuxtApp,
+} from '#app'
+
+export default defineNuxtRouteMiddleware((to) => {
+ const { $i18n } = useNuxtApp()
+ const availableLocales = $i18n.availableLocales as string[]
+
+ const segments = to.path.split('/').filter(Boolean)
+ if (!segments.length) return
+
+ const locale = segments[0]
+
+ // Only act on two-letter language codes
+ if (locale.length !== 2) return
+
+ // Supported locale → do nothing
+ if (availableLocales.includes(locale)) return
+
+ // Store unsupported locale to show banner
+ const bannerCookie = useCookie
('unsupported_language_code')
+ bannerCookie.value = locale
+
+ // Redirect to English path (strip the locale)
+ const newPath = '/' + segments.slice(1).join('/')
+ return navigateTo(newPath || '/', { redirectCode: 302 })
+})