Declare a message whose stored value is the key, not the text.
Some user-visible strings are produced as data far from where they are rendered — a command's
disabledReasons, contributed by a model-driven reducer that re-runs on selection/protection
changes but NOT on a language switch. Baking English (or any language) into that data freezes it:
whatever language was active when the reducer last ran is what the user keeps seeing. It is the
same trap as storing a localized formula, and it gets the same answer the rest of this codebase
uses — canonical storage, localized display:
At runtime this is the identity function — messageKey('k') IS 'k'. Its whole purpose is
to be a marker for scripts/i18n-extract.mjs, which scans for t( and messageKey( to build
the key set. That is what makes deleting a call site drop its key from messages.json, and what
makes a key with no English anywhere FAIL the build. A bare 'reason.sheetProtected' literal is
invisible to all of it, so the key would rot silently. It also tells the next reader that the
string is a key resolved elsewhere rather than an identifier.
The English is NOT a parameter — it lives only in the package's locales/en/messages.json
(I18N-DESIGN.md decision 9).
Prefer calling this AT THE CALL SITE over hoisting it into a module-level const: the const only
restates the key under a second name, which is the same duplication decision 9 rejects. (A const
holding a messageKey is not a correctness bug — the key is static, so it cannot freeze a
language the way const X = t(...) does, which evaluates its lookup once at import.)
Declare a message whose stored value is the key, not the text.
Some user-visible strings are produced as data far from where they are rendered — a command's
disabledReasons, contributed by a model-driven reducer that re-runs on selection/protection changes but NOT on a language switch. Baking English (or any language) into that data freezes it: whatever language was active when the reducer last ran is what the user keeps seeing. It is the same trap as storing a localized formula, and it gets the same answer the rest of this codebase uses — canonical storage, localized display:At runtime this is the identity function —
messageKey('k')IS'k'. Its whole purpose is to be a marker forscripts/i18n-extract.mjs, which scans fort(andmessageKey(to build the key set. That is what makes deleting a call site drop its key frommessages.json, and what makes a key with no English anywhere FAIL the build. A bare'reason.sheetProtected'literal is invisible to all of it, so the key would rot silently. It also tells the next reader that the string is a key resolved elsewhere rather than an identifier.The English is NOT a parameter — it lives only in the package's
locales/en/messages.json(I18N-DESIGN.md decision 9).Prefer calling this AT THE CALL SITE over hoisting it into a module-level
const: the const only restates the key under a second name, which is the same duplication decision 9 rejects. (A const holding amessageKeyis not a correctness bug — the key is static, so it cannot freeze a language the wayconst X = t(...)does, which evaluates its lookup once at import.)