@@ -53,39 +53,45 @@ function serializeChildren(children: ReactNode): {
5353 values : Record < string , unknown > ;
5454 components : Record < string , ReactElement > ;
5555} {
56- const messageParts : string [ ] = [ ] ;
5756 const values : Record < string , unknown > = { } ;
5857 const components : Record < string , ReactElement > = { } ;
59- let placeholderIndex = 0 ;
58+ // Single counter shared across recursion depths. Lingui's babel macro assigns placeholder indices
59+ // globally across the whole <Trans> tree (so a <strong><Value/></strong> tree extracts as
60+ // `<0><1/></0>`, not `<0><0/></0>`). Using a fresh counter per recursion would diverge from the
61+ // macro's id-generation algorithm — `generateMessageId` would hash a different string, the catalog
62+ // lookup would silently miss for every nested-element template, and components stored at colliding
63+ // keys would render swapped content (e.g. <Link> children replaced by the <Value> render).
64+ const counter = { next : 0 } ;
6065
61- Children . forEach ( children , ( child ) => {
62- if ( typeof child === "string" || typeof child === "number" ) {
63- messageParts . push ( String ( child ) ) ;
64- return ;
65- }
66+ function visit ( nodes : ReactNode ) : string {
67+ const parts : string [ ] = [ ] ;
68+ Children . forEach ( nodes , ( child ) => {
69+ if ( typeof child === "string" || typeof child === "number" ) {
70+ parts . push ( String ( child ) ) ;
71+ return ;
72+ }
6673
67- if ( isValidElement ( child ) ) {
68- const componentIndex = String ( placeholderIndex ++ ) ;
69- const elementChildren = ( child . props as { children ?: ReactNode } ) . children ;
70- if ( elementChildren === undefined || elementChildren === null ) {
71- messageParts . push ( `< ${ componentIndex } />` ) ;
72- } else {
73- const inner = serializeChildren ( elementChildren ) ;
74- messageParts . push ( `<${ componentIndex } >${ inner . message } </${ componentIndex } >` ) ;
75- Object . assign ( values , inner . values ) ;
76- Object . assign ( components , inner . components ) ;
74+ if ( isValidElement ( child ) ) {
75+ const componentIndex = String ( counter . next ++ ) ;
76+ components [ componentIndex ] = child ;
77+ const elementChildren = ( child . props as { children ?: ReactNode } ) . children ;
78+ if ( elementChildren === undefined || elementChildren === null ) {
79+ parts . push ( `< ${ componentIndex } />` ) ;
80+ } else {
81+ parts . push ( `<${ componentIndex } >${ visit ( elementChildren ) } </${ componentIndex } >` ) ;
82+ }
83+ return ;
7784 }
78- components [ componentIndex ] = child ;
79- return ;
80- }
8185
82- if ( child === null || child === undefined || typeof child === "boolean" ) return ;
86+ if ( child === null || child === undefined || typeof child === "boolean" ) return ;
8387
84- // Treat any remaining renderable (objects, arrays) as opaque values via {0}, {1}, ... placeholders.
85- const valueKey = String ( placeholderIndex ++ ) ;
86- values [ valueKey ] = child ;
87- messageParts . push ( `{${ valueKey } }` ) ;
88- } ) ;
88+ // Treat any remaining renderable (objects, arrays) as opaque values via {0}, {1}, ... placeholders.
89+ const valueKey = String ( counter . next ++ ) ;
90+ values [ valueKey ] = child ;
91+ parts . push ( `{${ valueKey } }` ) ;
92+ } ) ;
93+ return parts . join ( "" ) ;
94+ }
8995
90- return { message : messageParts . join ( "" ) , values, components } ;
96+ return { message : visit ( children ) , values, components } ;
9197}
0 commit comments