197 files

This commit is contained in:
Home Assistant Version Control
2026-08-21 18:53:37 +00:00
parent 89d9f5e49b
commit 383e9ed04e
197 changed files with 4386 additions and 1837 deletions
@@ -2,6 +2,7 @@
import { css } from "lit";
import EN from "./locales/en.json";
import { BUNDLE_VERSION } from "./helpers/bundle-version";
// Display fallback when the backend hasn't sent a currency symbol. The backend
// derives the real symbol from const.BUDGET_CURRENCIES[DEFAULT_BUDGET_CURRENCY].
@@ -47,8 +48,21 @@ const _localeGlobals: LocaleGlobals = (() => {
})();
const STORE = _localeGlobals.store;
// Each copy guarantees the bundled English fallback is present.
if (!STORE.en) STORE.en = EN as Translations;
/** Seed/extend the shared English table with this bundle's copy.
*
* MERGE, not first-wins (#135 regression): after an update, a cached app
* shell can still list an OLD sibling bundle (the card) next to the fresh
* panel. First-wins let the stale bundle's EN — missing every new key —
* claim the store, and the fresh panel rendered raw keys
* ("BATTERY_FLEET_ADD"). Existing entries win conflicts (steady state is
* unchanged); every bundle contributes the keys it knows, so a newer
* bundle's keys can never be shadowed by an older first-loader.
*/
export function seedEnglish(en: Record<string, string>): void {
STORE.en = Object.assign({}, en, STORE.en ?? {});
}
seedEnglish(EN as Translations);
/** Languages available as runtime-loaded JSON. Keep in sync with locales/. */
const SUPPORTED_LANGS = new Set<string>([
@@ -79,6 +93,22 @@ export function t(key: string, lang?: string): string {
return STORE[l]?.[key] ?? STORE.en[key] ?? key;
}
/** The per-`updated()` locale boot every top-level surface needs: date/time
* prefs follow the HA profile (#97), and the user's UI language is lazily
* fetched with a re-render once it arrives (EN is bundled, so strings read
* in English until then rather than as raw keys). Was copy-pasted across the
* panel and both cards (drift audit 2026-08). */
export function syncLocaleFromHass(
host: { hass?: { language?: string; locale?: unknown }; requestUpdate: () => void },
changedProps: Map<string, unknown>,
): void {
if (changedProps.has("hass")) setDateTimePrefs(host.hass?.locale as Parameters<typeof setDateTimePrefs>[0]);
const lang = host.hass?.language;
if (lang && !isLocaleLoaded(lang)) {
ensureLocale(lang).then(() => host.requestUpdate());
}
}
/**
* The UI language for a hass object — the ONE rule every component's `_lang`
* getter delegates to. Before this helper the 23 hand-copied getters had
@@ -108,7 +138,11 @@ export function ensureLocale(lang?: string): Promise<void> {
return Promise.resolve();
}
if (!(l in _localeInflight)) {
_localeInflight[l] = fetch(`${LOCALES_BASE}/${l}.json`)
// Version-busted (#135, same family as #124): the locale files are served
// without Cache-Control, so browsers cache them heuristically — after an
// update a stale table would miss every new key and silently fall back to
// English for them. "dev" builds keep a stable URL.
_localeInflight[l] = fetch(`${LOCALES_BASE}/${l}.json?v=${BUNDLE_VERSION}`)
.then((r) => (r.ok ? r.json() : null))
.then((data) => {
if (data) {
@@ -1067,48 +1101,9 @@ export const sharedStyles = css`
.confidence-dot.medium { background: var(--warning-color, #ff9800); }
.confidence-dot.high { background: var(--success-color, #4caf50); }
/* Feedback toggle buttons in complete dialog */
.feedback-section {
display: flex;
flex-direction: column;
gap: 8px;
padding: 8px 0;
border-top: 1px solid var(--divider-color);
}
.feedback-label {
font-weight: 500;
font-size: 13px;
color: var(--secondary-text-color);
}
.feedback-buttons {
display: flex;
gap: 8px;
}
.feedback-btn {
flex: 1;
padding: 8px 12px;
border: 1px solid var(--divider-color);
border-radius: 8px;
background: var(--card-background-color, #fff);
color: var(--primary-text-color);
font-size: 13px;
cursor: pointer;
text-align: center;
transition: all 0.2s;
}
.feedback-btn:hover {
background: var(--secondary-background-color, #f5f5f5);
}
.feedback-btn.selected {
background: var(--primary-color);
color: var(--text-primary-color, #fff);
border-color: var(--primary-color);
}
/* (The complete-dialog's .feedback-* rules live in complete-dialog.ts —
this sheet carried a dead byte-identical copy until the 2026-08 drift
audit; complete-dialog never imports sharedStyles.) */
/* Seasonal chart */
.seasonal-chart {