1+ import type { CompletionAdapter , HttpExtra } from 'adminforth' ;
2+ import { createRequire } from 'node:module' ;
3+
4+ const require = createRequire ( import . meta. url ) ;
5+
6+ const ISO_3166_1_ALPHA_2_RE = / ^ [ A - Z ] { 2 } $ / ;
7+
8+ const EN_PLACEHOLDER_MESSAGES = [
9+ 'Find most costy apartment' ,
10+ 'Give me apartments price histogram' ,
11+ 'What is sum cost of all apratments' ,
12+ 'Who modifiend most costy appartment' ,
13+ ] as const ;
14+
15+ const LOCALIZED_PLACEHOLDER_MESSAGES_SCHEMA = {
16+ name : 'localized_placeholder_messages' ,
17+ strict : true ,
18+ schema : {
19+ type : 'object' ,
20+ additionalProperties : false ,
21+ required : [ 'messages' ] ,
22+ properties : {
23+ messages : {
24+ type : 'array' ,
25+ items : {
26+ type : 'string' ,
27+ } ,
28+ } ,
29+ } ,
30+ } ,
31+ } as const ;
32+
33+ type TerritoryInfoJson = {
34+ supplemental ?: {
35+ territoryInfo ?: Record <
36+ string ,
37+ {
38+ languagePopulation ?: Record <
39+ string ,
40+ {
41+ _populationPercent ?: number | string ;
42+ }
43+ > ;
44+ }
45+ > ;
46+ } ;
47+ } ;
48+
49+ const territoryInfoJson = require ( 'cldr-core/supplemental/territoryInfo.json' ) as TerritoryInfoJson ;
50+ const localizedPlaceholderMessagesCache = new Map < string , Promise < string [ ] > > ( ) ;
51+
52+ function normalizeCountryCode ( countryCode ?: string ) : string {
53+ const normalizedCountryCode = countryCode ?. trim ( ) . toUpperCase ( ) ?? '' ;
54+ return ISO_3166_1_ALPHA_2_RE . test ( normalizedCountryCode )
55+ ? normalizedCountryCode
56+ : 'XX' ;
57+ }
58+
59+ export function getClientCountry ( httpExtra : HttpExtra ) : string {
60+ return normalizeCountryCode (
61+ httpExtra . headers [ 'cf-ipcountry' ] ?? httpExtra . headers [ 'CF-IPCountry' ] ,
62+ ) ;
63+ }
64+
65+ function getTopTerritoryLanguage ( countryCode : string ) : string | null {
66+ const territory = territoryInfoJson . supplemental ?. territoryInfo ?. [ countryCode ] ;
67+ const languagePopulation = territory ?. languagePopulation ;
68+
69+ if ( ! languagePopulation ) {
70+ return null ;
71+ }
72+
73+ const topLanguage = Object . entries ( languagePopulation )
74+ . map ( ( [ language , meta ] ) => {
75+ const primaryLanguage = String ( language ) . split ( '-' ) [ 0 ] ?. toLowerCase ( ) ?? '' ;
76+ const populationPercent = Number ( meta ?. _populationPercent ?? 0 ) ;
77+
78+ return {
79+ language : primaryLanguage ,
80+ populationPercent : Number . isFinite ( populationPercent )
81+ ? populationPercent
82+ : 0 ,
83+ } ;
84+ } )
85+ . filter ( ( item ) => item . language . length === 2 )
86+ . filter ( ( item ) => item . language !== 'en' )
87+ . sort ( ( left , right ) => right . populationPercent - left . populationPercent ) [ 0 ] ;
88+
89+ return topLanguage ?. language ?? null ;
90+ }
91+
92+ async function translatePlaceholderMessages ( {
93+ completionAdapter,
94+ countryCode,
95+ language,
96+ } : {
97+ completionAdapter : CompletionAdapter ;
98+ countryCode : string ;
99+ language : string ;
100+ } ) : Promise < string [ ] > {
101+ const content = [
102+ 'You are a UI placeholder translation engine.' ,
103+ 'Translate the `messages` array to the language in `target_language_iso639_1`.' ,
104+ 'Keep each message concise, natural, and suitable for a chat placeholder prompt.' ,
105+ 'Preserve the array length and ordering.' ,
106+ 'When the target language distinguishes between informal and formal second-person pronouns, always use the informal singular form.' ,
107+ 'Respond with a single JSON object matching the schema.' ,
108+ JSON . stringify ( {
109+ country_iso3166_1 : countryCode ,
110+ target_language_iso639_1 : language ,
111+ messages : EN_PLACEHOLDER_MESSAGES ,
112+ } ) ,
113+ ] . join ( '\n\n' ) ;
114+
115+ const result = await completionAdapter . complete (
116+ content ,
117+ 300 ,
118+ LOCALIZED_PLACEHOLDER_MESSAGES_SCHEMA ,
119+ 'low' ,
120+ ) ;
121+
122+ if ( result . error ) {
123+ throw new Error ( result . error ) ;
124+ }
125+
126+ if ( ! result . content ) {
127+ throw new Error ( 'Completion adapter returned an empty localized placeholder response' ) ;
128+ }
129+
130+ const parsed = JSON . parse ( result . content ) as { messages : string [ ] } ;
131+
132+ if ( ! Array . isArray ( parsed . messages ) || parsed . messages . length !== EN_PLACEHOLDER_MESSAGES . length ) {
133+ throw new Error ( 'Completion adapter returned invalid localized placeholder messages' ) ;
134+ }
135+
136+ return parsed . messages . map ( ( message ) => message . trim ( ) ) ;
137+ }
138+
139+ export async function getLocalizedPlaceholderMessages ( {
140+ completionAdapter,
141+ httpExtra,
142+ } : {
143+ completionAdapter : CompletionAdapter ;
144+ httpExtra : HttpExtra ;
145+ } ) : Promise < string [ ] > {
146+ const countryCode = getClientCountry ( httpExtra ) ;
147+ const topLanguage = getTopTerritoryLanguage ( countryCode ) ;
148+
149+ if ( ! topLanguage ) {
150+ return [ ...EN_PLACEHOLDER_MESSAGES ] ;
151+ }
152+
153+ const cacheKey = `${ countryCode } :${ topLanguage } ` ;
154+ const cachedMessages = localizedPlaceholderMessagesCache . get ( cacheKey ) ;
155+
156+ if ( cachedMessages ) {
157+ return cachedMessages ;
158+ }
159+
160+ const localizationPromise = translatePlaceholderMessages ( {
161+ completionAdapter,
162+ countryCode,
163+ language : topLanguage ,
164+ } ) . catch ( ( error ) => {
165+ console . error ( 'Failed to localize agent placeholder messages' , error ) ;
166+ return [ ...EN_PLACEHOLDER_MESSAGES ] ;
167+ } ) ;
168+
169+ localizedPlaceholderMessagesCache . set ( cacheKey , localizationPromise ) ;
170+
171+ return localizationPromise ;
172+ }
0 commit comments