feat(admin): a plugin can translate its own texts - #375
Merged
Conversation
The snippet system already takes plugin files: RegistrySnippetSyncService reads
{"snippets": {"de-DE": "..."}} out of a package's registry.json and even checks that
every key carries the pluginId as a prefix, so no package can overwrite another's
texts. The admin shell loads all of them and translates with t(key, fallback).
What was missing was the way in. @callora/admin/extensions exported no translation
function, and a plugin bundling vue-i18n itself does not help: the vite preset makes
only vue external, so the plugin would get a second instance that knows none of the
loaded snippets — a translation function that always returns the fallback and never
says why.
So t() goes through globalThis.CalloraAdmin like everything else in that barrel, and
the shell fills it with the t it already has. A plugin bundle stays free of the shell.
The second parameter is what makes this adoptable one line at a time: until a key
exists in the plugin's snippet files, the fallback is what shows — never the key
itself. A screen displaying `pbx.person.blocked` to an operator is worse than one
that was never translated.
It is the only function in that barrel that does not warn when the shell is absent.
Every other one registers something and can honestly say "that did not happen"; this
one has to return a string, and the fallback is a correct answer rather than a
degraded one. A warning per rendered label would drown a test run in noise for a case
that works as designed.
Admin suite: 599 tests green, types clean.
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.
The snippet system already takes plugin files.
RegistrySnippetSyncServicereads{"snippets": {"de-DE": "snippets/de-DE.json"}}out of a package'sregistry.jsonand even checks that every key carries the pluginId as a prefix, so no package can overwrite another's texts. The admin shell loads all of them and translates witht(key, fallback).What was missing was the way in.
@callora/admin/extensionsexported no translation function, so a plugin had two options: hardcode its texts, or bundlevue-i18nitself. The second does not work — the vite preset makes onlyvueexternal, so the plugin would get a second i18n instance that knows none of the loaded snippets. A translation function that always returns the fallback and never says why.Which is why every plugin UI in this repo has its texts in the source, in one language.
The shape
t()goes throughglobalThis.CalloraAdminlike everything else in that barrel, and the shell fills it with thetit already has. The plugin bundle stays free of the shell — the same reason Vue is shared rather than bundled.The second parameter is what makes this adoptable one line at a time. Until a key exists in the plugin's snippet files, the fallback is what shows — never the key itself. A screen displaying
pbx.person.blockedto an operator is worse than one that was never translated. It is the same bargain the shell's owntalready makes with its screens.One deliberate asymmetry
This is the only function in that barrel that does not warn when the shell is absent. Every other one registers something and can honestly say "that did not happen"; this one has to return a string, and the fallback is a correct answer rather than a degraded one. A warning per rendered label would drown a test run in noise for a case that works exactly as designed.
Verification
Admin suite: 599 tests green across 89 files,
vue-tscclean. Three new tests cover the three states: key known, key unknown, shell absent — the last one asserting that nothing is warned.