From 331d13afdecd61fc445113fb12cea89909180fe3 Mon Sep 17 00:00:00 2001 From: rcourtman Date: Sun, 14 Jun 2026 08:41:11 +0100 Subject: [PATCH] Localize first-session monitoring journey --- ...ces-first-session-monitoring-2026-06-14.md | 28 + docs/release-control/v6/internal/status.json | 65 +++ .../v6/internal/subsystems/agent-lifecycle.md | 6 + .../v6/internal/subsystems/cloud-paid.md | 4 + .../subsystems/frontend-primitives.md | 7 +- .../SetupWizard/SetupCompletionPanel.tsx | 98 ++-- .../components/SetupWizard/SetupWizard.tsx | 23 +- .../components/SetupWizard/StepIndicator.tsx | 11 +- .../SetupCompletionPanel.guardrails.test.ts | 103 +++- .../SetupWizard.localization.test.tsx | 163 ++++++ .../__tests__/SetupWizard.test.tsx | 12 + .../SetupWizard/setupCompletionModel.ts | 71 ++- .../SetupWizard/steps/SecurityStep.tsx | 62 ++- .../SetupWizard/steps/WelcomeStep.tsx | 184 ++++--- .../src/i18n/__tests__/i18n.test.ts | 62 +++ frontend-modern/src/i18n/index.ts | 3 + frontend-modern/src/i18n/messages.ts | 516 ++++++++++++++++++ frontend-modern/src/i18n/policy.ts | 35 +- frontend-modern/src/pages/RuntimeHome.tsx | 4 +- .../src/pages/__tests__/RuntimeHome.test.tsx | 15 +- 20 files changed, 1227 insertions(+), 245 deletions(-) create mode 100644 docs/release-control/v6/internal/records/localized-product-surfaces-first-session-monitoring-2026-06-14.md create mode 100644 frontend-modern/src/components/SetupWizard/__tests__/SetupWizard.localization.test.tsx diff --git a/docs/release-control/v6/internal/records/localized-product-surfaces-first-session-monitoring-2026-06-14.md b/docs/release-control/v6/internal/records/localized-product-surfaces-first-session-monitoring-2026-06-14.md new file mode 100644 index 000000000..5c5fea898 --- /dev/null +++ b/docs/release-control/v6/internal/records/localized-product-surfaces-first-session-monitoring-2026-06-14.md @@ -0,0 +1,28 @@ +# Localized Product Surfaces First Session Monitoring - 2026-06-14 + +## Slice + +- Claimed governed slice: `candidate-lane:localized-product-surfaces`. +- Migrated journey: first-session setup through the first monitoring handoff, including Runtime Home loading copy, the Setup Wizard shell and progress indicator, Welcome bootstrap-token guidance, Security admin-account creation copy, Setup Completion credential/next-step/source-choice copy, and the connected first monitored system handoff. +- First-wave locales: German (`de`) and Spanish (`es`). + +## Evidence + +- `frontend-modern/src/i18n/messages.ts` now includes explicit German and Spanish catalog entries for the migrated first-session monitoring journey. +- `frontend-modern/src/i18n/policy.ts` records first-session monitoring non-translation tokens for commands, URLs, API labels, generated credentials, product/source identifiers, and setup-token file names. +- `frontend-modern/src/components/SetupWizard/SetupWizard.tsx`, `StepIndicator.tsx`, `steps/WelcomeStep.tsx`, `steps/SecurityStep.tsx`, `SetupCompletionPanel.tsx`, and `setupCompletionModel.ts` now render migrated operator copy through the i18n owner layer instead of page-local English. +- `frontend-modern/src/pages/RuntimeHome.tsx` now renders the authenticated workspace handoff through the same catalog. +- Guardrails cover catalog completeness, explicit first-wave translations, unsupported-locale fallback through the shared i18n foundation, machine-facing token preservation, localized render proof for German and Spanish setup states, and source checks that fail if migrated first-session copy returns to hardcoded English in the touched surfaces. + +## Verification + +- `npm --prefix frontend-modern test -- --run src/i18n/__tests__/i18n.test.ts src/components/SetupWizard/__tests__/SetupWizard.localization.test.tsx src/components/SetupWizard/__tests__/SetupCompletionPanel.guardrails.test.ts src/components/SetupWizard/__tests__/WelcomeStep.test.tsx src/components/SetupWizard/__tests__/SetupCompletionPanel.test.tsx src/components/SetupWizard/__tests__/SetupCompletionPreview.test.tsx src/components/SetupWizard/__tests__/SetupWizard.test.tsx src/pages/__tests__/RuntimeHome.test.tsx` passed: 8 files, 44 tests. +- `npm --prefix frontend-modern test -- --run src/components/SetupWizard/__tests__/SetupWizard.localization.test.tsx src/i18n/__tests__/i18n.test.ts` passed: 2 files, 18 tests. +- `npm --prefix frontend-modern run type-check` passed. +- Browser proof passed against the managed dev server at `http://127.0.0.1:5173`: `/settings/system-general` switched the app language through the visible `Español` and `Deutsch` controls; `/preview/setup-complete` rendered the empty first-source handoff in Spanish with `Elige tu primera fuente de infraestructura`, `Credenciales que debes guardar ahora`, `Agregar infraestructura`, and `Instalar Pulse Agent`; `/preview/setup-complete?scenario=vmware-api-backed` rendered the connected handoff in German with `Erstes ueberwachtes System verbunden` and `Infrastruktur oeffnen` while preserving `vCenter Prod`, `VMware vSphere`, `API`, and `Pulse Agent`. + +## Residuals + +- The broader localized-product-surfaces lane remains open. This slice does not translate website/acquisition copy, install and troubleshooting docs outside the first-session UI, Settings surfaces beyond Settings General, core monitoring tables, alerts/operator-action copy, mobile handoff copy, commercial trust surfaces, public checkout/account surfaces, or native mobile surfaces. +- Locale QA remains local guardrail and browser proof for the migrated frontend-modern journey. Broader screenshot review, native speaker review, pseudo-locale coverage, extraction tooling, and future French/Brazilian Portuguese/Japanese/Simplified Chinese/Korean expansion remain separate governed work. +- Machine-facing payloads, log text, config keys, API fields, commands, source resource names, URLs, generated credentials, and user-entered identifiers remain intentionally untranslated. diff --git a/docs/release-control/v6/internal/status.json b/docs/release-control/v6/internal/status.json index 2bfc35037..1c51e20a2 100644 --- a/docs/release-control/v6/internal/status.json +++ b/docs/release-control/v6/internal/status.json @@ -6929,11 +6929,26 @@ "proposed_resolution": "new-lane", "coverage_impact": 8, "evidence": [ + { + "repo": "pulse", + "path": "docs/release-control/v6/internal/records/localized-product-surfaces-first-session-monitoring-2026-06-14.md", + "kind": "file" + }, { "repo": "pulse", "path": "docs/release-control/v6/internal/records/localized-product-surfaces-foundation-2026-06-13.md", "kind": "file" }, + { + "repo": "pulse", + "path": "docs/release-control/v6/internal/subsystems/agent-lifecycle.md", + "kind": "file" + }, + { + "repo": "pulse", + "path": "docs/release-control/v6/internal/subsystems/cloud-paid.md", + "kind": "file" + }, { "repo": "pulse", "path": "docs/release-control/v6/internal/subsystems/frontend-primitives.md", @@ -6959,6 +6974,51 @@ "path": "frontend-modern/src/components/Settings/settingsNavCatalog.ts", "kind": "file" }, + { + "repo": "pulse", + "path": "frontend-modern/src/components/SetupWizard/__tests__/SetupCompletionPanel.guardrails.test.ts", + "kind": "file" + }, + { + "repo": "pulse", + "path": "frontend-modern/src/components/SetupWizard/__tests__/SetupWizard.localization.test.tsx", + "kind": "file" + }, + { + "repo": "pulse", + "path": "frontend-modern/src/components/SetupWizard/setupCompletionModel.ts", + "kind": "file" + }, + { + "repo": "pulse", + "path": "frontend-modern/src/components/SetupWizard/SetupCompletionPanel.tsx", + "kind": "file" + }, + { + "repo": "pulse", + "path": "frontend-modern/src/components/SetupWizard/SetupWizard.tsx", + "kind": "file" + }, + { + "repo": "pulse", + "path": "frontend-modern/src/components/SetupWizard/StepIndicator.tsx", + "kind": "file" + }, + { + "repo": "pulse", + "path": "frontend-modern/src/components/SetupWizard/steps/SecurityStep.tsx", + "kind": "file" + }, + { + "repo": "pulse", + "path": "frontend-modern/src/components/SetupWizard/steps/WelcomeStep.tsx", + "kind": "file" + }, + { + "repo": "pulse", + "path": "frontend-modern/src/i18n/__tests__/i18n.test.ts", + "kind": "file" + }, { "repo": "pulse", "path": "frontend-modern/src/i18n/messages.ts", @@ -6968,6 +7028,11 @@ "repo": "pulse", "path": "frontend-modern/src/i18n/policy.ts", "kind": "file" + }, + { + "repo": "pulse", + "path": "frontend-modern/src/pages/RuntimeHome.tsx", + "kind": "file" } ] } diff --git a/docs/release-control/v6/internal/subsystems/agent-lifecycle.md b/docs/release-control/v6/internal/subsystems/agent-lifecycle.md index 9b5424451..230558929 100644 --- a/docs/release-control/v6/internal/subsystems/agent-lifecycle.md +++ b/docs/release-control/v6/internal/subsystems/agent-lifecycle.md @@ -1127,6 +1127,12 @@ instead of lifecycle-local centered icon/text shells. may accept a preview-provided connected-resource override, but the live first-session runtime path must keep `/api/state` polling as the sole source of connected-system truth when no override is supplied. + Localized setup-completion copy may route through + `frontend-modern/src/i18n/messages.ts`, but the lifecycle contract remains + unchanged: add-infrastructure and agent-install destinations, API token + meaning, generated credential values, install/source product identifiers, + and reported resource names must stay stable while only surrounding + operator-facing guidance translates. 7a. Keep lifecycle-neutral shared `internal/api/` changes from altering agent setup, registration, install, or profile payloads by accident. AI runtime or entitlement work that touches shared router or handler wiring must keep diff --git a/docs/release-control/v6/internal/subsystems/cloud-paid.md b/docs/release-control/v6/internal/subsystems/cloud-paid.md index 06116f738..5c4717f60 100644 --- a/docs/release-control/v6/internal/subsystems/cloud-paid.md +++ b/docs/release-control/v6/internal/subsystems/cloud-paid.md @@ -924,6 +924,10 @@ acquisition destinations. The Docker / Podman runtime route remains `/docker` for search familiarity, but the shell may label the destination as Docker so it reads as the recognizable container runtime lens rather than an owning infrastructure platform. +Localized runtime-home loading copy may route through the shared frontend i18n +catalog, but this does not move route ownership or commercial posture into +localization: authenticated `/`, hosted workspaces, public-demo suppression, and +entitlement-read prohibitions remain governed by this cloud-paid boundary. The Proxmox platform tab in the authenticated app shell is ordinary product navigation. It must stay separate from hosted acquisition, monitored-system diff --git a/docs/release-control/v6/internal/subsystems/frontend-primitives.md b/docs/release-control/v6/internal/subsystems/frontend-primitives.md index 35dd3afb4..f1df5ba5a 100644 --- a/docs/release-control/v6/internal/subsystems/frontend-primitives.md +++ b/docs/release-control/v6/internal/subsystems/frontend-primitives.md @@ -200,7 +200,12 @@ fields, config keys, log lines, error codes, hostnames, resource names, product identifiers, and vendor object names stay untranslated unless the owning runtime contract explicitly says otherwise. Migrated settings surfaces must render customer-facing copy through the catalog and shared presentation helpers -rather than reintroducing panel-local English. +rather than reintroducing panel-local English. Migrated first-session surfaces, +including the Setup Wizard shell, welcome/security steps, setup completion +handoff, and runtime-home loading handoff, follow the same catalog path; their +guardrails must fail if the migrated journey returns to page-local English or +translates commands, URLs, generated credentials, product/source identifiers, or +reported resource names. Alert thresholds consume the shared FilterBar primitive and route state, while the alerts subsystem owns the resource data and platform-specific threshold diff --git a/frontend-modern/src/components/SetupWizard/SetupCompletionPanel.tsx b/frontend-modern/src/components/SetupWizard/SetupCompletionPanel.tsx index 0affcf8a9..6671e3972 100644 --- a/frontend-modern/src/components/SetupWizard/SetupCompletionPanel.tsx +++ b/frontend-modern/src/components/SetupWizard/SetupCompletionPanel.tsx @@ -1,4 +1,5 @@ import { Component, createSignal, createEffect, createMemo, onCleanup, Show, For } from 'solid-js'; +import { t } from '@/i18n'; import { copyToClipboard } from '@/utils/clipboard'; import { logger } from '@/utils/logger'; import { apiFetchJSON } from '@/utils/apiClient'; @@ -21,21 +22,6 @@ interface CompleteStepProps { connectedResourcesOverride?: readonly Resource[]; } -const SOURCE_STRATEGY_OPTIONS = [ - { - title: 'Platform API', - description: 'Inventory and health from Proxmox, TrueNAS, VMware, PBS, or PMG.', - }, - { - title: 'Pulse Agent', - description: 'Node-local telemetry for standalone hosts, services, Docker, and Kubernetes.', - }, - { - title: 'Use both', - description: 'Combine platform inventory with Agent telemetry when full coverage matters.', - }, -] as const; - const ADD_INFRASTRUCTURE_PATH = buildInfrastructureOnboardingPath('pick'); const AGENT_INSTALL_PATH = buildInfrastructureOnboardingPath('linux-host'); const INFRASTRUCTURE_WORKSPACE_PATH = buildInfrastructureWorkspacePath(); @@ -115,29 +101,14 @@ export const SetupCompletionPanel: Component = (props) => { const downloadCredentials = () => { const baseUrl = getPulseBaseUrl(); const infrastructureUrl = `${baseUrl.replace(/\/$/, '')}${ADD_INFRASTRUCTURE_PATH}`; - const content = `Pulse Credentials -================== -Generated: ${new Date().toISOString()} - -Web Login: ----------- -URL: ${baseUrl} -Username: ${props.state.username} -Password: ${props.state.password} - -Admin API Token: ----------------- -${props.state.apiToken} - -Infrastructure: ---------------- -${infrastructureUrl} - -Use Add infrastructure to choose a platform API, Pulse Agent, or both -for the first system Pulse should monitor. - -Keep these credentials secure! -`; + const content = t('setup.completion.download.content', { + generatedAt: new Date().toISOString(), + baseUrl, + username: props.state.username, + password: props.state.password, + apiToken: props.state.apiToken, + infrastructureUrl, + }); const blob = new Blob([content], { type: 'text/plain' }); const url = URL.createObjectURL(blob); @@ -163,6 +134,20 @@ Keep these credentials secure! }; const completionViewModel = createMemo(() => buildSetupCompletionViewModel(connectedSystems())); + const sourceStrategyOptions = createMemo(() => [ + { + title: t('setup.completion.sourceOptions.platformApi.title'), + description: t('setup.completion.sourceOptions.platformApi.description'), + }, + { + title: t('setup.completion.sourceOptions.agent.title'), + description: t('setup.completion.sourceOptions.agent.description'), + }, + { + title: t('setup.completion.sourceOptions.both.title'), + description: t('setup.completion.sourceOptions.both.description'), + }, + ]); return (
@@ -246,14 +231,15 @@ Keep these credentials secure!
- Credentials you must save now + {t('setup.completion.credentials.title')} - Shown during setup + {t('setup.completion.credentials.badge')}

- Save the admin login and API token before leaving this screen, then continue into{' '} - {completionViewModel().credentialsContinuationText} + {t('setup.completion.credentials.description', { + destination: completionViewModel().credentialsContinuationText, + })}

@@ -276,14 +262,14 @@ Keep these credentials secure!
- Username + {t('setup.completion.credentials.usernameLabel')}
{props.state.username}
- Password + {t('setup.completion.credentials.passwordLabel')}
@@ -328,7 +314,7 @@ Keep these credentials secure!
- Admin API Token + {t('setup.completion.credentials.apiTokenLabel')}
@@ -383,14 +369,14 @@ Keep these credentials secure! d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" /> - Download credentials + {t('setup.completion.action.downloadCredentials')}
@@ -413,11 +399,13 @@ Keep these credentials secure!
- Recommended next step + {t('setup.completion.nextStep.badge')}
-
Next step
+
+ {t('setup.completion.nextStep.label')} +
{completionViewModel().nextStepSummary}
@@ -432,15 +420,15 @@ Keep these credentials secure! class="inline-flex items-center justify-center gap-2 rounded-md bg-blue-600 px-4 py-3 text-sm font-semibold text-white transition-colors hover:bg-blue-700" > {completionViewModel().primaryAction === 'infrastructure' - ? 'Open Infrastructure' - : 'Add infrastructure'} + ? t('setup.completion.action.openInfrastructure') + : t('setup.completion.action.addInfrastructure')} @@ -448,16 +436,16 @@ Keep these credentials secure! onClick={handleOpenAgentInstall} class="inline-flex items-center justify-center gap-2 rounded-md border border-border px-4 py-3 text-sm font-medium text-base-content transition-colors hover:bg-surface-hover" > - Install Pulse Agent + {t('setup.completion.action.installAgent')}
- Source choices + {t('setup.completion.sourceOptions.title')}
    - + {(option) => (
  • {option.title} diff --git a/frontend-modern/src/components/SetupWizard/SetupWizard.tsx b/frontend-modern/src/components/SetupWizard/SetupWizard.tsx index 7c5aed44a..31f303501 100644 --- a/frontend-modern/src/components/SetupWizard/SetupWizard.tsx +++ b/frontend-modern/src/components/SetupWizard/SetupWizard.tsx @@ -1,4 +1,5 @@ -import { Component, createSignal, Show } from 'solid-js'; +import { Component, createMemo, createSignal, Show } from 'solid-js'; +import { t } from '@/i18n'; import { WelcomeStep } from './steps/WelcomeStep'; import { SecurityStep } from './steps/SecurityStep'; import { SetupCompletionPanel } from './SetupCompletionPanel'; @@ -59,15 +60,27 @@ export const SetupWizard: Component = (props) => { setCurrentStep('completion'); }; - const stepLabels = ['Unlock server', 'Security', 'First source']; + const stepLabels = createMemo(() => [ + t('setup.step.unlockServer'), + t('setup.step.security'), + t('setup.step.firstSource'), + ]); return ( -
    +
    {/* Background decoration */} - diff --git a/frontend-modern/src/components/SetupWizard/steps/WelcomeStep.tsx b/frontend-modern/src/components/SetupWizard/steps/WelcomeStep.tsx index 9f1868929..293086eb1 100644 --- a/frontend-modern/src/components/SetupWizard/steps/WelcomeStep.tsx +++ b/frontend-modern/src/components/SetupWizard/steps/WelcomeStep.tsx @@ -1,4 +1,5 @@ import { Component, createMemo, createSignal, onMount, Show } from 'solid-js'; +import { t } from '@/i18n'; import { showError, showSuccess } from '@/utils/toast'; import { apiFetch, apiFetchJSON } from '@/utils/apiClient'; import { logger } from '@/utils/logger'; @@ -26,9 +27,9 @@ export const WelcomeStep: Component = (props) => { if (copied) { setCopied(true); setTimeout(() => setCopied(false), 2000); - showSuccess('Command copied to clipboard'); + showSuccess(t('setup.welcome.success.commandCopied')); } else { - showError('Failed to copy command'); + showError(t('setup.welcome.error.copyCommandFailed')); } }; @@ -76,17 +77,16 @@ export const WelcomeStep: Component = (props) => { } }; - const snapshotPasteHelp = - 'That looks like the encrypted .bootstrap_token file contents, not the raw setup token. Run the command above and paste the token string it prints.'; + const snapshotPasteHelp = () => t('setup.welcome.error.snapshotPaste'); const handleUnlock = async () => { const trimmedToken = props.bootstrapToken.trim(); if (!trimmedToken) { - showError('Please enter the bootstrap token'); + showError(t('setup.welcome.error.missingBootstrapToken')); return; } if (looksLikeBootstrapTokenSnapshot(trimmedToken)) { - showError(snapshotPasteHelp); + showError(snapshotPasteHelp()); return; } @@ -103,13 +103,13 @@ export const WelcomeStep: Component = (props) => { }); if (!response.ok) { - throw new Error('Invalid bootstrap token'); + throw new Error(t('setup.welcome.error.invalidBootstrapTokenResponse')); } props.setIsUnlocked(true); props.onNext(); } catch (_error) { - showError('Invalid bootstrap token. Please check and try again.'); + showError(t('setup.welcome.error.invalidBootstrapToken')); } finally { setIsValidating(false); } @@ -130,37 +130,39 @@ export const WelcomeStep: Component = (props) => { const deploymentLabel = createMemo(() => { if (isDocker()) { - return 'Docker deployment'; + return t('setup.welcome.deploymentLabel.docker'); } if (inContainer() && lxcCtid()) { - return 'LXC container'; + return t('setup.welcome.deploymentLabel.lxc'); } if (inContainer()) { - return 'Containerized deployment'; + return t('setup.welcome.deploymentLabel.containerized'); } - return 'Direct host install'; + return t('setup.welcome.deploymentLabel.direct'); }); const deploymentHint = createMemo(() => { if (isDocker()) { return dockerContainerName() - ? `Pulse appears to be running in Docker as container "${dockerContainerName()}". Run the command on the Docker host to print the one-time setup token from that container.` - : 'Pulse appears to be running in Docker. Run the command on the Docker host and replace with the running Pulse container name.'; + ? t('setup.welcome.deploymentHint.dockerNamed', { + containerName: dockerContainerName(), + }) + : t('setup.welcome.deploymentHint.dockerUnnamed'); } if (inContainer() && lxcCtid()) { - return `Pulse appears to be running in LXC container ${lxcCtid()}. Run the command on the Proxmox host to execute into that container and print the one-time setup token.`; + return t('setup.welcome.deploymentHint.lxc', { ctid: lxcCtid() }); } if (inContainer()) { - return 'Pulse appears to be running in a containerized environment. Run the command from the host that manages the container so you can print the one-time setup token.'; + return t('setup.welcome.deploymentHint.containerized'); } - return 'Run the command directly in a shell on the Pulse server to print the one-time setup token.'; + return t('setup.welcome.deploymentHint.direct'); }); const unlockHelp = createMemo(() => { if (isDocker()) { - return 'This one-time bootstrap token only unlocks first-run setup. Run the command above and paste the token string it prints. After verification, you will create the admin account and Pulse will generate the long-lived API token separately.'; + return t('setup.welcome.tokenHelp.docker'); } - return 'This one-time bootstrap token only unlocks first-run setup on this Pulse server. Run the command above and paste the token string it prints. It is not your admin password and it is not the API token you will use after setup.'; + return t('setup.welcome.tokenHelp.host'); }); return ( @@ -172,22 +174,25 @@ export const WelcomeStep: Component = (props) => { class="w-20 h-20 rounded-md mb-6 mx-auto dark:shadow-none" />

    - Welcome to Pulse + {t('setup.welcome.hero.title')}

    - Three steps:{' '} - Unlock this Pulse server,{' '} - Create the admin account, then{' '} - Choose the first source. + {t('setup.welcome.hero.stepsIntro')}{' '} + {t('setup.welcome.hero.step.unlock')},{' '} + {t('setup.welcome.hero.step.admin')},{' '} + {t('setup.welcome.hero.stepsThen')}{' '} + {t('setup.welcome.hero.step.source')}.

    - Connect a platform API, install Pulse Agent, or use both for full coverage. + {t('setup.welcome.hero.coverage')}

    -

    Unlock setup

    +

    + {t('setup.welcome.unlockTitle')} +

    {deploymentHint()}

    @@ -199,7 +204,7 @@ export const WelcomeStep: Component = (props) => { -
    + +
    @@ -294,7 +298,7 @@ export const WelcomeStep: Component = (props) => { onClick={props.onNext} class="py-4 px-10 bg-blue-600 hover:bg-blue-700 text-white text-lg font-medium rounded-md transition-colors duration-200" > - Continue to Security → + {t('setup.welcome.action.continueSecurity')}
diff --git a/frontend-modern/src/i18n/__tests__/i18n.test.ts b/frontend-modern/src/i18n/__tests__/i18n.test.ts index d94d34f8c..6e8be7bf5 100644 --- a/frontend-modern/src/i18n/__tests__/i18n.test.ts +++ b/frontend-modern/src/i18n/__tests__/i18n.test.ts @@ -1,9 +1,12 @@ import { afterEach, describe, expect, it } from 'vitest'; import { DEFAULT_LOCALE, + FIRST_SESSION_MONITORING_ALLOWED_IDENTICAL_TRANSLATIONS, + FIRST_SESSION_MONITORING_NON_TRANSLATABLE_TOKENS, FIRST_LOCALIZATION_LOCALES, I18N_MESSAGES, LOCALIZATION_FOUNDATION, + LOCALIZED_FIRST_SESSION_MONITORING_JOURNEY_KEYS, LOCALIZED_SETTINGS_GENERAL_JOURNEY_KEYS, NEVER_TRANSLATE_COPY_RULES, NEXT_LOCALIZATION_LOCALES, @@ -128,6 +131,25 @@ describe('i18n foundation', () => { '"Update"', ]), ); + expect(FIRST_SESSION_MONITORING_NON_TRANSLATABLE_TOKENS).toEqual( + expect.arrayContaining([ + 'Pulse', + 'Pulse Agent', + 'API', + 'URL', + 'Docker', + 'Kubernetes', + 'Proxmox', + 'TrueNAS', + 'VMware', + 'PBS', + 'PMG', + '.bootstrap_token', + 'sudo pulse bootstrap-token', + 'docker exec', + 'pct exec', + ]), + ); }); it('requires explicit first-wave translations for the migrated settings general journey', () => { @@ -162,4 +184,44 @@ describe('i18n foundation', () => { ).toContain('PVE_POLLING_INTERVAL'); } }); + + it('requires explicit first-wave translations for the migrated first-session monitoring journey', () => { + for (const locale of FIRST_LOCALIZATION_LOCALES) { + const allowedIdenticalKeys: ReadonlySet = new Set([ + ...((SETTINGS_GENERAL_ALLOWED_IDENTICAL_TRANSLATIONS[locale] ?? + []) as readonly I18nMessageKey[]), + ...((FIRST_SESSION_MONITORING_ALLOWED_IDENTICAL_TRANSLATIONS[locale] ?? + []) as readonly I18nMessageKey[]), + ]); + for (const key of LOCALIZED_FIRST_SESSION_MONITORING_JOURNEY_KEYS) { + expect(I18N_MESSAGES[locale][key], `${locale}:${key}`).toBeTruthy(); + if (!allowedIdenticalKeys.has(key)) { + expect(I18N_MESSAGES[locale][key], `${locale}:${key}`).not.toBe(I18N_MESSAGES.en[key]); + } + } + } + }); + + it('keeps machine-facing identifiers unchanged in first-session monitoring catalog copy', () => { + for (const locale of FIRST_LOCALIZATION_LOCALES) { + expect(I18N_MESSAGES[locale]['setup.welcome.deploymentHint.dockerUnnamed']).toContain( + '', + ); + expect(I18N_MESSAGES[locale]['setup.welcome.deploymentHint.lxc']).toContain('Proxmox'); + expect(I18N_MESSAGES[locale]['setup.welcome.error.snapshotPaste']).toContain( + '.bootstrap_token', + ); + expect(I18N_MESSAGES[locale]['setup.completion.download.content']).toContain('URL:'); + expect( + I18N_MESSAGES[locale]['setup.completion.sourceOptions.platformApi.description'], + ).toContain('Proxmox'); + expect( + I18N_MESSAGES[locale]['setup.completion.sourceOptions.platformApi.description'], + ).toContain('TrueNAS'); + expect(I18N_MESSAGES[locale]['setup.completion.sourceOptions.agent.description']).toContain( + 'Docker', + ); + expect(I18N_MESSAGES[locale]['setup.security.nextScreen.itemApiToken']).toContain('API'); + } + }); }); diff --git a/frontend-modern/src/i18n/index.ts b/frontend-modern/src/i18n/index.ts index 8876bd97f..f4007d189 100644 --- a/frontend-modern/src/i18n/index.ts +++ b/frontend-modern/src/i18n/index.ts @@ -139,7 +139,10 @@ export { type SupportedLocale, } from './locales'; export { + FIRST_SESSION_MONITORING_ALLOWED_IDENTICAL_TRANSLATIONS, + FIRST_SESSION_MONITORING_NON_TRANSLATABLE_TOKENS, LOCALIZATION_FOUNDATION, + LOCALIZED_FIRST_SESSION_MONITORING_JOURNEY_KEYS, LOCALIZED_SETTINGS_GENERAL_JOURNEY_KEYS, NEVER_TRANSLATE_COPY_RULES, SETTINGS_GENERAL_ALLOWED_IDENTICAL_TRANSLATIONS, diff --git a/frontend-modern/src/i18n/messages.ts b/frontend-modern/src/i18n/messages.ts index 2befe095c..dac49958c 100644 --- a/frontend-modern/src/i18n/messages.ts +++ b/frontend-modern/src/i18n/messages.ts @@ -1,6 +1,140 @@ import type { SupportedLocale } from './locales'; export const EN_MESSAGES = { + 'runtimeHome.openingWorkspace': 'Opening workspace...', + 'setup.completion.action.addInfrastructure': 'Add infrastructure', + 'setup.completion.action.downloadCredentials': 'Download credentials', + 'setup.completion.action.installAgent': 'Install Pulse Agent', + 'setup.completion.action.openInfrastructure': 'Open Infrastructure', + 'setup.completion.connectedSummary.singular': 'Connected ({count} system)', + 'setup.completion.connectedSummary.plural': 'Connected ({count} systems)', + 'setup.completion.credentials.apiTokenLabel': 'Admin API Token', + 'setup.completion.credentials.badge': 'Shown during setup', + 'setup.completion.credentials.continuation.empty': 'Add infrastructure.', + 'setup.completion.credentials.continuation.connected': 'Infrastructure or Add infrastructure.', + 'setup.completion.credentials.description': + 'Save the admin login and API token before leaving this screen, then continue into {destination}', + 'setup.completion.credentials.passwordLabel': 'Password', + 'setup.completion.credentials.title': 'Credentials you must save now', + 'setup.completion.credentials.usernameLabel': 'Username', + 'setup.completion.download.content': + 'Pulse Credentials\n==================\nGenerated: {generatedAt}\n\nWeb Login:\n----------\nURL: {baseUrl}\nUsername: {username}\nPassword: {password}\n\nAdmin API Token:\n----------------\n{apiToken}\n\nInfrastructure:\n---------------\n{infrastructureUrl}\n\nUse Add infrastructure to choose a platform API, Pulse Agent, or both\nfor the first system Pulse should monitor.\n\nKeep these credentials secure!\n', + 'setup.completion.hero.connected.description.agent': + 'Your admin account is ready and Pulse is already receiving telemetry. Open Infrastructure to inspect the first system, then return to Add infrastructure when you want another Pulse Agent or platform API source.', + 'setup.completion.hero.connected.description.api': + 'Your admin account is ready and Pulse is already receiving telemetry. Open Infrastructure to inspect the first system, then return to Add infrastructure when you want another platform API or Pulse Agent source.', + 'setup.completion.hero.connected.description.both': + 'Your admin account is ready and Pulse is already receiving telemetry. Open Infrastructure to inspect the first system, then return to Add infrastructure when you want another platform API or Agent source.', + 'setup.completion.hero.connected.title': 'First monitored system connected', + 'setup.completion.hero.empty.description': + 'Your admin account is ready. Next, choose how the first system should enter the unified infrastructure model: platform API inventory, Pulse Agent telemetry, or both.', + 'setup.completion.hero.empty.title': 'Choose your first infrastructure source', + 'setup.completion.nextStep.ariaLabel': 'Setup next step', + 'setup.completion.nextStep.badge': 'Recommended next step', + 'setup.completion.nextStep.detail.agent': + 'Add infrastructure stays available for more Pulse Agent systems or platform API inventory when a platform manages the estate.', + 'setup.completion.nextStep.detail.api': + 'Add infrastructure stays available for more API-backed systems or Pulse Agent telemetry when a system needs node-local coverage.', + 'setup.completion.nextStep.detail.both': + 'Add infrastructure stays available any time you want to expand from this first system with another API source, Agent source, or both.', + 'setup.completion.nextStep.detail.empty': + 'Start with a platform API when a platform manages the estate. Install Pulse Agent when the system itself should report node-local telemetry.', + 'setup.completion.nextStep.label': 'Next step', + 'setup.completion.nextStep.summary.connected.singular': + 'Open Infrastructure to review your first connected system.', + 'setup.completion.nextStep.summary.connected.plural': + 'Open Infrastructure to review your connected systems.', + 'setup.completion.nextStep.summary.empty': + 'Open Add infrastructure to choose a platform API, Pulse Agent, or both.', + 'setup.completion.nextStep.title.connected': 'Open Infrastructure', + 'setup.completion.nextStep.title.empty': 'Choose the first source strategy', + 'setup.completion.resource.unknownName': 'Unknown', + 'setup.completion.sourceOptions.agent.description': + 'Node-local telemetry for standalone hosts, services, Docker, and Kubernetes.', + 'setup.completion.sourceOptions.agent.title': 'Pulse Agent', + 'setup.completion.sourceOptions.both.description': + 'Combine platform inventory with Agent telemetry when full coverage matters.', + 'setup.completion.sourceOptions.both.title': 'Use both', + 'setup.completion.sourceOptions.platformApi.description': + 'Inventory and health from Proxmox, TrueNAS, VMware, PBS, or PMG.', + 'setup.completion.sourceOptions.platformApi.title': 'Platform API', + 'setup.completion.sourceOptions.title': 'Source choices', + 'setup.progress.ariaLabel': 'Setup progress', + 'setup.progress.state.completed': ', completed', + 'setup.progress.state.current': ', current', + 'setup.progress.stepAriaLabel': 'Step {index}: {step}{state}', + 'setup.security.action.back': 'Back', + 'setup.security.action.createAccount': 'Create Account & Continue', + 'setup.security.action.settingUp': 'Setting up...', + 'setup.security.customPassword': 'Custom password', + 'setup.security.description': 'The next step is to choose the first infrastructure source.', + 'setup.security.error.passwordMismatch': 'Passwords do not match', + 'setup.security.error.passwordRequired': 'Please enter a password', + 'setup.security.error.passwordTooShort': 'Password must be at least 12 characters', + 'setup.security.error.setupFailed': 'Setup failed: {error}', + 'setup.security.generatedPasswordHelp': + 'A secure 20-character password will be generated and shown on the next screen.', + 'setup.security.label.confirmPassword': 'Confirm password', + 'setup.security.label.password': 'Password', + 'setup.security.label.username': 'Username', + 'setup.security.minimumPasswordHelp': 'Minimum 12 characters.', + 'setup.security.nextScreen.itemApiToken': 'An admin API token for automation', + 'setup.security.nextScreen.itemCredentials': 'Your username and password', + 'setup.security.nextScreen.saveOnce': 'Save them before continuing, they are shown once.', + 'setup.security.nextScreen.title': 'On the next screen', + 'setup.security.passwordMode.autoGenerate': 'Auto-generate', + 'setup.security.placeholder.password': 'Password (min 12 characters)', + 'setup.security.placeholder.username': 'admin', + 'setup.security.showPassword.hide': 'Hide', + 'setup.security.showPassword.show': 'Show', + 'setup.security.title': 'Create your admin account', + 'setup.step.firstSource': 'First source', + 'setup.step.security': 'Security', + 'setup.step.unlockServer': 'Unlock server', + 'setup.wizard.ariaLabel': 'Pulse Setup Wizard', + 'setup.welcome.action.continueSecurity': 'Continue to Security', + 'setup.welcome.action.verifyToken': 'Verify bootstrap token', + 'setup.welcome.action.verifyingToken': 'Verifying bootstrap token...', + 'setup.welcome.copyCommandTitle': 'Copy command', + 'setup.welcome.deploymentHint.containerized': + 'Pulse appears to be running in a containerized environment. Run the command from the host that manages the container so you can print the one-time setup token.', + 'setup.welcome.deploymentHint.direct': + 'Run the command directly in a shell on the Pulse server to print the one-time setup token.', + 'setup.welcome.deploymentHint.dockerNamed': + 'Pulse appears to be running in Docker as container "{containerName}". Run the command on the Docker host to print the one-time setup token from that container.', + 'setup.welcome.deploymentHint.dockerUnnamed': + 'Pulse appears to be running in Docker. Run the command on the Docker host and replace with the running Pulse container name.', + 'setup.welcome.deploymentHint.lxc': + 'Pulse appears to be running in LXC container {ctid}. Run the command on the Proxmox host to execute into that container and print the one-time setup token.', + 'setup.welcome.deploymentLabel.containerized': 'Containerized deployment', + 'setup.welcome.deploymentLabel.direct': 'Direct host install', + 'setup.welcome.deploymentLabel.docker': 'Docker deployment', + 'setup.welcome.deploymentLabel.lxc': 'LXC container', + 'setup.welcome.error.copyCommandFailed': 'Failed to copy command', + 'setup.welcome.error.invalidBootstrapToken': + 'Invalid bootstrap token. Please check and try again.', + 'setup.welcome.error.invalidBootstrapTokenResponse': 'Invalid bootstrap token', + 'setup.welcome.error.missingBootstrapToken': 'Please enter the bootstrap token', + 'setup.welcome.error.snapshotPaste': + 'That looks like the encrypted .bootstrap_token file contents, not the raw setup token. Run the command above and paste the token string it prints.', + 'setup.welcome.hero.coverage': + 'Connect a platform API, install Pulse Agent, or use both for full coverage.', + 'setup.welcome.hero.step.admin': 'Create the admin account', + 'setup.welcome.hero.step.source': 'Choose the first source', + 'setup.welcome.hero.step.unlock': 'Unlock this Pulse server', + 'setup.welcome.hero.stepsIntro': 'Three steps:', + 'setup.welcome.hero.stepsThen': 'then', + 'setup.welcome.hero.title': 'Welcome to Pulse', + 'setup.welcome.placeholder.bootstrapToken': 'Paste your bootstrap token', + 'setup.welcome.success.commandCopied': 'Command copied to clipboard', + 'setup.welcome.tokenHelp.afterVerify': + 'After Pulse verifies this token, the next step is creating the admin account for this server.', + 'setup.welcome.tokenHelp.docker': + 'This one-time bootstrap token only unlocks first-run setup. Run the command above and paste the token string it prints. After verification, you will create the admin account and Pulse will generate the long-lived API token separately.', + 'setup.welcome.tokenHelp.host': + 'This one-time bootstrap token only unlocks first-run setup on this Pulse server. Run the command above and paste the token string it prints. It is not your admin password and it is not the API token you will use after setup.', + 'setup.welcome.tokenHelp.title': 'What this token does', + 'setup.welcome.unlockTitle': 'Unlock setup', 'settings.general.appearance.title': 'Appearance', 'settings.general.fullWidth.description': 'Expand content to use all available screen width on large monitors.', @@ -239,10 +373,255 @@ export const SETTINGS_GENERAL_MIGRATED_MESSAGE_KEYS = [ 'settings.general.theme.title', ] as const satisfies readonly I18nMessageKey[]; +export const FIRST_SESSION_MONITORING_MIGRATED_MESSAGE_KEYS = [ + 'runtimeHome.openingWorkspace', + 'setup.completion.action.addInfrastructure', + 'setup.completion.action.downloadCredentials', + 'setup.completion.action.installAgent', + 'setup.completion.action.openInfrastructure', + 'setup.completion.connectedSummary.singular', + 'setup.completion.connectedSummary.plural', + 'setup.completion.credentials.apiTokenLabel', + 'setup.completion.credentials.badge', + 'setup.completion.credentials.continuation.empty', + 'setup.completion.credentials.continuation.connected', + 'setup.completion.credentials.description', + 'setup.completion.credentials.passwordLabel', + 'setup.completion.credentials.title', + 'setup.completion.credentials.usernameLabel', + 'setup.completion.download.content', + 'setup.completion.hero.connected.description.agent', + 'setup.completion.hero.connected.description.api', + 'setup.completion.hero.connected.description.both', + 'setup.completion.hero.connected.title', + 'setup.completion.hero.empty.description', + 'setup.completion.hero.empty.title', + 'setup.completion.nextStep.ariaLabel', + 'setup.completion.nextStep.badge', + 'setup.completion.nextStep.detail.agent', + 'setup.completion.nextStep.detail.api', + 'setup.completion.nextStep.detail.both', + 'setup.completion.nextStep.detail.empty', + 'setup.completion.nextStep.label', + 'setup.completion.nextStep.summary.connected.singular', + 'setup.completion.nextStep.summary.connected.plural', + 'setup.completion.nextStep.summary.empty', + 'setup.completion.nextStep.title.connected', + 'setup.completion.nextStep.title.empty', + 'setup.completion.resource.unknownName', + 'setup.completion.sourceOptions.agent.description', + 'setup.completion.sourceOptions.agent.title', + 'setup.completion.sourceOptions.both.description', + 'setup.completion.sourceOptions.both.title', + 'setup.completion.sourceOptions.platformApi.description', + 'setup.completion.sourceOptions.platformApi.title', + 'setup.completion.sourceOptions.title', + 'setup.progress.ariaLabel', + 'setup.progress.state.completed', + 'setup.progress.state.current', + 'setup.progress.stepAriaLabel', + 'setup.security.action.back', + 'setup.security.action.createAccount', + 'setup.security.action.settingUp', + 'setup.security.customPassword', + 'setup.security.description', + 'setup.security.error.passwordMismatch', + 'setup.security.error.passwordRequired', + 'setup.security.error.passwordTooShort', + 'setup.security.error.setupFailed', + 'setup.security.generatedPasswordHelp', + 'setup.security.label.confirmPassword', + 'setup.security.label.password', + 'setup.security.label.username', + 'setup.security.minimumPasswordHelp', + 'setup.security.nextScreen.itemApiToken', + 'setup.security.nextScreen.itemCredentials', + 'setup.security.nextScreen.saveOnce', + 'setup.security.nextScreen.title', + 'setup.security.passwordMode.autoGenerate', + 'setup.security.placeholder.password', + 'setup.security.placeholder.username', + 'setup.security.showPassword.hide', + 'setup.security.showPassword.show', + 'setup.security.title', + 'setup.step.firstSource', + 'setup.step.security', + 'setup.step.unlockServer', + 'setup.wizard.ariaLabel', + 'setup.welcome.action.continueSecurity', + 'setup.welcome.action.verifyToken', + 'setup.welcome.action.verifyingToken', + 'setup.welcome.copyCommandTitle', + 'setup.welcome.deploymentHint.containerized', + 'setup.welcome.deploymentHint.direct', + 'setup.welcome.deploymentHint.dockerNamed', + 'setup.welcome.deploymentHint.dockerUnnamed', + 'setup.welcome.deploymentHint.lxc', + 'setup.welcome.deploymentLabel.containerized', + 'setup.welcome.deploymentLabel.direct', + 'setup.welcome.deploymentLabel.docker', + 'setup.welcome.deploymentLabel.lxc', + 'setup.welcome.error.copyCommandFailed', + 'setup.welcome.error.invalidBootstrapToken', + 'setup.welcome.error.invalidBootstrapTokenResponse', + 'setup.welcome.error.missingBootstrapToken', + 'setup.welcome.error.snapshotPaste', + 'setup.welcome.hero.coverage', + 'setup.welcome.hero.step.admin', + 'setup.welcome.hero.step.source', + 'setup.welcome.hero.step.unlock', + 'setup.welcome.hero.stepsIntro', + 'setup.welcome.hero.stepsThen', + 'setup.welcome.hero.title', + 'setup.welcome.placeholder.bootstrapToken', + 'setup.welcome.success.commandCopied', + 'setup.welcome.tokenHelp.afterVerify', + 'setup.welcome.tokenHelp.docker', + 'setup.welcome.tokenHelp.host', + 'setup.welcome.tokenHelp.title', +] as const satisfies readonly I18nMessageKey[]; + export const I18N_MESSAGES: I18nCatalogs = { en: EN_MESSAGES, de: { ...EN_MESSAGES, + 'runtimeHome.openingWorkspace': 'Arbeitsbereich wird geoeffnet...', + 'setup.completion.action.addInfrastructure': 'Infrastruktur hinzufuegen', + 'setup.completion.action.downloadCredentials': 'Zugangsdaten herunterladen', + 'setup.completion.action.installAgent': 'Pulse Agent installieren', + 'setup.completion.action.openInfrastructure': 'Infrastruktur oeffnen', + 'setup.completion.connectedSummary.singular': 'Verbunden ({count} System)', + 'setup.completion.connectedSummary.plural': 'Verbunden ({count} Systeme)', + 'setup.completion.credentials.apiTokenLabel': 'Admin-API-Token', + 'setup.completion.credentials.badge': 'Waehrend der Einrichtung angezeigt', + 'setup.completion.credentials.continuation.empty': 'Infrastruktur hinzufuegen.', + 'setup.completion.credentials.continuation.connected': + 'Infrastruktur oder Infrastruktur hinzufuegen.', + 'setup.completion.credentials.description': + 'Speichern Sie Admin-Anmeldung und API-Token, bevor Sie diesen Bildschirm verlassen, und fahren Sie dann mit {destination} fort', + 'setup.completion.credentials.passwordLabel': 'Passwort', + 'setup.completion.credentials.title': 'Zugangsdaten jetzt speichern', + 'setup.completion.credentials.usernameLabel': 'Benutzername', + 'setup.completion.download.content': + 'Pulse-Zugangsdaten\n==================\nErstellt: {generatedAt}\n\nWeb-Anmeldung:\n----------\nURL: {baseUrl}\nBenutzername: {username}\nPasswort: {password}\n\nAdmin-API-Token:\n----------------\n{apiToken}\n\nInfrastruktur:\n---------------\n{infrastructureUrl}\n\nVerwenden Sie Infrastruktur hinzufuegen, um eine Plattform-API, Pulse Agent oder beides\nfuer das erste System auszuwaehlen, das Pulse ueberwachen soll.\n\nBewahren Sie diese Zugangsdaten sicher auf!\n', + 'setup.completion.hero.connected.description.agent': + 'Ihr Admin-Konto ist bereit und Pulse empfaengt bereits Telemetrie. Oeffnen Sie Infrastruktur, um das erste System zu pruefen, und kehren Sie zu Infrastruktur hinzufuegen zurueck, wenn Sie eine weitere Pulse Agent- oder Plattform-API-Quelle wollen.', + 'setup.completion.hero.connected.description.api': + 'Ihr Admin-Konto ist bereit und Pulse empfaengt bereits Telemetrie. Oeffnen Sie Infrastruktur, um das erste System zu pruefen, und kehren Sie zu Infrastruktur hinzufuegen zurueck, wenn Sie eine weitere Plattform-API- oder Pulse Agent-Quelle wollen.', + 'setup.completion.hero.connected.description.both': + 'Ihr Admin-Konto ist bereit und Pulse empfaengt bereits Telemetrie. Oeffnen Sie Infrastruktur, um das erste System zu pruefen, und kehren Sie zu Infrastruktur hinzufuegen zurueck, wenn Sie eine weitere Plattform-API- oder Agent-Quelle wollen.', + 'setup.completion.hero.connected.title': 'Erstes ueberwachtes System verbunden', + 'setup.completion.hero.empty.description': + 'Ihr Admin-Konto ist bereit. Waehlen Sie als Naechstes, wie das erste System in das einheitliche Infrastrukturmodell gelangt: Plattform-API-Inventar, Pulse Agent-Telemetrie oder beides.', + 'setup.completion.hero.empty.title': 'Erste Infrastrukturquelle waehlen', + 'setup.completion.nextStep.ariaLabel': 'Naechster Einrichtungsschritt', + 'setup.completion.nextStep.badge': 'Empfohlener naechster Schritt', + 'setup.completion.nextStep.detail.agent': + 'Infrastruktur hinzufuegen bleibt fuer weitere Pulse Agent-Systeme oder Plattform-API-Inventar verfuegbar, wenn eine Plattform die Umgebung verwaltet.', + 'setup.completion.nextStep.detail.api': + 'Infrastruktur hinzufuegen bleibt fuer weitere API-gestuetzte Systeme oder Pulse Agent-Telemetrie verfuegbar, wenn ein System lokale Knotenabdeckung braucht.', + 'setup.completion.nextStep.detail.both': + 'Infrastruktur hinzufuegen bleibt jederzeit verfuegbar, wenn Sie dieses erste System um eine weitere API-Quelle, Agent-Quelle oder beides erweitern wollen.', + 'setup.completion.nextStep.detail.empty': + 'Beginnen Sie mit einer Plattform-API, wenn eine Plattform die Umgebung verwaltet. Installieren Sie Pulse Agent, wenn das System selbst lokale Knotentelemetrie melden soll.', + 'setup.completion.nextStep.label': 'Naechster Schritt', + 'setup.completion.nextStep.summary.connected.singular': + 'Oeffnen Sie Infrastruktur, um Ihr erstes verbundenes System zu pruefen.', + 'setup.completion.nextStep.summary.connected.plural': + 'Oeffnen Sie Infrastruktur, um Ihre verbundenen Systeme zu pruefen.', + 'setup.completion.nextStep.summary.empty': + 'Oeffnen Sie Infrastruktur hinzufuegen, um eine Plattform-API, Pulse Agent oder beides zu waehlen.', + 'setup.completion.nextStep.title.connected': 'Infrastruktur oeffnen', + 'setup.completion.nextStep.title.empty': 'Strategie fuer erste Quelle waehlen', + 'setup.completion.resource.unknownName': 'Unbekannt', + 'setup.completion.sourceOptions.agent.description': + 'Knotenlokale Telemetrie fuer eigenstaendige Hosts, Dienste, Docker und Kubernetes.', + 'setup.completion.sourceOptions.agent.title': 'Pulse Agent', + 'setup.completion.sourceOptions.both.description': + 'Kombinieren Sie Plattforminventar mit Agent-Telemetrie, wenn vollstaendige Abdeckung wichtig ist.', + 'setup.completion.sourceOptions.both.title': 'Beides verwenden', + 'setup.completion.sourceOptions.platformApi.description': + 'Inventar und Zustand aus Proxmox, TrueNAS, VMware, PBS oder PMG.', + 'setup.completion.sourceOptions.platformApi.title': 'Plattform-API', + 'setup.completion.sourceOptions.title': 'Quellenoptionen', + 'setup.progress.ariaLabel': 'Einrichtungsfortschritt', + 'setup.progress.state.completed': ', abgeschlossen', + 'setup.progress.state.current': ', aktuell', + 'setup.progress.stepAriaLabel': 'Schritt {index}: {step}{state}', + 'setup.security.action.back': 'Zurueck', + 'setup.security.action.createAccount': 'Konto erstellen und fortfahren', + 'setup.security.action.settingUp': 'Einrichtung laeuft...', + 'setup.security.customPassword': 'Eigenes Passwort', + 'setup.security.description': + 'Der naechste Schritt ist die Auswahl der ersten Infrastrukturquelle.', + 'setup.security.error.passwordMismatch': 'Passwoerter stimmen nicht ueberein', + 'setup.security.error.passwordRequired': 'Bitte geben Sie ein Passwort ein', + 'setup.security.error.passwordTooShort': 'Das Passwort muss mindestens 12 Zeichen haben', + 'setup.security.error.setupFailed': 'Einrichtung fehlgeschlagen: {error}', + 'setup.security.generatedPasswordHelp': + 'Ein sicheres 20-Zeichen-Passwort wird erzeugt und auf dem naechsten Bildschirm angezeigt.', + 'setup.security.label.confirmPassword': 'Passwort bestaetigen', + 'setup.security.label.password': 'Passwort', + 'setup.security.label.username': 'Benutzername', + 'setup.security.minimumPasswordHelp': 'Mindestens 12 Zeichen.', + 'setup.security.nextScreen.itemApiToken': 'Ein Admin-API-Token fuer Automatisierung', + 'setup.security.nextScreen.itemCredentials': 'Ihr Benutzername und Passwort', + 'setup.security.nextScreen.saveOnce': + 'Speichern Sie sie vor dem Fortfahren; sie werden nur einmal angezeigt.', + 'setup.security.nextScreen.title': 'Auf dem naechsten Bildschirm', + 'setup.security.passwordMode.autoGenerate': 'Automatisch erzeugen', + 'setup.security.placeholder.password': 'Passwort (mind. 12 Zeichen)', + 'setup.security.placeholder.username': 'admin', + 'setup.security.showPassword.hide': 'Ausblenden', + 'setup.security.showPassword.show': 'Anzeigen', + 'setup.security.title': 'Admin-Konto erstellen', + 'setup.step.firstSource': 'Erste Quelle', + 'setup.step.security': 'Sicherheit', + 'setup.step.unlockServer': 'Server entsperren', + 'setup.wizard.ariaLabel': 'Pulse-Einrichtungsassistent', + 'setup.welcome.action.continueSecurity': 'Weiter zu Sicherheit', + 'setup.welcome.action.verifyToken': 'Bootstrap-Token pruefen', + 'setup.welcome.action.verifyingToken': 'Bootstrap-Token wird geprueft...', + 'setup.welcome.copyCommandTitle': 'Befehl kopieren', + 'setup.welcome.deploymentHint.containerized': + 'Pulse scheint in einer containerisierten Umgebung zu laufen. Fuehren Sie den Befehl auf dem Host aus, der den Container verwaltet, damit Sie das einmalige Setup-Token ausgeben koennen.', + 'setup.welcome.deploymentHint.direct': + 'Fuehren Sie den Befehl direkt in einer Shell auf dem Pulse-Server aus, um das einmalige Setup-Token auszugeben.', + 'setup.welcome.deploymentHint.dockerNamed': + 'Pulse scheint in Docker als Container "{containerName}" zu laufen. Fuehren Sie den Befehl auf dem Docker-Host aus, um das einmalige Setup-Token aus diesem Container auszugeben.', + 'setup.welcome.deploymentHint.dockerUnnamed': + 'Pulse scheint in Docker zu laufen. Fuehren Sie den Befehl auf dem Docker-Host aus und ersetzen Sie durch den Namen des laufenden Pulse-Containers.', + 'setup.welcome.deploymentHint.lxc': + 'Pulse scheint in LXC-Container {ctid} zu laufen. Fuehren Sie den Befehl auf dem Proxmox-Host aus, um in diesen Container zu wechseln und das einmalige Setup-Token auszugeben.', + 'setup.welcome.deploymentLabel.containerized': 'Containerisierte Bereitstellung', + 'setup.welcome.deploymentLabel.direct': 'Direkte Host-Installation', + 'setup.welcome.deploymentLabel.docker': 'Docker-Bereitstellung', + 'setup.welcome.deploymentLabel.lxc': 'LXC-Container', + 'setup.welcome.error.copyCommandFailed': 'Befehl konnte nicht kopiert werden', + 'setup.welcome.error.invalidBootstrapToken': + 'Ungueltiges Bootstrap-Token. Bitte pruefen und erneut versuchen.', + 'setup.welcome.error.invalidBootstrapTokenResponse': 'Ungueltiges Bootstrap-Token', + 'setup.welcome.error.missingBootstrapToken': 'Bitte geben Sie das Bootstrap-Token ein', + 'setup.welcome.error.snapshotPaste': + 'Das sieht nach dem verschluesselten Inhalt der Datei .bootstrap_token aus, nicht nach dem rohen Setup-Token. Fuehren Sie den obigen Befehl aus und fuegen Sie die ausgegebene Token-Zeichenfolge ein.', + 'setup.welcome.hero.coverage': + 'Verbinden Sie eine Plattform-API, installieren Sie Pulse Agent oder verwenden Sie beides fuer vollstaendige Abdeckung.', + 'setup.welcome.hero.step.admin': 'Admin-Konto erstellen', + 'setup.welcome.hero.step.source': 'Erste Quelle waehlen', + 'setup.welcome.hero.step.unlock': 'Diesen Pulse-Server entsperren', + 'setup.welcome.hero.stepsIntro': 'Drei Schritte:', + 'setup.welcome.hero.stepsThen': 'dann', + 'setup.welcome.hero.title': 'Willkommen bei Pulse', + 'setup.welcome.placeholder.bootstrapToken': 'Bootstrap-Token einfuegen', + 'setup.welcome.success.commandCopied': 'Befehl in Zwischenablage kopiert', + 'setup.welcome.tokenHelp.afterVerify': + 'Nachdem Pulse dieses Token geprueft hat, erstellen Sie im naechsten Schritt das Admin-Konto fuer diesen Server.', + 'setup.welcome.tokenHelp.docker': + 'Dieses einmalige Bootstrap-Token entsperrt nur die Ersteinrichtung. Fuehren Sie den obigen Befehl aus und fuegen Sie die ausgegebene Token-Zeichenfolge ein. Nach der Pruefung erstellen Sie das Admin-Konto, und Pulse erzeugt das langlebige API-Token separat.', + 'setup.welcome.tokenHelp.host': + 'Dieses einmalige Bootstrap-Token entsperrt nur die Ersteinrichtung auf diesem Pulse-Server. Fuehren Sie den obigen Befehl aus und fuegen Sie die ausgegebene Token-Zeichenfolge ein. Es ist nicht Ihr Admin-Passwort und nicht das API-Token, das Sie nach der Einrichtung verwenden.', + 'setup.welcome.tokenHelp.title': 'Was dieses Token macht', + 'setup.welcome.unlockTitle': 'Einrichtung entsperren', 'settings.general.appearance.title': 'Darstellung', 'settings.general.fullWidth.description': 'Nutzen Sie auf grossen Monitoren die gesamte verfuegbare Bildschirmbreite.', @@ -428,6 +807,143 @@ export const I18N_MESSAGES: I18nCatalogs = { }, es: { ...EN_MESSAGES, + 'runtimeHome.openingWorkspace': 'Abriendo el espacio de trabajo...', + 'setup.completion.action.addInfrastructure': 'Agregar infraestructura', + 'setup.completion.action.downloadCredentials': 'Descargar credenciales', + 'setup.completion.action.installAgent': 'Instalar Pulse Agent', + 'setup.completion.action.openInfrastructure': 'Abrir Infraestructura', + 'setup.completion.connectedSummary.singular': 'Conectado ({count} sistema)', + 'setup.completion.connectedSummary.plural': 'Conectados ({count} sistemas)', + 'setup.completion.credentials.apiTokenLabel': 'Token de API de administrador', + 'setup.completion.credentials.badge': 'Se muestra durante la configuración', + 'setup.completion.credentials.continuation.empty': 'Agregar infraestructura.', + 'setup.completion.credentials.continuation.connected': + 'Infraestructura o Agregar infraestructura.', + 'setup.completion.credentials.description': + 'Guarda el inicio de sesión de administrador y el token de API antes de salir de esta pantalla; luego continúa en {destination}', + 'setup.completion.credentials.passwordLabel': 'Contraseña', + 'setup.completion.credentials.title': 'Credenciales que debes guardar ahora', + 'setup.completion.credentials.usernameLabel': 'Usuario', + 'setup.completion.download.content': + 'Credenciales de Pulse\n==================\nGenerado: {generatedAt}\n\nInicio de sesión web:\n----------\nURL: {baseUrl}\nUsuario: {username}\nContraseña: {password}\n\nToken de API de administrador:\n----------------\n{apiToken}\n\nInfraestructura:\n---------------\n{infrastructureUrl}\n\nUsa Agregar infraestructura para elegir una API de plataforma, Pulse Agent o ambos\npara el primer sistema que Pulse debe supervisar.\n\nGuarda estas credenciales en un lugar seguro.\n', + 'setup.completion.hero.connected.description.agent': + 'Tu cuenta de administrador está lista y Pulse ya recibe telemetría. Abre Infraestructura para revisar el primer sistema; luego vuelve a Agregar infraestructura cuando quieras otra fuente Pulse Agent o API de plataforma.', + 'setup.completion.hero.connected.description.api': + 'Tu cuenta de administrador está lista y Pulse ya recibe telemetría. Abre Infraestructura para revisar el primer sistema; luego vuelve a Agregar infraestructura cuando quieras otra API de plataforma o fuente Pulse Agent.', + 'setup.completion.hero.connected.description.both': + 'Tu cuenta de administrador está lista y Pulse ya recibe telemetría. Abre Infraestructura para revisar el primer sistema; luego vuelve a Agregar infraestructura cuando quieras otra API de plataforma, fuente Agent o ambas.', + 'setup.completion.hero.connected.title': 'Primer sistema supervisado conectado', + 'setup.completion.hero.empty.description': + 'Tu cuenta de administrador está lista. Ahora elige cómo entrará el primer sistema en el modelo unificado de infraestructura: inventario por API de plataforma, telemetría de Pulse Agent o ambos.', + 'setup.completion.hero.empty.title': 'Elige tu primera fuente de infraestructura', + 'setup.completion.nextStep.ariaLabel': 'Siguiente paso de configuración', + 'setup.completion.nextStep.badge': 'Siguiente paso recomendado', + 'setup.completion.nextStep.detail.agent': + 'Agregar infraestructura queda disponible para más sistemas Pulse Agent o inventario por API de plataforma cuando una plataforma administra el entorno.', + 'setup.completion.nextStep.detail.api': + 'Agregar infraestructura queda disponible para más sistemas respaldados por API o telemetría de Pulse Agent cuando un sistema necesita cobertura local del nodo.', + 'setup.completion.nextStep.detail.both': + 'Agregar infraestructura queda disponible cuando quieras ampliar este primer sistema con otra fuente API, fuente Agent o ambas.', + 'setup.completion.nextStep.detail.empty': + 'Empieza con una API de plataforma cuando una plataforma administra el entorno. Instala Pulse Agent cuando el propio sistema deba informar telemetría local del nodo.', + 'setup.completion.nextStep.label': 'Siguiente paso', + 'setup.completion.nextStep.summary.connected.singular': + 'Abre Infraestructura para revisar tu primer sistema conectado.', + 'setup.completion.nextStep.summary.connected.plural': + 'Abre Infraestructura para revisar tus sistemas conectados.', + 'setup.completion.nextStep.summary.empty': + 'Abre Agregar infraestructura para elegir una API de plataforma, Pulse Agent o ambos.', + 'setup.completion.nextStep.title.connected': 'Abrir Infraestructura', + 'setup.completion.nextStep.title.empty': 'Elige la estrategia de primera fuente', + 'setup.completion.resource.unknownName': 'Desconocido', + 'setup.completion.sourceOptions.agent.description': + 'Telemetría local del nodo para hosts independientes, servicios, Docker y Kubernetes.', + 'setup.completion.sourceOptions.agent.title': 'Pulse Agent', + 'setup.completion.sourceOptions.both.description': + 'Combina inventario de plataforma con telemetría de Agent cuando importa la cobertura completa.', + 'setup.completion.sourceOptions.both.title': 'Usar ambos', + 'setup.completion.sourceOptions.platformApi.description': + 'Inventario y salud desde Proxmox, TrueNAS, VMware, PBS o PMG.', + 'setup.completion.sourceOptions.platformApi.title': 'API de plataforma', + 'setup.completion.sourceOptions.title': 'Opciones de fuente', + 'setup.progress.ariaLabel': 'Progreso de configuración', + 'setup.progress.state.completed': ', completado', + 'setup.progress.state.current': ', actual', + 'setup.progress.stepAriaLabel': 'Paso {index}: {step}{state}', + 'setup.security.action.back': 'Atrás', + 'setup.security.action.createAccount': 'Crear cuenta y continuar', + 'setup.security.action.settingUp': 'Configurando...', + 'setup.security.customPassword': 'Contraseña personalizada', + 'setup.security.description': + 'El siguiente paso es elegir la primera fuente de infraestructura.', + 'setup.security.error.passwordMismatch': 'Las contraseñas no coinciden', + 'setup.security.error.passwordRequired': 'Ingresa una contraseña', + 'setup.security.error.passwordTooShort': 'La contraseña debe tener al menos 12 caracteres', + 'setup.security.error.setupFailed': 'La configuración falló: {error}', + 'setup.security.generatedPasswordHelp': + 'Se generará una contraseña segura de 20 caracteres y se mostrará en la siguiente pantalla.', + 'setup.security.label.confirmPassword': 'Confirmar contraseña', + 'setup.security.label.password': 'Contraseña', + 'setup.security.label.username': 'Usuario', + 'setup.security.minimumPasswordHelp': 'Mínimo 12 caracteres.', + 'setup.security.nextScreen.itemApiToken': + 'Un token de API de administrador para automatización', + 'setup.security.nextScreen.itemCredentials': 'Tu usuario y contraseña', + 'setup.security.nextScreen.saveOnce': 'Guárdalos antes de continuar; se muestran una sola vez.', + 'setup.security.nextScreen.title': 'En la siguiente pantalla', + 'setup.security.passwordMode.autoGenerate': 'Generar automáticamente', + 'setup.security.placeholder.password': 'Contraseña (mínimo 12 caracteres)', + 'setup.security.placeholder.username': 'admin', + 'setup.security.showPassword.hide': 'Ocultar', + 'setup.security.showPassword.show': 'Mostrar', + 'setup.security.title': 'Crear tu cuenta de administrador', + 'setup.step.firstSource': 'Primera fuente', + 'setup.step.security': 'Seguridad', + 'setup.step.unlockServer': 'Desbloquear servidor', + 'setup.wizard.ariaLabel': 'Asistente de configuración de Pulse', + 'setup.welcome.action.continueSecurity': 'Continuar a Seguridad', + 'setup.welcome.action.verifyToken': 'Verificar token de bootstrap', + 'setup.welcome.action.verifyingToken': 'Verificando token de bootstrap...', + 'setup.welcome.copyCommandTitle': 'Copiar comando', + 'setup.welcome.deploymentHint.containerized': + 'Pulse parece ejecutarse en un entorno contenedorizado. Ejecuta el comando desde el host que administra el contenedor para imprimir el token de configuración de un solo uso.', + 'setup.welcome.deploymentHint.direct': + 'Ejecuta el comando directamente en una shell del servidor Pulse para imprimir el token de configuración de un solo uso.', + 'setup.welcome.deploymentHint.dockerNamed': + 'Pulse parece ejecutarse en Docker como contenedor "{containerName}". Ejecuta el comando en el host Docker para imprimir el token de configuración de un solo uso desde ese contenedor.', + 'setup.welcome.deploymentHint.dockerUnnamed': + 'Pulse parece ejecutarse en Docker. Ejecuta el comando en el host Docker y reemplaza por el nombre del contenedor Pulse en ejecución.', + 'setup.welcome.deploymentHint.lxc': + 'Pulse parece ejecutarse en el contenedor LXC {ctid}. Ejecuta el comando en el host Proxmox para entrar en ese contenedor e imprimir el token de configuración de un solo uso.', + 'setup.welcome.deploymentLabel.containerized': 'Despliegue contenedorizado', + 'setup.welcome.deploymentLabel.direct': 'Instalación directa en host', + 'setup.welcome.deploymentLabel.docker': 'Despliegue Docker', + 'setup.welcome.deploymentLabel.lxc': 'Contenedor LXC', + 'setup.welcome.error.copyCommandFailed': 'No se pudo copiar el comando', + 'setup.welcome.error.invalidBootstrapToken': + 'Token de bootstrap no válido. Revísalo e inténtalo de nuevo.', + 'setup.welcome.error.invalidBootstrapTokenResponse': 'Token de bootstrap no válido', + 'setup.welcome.error.missingBootstrapToken': 'Ingresa el token de bootstrap', + 'setup.welcome.error.snapshotPaste': + 'Eso parece el contenido cifrado del archivo .bootstrap_token, no el token de configuración sin procesar. Ejecuta el comando anterior y pega la cadena de token que imprime.', + 'setup.welcome.hero.coverage': + 'Conecta una API de plataforma, instala Pulse Agent o usa ambos para cobertura completa.', + 'setup.welcome.hero.step.admin': 'Crear la cuenta de administrador', + 'setup.welcome.hero.step.source': 'Elegir la primera fuente', + 'setup.welcome.hero.step.unlock': 'Desbloquear este servidor Pulse', + 'setup.welcome.hero.stepsIntro': 'Tres pasos:', + 'setup.welcome.hero.stepsThen': 'luego', + 'setup.welcome.hero.title': 'Bienvenido a Pulse', + 'setup.welcome.placeholder.bootstrapToken': 'Pega tu token de bootstrap', + 'setup.welcome.success.commandCopied': 'Comando copiado al portapapeles', + 'setup.welcome.tokenHelp.afterVerify': + 'Después de que Pulse verifique este token, el siguiente paso es crear la cuenta de administrador para este servidor.', + 'setup.welcome.tokenHelp.docker': + 'Este token de bootstrap de un solo uso solo desbloquea la configuración inicial. Ejecuta el comando anterior y pega la cadena de token que imprime. Después de la verificación, crearás la cuenta de administrador y Pulse generará el token de API de larga duración por separado.', + 'setup.welcome.tokenHelp.host': + 'Este token de bootstrap de un solo uso solo desbloquea la configuración inicial en este servidor Pulse. Ejecuta el comando anterior y pega la cadena de token que imprime. No es tu contraseña de administrador ni el token de API que usarás después de la configuración.', + 'setup.welcome.tokenHelp.title': 'Qué hace este token', + 'setup.welcome.unlockTitle': 'Desbloquear configuración', 'settings.general.appearance.title': 'Apariencia', 'settings.general.fullWidth.description': 'Expande el contenido para usar todo el ancho disponible en monitores grandes.', diff --git a/frontend-modern/src/i18n/policy.ts b/frontend-modern/src/i18n/policy.ts index 0f4f8f397..b938e3d60 100644 --- a/frontend-modern/src/i18n/policy.ts +++ b/frontend-modern/src/i18n/policy.ts @@ -4,7 +4,11 @@ import { SUPPORTED_LOCALES, type SupportedLocale, } from './locales'; -import { SETTINGS_GENERAL_MIGRATED_MESSAGE_KEYS, type I18nMessageKey } from './messages'; +import { + FIRST_SESSION_MONITORING_MIGRATED_MESSAGE_KEYS, + SETTINGS_GENERAL_MIGRATED_MESSAGE_KEYS, + type I18nMessageKey, +} from './messages'; export const LOCALIZATION_FOUNDATION = { ownerLayer: 'frontend-modern/src/i18n', @@ -38,10 +42,39 @@ export const SETTINGS_GENERAL_NON_TRANSLATABLE_TOKENS = [ '"Update"', ] as const; +export const FIRST_SESSION_MONITORING_NON_TRANSLATABLE_TOKENS = [ + 'Pulse', + 'Pulse Agent', + 'API', + 'URL', + 'Docker', + 'Kubernetes', + 'Proxmox', + 'TrueNAS', + 'VMware', + 'PBS', + 'PMG', + 'LXC', + '.bootstrap_token', + '', + '', + 'sudo pulse bootstrap-token', + 'docker exec', + 'pct exec', +] as const; + export const LOCALIZED_SETTINGS_GENERAL_JOURNEY_KEYS = SETTINGS_GENERAL_MIGRATED_MESSAGE_KEYS satisfies readonly I18nMessageKey[]; +export const LOCALIZED_FIRST_SESSION_MONITORING_JOURNEY_KEYS = + FIRST_SESSION_MONITORING_MIGRATED_MESSAGE_KEYS satisfies readonly I18nMessageKey[]; + export const SETTINGS_GENERAL_ALLOWED_IDENTICAL_TRANSLATIONS = { de: ['settings.nav.group.system', 'settings.general.theme.option.system'], es: ['settings.header.systemGeneral.title', 'settings.nav.item.general'], } as const satisfies Partial>; + +export const FIRST_SESSION_MONITORING_ALLOWED_IDENTICAL_TRANSLATIONS = { + de: ['setup.completion.sourceOptions.agent.title', 'setup.security.placeholder.username'], + es: ['setup.completion.sourceOptions.agent.title', 'setup.security.placeholder.username'], +} as const satisfies Partial>; diff --git a/frontend-modern/src/pages/RuntimeHome.tsx b/frontend-modern/src/pages/RuntimeHome.tsx index d4d643435..bc4893897 100644 --- a/frontend-modern/src/pages/RuntimeHome.tsx +++ b/frontend-modern/src/pages/RuntimeHome.tsx @@ -1,3 +1,5 @@ +import { t } from '@/i18n'; + export default function RuntimeHome() { - return
Opening workspace...
; + return
{t('runtimeHome.openingWorkspace')}
; } diff --git a/frontend-modern/src/pages/__tests__/RuntimeHome.test.tsx b/frontend-modern/src/pages/__tests__/RuntimeHome.test.tsx index 74a95a2c2..5e562a952 100644 --- a/frontend-modern/src/pages/__tests__/RuntimeHome.test.tsx +++ b/frontend-modern/src/pages/__tests__/RuntimeHome.test.tsx @@ -1,11 +1,24 @@ import { render, screen } from '@solidjs/testing-library'; -import { describe, expect, it } from 'vitest'; +import { afterEach, describe, expect, it } from 'vitest'; +import { DEFAULT_LOCALE, setActiveLocale } from '@/i18n'; import RuntimeHome from '@/pages/RuntimeHome'; describe('RuntimeHome', () => { + afterEach(() => { + setActiveLocale(DEFAULT_LOCALE); + }); + it('defers workspace routing to the authenticated app shell', () => { render(() => ); expect(screen.getByText('Opening workspace...')).toBeInTheDocument(); }); + + it('renders the authenticated workspace handoff through the active locale', () => { + setActiveLocale('es'); + + render(() => ); + + expect(screen.getByText('Abriendo el espacio de trabajo...')).toBeInTheDocument(); + }); });