Skip to content

Commit bfda069

Browse files
s00dcursoragent
andcommitted
fix(redirect): keep localeRoutes aliases under autoDetectPath /
Preference gating must not skip getClientRedirect for unprefixed paths — force defaultLocale instead so /product → /our-products works. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent d2b6ed7 commit bfda069

3 files changed

Lines changed: 47 additions & 41 deletions

File tree

packages/utils/src/auto-detect-path.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
/**
2-
* Whether preference-based locale redirects may run for an unprefixed path (#242).
2+
* Whether cookie / Accept-Language preference may steer locale redirects (#242).
33
*
44
* Modes (`ModuleOptions.autoDetectPath`):
55
* - `'/'` (default) — only `/`
66
* - `'no_prefix'` — any path without a locale prefix
77
* - `'*'` — every path (also enables aggressive prefixed-URL rewrites in the plugin)
88
* - any other string — exact path match
99
*
10-
* Prefixed strategy cleanup (e.g. `/en` → `/` under `prefix_except_default`) is not gated here.
10+
* Does not gate strategy / `localeRoutes` canonicalization: when preference is denied on an
11+
* unprefixed path, the plugin still runs `getClientRedirect` with the default locale.
1112
*/
1213
export function shouldAttemptLocaleRedirect(path: string, options: { autoDetectPath?: string; hasLocalePrefix?: boolean } = {}): boolean {
1314
const mode = options.autoDetectPath ?? '/'

src/runtime/middleware/i18n-redirect.global.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,11 @@ export default defineNuxtRouteMiddleware((to, from) => {
4343
const hasLocalePrefix = Boolean(firstSegment && validLocales.includes(firstSegment))
4444
const allowPreferenceRedirect = shouldAttemptLocaleRedirect(path, { autoDetectPath, hasLocalePrefix })
4545

46-
// Preference redirects on unprefixed paths are gated by autoDetectPath.
47-
// Prefixed strategy cleanup (e.g. /en → /) always runs.
48-
if (!hasLocalePrefix && !allowPreferenceRedirect) return
46+
// Cookie / Accept-Language preference is gated by autoDetectPath (#242).
47+
// Unprefixed deep links still canonicalize with the default locale (localeRoutes aliases).
48+
if (!hasLocalePrefix && !allowPreferenceRedirect) {
49+
preferredLocale = defaultLocale
50+
}
4951

5052
if (autoDetectPath === '*' && !hasLocalePrefix) {
5153
preferredLocale = defaultLocale

src/runtime/plugins/06.redirect.ts

Lines changed: 39 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -90,45 +90,48 @@ export default defineNuxtPlugin({
9090
if (i18nConfig.redirects !== false) {
9191
const allowPreferenceRedirect = shouldAttemptLocaleRedirect(path, { autoDetectPath, hasLocalePrefix })
9292

93-
// Preference redirects on unprefixed paths are gated by autoDetectPath.
94-
// Prefixed strategy cleanup (e.g. /en → /) always runs.
95-
if (hasLocalePrefix || allowPreferenceRedirect) {
96-
const localeState = autoDetectPath !== '*' ? useState<string | null>('i18n-locale', () => null) : null
97-
let preferredLocale = resolvePreferredLocale({
98-
defaultLocale,
99-
validLocales,
100-
autoDetectLanguage: i18nConfig.autoDetectLanguage,
101-
stateLocale: localeState?.value ?? null,
102-
cookieLocale: cookieName ? getCookie(event, cookieName) : null,
103-
acceptLanguageHeader: getHeader(event, 'accept-language'),
104-
ignoreStateLocale: autoDetectPath === '*',
105-
})
106-
107-
if (autoDetectPath === '*' && !hasLocalePrefix) {
108-
preferredLocale = defaultLocale
109-
}
93+
const localeState = autoDetectPath !== '*' ? useState<string | null>('i18n-locale', () => null) : null
94+
let preferredLocale = resolvePreferredLocale({
95+
defaultLocale,
96+
validLocales,
97+
autoDetectLanguage: i18nConfig.autoDetectLanguage,
98+
stateLocale: localeState?.value ?? null,
99+
cookieLocale: cookieName ? getCookie(event, cookieName) : null,
100+
acceptLanguageHeader: getHeader(event, 'accept-language'),
101+
ignoreStateLocale: autoDetectPath === '*',
102+
})
103+
104+
// Cookie / Accept-Language preference is gated by autoDetectPath (#242).
105+
// Unprefixed deep links still run getClientRedirect with the default locale so
106+
// custom localeRoutes aliases (e.g. /product → /our-products) keep working.
107+
if (!hasLocalePrefix && !allowPreferenceRedirect) {
108+
preferredLocale = defaultLocale
109+
}
110110

111-
if (autoDetectPath === '*' && hasLocalePrefix && firstSegment !== preferredLocale) {
112-
const rest = pathSegments.slice(1).join('/')
113-
let targetPath: string
114-
if (preferredLocale === defaultLocale && i18nConfig.strategy === 'prefix_except_default') {
115-
targetPath = rest ? `/${rest}` : '/'
116-
} else {
117-
targetPath = rest ? `/${preferredLocale}/${rest}` : `/${preferredLocale}`
118-
}
119-
if (cookieName) {
120-
const { watch: _w2, ...cookieOpts2 } = getLocaleCookieOptions()
121-
setCookie(event, cookieName, preferredLocale, cookieOpts2)
122-
}
123-
if (DEBUG) console.error('[i18n-redirect] REDIRECT autoDetectPath *', { path, targetPath, preferredLocale })
124-
return performRedirect(targetPath + (url.search || '') + (url.hash || ''))
125-
}
111+
if (autoDetectPath === '*' && !hasLocalePrefix) {
112+
preferredLocale = defaultLocale
113+
}
126114

127-
const redirectPath = i18nStrategy.getClientRedirect(path, preferredLocale)
128-
if (redirectPath) {
129-
if (DEBUG) console.error('[i18n-redirect] REDIRECT', { path, redirectPath, preferredLocale })
130-
return performRedirect(redirectPath + (url.search || '') + (url.hash || ''))
115+
if (autoDetectPath === '*' && hasLocalePrefix && firstSegment !== preferredLocale) {
116+
const rest = pathSegments.slice(1).join('/')
117+
let targetPath: string
118+
if (preferredLocale === defaultLocale && i18nConfig.strategy === 'prefix_except_default') {
119+
targetPath = rest ? `/${rest}` : '/'
120+
} else {
121+
targetPath = rest ? `/${preferredLocale}/${rest}` : `/${preferredLocale}`
131122
}
123+
if (cookieName) {
124+
const { watch: _w2, ...cookieOpts2 } = getLocaleCookieOptions()
125+
setCookie(event, cookieName, preferredLocale, cookieOpts2)
126+
}
127+
if (DEBUG) console.error('[i18n-redirect] REDIRECT autoDetectPath *', { path, targetPath, preferredLocale })
128+
return performRedirect(targetPath + (url.search || '') + (url.hash || ''))
129+
}
130+
131+
const redirectPath = i18nStrategy.getClientRedirect(path, preferredLocale)
132+
if (redirectPath) {
133+
if (DEBUG) console.error('[i18n-redirect] REDIRECT', { path, redirectPath, preferredLocale })
134+
return performRedirect(redirectPath + (url.search || '') + (url.hash || ''))
132135
}
133136
}
134137
}

0 commit comments

Comments
 (0)