Repository navigation
Give a $strings. reference somewhere to resolve against, at runtime - #47
Merged
Merged
Conversation
Screenplay compiles a $strings.<key> reference symbolically and prints it unquoted - nothing in Scene or Stage ever turned it into the text a .strings file actually holds. Stage's frontend rendered the literal token, because resolution has to happen at runtime for a locale switch to take effect without recompiling the model, and nothing in either repository did that work. resolveStringsInElement walks an element tree the same way the rest of this package already distinguishes element kinds - an ExternalComponent's named slots, a ContentControl's content, an ItemsControl's itemTemplate, a Panel's children - resolving every $strings. reference in every properties bag it finds along the way against a flat locale dictionary. A missing key is left as its literal token rather than blanked out, the same failure mode a screen already had before resolution existed, and one an author notices immediately instead of a silently empty label. This is deliberately the generic half only: turning a dictionary and a tree into a resolved tree, with no opinion about where the dictionary comes from. Discovering and parsing the .strings files themselves is filesystem access only a host has - Stage now owns that side.
einari
added a commit
to Cratis/Stage
that referenced
this pull request
Sep 24, 2026
…130) The frontend rendered a $strings.<key> reference literally - nothing served the dictionary a .strings file actually holds, so a screen author had a localization mechanism that compiled and validated but never showed anything but the token itself in a running application. StageStrings discovers every .strings file under the mounted model root via Cratis.Screenplay.Strings' existing IStringsFiles/StringsFile.Parse, merging every file that declares a given locale into one flat dictionary. Two new endpoints - /stage/locales and /stage/strings/{locale} - serve that alongside /stage/scene and /stage/routes rather than folding it into either: a locale switch is a question about what a running Stage offers, not something the scene itself needs to carry. The frontend's useStrings hook fetches both, defaulting to the first locale reported, and resolveStringsInElement (Cratis/Scene#47) resolves every reference in a screen's element tree before it reaches the registry - the same seam SceneContent already had for handing an element to SceneElementView. A locale switcher only appears once more than one locale exists; one locale is not a choice worth a control for. Authored .strings files for every $strings. reference the Billing sample already declared, next to whichever .play file names each key - the established <base>.<locale>.strings convention, not a new format. This is what made the reference resolve to a real value in verification rather than to an empty dictionary that happened to compile. Verified end to end against a real running Stage container built from these changes: /stage/locales and /stage/strings/en both serve real data, and InvoiceList's title renders as "Invoices" - the value from the .strings file - rather than the literal $strings.invoice.listTitle token it showed before this.
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.
Why
Screenplay compiles a
$strings.<key>reference symbolically and prints it unquoted, but nothing in Scene or Stage ever turned it into the text a.stringsfile actually holds - Stage frontend rendered the literal token. Resolution has to happen at runtime, not compile time, so a locale switch or an edited translation takes effect without recompiling the model.What changed
resolveStringsInElementwalks an element tree the same wayelementKind.tsalready distinguishes kinds elsewhere in this package - anExternalComponentnamedslots, aContentControlowncontent, anItemsControlitemTemplate, aPanelchildren- resolving every$strings.reference in everypropertiesbag along the way against a flat dictionary. A missing key stays as its literal token rather than going blank, matching the failure mode that already existed and staying visible to an author.This is the generic half only - turning a dictionary and a tree into a resolved tree, no opinion about where the dictionary comes from. That is filesystem access only a host has; Stage picks it up from here in a follow-up PR.
Verification
yarn test/yarn lintinSource/JavaScript/engine: 236/236 passing (9 new), clean lint.