Skip to content

Commit bfed48e

Browse files
committed
chore: meeting coderabbit suggestions
1 parent f7b92e1 commit bfed48e

File tree

1 file changed

+84
-24
lines changed

1 file changed

+84
-24
lines changed

app/[lang]/_components/LocalizedLink.tsx

Lines changed: 84 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -12,31 +12,77 @@ import type {UrlObject} from 'url'
1212

1313
/* eslint-disable @typescript-eslint/naming-convention */
1414
declare const __adrsbl: {run: (event: string, conversion: boolean) => void} | undefined
15-
/* eslint-enable @typescript-eslint/naming-convention */
1615

1716
type TLocalizedLinkProps = LinkProps &
1817
AnchorHTMLAttributes<HTMLAnchorElement> & {
1918
children: ReactNode
2019
}
2120

21+
/**
22+
* A localized version of Next.js Link that automatically prepends the current language
23+
*/
2224
export const LocalizedLink = forwardRef<HTMLAnchorElement, TLocalizedLinkProps>(
2325
({href, children, onClick, ...props}, ref) => {
2426
const pathname = usePathname()
2527
const currentLanguage = getLanguageFromPath(pathname) || DEFAULT_LANGUAGE
2628

27-
// Convert href to string for processing
28-
const hrefString =
29-
typeof href === 'string'
30-
? href
31-
: typeof href === 'object' &&
32-
href !== null &&
33-
'pathname' in href &&
34-
typeof (href as UrlObject).pathname === 'string'
35-
? (href as UrlObject).pathname
36-
: ''
29+
// Preserve UrlObject hrefs (pathname + query + hash) when provided
30+
const hrefObj = typeof href === 'object' && href !== null ? (href as UrlObject) : undefined
3731

38-
// External app.shapeshift.com link detection
39-
const isAppLink = typeof hrefString === 'string' && /^https?:\/\/app\.shapeshift\.com(\/|$)/i.test(hrefString)
32+
// Convert href to string for pathname-based processing when needed
33+
const hrefString = typeof href === 'string' ? href : (hrefObj?.pathname ?? '')
34+
35+
// Helper to build a full absolute URL string from a UrlObject when it has host/protocol
36+
const fullUrlFromObj = (obj: UrlObject) => {
37+
if (!obj) {
38+
return ''
39+
}
40+
const protocol = (obj.protocol as string) ?? ''
41+
const host = (obj.host as string) ?? (obj.hostname as string) ?? ''
42+
const pathname = (obj.pathname as string) ?? ''
43+
const hash = (obj.hash as string) ?? ''
44+
let query = ''
45+
// Support both string-form and object-form queries.
46+
if (obj.query) {
47+
if (typeof obj.query === 'string') {
48+
// Use provided string query, ensure it starts with '?'
49+
const raw = obj.query as string
50+
query = raw.startsWith('?') ? raw : `?${raw}`
51+
} else if (typeof obj.query === 'object') {
52+
// obj.query can be Record<string, string | string[]>.
53+
const params = new URLSearchParams()
54+
for (const [k, v] of Object.entries(obj.query as Record<string, string | string[]>)) {
55+
if (Array.isArray(v)) {
56+
for (const item of v) {
57+
params.append(k, String(item))
58+
}
59+
} else if (v !== undefined && v !== null) {
60+
params.append(k, String(v))
61+
} else {
62+
params.append(k, '')
63+
}
64+
}
65+
const qs = params.toString()
66+
query = qs ? `?${qs}` : ''
67+
}
68+
}
69+
if (host) {
70+
return `${protocol || 'https:'}//${host}${pathname}${query}${hash}`
71+
}
72+
return `${pathname}${query}${hash}`
73+
}
74+
75+
// External app.shapeshift.com link detection (works for string hrefs and UrlObject with host)
76+
const isAppLink = (() => {
77+
if (typeof href === 'string') {
78+
return /^https?:\/\/app\.shapeshift\.com(\/|$)/i.test(href)
79+
}
80+
if (hrefObj) {
81+
const full = fullUrlFromObj(hrefObj)
82+
return /^https?:\/\/app\.shapeshift\.com(\/|$)/i.test(full)
83+
}
84+
return false
85+
})()
4086

4187
// Compose click handler for external app links
4288
const handleClick = (e: React.MouseEvent<HTMLAnchorElement>) => {
@@ -50,10 +96,14 @@ export const LocalizedLink = forwardRef<HTMLAnchorElement, TLocalizedLinkProps>(
5096
}
5197
e.preventDefault()
5298
try {
53-
window.open(hrefString || (typeof href === 'string' ? href : ''), '_blank', 'noopener,noreferrer')
99+
const absolute = hrefObj
100+
? fullUrlFromObj(hrefObj)
101+
: hrefString || (typeof href === 'string' ? href : '')
102+
window.open(absolute, '_blank', 'noopener,noreferrer')
54103
} catch {
55-
if (hrefString) {
56-
window.location.assign(hrefString)
104+
const absolute = hrefObj ? fullUrlFromObj(hrefObj) : hrefString
105+
if (absolute) {
106+
window.location.assign(absolute)
57107
}
58108
}
59109
}
@@ -63,12 +113,16 @@ export const LocalizedLink = forwardRef<HTMLAnchorElement, TLocalizedLinkProps>(
63113
}
64114

65115
// Don't modify external links, anchors, or already localized paths
116+
const isExternalObject = !!hrefObj && !!(hrefObj.host || hrefObj.hostname || hrefObj.protocol)
66117
if (
67-
hrefString?.startsWith('http') ||
68-
hrefString?.startsWith('#') ||
69-
hrefString?.startsWith('mailto:') ||
70-
hrefString?.startsWith('tel:') ||
71-
!hrefString?.startsWith('/')
118+
typeof href === 'string'
119+
? hrefString.startsWith('http') ||
120+
hrefString.startsWith('#') ||
121+
hrefString.startsWith('mailto:') ||
122+
hrefString.startsWith('tel:') ||
123+
!hrefString.startsWith('/')
124+
: // href is an object
125+
isExternalObject
72126
) {
73127
return (
74128
<Link
@@ -84,12 +138,18 @@ export const LocalizedLink = forwardRef<HTMLAnchorElement, TLocalizedLinkProps>(
84138
// Check if the href already has a language prefix
85139
const hasLanguagePrefix = hrefString.match(/^\/([a-z]{2})(\/|$)/)
86140

87-
// Build the localized href
88-
let localizedHref = hrefString
141+
// Build the localized href. Preserve query/hash by returning a UrlObject when the original
142+
// href was a UrlObject, otherwise return a string.
143+
let localizedPathname = hrefString
89144
if (!hasLanguagePrefix && currentLanguage !== DEFAULT_LANGUAGE) {
90-
localizedHref = `/${currentLanguage}${hrefString}`
145+
localizedPathname = `/${currentLanguage}${hrefString}`
91146
}
92147

148+
const localizedHref = hrefObj
149+
? // copy the original UrlObject but replace/normalize pathname
150+
({...hrefObj, pathname: localizedPathname} as UrlObject)
151+
: localizedPathname
152+
93153
return (
94154
<Link
95155
href={localizedHref}

0 commit comments

Comments
 (0)