diff --git a/CHANGELOG.md b/CHANGELOG.md index 2334398a..1abeaeca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ Sections dated before 2026-09-19 predate the cycle and stay as they are. Named m ## Unreleased +- learn(multilingual-app): **a second language is four decisions the translations never see — source language, enabled languages, the user's starting language, and a switch a normal role can use.** New `skills/learned-multilingual-app.md` (routed on demand, stages 3/5/6, architect + mdl + review). Field run 2026-10-09 (Mendix 11.15.0, mxcli v0.25.0, an app authored in Spanish gaining ES + PT): 418 strings per language written correctly, and still the language picker raised an error, showed a blank label and changed nothing, with check, exec.sh and lint green. Causes: captions authored under the default `en_US` (so `en_US` is the authored language); demo users on that hidden source language; the Marketplace LanguageSelector writing `System.User_Language` from the browser, which this project's non-admin role could not, with no grant route found (mxcli `check` passes the grant, `exec` refuses it; Studio Pro does not offer the member, so the first fix — a Studio Pro hand step — was impossible; a probe on the `System.User` module role now comes first); `login.html` outside the translation system. Recipe: a microflow sets `$currentUser`'s language, a nanoflow reloads, CSS highlights from ``; seeded users start in a visible language. `learned-mdl-cannot-express.md` gains the LanguageSelector row. Credit: Carles Zaragoza (field build). - fix(conformance-check): read ledger rows whose pointer cell is bare (no backticks) and treat status `planned` as not-built; a ledger whose acceptance text is also bare prose is still not measured (acceptance must hold a backticked SHOW/DESCRIBE), but it now exits 2 with a FAULT that says rows were found and the text is prose, instead of "shape changed or glob matched nothing" (TK-006, partial: that ledger shape is diagnosed, not fixed) — field report TK-006 - fix(app-restart): **a secret with spaces in quotes is masked whole, and `Pwd` and `Credential` join the secret words.** Follow-up to #403: `--constant "Mod.ApiKey=two words"` and `--db-password 'two words'` printed everything after the first word (the quoted-flag case leaked before #403 too). `mask()` now masks each argument before quoting it for display, so the whole quoted value goes; the executed command is untouched, and a real-launch test proves the started app receives the raw argv; an env assignment at the start of a `--cmd` string keeps the whole command visible; the per-argument seds only run for arguments holding a secret word. The substring match still over-masks names like `Monkey` (Key) and `Turkey`; that is display only, kept, and pinned by a test. `tests/wave2/test-app-restart.sh` gains 22 cases, 8 of them failing on master. — review of #403 - fix(install-manifest): **a clean toolkit checkout passes its own `project-bin/` manifest self-check, so adopters no longer see the "named by NEITHER list" warning at the top of every sync.** `coverage-check-all.sh` (found by `review-module.sh` through its `_tool` lookup in the toolkit clone) and `ts-sync.sh` (UNPROVEN and SUPERSEDED per its header) join `MXTK_PROJECT_BIN_NOINSTALL` with reasons. New `tests/wave2/test-manifest-clean-checkout.sh` fails, rather than warns, when a `project-bin/` file is in neither list. — joshuamoesa (#381) diff --git a/README.md b/README.md index f7f6f179..17822a67 100644 --- a/README.md +++ b/README.md @@ -604,6 +604,7 @@ Every mxcli project has a `.ai-context/skills/` directory (bundled by `mxcli ini |---|---| | Dispatching a build step to a helper — generate its one-file pack (the brief's Build steps row, mxcli brain brief, live DESCRIBEs, example, folder) and hand over the path instead of a reading list. Measured: 25-38% fewer tool calls and 30-41% less time, same quality; the token saving is not proven | `project-bin/context-pack.sh` | | An exec.sh guard refused a write (not a permission prompt), or building a module with mxcli — verified, iterative, coverage-checklist gated; on a refusal: commit, wait or stop, never FORCE_EXEC | `skills/iterative-build-loop.md` | +| Adding a second language, a language picker or a translation script — and the moment a language picker errors, shows a blank label or changes nothing: source language, enabled languages, the user's starting language, a switch a non-admin role can use | `skills/learned-multilingual-app.md` | | After marking a module done, or any time "how much is built vs proven" is asked — renders build-plan.html from done- prefixes and verify-module.sh/improvement-register.md, kept as two honestly separate views; --json writes architecture/build-plan.json parsed from build-plan.md's Phase headings (a plan with no Phase headings gets no file) | `project-bin/build-plan-status.sh` | | Turning a client-derived Mendix app into a clean, shareable demo with zero client fingerprint — branding, data, custom widgets | `skills/anonymize-client-app-for-demo.md` | | Handing a headless-built model to a person — opening it in Studio Pro, a free sandbox, or a colleague's machine: the model travels, the demo data and runtime config (keys, an agent's bound model) do not, and each needs its own re-establish step | `skills/handoff-to-studio-pro.md` | diff --git a/ROUTING.md b/ROUTING.md index 22b4b0c3..f26b1b73 100644 --- a/ROUTING.md +++ b/ROUTING.md @@ -143,6 +143,7 @@ picks the row up. That is the whole procedure — there is no second list to rem | Before any mxcli exec / exec.sh / --mcp write — ask or run? the knob decides | `bin/exec-approval.sh` | mdl,gate | 5,6 | baseline | | Dispatching a build step to a helper — generate its one-file pack (the brief's Build steps row, mxcli brain brief, live DESCRIBEs, example, folder) and hand over the path instead of a reading list. Measured: 25-38% fewer tool calls and 30-41% less time, same quality; the token saving is not proven | `project-bin/context-pack.sh` | mdl | 5 | ondemand | | An exec.sh guard refused a write (not a permission prompt), or building a module with mxcli — verified, iterative, coverage-checklist gated; on a refusal: commit, wait or stop, never FORCE_EXEC | `skills/iterative-build-loop.md` | mdl,gate | 5 | ondemand | +| Adding a second language, a language picker or a translation script — and the moment a language picker errors, shows a blank label or changes nothing: source language, enabled languages, the user's starting language, a switch a non-admin role can use | `skills/learned-multilingual-app.md` | architect,mdl,review | 3,5,6 | ondemand | | After marking a module done, or any time "how much is built vs proven" is asked — renders build-plan.html from done- prefixes and verify-module.sh/improvement-register.md, kept as two honestly separate views; --json writes architecture/build-plan.json parsed from build-plan.md's Phase headings (a plan with no Phase headings gets no file) | `project-bin/build-plan-status.sh` | architect,gate,review | 4,5,6 | ondemand | | Turning a client-derived Mendix app into a clean, shareable demo with zero client fingerprint — branding, data, custom widgets | `skills/anonymize-client-app-for-demo.md` | mdl,review | 6 | ondemand | | Handing a headless-built model to a person — opening it in Studio Pro, a free sandbox, or a colleague's machine: the model travels, the demo data and runtime config (keys, an agent's bound model) do not, and each needs its own re-establish step | `skills/handoff-to-studio-pro.md` | test,review | 6,7 | ondemand | diff --git a/agents/architect-agent.md b/agents/architect-agent.md index 2bfd0c0d..17f66961 100644 --- a/agents/architect-agent.md +++ b/agents/architect-agent.md @@ -72,6 +72,7 @@ This table is your whole list. The baseline table in the project's CLAUDE.local. | `skills/brd-to-build-plan.md` | Turning BRDs plus architecture into a numbered, dependency-ordered build plan | | `skills/coverage-ledger.md` | Building the Stage 4 coverage ledger | | `bin/coverage-check.sh` | Checking a coverage ledger against its BRD | +| `skills/learned-multilingual-app.md` | Adding a second language, a language picker or a translation script | | `skills/mendix-agents.md` | Building a Mendix AI agent | | `skills/company-brain.md` | Setting up or wiring a COMPANY BRAIN | | `skills/close-the-loop.md` | Cutover and retrospective | diff --git a/agents/mdl-agent.md b/agents/mdl-agent.md index 3d348cd7..d83f93b1 100644 --- a/agents/mdl-agent.md +++ b/agents/mdl-agent.md @@ -76,6 +76,7 @@ This table is your whole list. The baseline table in the project's CLAUDE.local. | `skills/mdl-cookbook-microflows.md` | Writing MDL microflow scripts | | `skills/build/mdl/oneshot-mdl-method.md` | Writing a single MDL script that takes a project from nothing to a working vertical slice | | `skills/learned-page-patterns.md` | Building and auditing Mendix pages, or starting a UI fix round across many pages | +| `skills/learned-multilingual-app.md` | Adding a second language, a language picker or a translation script | | `skills/oneshot-page-structure-patterns.md` | Generating a whole page tree in one script | | `skills/mendix-agents.md` | Building a Mendix AI agent | | `skills/mendix-agent-ui.md` | Embedding a copilot chat panel | diff --git a/agents/review-agent.md b/agents/review-agent.md index 22c8f86b..8a7f76ec 100644 --- a/agents/review-agent.md +++ b/agents/review-agent.md @@ -64,6 +64,7 @@ This table is your whole list. The baseline table in the project's CLAUDE.local. | `skills/app-analysis.md` | Building or refreshing the standing dossier of an EXISTING app before changing it: inventory, module dependency shape, loop risk patterns, each section with a verdict and a fault where nothing was measured (a standing document; nothing else in the toolkit reads it yet) | | `skills/module-dependency-review.md` | Judging module tangles, bidirectional pairs, cohesion and hubs from analysis/app-facts/dependencies.json; stock graph_module_* views split names on the first dot and are not trusted | | `skills/microflow-loop-antipatterns.md` | Reading what loop bodies do (LOOP_TQ, deferred commit, nested loop, REST in loop, transaction control per item, scheduled-event reachability) from described MDL; the catalog holds top-level activities only and cannot see inside a loop | +| `skills/learned-multilingual-app.md` | Adding a second language, a language picker or a translation script | | `project-bin/conformance-check.sh` | Running the ledger rung alone | | `project-bin/graph-sweep.sh` | Running the wiring rung alone | | `skills/fixture-seeding.md` | Seed data, test data, demo data or fixtures are needed, or pages come up empty | diff --git a/bin/gate-check.sh b/bin/gate-check.sh index 2a79dbcc..98f1fa5a 100755 --- a/bin/gate-check.sh +++ b/bin/gate-check.sh @@ -1769,10 +1769,10 @@ stage_protocol_paths() { 0) echo "skills/interview-protocol.md skills/grill-mode.md skills/checkpoints/checkpoint-template.md skills/checkpoints/checkpoint-scope.md skills/source-triage.md skills/image-transcription.md skills/small-project-tier.md skills/existing-app-change.md skills/decompose-app.md skills/app-analysis.md skills/layering-review.md skills/module-dependency-review.md skills/microflow-loop-antipatterns.md skills/assess-migration.md skills/migration-pipeline.md skills/rehost-app.md skills/migrate-general.md skills/migrate-outsystems.md skills/source-os11.md skills/os-xml-schema.md skills/source-node-express-react.md skills/document-discovery.md skills/extractor-quality-loop.md skills/qa-loop-goal-pattern.md skills/mendix-epics-api.md skills/corpus-extraction-integrity.md skills/gate-check-file-locations.md" ;; 1) echo "skills/interview-protocol.md skills/grill-mode.md skills/checkpoints/checkpoint-template.md skills/checkpoints/checkpoint-extraction.md skills/dispatching.md skills/image-transcription.md skills/small-project-tier.md skills/migration-pipeline.md skills/rehost-app.md skills/source-os11.md skills/os-xml-schema.md skills/source-node-express-react.md skills/document-discovery.md skills/extractor-quality-loop.md skills/kb-generation.md skills/corpus-extraction-integrity.md" ;; 2) echo "skills/interview-protocol.md skills/change-intake.md skills/change-intake-detail.md skills/grill-mode.md skills/checkpoints/checkpoint-template.md skills/checkpoints/checkpoint-brd.md skills/checkpoints/checkpoint-architecture.md skills/dispatching.md skills/image-transcription.md skills/small-project-tier.md skills/kb-generation.md skills/brd-generation.md skills/brd-validation.md" ;; - 3) echo "skills/interview-protocol.md skills/change-intake.md skills/change-intake-detail.md skills/grill-mode.md skills/checkpoints/checkpoint-template.md skills/checkpoints/checkpoint-design.md skills/dispatching.md skills/small-project-tier.md skills/mendix-best-practices-index.md skills/decompose-app.md skills/mendix-service-seam.md skills/layering-review.md skills/architecture-blueprint.md skills/modularize-domain.md skills/design-artifacts.md skills/interaction-model.md skills/brd-to-build-plan.md skills/workflow-structure-rules.md skills/learned-mdl-cannot-express.md" ;; + 3) echo "skills/interview-protocol.md skills/change-intake.md skills/change-intake-detail.md skills/grill-mode.md skills/checkpoints/checkpoint-template.md skills/checkpoints/checkpoint-design.md skills/dispatching.md skills/small-project-tier.md skills/mendix-best-practices-index.md skills/decompose-app.md skills/mendix-service-seam.md skills/layering-review.md skills/architecture-blueprint.md skills/modularize-domain.md skills/design-artifacts.md skills/interaction-model.md skills/brd-to-build-plan.md skills/learned-multilingual-app.md skills/workflow-structure-rules.md skills/learned-mdl-cannot-express.md" ;; 4) echo "skills/interview-protocol.md skills/change-intake.md skills/change-intake-detail.md skills/grill-mode.md skills/checkpoints/checkpoint-template.md skills/checkpoints/checkpoint-build.md skills/agent-roles.md skills/dispatching.md skills/small-project-tier.md skills/module-brief.md skills/module-folder-convention.md skills/brd-to-build-plan.md skills/coverage-ledger.md skills/workflow-structure-rules.md skills/rest-integration-first-time-right.md skills/learned-constants-and-secrets.md" ;; - 5|build-ready) echo "skills/interview-protocol.md skills/change-intake.md skills/change-intake-detail.md skills/grill-mode.md skills/agent-roles.md skills/dispatching.md skills/module-brief.md skills/learned-mdl-preflight.md skills/module-folder-convention.md skills/learned-microflow-patterns.md skills/microflow-preflight.md skills/mendix-best-practices-index.md skills/ui-preflight-pages.md skills/design-spacing.md skills/learned-stylegallery.md skills/ui-loop.md skills/module-review.md skills/testing-shape.md skills/decompose-app.md skills/mendix-service-seam.md skills/microflow-loop-antipatterns.md skills/rehost-app.md skills/iterative-build-loop.md skills/mdl-cookbook-microflows.md skills/build/mdl/oneshot-mdl-method.md skills/learned-page-patterns.md skills/oneshot-page-structure-patterns.md skills/mendix-agents.md skills/mendix-agent-ui.md skills/mendix-agent-setup.md skills/fixture-seeding.md skills/journey-proof.md skills/unhappy-path-testing.md skills/monkey-test.md skills/e2e-locators-that-lie.md skills/report-schema.md skills/harness-architecture.md skills/process-coherence-pass.md skills/lint-that-actually-runs.md skills/improvement-register.md skills/journey-examples.md skills/wiring-sweep.md skills/learned-workflow-patterns.md skills/workflow-structure-rules.md skills/rest-integration-first-time-right.md skills/bug-submission-checklist.md skills/empty-widget-triage.md skills/learned-sidebar-collapse-icons.md skills/learned-popup-navigation.md skills/learned-datagrid-customcontent-binding.md skills/learned-popup-feedback-pattern.md skills/learned-mdl-cannot-express.md skills/learned-css-that-never-applied.md skills/learned-detection-gaps.md skills/learned-dg2-patterns.md skills/learned-file-upload-widget.md skills/security-is-not-a-later-script.md skills/learned-local-db-confusion.md skills/full-harness-audit.md skills/test-result-audit.md skills/finding-disposition.md skills/preview-over-hub-tunnel.md skills/walking-skeleton.md skills/platform-link.md skills/teamserver-alignment.md skills/learned-constants-and-secrets.md" ;; - 6) echo "skills/interview-protocol.md skills/change-intake.md skills/change-intake-detail.md skills/grill-mode.md skills/checkpoints/checkpoint-template.md skills/checkpoints/checkpoint-cutover.md skills/dispatching.md skills/mendix-best-practices-index.md skills/module-review.md skills/testing-shape.md skills/existing-app-assurance.md skills/decompose-app.md skills/app-analysis.md skills/module-dependency-review.md skills/microflow-loop-antipatterns.md skills/rehost-app.md skills/qa-loop-goal-pattern.md skills/mendix-agent-setup.md skills/e2e-harness-base.md skills/learned-db-assertions.md skills/fixture-seeding.md skills/journey-proof.md skills/unhappy-path-testing.md skills/monkey-test.md skills/e2e-locators-that-lie.md skills/learned-skill-ux-audit.md skills/learned-skill-scope-delta.md skills/report-schema.md skills/harness-architecture.md skills/process-coherence-pass.md skills/e2e-evidence-report.md skills/record-demo-video.md skills/share-demo-package.md skills/lint-that-actually-runs.md skills/improvement-register.md skills/journey-examples.md skills/wiring-sweep.md skills/workflow-structure-rules.md skills/bug-submission-checklist.md skills/empty-widget-triage.md skills/anonymize-client-app-for-demo.md skills/learned-css-that-never-applied.md skills/learned-detection-gaps.md skills/learned-local-db-confusion.md skills/full-harness-audit.md skills/test-result-audit.md skills/finding-disposition.md skills/handoff-to-studio-pro.md skills/preview-over-hub-tunnel.md skills/platform-link.md skills/teamserver-alignment.md skills/learned-constants-and-secrets.md" ;; + 5|build-ready) echo "skills/interview-protocol.md skills/change-intake.md skills/change-intake-detail.md skills/grill-mode.md skills/agent-roles.md skills/dispatching.md skills/module-brief.md skills/learned-mdl-preflight.md skills/module-folder-convention.md skills/learned-microflow-patterns.md skills/microflow-preflight.md skills/mendix-best-practices-index.md skills/ui-preflight-pages.md skills/design-spacing.md skills/learned-stylegallery.md skills/ui-loop.md skills/module-review.md skills/testing-shape.md skills/decompose-app.md skills/mendix-service-seam.md skills/microflow-loop-antipatterns.md skills/rehost-app.md skills/iterative-build-loop.md skills/mdl-cookbook-microflows.md skills/build/mdl/oneshot-mdl-method.md skills/learned-page-patterns.md skills/learned-multilingual-app.md skills/oneshot-page-structure-patterns.md skills/mendix-agents.md skills/mendix-agent-ui.md skills/mendix-agent-setup.md skills/fixture-seeding.md skills/journey-proof.md skills/unhappy-path-testing.md skills/monkey-test.md skills/e2e-locators-that-lie.md skills/report-schema.md skills/harness-architecture.md skills/process-coherence-pass.md skills/lint-that-actually-runs.md skills/improvement-register.md skills/journey-examples.md skills/wiring-sweep.md skills/learned-workflow-patterns.md skills/workflow-structure-rules.md skills/rest-integration-first-time-right.md skills/bug-submission-checklist.md skills/empty-widget-triage.md skills/learned-sidebar-collapse-icons.md skills/learned-popup-navigation.md skills/learned-datagrid-customcontent-binding.md skills/learned-popup-feedback-pattern.md skills/learned-mdl-cannot-express.md skills/learned-css-that-never-applied.md skills/learned-detection-gaps.md skills/learned-dg2-patterns.md skills/learned-file-upload-widget.md skills/security-is-not-a-later-script.md skills/learned-local-db-confusion.md skills/full-harness-audit.md skills/test-result-audit.md skills/finding-disposition.md skills/preview-over-hub-tunnel.md skills/walking-skeleton.md skills/platform-link.md skills/teamserver-alignment.md skills/learned-constants-and-secrets.md" ;; + 6) echo "skills/interview-protocol.md skills/change-intake.md skills/change-intake-detail.md skills/grill-mode.md skills/checkpoints/checkpoint-template.md skills/checkpoints/checkpoint-cutover.md skills/dispatching.md skills/mendix-best-practices-index.md skills/module-review.md skills/testing-shape.md skills/existing-app-assurance.md skills/decompose-app.md skills/app-analysis.md skills/module-dependency-review.md skills/microflow-loop-antipatterns.md skills/rehost-app.md skills/qa-loop-goal-pattern.md skills/learned-multilingual-app.md skills/mendix-agent-setup.md skills/e2e-harness-base.md skills/learned-db-assertions.md skills/fixture-seeding.md skills/journey-proof.md skills/unhappy-path-testing.md skills/monkey-test.md skills/e2e-locators-that-lie.md skills/learned-skill-ux-audit.md skills/learned-skill-scope-delta.md skills/report-schema.md skills/harness-architecture.md skills/process-coherence-pass.md skills/e2e-evidence-report.md skills/record-demo-video.md skills/share-demo-package.md skills/lint-that-actually-runs.md skills/improvement-register.md skills/journey-examples.md skills/wiring-sweep.md skills/workflow-structure-rules.md skills/bug-submission-checklist.md skills/empty-widget-triage.md skills/anonymize-client-app-for-demo.md skills/learned-css-that-never-applied.md skills/learned-detection-gaps.md skills/learned-local-db-confusion.md skills/full-harness-audit.md skills/test-result-audit.md skills/finding-disposition.md skills/handoff-to-studio-pro.md skills/preview-over-hub-tunnel.md skills/platform-link.md skills/teamserver-alignment.md skills/learned-constants-and-secrets.md" ;; 7) echo "skills/interview-protocol.md skills/change-intake.md skills/change-intake-detail.md skills/grill-mode.md skills/checkpoints/checkpoint-template.md skills/checkpoints/checkpoint-cutover.md skills/close-the-loop.md skills/share-demo-package.md skills/handoff-to-studio-pro.md skills/platform-link.md skills/teamserver-alignment.md skills/deploy-to-sandbox.md" ;; *) echo "" ;; # diff --git a/bin/lib/skill-routing.tsv b/bin/lib/skill-routing.tsv index ca7d72ec..ca3bbfed 100644 --- a/bin/lib/skill-routing.tsv +++ b/bin/lib/skill-routing.tsv @@ -160,6 +160,7 @@ iterative-build-loop skills/iterative-build-loop.md An exec.sh guard refused a w mdl-cookbook-microflows skills/mdl-cookbook-microflows.md Writing MDL microflow scripts — worked recipes mdl 5 ondemand build/mdl oneshot-mdl-method skills/build/mdl/oneshot-mdl-method.md Writing a single MDL script that takes a project from nothing to a working vertical slice — execution order, why it is deliberately non-idempotent, the instrument hierarchy, and the silent failures that pass every check mdl 5 ondemand build/mdl learned-page-patterns skills/learned-page-patterns.md Building and auditing Mendix pages, or starting a UI fix round across many pages — widget patterns, datasource shapes; for a round: SCSS first, four regressions to check mdl 5 ondemand build/pages +learned-multilingual-app skills/learned-multilingual-app.md Adding a second language, a language picker or a translation script — and the moment a language picker errors, shows a blank label or changes nothing: source language, enabled languages, the user's starting language, a switch a non-admin role can use architect,mdl,review 3,5,6 ondemand build oneshot-page-structure-patterns skills/oneshot-page-structure-patterns.md Generating a whole page tree in one script — the structure patterns that survive it mdl 5 ondemand build/pages mendix-agents skills/mendix-agents.md Building a Mendix AI agent — the agent is runtime data not a model document, so JSON import, tool microflows, knowledge base chunk loading and the runtime wiring all sit outside MDL, and mxbuild stays green when they are wrong mdl,architect 5 ondemand build/agents mendix-agent-ui skills/mendix-agent-ui.md Embedding a copilot chat panel — the frame is yours, ConversationalUI owns the conversation; wireframe to tokens to snippet to page placement mdl 5 ondemand build/agents diff --git a/skills/learned-mdl-cannot-express.md b/skills/learned-mdl-cannot-express.md index 659b9945..507965a8 100644 --- a/skills/learned-mdl-cannot-express.md +++ b/skills/learned-mdl-cannot-express.md @@ -22,6 +22,7 @@ would have taken four minutes to find at Stage 3, when the design was still chea | A **file upload** control | nothing. `mxcli syntax page.widgets` prints the complete keyword set and there is no `filemanager`/`fileuploader`/`imageuploader` in it — `filemanager fm (…)` is a parse error, not an unknown property | ⛔ **not buildable** | | A **pluggable widget with a list-valued property** (an HTML element's `attributes`, a chart's `series`, a datagrid's `columns` written by hand) | nothing. On v0.20.0 a pluggable widget takes **scalar properties in parens and no body at all**: `pluggablewidget '' ( tagName: 'div' )` parses, and *every* body form is rejected with `mismatched input '' expecting '}'` — the object-list keywords (`attribute`, `event`) and the child-slot keywords (`tagcontentcontainer`) alike | ⛔ **not buildable** | | A page-level **boolean a button toggles** (a collapsible section, a show/hide) | page `variables:` exist and can be READ by `visible:`, but nothing in MDL writes one — that needs a nanoflow | ⛔ **not buildable** | +| A **language picker** a non-admin user can use (the Marketplace LanguageSelector) | no grant route found on one project for `System.User_Language`: `grant read (User_Language) on entity Administration.Account …` passes `check` and `exec` refuses it (`has no member(s) User_Language`, v0.25.0); Studio Pro does not offer the member either, so the widget errors on pick | ⚠️ **workaround**: a microflow sets `$currentUser`'s language + a nanoflow reloads — `learned-multilingual-app.md` §4 | | A **gallery empty-placeholder** | no `emptyState`/`emptyplaceholder` block on `gallery`. On v0.24.0 the Data grid 2 `emptyplaceholder` slot fails mxbuild with CE0463 too, because the bundled definition fixes `showEmptyPlaceholder` to `none` (BUG-150) | ⚠️ **workaround**: a one-attribute non-persistent entity the page switches on | | An **aggregate over an association** inside a gallery/listview template | client expressions have no `count()`; XPath does, but a template expression is not XPath | ⚠️ **workaround**: denormalise, with ONE writer that recomputes rather than increments | | A **count badge on a tab** ("Documents 3") | nothing on v0.24.0: a tab page is written with Caption and Widgets only, `Badge:` is refused by `check`, and `update widgets` reaches only pluggable property bags. `Forms$TabPage.Badge` exists in the metamodel | ⛔ **not buildable** — say the count in the pane's lead line | diff --git a/skills/learned-multilingual-app.md b/skills/learned-multilingual-app.md new file mode 100644 index 00000000..f269daa0 --- /dev/null +++ b/skills/learned-multilingual-app.md @@ -0,0 +1,187 @@ +# A second language — the four decisions that decide whether a user ever sees the translations + +**Applies to:** any mxcli project. +**Purpose:** adding a language to a Mendix app built with MDL: which language is the source, which +are enabled, which one a user starts in, and a language switch that works for a non-admin role. +Read it before the first translation script, and the moment a language picker errors, shows a blank +label, or changes nothing. +**Source:** field run 2026-10-09, a regulatory-QA demo app (Mendix 11.15.0, mxcli v0.25.0) authored +in Spanish, with Spanish and Portuguese added as a change request. Writing the strings went cleanly +(418 per language, `create or modify translations without marketplace`). Everything around them +failed — four separate failures, each green on `mxcli check`, `exec.sh` (mxbuild 0 errors) and +`mxcli lint`. Writing the strings themselves is mxcli's `translations` skill; this file is the rest. + +--- + +## The one-line finding + +> The translations were correct the whole time. What the user saw was decided by the source +> language, the user's language and the switch — none of which a gate looks at. + +| What the user saw | What was actually wrong | § | +|---|---|---| +| Picking a language raised an error and nothing changed | The Marketplace LanguageSelector writes `System.User_Language` from the browser; a normal role could not, and on this project no grant route was found | 4 | +| The picker showed no text at all | The demo users were on `en_US`, which this project's picker configuration left out of its list | 3 | +| There is no English to offer | Captions were authored in Spanish under the default `en_US` | 1 | +| The login page stayed Spanish | `theme/web/login.html` is outside the translation system | 5 | + +--- + +## 1. Decide the source language before the first caption + +Every caption is stored under the project's **default language**, whatever language it is written +in. This app's captions were authored in Spanish under the default `en_US`, so `en_US` *is* Spanish +— a user on `en_US` sees Spanish, and there is no English to offer without retranslating. + +Changing the default later moves no text and nothing warns (`mx check` is 0 errors either way). +mxcli's own fix for a wrong source is to change `DefaultLanguageCode` and re-run the `create` +statements that wrote the captions — workable while the scripts are the whole source of truth. +So, at Stage 3, ask the user one question and record the answer in `PROJECT.md`: *which language +are captions written in, and is that the language users get, or a hidden source?* + +When the authored language is not the default (the common case on a project that started from +`mxcli new`): keep the default as a hidden source, enable the real languages, and give the authored +language a translation that **copies its strings verbatim**. That same file is where Mendix's own +English platform texts — validation messages, paging, confirmation buttons — are translated; +otherwise they stay English in the Spanish UI. + +```sql +mdl 1; +alter settings language add or modify 'es_ES' ( CheckCompleteness: false ); +alter settings language add or modify 'pt_PT' ( CheckCompleteness: false ); + +create or modify translations without marketplace for es_ES ( + 'Guardar' as 'Guardar', -- authored in Spanish: copied as is + 'Go to first page' as 'Ir a la primera página' -- Mendix platform text: translated +); +``` + +Settings writes work from the CLI (`learned-mdl-preflight.md` STOP #2, retired 2026-09-30), so +this needs no stop-and-ask. Keep the dictionary in one generator (a small script that writes one `.mdl` per language), +not in hand-edited MDL: a renamed caption has to change in one place, and the scripts are +re-applied after every caption change — this project re-applied both the same day it wrote them, after the switch's captions changed. + +**Probe:** `./mxcli -p .mpr -c "DESCRIBE SETTINGS" | grep -E "DefaultLanguageCode|LANGUAGE add"` +— the default and the enabled list. `describe translations for ` ends with +`N source string(s), M translated, K to go`. + +## 2. Agree what is not translated + +Captions are model text; **data is not**. Product names, imported documents, generated rule +explanations, certificate and CSV contents stay in the language they were stored in. Ask the user +before translating (this project: *data stays Spanish*, recommended in chat, not contested) and +record it — or the first review reports a half-translated app. + +## 3. Start users in a language they can see + +A user's language is `System.User_Language`. A user on no language or on the hidden source gets +source text, and a picker whose list leaves that language out has nothing to show as the current +one. That was this project's blank label — observed with this widget configuration: the list was +`System.Language` filtered `[Code != 'en_US']`, so the demo users' own language was not in it. + +Set it where accounts are created; for seeded demo accounts, in after-startup, leaving a choice the +user already made alone: + +```sql +retrieve $Spanish from System.Language where Code = 'es_ES' first; +retrieve $Demo from Administration.Account + where [Name = 'demo.user@example.com' and not(System.User_Language/System.Language[Code != 'en_US'])] first; +if $Spanish != empty and $Demo != empty then + change $Demo (System.User_Language = $Spanish) commit without events; +end if; +``` + +## 4. The language switch: do not use the Marketplace LanguageSelector for a normal role + +**What failed.** LanguageSelector v1.1.3 reads `System.User_Language` from the session user and +commits a new value **from the browser**. For this project's non-admin role the pick raised an +error and changed nothing, and **on this project no grant route was found**: + +- **mxcli** — `grant read (User_Language) on entity Administration.Account …` *passes `check`* + and is refused by `exec`, verbatim (recorded 2026-10-09, v0.25.0): + `entity Administration.Account has no member(s) User_Language`. The qualified + `System.User_Language` does not parse there (`mismatched input '.' expecting {',', ')'}`). + This is deliberate in mxcli: its access-rule walk stops at `System.User`, so a platform member + listed in a rule on a specialization is CE0066. +- **Studio Pro** — the first fix was written as a Studio Pro hand step (Account → access rule → + tick `User_Language`). The user tried it: **Studio Pro does not offer that member.** The hand + step was impossible, and a script had already shipped describing it. + +What is **not** established is that a normal user can never write their own `User_Language`: that +member is governed by the System module's own access rules, which a user role receives through the +`System.User` module role. So before replacing the widget, run one probe: + +- `./mxcli -p .mpr -c 'DESCRIBE USER ROLE ""'` — does `ModuleRoles` include `System.User`? + (On this project it did, for both roles, and the pick still failed.) +- Is the picker reading the session user (`System.User`) rather than a specialization such as + `Administration.Account`? This widget takes no user binding; it uses the session user. + +If both hold and the pick still fails, do not write the Account hand step and do not spend a session +on the access rule. Replace the widget — the recipe below works either way. + +**What works.** A microflow applies no entity access, so it may set the signed-in user's own +language; Mendix applies a language when the client loads, so the button reloads afterwards. + +```sql +mdl 1; +/** Sets the signed-in user's language to the System.Language with this code. Only touches the current user; ignores an unknown code. */ +create or modify microflow MyModule.SUB_SetLanguage ($Code: String) +begin + retrieve $Language from System.Language where Code = $Code first; + if $Language != empty and $currentUser != empty then + change $currentUser (System.User_Language = $Language) commit; + end if; +end; + +/** ES button: switch the user to Spanish, then reload so the language applies. */ +create or modify nanoflow MyModule.ACT_LanguageES () +begin + call microflow MyModule.SUB_SetLanguage(Code = 'es_ES'); + call javascript action NanoflowCommons.Reload(); +end; + +grant execute on microflow MyModule.SUB_SetLanguage to MyModule.User; +grant execute on nanoflow MyModule.ACT_LanguageES to MyModule.User; +``` + +One nanoflow per language, two buttons in the layout's top bar (`alter layout … { replace + with { container … { actionbutton … } } }` swaps the widget in place). Needs +NanoflowCommons for `Reload`. Label the buttons in their own words (`ES`, `PT`), not with +`System.Language.Description`, which is whatever the runtime stored and is usually English. +`mxcli lint` flags the nanoflow — CONV010, *"ACT_ microflows should only contain UI actions and +sub-microflow calls"*, raised here on the JavaScript `Reload` call; accept it in the baseline with +that reason, or name the nanoflow without the `ACT_` prefix. + +Highlight the active language from **``**, which Mendix writes from the user's language +— no data read, so no access question at all: + +```scss +html[lang^="es"] .lang-switch-es, +html[lang^="pt"] .lang-switch-pt { background: var(--color-primary); color: var(--color-on-primary); } +``` + +**Probe:** in the running app, after switching, `document.documentElement.lang` starts with the new +code. Measured here: `pt` after switching to `pt_PT`, with the menu reading *Histórico*. + +## 5. The login page is yours to translate + +`theme/web/login.html` is a static file: no Mendix translation reaches it and there is no user +language before sign-in. This project gave it `data-i18n` keys, a small inline script swapping them +and an `ES | PT` toggle remembered in `localStorage`. It does not set the user's language after +sign-in — §3 and §4 do. + +## 6. Verify in the running app — per language, per role + +A green gate proves none of §1–§5. **Sign in as a non-admin user** — the §4 failure does not +reproduce as an administrator — and for each enabled language: + +1. Switch; the page reloads in that language: menu, page titles, buttons, grid headers, and one + Mendix platform text (a validation message or the paging bar). +2. The active button is highlighted (`` changed). +3. Sign out and in again: the choice persisted. +4. A page with data shows the data in its stored language (§2). + +**Done means** 4 of 4 checks, for every enabled language × every user role, written down with a +screenshot each (this project: 2 languages × 2 roles, the screenshots in its demo guides). A +language showing source text everywhere is an enabled-language question first +(`describe settings`), before anything is re-translated.