Skip to content

action copy templates: a bulk param named locale sets the formatter locale of the whole confirm sentence, because the template engine reads its locale from the same extra roots #26232

Closed

Description

@objectstack-fleet

Filing gate ②: a seat ruling on an out-of-scope finding. On #25942 the domain:ui seat 2 ruled B: the locale question goes on its own engine card and is not folded into the bulk-confirm fix. ⛔ Not a claim.

The template engine takes the locale its formatters use from ctx.extra.locale (packages/formula/src/template-engine.ts:276). The console's action-copy filler passes the template's roots in that same extra object (ui/packages/core/src/actions/actionCopyTemplate.ts:82, templateEngine.evaluate({ dialect: 'template', source }, { extra: roots })). Since #25942, a bulk action's params are template roots. So a param named locale silently sets the formatter locale of the whole confirm sentence. When there is no such param, formatters render in en-US rather than the viewer's language.

Business reason. A bulk confirm sentence such as "Set the region to {{ region }} on {{ count }} records" formats its numbers and dates in en-US for a Chinese viewer. A param that happens to be called locale (a plausible field on any localization object) changes how every formatted value in the sentence renders.

Who acts. Whoever owns @objectstack/formula's template evaluation context. The likely shape is a dedicated locale slot on the evaluate context, kept apart from the author-visible roots, which the console's filler passes from the display locale. The renderer side is fillTemplateHoles in ui/packages/core. Triage sets domain and route.

Found by. The os-dev report on #25942 (comment 6420654854, step 2 / PR #26217), out_of_scope_findings.

Duplicate check. Searched for "template engine extra locale", "formatter locale hijack" and "fillTemplateHoles locale" in the 40 newest issues. 0 hits.

Activity

  1. added
    bugSomething isn't working
    domain:engineRetired 2026-10-22, alias of domain:server (engine surface); in-flight cards keep it until they land
    area:i18nThe customer's own language, across UI, metadata and notifications
    on Oct 26, 2026
  2. objectstack-fleet commented on Oct 26, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: first grade — bug · priority:p3 · domain:server (lane merge: domain:engine → domain:server) · area:i18n · pm:queue · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-26T16:57Z

    ⛔ Not a claim, ⛔ not a dispatch.


    Generated by Claude Code

  3. added
    domain:serverDomain lane — seat card indexed by label:pm:seat
    and removed
    domain:engineRetired 2026-10-22, alias of domain:server (engine surface); in-flight cards keep it until they land
    on Oct 26, 2026
  4. objectstack-fleet commented on Oct 26, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 3 · card thread
    Session: session_0186vfjHHMsRXC9EUT9hTFMo (the seat); the card thread is session_01XbF1LGGBRTWwTfLay1d51s
    Account: os-project-manager (the seat's linked user; the card's assignee)
    Branch: claude/project-thread-vqfv9q (the card thread's harness-pinned branch; it names no issue, so this claim is the identity record)
    Worktree: the card thread's own container checkout
    Domain: domain:server
    Seat: domain:server#2
    Ruling-ref: triage 6422123580 (p3, lane merge domain:engine → domain:server), dispatched under the maintainer's 「p1 p2 处理完了,就可以处理 p3」 (2026-10-26T10:29Z); domain:ui seat 2 ruling B on #25942 — the template engine reads its formatter locale from a reserved channel on the evaluate context, never from an author-visible root (⛔ no second copy of the locale resolution; the console filler passes the display locale it already has)
    File surface: packages/formula/src/template-engine.ts and its evaluate context, ui/packages/core/src/actions/actionCopyTemplate.ts (the filler), every other caller that passes extra.locale today, their tests, one changeset. ⛔ No packages/spec/** (if the evaluate context is a published spec type, stop and put it in open_questions with status: needs_decision)
    Container & model: M, bug, mode:thread, default model and effort (set at thread open)
    Clause-②: yes (provisional: a root named locale that used to set the formatter locale stops doing so; the dev declares from the real diff)
    Metadata-protocol: none
    Responsibility: graded by triage 6422123580 from the #25942 out-of-scope finding
    Thread-read: 6422123580
    Premise: holds (read 2026-10-26T18:12Z at origin/main 9598405ec9: card open, unassigned, pm:queue, no claim on the thread)
    Resume-from: none — fresh start from origin/main
    Serial constraints cleared: no open PR touches packages/formula/src/template-engine.ts or actionCopyTemplate.ts known to the seat at this act; the dev re-measures every open PR on those paths before the first edit and before pr_create


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 26, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 26232,
      "status": "done",
      "branch": "claude/project-thread-vqfv9q",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/26282",
      "session": "session_01XbF1LGGBRTWwTfLay1d51s",
      "premise_still_valid": true,
      "summary": "Premise held at base 9598405ec: a probe through templateEngine.evaluate rendered 'Set 1,234,567.50 ... due May 6, 2031' with no locale root and 'Set 1.234.567,50 ... due 6. Mai 2031' with extra.locale = 'de-DE', and fillTemplateHoles passed only { extra: roots }, so no viewer locale was reached. Fix: EvalContext gains a reserved locale field (not bound as a variable, never set by a root) and the template engine reads its formatter locale from it alone (en-US when unset or empty); the extra.locale rung is gone. Console: fillTemplateHoles(source, roots, locale?) passes it on that channel; ActionRunner.setDisplayLocale(read) is read per fill for confirmText and successMessage and kept across a callContext dispatch; ActionProvider and useActionRunner install useDisplayLocale() (no second locale resolution). Every caller that passed extra.locale was measured: only formula's own template-formatters test (moved to locale), plus service-automation renderTextSlot, whose doc named a flow variable 'locale' as the formatters' locale; under the ruling that variable is now a value (0 in-repo producers), doc line corrected. Clause-② declared from the diff: yes (narrowing) - EvalContext and fillTemplateHoles widen, a root named locale stops setting the formatter. Metadata-protocol: none, patch x4, BREAKING banner, ADR-0087 not-required (no-migration-prescription). The bulk confirm dialog is not wired to the display locale here because open draft #26217 rewrites bulkConfirmText.ts and BulkActionDialog.tsx; the hijack is closed for it by the engine change.",
      "tests": "head 6507217d8. formula: pnpm --filter @objectstack/formula test -> Test Files 53 passed, Tests 1427 passed; typecheck exit 0 (check:test-typecheck OK). service-automation (closure built): Test Files 207 passed, Tests 2699 passed; typecheck exit 0. ui core: pnpm --filter @object-ui/core test -> Test Files 213 passed, Tests 4097 passed / 27 skipped; type-check exit 0. ui react: pnpm exec vitest run packages/react/ (from ui/; the package script answers 'vitest: not found') -> Test Files 120 passed, Tests 1483 passed; type-check exit 0. plugin-grid bulk suites: 27 files / 264 passed. New pins: formula template-engine.test.ts 'formatter locale channel' (3), automation text-slot-template.test.ts (1), core ActionRunner.copyTemplateLocale-26232.test.ts (7), react ActionProvider.copyTemplateLocale-26232.test.tsx (4); expected strings computed with Intl in-test (ICU drift safe). Ablation at 7f9627df via scripts/ablation-replace.mjs (formula tests import ./template-engine from src, no dist leg): extra.locale rung restored ahead of ctx.locale -> 'a root named locale is a value a hole prints, not the formatter locale' FAILED (1 failed / 10 passed); restore: blob a2e9acd8fba9 == HEAD, git diff HEAD empty.",
      "gates": "local, final head 6507217d8: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack -> 80 commands, each run with exit captured before any pipe: 80 x exit 0; --ran reconciliation: '80 derived famil(ies) accounted for - 80 run, 0 NOT-MEASURED'. Along the way: check-plugin-teardown-shape --self-test exit 3 on the shallow clone until its control 621a4876 was fetched; check-sdui-manifest exit 1 until node scripts/gen-sdui-manifest-node.mjs re-recorded core and react (committed); check:dual-build-cjs-loads exit 3 until the full build. Full pnpm build exit 0; check:dual-build-cjs-loads exit 0; check:dts-closure exit 0. Lint (eslint on the 13 touched TS files, --no-inline-config): 0 errors, 29 warnings, the same set the base versions of those files carry. CI: 0 check-runs and 0 workflow runs on 6507217d8 at report time (relay-opened draft); NOT MEASURED: every CI family, reason: no run has started on the PR head.",
      "dogfood_battery": "6507217d8 · os-verify-lock: VERDICT command-exit 0 (held 773s) · Test Files 297 passed / 3 skipped (300) · Tests 2527 passed / 18 skipped (2545) · no failures",
      "mcp_calls": "0 - no MCP GitHub calls; reads via gh api REST",
      "api_writes": "3 - relay pr_create (POST /repos/objectstack-ai/objectstack/pulls, POST /repos/objectstack-ai/objectstack/issues/26282/assignees) and this os-dev-report comment (POST /repos/objectstack-ai/objectstack/issues/26232/comments); git push not counted",
      "open_questions": [],
      "out_of_scope_findings": [
        "carrier: #26217 · the bulk confirm dialog (BulkActionDialog -> bulkConfirmText -> fillTemplateHoles) still formats in en-US for every viewer; after this PR it is one argument, useDisplayLocale() into fillTemplateHoles's third parameter, which #26217 (open draft, rewrites both files) can carry or a Blocked-by #26217 follow-up can; left out here to avoid colliding with it · dedupe words: bulk confirm display locale, fillTemplateHoles locale, BulkActionDialog useDisplayLocale",
        "carrier: none · noted, not filed · flow text slots (notify/screen/end/http via renderTextSlot) format in en-US: no flow-level locale reaches the engine now that a variable named locale is a plain value; notify already documents its title/message as one text for every recipient · in PR Acceptance notes"
      ]
    }

    Generated by Claude Code

  6. objectstack-fleet commented on Oct 26, 2026

    @objectstack-fleet
    ContributorAuthor

    2026-10-26T20:23Z · domain:server seat 2 · ACCEPT → PR #26282 comment 6426471206, contract review 6426083828 PASS (landing through the queue).


    Generated by Claude Code

  7. objectstack-fleet commented on Oct 26, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed

    2026-10-26T20:50Z · domain:server seat 2 · os-project-manager · session_0186vfjHHMsRXC9EUT9hTFMo. PR #26282 merged at 2026-10-26T20:50Z by the merge queue as 14b55970e6 on main (Fixes #26232; ACCEPT 6426471206, contract review 6426083828 PASS). The merge closed this card as completed.

    Reading Probe Result
    Queue membership git ls-remote origin 'refs/heads/gh-readonly-queue/main/pr-26282-*' 0 refs (left the queue)
    Ancestry git merge-base --is-ancestor 14b55970e6 origin/main holds
    Card state GET /repos/objectstack-ai/objectstack/issues/26232 closed, completed

    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

area:i18nThe customer's own language, across UI, metadata and notificationsbugSomething isn't workingdomain:serverDomain lane — seat card indexed by label:pm:seatpm:dispatchedpriority:p3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions