@@ -38,6 +38,45 @@ async function loadMessages(lang: string): Promise<Record<string, Record<string,
3838 }
3939}
4040
41+ /** Resolve `{name}`-style placeholders. */
42+ function interpolate ( template : string , values ?: Record < string , string | number > ) : string {
43+ if ( ! values ) return template ;
44+ return template . replace ( / \{ ( \w + ) \} / g, ( _ , k ) => ( k in values ? String ( values [ k ] ) : `{${ k } }` ) ) ;
45+ }
46+
47+ /** Resolve a single ICU plural message like `{count, plural, one {# year} other {# years}}`. */
48+ function resolvePlural ( template : string , locale : string , values ?: Record < string , string | number > ) : string {
49+ const pluralMatch = template . match ( / ^ \{ ( \w + ) , \s * p l u r a l , \s * ( [ \s \S ] + ) \} $ / ) ;
50+ if ( ! pluralMatch ) return interpolate ( template , values ) ;
51+
52+ const [ , varName , body ] = pluralMatch ;
53+ const count = Number ( values ?. [ varName ] ?? 0 ) ;
54+
55+ // Parse branches like: one {# year} other {# years} =0 {no years}
56+ const branches = new Map < string , string > ( ) ;
57+ const branchRegex = / ( = ? \w + ) \s * \{ ( [ ^ { } ] * ) \} / g;
58+ let m : RegExpExecArray | null ;
59+ while ( ( m = branchRegex . exec ( body ) ) ) {
60+ branches . set ( m [ 1 ] , m [ 2 ] ) ;
61+ }
62+
63+ let chosen = branches . get ( `=${ count } ` ) ;
64+ if ( chosen === undefined ) {
65+ const cat = new Intl . PluralRules ( locale ) . select ( count ) ;
66+ chosen = branches . get ( cat ) ?? branches . get ( "other" ) ?? template ;
67+ }
68+
69+ return interpolate ( chosen . replace ( / # / g, String ( count ) ) , values ) ;
70+ }
71+
72+ function makeTranslator ( messages : Record < string , Record < string , string > > , namespace : string , locale : string ) {
73+ return ( key : string , values ?: Record < string , string | number > ) : string => {
74+ const template = messages [ namespace ] ?. [ key ] ;
75+ if ( ! template ) return key ;
76+ return resolvePlural ( template , locale , values ) ;
77+ } ;
78+ }
79+
4180export async function GET ( request : NextRequest ) {
4281 const { searchParams } = request . nextUrl ;
4382 const ids = searchParams . get ( "ids" ) ;
@@ -77,7 +116,9 @@ export async function GET(request: NextRequest) {
77116 const font = await getFont ( ) ;
78117
79118 const nowLabel = messages . common ?. now || "Now" ;
80- let timeline = allEvents . length > 0 ? computeTimeline ( allEvents , 2 , "en-US" , nowLabel , undefined , hideNow ) : null ;
119+ const spanT = makeTranslator ( messages , "date" , lang ) ;
120+ const eventLabelT = makeTranslator ( messages , "eventLabel" , lang ) ;
121+ let timeline = allEvents . length > 0 ? computeTimeline ( allEvents , 2 , lang , nowLabel , spanT , hideNow ) : null ;
81122
82123 const fallbackDescription = messages . meta ?. siteDescription || "Visualize the time between historical events." ;
83124
@@ -197,9 +238,9 @@ export async function GET(request: NextRequest) {
197238 overflow : "visible" ,
198239 } }
199240 >
200- { isNow ? nowLabel : eventDisplayName ( marker . event ) . length > 25
201- ? eventDisplayName ( marker . event ) . slice ( 0 , 23 ) + "\u2026"
202- : eventDisplayName ( marker . event ) }
241+ { isNow ? nowLabel : eventDisplayName ( marker . event , eventLabelT ) . length > 25
242+ ? eventDisplayName ( marker . event , eventLabelT ) . slice ( 0 , 23 ) + "\u2026"
243+ : eventDisplayName ( marker . event , eventLabelT ) }
203244 </ div >
204245 </ div >
205246 ) ;
0 commit comments