From f9285ab69e1ae33a42af874b4cb4ddf822e13bd0 Mon Sep 17 00:00:00 2001 From: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com> Date: Sat, 1 Aug 2026 09:27:41 +0800 Subject: [PATCH 1/3] fix(environment): localize the entitlement dialog + read cloud's nested error envelope MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../environment/EnvironmentListToolbar.tsx | 4 +- .../__tests__/entitlements.test.ts | 90 +++++++++- .../app-shell/src/environment/entitlements.ts | 154 ++++++++++++++---- .../useConsoleActionRuntime.test.tsx | 9 + .../src/hooks/useConsoleActionRuntime.tsx | 9 +- packages/i18n/src/locales/ar.ts | 14 ++ packages/i18n/src/locales/de.ts | 14 ++ packages/i18n/src/locales/en.ts | 19 +++ packages/i18n/src/locales/es.ts | 14 ++ packages/i18n/src/locales/fr.ts | 14 ++ packages/i18n/src/locales/ja.ts | 14 ++ packages/i18n/src/locales/ko.ts | 14 ++ packages/i18n/src/locales/pt.ts | 14 ++ packages/i18n/src/locales/ru.ts | 14 ++ packages/i18n/src/locales/zh.ts | 16 ++ 15 files changed, 374 insertions(+), 39 deletions(-) diff --git a/packages/app-shell/src/environment/EnvironmentListToolbar.tsx b/packages/app-shell/src/environment/EnvironmentListToolbar.tsx index 1b0b59a7f6..ca967220e8 100644 --- a/packages/app-shell/src/environment/EnvironmentListToolbar.tsx +++ b/packages/app-shell/src/environment/EnvironmentListToolbar.tsx @@ -93,7 +93,7 @@ export function EnvironmentListToolbar({ actions, entitlements, onUpgrade }: Pro // onUpgrade sets parent state. useEffect(() => { if (autoRunCreate && ctaKind === 'upgrade_for_development' && entitlements) { - onUpgrade(upgradeDialogSpec(entitlements)); + onUpgrade(upgradeDialogSpec(entitlements, t)); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [autoRunCreate, ctaKind]); @@ -114,7 +114,7 @@ export function EnvironmentListToolbar({ actions, entitlements, onUpgrade }: Pro )}