@@ -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 > & {
@@ -27,19 +26,61 @@ export const LocalizedLink = forwardRef<HTMLAnchorElement, TLocalizedLinkProps>(
2726 const pathname = usePathname ( )
2827 const currentLanguage = getLanguageFromPath ( pathname ) || DEFAULT_LANGUAGE
2928
30- // Convert href to string for processing
31- const hrefString =
32- typeof href === 'string'
33- ? href
34- : typeof href === 'object' &&
35- href !== null &&
36- 'pathname' in href &&
37- typeof ( href as UrlObject ) . pathname === 'string'
38- ? ( href as UrlObject ) . pathname
39- : ''
29+ // Preserve UrlObject hrefs (pathname + query + hash) when provided
30+ const hrefObj = typeof href === 'object' && href !== null ? ( href as UrlObject ) : undefined
4031
41- // External app.shapeshift.com link detection
42- 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 )
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+ }
62+ }
63+ const qs = params . toString ( )
64+ query = qs ? `?${ qs } ` : ''
65+ }
66+ }
67+ if ( host ) {
68+ return `${ protocol || 'https:' } //${ host } ${ pathname } ${ query } ${ hash } `
69+ }
70+ return `${ pathname } ${ query } ${ hash } `
71+ }
72+
73+ // External app.shapeshift.com link detection (works for string hrefs and UrlObject with host)
74+ const isAppLink = ( ( ) => {
75+ if ( typeof href === 'string' ) {
76+ 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 )
77+ }
78+ if ( hrefObj ) {
79+ const full = fullUrlFromObj ( hrefObj )
80+ 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 )
81+ }
82+ return false
83+ } ) ( )
4384
4485 // Compose click handler for external app links
4586 const handleClick = ( e : React . MouseEvent < HTMLAnchorElement > ) => {
@@ -52,11 +93,14 @@ export const LocalizedLink = forwardRef<HTMLAnchorElement, TLocalizedLinkProps>(
5293 // ignore
5394 }
5495 e . preventDefault ( )
96+ const absolute = hrefObj
97+ ? fullUrlFromObj ( hrefObj )
98+ : hrefString || ( typeof href === 'string' ? href : '' )
5599 try {
56- window . open ( hrefString || ( typeof href === 'string' ? href : '' ) , '_blank' , 'noopener,noreferrer' )
100+ window . open ( absolute , '_blank' , 'noopener,noreferrer' )
57101 } catch {
58- if ( hrefString ) {
59- window . location . assign ( hrefString )
102+ if ( absolute ) {
103+ window . location . assign ( absolute )
60104 }
61105 }
62106 }
@@ -66,12 +110,16 @@ export const LocalizedLink = forwardRef<HTMLAnchorElement, TLocalizedLinkProps>(
66110 }
67111
68112 // Don't modify external links, anchors, or already localized paths
113+ const isExternalObject = ! ! hrefObj && ! ! ( hrefObj . host || hrefObj . hostname || hrefObj . protocol )
69114 if (
70- hrefString ?. startsWith ( 'http' ) ||
71- hrefString ?. startsWith ( '#' ) ||
72- hrefString ?. startsWith ( 'mailto:' ) ||
73- hrefString ?. startsWith ( 'tel:' ) ||
74- ! hrefString ?. startsWith ( '/' )
115+ typeof href === 'string'
116+ ? hrefString . startsWith ( 'http' ) ||
117+ hrefString . startsWith ( '#' ) ||
118+ hrefString . startsWith ( 'mailto:' ) ||
119+ hrefString . startsWith ( 'tel:' ) ||
120+ ! hrefString . startsWith ( '/' )
121+ : // href is an object
122+ isExternalObject
75123 ) {
76124 return (
77125 < Link
@@ -87,12 +135,18 @@ export const LocalizedLink = forwardRef<HTMLAnchorElement, TLocalizedLinkProps>(
87135 // Check if the href already has a language prefix
88136 const hasLanguagePrefix = hrefString . match ( / ^ \/ ( [ a - z ] { 2 } ) ( \/ | $ ) / )
89137
90- // Build the localized href
91- let localizedHref = hrefString
138+ // Build the localized href. Preserve query/hash by returning a UrlObject when the original
139+ // href was a UrlObject, otherwise return a string.
140+ let localizedPathname = hrefString
92141 if ( ! hasLanguagePrefix && currentLanguage !== DEFAULT_LANGUAGE ) {
93- localizedHref = `/${ currentLanguage } ${ hrefString } `
142+ localizedPathname = `/${ currentLanguage } ${ hrefString } `
94143 }
95144
145+ const localizedHref = hrefObj
146+ ? // copy the original UrlObject but replace/normalize pathname
147+ ( { ...hrefObj , pathname : localizedPathname } as UrlObject )
148+ : localizedPathname
149+
96150 return (
97151 < Link
98152 href = { localizedHref }
0 commit comments