Commit 93ee0b3
feat: add the Esperanto (eo) locale across all four translated surfaces (#2179)
* feat: add the Esperanto (eo) locale across all four translated surfaces
The two authored catalogs plus their generated add-on projections. The
post-merge locale-sync run fills the remaining 734 strings; a dry run
plans them as one locale and touches nothing else.
Two things about the authored set are deliberate.
It carries `actions.approve` and `actions.deny` on top of the keys the
ungated checks demand. Every shipped catalog keeps the button verb and
the decision participle on one stem -- Genehmigen/genehmigt,
Approuver/approuvee, Godkann/godkant, 9 of 9, including the three the
machine filled. Esperanto can reach the "Deny" side from either `rifuzi`
or `malaprobi`, so pinning both halves here is what stops the sync from
picking the other stem for the verb it fills later.
Home Assistant's own Esperanto frontend catalog cannot serve as the
house-convention reference the other locales were measured against. Of
the 8472 keys it shares with English, 8336 (98.4%) are still English,
and 11 of the 136 translated ones are byte-identical to Home Assistant's
French catalog rather than Esperanto at all (none match the German one).
German, as a control, sits at 5.4% English -- 460 of the same 8472 keys.
These strings were therefore checked against the
nine shipped ha-mcp catalogs and against Esperanto grammar instead --
which is also why `exists_long` reads `iu ajn valoro` rather than the
`ajna` back-formation.
* fix: name Home Assistant as the asker in the Esperanto OAuth string
The English reads "restart Home Assistant when it asks you to". The first
version rendered that impersonally as `kiam oni petas vin`, on the grounds
that the agentless construction was what most shipped catalogs use. That
count was taken over seven catalogs when nine ship. Over all nine, five name
the asker and four do not, so the impersonal reading was the minority rather
than the majority.
`kiam ĝi petas vin pri tio` names Home Assistant as the English does and
keeps the second-person pronoun. The pronoun is not decoration here: this
key is the component surface's only register anchor, and the sync shows it
to the engine as the address sample for every component string it fills
afterwards.
No generated output changes -- component catalogs are not projected into the
add-on YAMLs.
* fix: correct two English sources and give eo a second style anchor
Review round two. Three corrections at the canonical English source, one in a
test that names the wrong catalog, and one gap the review exposed rather than
named.
`addon.enable_auto_backup.description` promised the opposite of what the code
does. Three tools decorate with `mandatory=True` -- `ha_write_file`
(`tools_filesystem.py:723`), `ha_delete_file` (`:866`) and `ha_config_set_yaml`
(`tools_yaml_config.py:308`) -- and that gate fails the call closed in two
cases: the master toggle is off, or the capture itself fails. It refuses a
write rather than a call: `ha_config_set_yaml` also passes
`skip_fn=_is_preview_only_call`, which the wrapper runs before the gate, so a
confirm-flow preview -- the default first call, since `enable_yaml_edit_confirm`
ships on -- writes nothing and is not refused. The string said "never block the underlying write" without
qualification. It now separates the best-effort domains from those two.
The enumerated domain list was short by four, not the two the review names.
Walking every `@with_auto_backup` site rather than the two cited ones gives 34
decorations over 18 domain families -- 17 spelled literally, plus the
one the two `domain_fn` sites compute; the sentence listed 14 of them. `device` and
`dashboard_resource` join the enumeration, and `file` and `yaml` -- which are
exactly the mandatory pair -- are covered by the new sentence instead.
`features.enable_beta_features.help` hard-coded "the 5 experimental sub-flags"
against a `config.BETA_FEATURE_FIELDS` that holds nine. The numeral is gone
rather than corrected, so the count cannot drift again.
Regeneration moves `en.yaml` and `eo.yaml` in both add-on flavors plus the
`FEATURE_META` block and nothing else, because the other nine catalogs carry
their own translations of both keys. No baseline repin: this branch changed
English without translating it, and repinning would tell the next sync those
nine translations already cover the new wording (AGENTS.md).
The style-sample gap is the part worth the most. `_prompt` hands `queued_keys`
to `_style_samples`, `_style_sample_keys` drops them, and both the tone block
and the register rule are guarded on `if samples` -- so a catalog whose only
reader-addressing key is itself queued sends a request with neither. The eo
settings catalog rested on exactly one such key, and the two tests that pin
this pass the default empty `exclude`, which is the one call production never
makes. Two more anchors go in, and a new test walks the real call shape:
queue any one sample key and samples must remain. Dropping both new anchors
turns it red for eo alone. Dropping only one does not, and should not: two
anchors still satisfy the property, and the test pins the property rather than
either particular key.
The test docstring claiming `nl` is the one-key component catalog was already
wrong: `nl.json` carries all 93 keys with 16 anchors. `eo` is that catalog.
Measured over the four files that read the completeness flag -- three carry
gated tests, the fourth asserts about the marker -- 167 pass ungated. Under the flag the
branch head fails 8 and this commit adds exactly one: the baseline-staleness
check, which is the queue signal the post-merge sync consumes. Both numbers
need the branch's own `src` on `PYTHONPATH`: the shared editable install
resolves `ha_mcp` to the main clone, which ships no `eo.json`, so the `[eo]`
parameters never exist rather than failing.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: let the literal-parity guards see a locale before its first sync run
The guards added in 103ad6c (#2180) ask every shipped locale for all four
guarded surfaces, and for at least ten pairs on each. A catalog ships its
authored anchors before the sync fills it, so a fresh one carries neither
`tools` nor `tool_groups` and holds a single component key; three of its four
surfaces therefore miss the bar for being new rather than for being broken.
`eo` is the first such catalog to meet these guards. It reddens ungated on the
surface-set assert, which raises before the floor is ever evaluated, and on
the floor as soon as that assert is relaxed.
So both are relaxed, and each carries its own weight: the surface set is now
derived per locale from what the catalogs hold, and the ten-pair floor applies
only where the catalog holds ten keys to begin with. Reverting either one
alone puts `[eo]` back in the red, and the new union test with it, since that
one walks every locale.
What the elasticity costs is measured rather than argued, by renaming the
`tools` section under both versions. Renamed at the pairs site alone, the new
code still reddens nine per-locale tests, exactly as the old assertion did --
that drift empties the filter, and the sizes are read before it. Renamed
everywhere, it reddens none of them, because the expectation reads the same
`settings_catalog.get("tools", ...)` as the pairs do and moves with it; the
added `test_every_guarded_surface_resolves_for_some_locale` catches that case,
along with two neighbouring tests. The code comment names this split rather
than claiming the expectation cannot move.
The `ru` literal-parity tolerance excused the loss of "5" from
`features.enable_beta_features.help`. The previous commit removed that numeral
from the English at a reviewer's request, so no translation can lose it again;
the entry is dead going forward rather than merely pending its rewrite, and it
is dropped. The window it died in -- English moved, sync not yet run, pair
excluded as pending -- is documented on the liveness guard instead, since that
is where the next reworded English will meet it.
`test_an_exception_that_over_records_is_refused` took its specimen from the
shipped table, so it reported the corpus twice over: at HEAD the entry's pair
had stopped resolving, and dropping the entry would have left it with no
loss-recording specimen at all. Both failures are loud and neither is about
containment, so it builds its pair synthetically now. The prose around the
table and in that docstring follows the entry that remains, which records an
addition and no loss.
`addon.enable_auto_backup.description` gains the throttle qualifier:
`maybe_snapshot` returns before the capture while the per-entity window is
open, and the skip is not conditioned on `mandatory`, so "before every wrapped
call" contradicted the adjacent throttle option. Derived catalogs regenerated;
that moves the `en` and `eo` projections only, since the other nine catalogs
carry their own translation of the key.
* chore: open component version 1.3.4
Master's manifest is level with the mirror's released stable -- v1.3.3, cut
2026-08-10 -- and this PR ships a component translation catalog, so the
Component Version Gate fails it on `equal`. Its own error states why that is
not cosmetic: merged on a released version the change never ships, because the
stable tag step skips an existing tag.
Bumped once, patch by default, per AGENTS.md "Version bumps ride the stable
release cycle". manifest.json, COMPONENT_VERSION and the parity-test literal
move together, which is what the parity test is there to force. No
MIN_COMPONENT_VERSION change: this PR adds no service or argument the server
depends on.
The window was open when the PR was raised and closed under it. The gate
belongs at PR-open and again before the merge, not once.
* fix: give the eo component catalog its own style anchors
The component surface rested on `common.oauth_not_serving` alone. A run that
rewords that key's English queues it, `_style_sample_keys` drops queued keys
from the candidates, and both the tone block and the register rule are guarded
on `if samples` -- so the run that fills the rest of the surface would go out
with neither, leaving one line on stderr inside an unattended workflow.
Authors the two shortest second-person component keys, `server_port` (167
characters of English) and `server_url` (222; the next candidate is 254). That
takes the surface from one candidate to three, `_STYLE_SAMPLE_COUNT` exactly,
so losing any single one still leaves two to imitate.
`test_component_samples_survive_their_own_anchor_being_queued` mirrors the
settings twin onto this surface. It passes a non-empty `exclude`, which the
shipped-catalog component check does not, so it sees the call production
actually makes. Dropping both new anchors reddens it for `eo` alone; dropping
one leaves it green, which is the property it pins rather than either key.
Terminology is measured against the nine other shipped component catalogs, as
the rest of this branch is: `vojo` for the slot all nine render as a route or a
path, with the numerals and product names carried through verbatim.
* docs: raise the component anchor floor to two keys
Both places that tell the next contributor what a fresh component catalog owes
said one reader-addressing key. The new survival check makes one insufficient:
a catalog with a single candidate loses it to the exclusion the moment that
key's English is reworded, which is the run that would have needed it.
States two, and names the check that pins it, in AGENTS.md § Translations and
in the locales README. Neither changes the empty-catalog case, which stays
allowed and is filtered out of both parametrisations.
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>1 parent 851c186 commit 93ee0b3
15 files changed
Lines changed: 704 additions & 79 deletions
File tree
- custom_components/ha_mcp_tools
- translations
- homeassistant-addon-dev/translations
- homeassistant-addon/translations
- src/ha_mcp/settings_ui
- locales
- tests/src/unit
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
746 | 746 | | |
747 | 747 | | |
748 | 748 | | |
749 | | - | |
| 749 | + | |
750 | 750 | | |
751 | 751 | | |
752 | 752 | | |
| |||
773 | 773 | | |
774 | 774 | | |
775 | 775 | | |
| 776 | + | |
| 777 | + | |
| 778 | + | |
| 779 | + | |
776 | 780 | | |
777 | 781 | | |
778 | 782 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
25 | 25 | | |
26 | 26 | | |
27 | 27 | | |
28 | | - | |
| 28 | + | |
29 | 29 | | |
30 | 30 | | |
31 | 31 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
22 | 22 | | |
23 | 23 | | |
24 | 24 | | |
25 | | - | |
| 25 | + | |
26 | 26 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
80 | 80 | | |
81 | 81 | | |
82 | 82 | | |
83 | | - | |
| 83 | + | |
84 | 84 | | |
85 | 85 | | |
86 | 86 | | |
| |||
199 | 199 | | |
200 | 200 | | |
201 | 201 | | |
202 | | - | |
203 | | - | |
204 | | - | |
205 | | - | |
206 | | - | |
207 | | - | |
208 | | - | |
209 | | - | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
210 | 217 | | |
211 | 218 | | |
212 | 219 | | |
| |||
0 commit comments