@@ -68,7 +68,7 @@ import {
6868 type ExportCellMeta ,
6969} from './export-format.js' ;
7070import { importControlOptOutRefusal , prepareImportRequest } from './import-prepare.js' ;
71- import { readTemplateMode , templateFieldDefs } from './import-template.js' ;
71+ import { readTemplateMode , templateFieldDefs , templateText } from './import-template.js' ;
7272import { refuseRepeatedQueryParams } from './query-multiplicity.js' ;
7373import { refuseUnknownQueryParams } from './query-allowlist.js' ;
7474import { handleRouteError , logUnexpectedRouteError , mapDataError , nestedRefusalEnvelope } from './error-response.js' ;
@@ -200,6 +200,8 @@ export interface DataActionRouteHost {
200200 translateMetaItem : RestServer [ 'translateMetaItem' ] ;
201201 resolveMessageTranslator : RestServer [ 'resolveMessageTranslator' ] ;
202202 resolveSecurityService : RestServer [ 'resolveSecurityService' ] ;
203+ resolveI18nService : RestServer [ 'resolveI18nService' ] ;
204+ extractLocale : RestServer [ 'extractLocale' ] ;
203205 answerImportTemplate : RestServer [ 'answerImportTemplate' ] ;
204206}
205207
@@ -884,8 +886,15 @@ export function registerDataActionEndpoints(this: DataActionRouteHost, basePath:
884886 //
885887 // Values are formatted for readability from the object schema: lookup /
886888 // user fields resolve to a name (via injected $expand), select fields to
887- // their option label, booleans to 是/否, dates to YYYY-MM-DD. When the
888- // schema is unavailable the raw stored values stream through unchanged.
889+ // their option label, booleans to the caller's yes / no words, dates to
890+ // YYYY-MM-DD. When the schema is unavailable the raw stored values stream
891+ // through unchanged.
892+ //
893+ // [#24945] Boolean / toggle cells are written in the language of the
894+ // header row: `templateText(locale).booleanWords` (yes / no, or 是 / 否 for
895+ // a `zh*` locale), for the locale the header labels are translated to
896+ // (`Accept-Language`, then `?locale=`, then the i18n service's default).
897+ // The importer reads both languages back, so the file re-imports.
889898 //
890899 // [#8373] `datetime` cells render in the caller's BUSINESS timezone
891900 // (`ExecutionContext.timezone`), so the file agrees with the screen;
@@ -1084,7 +1093,7 @@ export function registerDataActionEndpoints(this: DataActionRouteHost, basePath:
10841093 const fieldsExplicit = ! ! fields && fields . length > 0 ;
10851094
10861095 // Field metadata drives readable formatting (lookup names, select
1087- // labels, 是/否 , formatted dates) and the $expand that resolves
1096+ // labels, yes / no words , formatted dates) and the $expand that resolves
10881097 // references. Best-effort: when the schema is unavailable the export
10891098 // falls back to raw values, byte-identical to the un-formatted path.
10901099 let metaMap = new Map < string , ExportCellMeta > ( ) ;
@@ -1111,13 +1120,23 @@ export function registerDataActionEndpoints(this: DataActionRouteHost, basePath:
11111120 }
11121121 return schema ;
11131122 } ;
1123+ // [#24945] The file's language, read once for both of its halves:
1124+ // the header labels below are translated with THIS i18n service,
1125+ // which `extractLocale` also reads the request's locale from (its
1126+ // default locale is the last fallback), and the boolean cells are
1127+ // written in the words of the same locale — the pair the import
1128+ // template offers (`templateText`, `./import-template.ts`). One
1129+ // service and one locale, so the header and the cells cannot read
1130+ // two different languages.
1131+ const i18n = await this . resolveI18nService ( environmentId , req ) . catch ( ( ) => undefined ) ;
1132+ const booleanWords = templateText ( this . extractLocale ( req , i18n ) ) . booleanWords ;
11141133 try {
11151134 let schema : any = await readObjectSchema ( objectName ) ;
11161135 // Localize field labels to the request locale (Accept-Language /
11171136 // `?locale=`) the same way the metadata endpoints do, so the
11181137 // export header row matches the UI column headers instead of
11191138 // leaking the raw, untranslated `field.label` values.
1120- schema = await this . translateMetaItem ( req , 'object' , environmentId , schema ) ;
1139+ schema = await this . translateMetaItem ( req , 'object' , environmentId , schema , i18n ) ;
11211140 if ( typeof schema ?. label === 'string' && schema . label . length > 0 ) {
11221141 objectLabel = schema . label ;
11231142 }
@@ -1291,15 +1310,15 @@ export function registerDataActionEndpoints(this: DataActionRouteHost, basePath:
12911310 }
12921311
12931312 if ( format === 'csv' ) {
1294- const text = rowsToCsv ( fields ?? [ ] , rows , firstChunk && includeHeader , metaMap , timezone ) ;
1313+ const text = rowsToCsv ( fields ?? [ ] , rows , firstChunk && includeHeader , metaMap , timezone , booleanWords ) ;
12951314 res . write ( text ) ;
12961315 } else if ( format === 'xlsx' ) {
12971316 if ( firstChunk && includeHeader ) {
12981317 xlsx ! . ws . addRow ( ( fields ?? [ ] ) . map ( ( f ) => headerLabel ( f , metaMap ) ) ) . commit ( ) ;
12991318 }
13001319 const cols = fields ?? [ ] ;
13011320 for ( const row of rows ) {
1302- const r = xlsx ! . ws . addRow ( formatRowCells ( row , cols , metaMap , timezone ) ) ;
1321+ const r = xlsx ! . ws . addRow ( formatRowCells ( row , cols , metaMap , timezone , booleanWords ) ) ;
13031322 if ( styled ) {
13041323 cols . forEach ( ( f , i ) => {
13051324 const argb = cellFontColor ( row ?. [ f ] , metaMap . get ( f ) ) ;
@@ -1320,7 +1339,7 @@ export function registerDataActionEndpoints(this: DataActionRouteHost, basePath:
13201339 for ( let i = 0 ; i < rows . length ; i ++ ) {
13211340 const prefix = ( firstChunk && i === 0 ) ? '' : ',' ;
13221341 const row = fieldsExplicit ? pickExportKeys ( rows [ i ] , fields ! ) : rows [ i ] ;
1323- res . write ( prefix + JSON . stringify ( formatRowForJson ( row , metaMap , timezone ) ) ) ;
1342+ res . write ( prefix + JSON . stringify ( formatRowForJson ( row , metaMap , timezone , booleanWords ) ) ) ;
13241343 }
13251344 }
13261345 firstChunk = false ;
0 commit comments