Repository navigation
fix(rest): export boolean cells follow the caller's language - #24998
Conversation
Red on main: an Accept-Language en-US caller gets the English header row over boolean cells written as the literal 是 / 否, in csv, xlsx and json. Claude-Session: https://claude.ai/code/session_01LmjU2x1faLZ39DVTFyVca6 Co-authored-by: Claude <noreply@anthropic.com>
GET /data/:object/export wrote every boolean / toggle cell as the literal 是 / 否, while the header row of the same file followed the caller's locale. The route now resolves the request's i18n service once, translates the header with it, and writes the boolean cells in templateText(locale).booleanWords for the locale extractLocale reads off that same service: yes / no, or 是 / 否 for a zh* locale. csv, xlsx and json alike. The importer reads both languages back. Existing pins on 是 / 否 named no locale: the Chinese fixtures now name a zh-CN caller (their recorded bytes are unchanged); the English fixture expects yes / no. Claude-Session: https://claude.ai/code/session_01LmjU2x1faLZ39DVTFyVca6 Co-authored-by: Claude <noreply@anthropic.com>
… language Claude-Session: https://claude.ai/code/session_01LmjU2x1faLZ39DVTFyVca6 Co-authored-by: Claude <noreply@anthropic.com>
…port-boolean-locale
check:query-options-erasure counts an options bag cast to any; the re-import pins pass a typed orderBy instead. Claude-Session: https://claude.ai/code/session_01LmjU2x1faLZ39DVTFyVca6 Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): 5 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 4 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 19 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin e003539fddf28c6a73ab9c8f1303ce7a7e725f55 && git checkout e003539fddf28c6a73ab9c8f1303ce7a7e725f55
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 4b8f597ba357ba45c7b4b4a4504c78b2a079f123 3d3bafba2842e08acbfc9de0409d368a1e30ba9e && git checkout -B drift-repro 4b8f597ba357ba45c7b4b4a4504c78b2a079f123 && git merge --no-ff 3d3bafba2842e08acbfc9de0409d368a1e30ba9e
node scripts/docs-audit/affected-docs.mjs --json 4b8f597ba357ba45c7b4b4a4504c78b2a079f123
|
席位核对 · ACCEPT
Accepted deviations:
Acceptance notes (one line each):
Landing: through the merge queue by this seat once every check on this head reads Generated by Claude Code |
Fixes #24945
Clause-②: no
What changes
GET /api/v1/data/:object/exportwrote every boolean or toggle cell as the literal是/否. The header row of the same file follows the caller's locale, so anAccept-Language: en-UScaller got "Converted" and "Do Not Call" over Chinese cells. The cell now follows the same locale as the header.packages/rest/src/export-format.tsformatCellValue,formatRowCellsandformatRowForJsontakebooleanWordsbesidetimezone. The boolean / toggle branch writesbooleanWords[0]/booleanWords[1].templateText(locale).booleanWordsinimport-template.ts, the table the import template already uses (yes/no, or是/否for azh*locale). The newExportBooleanWordstype is that table's own field type. No second table.templateText(undefined)answers (yes/no). That is the same answer the import template gives a request with no locale.packages/rest/src/data-action-routes.ts, the export routeresolveI18nService, thenextractLocale(req, i18n)). That is the same readanswerImportTemplateuses. It computes the words once per export from that locale.translateMetaItem(its existing optional fifth argument). So the header translation and the words read one locale, from one service:Accept-Language, then?locale=, then the service's default locale.packages/rest/src/rest-server.ts. This file is outside the claimed file surface. See Deviations.DataActionRouteHostliteral gainsresolveI18nServiceandextractLocale. The route had no locale in hand: the header's locale was resolved insidetranslateMetaItemand never left it.rowsToCsv, the csv arm's serializer, lives here. It takesbooleanWordsaftertimezoneand passes them toformatRowCells.packages/rest/src/import-template.ts: one import line.buildFieldMetaMap/ExportFieldMetanow come from@objectstack/core, where they are defined, and no longer throughexport-format.ts's re-export.export-format.tsnow importstemplateTextfrom here, and this keeps the two modules from importing each other.dist/index.d.ts,booleanWords,ExportBooleanWords,templateTextandrowsToCsveach count 0 hits, and the controlExportFieldMetacounts 3.Readings on the mechanism assumptions
All readings come from the real export and import routes, on a real ObjectQL engine and protocol with a sqlite
:memory:driver (export-boolean-locale.test.ts).是/否to an English caller, in every format. At base78ab6474ca, anen-UScsv export reads headerID,Full Name,Converted,Do Not Call, then row1,Wren Halvorsen,是,否. xlsx gives the same cells. json gives"是"/"否"as strings: the json arm runs the same branch throughformatRowForJson, so it never kept rawtrue/false. With this PR, json writes the caller's words as strings. It does not switch to raw booleans, because that would move a payload shape.zh-CNcaller keeps是/否, under the Chinese header.templateText(undefined)answers the English table, so a request with no locale getsyes/no.zh-CNdefault and for anendefault.IMPORT_BOOLEAN_TRUE_TOKENS/IMPORT_BOOLEAN_FALSE_TOKENSinpackages/spec) readsyes/noand是/否. Each language's csv and xlsx export, posted toPOST /api/v1/data/:object/importinto a fresh store, reads back as[true, false]/[false, true]. All four pins are green.是/否" in its plan. It was written before the export resolved any locale, it has no comments, and its author is a contributor. 导出/导入文件名与多语言优化:显示名+视图名+时间戳命名、模板全中文、导出↔导入选项标签闭环 #2916 made export and import follow the caller's language and does not mention booleans. Neither is a maintainer ruling that booleans are是/否for every locale.Evidence
d913467d6e, which is the new test file alone on base78ab6474ca:Tests 5 failed | 6 passed (11). The red pins are en csv, the xlsx pair, the json pair, the default-locale pin (itsenhalf) and the no-i18n pin. The 6 green ones are the zh csv control,?locale=zh-CN, and the four re-import pins: main's是/否file re-imports.export-boolean-locale.test.ts:Tests 11 passed (11). The 11 affected export / import files:Tests 620 passed (620).templateText(this.extractLocale(req, i18n)).booleanWordswithtemplateText(undefined).booleanWordsthroughscripts/ablation-replace.mjs, ondata-action-routes.ts(anchor 1 → 0, blob53f7b7e9→5c3ec476).Tests 5 failed | 6 passed (11). Every zh pin went red: csv, the xlsx and json pairs,?locale=, and the default-locale pin.templateText(undefined), which is the English pair. So the en pin's red reading is the main-red above, not this ablation.53f7b7e9, andgit diff HEADis empty.this.extractLocale(req, i18n)withthis.extractLocale(req, undefined)(blob53f7b7e9→8299404a).Tests 1 failed | 10 passed (11). The red pin is the default-locale one: azh-CN-default deployment wrote the header编号,姓名,已转化,请勿致电overyes/nocells.53f7b7e9, andgit diff HEADis empty.src/, reached by relative import from the test. Nodist/rebuild was needed.Fixture triage
The existing pins on
是/否named no locale. Each was triaged by its fixture's language, so its header and cells keep one language:zh-CNcaller. These areexport-integration.test.ts,rest.test.ts's mocked export suite, andimport-template-route.test.ts'sexportOnce.export-json-fields.test.ts(labelsID/Title, no locale) now expectsyes/no.Gates (final head
3d3bafba28)node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderived 68 commands. All 68 exited 0.check:init-service-contract,live-db-isolation,meta-type-normalized,optional-error-sink,resume-authority-declared,runner-env-posture,settings-bind-window,startup-registry-verdict,verify-stand-inandwildcard-fallthrough.--ranreconciliation:✓ dispatch-gates --ran: 68 derived famil(ies) accounted for — 68 run, 0 NOT-MEASURED (a DERIVED zero — all 68 recorded an exit code and none of them is 3).pnpm lint(eslint . --no-inline-config): exit 0, no findings printed.pnpm --filter @objectstack/rest exec vitest run --project local:Test Files 307 passed (307),Tests 6165 passed | 326 skipped (6491).pnpm --filter @objectstack/rest typecheck: exit 0. The test layer compiles the new file:--listFilescountsexport-boolean-locale.test.tsonce.check:query-options-erasure(test surface 236 → 238). The cause was twoas anycasts on the re-import pins'engine.findoptions. They are typed now, and the gate is green at the final head.Deviations
packages/rest/src/rest-server.ts. The fix needs it twice: once for the host wiring, because the route had no locale, and once forrowsToCsv, because the csv serializer lives there. The edits come to two host entries, one import name and therowsToCsvparameter with its doc.Acceptance notes
yes/nowhile the header shows the authored labels. A deployment with Chinese-authored labels, no i18n service and a header-less caller would show a Chinese header over English words. The import template behaves the same way in that case (templateText(undefined)). A deployment that registers the i18n service gets its default locale for both halves."yes"/"是"), as they were ("是") before this PR. That is the formatting layer's existing design, unchanged here.Generated by Claude Code