|
1 | 1 | /** |
2 | | - * SEM Localization — shared translation tables for all SEM dashboard cards |
| 2 | + * SEM Localization — loads dashboard translations from translations.json |
3 | 3 | * |
4 | | - * Usage in cards: |
5 | | - * const _t = (key) => semLocalize(key, this._hass?.language); |
6 | | - * this.shadowRoot.querySelector('.label').textContent = _t('charging'); |
| 4 | + * Single source of truth: /dashboard/translations.json |
| 5 | + * Used by all SEM cards via semLocalize(key, lang) |
| 6 | + * Also loaded by dashboard_generator.py for YAML template translation |
7 | 7 | */ |
8 | 8 |
|
9 | | -const SEM_TRANSLATIONS = { |
10 | | - en: { |
11 | | - // Status |
12 | | - charging: 'Charging', discharging: 'Discharging', idle: 'Idle', |
13 | | - connected: 'Connected', disconnected: 'Disconnected', |
14 | | - importing: 'Import', exporting: 'Export', grid: 'Grid', |
| 9 | +let SEM_TRANSLATIONS = null; |
| 10 | +let _loadPromise = null; |
15 | 11 |
|
16 | | - // Tab headers |
17 | | - home: 'Home', energy: 'Energy', battery: 'Battery', |
18 | | - ev_charging: 'EV Charging', control: 'Control', costs: 'Costs', system: 'System', |
19 | | - |
20 | | - // Tab subtitles |
21 | | - home_sub: 'Energy overview', energy_sub: 'Production & consumption', |
22 | | - battery_sub: 'Storage & health', ev_sub: 'Vehicle & sessions', |
23 | | - control_sub: 'Devices & scheduling', costs_sub: 'Savings & tariffs', |
24 | | - system_sub: 'Health & diagnostics', |
25 | | - |
26 | | - // Metric labels |
27 | | - solar: 'Solar', autarky: 'Autarky', today: 'Today', soc: 'SOC', |
28 | | - power: 'Power', health: 'Health', cycles: 'Cycles', temperature: 'Temperature', |
29 | | - status: 'Status', session: 'Session', current: 'Current', |
30 | | - solar_share: 'Solar share', strategy: 'Strategy', peak: 'Peak', |
31 | | - devices: 'Devices', active: 'Active', cost: 'Cost', saved: 'Saved', net: 'Net', |
32 | | - score: 'Score', co2: 'CO₂', self_use: 'Self-use', |
33 | | - |
34 | | - // Battery card |
35 | | - charge_today: 'Charge today', discharge_today: 'Discharge today', |
36 | | - savings_today: 'Savings today', |
37 | | - |
38 | | - // EV card |
39 | | - session_cost: 'Session cost', no_vehicle: 'No vehicle connected', |
40 | | - |
41 | | - // Schedule card |
42 | | - tariff: 'Tariff', night: 'Night', surplus: 'Surplus', ev: 'EV', |
43 | | - ht: 'HT', nt: 'NT', |
44 | | - |
45 | | - // Device control modes |
46 | | - mode: 'Mode', off: 'Off', peak_only: 'Peak Only', |
47 | | - surplus_mode: 'Surplus', critical: 'Critical', |
48 | | - |
49 | | - // Units |
50 | | - kwh: 'kWh', w: 'W', kw: 'kW', |
51 | | - }, |
52 | | - de: { |
53 | | - charging: 'Laden', discharging: 'Entladen', idle: 'Leerlauf', |
54 | | - connected: 'Verbunden', disconnected: 'Getrennt', |
55 | | - importing: 'Import', exporting: 'Export', grid: 'Netz', |
56 | | - |
57 | | - home: 'Übersicht', energy: 'Energie', battery: 'Batterie', |
58 | | - ev_charging: 'EV-Laden', control: 'Steuerung', costs: 'Kosten', system: 'System', |
59 | | - |
60 | | - home_sub: 'Energieübersicht', energy_sub: 'Erzeugung & Verbrauch', |
61 | | - battery_sub: 'Speicher & Gesundheit', ev_sub: 'Fahrzeug & Sitzungen', |
62 | | - control_sub: 'Geräte & Planung', costs_sub: 'Ersparnis & Tarife', |
63 | | - system_sub: 'Zustand & Diagnose', |
64 | | - |
65 | | - solar: 'Solar', autarky: 'Autarkie', today: 'Heute', soc: 'SOC', |
66 | | - power: 'Leistung', health: 'Zustand', cycles: 'Zyklen', temperature: 'Temperatur', |
67 | | - status: 'Status', session: 'Sitzung', current: 'Strom', |
68 | | - solar_share: 'Solaranteil', strategy: 'Strategie', peak: 'Spitze', |
69 | | - devices: 'Geräte', active: 'Aktiv', cost: 'Kosten', saved: 'Gespart', net: 'Netto', |
70 | | - score: 'Bewertung', co2: 'CO₂', self_use: 'Eigenverbrauch', |
71 | | - |
72 | | - charge_today: 'Ladung heute', discharge_today: 'Entladung heute', |
73 | | - savings_today: 'Ersparnis heute', |
74 | | - |
75 | | - session_cost: 'Sitzungskosten', no_vehicle: 'Kein Fahrzeug verbunden', |
76 | | - |
77 | | - tariff: 'Tarif', night: 'Nacht', surplus: 'Überschuss', ev: 'EV', |
78 | | - ht: 'HT', nt: 'NT', |
79 | | - |
80 | | - mode: 'Modus', off: 'Aus', peak_only: 'Nur Spitze', |
81 | | - surplus_mode: 'Überschuss', critical: 'Kritisch', |
82 | | - |
83 | | - kwh: 'kWh', w: 'W', kw: 'kW', |
84 | | - }, |
85 | | - fr: { |
86 | | - charging: 'En charge', discharging: 'Décharge', idle: 'Inactif', |
87 | | - connected: 'Connecté', disconnected: 'Déconnecté', |
88 | | - importing: 'Import', exporting: 'Export', grid: 'Réseau', |
89 | | - |
90 | | - home: 'Accueil', energy: 'Énergie', battery: 'Batterie', |
91 | | - ev_charging: 'Charge VE', control: 'Contrôle', costs: 'Coûts', system: 'Système', |
92 | | - |
93 | | - home_sub: "Vue d'ensemble", energy_sub: 'Production & consommation', |
94 | | - battery_sub: 'Stockage & santé', ev_sub: 'Véhicule & sessions', |
95 | | - control_sub: 'Appareils & planification', costs_sub: 'Économies & tarifs', |
96 | | - system_sub: 'Santé & diagnostic', |
97 | | - |
98 | | - solar: 'Solaire', autarky: 'Autarcie', today: "Aujourd'hui", soc: 'SOC', |
99 | | - power: 'Puissance', health: 'Santé', cycles: 'Cycles', temperature: 'Température', |
100 | | - status: 'État', session: 'Session', current: 'Courant', |
101 | | - solar_share: 'Part solaire', strategy: 'Stratégie', peak: 'Pointe', |
102 | | - devices: 'Appareils', active: 'Actifs', cost: 'Coût', saved: 'Économisé', net: 'Net', |
103 | | - score: 'Score', co2: 'CO₂', self_use: 'Autoconsommation', |
104 | | - |
105 | | - charge_today: "Charge aujourd'hui", discharge_today: "Décharge aujourd'hui", |
106 | | - savings_today: "Économies aujourd'hui", |
107 | | - |
108 | | - session_cost: 'Coût session', no_vehicle: 'Aucun véhicule connecté', |
109 | | - |
110 | | - tariff: 'Tarif', night: 'Nuit', surplus: 'Surplus', ev: 'VE', |
111 | | - ht: 'HP', nt: 'HC', |
112 | | - |
113 | | - mode: 'Mode', off: 'Arrêt', peak_only: 'Pointe seule', |
114 | | - surplus_mode: 'Surplus', critical: 'Critique', |
115 | | - |
116 | | - kwh: 'kWh', w: 'W', kw: 'kW', |
117 | | - }, |
118 | | - es: { |
119 | | - charging: 'Cargando', discharging: 'Descargando', idle: 'Inactivo', |
120 | | - connected: 'Conectado', disconnected: 'Desconectado', |
121 | | - importing: 'Importación', exporting: 'Exportación', grid: 'Red', |
122 | | - |
123 | | - home: 'Inicio', energy: 'Energía', battery: 'Batería', |
124 | | - ev_charging: 'Carga VE', control: 'Control', costs: 'Costes', system: 'Sistema', |
125 | | - |
126 | | - home_sub: 'Vista general', energy_sub: 'Producción y consumo', |
127 | | - battery_sub: 'Almacenamiento y salud', ev_sub: 'Vehículo y sesiones', |
128 | | - control_sub: 'Dispositivos y planificación', costs_sub: 'Ahorro y tarifas', |
129 | | - system_sub: 'Salud y diagnóstico', |
130 | | - |
131 | | - solar: 'Solar', autarky: 'Autarquía', today: 'Hoy', soc: 'SOC', |
132 | | - power: 'Potencia', health: 'Salud', cycles: 'Ciclos', temperature: 'Temperatura', |
133 | | - status: 'Estado', session: 'Sesión', current: 'Corriente', |
134 | | - solar_share: 'Cuota solar', strategy: 'Estrategia', peak: 'Pico', |
135 | | - devices: 'Dispositivos', active: 'Activos', cost: 'Coste', saved: 'Ahorrado', net: 'Neto', |
136 | | - score: 'Puntuación', co2: 'CO₂', self_use: 'Autoconsumo', |
137 | | - |
138 | | - charge_today: 'Carga hoy', discharge_today: 'Descarga hoy', |
139 | | - savings_today: 'Ahorro hoy', |
140 | | - |
141 | | - session_cost: 'Coste sesión', no_vehicle: 'Ningún vehículo conectado', |
142 | | - |
143 | | - tariff: 'Tarifa', night: 'Noche', surplus: 'Excedente', ev: 'VE', |
144 | | - ht: 'HP', nt: 'HV', |
145 | | - |
146 | | - mode: 'Modo', off: 'Apagado', peak_only: 'Solo pico', |
147 | | - surplus_mode: 'Excedente', critical: 'Crítico', |
148 | | - |
149 | | - kwh: 'kWh', w: 'W', kw: 'kW', |
150 | | - }, |
151 | | - it: { |
152 | | - charging: 'In carica', discharging: 'Scarica', idle: 'Inattivo', |
153 | | - connected: 'Connesso', disconnected: 'Disconnesso', |
154 | | - importing: 'Importazione', exporting: 'Esportazione', grid: 'Rete', |
155 | | - |
156 | | - home: 'Home', energy: 'Energia', battery: 'Batteria', |
157 | | - ev_charging: 'Carica VE', control: 'Controllo', costs: 'Costi', system: 'Sistema', |
158 | | - |
159 | | - home_sub: "Panoramica energia", energy_sub: 'Produzione e consumo', |
160 | | - battery_sub: 'Accumulo e salute', ev_sub: 'Veicolo e sessioni', |
161 | | - control_sub: 'Dispositivi e pianificazione', costs_sub: 'Risparmio e tariffe', |
162 | | - system_sub: 'Salute e diagnostica', |
163 | | - |
164 | | - solar: 'Solare', autarky: 'Autarchia', today: 'Oggi', soc: 'SOC', |
165 | | - power: 'Potenza', health: 'Salute', cycles: 'Cicli', temperature: 'Temperatura', |
166 | | - status: 'Stato', session: 'Sessione', current: 'Corrente', |
167 | | - solar_share: 'Quota solare', strategy: 'Strategia', peak: 'Picco', |
168 | | - devices: 'Dispositivi', active: 'Attivi', cost: 'Costo', saved: 'Risparmiato', net: 'Netto', |
169 | | - score: 'Punteggio', co2: 'CO₂', self_use: 'Autoconsumo', |
170 | | - |
171 | | - charge_today: 'Carica oggi', discharge_today: 'Scarica oggi', |
172 | | - savings_today: 'Risparmio oggi', |
173 | | - |
174 | | - session_cost: 'Costo sessione', no_vehicle: 'Nessun veicolo connesso', |
175 | | - |
176 | | - tariff: 'Tariffa', night: 'Notte', surplus: 'Eccedenza', ev: 'VE', |
177 | | - ht: 'FP', nt: 'FV', |
178 | | - |
179 | | - mode: 'Modalità', off: 'Spento', peak_only: 'Solo picco', |
180 | | - surplus_mode: 'Eccedenza', critical: 'Critico', |
181 | | - |
182 | | - kwh: 'kWh', w: 'W', kw: 'kW', |
183 | | - }, |
184 | | - nl: { |
185 | | - charging: 'Laden', discharging: 'Ontladen', idle: 'Inactief', |
186 | | - connected: 'Verbonden', disconnected: 'Ontkoppeld', |
187 | | - importing: 'Import', exporting: 'Export', grid: 'Net', |
188 | | - |
189 | | - home: 'Home', energy: 'Energie', battery: 'Batterij', |
190 | | - ev_charging: 'EV laden', control: 'Bediening', costs: 'Kosten', system: 'Systeem', |
191 | | - |
192 | | - home_sub: 'Energieoverzicht', energy_sub: 'Productie & verbruik', |
193 | | - battery_sub: 'Opslag & gezondheid', ev_sub: 'Voertuig & sessies', |
194 | | - control_sub: 'Apparaten & planning', costs_sub: 'Besparing & tarieven', |
195 | | - system_sub: 'Gezondheid & diagnose', |
196 | | - |
197 | | - solar: 'Zon', autarky: 'Autarkie', today: 'Vandaag', soc: 'SOC', |
198 | | - power: 'Vermogen', health: 'Gezondheid', cycles: 'Cycli', temperature: 'Temperatuur', |
199 | | - status: 'Status', session: 'Sessie', current: 'Stroom', |
200 | | - solar_share: 'Zonneaandeel', strategy: 'Strategie', peak: 'Piek', |
201 | | - devices: 'Apparaten', active: 'Actief', cost: 'Kosten', saved: 'Bespaard', net: 'Netto', |
202 | | - score: 'Score', co2: 'CO₂', self_use: 'Eigenverbruik', |
203 | | - |
204 | | - charge_today: 'Laden vandaag', discharge_today: 'Ontladen vandaag', |
205 | | - savings_today: 'Besparing vandaag', |
206 | | - |
207 | | - session_cost: 'Sessiekosten', no_vehicle: 'Geen voertuig verbonden', |
208 | | - |
209 | | - tariff: 'Tarief', night: 'Nacht', surplus: 'Overschot', ev: 'EV', |
210 | | - ht: 'HT', nt: 'LT', |
211 | | - |
212 | | - mode: 'Modus', off: 'Uit', peak_only: 'Alleen piek', |
213 | | - surplus_mode: 'Overschot', critical: 'Kritiek', |
| 12 | +/** |
| 13 | + * Load translations from the JSON file (async, cached). |
| 14 | + */ |
| 15 | +function _loadTranslations() { |
| 16 | + if (_loadPromise) return _loadPromise; |
| 17 | + _loadPromise = fetch('/local/custom_components/solar_energy_management/dashboard/translations.json') |
| 18 | + .then(r => r.json()) |
| 19 | + .then(data => { |
| 20 | + SEM_TRANSLATIONS = data; |
| 21 | + window.SEM_TRANSLATIONS = data; |
| 22 | + return data; |
| 23 | + }) |
| 24 | + .catch(() => { |
| 25 | + // Fallback: minimal English if fetch fails |
| 26 | + SEM_TRANSLATIONS = { en: {} }; |
| 27 | + window.SEM_TRANSLATIONS = SEM_TRANSLATIONS; |
| 28 | + return SEM_TRANSLATIONS; |
| 29 | + }); |
| 30 | + return _loadPromise; |
| 31 | +} |
214 | 32 |
|
215 | | - kwh: 'kWh', w: 'W', kw: 'kW', |
216 | | - }, |
217 | | -}; |
| 33 | +// Start loading immediately |
| 34 | +_loadTranslations(); |
218 | 35 |
|
219 | 36 | /** |
220 | 37 | * Get translated string for the given key and language. |
221 | 38 | * Falls back to English if key not found in target language. |
| 39 | + * Returns the key itself if no translation loaded yet. |
222 | 40 | */ |
223 | 41 | function semLocalize(key, lang) { |
224 | | - const t = SEM_TRANSLATIONS[lang] || SEM_TRANSLATIONS.en; |
225 | | - return t[key] || SEM_TRANSLATIONS.en[key] || key; |
| 42 | + if (!SEM_TRANSLATIONS) return key; |
| 43 | + const t = SEM_TRANSLATIONS[lang] || SEM_TRANSLATIONS.en || {}; |
| 44 | + return t[key] || (SEM_TRANSLATIONS.en || {})[key] || key; |
226 | 45 | } |
227 | 46 |
|
228 | | -// Export globally for all SEM cards |
| 47 | +// Export globally |
229 | 48 | if (typeof window !== 'undefined') { |
230 | | - window.SEM_TRANSLATIONS = SEM_TRANSLATIONS; |
231 | 49 | window.semLocalize = semLocalize; |
| 50 | + window._semLoadTranslations = _loadTranslations; |
232 | 51 | } |
0 commit comments