Decouple i18n from feature modules and fix auth/login UX

Replace hardcoded updateAllText() calls with languageChanged event
pattern so feature modules subscribe independently. Guard all API
calls behind apiKey checks to prevent unauthorized requests when not
logged in. Fix login modal localization, hide tabs when logged out,
clear all panels on logout, and treat profiles with no conditions as
always-true.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-19 12:32:14 +03:00
parent 747cdfabd6
commit 6388e0defa
13 changed files with 81 additions and 40 deletions

View File

@@ -593,6 +593,15 @@ export function stopUptimeTimer() {
_stopUptimeTimer();
}
// Re-render dashboard when language changes
document.addEventListener('languageChanged', () => {
if (!apiKey) return;
// Force perf section rebuild with new locale
const perfEl = document.querySelector('.dashboard-perf-persistent');
if (perfEl) perfEl.remove();
loadDashboard();
});
export function stopDashboardWS() {
if (_dashboardWS) {
_dashboardWS.close();

View File

@@ -2,14 +2,17 @@
* Profiles — profile cards, editor, condition builder, process picker.
*/
import { _profilesCache, set_profilesCache } from '../core/state.js';
import { apiKey, _profilesCache, set_profilesCache } from '../core/state.js';
import { API_BASE, getHeaders, escapeHtml, handle401Error } from '../core/api.js';
import { t, updateAllText } from '../core/i18n.js';
import { t } from '../core/i18n.js';
import { showToast, showConfirm } from '../core/ui.js';
import { Modal } from '../core/modal.js';
const profileModal = new Modal('profile-editor-modal');
// Re-render profiles when language changes
document.addEventListener('languageChanged', () => { if (apiKey) loadProfiles(); });
export async function loadProfiles() {
const container = document.getElementById('profiles-content');
if (!container) return;
@@ -39,7 +42,11 @@ function renderProfiles(profiles) {
html += '</div>';
container.innerHTML = html;
updateAllText();
// Localize data-i18n elements within the profiles container only
// (calling global updateAllText() would trigger loadProfiles() again → infinite loop)
container.querySelectorAll('[data-i18n]').forEach(el => {
el.textContent = t(el.getAttribute('data-i18n'));
});
}
function createProfileCard(profile) {
@@ -141,7 +148,9 @@ export async function openProfileEditor(profileId) {
}
profileModal.open();
updateAllText();
modal.querySelectorAll('[data-i18n]').forEach(el => {
el.textContent = t(el.getAttribute('data-i18n'));
});
}
export function closeProfileEditorModal() {

View File

@@ -18,6 +18,7 @@ import {
_currentTestStreamId, set_currentTestStreamId,
_currentTestPPTemplateId, set_currentTestPPTemplateId,
_lastValidatedImageSource, set_lastValidatedImageSource,
apiKey,
} from '../core/state.js';
import { API_BASE, getHeaders, fetchWithAuth, escapeHtml, handle401Error } from '../core/api.js';
import { t } from '../core/i18n.js';
@@ -25,6 +26,9 @@ import { Modal } from '../core/modal.js';
import { showToast, showConfirm, openLightbox, openFullImageLightbox, showOverlaySpinner, hideOverlaySpinner } from '../core/ui.js';
import { openDisplayPicker, formatDisplayLabel } from './displays.js';
// Re-render picture sources when language changes
document.addEventListener('languageChanged', () => { if (apiKey) loadPictureSources(); });
// ===== Modal instances =====
const templateModal = new Modal('template-modal');

View File

@@ -10,12 +10,13 @@ export function switchTab(name) {
localStorage.setItem('activeTab', name);
if (name === 'dashboard') {
// Use window.* to avoid circular imports with feature modules
if (typeof window.loadDashboard === 'function') window.loadDashboard();
if (typeof window.startDashboardWS === 'function') window.startDashboardWS();
if (apiKey && typeof window.loadDashboard === 'function') window.loadDashboard();
if (apiKey && typeof window.startDashboardWS === 'function') window.startDashboardWS();
} else {
if (typeof window.stopDashboardWS === 'function') window.stopDashboardWS();
if (typeof window.stopPerfPolling === 'function') window.stopPerfPolling();
if (typeof window.stopUptimeTimer === 'function') window.stopUptimeTimer();
if (!apiKey) return;
if (name === 'streams') {
if (typeof window.loadPictureSources === 'function') window.loadPictureSources();
} else if (name === 'targets') {

View File

@@ -3,6 +3,7 @@
*/
import {
apiKey,
_targetEditorDevices, set_targetEditorDevices,
_deviceBrightnessCache,
kcWebSockets,
@@ -17,6 +18,9 @@ import { createKCTargetCard, connectKCWebSocket, disconnectKCWebSocket } from '.
// createPatternTemplateCard is imported via window.* to avoid circular deps
// (pattern-templates.js calls window.loadTargetsTab)
// Re-render targets tab when language changes
document.addEventListener('languageChanged', () => { if (apiKey) loadTargetsTab(); });
class TargetEditorModal extends Modal {
constructor() {
super('target-editor-modal');