Skip to content

Give a $strings. reference somewhere to resolve against, at runtime - #47

Merged
einari merged 2 commits into
mainfrom
feature/runtime-strings-resolution
Sep 24, 2026
Merged

einari merged 2 commits into
mainfrom
feature/runtime-strings-resolution

Conversation

@einari

@einari einari commented Sep 24, 2026

Copy link
Copy Markdown
Contributor

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 .strings file 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

resolveStringsInElement walks an element tree the same way elementKind.ts already distinguishes kinds elsewhere in this package - an ExternalComponent named slots, a ContentControl own content, an ItemsControl itemTemplate, a Panel children - resolving every $strings. reference in every properties bag 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 lint in Source/JavaScript/engine: 236/236 passing (9 new), clean lint.

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 einari added the minor label Sep 24, 2026
@einari
einari merged commit 0b03d29 into main Sep 24, 2026
3 checks passed
@einari
einari deleted the feature/runtime-strings-resolution branch September 24, 2026 05:31
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant