@koehler8/cms 1.0.0-beta.23 β 1.0.0-beta.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/components/Header.vue +21 -2
- package/src/router/index.js +37 -0
package/package.json
CHANGED
|
@@ -97,7 +97,7 @@
|
|
|
97
97
|
<li v-for="locale in availableLocales" :key="locale">
|
|
98
98
|
<a
|
|
99
99
|
class="locale-option"
|
|
100
|
-
:href="
|
|
100
|
+
:href="localeHref(locale)"
|
|
101
101
|
@click="handleLocaleLinkClick(locale)"
|
|
102
102
|
>
|
|
103
103
|
<span class="locale-flag" aria-hidden="true">{{ localeEmojis[locale] || 'π' }}</span>
|
|
@@ -120,7 +120,7 @@ import { computed, inject, nextTick, onBeforeUnmount, onMounted, ref, useSlots,
|
|
|
120
120
|
import { resolveAsset } from '../utils/assetResolver.js';
|
|
121
121
|
import { trackEvent } from '../utils/analytics.js';
|
|
122
122
|
import { getExtensionComponent } from '../extensions/extensionLoader.js';
|
|
123
|
-
import { availableLocales as siteLocales } from '../utils/loadConfig.js';
|
|
123
|
+
import { availableLocales as siteLocales, baseLocale as siteBaseLocale } from '../utils/loadConfig.js';
|
|
124
124
|
|
|
125
125
|
const slots = useSlots();
|
|
126
126
|
const hasActionsSlot = computed(() => Boolean(slots.actions));
|
|
@@ -129,6 +129,14 @@ const isLangOpen = ref(false);
|
|
|
129
129
|
const isHeaderCompact = ref(false);
|
|
130
130
|
const currentLocale = ref('');
|
|
131
131
|
const availableLocales = computed(() => siteLocales.map((code) => code.trim()).filter(Boolean));
|
|
132
|
+
|
|
133
|
+
// The base locale is served at the unprefixed root path (`/`) β clicking
|
|
134
|
+
// the dropdown item for it should NOT navigate to `/{baseLocale}` because
|
|
135
|
+
// that URL doesn't pre-render and would 404. Build the link path
|
|
136
|
+
// accordingly: base β `/`, others β `/{locale}`.
|
|
137
|
+
const localeHref = (locale) => {
|
|
138
|
+
return locale === siteBaseLocale ? '/' : `/${locale}`;
|
|
139
|
+
};
|
|
132
140
|
const localeLabels = {
|
|
133
141
|
en: 'English',
|
|
134
142
|
fr: 'FranΓ§ais',
|
|
@@ -406,6 +414,17 @@ const updateLocaleMenuPosition = () => {
|
|
|
406
414
|
});
|
|
407
415
|
}
|
|
408
416
|
isLangOpen.value = false;
|
|
417
|
+
// Persist the user's explicit choice so it survives navigation to the
|
|
418
|
+
// unprefixed base-locale URL (`/`). The router guard only writes
|
|
419
|
+
// localStorage when a locale param is present in the URL β clicking
|
|
420
|
+
// the base locale navigates to `/` (no param), so the write must
|
|
421
|
+
// happen here instead. Also keeps loadConfigData's localStorage
|
|
422
|
+
// fallback in sync with the active selection.
|
|
423
|
+
if (!import.meta.env.SSR && typeof localStorage !== 'undefined') {
|
|
424
|
+
try {
|
|
425
|
+
localStorage.setItem('locale', locale);
|
|
426
|
+
} catch {}
|
|
427
|
+
}
|
|
409
428
|
};
|
|
410
429
|
|
|
411
430
|
onMounted(() => {
|
package/src/router/index.js
CHANGED
|
@@ -5,6 +5,19 @@ import Home from '../components/Home.vue';
|
|
|
5
5
|
|
|
6
6
|
import { availableLocales, baseLocale } from '../utils/loadConfig.js';
|
|
7
7
|
|
|
8
|
+
// Test for `/{baseLocale}` or `/{baseLocale}/...` paths β these no longer
|
|
9
|
+
// pre-render or match the locale layout (the canonical URL for the base
|
|
10
|
+
// locale is unprefixed). Used by the router guard to redirect in-SPA
|
|
11
|
+
// navigation so external clicks land on the canonical URL instead of the
|
|
12
|
+
// SPA catch-all β 404 path.
|
|
13
|
+
function stripBaseLocalePrefix(targetPath, base) {
|
|
14
|
+
if (!base || !targetPath) return null;
|
|
15
|
+
const root = `/${base}`;
|
|
16
|
+
if (targetPath === root) return '/';
|
|
17
|
+
if (targetPath.startsWith(`${root}/`)) return targetPath.slice(root.length) || '/';
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
|
|
8
21
|
const LocaleLayout = {
|
|
9
22
|
name: 'LocaleLayout',
|
|
10
23
|
render() {
|
|
@@ -85,6 +98,17 @@ function normalizeLocale(value = '') {
|
|
|
85
98
|
|
|
86
99
|
export function applyRouterGuards(router, localePrefixes = computeLocalePrefixes()) {
|
|
87
100
|
router.beforeEach((to) => {
|
|
101
|
+
// SPA-side redirect for stale base-locale-prefixed URLs. When a user
|
|
102
|
+
// navigates inside the app to `/en/about` (and `en` is base), the URL
|
|
103
|
+
// doesn't pre-render and the catch-all renders the 404 page. Rewrite
|
|
104
|
+
// to the canonical unprefixed equivalent. Direct external hits to
|
|
105
|
+
// these URLs still need an Amplify customRule on the host side β see
|
|
106
|
+
// CLAUDE.md "URL hygiene".
|
|
107
|
+
const stripped = stripBaseLocalePrefix(to.path, baseLocale);
|
|
108
|
+
if (stripped !== null && stripped !== to.path) {
|
|
109
|
+
return { path: stripped, replace: true };
|
|
110
|
+
}
|
|
111
|
+
|
|
88
112
|
const rawLocale = (to.params.locale || '').toString().trim();
|
|
89
113
|
|
|
90
114
|
if (rawLocale) {
|
|
@@ -112,6 +136,19 @@ export function applyRouterGuards(router, localePrefixes = computeLocalePrefixes
|
|
|
112
136
|
}
|
|
113
137
|
|
|
114
138
|
if (!import.meta.env.SSR) {
|
|
139
|
+
// Browser-language auto-redirect on first visit only. If the user
|
|
140
|
+
// already has a stored locale preference (set by either an explicit
|
|
141
|
+
// dropdown click or a previous visit to a locale-prefixed URL),
|
|
142
|
+
// respect that β don't second-guess them with browser-language
|
|
143
|
+
// detection. This prevents the bug where clicking "EN" on /de
|
|
144
|
+
// navigates to / and the auto-detect bounces back to /de because
|
|
145
|
+
// the browser is set to German.
|
|
146
|
+
let storedPreference = '';
|
|
147
|
+
try {
|
|
148
|
+
storedPreference = (localStorage.getItem('locale') || '').toString().toLowerCase();
|
|
149
|
+
} catch {}
|
|
150
|
+
if (storedPreference) return;
|
|
151
|
+
|
|
115
152
|
const navLang = (navigator.language || navigator.userLanguage || '').toString();
|
|
116
153
|
const shortLang = normalizeLocale(navLang || '');
|
|
117
154
|
|