fix(environment): localize the entitlement dialog + read cloud's nested error envelope - #3130
Merged
Conversation
…ed error envelope
The free-plan "Development environments are a paid feature" prompt is a
paid-conversion surface, and it was hardcoded English in an otherwise
localized console (cloud#959). Two separate defects behind that:
**Copy was English literals.** `upgradeDialogSpec` built the sentence users
actually reported — including its lowercase `your free plan`, which came from
string concatenation here, not from the control plane. Both spec builders now
take a translator and resolve `environment.entitlement.*`; all ten locale
packs carry the strings (objectui#2872 parity). `entitlements.ts` stays
dependency-free — `t` is passed in, not imported, and defaults to the English
copy with local `{{token}}` interpolation.
The dialog now renders the CONSOLE's copy rather than the server's prose. A
control plane upgrades independently of the console and only localizes these
messages from cloud#959 on, so preferring the server string would have left
the reactive path English against every older deployment — and the two paths
are meant to read identically anyway.
**The reactive dialog had stopped firing.** cloud#948 moved coded errors into
a nested envelope (`{ success, error: { code, message, … } }`);
`entitlementDialogFromError` read `code` off the top level, so it returned
null for every entitlement 403 and the friendly upgrade dialog silently
degraded to a generic red error toast. It now reads both shapes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Contributor
❌ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
Contributor
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
Verified in a zh-CN console: title, body and the upgrade CTA all rendered Chinese while the dismiss button still read "Close". It is the dialog's own chrome rather than part of the spec, so it resolves `common.close` — a key every locale pack already carries. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Contributor
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Companion to objectstack-ai/cloud#959. That issue reported the free-plan paywall as a cloud-side hardcoded string; tracing it, the sentence users actually see comes from here.
The reported dialog is built client-side
A free-plan org that already owns its production env gets
decideEnvironmentCta → 'upgrade_for_development', so clicking + 新建环境 opensupgradeDialogSpec()without ever calling cloud. That builder concatenated:with
planLabel = 'your free plan'— which is exactly the reported text, lowercaseyourand all. (Cloud's own copy reads differently and has always been capitalized; it is the reactive 403 body, on a path this user never reached.)What changed
1. Copy moved into
@object-ui/i18n.upgradeDialogSpecandentitlementDialogFromErrornow take a translator and resolveenvironment.entitlement.*. All ten locale packs carry the strings, so the objectui#2872 parity gate stays green.entitlements.tskeeps its "dependency-free so it is trivially unit-testable" property:tis passed in, not imported, and defaults to the English copy with a local{{token}}interpolator. Existing callers without a translator behave exactly as before.2. The dialog renders the Console's copy, not the server's prose. Previously
serverMessage || fallback— the server string won. A control plane upgrades independently of the Console and only localizes these messages from cloud#959 onward, so preferring it would leave the reactive path English against every older deployment. The two paths were already meant to read identically (see the comment onupgradeDialogSpec); now they provably do — there's a test asserting the specs aretoEqual. Dynamic detail is preserved:DEV_ENV_LIMITinterpolates the server's owncurrent/limit.3. Bug fix — the reactive dialog had stopped firing at all. objectstack-ai/cloud#948 moved coded errors into a nested envelope:
entitlementDialogFromErrorreadcodeoff the top level, so against an up-to-date control plane it returnednullfor every entitlement 403 — anduseConsoleActionRuntimefell through to a generic red error toast instead of the friendly upgrade dialog with its CTA. It now reads both shapes.Tests
entitlements.test.ts— nested + legacy envelopes, plan naming, seat-count interpolation, localized output via an injected translator, proactive/reactive parity, and the capital-letter regression.useConsoleActionRuntime.test.tsx— its@object-ui/i18nmock gaineduseObjectTranslation.packages/app-shell+packages/i18n: 266 files / 2261 tests green (incl. the all-locale key-parity gate).Out of scope
The chip / status labels (
Propose blueprint,Awaiting Approval,Not yet built) tracked in #2458 item 4 — untouched here.🤖 Generated with Claude Code