Skip to content

fix(plugin-email): sendTemplate binds renderOpts.locale to the resolved template row (#7801) - #8064

Merged
huangyiirene merged 1 commit into
mainfrom
claude/issue-7801-sendtemplate-resolved-locale
Aug 12, 2026
Merged

fix(plugin-email): sendTemplate binds renderOpts.locale to the resolved template row (#7801)#8064
huangyiirene merged 1 commit into
mainfrom
claude/issue-7801-sendtemplate-resolved-locale

Conversation

@huangyiirene

Copy link
Copy Markdown
Collaborator

Fixes#7801

Implements the maintainer's ruling recorded in comment 5265942667 (Option A): a no-locale sendTemplate binds renderOpts.locale to the resolved template row's locale. The row is the single locale authority; mixed-locale rendering is a defect, not a feature.

The seam

packages/plugins/plugin-email/src/email-service.tssendTemplate, the renderOpts construction. The card's anchor (:1185) was accurate on main; it did not mislead.

-const renderOpts = {- ...(input.locale ? { locale: input.locale } : {}),+const locale = preferred || row.locale;+const renderOpts = {+ ...(locale ? { locale } : {}),
...(input.timezone ? { timeZone: input.timezone } : {}),
};

preferred is the already-computed trimmed spelling the locale ladder resolved on, a few lines above.

⚠️ The card's stated symptom does not hold — the ruling is unaffected

The issue says the filters render "under the runtime locale". They never did. formatValue (packages/formula/src/template-engine.ts:155) hard-defaults to en-US:

returnfmt(value,arg,opts.locale??'en-US',opts.timeZone);

So the en-US/runtime mismatch the card describes is unreachable, and that is exactly why the split stayed invisible: whenever the resolved row was en-US, the row's locale and the filter default coincided. The defect is the mirror image — a bundle with no en-US row resolves e.g. zh-CN (the ladder's last rung) and renders en-US dates and numbers inside zh-CN body text.

The §5 STOP premise itself holds: renderOpts.localeis the value the filters read (renderTemplateformatValueIntl.*), and the resolved row is in scope at the seam. The ruling is implemented as written; only the direction of the observable symptom differs from the card.

Reverse verification

Pins live in the existing template-locale-resolution.test.ts (no new test file, no new workspace dependency).

Pinon mainafter
(a) no-locale → en-US row → en-US formatting✅ passes (vacuously — the filter default is also en-US)✅ passes
(a) no-locale → zh-CN row → zh-CN formattingfails[zh-CN] 3/5/26, 2:30 PM✅ passes — [zh-CN] 2026/3/5 14:30
(b) explicit input.locale wins (row exists)✅ passes✅ passes
(b) explicit input.locale wins on the en-US fallback row✅ passes✅ passes
padded ' de-DE ' renders instead of throwingfailsRangeError✅ passes

Pin (b) passing on main is the expected reading — it guards existing-correct behaviour against the fix over-reaching into "the row always wins". No second defect there.

⚠️ A second, previously unreported defect — fixed here, not folded in silently

An input.locale carrying whitespace (' de-DE ') resolved the de-DE row via the ladder's trimming, then passed the untrimmed tag to Intl, which throws RangeError: Incorrect locale information provided — taking the entire send down. Binding to preferred rather than input.locale closes it. Called out explicitly so it can be split out if the reviewer would rather it landed on its own.

Gates

  • packages/plugins/plugin-email: 339/339 tests pass, tsc --noEmit clean, eslint clean.
  • pnpm check:test-source-aliasOK, 72 packages scanned. (No new test file and no new cross-package import; the only added dependency is the Intl global.)
  • pnpm check:type-check-debtOK (self-test + --re-measure with the full package closure built): 35 ledger entries re-measured, none above its recorded number. No ledger entry raised.
  • Not applicable and not touched: engine fakes (check:engine-double-contract), ADR-0112 refusals, packages/spec/src/** (so no gen:schema / gen:docs), content/docs/releases/**.

Changeset

.changeset/sendtemplate-resolved-row-locale.md@objectstack/plugin-email patch.


Generated by Claude Code

…ed template row (#7801)
A `sendTemplate` call that named no `locale` resolved a concrete template row
(#7731) but left `renderOpts.locale` unset, so the locale-sensitive format
filters fell through to `formatValue`'s own `?? 'en-US'` default instead of
following the row they were rendering into. Per the maintainer's ruling on
#7801 the template row is the SINGLE locale authority; mixed-locale output —
a row's body text in one locale, its dates and numbers in another — is a
defect, not a feature.
The seam is `email-service.ts`'s `sendTemplate` renderOpts construction: it now
binds `preferred || row.locale` rather than the raw `input.locale`.
Note on the card's framing: it reported the filters rendering "under the
RUNTIME locale". They never did — `formatValue` hard-defaults to en-US — which
is why the split stayed invisible whenever the resolved row happened to BE
en-US. The observable defect is the mirror image: a bundle with no en-US row
resolves e.g. zh-CN and renders en-US dates inside zh-CN body text. The ruling
is unaffected; only the direction of the symptom is.
Binding to `preferred` (the trimmed spelling the locale ladder actually
resolved on) also fixes a second, previously unreported defect: an
`input.locale` carrying whitespace resolved its row and then threw
`RangeError: Incorrect locale information provided` out of `Intl`, taking the
whole send down.
Pins in `template-locale-resolution.test.ts` cover both halves of the ruling —
the resolved row drives the filters when the caller named none, and an explicit
`input.locale` still wins, including when it falls back to the en-US row.
Fixes#7801
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LEZfvePJ4bpEBmvBEBEKpa
@vercel

vercelBot commented Aug 12, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
ProjectDeploymentActionsUpdated (UTC)
objectstackIgnoredIgnoredAug 12, 2026 2:09pm

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/plugin-email.

4 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/automation/flows.mdx(via @objectstack/plugin-email)
  • content/docs/deployment/environment-variables.mdx(via @objectstack/plugin-email)
  • content/docs/plugins/packages.mdx(via @objectstack/plugin-email)
  • content/docs/protocol/kernel/index.mdx(via @objectstack/plugin-email)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

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

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

finding: a no-locale sendTemplate resolves the en-US row but renders format filters under the runtime locale — renderOpts.locale stays unset

2 participants

@huangyiirene@claude