The editor's mockup writes like the screens, and speaks its own language around them

- Numbers in the mockup follow what the app sends the screens (style, grouping from five digits, the space before
  %), a state without a unit stays as it is, and built-in cards carry the screens' names (Instellingen, not the
  editor's Einstellungen).
- Automatic in Language & region is labelled with what it means now (Home Assistant's language decides).
- A screen's status (Synced, Loading tiles, ...) shows in the editor's language; the screens keep reporting it in
  English, which the app reads.
- The doorbell example uses the alert examples of the editor's language; control labels keep their capitals.
- Editor bundle rebuilt with every language's texts.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Max Gramser
2026-09-19 16:57:35 +02:00
parent 125346d94e
commit 365ed158b9
37 changed files with 217 additions and 73 deletions
+4 -1
View File
@@ -344,7 +344,10 @@ class Region:
'ha': self.ha_language(), 'languages': self.tr.languages(),
'clock': self.clock, 'clock_effective': '24' if self.clock_24h() else '12',
'numbers': self.numbers, 'numbers_effective': self.number_style(),
'group_min': self.group_min(), 'percent_space': self.percent_space()}
'group_min': self.group_min(), 'percent_space': self.percent_space(),
# What Automatic means now, for its label: the clock and the numbers of the language.
'clock_auto': '12' if self.written().get('clock') == '12' else '24',
'numbers_auto': self.written().get('numbers', 'point'), 'group_min_auto': int(self.written().get('group_min', 1))}
+19 -4
View File
@@ -53,6 +53,20 @@ RESEND_STATES = frozenset({
'Klaar voor tegelconfiguratie', 'Indeling opnieuw nodig', 'Tegels laden',
})
RESEND_GUARD_SECONDS = 120
# What a screen reports stays English, the app reads it (protocol); the editor shows it in its own language (app 0.2.90).
SCREEN_STATUS_WORDS = {'Synced': 'synced', 'Loading tiles': 'loading_tiles', 'Layout received': 'layout_received',
'Resend needed': 'resend_needed', 'Ready for tile configuration': 'ready'}
def status_text(status):
"""A screen's status for the editor: its protocol words in the editor's language, ours as they were made."""
status = shown(status)
if isinstance(status, str) and not isinstance(status, i18n.Text):
if status in SCREEN_STATUS_WORDS:
return t(f'addon.status.screen.{SCREEN_STATUS_WORDS[status]}')
if status.startswith('Error: '):
return t('addon.status.screen.error', reason=status[len('Error: '):])
return status
FORECAST_SECONDS = 1800
# A screen that answers its ping (firmware 0.2.49+) is asked with this timeout, so a busy screen never holds
# up the others. An answer that the screen lacks something repeats everything, after this many seconds right
@@ -1794,7 +1808,7 @@ def create_app(manager, development=False):
# The delivery and our own word for a screen that reports nothing, in the editor's language (app 0.2.90).
status = manager.status.get(screen['id'])
screen['delivery'] = shown(status) if status else t('addon.status.first_tiles')
screen['status'] = shown(screen.get('status'))
screen['status'] = status_text(screen.get('status'))
screen['update'] = manager.updates.state_for(screen, profiles)
screen['alert_action'] = alert_service(screen.get('node'))
screen['dismiss_action'] = alert_service(screen.get('node'), 'dismiss_alert')
@@ -1822,8 +1836,9 @@ def create_app(manager, development=False):
# What's new for the Update badge: here only, not in every live update (app 0.2.78).
payload['changelog'] = manager.updates.changelog
payload['claude_skill'] = claude_skill.status(manager.skill_dir)
payload['builtin'] = [{'id': key, 'name': builtin_name(key, t), 'device': t('addon.labels.built_into_screen'), 'area': '', 'state': 'ok'}
for key in BUILTIN]
# The name in the editor's language for the library, and as the screens show it for the mockup (app 0.2.90).
payload['builtin'] = [{'id': key, 'name': builtin_name(key, t), 'screen_name': builtin_name(key),
'device': t('addon.labels.built_into_screen'), 'area': '', 'state': 'ok'} for key in BUILTIN]
payload['header'] = {**header_bar.catalogue(), 'suggestions': {
screen['id']: header_bar.suggestions(screen, entities, manager.ha.states, manager.registry_index()) for screen in payload['screens']}}
return web.json_response(payload)
@@ -1920,7 +1935,7 @@ def create_app(manager, development=False):
inbox=manager.aliases.get(request.match_info['inbox'], request.match_info['inbox'])
if screen is None: raise ValueError(t('addon.errors.unknown_screen'))
layout=manager.layouts.get(inbox,{'tiles':[]})
return web.json_response({'screen':{**screen, 'status':shown(screen.get('status'))},
return web.json_response({'screen':{**screen, 'status':status_text(screen.get('status'))},
'delivery':shown(manager.status.get(inbox)), 'layout':layout,
'tiles':[{'entity':t['entity'],'state':manager.ha.states.get(t['entity'],{}).get('state'),
# Home Assistant's word for the state, as its own pages show it ("Heat/Cool", app 0.2.67).
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -5,7 +5,7 @@
<meta name="viewport" content="width=device-width,initial-scale=1" />
<meta name="color-scheme" content="light dark" />
<title>ESP Screens</title>
<script type="module" crossorigin src="./assets/index-BtUedJUv.js"></script>
<script type="module" crossorigin src="./assets/index-CRR4xeFy.js"></script>
<link rel="stylesheet" crossorigin href="./assets/index-B7AxjXTR.css">
</head>
<body>
+13 -1
View File
@@ -560,7 +560,15 @@
"too_large": "Layout gespeichert; zu groß für eine Nachricht an den Bildschirm, entferne ein paar Kacheln",
"sent": "An Home Assistant gesendet",
"offline": "Bildschirm offline; Änderungen gespeichert",
"retrying": "Senden fehlgeschlagen; wird automatisch wiederholt"
"retrying": "Senden fehlgeschlagen; wird automatisch wiederholt",
"screen": {
"synced": "Synchronisiert",
"loading_tiles": "Kacheln werden geladen",
"layout_received": "Layout empfangen",
"resend_needed": "Erneutes Senden nötig",
"ready": "Bereit für die Kacheln",
"error": "Fehler: {reason}"
}
},
"updates": {
"offline": "Bildschirm war offline; neuer Versuch beim nächsten Mal.",
@@ -1755,6 +1763,10 @@
"every": "Alle Bildschirme? Löse {event} einmal aus (siehe „Alle Bildschirme auf einmal“). Die Bildschirme arbeiten trotzdem unabhängig, und jeder meldet sich mit seinem eigenen {screen}.",
"claude": "Für die Automation lieber {bold}? Installiere den ESP Screens-Skill unter Einstellungen → Claude.",
"claude_bold": "Claude fragen"
},
"wait_yaml": {
"comment": "Türklingel: die Meldung zeigen und warten, bis jemand die Taste drückt.",
"message": "Jemand kommt zur Tür."
}
},
"override": {
+13 -1
View File
@@ -560,7 +560,15 @@
"too_large": "Layout saved; too large for one message to the screen, remove a few tiles",
"sent": "Sent to Home Assistant",
"offline": "Screen offline; changes saved",
"retrying": "Sending failed; retrying automatically"
"retrying": "Sending failed; retrying automatically",
"screen": {
"synced": "Synced",
"loading_tiles": "Loading tiles",
"layout_received": "Layout received",
"resend_needed": "Resend needed",
"ready": "Ready for tile configuration",
"error": "Error: {reason}"
}
},
"updates": {
"offline": "Screen was offline; will retry next time.",
@@ -1755,6 +1763,10 @@
"every": "Every screen? Fire {event} once (see All screens at once). The screens still work independently and each reports with its own {screen}.",
"claude": "Rather {bold} for the automation? Install the ESP Screens skill under Settings → Claude.",
"claude_bold": "ask Claude"
},
"wait_yaml": {
"comment": "Doorbell: show the alert and wait until someone presses the button.",
"message": "Someone is coming to the door."
}
},
"override": {
+13 -1
View File
@@ -560,7 +560,15 @@
"too_large": "Diseño guardado; es demasiado grande para un mensaje a la pantalla, elimina algunos mosaicos",
"sent": "Enviado a Home Assistant",
"offline": "Pantalla sin conexión; cambios guardados",
"retrying": "Error al enviar; se reintenta automáticamente"
"retrying": "Error al enviar; se reintenta automáticamente",
"screen": {
"synced": "Sincronizado",
"loading_tiles": "Cargando mosaicos",
"layout_received": "Diseño recibido",
"resend_needed": "Hay que volver a enviar",
"ready": "Listo para los mosaicos",
"error": "Error: {reason}"
}
},
"updates": {
"offline": "La pantalla estaba sin conexión; se volverá a intentar la próxima vez.",
@@ -1755,6 +1763,10 @@
"every": "¿Todas las pantallas? Dispara {event} una vez (consulta «Todas las pantallas a la vez»). Las pantallas siguen funcionando por separado y cada una informa con su propio {screen}.",
"claude": "¿Prefieres {bold} que te haga la automatización? Instala la habilidad de ESP Screens en Configuración → Claude.",
"claude_bold": "pedirle a Claude"
},
"wait_yaml": {
"comment": "Timbre: muestra la alerta y espera a que alguien pulse el botón.",
"message": "Alguien va a abrir la puerta."
}
},
"override": {
+13 -1
View File
@@ -560,7 +560,15 @@
"too_large": "Mise en page enregistrée ; trop grande pour un seul message à l'écran, retirez quelques tuiles",
"sent": "Envoyé à Home Assistant",
"offline": "Écran hors ligne ; modifications enregistrées",
"retrying": "Échec de l'envoi ; nouvel essai automatique"
"retrying": "Échec de l'envoi ; nouvel essai automatique",
"screen": {
"synced": "Synchronisé",
"loading_tiles": "Chargement des tuiles",
"layout_received": "Disposition reçue",
"resend_needed": "Nouvel envoi nécessaire",
"ready": "Prêt pour les tuiles",
"error": "Erreur : {reason}"
}
},
"updates": {
"offline": "L'écran était hors ligne ; nouvel essai la prochaine fois.",
@@ -1755,6 +1763,10 @@
"every": "Tous les écrans ? Déclenchez {event} une seule fois (voir Tous les écrans à la fois). Les écrans restent indépendants et chacun répond avec son propre {screen}.",
"claude": "Vous préférez {bold} pour l'automatisation ? Installez la compétence ESP Screens dans Paramètres → Claude.",
"claude_bold": "demander à Claude"
},
"wait_yaml": {
"comment": "Sonnette : afficher l’alerte et attendre qu’on appuie sur le bouton.",
"message": "Quelqu’un arrive à la porte."
}
},
"override": {
+13 -1
View File
@@ -560,7 +560,15 @@
"too_large": "Disposizione salvata; troppo grande per un solo messaggio allo schermo, rimuovi qualche riquadro",
"sent": "Inviato a Home Assistant",
"offline": "Schermo offline; modifiche salvate",
"retrying": "Invio non riuscito; nuovo tentativo automatico"
"retrying": "Invio non riuscito; nuovo tentativo automatico",
"screen": {
"synced": "Sincronizzato",
"loading_tiles": "Caricamento dei riquadri",
"layout_received": "Layout ricevuto",
"resend_needed": "Serve un nuovo invio",
"ready": "Pronto per i riquadri",
"error": "Errore: {reason}"
}
},
"updates": {
"offline": "Lo schermo era offline; nuovo tentativo la prossima volta.",
@@ -1755,6 +1763,10 @@
"every": "Tutti gli schermi? Attiva {event} una sola volta (vedi Tutti gli schermi insieme). Gli schermi restano indipendenti e ognuno risponde con il proprio {screen}.",
"claude": "Preferisci {bold} l'automazione? Installa la skill di ESP Screens in Impostazioni → Claude.",
"claude_bold": "chiedere a Claude"
},
"wait_yaml": {
"comment": "Campanello: mostra l’avviso e aspetta che qualcuno prema il pulsante.",
"message": "Qualcuno sta arrivando alla porta."
}
},
"override": {
+13 -1
View File
@@ -560,7 +560,15 @@
"too_large": "Indeling bewaard; te groot voor één bericht aan het scherm, verwijder een paar tegels",
"sent": "Verstuurd naar Home Assistant",
"offline": "Scherm offline; wijzigingen bewaard",
"retrying": "Versturen mislukt; automatisch opnieuw proberen"
"retrying": "Versturen mislukt; automatisch opnieuw proberen",
"screen": {
"synced": "Gesynchroniseerd",
"loading_tiles": "Tegels laden",
"layout_received": "Indeling ontvangen",
"resend_needed": "Indeling opnieuw nodig",
"ready": "Klaar voor tegelconfiguratie",
"error": "Fout: {reason}"
}
},
"updates": {
"offline": "Scherm was offline; volgende keer opnieuw.",
@@ -1755,6 +1763,10 @@
"every": "Alle schermen? Vuur {event} één keer af (zie Alle schermen tegelijk). De schermen werken nog steeds los van elkaar en melden zich elk met hun eigen {screen}.",
"claude": "Liever {bold} om de automatisering te schrijven? Installeer de ESP Screens-skill onder Instellingen → Claude.",
"claude_bold": "Claude vragen"
},
"wait_yaml": {
"comment": "Deurbel: toon de alert en wacht tot iemand op de knop drukt.",
"message": "Er komt iemand naar de deur."
}
},
"override": {
+13 -1
View File
@@ -560,7 +560,15 @@
"too_large": "Układ zapisany; za duży na jedną wiadomość do ekranu, usuń kilka kafelków",
"sent": "Wysłano do Home Assistant",
"offline": "Ekran offline; zmiany zapisane",
"retrying": "Wysyłanie nieudane; automatyczne ponawianie"
"retrying": "Wysyłanie nieudane; automatyczne ponawianie",
"screen": {
"synced": "Zsynchronizowano",
"loading_tiles": "Ładowanie kafelków",
"layout_received": "Odebrano układ",
"resend_needed": "Wymagane ponowne wysłanie",
"ready": "Gotowy na kafelki",
"error": "Błąd: {reason}"
}
},
"updates": {
"offline": "Ekran był offline; kolejna próba następnym razem.",
@@ -1755,6 +1763,10 @@
"every": "Wszystkie ekrany? Wywołaj {event} raz (zobacz „Wszystkie ekrany naraz”). Ekrany nadal działają niezależnie i każdy zgłasza się z własnym polem {screen}.",
"claude": "Wolisz {bold} o automatyzację? Zainstaluj umiejętność ESP Screens w sekcji Ustawienia → Claude.",
"claude_bold": "poprosić Claude"
},
"wait_yaml": {
"comment": "Dzwonek: pokaż alert i poczekaj, aż ktoś naciśnie przycisk.",
"message": "Ktoś idzie do drzwi."
}
},
"override": {
+13 -1
View File
@@ -560,7 +560,15 @@
"too_large": "Layout guardado; demasiado grande para uma mensagem para o ecrã, remova alguns mosaicos",
"sent": "Enviado para o Home Assistant",
"offline": "Ecrã offline; alterações guardadas",
"retrying": "O envio falhou; a tentar novamente automaticamente"
"retrying": "O envio falhou; a tentar novamente automaticamente",
"screen": {
"synced": "Sincronizado",
"loading_tiles": "A carregar mosaicos",
"layout_received": "Layout recebido",
"resend_needed": "É preciso reenviar",
"ready": "Pronto para os mosaicos",
"error": "Erro: {reason}"
}
},
"updates": {
"offline": "O ecrã estava offline; vai tentar novamente da próxima vez.",
@@ -1755,6 +1763,10 @@
"every": "Todos os ecrãs? Dispare {event} uma vez (ver «Todos os ecrãs de uma vez»). Os ecrãs continuam a funcionar de forma independente e cada um reporta com o seu próprio {screen}.",
"claude": "Prefere {bold} que faça a automatização? Instale a habilidade do ESP Screens em Definições → Claude.",
"claude_bold": "pedir ao Claude"
},
"wait_yaml": {
"comment": "Campainha: mostra o alerta e espera que alguém prima o botão.",
"message": "Vem alguém à porta."
}
},
"override": {