Notation like Home Assistant, each screen in its own language, and the language in every build

From the end-to-end test on the bench boards and an independent review:
- Units spaced as Home Assistant does (54% / 54 % in German and French, 18°, 21.5 °C), in the firmware and the app.
- Grouping from five digits where the language does (1234 but 12.345 in Spanish, Polish, Portuguese): the layout
  message carries group_min and percent_space; an unknown number style no longer refuses a layout.
- Automatic follows Home Assistant's own language for the clock and numbers, also without a translation (Swedish:
  24 hours, 1 234,5; pt-BR: 1.234,5): regions.json from the CLDR for all 63 languages Home Assistant offers.
- 12-hour clock: the top bar says 3:48 PM, the clock card 3:48 (not 03:48), Last runs follow the clock.
- The climate setpoint font has the decimal comma; the value fonts have ², ³ and µ (m³ was a box).
- Weather days: at most three letters without the dot (mié, sáb, czw); the screen took three bytes.
- Texts fall back per key through the base language (pt-BR -> pt -> en) in the firmware and the app.
- Each screen gets the app's words in the language its firmware speaks; firmware from before the languages gets
  English and its own letters, not boxes.
- set_language re-reads what it writes (indentation, comments, quoted keys, CRLF, !include) and every build writes
  the language first; the 24-hour pin no longer hits a fresh install and waits for the old clock switches.
- Tests: region and notation, plural parity with the C++ rules, set_language edge cases, the pin, PUT /api/language.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Max Gramser
2026-09-19 16:52:34 +02:00
parent ad8a8ccf56
commit 125346d94e
37 changed files with 1419 additions and 448 deletions
+16 -6
View File
@@ -94,19 +94,29 @@ def cpp_string(text):
return '"' + ''.join(out) + '"'
def chain(language, folder=TRANSLATIONS):
"""The files a language's texts come from, most specific first: pt-BR, then pt, then English."""
found = [language]
base = language.split('-')[0]
if base != language and load(base, folder) is not None:
found.append(base)
return found + ([] if 'en' in found else ['en'])
def table(code, folder=TRANSLATIONS):
"""(language, keys, texts, plural rule) of a build: every English key, in its language where it has one."""
"""(language, keys, texts, plural rule) of a build: every English key, in its language where it has one, else in its
base language (a small pt-BR file takes the rest from pt), else in English."""
english = load('en', folder)
language = resolve(code, folder)
own = dict(screen_pairs(load(language, folder))) if language != 'en' else {}
files = [load(name, folder) or {} for name in chain(language, folder)]
texts_of = [dict(screen_pairs(data)) for data in files]
keys, texts = [], []
for key, text in screen_pairs(english):
keys.append(key)
translated = own.get(key)
# Only an empty text is missing: a separator of one space (French thousands) is a text.
texts.append(translated if isinstance(translated, str) and translated != '' else text)
meta = (load(language, folder) or {}).get('_meta', {})
rule = meta.get('plural') if meta.get('plural') in PLURAL_RULES else 'one_other'
texts.append(next((own[key] for own in texts_of if isinstance(own.get(key), str) and own[key] != ''), text))
plural = next((data.get('_meta', {}).get('plural') for data in files if data.get('_meta', {}).get('plural')), None)
rule = plural if plural in PLURAL_RULES else 'one_other'
return language, keys, texts, rule