Skip to content

Commit 76e878c

Browse files
committed
Merge remote-tracking branch 'origin/main' into claude/issue-14968-handshake-field
2 parents 025baf1 + fa85759 commit 76e878c

23 files changed

Lines changed: 1134 additions & 90 deletions

.changeset/great-clouds-repair.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
'@objectstack/plugin-hono-server': patch
3+
---
4+
5+
`GET /auth/me/localization` answers the deployment's resolved `currency` and `timezone` instead of `null`
6+
7+
The handler read both off the request `ExecutionContext`, citing ADR-0053, but the resolver serving this surface is a hand-rolled envelope that never carried them — so every authenticated caller was answered `currency: null, timezone: null` whatever the `localization` settings said, and the console's regional-formatting seed was fed nulls. All three values now come from one reading of the same `resolveLocalizationContext` cascade the dispatcher's shared assembler uses. `locale` resolution is unchanged. `timezone` now always answers (cascade floor `UTC`); `currency` still answers `null` when the deployment configures none — that value has no floor.
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
---
2+
"@objectstack/rest": patch
3+
---
4+
5+
fix(rest): metadata label lookup honours the stack's declared `i18n.fallbackLocale` / `defaultLocale` instead of falling through to the `en` bundle (#14882)
6+
7+
On a workspace whose labels are authored in `zh-CN` (`defaultLocale: 'zh-CN'`,
8+
`fallbackLocale: 'zh-CN'`) and which ships only a courtesy `en` translation bundle,
9+
`GET /api/v1/meta/object/:name`, the `/meta/:type` list, `GET /api/v1/meta` and the
10+
public-form schema served the ENGLISH bundle labels to a `zh-CN` request (`Entry Sheet`
11+
for an authored `填报单`, `KPI Assessment` for `KPI 考核管理`). The document translators walk
12+
`requested locale → fallback chain → authored label` and default the chain to a literal
13+
`['en']`; every REST seam passed none, so the declared fallback never reached the chain
14+
and `en` was consulted before the authored label.
15+
16+
Every metadata translation seam now passes `fallbackChain: [i18n.getFallbackLocale()]`
17+
the locale the i18n service's own `t()` falls back to, which `I18nServicePlugin` receives
18+
from the stack config as `fallbackLocale || defaultLocale || 'en'`. For the workspace
19+
above a `zh-CN` request now resolves `zh-CN → zh-CN → authored label` (the authored
20+
Chinese labels), an `en` request still gets the `en` bundle, and a `zh-CN` bundle, when one
21+
is shipped, still wins over the authored label.
22+
23+
Feature-detected: an i18n service that does not declare a fallback (the method is
24+
optional on `II18nService`; the core in-memory fallback has none) gets no chain and the
25+
resolver's own default applies exactly as before. A stack declaring `defaultLocale: 'zh-CN'`
26+
with `fallbackLocale: 'en'` is likewise unchanged — the declared `en` is honoured as it
27+
reads.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
"@objectstack/service-i18n": minor
3+
---
4+
5+
feat(service-i18n): `FileI18nAdapter.getFallbackLocale()` reports the `fallbackLocale` the adapter was constructed with (#14882)
6+
7+
Implements the new optional `II18nService.getFallbackLocale()`. `I18nServicePlugin`
8+
already receives `fallbackLocale || defaultLocale || 'en'` from the stack's `i18n`
9+
config on both boot paths (`os serve`, the dev plugin); this makes that declaration
10+
readable, so the REST metadata reads pass the document translators the same fallback
11+
locale `t()` itself consults. Returns `undefined` when no `fallbackLocale` was given.
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
---
2+
"@objectstack/spec": minor
3+
---
4+
5+
feat(spec): `II18nService.getFallbackLocale()` — the declared fallback locale is readable, so the metadata-document translators can be handed the chain the deployment declared (#14882)
6+
7+
`ResolveOptions.fallbackChain` on the `@objectstack/spec/system` label
8+
resolvers (`translateMetadataDocument`, `translateObject`, `translateApp`,
9+
`resolveViewLabel`, …) is the ordered list of locales consulted after the
10+
requested one and BEFORE the authored label. Nothing on `II18nService`
11+
exposed the deployment's declared fallback (`i18n.fallbackLocale`, else
12+
`defaultLocale`), so no serving layer could thread it, and every caller fell
13+
to the resolver's literal `['en']` default. A `zh-CN` workspace that shipped a
14+
courtesy `en` bundle therefore served English bundle text to a `zh-CN`
15+
request ahead of its own authored Chinese labels.
16+
17+
- New optional contract member `II18nService.getFallbackLocale?(): string | undefined`
18+
— the locale the service's own `t()` consults second. `undefined` (or the
19+
method absent) means nothing was declared, and a serving layer must then
20+
leave the resolver's default in place rather than invent a chain.
21+
- The `fallbackChain` documentation now states who supplies it (the serving
22+
layer, from `getFallbackLocale()`) and that the `['en']` default applies
23+
only when a caller declares no chain at all. The resolver's behaviour for
24+
a caller that passes nothing is unchanged.
25+
26+
Additive: no existing implementation or caller changes shape.

content/docs/permissions/system-context.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ The largest single consumer — **17 of the 106 sites**.
158158
|:--|:---|:---|:---|:---|
159159
| 48 | Object API-exposure gate bypassed (`apiEnabled` / `apiMethods`) | runtime | Get: internal self-writes ignore exposure declarations — these govern **external** exposure, not engine self-writes | `action-execution.ts:138` |
160160
| 49 | Action `requiredPermissions` bypassed | runtime | Get: engine self-invocation runs any action | `action-execution.ts:401` |
161-
| 50 | `manage_metadata` bypassed on metadata writes | runtime, rest | Get: schema writes without the capability | `domains/meta.ts:471`, `:874`, `rest-server.ts:5048`, `:6474`, `:6722`, `:7153`, `:7346` |
161+
| 50 | `manage_metadata` bypassed on metadata writes | runtime, rest | Get: schema writes without the capability | `domains/meta.ts:471`, `:874`, `rest-server.ts:5084`, `:6510`, `:6758`, `:7189`, `:7382` |
162162
| 51 | The shared metadata-write verdict itself returns `allowed` | metadata-core | Get: the one function all of row 50's doors consult answers yes before any capability is examined | `meta-write-capability.ts:134` |
163163
| 52 | Anonymous-deny seam satisfied on the domain dispatchers and the package/federation routes | runtime, rest | Get: passes with no `userId` | `domains/actions.ts:421`, `domains/ai.ts:60`, `domains/automation.ts:989`, `domains/meta.ts:232`, `domains/security.ts:78`, `domains/packages.ts:422`, `external-datasource-routes.ts:302`, `package-routes.ts:97` |
164164
| 53 | MCP principal check satisfied | runtime | Get: MCP surface reachable with no user | `domains/mcp.ts:61` |

content/docs/ui/translations.mdx

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,20 +66,24 @@ export default defineStack({
6666
| Field labels, help text, placeholders | `objects.<name>.fields.<field>.label` / `help` / `placeholder` |
6767
| Picklist option labels | `objects.<name>.fields.<field>.options.<value>` |
6868
| View titles, descriptions, empty states | `objects.<name>._views.<view>` |
69+
| Bulk-action copy on a list view (button, confirm prompt, dialog fields) | `objects.<name>._views.<view>.bulkActions.<def>.label` / `.confirmText` / `.confirmLabel` / `.params.<param>.label` / `.help` / `.placeholder` — a bulk param's hint is `help`, not `helpText` |
6970
| Action labels, confirm text, success messages | `objects.<name>._actions.<action>` |
7071
| Action result dialogs (title / description / acknowledge / field labels) | `objects.<name>._actions.<action>.resultDialog` |
7172
| Form sections | `objects.<name>._sections.<section>` |
7273
| Custom validation-rule messages | `objects.<name>._validations.<rule>.message` |
7374
| App navigation | `apps.<app>.navigation.<id>.label` |
7475
| Dashboard label / description | `dashboards.<name>.label` / `description` |
7576
| Dashboard widget title / description / sub-caption | `dashboards.<name>.widgets.<widgetId>.title` / `description` / `subCaption` |
77+
| Analytics dataset label / description | `datasets.<name>.label` / `description` |
78+
| Dataset dimension and measure labels | `datasets.<name>.dimensions.<dimension>.label` / `datasets.<name>.measures.<measure>.label` |
7679
| Page labels and `page:header` copy | `pages.<name>.label` / `description` / `title` / `subtitle` |
7780
| Screen-flow wizards (flow label, screen headings, screen field copy) | `flows.<flow>.label` / `flows.<flow>.screens.<node_id>.title` / `.fields.<field>.label` / `.placeholder` — see the boundary note below |
7881
| Global actions, settings, messages | `globalActions`, `settings`, `messages` |
7982

8083
The metadata types resolved per request are **object, view, action, app,
81-
dashboard, and page** — a field's labels are translated as part of its object
82-
document, and so are the labels of any actions the object declares inline
84+
dashboard, dataset, and page** — a field's labels are translated as part of
85+
its object document, and so are the labels of any actions the object declares
86+
inline
8387
(#3370). Before that, an object document went out with its authored action
8488
labels untouched: `sys_approval_request`'s Approve / Reject rendered in English
8589
in a zh-CN workspace for every consumer except the Console, which happened to
@@ -95,6 +99,21 @@ translates `widget.description`, `subCaption` translates
9599
`widget.options.description`, and neither reaches the other's field.
96100
</Callout>
97101

102+
<Callout type="info">
103+
**Dataset copy is keyed at the top level, not under the dashboard
104+
(#14253).** A dataset is the analytics definition — its dimensions and its
105+
measures — that widgets bind to by reference, and a measure's label is drawn
106+
*on the dashboard*: under every metric tile and on every chart axis. It gets
107+
its own `datasets.<name>` group rather than a slot under `dashboards.<name>`
108+
because the same measure is drawn by many widgets across many dashboards —
109+
keying it per presentation would ask for the same string once per widget, and
110+
would leave a dataset no dashboard references unaddressable. Below the
111+
dataset the copy is `label` and nothing else: a dimension and a measure each
112+
declare exactly one display string, so `description` belongs to the dataset
113+
itself and writing one on a dimension or measure is rejected with a message
114+
saying so.
115+
</Callout>
116+
98117
<Callout type="info">
99118
**Page headers are keyed by page name (#3589).** A page's `page:header`
100119
component has no stable id, so its `properties.title` / `properties.subtitle`

docs/qa/platform-checklist/areas/access-security.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2572,9 +2572,9 @@
25722572
"evidence": "the probe trace"
25732573
},
25742574
{
2575-
"clause": "localization rides the ExecutionContext without a setup gate: an ordinary member's /auth/me/localization answers 200 with currency/locale/timezone keys (nulls legal) — the SETTINGS surface is setup-gated, the resolved defaults deliberately are not",
2575+
"clause": "localization is RESOLVED without a setup gate: an ordinary member's /auth/me/localization answers 200 with currency/locale/timezone keys carrying the deployment cascade's own answers — the SETTINGS surface is setup-gated, the resolved defaults deliberately are not. \"Nulls legal\" no longer holds for all three (#15387, which repaired a resolver that carried none of them and made the endpoint answer null for currency AND timezone to every authenticated caller): locale and timezone ALWAYS answer, on cascade floors en-US / UTC, so a null for either is a FAIL and a regression of that repair. currency is the one key with no floor — null there is legal, and only when the deployment configures no localization.currency",
25762576
"oracle": "api",
2577-
"verify": "member trace carries authenticated:true plus the three keys",
2577+
"verify": "member trace carries authenticated:true plus the three keys, with timezone and locale non-null; configure localization.currency and localization.timezone and re-trace — both must move to the configured values (an unmoved trace is the #15387 defect, not a pass)",
25782578
"evidence": "the trace"
25792579
}
25802580
],

0 commit comments

Comments
 (0)