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:
+49
-3
@@ -7,7 +7,8 @@
|
||||
python3 tools/i18n.py header [--check] write (or compare) the key header and the tests' English table that the
|
||||
firmware's `screen` texts need, after changing the keys of en.json
|
||||
python3 tools/i18n.py cldr [--write] day and month names, date orders and the decimal mark from the Unicode CLDR
|
||||
(Node's Intl), into every language's screen.date and screen.number
|
||||
(Node's Intl), into every language's screen.date and screen.number, and the
|
||||
clock and numbers of every Home Assistant language (screen_manager/app/regions.json)
|
||||
python3 tools/i18n.py ha-words [--write] Home Assistant's own words for states (screen.ha) in every language, from a
|
||||
Home Assistant (HA_URL and HA_TOKEN); needs aiohttp
|
||||
python3 tools/i18n.py new CODE NAME ENGLISH PLURAL
|
||||
@@ -38,7 +39,8 @@ def generator():
|
||||
|
||||
def languages():
|
||||
"""{code: data} of every language file, English first."""
|
||||
found = {path.stem: json.loads(path.read_text(encoding='utf-8')) for path in sorted(FOLDER.glob('*.json'))}
|
||||
found = {path.stem: json.loads(path.read_text(encoding='utf-8')) for path in sorted(FOLDER.glob('*.json'))
|
||||
if re.fullmatch(r'[a-z]{2,3}(-[A-Za-z0-9]{2,8})?', path.stem)}
|
||||
return dict(sorted(found.items(), key=lambda item: (item[0] != 'en', item[0])))
|
||||
|
||||
|
||||
@@ -330,6 +332,40 @@ def main():
|
||||
|
||||
# The CLDR locale of a file where it differs: Portuguese is Home Assistant's pt, the one of Portugal.
|
||||
CLDR_LOCALE = {'pt': 'pt-PT'}
|
||||
# Every language Home Assistant's frontend offers (2026.9: the languages of its translationMetadata). A language ESP
|
||||
# Screens has no file for still writes the clock and numbers as Home Assistant does (regions.json); its texts fall back
|
||||
# to English.
|
||||
HA_LANGUAGES = ('af ar bg bn bs ca cs cy da de el en en-GB eo es et eu fa fi fy fr ga gl gsw he hi hr hu hy id it is ja '
|
||||
'ka ko lb lt lv mk ml nl nb nn pl pt pt-BR ro ru sk sl sr sr-Latn sv sq ta te th tr uk ur vi zh-Hans '
|
||||
'zh-Hant').split()
|
||||
# The languages that put a space between a number and "%" ("54 %"), as Home Assistant's frontend does
|
||||
# (src/common/translations/blank_before_unit.ts, blankBeforePercent); every other one writes "54%".
|
||||
HA_PERCENT_BLANK = ('cs', 'de', 'fi', 'fr', 'sk', 'sv')
|
||||
REGIONS = ROOT / 'screen_manager' / 'app' / 'regions.json'
|
||||
|
||||
|
||||
def percent_space(code):
|
||||
return code.split('-')[0] in HA_PERCENT_BLANK
|
||||
|
||||
|
||||
def number_style(number):
|
||||
"""point (1,234.5), comma (1.234,5) or space (1 234,5): the choices of Settings -> Language & region."""
|
||||
if number['decimal'] != ',':
|
||||
return 'point'
|
||||
return 'space' if number['group'].strip() == '' else 'comma'
|
||||
|
||||
|
||||
def regions():
|
||||
"""{code: {clock, numbers, group_min, percent_space}} for every Home Assistant language, from Node's Intl."""
|
||||
locales = [CLDR_LOCALE.get(code, code) for code in HA_LANGUAGES]
|
||||
found = json.loads(subprocess.run(['node', str(ROOT / 'tools' / 'i18n_cldr.mjs'), *locales], check=True,
|
||||
capture_output=True, text=True).stdout)
|
||||
table = {}
|
||||
for code, locale in zip(HA_LANGUAGES, locales):
|
||||
cldr = found[locale]
|
||||
table[code] = {'clock': cldr['clock'], 'numbers': number_style(cldr['number']),
|
||||
'group_min': int(cldr['number']['group_min']), 'percent_space': percent_space(code)}
|
||||
return table
|
||||
|
||||
|
||||
def write_language(code, data):
|
||||
@@ -360,11 +396,21 @@ def cldr_dates(write, codes=None):
|
||||
cldr = found[locale]
|
||||
before = json.dumps(data, ensure_ascii=False)
|
||||
data.setdefault('_meta', {})['clock'] = cldr['clock']
|
||||
merge(data.setdefault('screen', {}), {'number': cldr['number'], 'time': cldr['time'], 'date': cldr['date']})
|
||||
number = {**cldr['number'], 'percent': ' %' if percent_space(code) else '%'}
|
||||
merge(data.setdefault('screen', {}), {'number': number, 'time': cldr['time'], 'date': cldr['date']})
|
||||
if json.dumps(data, ensure_ascii=False) != before:
|
||||
print(f'{code}: {"wrote" if write else "would change"} the CLDR texts')
|
||||
if write:
|
||||
write_language(code, data)
|
||||
table = regions()
|
||||
text = json.dumps({'_about': 'GENERATED by tools/i18n.py cldr: how every Home Assistant language writes the clock and '
|
||||
'numbers (Unicode CLDR, Home Assistant\'s own rule for the space before %). The app takes '
|
||||
'it for Settings -> Language & region set to Automatic.', **table},
|
||||
ensure_ascii=False, indent=1) + '\n'
|
||||
if not REGIONS.exists() or REGIONS.read_text(encoding='utf-8') != text:
|
||||
print(f'regions.json: {"wrote" if write else "would change"} the clock and numbers of {len(table)} languages')
|
||||
if write:
|
||||
REGIONS.write_text(text, encoding='utf-8')
|
||||
return 0
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user