Skip to content

Add support for l10n globals and use for Harvester product name - #18647

Open
nwmac wants to merge 4 commits into
rancher:masterfrom
nwmac:i18n-globals
Open

Add support for l10n globals and use for Harvester product name#18647
nwmac wants to merge 4 commits into
rancher:masterfrom
nwmac:i18n-globals

Conversation

@nwmac

@nwmac nwmac commented Aug 5, 2026

Copy link
Copy Markdown
Member

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

  • Added substituteGlobals function in shell/store/i18n.js to replace [[name]] tokens with values registered via plugin.register('l10n-global', name, value).
  • Extended EXT_IDS in shell/core/plugin.ts with I18N_GLOBAL constant and updated register() to accept String values (not only Function).
  • Updated type definitions in shell/core/types.ts and shell/types/extension-manager.ts to reflect the new String union type for registered values.
  • Harvester Manager uses the new feature to override the product name to SUSE Virtualization when running in a Rancher Prime environment.
  • Corresponding translation files (en-us.yaml, zh-hans.yaml) updated to use [[Harvester]] tokens where the Harvester product name appears.
  • Added unit tests for substituteGlobals in shell/store/__tests__/i18n.test.ts.

Technical notes summary

  • Token syntax: [[name]] in a translation string is replaced by the registered global value for name. If no global is registered, the token name itself is used as the fallback.
  • Escape sequence: \[[name]] outputs a literal [[name]] without substitution.
  • The value registered via plugin.register('l10n-global', name, value) can be a plain string or a function returning a string.
  • The i18n t getter now receives rootState so it can access $extension for global lookups.

Areas or cases that should be tested

  • Verify [[Harvester]] tokens in translation strings are replaced with Harvester by default.
  • In a Rancher Prime environment, verify [[Harvester]] is replaced with SUSE Virtualization throughout the Harvester Manager product.
  • Verify \[[Harvester]] renders as the literal text [[Harvester]].
  • Verify that translation strings without [[ tokens are unaffected (no performance regression).
  • Test in both English (en-us) and Chinese (zh-hans) locales.

Areas which could experience regressions

  • Any translation string containing [[ that was previously treated as a literal character will now be subject to token substitution. Existing strings should be audited if they contain [[.
  • The i18n t getter signature change (added rootState) — any code that calls the getter directly may be affected.

Screenshot/Video

With this change:

Non-Prime:

image

Prime:

image

Checklist

  • The PR is linked to an issue and the linked issue has a Milestone, or no issue is needed
  • The PR has a Milestone
  • The PR template has been filled out
  • The PR has been self reviewed
  • The PR has a reviewer assigned
  • The PR has automated tests or clear instructions for manual tests and the linked issue has appropriate QA labels, or tests are not needed
  • The PR has reviewed with UX and tested in light and dark mode, or there are no UX changes
  • The PR has been reviewed in terms of Accessibility
  • The PR has considered, and if applicable tested with, the three Global Roles Admin, Standard User and User Base

@nwmac nwmac added this to the v2.16.0 milestone Aug 5, 2026
@nwmac
nwmac requested a lite review from Copilot August 5, 2026 09:09

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 i18n t() 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, the l10n and model-extension branches still assume fn is 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.

Comment thread shell/store/i18n.js
Comment on lines +20 to +23
* 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.
Comment thread shell/core/types.ts
Comment on lines +693 to +695
* @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 }
Comment on lines +20 to 21
register(type: string, name: string, fn: Function | String): void;
unregister(type: string, name: string, fn: Function): void;
Comment on lines +17 to +22
if (plugin.environment.isPrime) {
plugin.register('l10n-global', 'Harvester', 'SUSE Virtualization');
plugin.register('l10n', 'en-us', () => {
return { product: { harvesterManager: 'SUSE Virtualization' } };
});
}
Comment thread shell/store/i18n.js
Comment on lines +127 to +131
// Substitute [[name]] tokens with values registered as i18n globals.
const hasGlobal = msg.includes('[[');
const substituted = hasGlobal ? substituteGlobals(msg, rootState?.$extension) : msg;

if ( substituted?.includes('{')) {
Comment thread shell/core/plugin.ts
}

public register(type: string, name: string, fn: Function) {
public register(type: string, name: string, fn: Function | String) {
@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

🔄 Auto-Retry Failed Run

The Tests workflow failed for the commits below and has been automatically retried.
Follow up on a retried run through its link if needed.

Commit Workflow run Retried at
9066c11 30991516798 2026-08-05 09:42 UTC
734bb5c 30992002000 2026-08-05 09:48 UTC
95332e4 30995151969 2026-08-05 10:28 UTC
aff134c 30995475530 2026-08-05 10:43 UTC

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Ensure we use correct terminology for Harvester/SUSE Virtualisation

2 participants