Skip to content

Commit 93ee0b3

Browse files
Patch76claude
andauthored
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

AGENTS.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -746,7 +746,7 @@ identity holds by construction.
746746

747747
A language ships on all four surfaces or not at all —
748748
`tests/src/unit/test_locale_parity.py` enforces it. The same Home Assistant
749-
language code (`de`, `es`, `fr`, `it`, `nl`, `pl`, `ru`, `sv`, `zh-Hans`) names every file:
749+
language code (`de`, `eo`, `es`, `fr`, `it`, `nl`, `pl`, `ru`, `sv`, `zh-Hans`) names every file:
750750
`src/ha_mcp/settings_ui/locales/<code>.json`,
751751
`custom_components/ha_mcp_tools/translations/<code>.json`, and
752752
`homeassistant-addon{,-dev}/translations/<code>.yaml`.
@@ -773,6 +773,10 @@ rests on whichever of them addresses the reader — losing it costs the engine
773773
the register for every later string of that language and says so only on
774774
stderr, which is why
775775
`test_every_shipped_component_catalog_gets_reader_addressing_samples` pins it.
776+
Author two such keys rather than one: a run that rewords one of them queues it,
777+
and queued keys are dropped from the sample candidates, so a surface resting on
778+
a single anchor is anchorless in precisely the run that rewrites it —
779+
`test_component_samples_survive_their_own_anchor_being_queued` pins that.
776780
`src/ha_mcp/settings_ui/locales/README.md` names the tests — including the one
777781
that skips locally until `tests/js/` has its npm dependencies.
778782

custom_components/ha_mcp_tools/const.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
# in CI. The
2626
# capability negotiation — not this version — gates each WS command (see
2727
# ``websocket_api.CAPABILITIES``).
28-
COMPONENT_VERSION = "1.3.3"
28+
COMPONENT_VERSION = "1.3.4"
2929

3030
# Config-entry discriminator (``entry.data[CONF_ENTRY_TYPE]``). A missing value
3131
# means "tools" so the pre-existing services entry keeps working across the

custom_components/ha_mcp_tools/manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,5 @@
2222
"requirements": [
2323
"ruamel.yaml>=0.18.0"
2424
],
25-
"version": "1.3.3"
25+
"version": "1.3.4"
2626
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"common": {
3+
"oauth_not_serving": "Legacy OAuth ankoraŭ ne liveras ilin — rekomencu Home Assistant, kiam ĝi petas vin pri tio, por aktivigi ilin."
4+
},
5+
"options": {
6+
"step": {
7+
"init": {
8+
"data_description": {
9+
"server_port": "La pordo, ĉe kiu ĉi tiu servilo aŭskultas. La kromprogramo ha-mcp uzas 9583, do ĉi tie la defaŭlta valoro estas 9584, por ke ambaŭ povu funkcii samtempe — se vi ne uzas la kromprogramon, iu ajn libera pordo taŭgas.",
10+
"server_url": "La URL, kiun la servilo funkcianta en la sama procezo uzas por atingi vian Home Assistant (kutime ĉi tiu instanco mem). Lasu ĝin malplena por derivi la valoron el la pordo kaj la SSL-agordoj de ĉi tiu instanco; agordu valoron nur kiam la servilo bezonas alian vojon."
11+
}
12+
}
13+
}
14+
}
15+
}

homeassistant-addon-dev/translations/en.yaml

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ configuration:
8080
components, run arbitrary sandboxed Python, and edit tool docstrings the AI
8181
sees. There is no warranty and no support guarantee — you enable them at your
8282
OWN RISK. Take a Home Assistant backup before turning this on, and never enable
83-
in production without one. Master gate for the 5 experimental sub-flags below;
83+
in production without one. Master gate for the experimental sub-flags below;
8484
sub-flags are ignored at runtime while this master is off, even when explicitly
8585
set to true. The same toggle is also surfaced in the web settings UI under "Beta
8686
features (dangerous)" — either surface reflects the other on restart.
@@ -199,14 +199,21 @@ configuration:
199199
here.
200200
enable_auto_backup:
201201
name: Enable auto-backup of edits
202-
description: Captures a per-entity snapshot before every wrapped write/destructive
203-
MCP tool call (automation, script, scene, helper, dashboard, label, category,
204-
group, zone, area, calendar, todo, entity, integration, and sibling remove/delete
205-
tools). Snapshots are saved as YAML files under /data/ha_mcp_backups/ (override
206-
via HAMCP_BACKUP_DIR) and listed, restored, or deleted via the Backups tab in
207-
the web settings UI or via ha_manage_backup(scope='edits', ...). Best-effort
208-
— failures log a WARNING but never block the underlying write. On by default;
209-
uncheck to opt out. Requires restart to take effect.
202+
description: 'Captures a per-entity snapshot before every wrapped write/destructive
203+
MCP tool call (automation, script, scene, helper, dashboard, dashboard resource,
204+
label, category, group, zone, area, calendar, todo, entity, device, integration,
205+
and sibling remove/delete tools). The auto-backup throttle bounds how often
206+
that happens: 0 (default) captures on every such call, N>0 at most one snapshot
207+
per N minutes per entity. Snapshots are saved as YAML files under /data/ha_mcp_backups/
208+
(override via HAMCP_BACKUP_DIR) and listed, restored, or deleted via the Backups
209+
tab in the web settings UI or via ha_manage_backup(scope=''edits'', ...). For
210+
those domains the capture is best-effort — a failure logs a WARNING and the
211+
write proceeds. The file and raw-YAML tools are the exception: ha_write_file,
212+
ha_delete_file and ha_config_set_yaml treat the snapshot as a precondition,
213+
so a write through them is refused while this is off, and a failed capture blocks
214+
it. On by default; unchecking opts out of the best-effort captures and takes
215+
the file and raw-YAML tools out of service with them. Requires restart to take
216+
effect.'
210217
auto_backup_throttle_minutes:
211218
name: Auto-backup throttle (minutes)
212219
description: Per-entity throttle window. 0 (default) captures a snapshot on every

0 commit comments

Comments
 (0)