v0.252.0 — the sentences the program builds follow the language (R-557 slice 2 release A)
gates / gates (push) Successful in 23s

Slice 1 translated the dashboard's markup. The sentences the program BUILDS were still
Hungarian literals in Go, so an English household clicked an English button and was
answered in Hungarian. 226 of them move into the bundle here.

A flash was the hard part: it travels inside the redirect URL and is rendered by a
DIFFERENT request, so it now carries a bundle key plus its parameters. A link minted by
an older controller carries prose and is shown verbatim — never a raw key, never dropped.

Also converted: page data and view-model text, the internal/api JSON answers, the alert
banners (Alert.MessageKey, rendered on the way out of GetAlerts), 237 country names at
display, and the four page titles built around an app name (R-566 closed).

Hungarian is byte-identical, and that is measured rather than read:
scripts/i18n_go_parity.py freezes every Go literal at the base commit (7 467) and refuses
a key whose Hungarian is not that text, byte for byte. Three decoys, each seen to convict.
Its own first version filtered the capture through an ASCII-Hungarian word list and missed
seven real literals — the R-565 class. The filter is gone.

Nothing on the wire moved, and wire goldens now hold it there: the report's health
warnings and every notify event message stay Hungarian, because the hub MAILS the
controller's sentence when it has no entry of its own. Slice 3 (R-558) owns those.

MinAgent: 0.131.0 (unchanged). No hub release needed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
2026-09-18 10:15:56 +02:00
parent 736f54b496
commit 5270bad76e
52 changed files with 10755 additions and 478 deletions
+4
View File
@@ -89,6 +89,10 @@ GATES = [
# v0.247.0 — the i18n bundles: keys exist, no orphans, the English gap and the formal-form debt
# only shrink (ratchets), no pleading English. Fast: stdlib file reads.
("i18n", os.path.join(SCRIPTS, "i18n_missing_gate.py"), [], True, True),
# R-557 slice 2 — a Go-side message key may only carry text that existed at the base commit,
# byte for byte. The template half of parity is proved by rendered fixtures; Go-side copy cannot
# be, so it is proved structurally here. Fast: stdlib file reads.
("go-parity", os.path.join(SCRIPTS, "i18n_go_parity.py"), [], True, True),
# R-404 — ADVISORY. Reports the golden debt where it is created; never refuses.
("golden-notice", GOLDEN_NOTICE, [REPO], True, False),
]
File diff suppressed because one or more lines are too long
+507
View File
@@ -0,0 +1,507 @@
{
"_comment": "Localisation slice 2 (R-557). key -> the base-commit Go literal it replaced, or the ORDERED list of literals a concatenation joined. Checked by scripts/i18n_go_parity.py against scripts/i18n_go_base.json, which is frozen at 736f54b49610 (the base commit of release v0.252.0). A key whose Hungarian text is not byte-identical to what the Go code said fails the gate.",
"_preexisting": {
"func.state.running": "slice 0 -- pinned by TestLocaleFuncsHungarianBundleMatchesFuncMap",
"func.state.starting": "slice 0 -- pinned by TestLocaleFuncsHungarianBundleMatchesFuncMap",
"func.state.deploying": "slice 0 -- pinned by TestLocaleFuncsHungarianBundleMatchesFuncMap",
"func.state.unhealthy": "slice 0 -- pinned by TestLocaleFuncsHungarianBundleMatchesFuncMap",
"func.state.degraded": "slice 0 -- pinned by TestLocaleFuncsHungarianBundleMatchesFuncMap",
"func.state.stopped": "slice 0 -- pinned by TestLocaleFuncsHungarianBundleMatchesFuncMap",
"func.state.restarting": "slice 0 -- pinned by TestLocaleFuncsHungarianBundleMatchesFuncMap",
"func.state.not_deployed": "slice 0 -- pinned by TestLocaleFuncsHungarianBundleMatchesFuncMap",
"func.state.paused": "slice 0 -- pinned by TestLocaleFuncsHungarianBundleMatchesFuncMap",
"func.state.unknown": "slice 0 -- pinned by TestLocaleFuncsHungarianBundleMatchesFuncMap",
"func.time.now": "slice 0 -- pinned by TestLocaleFuncsHungarianBundleMatchesFuncMap",
"func.time.minutes_ago": "slice 0 -- pinned by TestLocaleFuncsHungarianBundleMatchesFuncMap",
"func.time.hours_ago": "slice 0 -- pinned by TestLocaleFuncsHungarianBundleMatchesFuncMap",
"func.time.days_ago": "slice 0 -- pinned by TestLocaleFuncsHungarianBundleMatchesFuncMap",
"func.time.yesterday": "slice 0 -- pinned by TestLocaleFuncsHungarianBundleMatchesFuncMap",
"func.time.today_at": "slice 0 -- pinned by TestLocaleFuncsHungarianBundleMatchesFuncMap",
"func.time.tomorrow_at": "slice 0 -- pinned by TestLocaleFuncsHungarianBundleMatchesFuncMap",
"page.title.backups": "slice 1 -- pinned by TestHandlerTitleKeysMatchHungarianTitle",
"page.title.backups_apps": "slice 1 -- pinned by TestHandlerTitleKeysMatchHungarianTitle",
"page.title.backups_remote": "slice 1 -- pinned by TestHandlerTitleKeysMatchHungarianTitle",
"page.title.backups_restore": "slice 1 -- pinned by TestHandlerTitleKeysMatchHungarianTitle",
"page.title.dashboard": "slice 1 -- pinned by TestHandlerTitleKeysMatchHungarianTitle",
"page.title.escrow": "slice 1 -- pinned by TestHandlerTitleKeysMatchHungarianTitle",
"page.title.launcher": "slice 1 -- pinned by TestHandlerTitleKeysMatchHungarianTitle",
"page.title.monitoring": "slice 1 -- pinned by TestHandlerTitleKeysMatchHungarianTitle",
"page.title.settings": "slice 1 -- pinned by TestHandlerTitleKeysMatchHungarianTitle",
"page.title.settings_notifications": "slice 1 -- pinned by TestHandlerTitleKeysMatchHungarianTitle",
"page.title.settings_security": "slice 1 -- pinned by TestHandlerTitleKeysMatchHungarianTitle",
"page.title.sharing": "slice 1 -- pinned by TestHandlerTitleKeysMatchHungarianTitle",
"page.title.stacks": "slice 1 -- pinned by TestHandlerTitleKeysMatchHungarianTitle",
"page.title.storage": "slice 1 -- pinned by TestHandlerTitleKeysMatchHungarianTitle",
"page.title.storage_attach": "slice 1 -- pinned by TestHandlerTitleKeysMatchHungarianTitle",
"page.title.storage_init": "slice 1 -- pinned by TestHandlerTitleKeysMatchHungarianTitle",
"page.title.storage_network": "slice 1 -- pinned by TestHandlerTitleKeysMatchHungarianTitle"
},
"flash.share.already_on": "A megosztás már be van kapcsolva.",
"flash.share.enable_failed": "A megosztás bekapcsolása nem sikerült.",
"flash.share.enabled": "A megosztás bekapcsolva.",
"flash.share.not_on": "A megosztás nincs bekapcsolva.",
"flash.share.relink_failed": "Az új link készítése nem sikerült.",
"flash.share.relinked": "Új megosztási link készült. A korábbi link és minden korábbi belépés érvénytelen.",
"flash.share.disable_failed": "A megosztás kikapcsolása nem sikerült.",
"flash.share.disabled": "A megosztás kikapcsolva.",
"flash.share.pw_clear_failed": "A jelszó törlése nem sikerült.",
"flash.share.pw_cleared": "A megosztási jelszó törölve.",
"flash.share.pw_too_short": "A jelszónak legalább 8 karakter hosszúnak kell lennie.",
"flash.share.pw_set_failed": "A jelszó beállítása nem sikerült.",
"flash.share.pw_set": "A megosztási jelszó beállítva. A korábbi belépések érvénytelenek.",
"flash.restore.started": "Visszaállítás elindult — az állapot itt frissül.",
"flash.restore.file_started": "Fájl-visszaállítás elindult — az állapot itt frissül.",
"flash.restore.full_started": "Teljes visszaállítás elindult — az állapot itt frissül.",
"flash.login.password_changed": "Jelszó sikeresen módosítva. Kérjük, jelentkezzen be az új jelszóval.",
"flash.backup.window_invalid_time": "Érvénytelen időpont. Használja a ÓÓ:PP formátumot (például 02:30).",
"flash.backup.window_save_failed": "A mentési időablak mentése nem sikerült.",
"flash.backup.window_updated": "Mentési időablak frissítve.",
"flash.sharing.save_failed": "A mentés nem sikerült.",
"flash.sharing.disabled": "A hálózati megosztás kikapcsolva. A mappák és a fájlok megmaradtak.",
"flash.sharing.prepare_running": "A megosztási szolgáltatás előkészítése már folyamatban van.",
"flash.sharing.saved_preparing": "Beállítás mentve. A megosztási szolgáltatás előkészítése folyamatban…",
"flash.sharing.pw_too_short": "A jelszónak legalább 8 karakter hosszúnak kell lennie.",
"flash.sharing.pw_mismatch": "A két jelszó nem egyezik.",
"flash.sharing.pw_set_failed": "A jelszó beállítása nem sikerült.",
"flash.sharing.pw_set_preparing_already": "Megosztási jelszó beállítva. Az előkészítés már folyamatban van.",
"flash.sharing.pw_set_preparing": "Megosztási jelszó beállítva. A megosztási szolgáltatás előkészítése folyamatban…",
"flash.sharing.folder_failed": "A mappa létrehozása nem sikerült.",
"flash.sharing.created": "A megosztás létrehozva.",
"flash.sharing.protected": "Ez a megosztás a rendszer része, nem törölhető.",
"flash.sharing.deleted": "A megosztás törölve — a mappa és a fájlok megmaradtak.",
"flash.sharing.saved": "Beállítás mentve.",
"flash.tier2.target_invalid": "A választott cél meghajtó nem érvényes.",
"flash.tier2.save_failed": "A beállítás mentése nem sikerült.",
"flash.tier2.saved": "A 2. mentés beállítása elmentve.",
"flash.tier2.app_email_off": "Email-küldés kikapcsolva ennél az alkalmazásnál.",
"flash.tier2.app_email_on": "Email-küldés bekapcsolva ennél az alkalmazásnál.",
"flash.offbox.mgr_unavailable": "A mentéskezelő nem elérhető.",
"flash.offbox.fields_required": "A cél címe, a felhasználó és a tárhely útvonala kötelező.",
"flash.offbox.path_absolute": "A tárhely útvonalának abszolútnak kell lennie (/-rel kezdődjön).",
"flash.offbox.config_invalid": [
"Érvénytelen beállítás: "
],
"flash.offbox.first_setup_needs_key": "Az első beállításhoz az SSH privát kulcs és a célgép ismert-host sora is kötelező.",
"flash.offbox.creds_save_failed": "A hitelesítő adatok mentése sikertelen.",
"flash.offbox.save_failed": "A beállítás mentése sikertelen.",
"flash.offbox.target_saved": "A távoli mentési cél elmentve.",
"flash.offbox.target_saved_escrow_agent_down": [
"A távoli mentési cél elmentve.",
" — a kulcs letéti előkészítése nem sikerült (az ügynök nem elérhető); próbáld újra."
],
"flash.offbox.target_saved_escrow_failed": [
"A távoli mentési cél elmentve.",
" — a kulcs letéti előkészítése nem sikerült; próbáld újra."
],
"flash.offbox.target_not_set": "A távoli mentési cél nincs beállítva.",
"flash.offbox.escrow_confirmed": "A kulcs letétbe helyezése megerősítve — a távoli mentés mostantól futhat.",
"flash.offbox.repo_pw_required": "A repo jelszó kötelező.",
"flash.offbox.repo_pw_failed": [
"A jelszó beállítása sikertelen: "
],
"flash.offbox.repo_pw_set": "A helyreállított repo jelszó beállítva.",
"flash.offbox.app_missing": "Hiányzó alkalmazás.",
"flash.offbox.app_setting_updated": "A távoli mentés beállítása frissítve.",
"flash.offbox.waiting_for_escrow": "A távoli mentés a kulcs letétbe helyezésére vár.",
"flash.offbox.repo_orphaned": "A távoli tároló elárvult — előbb indíts új távoli mentést a kártyán látható módon.",
"flash.offbox.run_started": "A távoli mentés elindult — az állapot itt frissül.",
"flash.offbox.not_orphaned": "Az offsite tároló nincs elárvult állapotban.",
"flash.offbox.needs_confirmation": "A visszaállításhoz megerősítés szükséges.",
"flash.offbox.restart_started": "Új távoli mentés indítása folyamatban — a régi előzmény félretéve (nem törölve).",
"flash.offbox.restore_started": "A távoli visszaállítás elindult — az állapot itt frissül.",
"flash.offbox.full_needs_confirmation": "A teljes visszaállítás megerősítés nélkül nem hajtható végre.",
"flash.offbox.full_restore_started": "A teljes visszaállítás elindult — az állapot itt frissül.",
"flash.offbox.mgr_unreachable": "A mentéskezelő nem érhető el.",
"flash.offbox.scratch_missing": "Hiányzó ellenőrző másolat.",
"flash.offbox.delete_needs_confirmation": "A törlés megerősítés nélkül nem hajtható végre.",
"flash.offbox.delete_failed": [
"A másolat törlése nem sikerült: "
],
"flash.offbox.scratch_deleted": "Az ellenőrző másolat törölve. A tényleges adataid változatlanok.",
"flash.offbox.recover_started": "A helyreállítás elindult — az állapot itt frissül.",
"flash.offbox.shares_restore_started": "A megosztások visszaállítása elindult — az állapot itt frissül.",
"flash.offbox.shares_recover_started": "A megosztások helyreállítása elindult — az állapot itt frissül.",
"flash.offbox.run_already_going": "Már fut egy távoli mentés — ez a kérés nem indított újat. A most látható eredmény még a korábbi futásé; várd meg, míg ez befejeződik.",
"page.title.logs": [
" — Naplók"
],
"page.title.deploy": [
" — Telepítés"
],
"page.title.app_settings": [
" — Beállítások"
],
"page.title.tier2_config": [
"2. mentés beállítása — "
],
"logs.fetch_failed": "Hiba a naplók lekérésekor: %v",
"storage.bar_purpose": "Külső adattároló — a telepített alkalmazások nagy méretű fájljai (média, dokumentumok) ide kerülnek; az adatbázisok a belső SSD-n vannak.",
"deploy.path_not_allowed": "hálózati tárhely — alkalmazáshoz nem választható",
"creds.filebrowser_note": "A Fájlkezelő belépése. A jelszót a Felhom állította be ezen a gépen.",
"ping.label.heartbeat": "Eletjel (Heartbeat)",
"ping.label.system_health": "Rendszer allapot",
"ping.label.db_dump": "Adatbazis mentes",
"ping.label.backup": "Biztonsagi mentes",
"ping.label.integrity": "Mentes integritas",
"ping.schedule.5min": "5 percenkent",
"ping.schedule.daily_at": [
"Naponta "
],
"offbox.selection_changed": "A kijelölés módosult az utolsó futás óta — a következő távoli mentés már tartalmazza.",
"backup.contents.db": "Konfig",
"backup.contents.data": "Adatok",
"backup.status.ok": "Alkalmazás-adat mentés rendben",
"backup.status.db_failed": "Adatbázis mentés sikertelen",
"backup.tier2.no_drive": "Nincs 2. meghajtó",
"backup.tier2.badge_ok": "Sikeres",
"backup.tier2.badge_error": "Hiba",
"backup.tier2.badge_running": "Fut...",
"backup.tier2.schedule_daily": "Naponta",
"backup.removed_app": "Eltávolított alkalmazás — a mentése megvan, visszaállítható",
"backup.tier2.dest_ssd": "belső SSD (csak DB/konfiguráció)",
"flash.backup.not_configured": {
"inside": "/backups/restore?flash_error=Ment%C3%A9s+nincs+be%C3%A1ll%C3%ADtva"
},
"escrow.no_retrieval_password": "Ezen a gépen nincs tárolt visszaállítási jelszó.",
"escrow.stack_mgr_unavailable": "Az alkalmazáskezelő nem elérhető.",
"escrow.unknown_app": "Ismeretlen alkalmazás.",
"escrow.missing_field": "Hiányzó mező.",
"escrow.field_not_revealable": "Ez a mező nem kérhető le.",
"escrow.app_settings_unreadable": "Az alkalmazás beállításai nem olvashatók.",
"escrow.no_stored_value": "Ehhez a mezőhöz nincs mentett érték.",
"escrow.initial_pw_read_failed": "A kezdeti jelszó beolvasása nem sikerült.",
"escrow.initial_pw_unavailable": "A kezdeti jelszó most nem olvasható ki — az alkalmazásnak futnia kell hozzá, és lehet, hogy a fájlt az első bejelentkezés után már törölték.",
"escrow.settings_unavailable": "A beállítások nem elérhetők.",
"escrow.filebrowser_not_ours": "A Fájlkezelő jelszavát nem a Felhom állította be ezen a gépen, ezért nem tudjuk megmutatni.",
"escrow.password_unreadable": "A jelszó most nem olvasható ki.",
"settings.pw_wrong_current": "Hibás jelenlegi jelszó",
"settings.pw_too_short": "A jelszónak legalább 8 karakter hosszúnak kell lennie",
"settings.pw_mismatch": "A két jelszó nem egyezik",
"settings.pw_save_error": "Belső hiba a jelszó mentésekor",
"settings.notify_email_required": "Adj meg egy értesítési e-mail címet – bekapcsolt értesítésekhez szükséges egy cím, ahova küldhetjük őket.",
"settings.notify_save_error": "Hiba a beállítások mentésekor",
"settings.notify_saved_sync_failed": "Értesítési beállítások mentve (helyi). A központi szinkronizálás sikertelen: %v",
"settings.notify_saved": "Értesítési beállítások mentve.",
"settings.app_email_save_error": "Hiba az alkalmazás-email beállítás mentésekor",
"settings.app_email_shim_failed": "A beállítás elmentve, de az email-szolgáltatás indítása nem sikerült.",
"settings.app_email_on": "Alkalmazás-email bekapcsolva. Kapcsold be az egyes alkalmazásoknál is, ahol email-küldést szeretnél.",
"settings.app_email_off": "Alkalmazás-email kikapcsolva.",
"settings.notify_disabled": "Az értesítések nincsenek bekapcsolva",
"settings.test_email_failed": "Teszt email küldése sikertelen: %v",
"settings.test_email_sent": "Teszt email elküldve.",
"storage.err_path_missing": "Az útvonal nem létezik vagy nem mappa.",
"storage.err_not_separate": "Ez az útvonal nem külön csatlakoztatott meghajtó. Adatok az SSD-re kerülnének!",
"storage.err_not_writable": "Az útvonal nem írható.",
"storage.err_overlaps": "Az útvonal átfedi a már regisztrált %s útvonalat.",
"storage.err_save": "Hiba a mentés során.",
"storage.err_in_use": "Nem törölhető: az alábbi alkalmazások használják: %s",
"storage.err_default": "Az alapértelmezett adattároló nem törölhető.",
"storage.err_last": "Az utolsó adattároló nem törölhető.",
"storage.err_delete": "Hiba a törlés során.",
"storage.err_label": "A megnevezés nem lehet üres és legfeljebb 50 karakter.",
"storage.err_label_save": "Hiba a megnevezés mentésekor.",
"storage.msg_added": [
"Adattároló sikeresen hozzáadva: "
],
"storage.msg_removed": [
"Adattároló eltávolítva: "
],
"storage.msg_default_set": [
"Alapértelmezett adattároló beállítva: "
],
"storage.msg_state_changed": [
"Adattároló állapot módosítva: "
],
"storage.msg_label_changed": [
"Megnevezés módosítva: "
],
"country.AF": "Afganisztán",
"country.AL": "Albánia",
"country.DZ": "Algéria",
"country.AS": "Amerikai Szamoa",
"country.AD": "Andorra",
"country.AO": "Angola",
"country.AI": "Anguilla",
"country.AQ": "Antarktisz",
"country.AG": "Antigua és Barbuda",
"country.AR": "Argentína",
"country.AM": "Örményország",
"country.AW": "Aruba",
"country.AU": "Ausztrália",
"country.AT": "Ausztria",
"country.AZ": "Azerbajdzsán",
"country.BS": "Bahama-szigetek",
"country.BH": "Bahrein",
"country.BD": "Banglades",
"country.BB": "Barbados",
"country.BY": "Fehéroroszország",
"country.BE": "Belgium",
"country.BZ": "Belize",
"country.BJ": "Benin",
"country.BM": "Bermuda",
"country.BT": "Bhután",
"country.BO": "Bolívia",
"country.BA": "Bosznia-Hercegovina",
"country.BW": "Botswana",
"country.BR": "Brazília",
"country.BN": "Brunei",
"country.BG": "Bulgária",
"country.BF": "Burkina Faso",
"country.BI": "Burundi",
"country.CV": "Cabo Verde",
"country.KH": "Kambodzsa",
"country.CM": "Kamerun",
"country.CA": "Kanada",
"country.KY": "Kajmán-szigetek",
"country.CF": "Közép-afrikai Köztársaság",
"country.TD": "Csád",
"country.CL": "Chile",
"country.CN": "Kína",
"country.CO": "Kolumbia",
"country.KM": "Comore-szigetek",
"country.CG": "Kongó",
"country.CD": "Kongói Demokratikus Köztársaság",
"country.CK": "Cook-szigetek",
"country.CR": "Costa Rica",
"country.CI": "Elefántcsontpart",
"country.HR": "Horvátország",
"country.CU": "Kuba",
"country.CW": "Curaçao",
"country.CY": "Ciprus",
"country.CZ": "Csehország",
"country.DK": "Dánia",
"country.DJ": "Dzsibuti",
"country.DM": "Dominika",
"country.DO": "Dominikai Köztársaság",
"country.EC": "Ecuador",
"country.EG": "Egyiptom",
"country.SV": "Salvador",
"country.GQ": "Egyenlítői-Guinea",
"country.ER": "Eritrea",
"country.EE": "Észtország",
"country.SZ": "Eswatini",
"country.ET": "Etiópia",
"country.FK": "Falkland-szigetek",
"country.FO": "Feröer-szigetek",
"country.FJ": "Fidzsi-szigetek",
"country.FI": "Finnország",
"country.FR": "Franciaország",
"country.GF": "Francia Guyana",
"country.PF": "Francia Polinézia",
"country.GA": "Gabon",
"country.GM": "Gambia",
"country.GE": "Grúzia",
"country.DE": "Németország",
"country.GH": "Ghána",
"country.GI": "Gibraltár",
"country.GR": "Görögország",
"country.GL": "Grönland",
"country.GD": "Grenada",
"country.GP": "Guadeloupe",
"country.GU": "Guam",
"country.GT": "Guatemala",
"country.GG": "Guernsey",
"country.GN": "Guinea",
"country.GW": "Bissau-Guinea",
"country.GY": "Guyana",
"country.HT": "Haiti",
"country.HN": "Honduras",
"country.HK": "Hongkong",
"country.HU": "Magyarország",
"country.IS": "Izland",
"country.IN": "India",
"country.ID": "Indonézia",
"country.IR": "Irán",
"country.IQ": "Irak",
"country.IE": "Írország",
"country.IM": "Man-sziget",
"country.IL": "Izrael",
"country.IT": "Olaszország",
"country.JM": "Jamaica",
"country.JP": "Japán",
"country.JE": "Jersey",
"country.JO": "Jordánia",
"country.KZ": "Kazahsztán",
"country.KE": "Kenya",
"country.KI": "Kiribati",
"country.KP": "Észak-Korea",
"country.KR": "Dél-Korea",
"country.KW": "Kuvait",
"country.KG": "Kirgizisztán",
"country.LA": "Laosz",
"country.LV": "Lettország",
"country.LB": "Libanon",
"country.LS": "Lesotho",
"country.LR": "Libéria",
"country.LY": "Líbia",
"country.LI": "Liechtenstein",
"country.LT": "Litvánia",
"country.LU": "Luxemburg",
"country.MO": "Makaó",
"country.MG": "Madagaszkár",
"country.MW": "Malawi",
"country.MY": "Malajzia",
"country.MV": "Maldív-szigetek",
"country.ML": "Mali",
"country.MT": "Málta",
"country.MH": "Marshall-szigetek",
"country.MQ": "Martinique",
"country.MR": "Mauritánia",
"country.MU": "Mauritius",
"country.YT": "Mayotte",
"country.MX": "Mexikó",
"country.FM": "Mikronézia",
"country.MD": "Moldova",
"country.MC": "Monaco",
"country.MN": "Mongólia",
"country.ME": "Montenegró",
"country.MS": "Montserrat",
"country.MA": "Marokkó",
"country.MZ": "Mozambik",
"country.MM": "Mianmar",
"country.NA": "Namíbia",
"country.NR": "Nauru",
"country.NP": "Nepál",
"country.NL": "Hollandia",
"country.NC": "Új-Kaledónia",
"country.NZ": "Új-Zéland",
"country.NI": "Nicaragua",
"country.NE": "Niger",
"country.NG": "Nigéria",
"country.NU": "Niue",
"country.NF": "Norfolk-sziget",
"country.MK": "Észak-Macedónia",
"country.MP": "Északi-Mariana-szigetek",
"country.NO": "Norvégia",
"country.OM": "Omán",
"country.PK": "Pakisztán",
"country.PW": "Palau",
"country.PS": "Palesztina",
"country.PA": "Panama",
"country.PG": "Pápua Új-Guinea",
"country.PY": "Paraguay",
"country.PE": "Peru",
"country.PH": "Fülöp-szigetek",
"country.PL": "Lengyelország",
"country.PT": "Portugália",
"country.PR": "Puerto Rico",
"country.QA": "Katar",
"country.RE": "Réunion",
"country.RO": "Románia",
"country.RU": "Oroszország",
"country.RW": "Ruanda",
"country.BL": "Saint-Barthélemy",
"country.SH": "Szent Ilona",
"country.KN": "Saint Kitts és Nevis",
"country.LC": "Saint Lucia",
"country.MF": "Saint-Martin",
"country.PM": "Saint-Pierre és Miquelon",
"country.VC": "Saint Vincent és a Grenadine-szigetek",
"country.WS": "Szamoa",
"country.SM": "San Marino",
"country.ST": "São Tomé és Príncipe",
"country.SA": "Szaúd-Arábia",
"country.SN": "Szenegál",
"country.RS": "Szerbia",
"country.SC": "Seychelle-szigetek",
"country.SL": "Sierra Leone",
"country.SG": "Szingapúr",
"country.SX": "Sint Maarten",
"country.SK": "Szlovákia",
"country.SI": "Szlovénia",
"country.SB": "Salamon-szigetek",
"country.SO": "Szomália",
"country.ZA": "Dél-afrikai Köztársaság",
"country.SS": "Dél-Szudán",
"country.ES": "Spanyolország",
"country.LK": "Srí Lanka",
"country.SD": "Szudán",
"country.SR": "Suriname",
"country.SE": "Svédország",
"country.CH": "Svájc",
"country.SY": "Szíria",
"country.TW": "Tajvan",
"country.TJ": "Tádzsikisztán",
"country.TZ": "Tanzánia",
"country.TH": "Thaiföld",
"country.TL": "Kelet-Timor",
"country.TG": "Togo",
"country.TK": "Tokelau",
"country.TO": "Tonga",
"country.TT": "Trinidad és Tobago",
"country.TN": "Tunézia",
"country.TR": "Törökország",
"country.TM": "Türkmenisztán",
"country.TC": "Turks- és Caicos-szigetek",
"country.TV": "Tuvalu",
"country.UG": "Uganda",
"country.UA": "Ukrajna",
"country.AE": "Egyesült Arab Emírségek",
"country.GB": "Egyesült Királyság",
"country.US": "Egyesült Államok",
"country.UY": "Uruguay",
"country.UZ": "Üzbegisztán",
"country.VU": "Vanuatu",
"country.VA": "Vatikán",
"country.VE": "Venezuela",
"country.VN": "Vietnám",
"country.VG": "Brit Virgin-szigetek",
"country.VI": "Amerikai Virgin-szigetek",
"country.WF": "Wallis és Futuna",
"country.EH": "Nyugat-Szahara",
"country.YE": "Jemen",
"country.ZM": "Zambia",
"country.ZW": "Zimbabwe",
"api.deploy.netstorage_unreachable": "A kiválasztott hálózati tárhely jelenleg nem érhető el az alkalmazások környezetéből — a telepítés nem indítható. Próbálja újra pár perc múlva, vagy jelezze az üzemeltetőnek.",
"api.deploy.not_installable": "Ez az alkalmazás jelenleg nem telepíthető.",
"api.deploy.no_space": "Nincs elég szabad tárhely a telepítéshez: csak %.0f GB szabad, és a rendszer %.0f GB tartalékot tart fenn az alapszolgáltatások (vezérlő, proxy) védelmében. Szabadítson fel helyet, vagy bővítse a tárhelyet.",
"api.deploy.started": "Telepítés elindítva – az állapot a kártyán követhető",
"api.start.drive_missing": "A(z) %s tárhely jelenleg nem elérhető — az alkalmazás nem indítható, amíg a meghajtó vissza nem csatlakozik.",
"api.start.not_enough_memory": "Nincs elég memória az indításhoz. Szükséges: %d MB, elérhető: %d MB (használt: %d MB / használható: %d MB)",
"api.action.state_not_saved": "A művelet nem hajtható végre: az alkalmazás beállításai nem menthetők.",
"api.update.started": "Frissítés elindult – az állapot a kártyán követhető",
"api.settings.updated": "Beállítások frissítve",
"api.integration.enabled": "Integráció engedélyezve",
"api.integration.disabled": "Integráció kikapcsolva",
"api.backup.already_running": "Mentés már folyamatban",
"api.backup.started": "Mentés elindítva",
"api.backup.tier2_started": "2. mentés elindítva",
"api.update.kicked_off": "Frissítés elindítva",
"api.config.unchanged": "A konfiguráció változatlan — nincs szükség újraindításra.",
"api.config.applied": "Konfiguráció alkalmazva — a vezérlő újraindul.",
"api.geo.set": "Geo-korlátozás beállítva",
"api.geo.no_cloudflare": "Cloudflare API nincs konfigurálva",
"api.geo.sync_started": "Szinkronizálás elindítva",
"api.geo.app_set": "Alkalmazás geo-korlátozás beállítva",
"api.geo.app_removed": "Alkalmazás geo-korlátozás eltávolítva",
"alert.link.settings": "Beállítások",
"alert.link.monitoring": "Rendszermonitor",
"alert.link.update": "Frissítés",
"alert.deadapp.group": "%d telepített alkalmazás nem fut — nézze meg a rendszermonitort",
"alert.deadapp.one": [
"Telepített alkalmazás nem fut: "
],
"alert.deadapp.one_state": [
"Telepített alkalmazás nem fut: ",
" (",
")"
],
"alert.storage.disconnected": "Meghajtó leválasztva: %s (%s)",
"alert.hub.disabled": "Hub kapcsolat kikapcsolva — a központi monitoring nem aktív",
"alert.hub.unreachable": "Hub nem elérhető — utolsó hiba: %s",
"alert.backup.disabled": "A biztonsági mentés nincs bekapcsolva",
"alert.update.available": "Új controller verzió elérhető: %s",
"alert.overflow": "+ %d további figyelmeztetés",
"disk.err.bad_request": "érvénytelen kérés",
"disk.err.bad_mountpoint": "érvénytelen csatlakoztatási pont",
"disk.err.target_required": "céltároló kötelező az áthelyezéshez",
"disk.err.name_mismatch": "a beírt név nem egyezik a csatlakoztatási névvel",
"disk.err.unknown_mode": "ismeretlen mód (migrate vagy anyway)",
"disk.err.device_required": "eszköz kötelező",
"disk.err.init_in_progress": "már folyamatban van egy meghajtó-inicializálás",
"disk.err.protected": "a meghajtó védett (rendszer/biztonsági mentés) — törlés csak operátori aláírással",
"disk.err.wipe_failed": [
"törlés sikertelen: "
],
"disk.err.attach_unavailable": "Ez a meghajtó most nem csatolható — lehet, hogy már regisztrálva van, vagy időközben lecsatolódott. Frissítsd az oldalt, és nézd meg a Tárhely → Meghajtók listát."
}
+351
View File
@@ -0,0 +1,351 @@
# -*- coding: utf-8 -*-
"""i18n_go_parity.py -- a Go-side message key may only carry text that ALREADY existed.
Run from controller/: python3 scripts/i18n_go_parity.py
Exit 0 clean * 1 convicted * 2 inconclusive (base capture or bundles missing).
Design: felhom.eu/documentation/architecture/10-localisation.md s1 -- the Hungarian product must
render byte-for-byte the same before and after every slice. Slice 1 proved that for TEMPLATES by
rendering fixtures captured from unconverted templates. Slice 2 moves GO literals into the bundle,
and a rendered fixture cannot cover them all (a flash, an API error, a country name each need their
own request). This gate proves the same property structurally, at push time:
Every key slice 2 introduced maps to a Go string literal that existed at the BASE COMMIT, byte
for byte. A literal that was split, joined, reworded or re-punctuated on the way into hu.json
fails here and names both sides.
Two files carry it:
scripts/i18n_go_base.json -- EVERY Go string literal at the base commit, captured ONCE with
`--capture` from a clean worktree of that commit. FROZEN: it is the
measurement, and regenerating it to make a conversion pass is the
one thing that would make this gate a wish. Slice 2's three
releases all measure against the same capture.
scripts/i18n_go_keys.json -- `key -> from`, written by the conversion. `from` is the base
literal the key replaced, or the ORDERED list of literals a
concatenation joined.
Checks:
1. LISTED. Every hu.json key named by Go code (s.msg / msgN / Msgf / MsgErrorf / b.Msg / b.Plural
with a literal key) appears in i18n_go_keys.json. Forgetting to list a converted key is the
hole this closes.
2. REAL. Every `from` literal appears in the base capture. A key cannot cite text nobody wrote.
3. BYTE-EQUAL. A single-literal key: hu.json[key] == from, exactly. A joined key: hu.json[key]
with its printf verbs removed == the `from` fragments concatenated in order, with their verbs
removed too. Plural keys compare their `.one`/`.other`-less base form.
The Go literal walker is the inventory's (felhom.eu/scripts/i18n_inventory.py `go_literals`),
copied rather than imported: this gate runs from a controller clone that may not sit beside
felhom.eu, and a gate that can fail to import its own scanner is a gate that can silently skip.
This source is ASCII-only: it holds no Hungarian, so there is nothing for a locale to mangle.
"""
from __future__ import annotations
import argparse
import io
import json
import os
import re
import sys
import urllib.parse
HERE = os.path.dirname(os.path.abspath(__file__))
CTRL = os.path.dirname(HERE)
LOCALES = os.path.join(CTRL, "internal", "i18n", "locales")
BASE_FILE = os.path.join(HERE, "i18n_go_base.json")
KEYS_FILE = os.path.join(HERE, "i18n_go_keys.json")
# Directories whose Go literals are IN SCOPE for the capture. cmd/ and internal/ minus the
# first-boot wizard, which is out of scope and slated for deletion (R-554, 10-localisation.md s6).
SCOPE_ROOTS = ["internal", "cmd"]
SCOPE_SKIP = [os.path.join("internal", "setup")]
# A string literal SHAPED like a bundle key: lower-case dotted segments, no spaces. Every such
# literal that hu.json actually knows is treated as "this key is named in Go".
#
# It deliberately does NOT look for `s.msg(` and its siblings. The first version did, and a decoy
# caught it: a key reaches the bundle through many shapes that are not a call to a message helper --
# `offboxRedirect(w, r, "flash.offbox.app_missing", true)` puts one in a redirect URL, an Alert
# carries one in a struct field, a handler stores one in `data["TitleKey"]`. Every one of those was
# invisible, so a converted key could be dropped from the map and nothing would say so. Matching the
# SHAPE and then requiring the bundle to know it cannot miss a delivery mechanism, because it does
# not model one.
KEY_SHAPE_RE = re.compile(r"^[a-z][a-z0-9_]*(?:\.[a-z0-9_\-]+)+$")
# printf verbs, including explicit argument indexes (%[2]s -- how English reorders while the
# Hungarian format string keeps the plain verbs it always had).
VERB_RE = re.compile(r"%(?:\[\d+\])?[-+# 0-9.*]*[a-zA-Z]")
def go_literals(src: str):
"""Yield (line, literal_body, raw) for every string literal outside comments.
Copied from felhom.eu/scripts/i18n_inventory.py `go_literals` (2026-09-17).
"""
i, n, line = 0, len(src), 1
while i < n:
c = src[i]
if c == "\n":
line += 1
i += 1
elif src.startswith("//", i):
j = src.find("\n", i)
i = n if j < 0 else j
elif src.startswith("/*", i):
j = src.find("*/", i + 2)
j = n if j < 0 else j + 2
line += src.count("\n", i, j)
i = j
elif c == "'":
j = i + 1
while j < n and src[j] != "'":
j += 2 if src[j] == "\\" else 1
i = j + 1
elif c == '"':
j = i + 1
while j < n and src[j] != '"' and src[j] != "\n":
j += 2 if src[j] == "\\" else 1
yield line, src[i + 1:j], False
i = j + 1
elif c == "`":
j = src.find("`", i + 1)
j = n if j < 0 else j
yield line, src[i + 1:j], True
line += src.count("\n", i, j)
i = j + 1
else:
i += 1
def go_files(root: str):
out = []
for d in SCOPE_ROOTS:
base = os.path.join(root, d)
if not os.path.isdir(base):
continue
for dirpath, _dirs, names in os.walk(base):
rel = os.path.relpath(dirpath, root)
if any(rel == s or rel.startswith(s + os.sep) for s in SCOPE_SKIP):
continue
for n in sorted(names):
if n.endswith(".go") and not n.endswith("_test.go"):
out.append(os.path.join(dirpath, n))
return sorted(out)
def read(p: str) -> str:
with io.open(p, encoding="utf-8", errors="replace") as fh:
return fh.read()
def unescape(body: str) -> str:
"""Turn a Go interpreted-literal BODY into the runtime string.
Only the escapes this codebase uses appear here; anything unknown is left alone rather than
guessed, which keeps the comparison honest (an unrecognised escape can only make a key FAIL
to match, never falsely match).
"""
out, i, n = [], 0, len(body)
simple = {"n": "\n", "t": "\t", "r": "\r", "\\": "\\", '"': '"', "'": "'"}
while i < n:
if body[i] == "\\" and i + 1 < n:
c = body[i + 1]
if c in simple:
out.append(simple[c])
i += 2
continue
if c == "u" and i + 6 <= n:
try:
out.append(chr(int(body[i + 2:i + 6], 16)))
i += 6
continue
except ValueError:
pass
out.append(body[i])
i += 1
return "".join(out)
def capture(root: str) -> dict:
"""Index EVERY Go string literal at this commit, not only the ones a word list calls Hungarian.
The first version filtered by `has_hu`, and the filter was wrong in exactly the way R-565 named:
a Hungarian string spelled without accents ("Naponta", "5 percenkent", "Eletjel (Heartbeat)",
"Adatbazis mentes") is invisible to a letter search, and a word list is a list -- it is short by
construction and nobody knows which words are missing. Seven such literals were silently absent
from the first capture and the gate correctly refused the keys that cited them.
Filtering is not needed here at all. This index answers ONE question -- "did the base commit
contain this exact text?" -- and an unfiltered index answers it for every string. It cannot be
fooled by an English literal that happens to equal a Hungarian sentence, because no such literal
exists. So the word list is gone and the blind spot with it.
"""
lits = {}
for p in go_files(root):
rel = os.path.relpath(p, root)
src = read(p)
for line, body, raw in go_literals(src):
text = body if raw else unescape(body)
if text == "":
continue
lits.setdefault(text, []).append("%s:%d" % (rel, line))
return lits
def strip_verbs(s: str) -> str:
"""Remove printf verbs, keeping a literal %% as a single percent sign."""
s = s.replace("%%", "\x00")
s = VERB_RE.sub("", s)
return s.replace("\x00", "%")
def load_json(p: str):
if not os.path.exists(p):
return None
with io.open(p, encoding="utf-8") as fh:
return json.load(fh)
def keys_named_in_go(root: str, hu):
"""Every bundle key a Go literal names -> the sites that name it."""
out = {}
for p in go_files(root):
rel = os.path.relpath(p, root)
src = read(p)
for i, line in enumerate(src.splitlines(), 1):
if line.lstrip().startswith("//"):
continue
for _line, body, raw in go_literals(line):
text = body if raw else unescape(body)
if hu is not None and text in hu and KEY_SHAPE_RE.match(text):
out.setdefault(text, []).append("%s:%d" % (rel, i))
return out
def base_form(hu: dict, key: str):
"""The Hungarian text for key, accepting the plural split (key, key.one, key.other)."""
if key in hu:
return hu[key]
for suffix in (".other", ".one"):
if key + suffix in hu:
return hu[key + suffix]
return None
def main(argv) -> int:
ap = argparse.ArgumentParser()
ap.add_argument("--capture", action="store_true",
help="rewrite i18n_go_base.json (run once, from a CLEAN worktree of the base commit)")
ap.add_argument("--root", default=CTRL,
help="controller root to capture FROM -- point it at a clean worktree of the base "
"commit, never at a tree that has already been converted")
ap.add_argument("--commit", default="", help="the commit being captured, recorded in the file")
args = ap.parse_args(argv[1:])
if args.capture:
lits = capture(args.root)
payload = {
"_comment": (
"FROZEN measurement -- every Hungarian Go string literal at the base commit of "
"localisation slice 2. Regenerating this to make a conversion pass is the one thing "
"that would turn i18n_go_parity.py into a wish. See its docstring."
),
"commit": args.commit,
"count": len(lits),
# One site per literal: enough to name where the text came from in a failure, and it
# keeps a frozen measurement small enough to read in a review.
"literals": {k: sorted(set(v))[0] for k, v in sorted(lits.items())},
}
with io.open(BASE_FILE, "w", encoding="utf-8") as fh:
json.dump(payload, fh, ensure_ascii=False, indent=1, sort_keys=False)
fh.write("\n")
print("captured %d Go string literals from %s -> %s"
% (len(lits), args.root, os.path.relpath(BASE_FILE, CTRL)))
return 0
base = load_json(BASE_FILE)
keys = load_json(KEYS_FILE)
hu = load_json(os.path.join(LOCALES, "hu.json"))
if base is None or keys is None or hu is None:
print("go-parity gate INCONCLUSIVE: base capture, key map or hu.json not found under %s" % CTRL)
return 2
base_lits = base.get("literals", {})
named = keys_named_in_go(CTRL, hu)
# Keys that predate slice 2 (the spike's and slice 1's copy-producing funcs). Each is pinned by
# its own Go test, named in the map; this gate accounts for them so a NEW Go key cannot hide
# among them. They are not measured against the base capture: their Hungarian was moved by
# slice 0/1 and proved by rendered fixtures, which is the stronger measurement.
preexisting = keys.pop("_preexisting", {}) or {}
keys.pop("_comment", None)
bad = []
# 1. LISTED -- a key a Go call site names must be accounted for.
for key in sorted(named):
if key not in keys and key not in preexisting:
bad.append("UNLISTED %s named at %s but absent from %s"
% (key, named[key][0], os.path.basename(KEYS_FILE)))
# 2 + 3. REAL and BYTE-EQUAL.
for key in sorted(keys):
spec = keys[key]
value = base_form(hu, key)
if value is None:
bad.append("NO-KEY %s listed but absent from hu.json" % key)
continue
# A third spec shape, for the handful of sites where the Hungarian was written ALREADY
# URL-ESCAPED inside a redirect target: {"inside": "<the base literal>"}. The literal is
# unescaped and the key's text must occur in it exactly once. Explicit and rare on purpose --
# a substring rule applied everywhere would let a reworded sentence pass.
if isinstance(spec, dict):
lit = spec.get("inside", "")
if lit not in base_lits:
bad.append("INVENTED %s cites a base literal that does not exist: %r" % (key, lit))
continue
decoded = urllib.parse.unquote_plus(lit)
n = decoded.count(value)
if n != 1:
bad.append("CHANGED %s occurs %d times (want 1) in the unescaped literal\n"
" hu.json : %r\n"
" unescaped: %r" % (key, n, value, decoded))
continue
froms = [spec] if isinstance(spec, str) else list(spec)
missing = [f for f in froms if f not in base_lits]
if missing:
bad.append("INVENTED %s cites text that is in no base-commit literal: %r"
% (key, missing[0]))
continue
# A STRING `from` means "this key replaced exactly that literal" -- compared byte for byte, so
# even a verb swapped from %s to %v convicts. A LIST means "this key joined these literals",
# which is how a concatenation or a trailing parameter becomes one message; there the printf
# verbs are what the join replaced, so both sides are compared with the verbs removed.
if isinstance(spec, str):
if value != froms[0]:
bad.append("CHANGED %s\n hu.json: %r\n base : %r (%s)"
% (key, value, froms[0], base_lits[froms[0]]))
else:
got, want = strip_verbs(value), strip_verbs("".join(froms))
if got != want:
bad.append("CHANGED %s (joined from %d literals)\n"
" hu.json (verbs removed): %r\n"
" base (verbs removed): %r"
% (key, len(froms), got, want))
print("go-parity: base capture %s (%d literals), %d slice-2 keys listed, %d pre-existing, "
"%d named in Go"
% (base.get("commit", "?")[:12] or "?", len(base_lits), len(keys), len(preexisting),
len(named)))
if bad:
print("\ngo-parity gate CONVICTS (%d):" % len(bad))
for b in bad:
print(" " + b)
return 1
print("go-parity gate OK: every Go-side key carries base-commit text, byte for byte.")
return 0
if __name__ == "__main__":
sys.exit(main(sys.argv))
+6 -1
View File
@@ -38,7 +38,12 @@ TEMPLATE_ROOTS = [os.path.join(CTRL, "internal", "web", "templates")]
MARKER = re.compile(r'\{\{\s*T\s+"([A-Za-z0-9_.\-]+)"\s*\}\}')
EN_MISSING_CEILING = 0
HU_FORMAL_CEILING = 16 # 6 -> 10 (release A) -> 12 (release B) -> 16 (release C): converted copy carries more „ön" forms (R-516), unchanged by rule
HU_FORMAL_CEILING = 18 # 6 -> 10 -> 12 -> 16 (slice 1) -> 18 (slice 2 release A): the count follows the
# conversion, it is not a judgement. Moving a Go literal into hu.json makes an „ön" form the gate could
# not see before VISIBLE to it -- the two that arrived are `flash.login.password_changed` („kérjük")
# and `alert.deadapp.group` („nézze meg"), both word-for-word what the Go code already said. Slice 2 may
# not reword a Hungarian sentence (parity, 10-localisation.md s1), so the ceiling rises with the
# measurement and R-516 still owns the words.
EN_FORBIDDEN = re.compile(r"\b(please|kindly)\b", re.I)
+21
View File
@@ -45,6 +45,8 @@ COVERS = {
"golden-notice": "R-410 in the other direction: an empty dir must not count as a bake",
"minagent-header": "the word MinAgent in prose / a code span, not the header line (test_minagent_header_gate.py)",
"i18n": "an undefined marker key, a pleading English value, a shrinking/growing gap (v0.247.0)",
"go-parity": "a Go-side key REWORDED, a key citing text no base literal has, and a converted "
"key left out of the map (v0.252.0, R-557)",
}
fails = []
@@ -173,6 +175,25 @@ swapped("retrieval-promise/en-ok", "retrieval_promise_gate.py", EN_JSON,
swapped("secret-markup/bundle", "secret_in_markup_gate.py", EN_JSON,
_one('"launcher.link_masolasa": "Copy link"', '"launcher.link_masolasa": "Copy {{.RetrievalPassword}}"'))
# --- go-parity (v0.252.0, R-557): a Go-side message key may only carry base-commit text. ---
# --- Three shapes, because the gate makes three different claims. ---
GO_KEYS = os.path.join(HERE, "i18n_go_keys.json")
_A_KEY = '"flash.share.enabled": "A megosztás bekapcsolva."'
_A_HU = '"flash.share.enabled": "A megosztás bekapcsolva."'
# 1. name-for-fact: the KEY is still listed and still named in Go, and its text was reworded by one
# word. A gate that only checked "is this key accounted for" passes this.
swapped("go-parity/reworded", "i18n_go_parity.py", HU_JSON,
_one(_A_HU, '"flash.share.enabled": "A megosztás most bekapcsolva."'))
# 2. constant-for-measurement: the key map cites text nobody ever wrote. A gate that compared
# hu.json against the MAP alone (rather than against the frozen capture) passes this.
swapped("go-parity/invented", "i18n_go_parity.py", GO_KEYS,
_one(_A_KEY, '"flash.share.enabled": "Ez a mondat sosem létezett."'))
# 3. declaration-for-reachability: a converted key is NAMED by Go and quietly dropped from the map.
# A gate that only walked the map passes this -- which is how a conversion escapes review.
swapped("go-parity/unlisted", "i18n_go_parity.py", GO_KEYS,
_one(_A_KEY + ",\n", ""))
# --- golden-notice: R-410's decoy, in the other direction. It is ADVISORY, so rc is never the ---
# --- question — what it COUNTED is. ---
ran += 1