Uh oh!
There was an error while loading. Please reload this page.
zh-CN demo dataset: a fully Chinese Ardenline for Chinese-locale demos (pnpm demo:zh) - #121
Merged
Merged
Conversation
The Chinese UI has been done for a while — nav, columns, statuses, progress phrases and form help all render in zh-CN. Every RECORD underneath it was still English, so a Chinese evaluator saw a Chinese frame around English content, which reads as "not localised" however good the chrome is. `DULY_DEMO_LOCALE` (unset = `en`, `zh-CN`) now chooses the fixture's language, read at compile time exactly as `DULY_DEMO_SEED` is and for the same reason — the seed is baked into `dist/objectstack.json`. `pnpm demo:zh` is `pnpm demo` with it set, and `scripts/demo.mjs` passes it to BOTH boots. One fixture, two languages, not two fixtures. The arrays stay authored in English and are mapped through `t()` once per section; `src/data/demo-zh.ts` holds every human-readable string keyed by its English original. That keeps one org chart, one history planner and one set of invariants — a parallel zh fixture would drift, and a Chinese demo with 19 catalog items where the English one has 20 is not a bug anybody spots on a screen. It also makes "is it translated?" a set comparison a test can make, in both directions. Machine values are deliberately untouched: unit codes, period keys, select values, timezones and frequencies are identical in both locales. Mailboxes stay ASCII pinyin on the reserved `.example` domain while display names are Chinese. Position codes are now readable in both languages — `Plant compliance officer` / `厂区合规专员` rather than `plant_compliance_officer`. The column is free text the app never parsed; it was only ever being read by people. The admin persona is MEASURED, not assumed (`@objectstack/*` 17.2.0): the priming boot PATCHes `sys_user.name` to `演示管理员` before the seed runs, signing in with the same credentials afterwards still returns 200, and the seed replays as a no-op against the renamed row. It fails loudly if the rename does not land, because the alternative is a fourteenth user owning the demo account's work and four empty screens on first boot. Tests: `test/seed-locale.test.ts` boots a kernel per locale and asserts equal row counts, equal status/caliber/period distributions, a Han character in every zh-CN display string, ASCII mailboxes, invented-only references, and the English names and subjects pinned literally. `test/demo-locale.test.ts` pins the switch and holds the dictionary to the fixture in both directions — which is what stops this card quietly rewording the English demo. `test/demo-script.test.ts` holds `scripts/demo.mjs` to the fixture's `ADMIN`. Closes#117 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SqkTcrxUFci7nqXdbBSe2p
os-warren
marked this pull request as ready for review
September 2, 2026 10:39
Uh oh!
There was an error while loading. Please reload this page.
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 freeto 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.
Closes#117
The Chinese UI has been done for a while — nav, columns, statuses, progress phrases and form help all render in zh-CN. Every record underneath it was still English, so a Chinese evaluator saw a Chinese frame around English content.
pnpm demo:zhnow opens the same worked example with 安岭集团, its people, its duty catalog and six months of its history all in Chinese.What this is, mechanically
DULY_DEMO_LOCALE(unset =en,zh-CN) chooses the fixture's language, read at compile time exactly asDULY_DEMO_SEEDis and for the same reason — the seed is baked intodist/objectstack.json, so both are compile-time decisions and both boots pass--compile. An unrecognised spelling (zh_CN,zh-Hans) is a hard error, not a silent fallback to English: a fallback brings the demo up in English, working perfectly, and nobody finds out until it is on a screen in front of a customer.One fixture in two languages, not two fixtures. The arrays stay authored in English and are mapped through
t()once per section;src/data/demo-zh.tsholds every human-readable string keyed by its English original. A paralleldemo-org.zh.ts/demo-catalog.zh.tswould duplicate the structure as well as the prose — two catalogs, two cadence sets, two review-state distributions — and they would drift invisibly. A Chinese demo with 19 catalog items where the English one has 20 is not a bug anybody spots on a screen.Keying on the English string also reduces "is it translated?" to a set comparison a test can make in both directions, which is what pins item 6: rewording an English line orphans its dictionary entry and goes red.
Machine values are deliberately untouched — unit codes,
period_keys, select values, timezones, frequencies are identical in both locales, and the zh-CN translations bundle is what renders the select values. Mailboxes stay ASCII pinyin (chen.zhiyuan@ardenline.example) while display names are Chinese:sys_user.emailis an identifier that gets typed, pasted and matched on.Item 5 — the admin persona, measured
The card said to measure this rather than assume it, and the measurement is the reason the rename ships.
Measured on
@objectstack/*17.2.0, live, on a clean database with nothing else holding it:PATCH /api/v1/data/sys_user/{id}with{"name":"演示管理员"}as the signed-in admin200,namereads back演示管理员200, user演示管理员, emailadmin@objectos.aipnpm demo:zhon a fresh DB1/2 done — admin account ready, renamed to 演示管理员 for this locale.sys_userrows, not 14 — the seed matched the renamed row and skipped it1/2 done — admin account ready.(no rename — idempotent), sign-in200, every row count identicalSo the rename lands, sign-in survives it, and the replay is a no-op.
Dev Adminstays in English.The failure this guards is specific and silent, which is why
scripts/demo.mjsfails loudly rather than warning: if the rename did not happen, the seed would find no演示管理员row, insert a fourteenth user, and hand the demo account's duties, tasks, assignment and work log to a person nobody can log in as — leaving My week, My duties, Sent by me and Work log empty on the first screen an evaluator opens, with the seed reporting success.The rename can only happen in the priming boot:
plugin-auth'smaybeSeedDevAdmindoes not take a name, and the seed'ssys_userrow for the admin carries its natural key and nothing else on purpose (anything more turns the loader's no-op skip into an UPDATE against a live credential-bearing account).Item 3 — readable position codes
plant_compliance_officer→Plant compliance officer/厂区合规专员, and the two siblings. The column is free text the app never parsed — it was only ever being read by people, who do not write their own job title that way in either language. Still matched verbatim by the sync and apply actions, soPlant compliance officerandplant compliance officerremain two different positions.No test was keyed on
POSITIONS; the ones usingplant_compliance_officer(catalog-instantiate,catalog-action-placement,catalog-engine-facade) supply their own literals and are untouched. The sync action'splaceholderwas deliberately not changed — it is mirrored in the zh-CN translations bundle, which this card excludes — filed as #120 along withsamples/catalog-items.csv, which still carries the old spelling.Tests
pnpm test868 passed / 35 files (baseline onorigin/mainwas 833 / 32), all four gates green ate05dcfb.test/seed-locale.test.tsboots a real kernel per locale with the declarative seeder running, and reads the rows back — not the fixture arrays, because half of these strings are natural keys and the failure a translation causes is a reference that resolves to nothing, not prose that reads oddly. Asserts: two independent databases (no Chinese duty in the English DB and vice versa — without which "same counts" would be trivially true of a doubly-seeded store); equal row counts across all 8 objects; equal status / caliber / review-state / frequency /period_keydistributions; a Han character in every one of the 395 zh-CN display strings, and none in the English ones; ASCII mailboxes on the reserved domain; every catalog reference an invented internal document (《…》第N条, and neverGB/ISO/国标); and the English unit, user, catalog, position, duty, assignment and log names pinned literally — nottoMatchSnapshot(), which-uupdates without anybody reading the diff.test/demo-locale.test.tspins the switch (accepted spellings, and thatzh_CNthrows rather than answering in English) and holds the dictionary to the fixture in both directions.test/demo-script.test.tsholdsscripts/demo.mjs's admin name to the fixture'sADMIN. They are two copies of one fact — plain.mjsthat runs before anything compiles, and TypeScript baked into the artifact — and they cannot be one constant.Both new guards were reverse-verified
A guard that has never been observed failing is indistinguishable from one that cannot fail, so each was ablated on a committed tree, with the mutation confirmed on disk by grepping for the removed and the injected text, and restored by an
EXIT INT TERMtrap.Ablation A — one translation replaced by its English original (
向承运方催了三张缺失的转移联单→ the English string; confirmed on disk: removed=0, injected=1). Both guards went red, and the seed-locale failure named the actual field:Ablation B — an English fixture line reworded (
Chased the carrier for three missing transfer notes→…for the missing transfer notes; confirmed on disk: original=0, reworded=1). Red three ways, which is the item-6 pin working:No rebuild leg was needed: vitest reads the TypeScript source, and both suites point
artifactPathat a path that cannot exist so a staledist/objectstack.jsoncannot be what is under test.Live boot evidence —
pnpm demo:zh, clean database, port 4321Row counts, both locales, both boots — identical throughout:
sys_usersys_business_unitsys_business_unit_memberduly_catalog_itemduly_dutyduly_assignmentduly_taskduly_log_entryAnd
pnpm demoon a clean database is unchanged — 183 tasks, 13 users,Dev Admin, English subjects, with the readable position codes item 3 asked for. That control was re-taken: an earlier reading was contaminated by a stray server (setsidre-parents, so$!is not the process-group leader and the first teardown missed four servers). All servers were killed by process group; none survive.Notes for review
samples/catalog-items.csv, which carries one extra row,Daily line start-up check). All 20 are translated.test/seed.test.tsfor the per-locale boot. It is intest/seed-locale.test.tsinstead:seed.test.tsboots one kernel from static imports, and a second locale needsvi.resetModules()plus dynamic imports, so a separate file is both cleaner and parallelisable.seed.test.tsis untouched and still green.objectstack.config.ts, the objects, the views or the translations bundle.🤖 Generated with Claude Code
https://claude.ai/code/session_01SqkTcrxUFci7nqXdbBSe2p