Add support for l10n globals and use for Harvester product name - #18647
Add support for l10n globals and use for Harvester product name#18647nwmac wants to merge 4 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
This PR introduces runtime substitution for l10n “global” tokens (e.g. [[Harvester]]) inside i18n translation strings, allowing extensions to register shared terms once and reuse them across locales. It then applies this mechanism to ensure the Harvester product naming can switch between Community (“Harvester”) and Prime (“SUSE Virtualization”).
Changes:
- Added
[[name]]token substitution in the i18nt()getter, backed by extension-registered globals (l10n-global) and an escape form (\[[name]]). - Extended extension registration typing and IDs to support registering string values (not just functions) for the new global-token extension point.
- Updated built-in and Harvester Manager translation strings to use
[[Harvester]]and added unit tests for the substitution behavior.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| shell/types/extension-manager.ts | Updates extension-manager typing to allow string-valued registrations. |
| shell/store/i18n.js | Implements [[name]] substitution and wires rootState into t() for extension lookups. |
| shell/store/tests/i18n.test.ts | Adds unit tests for global-token substitution, escaping, and fallback behavior. |
| shell/core/types.ts | Updates public extension API typing/docs to include string registration values. |
| shell/core/plugin.ts | Adds EXT_IDS.I18N_GLOBAL and widens register() signature to accept strings. |
| shell/assets/translations/zh-hans.yaml | Replaces hardcoded “Harvester” occurrences with [[Harvester]] tokens in core UI strings. |
| shell/assets/translations/en-us.yaml | Replaces hardcoded “Harvester” occurrences with [[Harvester]] tokens in core UI strings. |
| pkg/harvester-manager/l10n/zh-hans.yaml | Updates Harvester Manager strings to use [[Harvester]]. |
| pkg/harvester-manager/l10n/en-us.yaml | Updates Harvester Manager strings to use [[Harvester]]. |
| pkg/harvester-manager/index.ts | Registers l10n-global for Harvester and overrides product naming when running in Prime. |
Suppressed comments (1)
shell/core/plugin.ts:459
- After widening
register()to accept string values, thel10nandmodel-extensionbranches still assumefnis a function (casts and stores into function arrays). Add a runtime guard to prevent invalid registrations from being stored and failing later when invoked.
if (type === 'l10n') {
if (!this.l10n[name]) {
this.l10n[name] = [];
}
this.l10n[name].push(fn as Function);
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| * Replace [[name]] tokens with values registered as i18n globals via | ||
| * `extension.register('i18n-global', name, value)`. If the token is not | ||
| * registered, the name itself is used as the value. Use `\[[` to include a | ||
| * literal `[[` in a translation string. |
| * @param {Function|String|Boolean} fn function that dynamically loads the module for the thing being registered, or (for `l10n-global`) the value itself | ||
| */ | ||
| register(type: string, name: string, fn: Function | Boolean): void; | ||
| register(type: string, name: string, fn: Function | Boolean | String): void; |
| import { ClusterProvisionerContext } from '@shell/core/types'; | ||
|
|
||
| type ExtensionManagerType = { [name: string]: Function, } | ||
| type ExtensionManagerType = { [name: string]: Function | String } |
| register(type: string, name: string, fn: Function | String): void; | ||
| unregister(type: string, name: string, fn: Function): void; |
| if (plugin.environment.isPrime) { | ||
| plugin.register('l10n-global', 'Harvester', 'SUSE Virtualization'); | ||
| plugin.register('l10n', 'en-us', () => { | ||
| return { product: { harvesterManager: 'SUSE Virtualization' } }; | ||
| }); | ||
| } |
| // Substitute [[name]] tokens with values registered as i18n globals. | ||
| const hasGlobal = msg.includes('[['); | ||
| const substituted = hasGlobal ? substituteGlobals(msg, rootState?.$extension) : msg; | ||
|
|
||
| if ( substituted?.includes('{')) { |
| } | ||
|
|
||
| public register(type: string, name: string, fn: Function) { | ||
| public register(type: string, name: string, fn: Function | String) { |
|
🔄 Auto-Retry Failed Run The Tests workflow failed for the commits below and has been automatically retried.
|
Removed unused import of substituteGlobals from i18n.
Summary
Fixes #18646
Adds support for i18n global tokens —
[[name]]placeholders in translation strings that are resolved at runtime via values registered by extensions. This allows shared terms (e.g. product names) to be changed depending on whether a Community or Prime build of an extension is running.We use this mechanism to allow us to change the product name for Harvester to 'SUSE Vitualization' when needed - this will cover the strings for Harvester that are built into dashboard.
The way this works, when Community, we will use 'Harvester' and when Prime, 'SUSE Virtualization'.
We will need to update the Harvester extension to also update its strings - it won't be able to use this mechanism for now directly, since this change will only be in 2.16, but it can do the same thing in a similar way - it can also set the i18n global, so if you install the Harvester extension (not SUSE Virt) into Prime, it will change back to say Harvester.
Occurred changes and/or fixed issues
substituteGlobalsfunction inshell/store/i18n.jsto replace[[name]]tokens with values registered viaplugin.register('l10n-global', name, value).EXT_IDSinshell/core/plugin.tswithI18N_GLOBALconstant and updatedregister()to acceptStringvalues (not onlyFunction).shell/core/types.tsandshell/types/extension-manager.tsto reflect the newStringunion type for registered values.SUSE Virtualizationwhen running in a Rancher Prime environment.en-us.yaml,zh-hans.yaml) updated to use[[Harvester]]tokens where the Harvester product name appears.substituteGlobalsinshell/store/__tests__/i18n.test.ts.Technical notes summary
[[name]]in a translation string is replaced by the registered global value forname. If no global is registered, the token name itself is used as the fallback.\[[name]]outputs a literal[[name]]without substitution.plugin.register('l10n-global', name, value)can be a plain string or a function returning a string.tgetter now receivesrootStateso it can access$extensionfor global lookups.Areas or cases that should be tested
[[Harvester]]tokens in translation strings are replaced withHarvesterby default.[[Harvester]]is replaced withSUSE Virtualizationthroughout the Harvester Manager product.\[[Harvester]]renders as the literal text[[Harvester]].[[tokens are unaffected (no performance regression).en-us) and Chinese (zh-hans) locales.Areas which could experience regressions
[[that was previously treated as a literal character will now be subject to token substitution. Existing strings should be audited if they contain[[.tgetter signature change (addedrootState) — any code that calls the getter directly may be affected.Screenshot/Video
With this change:
Non-Prime:
Prime:
Checklist
Admin,Standard UserandUser Base