@@ -12,7 +12,6 @@ import type {UrlObject} from 'url'
1212
1313/* eslint-disable @typescript-eslint/naming-convention */
1414declare const __adrsbl : { run : ( event : string , conversion : boolean ) => void } | undefined
15- /* eslint-enable @typescript-eslint/naming-convention */
1615
1716type TLocalizedLinkProps = LinkProps &
1817 AnchorHTMLAttributes < HTMLAnchorElement > & {
@@ -32,19 +31,61 @@ export const LocalizedLink = forwardRef<HTMLAnchorElement, TLocalizedLinkProps>(
3231 const pathname = usePathname ( )
3332 const currentLanguage = getLanguageFromPath ( pathname ) || DEFAULT_LANGUAGE
3433
35- // Convert href to string for processing
36- const hrefString =
37- typeof href === 'string'
38- ? href
39- : typeof href === 'object' &&
40- href !== null &&
41- 'pathname' in href &&
42- typeof ( href as UrlObject ) . pathname === 'string'
43- ? ( href as UrlObject ) . pathname
44- : ''
34+ // Preserve UrlObject hrefs (pathname + query + hash) when provided
35+ const hrefObj = typeof href === 'object' && href !== null ? ( href as UrlObject ) : undefined
4536
46- // External app.shapeshift.com link detection
47- const isAppLink = typeof hrefString === 'string' && / ^ h t t p s ? : \/ \/ a p p \. s h a p e s h i f t \. c o m ( \/ | $ ) / i. test ( hrefString )
37+ // Convert href to string for pathname-based processing when needed
38+ const hrefString = typeof href === 'string' ? href : ( hrefObj ?. pathname ?? '' )
39+
40+ // Helper to build a full absolute URL string from a UrlObject when it has host/protocol
41+ const fullUrlFromObj = ( obj : UrlObject ) => {
42+ if ( ! obj ) {
43+ return ''
44+ }
45+ const protocol = ( obj . protocol as string ) ?? ''
46+ const host = ( obj . host as string ) ?? ( obj . hostname as string ) ?? ''
47+ const pathname = ( obj . pathname as string ) ?? ''
48+ const hash = ( obj . hash as string ) ?? ''
49+ let query = ''
50+ // Support both string-form and object-form queries.
51+ if ( obj . query ) {
52+ if ( typeof obj . query === 'string' ) {
53+ // Use provided string query, ensure it starts with '?'
54+ const raw = obj . query as string
55+ query = raw . startsWith ( '?' ) ? raw : `?${ raw } `
56+ } else if ( typeof obj . query === 'object' ) {
57+ // obj.query can be Record<string, string | string[]>.
58+ const params = new URLSearchParams ( )
59+ for ( const [ k , v ] of Object . entries ( obj . query as Record < string , string | string [ ] > ) ) {
60+ if ( Array . isArray ( v ) ) {
61+ for ( const item of v ) {
62+ params . append ( k , String ( item ) )
63+ }
64+ } else if ( v !== undefined && v !== null ) {
65+ params . append ( k , String ( v ) )
66+ }
67+ }
68+ const qs = params . toString ( )
69+ query = qs ? `?${ qs } ` : ''
70+ }
71+ }
72+ if ( host ) {
73+ return `${ protocol || 'https:' } //${ host } ${ pathname } ${ query } ${ hash } `
74+ }
75+ return `${ pathname } ${ query } ${ hash } `
76+ }
77+
78+ // External app.shapeshift.com link detection (works for string hrefs and UrlObject with host)
79+ const isAppLink = ( ( ) => {
80+ if ( typeof href === 'string' ) {
81+ return / ^ h t t p s ? : \/ \/ a p p \. s h a p e s h i f t \. c o m ( \/ | $ ) / i. test ( href )
82+ }
83+ if ( hrefObj ) {
84+ const full = fullUrlFromObj ( hrefObj )
85+ return / ^ h t t p s ? : \/ \/ a p p \. s h a p e s h i f t \. c o m ( \/ | $ ) / i. test ( full )
86+ }
87+ return false
88+ } ) ( )
4889
4990 // Compose click handler for external app links
5091 const handleClick = ( e : React . MouseEvent < HTMLAnchorElement > ) => {
@@ -57,11 +98,15 @@ export const LocalizedLink = forwardRef<HTMLAnchorElement, TLocalizedLinkProps>(
5798 // ignore
5899 }
59100 e . preventDefault ( )
101+ const absolute = hrefObj
102+ ? fullUrlFromObj ( hrefObj )
103+ : hrefString || ( typeof href === 'string' ? href : '' )
104+ if ( ! absolute ) return
60105 try {
61- window . open ( hrefString || ( typeof href === 'string' ? href : '' ) , '_blank' , 'noopener,noreferrer' )
106+ window . open ( absolute , '_blank' , 'noopener,noreferrer' )
62107 } catch {
63- if ( hrefString ) {
64- window . location . assign ( hrefString )
108+ if ( absolute ) {
109+ window . location . assign ( absolute )
65110 }
66111 }
67112 }
@@ -71,12 +116,16 @@ export const LocalizedLink = forwardRef<HTMLAnchorElement, TLocalizedLinkProps>(
71116 }
72117
73118 // Don't modify external links, anchors, or already localized paths
119+ const isExternalObject = ! ! hrefObj && ! ! ( hrefObj . host || hrefObj . hostname || hrefObj . protocol )
74120 if (
75- hrefString ?. startsWith ( 'http' ) ||
76- hrefString ?. startsWith ( '#' ) ||
77- hrefString ?. startsWith ( 'mailto:' ) ||
78- hrefString ?. startsWith ( 'tel:' ) ||
79- ! hrefString ?. startsWith ( '/' )
121+ typeof href === 'string'
122+ ? hrefString . startsWith ( 'http' ) ||
123+ hrefString . startsWith ( '#' ) ||
124+ hrefString . startsWith ( 'mailto:' ) ||
125+ hrefString . startsWith ( 'tel:' ) ||
126+ ! hrefString . startsWith ( '/' )
127+ : // href is an object
128+ isExternalObject
80129 ) {
81130 return (
82131 < Link
@@ -92,12 +141,18 @@ export const LocalizedLink = forwardRef<HTMLAnchorElement, TLocalizedLinkProps>(
92141 // Check if the href already has a language prefix
93142 const hasLanguagePrefix = hrefString . match ( / ^ \/ ( [ a - z ] { 2 } ) ( \/ | $ ) / )
94143
95- // Build the localized href
96- let localizedHref = hrefString
144+ // Build the localized href. Preserve query/hash by returning a UrlObject when the original
145+ // href was a UrlObject, otherwise return a string.
146+ let localizedPathname = hrefString
97147 if ( ! hasLanguagePrefix && currentLanguage !== DEFAULT_LANGUAGE ) {
98- localizedHref = `/${ currentLanguage } ${ hrefString } `
148+ localizedPathname = `/${ currentLanguage } ${ hrefString } `
99149 }
100150
151+ const localizedHref = hrefObj
152+ ? // copy the original UrlObject but replace/normalize pathname
153+ ( { ...hrefObj , pathname : localizedPathname } as UrlObject )
154+ : localizedPathname
155+
101156 return (
102157 < Link
103158 href = { localizedHref }
0 commit comments