Skip to content

Commit 08dd21e

Browse files
s00dcursoragent
andcommitted
fix(path-strategy): keep nested parent when child is absolute
Only skip parent join when the child path already starts with the parent (#239); restores change-activity nested routes. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent d151f7c commit 08dd21e

4 files changed

Lines changed: 26 additions & 18 deletions

File tree

packages/path-strategy/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@i18n-micro/path-strategy",
3-
"version": "1.3.8",
3+
"version": "1.3.9",
44
"description": "Shared strategy-based path and route name builder for Nuxt I18n Micro frontend routing.",
55
"keywords": [
66
"i18n",

packages/path-strategy/src/path.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,22 @@ export const normalizePath = (p: string): string => {
255255
return withLeadingSlash(withoutTrailingSlash(cleanDoubleSlashes(p))) || '/'
256256
}
257257

258+
/**
259+
* Join a nested child custom path under its parent.
260+
* If the child is already absolute and starts with the parent (e.g. parent `/blog-es`,
261+
* child `/blog-es/:slug` → `/blog-es/hello`), return the child as-is to avoid
262+
* duplicating the parent segment (#239 Case A). Otherwise prepend the parent
263+
* (e.g. parent `/change-activity`, child `/book-activity/skiing`).
264+
*/
265+
export function joinNestedCustomPath(parentPath: string, customPath: string): string {
266+
const child = normalizePath(customPath)
267+
if (!parentPath) return child
268+
const parent = normalizePath(parentPath)
269+
if (child === parent || child.startsWith(`${parent}/`)) return child
270+
const segment = child.charCodeAt(0) === 47 ? child.slice(1) : child
271+
return joinUrl(parent, segment)
272+
}
273+
258274
export function normalizePathForCompare(p: string): string {
259275
return withoutTrailingSlash(cleanDoubleSlashes(p || '/')) || '/'
260276
}

packages/path-strategy/src/strategies/common.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
*/
77

88
import { findLocalizedRouteName, preserveQueryAndHash, tryResolveByLocalizedName, tryResolveByLocalizedNameWithParams } from '../helpers'
9-
import { getPathSegments, hasKeys, joinUrl, nameKeyFirstSlash, nameKeyLastSlash, normalizePath, transformNameKeyToPath } from '../path'
9+
import { getPathSegments, hasKeys, joinNestedCustomPath, joinUrl, nameKeyFirstSlash, nameKeyLastSlash, normalizePath, transformNameKeyToPath } from '../path'
1010
import {
1111
analyzeRoute,
1212
getPathForUnlocalizedRoute,
@@ -151,16 +151,15 @@ export function defaultResolveLocaleRoute(
151151
const isNested = isNestedFirst || isNestedLast
152152
const keyWithSlash = isNestedLast ? keyLastSlash : keyFirstSlash
153153
let pathWithoutLocale: string
154-
if (isNested && customSegment.charCodeAt(0) !== 47) {
155-
// Relative nested segment — join under parent. Absolute custom paths are complete (#239).
154+
if (isNested) {
156155
const nameSegments = getPathSegments(keyWithSlash)
157156
const parentKey = nameSegments.length > 1 ? nameSegments.slice(0, -1).join('-') : ''
158157
const parentRules =
159158
parentKey && gr?.[parentKey] && typeof gr[parentKey] === 'object' && !Array.isArray(gr[parentKey])
160159
? (gr[parentKey] as Record<string, string>)
161160
: null
162161
const parentPath = parentRules?.[targetLocale] ? normalizePath(parentRules[targetLocale]) : joinUrl('/', ...nameSegments.slice(0, -1))
163-
pathWithoutLocale = joinUrl(parentPath, customSegment)
162+
pathWithoutLocale = joinNestedCustomPath(parentPath, customSegment)
164163
} else {
165164
pathWithoutLocale = normalizePath(customSegment)
166165
}

packages/path-strategy/src/strategies/prefix-except-default.ts

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
normalizePath,
2121
normalizePathForCompare,
2222
parentKeyFromSlashKey,
23+
joinNestedCustomPath,
2324
transformNameKeyToPath,
2425
} from '../path'
2526
import { analyzeRoute, getPathForUnlocalizedRoute, getPathForUnlocalizedRouteByName, isIndexRouteName, resolveCustomPath } from '../resolver'
@@ -427,21 +428,13 @@ export class PrefixExceptDefaultPathStrategy extends BasePathStrategy {
427428
sourceRoute: RouteLike,
428429
currentRoute?: ResolvedRouteLike,
429430
): RouteLike | string {
430-
// Absolute custom paths already include the full locale path (e.g. `/blog-es/:slug`).
431-
// Prepending a parent segment duplicates it when Nuxt's dashed name (`blog-slug`) is
432-
// mistaken for nesting under `blog` (#239 Case A).
433-
const isAbsoluteCustom = customPath.charCodeAt(0) === 47
431+
const nestedInfo = this.getNestedRouteInfo(routeName)
434432
let pathNorm: string
435-
if (isAbsoluteCustom) {
436-
pathNorm = normalizePath(customPath)
433+
if (nestedInfo) {
434+
const parentPath = this.getParentPathForTarget(nestedInfo.parentKey, nestedInfo.keyWithSlash, targetLocale, currentRoute)
435+
pathNorm = joinNestedCustomPath(parentPath, customPath)
437436
} else {
438-
const nestedInfo = this.getNestedRouteInfo(routeName)
439-
if (nestedInfo) {
440-
const parentPath = this.getParentPathForTarget(nestedInfo.parentKey, nestedInfo.keyWithSlash, targetLocale, currentRoute)
441-
pathNorm = parentPath ? joinUrl(parentPath, customPath) : normalizePath(customPath)
442-
} else {
443-
pathNorm = normalizePath(customPath)
444-
}
437+
pathNorm = normalizePath(customPath)
445438
}
446439
if (!needsPrefix) return preserveQueryAndHash(this.applyBaseUrl(targetLocale, pathNorm), sourceRoute)
447440
return preserveQueryAndHash(this.applyBaseUrl(targetLocale, joinUrl(targetLocale, pathNorm)), sourceRoute)

0 commit comments

Comments
 (0)