Skip to content

feat: add AI-generated translations for 9 languages - #79

Merged
g4bri3lDev merged 3 commits into
feat/clean-portfrom
feat/ai-translations
Jul 27, 2026
Merged

feat: add AI-generated translations for 9 languages#79
g4bri3lDev merged 3 commits into
feat/clean-portfrom
feat/ai-translations

Conversation

@g4bri3lDev

Copy link
Copy Markdown
Member

Adds Czech, Dutch, French, German, Italian, Polish, Portuguese (European and Brazilian), and Spanish translations, plus the workflow that keeps them current.

Cost: nothing. Uses GitHub Models via the built-in GITHUB_TOKEN with models: read. No API key, no secret, no billing. Runs on the Python standard library, so requirements.txt is unchanged.

How it avoids waste

A key is sent to a model only when it is missing, or when its English source was reworded. Everything else is left alone. A full backfill of 9 languages is 27 requests against a 150/day budget; a run after adding three strings is 9 tiny ones. Verified: re-running immediately after a full run makes zero API calls.

Manual corrections are permanent

.github/translation-state.json fingerprints both the English source and the translation the workflow wrote, so it can tell its own output from a human edit.

Situation Behaviour
Key missing Translated
Bot-written, English reworded Re-translated
Hand-corrected Never overwritten
Hand-corrected and English reworded Flagged under "Needs review", your wording kept

Verified end to end on the real files, not just unit-tested.

Validation

scripts/verify_translations.py runs as a workflow step and fails the build on placeholder mismatches, empty values, or unknown keys. It runs inside the translate workflow because PRs opened by GITHUB_TOKEN do not trigger other workflows, so hassfest will not run on the automated PR.

Placeholders like {name} are enforced exactly: a translation that drops or renames one is rejected and retried rather than committed. Moving a placeholder is allowed, since German word order requires it (Taste {number}).

Register

Translations avoid the familiar/polite distinction (German du/Sie, French tu/vous) using impersonal phrasing: {name} einrichten?, not Möchten Sie {name} einrichten?. A heuristic warns on slips.

This surfaced one issue in the English source, which said "in your configuration.yaml" and propagated a second-person possessive into every language. Reworded here.

Notes for review

  • pt and pt-BR are both included: 73 of 181 strings differ (ligação/conectar, encriptação/criptografia).
  • The flattening path separator is NUL, not a dot. Home Assistant select entities use their option values as translation keys, and the existing de.json on main has "100 - 864.000 Mhz (Europe, etc)", which a dot separator mis-nests.
  • ⚠️ Merging feat/clean-port into main will need manual resolution. The merge base contains no translations/ directory, so en.json, de.json, and pl.json are add/add conflicts. The de/pl/pt files on main translate the superseded string set (177-226 keys that no longer exist) and should be deleted during that merge, or verify_translations.py will fail on unknown keys.

Adds German, French, Dutch, Spanish, Italian, Polish, Brazilian
Portuguese and Czech translations, generated from translations/en.json
using GitHub Models and validated automatically before being written.

scripts/translate.py only sends keys that are missing or whose English
source was reworded, so a run after adding a few strings costs a few
strings' worth of tokens rather than the whole file. It records a
fingerprint of both the English source and the translation it wrote in
.github/translation-state.json, which lets it distinguish its own output
from a manual correction: hand-edited translations are never
overwritten, and are reported for review instead when the English behind
them changes.

scripts/verify_translations.py re-checks the files on disk as a CI gate.
Placeholder mismatches, empty values and unknown keys fail the build.
Translations use impersonal phrasing to stay neutral about the
familiar/polite distinction (German du/Sie, French tu/vous), and a
heuristic warns when one slips through.

Also rewords the url_not_allowed message to drop "your", which was
propagating a second-person possessive into every translation.

Uses only the Python standard library, so requirements.txt is unchanged,
and the built-in GITHUB_TOKEN with models: read, so there is no API key
to manage and no billing.
Home Assistant select entities use their option values as translation
keys, and those legitimately contain dots. The existing de.json on main
has entity.select.subghzchannel.state with keys like
"100 - 864.000 Mhz (Europe, etc)", which a dot separator silently
mis-nests on the way back out.

The current en.json happens to contain no such entity, so this never
surfaced. Switch the separator to NUL, which cannot appear in a key that
came from a real translation file, and add show() to render paths with
dots for logs and PR summaries.

Keys sent to the model are now plain indices rather than full paths.
They no longer carry the separator, and dropping the path text from the
response saves output tokens on every request.
Home Assistant treats pt and pt-BR as separate locales, and they differ
in everyday interface vocabulary: 73 of the 181 strings came out
differently (ligacao/conectar, encriptacao/criptografia, em progresso/em
andamento). main already shipped a pt.json, so covering both avoids
regressing European Portuguese users when the rewrite lands.

Documents in the README that translations other than English are machine
generated and that corrections are welcome, including the guarantee that
a hand-corrected string is never overwritten. Contributors have little
reason to submit a fix if they expect a bot to revert it.

Also reports pending review items in the per-language status line, which
previously read "up to date" even when a string was flagged because its
English source changed under a manual correction.
@g4bri3lDev
g4bri3lDev merged commit 390997f into feat/clean-port Jul 27, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant