From bef39598d0b6582459bff9c37ffe29c4a4e5eab2 Mon Sep 17 00:00:00 2001 From: kisfenyo Date: Fri, 18 Sep 2026 16:54:20 +0200 Subject: [PATCH] v0.256.0: the box sends its own sentence in the household's language (R-558 Part B) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit MinAgent: 0.131.0 (unchanged). Needs hub v0.118.0+, which shipped first and tolerates a box that sends none of this - every box in the fleet is that box until this release reaches it. The hub writes a household's e-mails in their language now, but about a third of those mails carry a sentence the BOX composed, naming a drive, an app or a number. The hub cannot translate one. So the box sends it twice. - message_customer on POST /api/v1/event, omitempty. A HUNGARIAN household sends nothing extra at all, so its payload stays byte-for-byte what every box sends today and the hub's fallback path keeps being the one production exercises rather than a branch nobody takes. - 19 producers render both sentences from ONE bundle key. `message` stays Hungarian always: it is what the operator is mailed and what the hub logs. - customer.language bootstraps a new box - stored choice, then config, then Hungarian. The config value is NEVER written into settings.json: that would record a choice the household never made. The Hungarian did not move, measured twice: the wire golden from the slice-2 base commit, and the Go parity gate over all 19 new keys. Three guards had to learn the change and one caught me: the test seam now carries the new field; the R-329 severity register reported two dynamic sites as no longer existing the moment they moved off PushEvent (the walk now checks 36 severity literals, up from 20); and TestConfigLanguageIsWiredInMain reads main.go, because cmd/ is gitignored and ripgrep does not. A mistake, named: the first pass dropped displayName from three producers, which would have mailed customers "Alkalmazás telepítve: %!s(MISSING)". Caught reading the diff; now pinned by a test that refuses %!/MISSING/%s/%d in either language. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS --- CHANGELOG.md | 46 + REPORT.md | 98 +- controller/README.md | 32 +- controller/cmd/controller/main.go | 5 + controller/internal/config/config.go | 14 +- controller/internal/i18n/locales/en.json | 21 +- controller/internal/i18n/locales/hu.json | 21 +- controller/internal/notify/deadapp_test.go | 8 +- .../internal/notify/disk_health_test.go | 10 +- .../internal/notify/message_customer_test.go | 169 ++ controller/internal/notify/notifier.go | 188 +- .../notify/r329_severity_contract_test.go | 16 +- .../internal/notify/wire_golden_test.go | 4 +- .../internal/settings/config_language_test.go | 109 + controller/internal/settings/settings.go | 41 +- controller/scripts/i18n_go_keys.json | 1887 +++++++++-------- 16 files changed, 1601 insertions(+), 1068 deletions(-) create mode 100644 controller/internal/notify/message_customer_test.go create mode 100644 controller/internal/settings/config_language_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a4ec69..f40679e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,49 @@ +## v0.256.0 — the box sends its own sentence in the household's language (2026-09-18, R-558 Part B) + +**MinAgent: 0.131.0** (unchanged). **Needs hub v0.118.0+**, which shipped first and tolerates a box +that sends none of this — every box in the fleet is that box until this release reaches it. + +Slice 3's other half. The hub now writes the household's e-mails in their language, but roughly a +third of those mails carry a sentence the BOX composed, naming a drive, an app or a number. The hub +cannot translate one. So the box sends it twice. + +- **`message_customer`** on `POST /api/v1/event` — the same sentence in the household's language, + beside the unchanged Hungarian `message`. `omitempty`, and **a Hungarian household sends nothing + extra at all**: its payload stays byte-for-byte what it is today, so the hub's fallback path — the + one the whole fleet uses — keeps being the path production exercises, rather than becoming a branch + nobody takes. +- **19 producers converted** to bundle keys, rendered twice from ONE key: health ×3, controller + started/updated/update-failed, storage disconnected/reconnected, backup target absent/restored, + app deployed/deploy-started/deploy-failed/removed, crossdrive ×2, disaster recovery ×2, db-dump + completed. `message` stays Hungarian **always** — it is what the operator is mailed and what + `notification_log` records. +- **The Hungarian is byte-identical, and it is measured twice over**: `TestEventMessageWireTextIsFrozen` + (goldens captured at the slice-2 base commit) and the **Go parity gate**, which now checks all 19 + keys against the base-commit literals and passes. +- **`customer.language` bootstraps a new box.** `GetLanguage` prefers the household's stored choice, + then `controller.yaml`'s `customer.language`, then Hungarian. The config value is **never written to + `settings.json`** — persisting it would record a choice the household never made, and a later + config pull could no longer be told apart from a deliberate switch. +- **The untranslatable tail** (a docker error, a validator's sentence) is appended to BOTH renderings, + so an English household gets an English sentence with a foreign tail rather than no sentence. + +**Three guards had to learn the change, and one of them caught me:** + +- The test seam `pushFn` now carries `messageCustomer`. A seam that cannot see a new field cannot test + it — the lesson this file already records one release back, when `PushEvent` had to become the seam. +- The R-329 severity contract walks call sites BY FUNCTION NAME. Converting producers moved two + dynamic-severity sites off `PushEvent`, and the register immediately reported them as no longer + existing — the "a stale register entry is also a failure" half doing exactly its job. The three new + push helpers are registered; the walk now checks **36** severity literals, up from 20. +- `TestConfigLanguageIsWiredInMain` reads `main.go` and asserts the setter is CALLED. `cmd/` is + gitignored, so ripgrep skips `main.go` and a reviewer's search finds nothing — which is precisely + how a seam gets built and never wired in this repo. + +**A mistake worth recording:** the first pass of the conversion dropped the `displayName` argument +from three producers, which would have mailed customers `Alkalmazás telepítve: %!s(MISSING)`. It was +caught by reading the diff, and is now pinned by a test that refuses any sentence containing `%!`, +`MISSING`, `%s` or `%d` in either language. + ## v0.255.0 — The globe on the sign-in-flow pages: styled, and inside the card (2026-09-18, fixes v0.254.0) **MinAgent: 0.131.0** (unchanged). **No hub release needed.** Nothing on the wire moved. One Hungarian diff --git a/REPORT.md b/REPORT.md index 207a12c..8e4c30c 100644 --- a/REPORT.md +++ b/REPORT.md @@ -1,70 +1,56 @@ -# REPORT — the globe on the sign-in-flow pages, fixed (controller v0.255.0) +# REPORT — localisation slice 3 Part B: the box sends its sentence in the household's language -**2026-09-18 · base `2e9d402` (v0.254.0) · MinAgent 0.131.0, unchanged · no hub release.** -Architecture named: `felhom.eu/documentation/architecture/10-localisation.md` §3 (who picks the -language, and where the switch lives). +**controller v0.256.0** · base `8fb2f9ef9d5a` · 2026-09-18 · R-558 Part B · MinAgent 0.131.0 (unchanged) -## 1. Claims in the prompt that live source disproved — named first +## What shipped -| the prompt said | source says | -|---|---| -| "only the three shells lack `?v=`" | **FIVE do**: `login`, `claim`, `recovery`, **and both guest share pages** (`launcher_shared`, `launcher_share_password`). The share pages carry no globe, but the stale-stylesheet fault is the same for any CSS change and their visitor is the likeliest to hold an old copy. All five fixed — which is why 3 fixtures outside the named three changed, against the prompt's "zero re-captures outside the three shells". (8 first-boot wizard templates also lack it; out of scope, R-554 deletes them.) | -| "`.Version` is already in their data" | **Only in two.** `login` (auth.go:343) and `claim` (claim.go:289) set it; `recovery` and both share pages did not. Rather than add it three times, it is set once in `executeTemplateLang` — the choke point every shell renders through — so the next shell cannot miss it. | -| "`login.html` L11 … `style.css` L3683-3684" | Confirmed, line for line. | +The hub half (v0.118.0/0.118.1) writes a household's e-mails in their language. This is the other +half: about a third of those mails carry a sentence the BOX composed — naming a drive, an app, a +number — and the hub cannot translate one. The box now sends it twice. -## 2. What was wrong, and why no test saw it +- **`message_customer`** on the event wire: the same sentence in the household's language, beside the + unchanged Hungarian `message`. `omitempty`, and **a Hungarian household sends nothing extra**, so + its payload stays byte-for-byte what every box in the fleet sends today. +- **19 producers converted** to bundle keys, both renderings from ONE key. +- **`customer.language` bootstraps a new box**: stored choice → config → Hungarian. The config value + is never persisted into `settings.json`; doing so would record a choice nobody made. -**The globe rendered unstyled** for anyone whose browser had `style.css` cached from before v0.254.0: -the shells requested it with no `?v=`, so the cached copy — which has no `.lang-globe` rules — kept -being served. The markup was correct, which is why every existing test passed: **they all read the -HTML, and the fault was in which CSS file the browser fetched.** It took a screenshot to see it. +## The Hungarian did not move, measured twice -**And the globe floated outside the card**, pinned to the corner of the viewport, reading as a stray -browser control rather than part of the page. +- `TestEventMessageWireTextIsFrozen` — goldens captured at the slice-2 base commit. +- **The Go parity gate** — all 19 new keys checked byte-for-byte against the base-commit literals + (`688 slice-2 keys listed, 34 pre-existing, 484 named in Go`, gate OK). -## 3. The fix +## Three guards had to learn the change, and one caught me -- `?v={{.Version}}` on all five shells; `Version` set in `executeTemplateLang`. -- `.shell-lang` is now `display:flex; justify-content:center; margin-top:1.5rem` — a plain block inside - the card. The absolute positioning and the menu-direction override are **deleted**, so the shared - `.lang-globe-menu` rule applies and the dashboard and the shells cannot drift apart. -- The div moved to the end of each card: under the footer on `login` and `claim`, last in the card on - `recovery` (which has no footer paragraph). +- **The test seam** `pushFn` now carries `messageCustomer`. A seam that cannot see a new field cannot + test it — the lesson this repo recorded one release ago when `PushEvent` had to become the seam. +- **The R-329 severity contract** walks call sites by FUNCTION NAME. Converting producers moved two + dynamic-severity sites off `PushEvent` and the register reported them as no longer existing — the + "a stale register entry is also a failure" half doing its job. The three new helpers are registered; + the walk now checks **36** severity literals, up from 20. +- **`TestConfigLanguageIsWiredInMain`** reads `main.go` and asserts the setter is CALLED. `cmd/` is + gitignored, so ripgrep skips `main.go` and a reviewer's search finds nothing — which is exactly how + a seam gets built and never wired in this repo. Red-proofed by deleting the call. -## 3b. A third defect, caught by an existing test +## A mistake, named -Putting the version on the guest share pages would have printed the controller build onto a page **a -stranger with a capability URL can open**. `TestShareGuest_HeadersTilesNoAdminChrome` already refused -that, and refused it here — a good test earning its keep. Those two pages now take an **opaque -per-build tag** instead (`AssetTag`): same cache-busting, no disclosure. +The first pass of the conversion **dropped the `displayName` argument from three producers** +(`app_deployed`, `app_deploy_started`, `app_removed`). Shipped, that would have mailed customers +`Alkalmazás telepítve: %!s(MISSING)`. Caught by reading my own diff, and now pinned by a test that +refuses any sentence containing `%!`, `MISSING`, `%s` or `%d` in either language. -The fill is one function, `addShellAssetData`, called by `executeTemplateLang` **and** by the parity -harness. Slice 2 release C got the fixture/production split wrong twice; sharing the function is what -makes a third time impossible rather than merely unlikely. +## What is NOT converted, and why -## 4. Tests, red-proofed +- **The operator-tier types** (15 of them, listed in the hub's `operatorOnlyEvents`): the operator + reads Hungarian, and these never reach a customer. +- **Producers whose sentence is composed in another package** and reaches the notifier already + finished — `backup_failed`, `db_dump_failed`, `backup_integrity_ok`/`_failed`, + `offbox_enlarge_blocked`, `local_api_endpoint_drift`. They still send Hungarian only, so an English + household can still see one Hungarian line in some mails. **Filed as a row rather than left as + prose.** +- The R-570 sentence, which stays Hungarian until that row closes. -`TestGlobeOnAnonymousShells` gained: the stylesheet carries a **non-empty** `?v=`; the globe is inside -the card; the globe is below the footer where one exists. Each was red-proofed by restoring the -defect's exact shape — the unversioned link (convicts, naming the stylesheet) and the globe put back -before the card (convicts, "floats outside it"). +## Green -`testServer` gained `version: "test"`: an empty `?v=` busts a cache exactly once and never again, and -baking that into a fixture would have hidden it. The test now refuses an empty one. - -## 5. Parity - -15 fixtures re-captured — 1 login, 4 claim, 7 recovery, 3 guest share — and **91 identical, including -every dashboard page**. Four change shapes: the three shells gained the moved globe AND the buster; -the two guest pages gained only the buster, as an opaque tag. The whole diff is the `?v=` attribute and the moved block, nothing else: -`felhom.eu/documentation/audits/i18n-slice2-2026-09-18/D/parity-diff.txt`. - -## 6. Green gate - -`go build ./... && go vet ./... && go test ./...` green; all controller gates OK. - -## 7. Row - -**R-579** — the cache-buster gap: found, fixed and closed in this session, with the general form -recorded (a template that loads a versioned asset without the version is invisible to every test that -reads markup). +`go build` / `go vet` / `go test ./...` clean. All 17 controller gates OK. All 23 decoys behave. diff --git a/controller/README.md b/controller/README.md index 53cef65..f55f74d 100644 --- a/controller/README.md +++ b/controller/README.md @@ -3614,7 +3614,7 @@ without reaching `Images()`. --- -### 18. Dashboard language (i18n) (v0.247.0–v0.254.0) +### 18. Dashboard language (i18n) (v0.247.0–v0.256.0) Design: `felhom.eu/documentation/architecture/10-localisation.md`. Inventory: `felhom.eu/documentation/audits/I18N-INVENTORY-2026-09-17.md`. @@ -3820,6 +3820,36 @@ controller/ ## Configuration + +#### 18.5 The e-mails the hub sends (v0.256.0, slice 3) + +**The hub writes a household's e-mails in the household's language** from hub v0.118.0. It learns +which language from the box: every report carries `"language"` (since v0.247.0), and the hub prefers +the last reported one over the operator's creation-time default. Nothing here needs a setting. + +**The box sends about a third of those sentences itself**, and the hub cannot translate one — it +arrives as finished text naming a drive, an app or a number. So `POST /api/v1/event` carries +**`message_customer`**: the same sentence in the household's language, beside the unchanged Hungarian +`message`. + +- `message` is **always Hungarian**. It is what the operator is mailed, what the hub logs and what + `notification_log` records; a household switching language moves nothing an operator reads. +- A **Hungarian household sends no second copy at all** (`omitempty`), so its payload is byte-for-byte + what every box has always sent. +- 19 producers render both from ONE bundle key (`event.*`): health ×3, controller started/updated/ + update-failed, storage disconnected/reconnected, backup target absent/restored, app deployed/ + deploy-started/deploy-failed/removed, crossdrive ×2, disaster recovery ×2, db-dump completed. +- An **untranslatable tail** (a docker error, a validator's sentence) is appended to both renderings. +- **Not converted, deliberately:** the operator-tier types (the hub keeps them off the customer + channel) and the producers whose sentence is composed in another package and reaches the notifier + already finished. Those still send Hungarian only, so an English household can still see one + Hungarian line in some mails. Tracked as a row. + +**A new box starts in the language the operator picked.** The hub renders `customer.language` into +`controller.yaml`; `GetLanguage` prefers the household's stored choice, then that, then Hungarian. +The config value is **never written into `settings.json`** — persisting it would record a choice the +household never made. + ### Controller config (`controller.yaml`) Single YAML file per customer, infrastructure-only. Does **not** contain app-specific config. diff --git a/controller/cmd/controller/main.go b/controller/cmd/controller/main.go index 65af88e..feeff70 100644 --- a/controller/cmd/controller/main.go +++ b/controller/cmd/controller/main.go @@ -341,6 +341,11 @@ func main() { logger.Fatalf("[FATAL] Failed to load settings from %s: %v", settingsPath, err) } sett.SetDebug(cfg.Logging.Level == "debug") + // v0.256.0 (R-558): the language the hub says this box should START in. Consulted by + // GetLanguage ONLY when the household has never chosen — an explicit choice in settings.json + // always wins, so a config pull cannot unseat it. A config change restarts the controller, so + // binding it once here is enough. Pinned by TestConfigLanguageIsWiredInMain. + sett.SetConfigLanguage(cfg.Customer.Language) // --- Auto-discover storage paths from deployed apps --- discoveredPaths := discoverHDDPaths(cfg.Paths.StacksDir, logger) diff --git a/controller/internal/config/config.go b/controller/internal/config/config.go index 8479362..5812d08 100644 --- a/controller/internal/config/config.go +++ b/controller/internal/config/config.go @@ -142,10 +142,16 @@ type SystemConfig struct { } type CustomerConfig struct { - ID string `yaml:"id"` - Name string `yaml:"name"` - Domain string `yaml:"domain"` - Email string `yaml:"email"` + ID string `yaml:"id"` + Name string `yaml:"name"` + Domain string `yaml:"domain"` + Email string `yaml:"email"` + // Language (v0.256.0, R-558) is the language the OPERATOR chose when creating this customer, as + // rendered by the hub into controller.yaml. It is the language this box STARTS in and nothing + // more: the moment the household picks one on the dashboard, settings.json holds their choice + // and this value is never consulted again. A config pull therefore cannot overwrite a + // household's choice. Empty (an older hub, or a hand-written config) means Hungarian. + Language string `yaml:"language"` TelegramChatID string `yaml:"telegram_chat_id"` } diff --git a/controller/internal/i18n/locales/en.json b/controller/internal/i18n/locales/en.json index baf6b14..8c1aed9 100644 --- a/controller/internal/i18n/locales/en.json +++ b/controller/internal/i18n/locales/en.json @@ -2267,5 +2267,24 @@ "note.tier2_unit_confirm_date_unproven_fmt": " The copy is from: %s – that is the time of the last backup attempt; we cannot prove it succeeded.", "note.tier2_unit_confirm_contrast": " The “Restore files” button beside it only fills in the missing files and overwrites nothing. The app stops while either one runs.", "note.tier2_unit_stale_clause": " WARNING: this copy's data package is older than the latest backup — the package on the main drive was incomplete, so the complete one was kept. The restore uses the package named above.", - "note.tier2_unit_stale_notice_fmt": "The copy's data package is older than the latest backup (%s): the package on the main drive was incomplete, so the complete one already there was kept." + "note.tier2_unit_stale_notice_fmt": "The copy's data package is older than the latest backup (%s): the package on the main drive was incomplete, so the complete one already there was kept.", + "event.health_critical": "System health is critical (was: %s)", + "event.health_degraded": "System health has got worse (was: %s)", + "event.health_recovered": "System health is back to normal: %s (was: %s)", + "event.db_dump_completed": "Database backup finished", + "event.controller_updated": "Controller updated: %s → %s", + "event.controller_update_failed": "Controller update failed: %s → %s", + "event.controller_started": "Controller started (%s)", + "event.storage_disconnected": "Drive disconnected unexpectedly: %s", + "event.backup_target_absent": "The whole-system backup drive is not available: %s (%s)", + "event.backup_target_restored": "The whole-system backup drive is available again: %s (%s)", + "event.storage_reconnected": "Drive reconnected: %s", + "event.app_deployed": "App installed: %s", + "event.app_deploy_started": "App install started: %s", + "event.app_deploy_failed": "App install did not succeed: %s", + "event.app_removed": "App removed: %s", + "event.crossdrive_completed": "Second backup copy finished: %s", + "event.crossdrive_failed": "Second backup copy failed: %s", + "event.disaster_recovery_started": "Disaster recovery started (%d app(s))", + "event.disaster_recovery_completed": "Disaster recovery finished (%d succeeded, %d failed)" } diff --git a/controller/internal/i18n/locales/hu.json b/controller/internal/i18n/locales/hu.json index 444c2c5..b182319 100644 --- a/controller/internal/i18n/locales/hu.json +++ b/controller/internal/i18n/locales/hu.json @@ -2256,5 +2256,24 @@ "note.tier2_unit_confirm_date_unproven_fmt": " A másolat kelte: %s – ez az utolsó mentési kísérlet ideje, azt nem tudjuk igazolni, hogy sikeres volt.", "note.tier2_unit_confirm_contrast": " A mellette lévő „Fájlok visszaállítása” ezzel szemben csak a hiányzó fájlokat pótolja, és semmit nem ír felül. Az alkalmazás a művelet idejére leáll.", "note.tier2_unit_stale_clause": " FIGYELEM: ennek a másolatnak az adatcsomagja régebbi, mint a legutóbbi mentés — a fő meghajtón lévő csomag hiányos volt, ezért a meglévő, teljes másolatot megőriztük. A visszaállítás a fent megadott csomagot használja.", - "note.tier2_unit_stale_notice_fmt": "A másolat adatcsomagja régebbi, mint a legutóbbi mentés (%s): a fő meghajtón lévő csomag hiányos volt, ezért a meglévő, teljes másolatot megőriztük." + "note.tier2_unit_stale_notice_fmt": "A másolat adatcsomagja régebbi, mint a legutóbbi mentés (%s): a fő meghajtón lévő csomag hiányos volt, ezért a meglévő, teljes másolatot megőriztük.", + "event.health_critical": "Rendszer állapot kritikus (volt: %s)", + "event.health_degraded": "Rendszer állapot romlott (volt: %s)", + "event.health_recovered": "Rendszer állapot helyreállt: %s (volt: %s)", + "event.db_dump_completed": "Adatbázis mentés elkészült", + "event.controller_updated": "Controller frissítve: %s → %s", + "event.controller_update_failed": "Controller frissítés sikertelen: %s → %s", + "event.controller_started": "Controller elindult (%s)", + "event.storage_disconnected": "Meghajtó váratlanul leválasztva: %s", + "event.backup_target_absent": "A rendszermentés meghajtója nem érhető el: %s (%s)", + "event.backup_target_restored": "A rendszermentés meghajtója újra elérhető: %s (%s)", + "event.storage_reconnected": "Meghajtó újra csatlakoztatva: %s", + "event.app_deployed": "Alkalmazás telepítve: %s", + "event.app_deploy_started": "Alkalmazás telepítése elindult: %s", + "event.app_deploy_failed": "Alkalmazás telepítése nem sikerült: %s", + "event.app_removed": "Alkalmazás eltávolítva: %s", + "event.crossdrive_completed": "Másodlagos mentés elkészült: %s", + "event.crossdrive_failed": "Másodlagos mentés sikertelen: %s", + "event.disaster_recovery_started": "Katasztrófa helyreállítás elindítva (%d alkalmazás)", + "event.disaster_recovery_completed": "Katasztrófa helyreállítás befejezve (%d sikeres, %d sikertelen)" } diff --git a/controller/internal/notify/deadapp_test.go b/controller/internal/notify/deadapp_test.go index 44b6348..a2d6f27 100644 --- a/controller/internal/notify/deadapp_test.go +++ b/controller/internal/notify/deadapp_test.go @@ -11,7 +11,7 @@ import ( func TestNotifyAppStartFailures_OneEventPerTransition(t *testing.T) { n := New("http://hub", "key", "cust", nil, log.New(io.Discard, "", 0), false) var events []string - n.pushFn = func(eventType, _, msg string, _ interface{}) { + n.pushFn = func(eventType, _, msg, _ string, _ interface{}) { if eventType == "app_start_failed" { events = append(events, msg) } @@ -45,7 +45,7 @@ func TestNotifyAppStartFailures_OneEventPerTransition(t *testing.T) { func TestNotifyAppStartFailures_FirstSeenDownFires(t *testing.T) { n := New("http://hub", "key", "cust", nil, log.New(io.Discard, "", 0), false) var count int - n.pushFn = func(eventType, _, _ string, _ interface{}) { + n.pushFn = func(eventType, _, _, _ string, _ interface{}) { if eventType == "app_start_failed" { count++ } @@ -67,7 +67,7 @@ func TestNotifyAppStartFailures_FirstSeenDownFires(t *testing.T) { func TestNotifyAppStartFailures_StopStartCrashSequence(t *testing.T) { n := New("http://hub", "key", "cust", nil, log.New(io.Discard, "", 0), false) var count int - n.pushFn = func(eventType, _, _ string, _ interface{}) { + n.pushFn = func(eventType, _, _, _ string, _ interface{}) { if eventType == "app_start_failed" { count++ } @@ -96,7 +96,7 @@ func TestNotifyAppStartFailures_StopStartCrashSequence(t *testing.T) { func TestNotifyAppStartFailures_HealthyNeverFires(t *testing.T) { n := New("http://hub", "key", "cust", nil, log.New(io.Discard, "", 0), false) var count int - n.pushFn = func(string, string, string, interface{}) { count++ } + n.pushFn = func(string, string, string, string, interface{}) { count++ } n.NotifyAppStartFailures([]AppRunState{{Name: "radarr", Down: false}}) n.NotifyAppStartFailures([]AppRunState{{Name: "radarr", Down: false}}) if count != 0 { diff --git a/controller/internal/notify/disk_health_test.go b/controller/internal/notify/disk_health_test.go index 913ac3c..09acbfd 100644 --- a/controller/internal/notify/disk_health_test.go +++ b/controller/internal/notify/disk_health_test.go @@ -38,7 +38,7 @@ func hubSeverityNotifies(sev string) bool { func TestNotifyDiskHealthDegraded_SeverityRoutes(t *testing.T) { n := &Notifier{} var gotSev string - n.pushFn = func(eventType, severity, message string, details interface{}) { gotSev = severity } + n.pushFn = func(eventType, severity, message, _ string, details interface{}) { gotSev = severity } n.NotifyDiskHealthDegraded(DiskAlert{Label: "sdb", Kind: DiskAlertWarn, Attributes: []string{"függőben lévő szektorok"}}) @@ -70,7 +70,7 @@ func TestNotifyDiskHealthDegraded_WarnShape(t *testing.T) { n := &Notifier{} var gotType, gotMsg string var gotDetails interface{} - n.pushFn = func(eventType, severity, message string, details interface{}) { + n.pushFn = func(eventType, severity, message, _ string, details interface{}) { gotType, gotMsg, gotDetails = eventType, message, details } @@ -100,7 +100,9 @@ func TestNotifyDiskHealthDegraded_FailShapes(t *testing.T) { n := &Notifier{} var gotMsg string var gotDetails interface{} - n.pushFn = func(eventType, severity, message string, details interface{}) { gotMsg, gotDetails = message, details } + n.pushFn = func(eventType, severity, message, _ string, details interface{}) { + gotMsg, gotDetails = message, details + } cases := []struct { name string @@ -154,7 +156,7 @@ func TestNotifyDiskHealthDegraded_FailShapes(t *testing.T) { func TestNotifyDiskHealthDegraded_CopyDiscipline(t *testing.T) { n := &Notifier{} var gotMsg string - n.pushFn = func(eventType, severity, message string, details interface{}) { gotMsg = message } + n.pushFn = func(eventType, severity, message, _ string, details interface{}) { gotMsg = message } for _, k := range []DiskAlertKind{DiskAlertWarn, DiskAlertFailSelfReported, DiskAlertFailSectors, DiskAlertFailTemperature, DiskAlertFailWorsened} { n.NotifyDiskHealthDegraded(DiskAlert{Label: "TESTDISK", Kind: k, Sectors: 8, TemperatureC: 61}) if !strings.Contains(gotMsg, "TESTDISK") { diff --git a/controller/internal/notify/message_customer_test.go b/controller/internal/notify/message_customer_test.go new file mode 100644 index 0000000..3fe1d58 --- /dev/null +++ b/controller/internal/notify/message_customer_test.go @@ -0,0 +1,169 @@ +package notify + +import ( + "io" + "log" + "path/filepath" + "strings" + "testing" + + "gitea.dooplex.hu/admin/felhom-controller/internal/i18n" + "gitea.dooplex.hu/admin/felhom-controller/internal/settings" +) + +// Slice 3 Part B (R-558) — the box sends its own sentence twice, and the Hungarian one never moves. +// +// The hub cannot translate a sentence the box composed: it arrives as finished text naming a drive, +// an app or a number. These tests are the whole of the controller's half of that. + +func notifierInLang(t *testing.T, lang string) *Notifier { + t.Helper() + sett, err := settings.Load(filepath.Join(t.TempDir(), "settings.json"), log.New(io.Discard, "", 0)) + if err != nil { + t.Fatal(err) + } + if lang != "" { + if err := sett.SetLanguage(lang); err != nil { + t.Fatal(err) + } + } + return &Notifier{settings: sett, logger: log.New(io.Discard, "", 0)} +} + +// converted lists every producer this release converted to a bundle key, with a call that exercises +// it. It is the table the two tests below share, so a producer cannot be covered by one and missed +// by the other. +func convertedProducers() []struct { + name string + call func(*Notifier) +} { + return []struct { + name string + call func(*Notifier) + }{ + // NotifyHealthChange compares against the PREVIOUS status it saw, so each case primes it + // with a first call (which records and returns) and then transitions. + {"health_critical", func(n *Notifier) { + n.enabled = true + n.NotifyHealthChange("ok", nil, nil) + n.NotifyHealthChange("fail", nil, nil) + }}, + {"health_degraded", func(n *Notifier) { + n.enabled = true + n.NotifyHealthChange("ok", nil, nil) + n.NotifyHealthChange("warn", nil, nil) + }}, + {"health_recovered", func(n *Notifier) { + n.enabled = true + n.NotifyHealthChange("fail", nil, nil) + n.NotifyHealthChange("ok", nil, nil) + }}, + {"db_dump_completed", func(n *Notifier) { n.NotifyDBDumpCompleted(DBDumpDetails{}) }}, + {"controller_updated", func(n *Notifier) { n.NotifyControllerUpdated("0.255.0", "0.256.0", true) }}, + {"controller_update_failed", func(n *Notifier) { n.NotifyControllerUpdated("0.255.0", "0.256.0", false) }}, + {"controller_started", func(n *Notifier) { n.NotifyControllerStarted("0.256.0", nil) }}, + {"storage_disconnected", func(n *Notifier) { n.NotifyStorageDisconnected("Kulso HDD", nil) }}, + {"storage_reconnected", func(n *Notifier) { n.NotifyStorageReconnected("Kulso HDD") }}, + {"backup_target_absent", func(n *Notifier) { n.NotifyBackupTargetAbsent("Kulso HDD", "/mnt/hdd_1") }}, + {"backup_target_restored", func(n *Notifier) { n.NotifyBackupTargetRestored("Kulso HDD", "/mnt/hdd_1") }}, + {"app_deployed", func(n *Notifier) { n.NotifyAppDeployed("privatebin", "PrivateBin") }}, + {"app_deploy_started", func(n *Notifier) { n.NotifyAppDeployStarted("privatebin", "PrivateBin") }}, + {"app_deploy_failed", func(n *Notifier) { n.NotifyAppDeployFailed("privatebin", "PrivateBin", "") }}, + {"app_removed", func(n *Notifier) { n.NotifyAppRemoved("privatebin", "PrivateBin") }}, + {"crossdrive_completed", func(n *Notifier) { n.NotifyCrossDriveCompleted(CrossDriveDetails{StackName: "privatebin"}) }}, + {"crossdrive_failed", func(n *Notifier) { n.NotifyCrossDriveFailed(CrossDriveDetails{StackName: "privatebin"}) }}, + {"disaster_recovery_started", func(n *Notifier) { n.NotifyDRStarted(3) }}, + {"disaster_recovery_completed", func(n *Notifier) { n.NotifyDRCompleted(7, 2) }}, + } +} + +// An ENGLISH household gets an English second sentence, and the Hungarian one is untouched. +func TestNotifierSendsMessageCustomerForAnEnglishHousehold(t *testing.T) { + for _, tc := range convertedProducers() { + t.Run(tc.name, func(t *testing.T) { + n := notifierInLang(t, "en") + var hu, en string + var seen int + n.pushFn = func(_, _, message, messageCustomer string, _ interface{}) { + hu, en, seen = message, messageCustomer, seen+1 + } + tc.call(n) + if seen == 0 { + t.Fatal("the producer pushed nothing — nothing below was compared") + } + if en == "" { + t.Fatalf("no household sentence was sent; the English household's mail would carry "+ + "the Hungarian %q", hu) + } + if en == hu { + t.Errorf("the household sentence is identical to the Hungarian one: %q", en) + } + // A rendering fault is silent in a mail body — %!s(MISSING) is a real thing customers + // have been sent by projects that did not check this. + for _, bad := range []string{"%!", "MISSING", "EXTRA", "%s", "%d"} { + if strings.Contains(en, bad) || strings.Contains(hu, bad) { + t.Errorf("a sentence carries a formatting fault (%s):\n hu %q\n en %q", bad, hu, en) + } + } + }) + } +} + +// A HUNGARIAN household sends NO second sentence at all. +// +// That is not an optimisation: it keeps the payload of every box in the fleet byte-for-byte what it +// is today, so the hub's fallback path — the one every existing box uses — stays the path that is +// actually exercised in production rather than becoming a branch nobody takes. +func TestHungarianHouseholdSendsNoSecondSentence(t *testing.T) { + for _, tc := range convertedProducers() { + t.Run(tc.name, func(t *testing.T) { + n := notifierInLang(t, "hu") + var hu, en string + n.pushFn = func(_, _, message, messageCustomer string, _ interface{}) { hu, en = message, messageCustomer } + tc.call(n) + if en != "" { + t.Errorf("a Hungarian household sent a second sentence %q — the payload is no longer "+ + "what every box in the fleet sends today", en) + } + if hu == "" { + t.Error("the Hungarian sentence is empty") + } + }) + } +} + +// A notifier with NO settings at all (setup mode, and several older tests) is Hungarian rather than +// a panic. The nil-prefs panic this project already fixed once is the same shape. +func TestNotifierWithoutSettingsIsHungarian(t *testing.T) { + n := &Notifier{logger: log.New(io.Discard, "", 0)} + var hu, en string + n.pushFn = func(_, _, message, messageCustomer string, _ interface{}) { hu, en = message, messageCustomer } + n.NotifyAppDeployed("privatebin", "PrivateBin") + if en != "" { + t.Errorf("a notifier with no settings sent a household sentence: %q", en) + } + b, err := i18n.Shared() + if err != nil { + t.Fatal(err) + } + if hu != b.Msgf("hu", "event.app_deployed", "PrivateBin") { + t.Errorf("the Hungarian sentence is not the bundle's: %q", hu) + } +} + +// The untranslatable tail is appended to BOTH renderings — an English household gets an English +// sentence with a foreign tail, never no sentence at all. +func TestUntranslatableTailIsKeptInBothLanguages(t *testing.T) { + n := notifierInLang(t, "en") + var hu, en string + n.pushFn = func(_, _, message, messageCustomer string, _ interface{}) { hu, en = message, messageCustomer } + n.NotifyAppDeployFailed("privatebin", "PrivateBin", "exit status 1") + for _, s := range []string{hu, en} { + if !strings.HasSuffix(s, " — exit status 1") { + t.Errorf("the untranslatable tail is missing from %q", s) + } + } + if !strings.Contains(en, "PrivateBin") || en == hu { + t.Errorf("the English sentence is wrong: %q (hu %q)", en, hu) + } +} diff --git a/controller/internal/notify/notifier.go b/controller/internal/notify/notifier.go index b341519..64a50ad 100644 --- a/controller/internal/notify/notifier.go +++ b/controller/internal/notify/notifier.go @@ -13,6 +13,8 @@ import ( "time" "gitea.dooplex.hu/admin/felhom-controller/internal/settings" + + "gitea.dooplex.hu/admin/felhom-controller/internal/i18n" ) // Notifier sends structured events to the hub via /api/v1/event. @@ -51,7 +53,11 @@ type Notifier struct { // pushFn is a test seam for the transition-emitting notifiers (fix-3). nil → the real async // PushEvent; tests inject a synchronous recorder. - pushFn func(eventType, severity, message string, details interface{}) + // v0.256.0 (R-558): messageCustomer is IN the seam, not beside it. A seam that cannot see a new + // field is a seam that cannot test it — the same lesson this file already records one comment + // down, where PushEvent had to become the seam because emit alone made a whole class of + // producer invisible. + pushFn func(eventType, severity, message, messageCustomer string, details interface{}) // Event history ring buffer (debug page) historyMu sync.RWMutex @@ -164,17 +170,92 @@ type CrossDriveDetails struct { // eventRequest is the JSON payload sent to /api/v1/event. type eventRequest struct { - CustomerID string `json:"customer_id"` - EventType string `json:"event_type"` - Severity string `json:"severity"` - Message string `json:"message"` - Details json.RawMessage `json:"details,omitempty"` + CustomerID string `json:"customer_id"` + EventType string `json:"event_type"` + Severity string `json:"severity"` + Message string `json:"message"` + // MessageCustomer (v0.256.0, R-558) is the SAME sentence in the household's language. + // + // `omitempty` is load-bearing: a Hungarian household sends no second copy at all, so its event + // payload is byte-for-byte what it has always been, and the hub's fallback path — the one every + // box in the fleet uses today — is the one that keeps being exercised in production rather than + // becoming a branch nobody takes. + // + // Message stays Hungarian ALWAYS. It is what the operator is mailed, what the hub logs and what + // notification_log records; nothing an operator reads may move because a household switched. + MessageCustomer string `json:"message_customer,omitempty"` + Details json.RawMessage `json:"details,omitempty"` } // PushEvent sends a structured event to the hub's /api/v1/event endpoint. // Non-blocking (goroutine). Retries twice with 3s backoff. // details may be nil (omitted from JSON) or a struct that marshals to JSON. +// pushEventMsg renders ONE bundle key twice — Hungarian for the wire, the household's language +// beside it — and pushes both (R-558). +// +// Producers call this instead of composing a Hungarian sentence, because the hub CANNOT translate a +// sentence the box composed: it arrives as finished text naming a drive, an app or a number. The +// only place both languages can be produced is here, where the key and its arguments still exist. +// +// The Hungarian is rendered from the SAME key, so it cannot drift from the English: there is one +// sentence with two spellings, not two sentences. That the Hungarian is byte-identical to the +// literal it replaced is pinned by TestEventMessageWireTextIsFrozen and by the Go parity gate. +func (n *Notifier) pushEventMsg(eventType, severity, key string, details interface{}, args ...interface{}) { + b, err := i18n.Shared() + if err != nil { + // The bundle is embedded, so this is a broken build rather than a runtime condition. Push + // the key itself rather than an empty sentence: an event with a visible key reaches the + // operator and gets fixed; an event with an empty message is a silent hole. + n.logger.Printf("[ERROR] pushEventMsg: bundle unavailable for %s: %v", key, err) + n.pushEventBoth(eventType, severity, key, "", details) + return + } + hungarian := b.Msgf(i18n.Default, key, args...) + + // A Hungarian household sends nothing extra — see MessageCustomer's comment. + household := "" + if lang := n.boxLang(); lang != i18n.Default { + household = b.Msgf(lang, key, args...) + } + n.pushEventBoth(eventType, severity, hungarian, household, details) +} + +// pushEventMsgSuffix is pushEventMsg with a tail that CANNOT be translated — a docker error, a +// validator's sentence, something that arrived as finished text. It is appended to both renderings, +// so an English household gets an English sentence with a foreign tail rather than no sentence. +func (n *Notifier) pushEventMsgSuffix(eventType, severity, key, suffix string, details interface{}, args ...interface{}) { + b, err := i18n.Shared() + if err != nil { + n.logger.Printf("[ERROR] pushEventMsgSuffix: bundle unavailable for %s: %v", key, err) + n.pushEventBoth(eventType, severity, key+suffix, "", details) + return + } + household := "" + if lang := n.boxLang(); lang != i18n.Default { + household = b.Msgf(lang, key, args...) + suffix + } + n.pushEventBoth(eventType, severity, b.Msgf(i18n.Default, key, args...)+suffix, household, details) +} + +// boxLang is the household's language, nil-safe. A notifier built without settings (several tests, +// and the setup-mode path) is Hungarian rather than a panic. +func (n *Notifier) boxLang() string { + if n.settings == nil { + return i18n.Default + } + return n.settings.GetLanguage() +} + +// PushEvent sends a structured event to the hub's /api/v1/event endpoint, Hungarian only. +// +// Kept for producers whose sentence is composed somewhere else and reaches them already finished — +// and for the OPERATOR-tier types, which are Hungarian (or English) by design and have no household +// half. A customer-facing producer should use pushEventMsg. func (n *Notifier) PushEvent(eventType, severity, message string, details interface{}) { + n.pushEventBoth(eventType, severity, message, "", details) +} + +func (n *Notifier) pushEventBoth(eventType, severity, message, messageCustomer string, details interface{}) { // The test seam sits HERE, not only in emit (v0.252.0, R-557). Most producers call PushEvent // directly, so a test that set pushFn saw nothing from them and the difference was invisible: a // producer that pushes nothing and a producer whose message is empty both leave a recorder empty. @@ -182,7 +263,7 @@ func (n *Notifier) PushEvent(eventType, severity, message string, details interf // only do that if every producer is reachable through one seam. In production pushFn is nil and // this line does nothing. if n.pushFn != nil { - n.pushFn(eventType, severity, message, details) + n.pushFn(eventType, severity, message, messageCustomer, details) return } if !n.enabled { @@ -200,11 +281,12 @@ func (n *Notifier) PushEvent(eventType, severity, message string, details interf } payload := eventRequest{ - CustomerID: n.customerID, - EventType: eventType, - Severity: severity, - Message: message, - Details: detailsJSON, + CustomerID: n.customerID, + EventType: eventType, + Severity: severity, + Message: message, + MessageCustomer: messageCustomer, + Details: detailsJSON, } jsonData, err := json.Marshal(payload) @@ -293,16 +375,13 @@ func (n *Notifier) NotifyHealthChange(status string, issues, warnings []string) if newRank > prevRank { // Degradation if status == "fail" { - n.PushEvent("health_critical", "error", - fmt.Sprintf("Rendszer állapot kritikus (volt: %s)", prev), details) + n.pushEventMsg("health_critical", "error", "event.health_critical", details, prev) } else if status == "warn" { - n.PushEvent("health_degraded", "warning", - fmt.Sprintf("Rendszer állapot romlott (volt: %s)", prev), details) + n.pushEventMsg("health_degraded", "warning", "event.health_degraded", details, prev) } } else { // Recovery - n.PushEvent("health_recovered", "info", - fmt.Sprintf("Rendszer állapot helyreállt: %s (volt: %s)", status, prev), details) + n.pushEventMsg("health_recovered", "info", "event.health_recovered", details, status, prev) } } @@ -402,7 +481,7 @@ func (n *Notifier) NotifyDBDumpFailed(message, errMsg string) { // NotifyDBDumpCompleted sends a DB dump success event. func (n *Notifier) NotifyDBDumpCompleted(details DBDumpDetails) { - n.PushEvent("db_dump_completed", "info", "Adatbázis mentés elkészült", details) + n.pushEventMsg("db_dump_completed", "info", "event.db_dump_completed", details) } // NotifyIntegrityFailed sends a backup integrity check failure event. @@ -435,29 +514,27 @@ func (n *Notifier) NotifyIntegrityOK(message string) { // NotifyControllerUpdated sends a controller update event. func (n *Notifier) NotifyControllerUpdated(fromVer, toVer string, success bool) { severity := "info" - msg := fmt.Sprintf("Controller frissítve: %s → %s", fromVer, toVer) + key := "event.controller_updated" details := UpdateDetails{FromVersion: fromVer, ToVersion: toVer} if !success { severity = "error" - msg = fmt.Sprintf("Controller frissítés sikertelen: %s → %s", fromVer, toVer) + key = "event.controller_update_failed" } - n.PushEvent("controller_updated", severity, msg, details) + n.pushEventMsg("controller_updated", severity, key, details, fromVer, toVer) } // NotifyControllerStarted sends a controller startup event. // details may include self-test summary (e.g., {"selftest_pass": 8, "selftest_warn": 1, "selftest_fail": 0}). func (n *Notifier) NotifyControllerStarted(version string, details map[string]interface{}) { - n.PushEvent("controller_started", "info", - fmt.Sprintf("Controller elindult (%s)", version), details) + n.pushEventMsg("controller_started", "info", "event.controller_started", details, version) } // NotifyStorageDisconnected sends a drive disconnection event. func (n *Notifier) NotifyStorageDisconnected(label string, stoppedApps []string) { - msg := fmt.Sprintf("Meghajtó váratlanul leválasztva: %s", label) - n.PushEvent("storage_disconnected", "error", msg, StorageDetails{ + n.pushEventMsg("storage_disconnected", "error", "event.storage_disconnected", StorageDetails{ Label: label, StoppedApps: stoppedApps, - }) + }, label) } // NotifyBackupTargetAbsent (E-2) reports that the drive holding the WHOLE-GUEST backup is gone. @@ -468,23 +545,21 @@ func (n *Notifier) NotifyStorageDisconnected(label string, stoppedApps []string) // at all: the tier stays DUE (targetStoragePresent checks name presence, never reachability), so the // only evidence was its own failure at the next due cycle, up to ~24 h away on the daily local tier. func (n *Notifier) NotifyBackupTargetAbsent(label, target string) { - n.PushEvent("backup_target_absent", "error", - fmt.Sprintf("A rendszermentés meghajtója nem érhető el: %s (%s)", label, target), - StorageDetails{Label: label}) + n.pushEventMsg("backup_target_absent", "error", "event.backup_target_absent", + StorageDetails{Label: label}, label, target) } // NotifyBackupTargetRestored is the paired recovery. info severity — the existing recovery pattern; // severityNotifies is deliberately NOT widened. func (n *Notifier) NotifyBackupTargetRestored(label, target string) { - n.PushEvent("backup_target_restored", "info", - fmt.Sprintf("A rendszermentés meghajtója újra elérhető: %s (%s)", label, target), - StorageDetails{Label: label}) + n.pushEventMsg("backup_target_restored", "info", "event.backup_target_restored", + StorageDetails{Label: label}, label, target) } // NotifyStorageReconnected sends a drive reconnection event. func (n *Notifier) NotifyStorageReconnected(label string) { - n.PushEvent("storage_reconnected", "info", - fmt.Sprintf("Meghajtó újra csatlakoztatva: %s", label), StorageDetails{Label: label}) + n.pushEventMsg("storage_reconnected", "info", "event.storage_reconnected", + StorageDetails{Label: label}, label) } // AgentChannelDetails carries the classified reason for a controller→agent channel-down event. @@ -532,9 +607,8 @@ type EndpointDriftDetails struct { // so an install that was interrupted five seconds later still stood on the hub's timeline as // „Alkalmazás telepítve" forever — measured 2026-09-16 with mealie, which ended `not_deployed`. func (n *Notifier) NotifyAppDeployed(stackName, displayName string) { - n.PushEvent("app_deployed", "info", - fmt.Sprintf("Alkalmazás telepítve: %s", displayName), - AppDetails{StackName: stackName, DisplayName: displayName}) + n.pushEventMsg("app_deployed", "info", "event.app_deployed", + AppDetails{StackName: stackName, DisplayName: displayName}, displayName) } // NotifyAppDeployStarted records the ACCEPTANCE — the fact `app_deployed` used to assert. It keeps @@ -543,21 +617,22 @@ func (n *Notifier) NotifyAppDeployed(stackName, displayName string) { // NOTE: the hub validates event_type against allowedEventTypes and 400s an unknown one, so this type // MUST exist there too (hub handler.go) or the event is silently inert. func (n *Notifier) NotifyAppDeployStarted(stackName, displayName string) { - n.PushEvent("app_deploy_started", "info", - fmt.Sprintf("Alkalmazás telepítése elindult: %s", displayName), - AppDetails{StackName: stackName, DisplayName: displayName}) + n.pushEventMsg("app_deploy_started", "info", "event.app_deploy_started", + AppDetails{StackName: stackName, DisplayName: displayName}, displayName) } // NotifyAppDeployFailed closes the pair. severity=warning, not info: an install the customer started // and that did not finish is a thing someone should see, and the silent version of this is exactly // what left a completed-install record for an app that was never installed. func (n *Notifier) NotifyAppDeployFailed(stackName, displayName, reason string) { - msg := fmt.Sprintf("Alkalmazás telepítése nem sikerült: %s", displayName) + // The reason is appended to BOTH renderings rather than folded into the key: it arrives as + // finished text (a docker error, a validator's sentence) that this function cannot translate. + suffix := "" if reason != "" { - msg += " — " + reason + suffix = " — " + reason } - n.PushEvent("app_deploy_failed", "warning", msg, - AppDetails{StackName: stackName, DisplayName: displayName}) + n.pushEventMsgSuffix("app_deploy_failed", "warning", "event.app_deploy_failed", suffix, + AppDetails{StackName: stackName, DisplayName: displayName}, displayName) } // AppRunState is one deployed app's running state for the fix-3 start-failure notifier: Down=true @@ -725,9 +800,12 @@ func (n *Notifier) NotifyDiskHealthDegraded(a DiskAlert) { } // emit sends an event through the test seam if set, else the real async PushEvent. +// +// Hungarian only: its one producer (the disk-health alerts) still composes a finished sentence. +// It passes no household copy, which is what an un-converted producer looks like. func (n *Notifier) emit(eventType, severity, message string, details interface{}) { if n.pushFn != nil { - n.pushFn(eventType, severity, message, details) + n.pushFn(eventType, severity, message, "", details) return } n.PushEvent(eventType, severity, message, details) @@ -735,27 +813,23 @@ func (n *Notifier) emit(eventType, severity, message string, details interface{} // NotifyAppRemoved sends an app removal event. func (n *Notifier) NotifyAppRemoved(stackName, displayName string) { - n.PushEvent("app_removed", "info", - fmt.Sprintf("Alkalmazás eltávolítva: %s", displayName), - AppDetails{StackName: stackName, DisplayName: displayName}) + n.pushEventMsg("app_removed", "info", "event.app_removed", + AppDetails{StackName: stackName, DisplayName: displayName}, displayName) } // NotifyCrossDriveCompleted sends a cross-drive backup success event. func (n *Notifier) NotifyCrossDriveCompleted(details CrossDriveDetails) { - n.PushEvent("crossdrive_completed", "info", - fmt.Sprintf("Másodlagos mentés elkészült: %s", details.StackName), details) + n.pushEventMsg("crossdrive_completed", "info", "event.crossdrive_completed", details, details.StackName) } // NotifyCrossDriveFailed sends a cross-drive backup failure event. func (n *Notifier) NotifyCrossDriveFailed(details CrossDriveDetails) { - n.PushEvent("crossdrive_failed", "error", - fmt.Sprintf("Másodlagos mentés sikertelen: %s", details.StackName), details) + n.pushEventMsg("crossdrive_failed", "error", "event.crossdrive_failed", details, details.StackName) } // NotifyDRStarted sends a disaster recovery start event. func (n *Notifier) NotifyDRStarted(appCount int) { - n.PushEvent("disaster_recovery_started", "warning", - fmt.Sprintf("Katasztrófa helyreállítás elindítva (%d alkalmazás)", appCount), nil) + n.pushEventMsg("disaster_recovery_started", "warning", "event.disaster_recovery_started", nil, appCount) } // NotifyDRCompleted sends a disaster recovery completion event. @@ -764,8 +838,8 @@ func (n *Notifier) NotifyDRCompleted(successCount, failCount int) { if failCount > 0 { severity = "warning" } - n.PushEvent("disaster_recovery_completed", severity, - fmt.Sprintf("Katasztrófa helyreállítás befejezve (%d sikeres, %d sikertelen)", successCount, failCount), nil) + n.pushEventMsg("disaster_recovery_completed", severity, "event.disaster_recovery_completed", nil, + successCount, failCount) } // ── Preferences sync ───────────────────────────────────────────────── diff --git a/controller/internal/notify/r329_severity_contract_test.go b/controller/internal/notify/r329_severity_contract_test.go index 8fb1d17..99a4e9b 100644 --- a/controller/internal/notify/r329_severity_contract_test.go +++ b/controller/internal/notify/r329_severity_contract_test.go @@ -49,6 +49,14 @@ var hubSeverityVocabulary = map[string]bool{ var severityArg = map[string]int{ "emit": 1, // (eventType, severity, message, details) "PushEvent": 1, // (eventType, severity, message, details) + // v0.256.0 (R-558): the key-based producers. Registered here in the SAME commit that introduced + // them — the walk finds severities by FUNCTION NAME, so a new push helper that is not listed is + // a set of call sites this contract silently stops checking. It caught its own omission: the + // conversion moved two dynamic sites off PushEvent and the register immediately reported them as + // no longer existing, which is the "a stale register entry is also a failure" half doing its job. + "pushEventMsg": 1, // (eventType, severity, key, details, args...) + "pushEventMsgSuffix": 1, // (eventType, severity, key, suffix, details, args...) + "pushEventBoth": 1, // (eventType, severity, message, messageCustomer, details) } // knownDynamicSeveritySites are the call sites that pass a VARIABLE rather than a literal, so this @@ -57,7 +65,13 @@ var severityArg = map[string]int{ // honest limit of an AST check is that it cannot follow a variable, and an unlisted limit is not a // limit, it is a hole. var knownDynamicSeveritySites = map[string]string{ - "internal/notify/notifier.go:emit": `pass-through of its own severity parameter to PushEvent — the value is checked at emit's CALLERS, above`, + "internal/notify/notifier.go:emit": `pass-through of its own severity parameter to PushEvent — the value is checked at emit's CALLERS, above`, + // v0.256.0 (R-558): three more pass-throughs, the same shape as emit. Each forwards its OWN + // severity parameter to pushEventBoth without inspecting it, so the value is checked at their + // callers — which is where the walk's 36 literals are found. + "internal/notify/notifier.go:PushEvent": `pass-through of its own severity parameter to pushEventBoth — checked at PushEvent's callers`, + "internal/notify/notifier.go:pushEventMsg": `pass-through of its own severity parameter to pushEventBoth — checked at pushEventMsg's callers`, + "internal/notify/notifier.go:pushEventMsgSuffix": `pass-through of its own severity parameter to pushEventBoth — checked at pushEventMsgSuffix's callers`, "internal/notify/notifier.go:NotifyControllerUpdated": `local var: "info", or "error" when the update failed`, "internal/notify/notifier.go:NotifyDRCompleted": `local var: "info", or "warning" when failCount > 0`, "internal/notify/notifier.go:NotifyAgentChannelDown": `internal/channelhealth's classifier — every severity literal in checker.go is "warning" or "error"`, diff --git a/controller/internal/notify/wire_golden_test.go b/controller/internal/notify/wire_golden_test.go index 4bac85f..ec6af61 100644 --- a/controller/internal/notify/wire_golden_test.go +++ b/controller/internal/notify/wire_golden_test.go @@ -79,7 +79,7 @@ func TestEventMessageWireTextIsFrozen(t *testing.T) { var got string var seen int n := &Notifier{} - n.pushFn = func(_, _, message string, _ interface{}) { + n.pushFn = func(_, _, message, _ string, _ interface{}) { got, seen = message, seen+1 } tc.call(n) @@ -102,7 +102,7 @@ func TestEventMessageWireTextIsFrozen(t *testing.T) { func TestEventMessagesCarryNoBundleKey(t *testing.T) { var msgs []string n := &Notifier{} - n.pushFn = func(_, _, message string, _ interface{}) { msgs = append(msgs, message) } + n.pushFn = func(_, _, message, _ string, _ interface{}) { msgs = append(msgs, message) } n.NotifyAppDeployed("privatebin", "PrivateBin") n.NotifyAppRemoved("privatebin", "PrivateBin") diff --git a/controller/internal/settings/config_language_test.go b/controller/internal/settings/config_language_test.go new file mode 100644 index 0000000..fc02182 --- /dev/null +++ b/controller/internal/settings/config_language_test.go @@ -0,0 +1,109 @@ +package settings + +import ( + "io" + "log" + "os" + "path/filepath" + "strings" + "testing" +) + +// The bootstrap rule (R-558): a box starts in the language the hub says, until the household picks. + +func newSettings(t *testing.T) *Settings { + t.Helper() + s, err := Load(filepath.Join(t.TempDir(), "settings.json"), log.New(io.Discard, "", 0)) + if err != nil { + t.Fatal(err) + } + return s +} + +func TestLanguageBootstrapFromConfig(t *testing.T) { + // 3. Nothing anywhere → Hungarian. + s := newSettings(t) + if got := s.GetLanguage(); got != "hu" { + t.Errorf("no choice, no config = %q, want hu", got) + } + + // 2. The config's language, for a box nobody has told yet. + s.SetConfigLanguage("en") + if got := s.GetLanguage(); got != "en" { + t.Errorf("config en, no choice = %q, want en — a box created as English starts Hungarian", got) + } + + // 1. An explicit choice WINS, in both directions. + if err := s.SetLanguage("hu"); err != nil { + t.Fatal(err) + } + if got := s.GetLanguage(); got != "hu" { + t.Errorf("chose hu with config en = %q, want hu — the household's choice lost", got) + } + s2 := newSettings(t) + s2.SetConfigLanguage("hu") + if err := s2.SetLanguage("en"); err != nil { + t.Fatal(err) + } + if got := s2.GetLanguage(); got != "en" { + t.Errorf("chose en with config hu = %q, want en", got) + } + + // An unsupported config value is ignored, not stored and not rendered. + s3 := newSettings(t) + s3.SetConfigLanguage("klingon") + if got := s3.GetLanguage(); got != "hu" { + t.Errorf("unsupported config language = %q, want hu", got) + } +} + +// The config default must NEVER be written to settings.json. If it were, a box would record a +// choice the household never made, and the next config pull could no longer be distinguished from +// a deliberate switch. +func TestConfigLanguageIsNotPersisted(t *testing.T) { + path := filepath.Join(t.TempDir(), "settings.json") + s, err := Load(path, log.New(io.Discard, "", 0)) + if err != nil { + t.Fatal(err) + } + s.SetConfigLanguage("en") + // Force a save through an unrelated setter, the way any ordinary use would. + if err := s.SetBackupWindowStart("01:00"); err != nil { + t.Fatal(err) + } + raw, err := os.ReadFile(path) + if err != nil { + t.Fatal(err) + } + if strings.Contains(string(raw), `"language"`) { + t.Errorf("the config default was persisted into settings.json — it is now indistinguishable "+ + "from a household's choice:\n%s", raw) + } + // And a REAL choice IS persisted, or the control proves nothing. + if err := s.SetLanguage("en"); err != nil { + t.Fatal(err) + } + raw, _ = os.ReadFile(path) + if !strings.Contains(string(raw), `"language"`) { + t.Error("a real choice was NOT persisted — the negative control above is meaningless") + } +} + +// SEAM-WIRING PIN (the class with four instances in this project): the setter exists, but is it +// CALLED? `cmd/` is gitignored, so ripgrep skips main.go and a reviewer's search finds nothing — +// which is exactly how a seam gets built and never wired here. +func TestConfigLanguageIsWiredInMain(t *testing.T) { + raw, err := os.ReadFile(filepath.Join("..", "..", "cmd", "controller", "main.go")) + if err != nil { + t.Fatalf("cannot read main.go to check the wiring: %v", err) + } + src := string(raw) + if !strings.Contains(src, "sett.SetConfigLanguage(cfg.Customer.Language)") { + t.Error("main.go never calls SetConfigLanguage — the bootstrap default is an unwired seam, " + + "and every box would start Hungarian whatever the hub said") + } + // It must come from the CONFIG, not from a literal someone pasted to make this pass. + if strings.Contains(src, `SetConfigLanguage("`) { + t.Error("main.go passes a literal to SetConfigLanguage — it must pass cfg.Customer.Language") + } +} diff --git a/controller/internal/settings/settings.go b/controller/internal/settings/settings.go index db16635..bfcf8e2 100644 --- a/controller/internal/settings/settings.go +++ b/controller/internal/settings/settings.go @@ -27,6 +27,13 @@ type Settings struct { // surfaced to the dashboard as a persistent banner. Not persisted. LoadWarning string `json:"-"` + // configLanguage (v0.256.0, R-558) is `customer.language` from controller.yaml — the language + // the hub says this box should START in. It is consulted ONLY when the household has never + // chosen: GetLanguage prefers the stored choice, always. Not persisted, and deliberately not a + // field of the JSON: it belongs to the config, which is re-pulled, and writing it into + // settings.json would turn a default into a choice the household never made. + configLanguage string `json:"-"` + // Auth PasswordHash string `json:"password_hash,omitempty"` // bcrypt hash, overrides controller.yaml @@ -819,12 +826,40 @@ func (s *Settings) SetBackupWindowStart(start string) error { return s.save() } -// GetLanguage returns the household's dashboard language, normalised: anything unset or unknown is -// Hungarian (i18n.Default). +// SetConfigLanguage records the language controller.yaml says this box should start in. +// +// Called once at startup and again after each config pull. It never touches s.Language: a +// re-delivered config must not be able to change what a household chose. +func (s *Settings) SetConfigLanguage(lang string) { + s.mu.Lock() + defer s.mu.Unlock() + s.configLanguage = lang +} + +// GetLanguage returns the household's dashboard language, normalised. +// +// THE ORDER, and it is the whole of the bootstrap rule (R-558): +// +// 1. What the HOUSEHOLD chose, from settings.json. An explicit choice always wins, and it wins +// forever — a later config pull cannot unseat it. +// 2. What the hub said to start in (`customer.language`), for a box nobody has told yet. This is +// what makes "create the customer as English" produce an English box on first boot. +// 3. Hungarian. +// +// Steps 1 and 2 are DIFFERENT KINDS OF FACT and the difference is the reason this is not one field: +// an empty s.Language means "never chosen", which is not the same as "chose Hungarian". Collapsing +// them would make an English household's box silently revert to Hungarian the first time anyone +// looked at a config default. func (s *Settings) GetLanguage() string { s.mu.RLock() defer s.mu.RUnlock() - return i18n.Normalize(s.Language) + if i18n.IsSupported(s.Language) { + return s.Language + } + if i18n.IsSupported(s.configLanguage) { + return s.configLanguage + } + return i18n.Default } // SetLanguage stores the household's dashboard language and saves. Only a supported language is diff --git a/controller/scripts/i18n_go_keys.json b/controller/scripts/i18n_go_keys.json index 811671e..6dbbf10 100644 --- a/controller/scripts/i18n_go_keys.json +++ b/controller/scripts/i18n_go_keys.json @@ -1,936 +1,955 @@ { - "_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.single": [ - "Telepített alkalmazás nem fut: " - ], - "alert.deadapp.single_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.", - "err.stacks.migracio_mar_folyamatban": "migráció már folyamatban", - "err.stacks.alkalmazas_nem_talalhato": "alkalmazás nem található: %s", - "err.stacks.ismeretlen_migracios_hatokor": "ismeretlen migrációs hatókör: %s", - "err.stacks.migracios_naplo_irasa": [ - "migrációs napló írása: %w" - ], - "err.stacks.biztonsagi_mentes_folyamatban_probald_ujra": "biztonsági mentés folyamatban, próbáld újra", - "err.stacks.az_rsync_nem_erheto_el_a": "az rsync nem érhető el a rendszeren", - "err.stacks.a_cel_es_a_forras_tarolo": "a cél és a forrás tároló megegyezik", - "err.stacks.a_celtarolo_nem_elerheto_vagy_nem": "a céltároló nem elérhető vagy nem választható", - "err.stacks.utkozes_a_celtarolon_mar_letezik_ezeknek": "ütközés a céltárolón — már létezik ezeknek az alkalmazásoknak az adata: %s", - "err.stacks.nincs_eleg_hely_a_celtarolon_kb": "nincs elég hely a céltárolón (kb. %d GB szükséges, %.1f GB szabad)", - "err.stacks.ismeretlen_migracios_fazis": "ismeretlen migrációs fázis: %q", - "err.stacks.alkalmazas_leallitasa_sikertelen": [ - "alkalmazás leállítása sikertelen (%s): %w" - ], - "err.stacks.masolas_sikertelen_appdata": [ - "másolás sikertelen (%s appdata): %w" - ], - "err.stacks.masolas_sikertelen_mentes": [ - "másolás sikertelen (%s mentés): %w" - ], - "err.stacks.ugyfel_adatok_osszefesulese_sikertelen": [ - "ügyfél-adatok összefésülése sikertelen: %w" - ], - "err.stacks.ellenorzes_sikertelen_appdata": [ - "ellenőrzés sikertelen (%s appdata): %w" - ], - "err.stacks.ellenorzes_sikertelen_mentes": [ - "ellenőrzés sikertelen (%s mentés): %w" - ], - "err.stacks.ugyfel_adatok_ellenorzese_sikertelen": [ - "ügyfél-adatok ellenőrzése sikertelen: %w" - ], - "err.stacks.ujratelepites_sikertelen": [ - "újratelepítés sikertelen (%s): %w" - ], - "err.stacks.forras_torlese_sikertelen_appdata": [ - "forrás törlése sikertelen (%s appdata): %w" - ], - "err.stacks.forras_torlese_sikertelen_mentes": [ - "forrás törlése sikertelen (%s mentés): %w" - ], - "err.stacks.forras_nevter_olvasasa_sikertelen": [ - "forrás névtér olvasása sikertelen: %w" - ], - "err.stacks.forras_torlese_sikertelen": [ - "forrás törlése sikertelen (%s): %w" - ], - "err.stacks.cleanup_gate_a_z_egyseg_nincs": "cleanup gate: a(z) %q egység nincs ellenőrizve (állapot=%s)", - "err.stacks.cleanup_gate_a_z_alkalmazas_nincs": "cleanup gate: a(z) %q alkalmazás nincs újratelepítve (állapot=%s)", - "err.stacks.az_alkalmazas_nem_indult_el_az": "az alkalmazás nem indult el az új tárhelyen", - "err.stacks.fuggoben_levo_atvitel_maradt_pl": "%d függőben lévő átvitel maradt, pl. %q", - "err.stacks.a_forrasfajlnak_nincs_azonos_masolata_a": "a forrásfájlnak nincs azonos másolata a célon: %s", - "err.stacks.az_aldomain_nem_lehet_ures": "az aldomain nem lehet üres", - "err.stacks.az_aldomain_csak_kisbetuket_szamokat_es": "az aldomain csak kisbetűket, számokat és kötőjelet tartalmazhat, és nem kezdődhet/végződhet kötőjellel", - "err.stacks.a_z_aldomain_foglalt_rendszer_szamara": "a(z) %q aldomain foglalt rendszer számára", - "err.stacks.a_z_aldomain_mar_hasznalatban_van": "a(z) %q aldomain már használatban van egy másik alkalmazásban", - "err.stacks.a_beallitasok_nem_erhetok_el": "a beállítások nem érhetők el", - "err.stacks.a_halozati_megosztas_nincs_bekapcsolva": "a hálózati megosztás nincs bekapcsolva", - "err.stacks.a_megosztas_szolgaltatas_inditasa_sikertelen": [ - "a megosztás szolgáltatás indítása sikertelen: %w" - ], - "err.stacks.a_megosztas_leallitasa_sikertelen": [ - "a megosztás leállítása sikertelen: %w" - ], - "err.stacks.a_z_alkalmazas_nem_tamogatja_az": "a(z) %q alkalmazás nem támogatja az email-küldést", - "err.stacks.a_z_alkalmazas_nincs_telepitve": "a(z) %q alkalmazás nincs telepítve", - "err.backup.a_biztonsagi_mentes_elott_nem_sikerult": [ - "a biztonsági mentés előtt nem sikerült felderíteni az adatbázisokat: %w" - ], - "err.backup.a_biztonsagi_mentes_konyvtara_nem_hozhato": [ - "a biztonsági mentés könyvtára nem hozható létre: %w" - ], - "err.backup.a_jelenlegi_adatbazis_biztonsagi_mentese_sikertelen": [ - "a jelenlegi adatbázis biztonsági mentése sikertelen (%s): %w — a visszaállítás nem indult el" - ], - "err.backup.a_visszavonas_elott_nem_sikerult_felderiteni": [ - "a visszavonás előtt nem sikerült felderíteni az adatbázisokat: %w" - ], - "err.backup.a_visszavonashoz_szukseges_mentes_nem_talalhato": [ - "a visszavonáshoz szükséges mentés nem található (%s): %w" - ], - "err.backup.a_z_adatbazis_taroloja_nem_talalhato": "a(z) %s adatbázis-tárolója nem található a visszavonáshoz", - "err.backup.a_korabbi_allapot_visszaallitasa_sikertelen": [ - "a korábbi állapot visszaállítása sikertelen (%s): %w" - ], - "err.backup.egy_masik_mentesi_visszaallitasi_muvelet_mar": "egy másik mentési/visszaállítási művelet már fut", - "err.backup.nincs_elokeszitett_teljes_visszaallitas_futtass_elobb": "nincs előkészített teljes visszaállítás — futtass előbb egy teljes visszaállítást", - "err.backup.a_teljes_visszaallitas_hianyos_nincs_meg": "a teljes visszaállítás hiányos (%s nincs meg) — futtass előbb egy teljes visszaállítást", - "err.backup.a_pillanatkepben_nincs_mentesi_egyseg_a": "a pillanatképben nincs mentési egység — a visszaállítás nem indítható", - "err.backup.a_biztonsagi_mentes_nem_talalhato_a": "a biztonsági mentés nem található a lemezen — a visszaállítás biztonsági okból nem indult el", - "err.backup.az_adatbazis_szolgaltatas_nem_azonosithato_a": "Az adatbázis-szolgáltatás nem azonosítható a(z) %s alkalmazásban — a visszaállítás biztonsági okból nem indult el.", - "err.backup.a_z_leallitasa_elotti_jelolo_nem": [ - "a(z) %s leállítása előtti jelölő nem menthető: %w" - ], - "err.backup.a_z_fajljainak_visszaallitasa_sikertelen": [ - "a(z) %s fájljainak visszaállítása sikertelen: %w" - ], - "err.backup.a_z_adatkotetenek_visszaallitasa_sikertelen": [ - "a(z) %s adatkötetének visszaállítása sikertelen: %w" - ], - "err.backup.a_z_adatbazis_szolgaltatasanak_inditasa_sikertelen": [ - "a(z) %s adatbázis-szolgáltatásának indítása sikertelen: %w" - ], - "err.backup.a_z_ujrainditasa_sikertelen_a_fajlok": [ - "a(z) %s újraindítása sikertelen a fájlok visszaállítása után: %w" - ], - "err.backup.offbox_nincs_pillanatkep_a_z_alkalmazashoz": "offbox: nincs pillanatkép a(z) %s alkalmazáshoz", - "err.backup.offbox_a_z_pillanatkep_merete_ismeretlen": "offbox: a(z) %s pillanatkép mérete ismeretlen", - "err.backup.a_z_pillanatkepeben_nincs_mentesi_egyseg": "a(z) %s pillanatképében nincs mentési egység — a visszaállítás nem indítható", - "err.backup.a_pillanatkepben_nincs_mentesi_egyseg_backups": "a pillanatképben nincs mentési egység (backups/primary/%s)", - "err.backup.a_pillanatkep_egy_utvonala_a_nevteren": "a pillanatkép egy útvonala a névtéren kívülre mutat: %s", - "err.backup.a_pillanatkep_egy_utvonala_ervenytelen": "a pillanatkép egy útvonala érvénytelen (..): %s", - "err.backup.nem_egyseg_utvonal_a_fenntartott_backups": "nem-egység útvonal a fenntartott backups zónában: %s", - "err.backup.a_z_nincs_telepitve_elobb_allitsd": "a(z) %s nincs telepítve — előbb állítsd helyre az alkalmazást, utána az adatokat", - "err.backup.a_z_helyreallitasa_sikertelen": [ - "a(z) %s helyreállítása sikertelen: %w" - ], - "err.backup.nincs_elerheto_adatmeghajto_a_visszaallitashoz": "nincs elérhető adatmeghajtó a visszaállításhoz", - "err.backup.a_tavoli_mentes_nincs_beallitva": "a távoli mentés nincs beállítva", - "err.backup.nincs_eleg_szabad_hely_a_visszaallitashoz": "Nincs elég szabad hely a visszaállításhoz (%s szükséges, %s szabad).", - "err.backup.nincs_visszaallithato_megosztas_mentes": [ - "nincs visszaállítható megosztás-mentés: %w" - ], - "err.backup.a_megosztasok_visszaallitasa_sikertelen": [ - "a megosztások visszaállítása sikertelen: %w: %s" - ], - "err.backup.a_mentesben_nincs_megosztas_leiro": [ - "a mentésben nincs megosztás-leíró: %w" - ], - "err.backup.a_megosztas_leiro_olvashatatlan": [ - "a megosztás-leíró olvashatatlan: %w" - ], - "err.backup.nincs_elokeszitett_visszaallitas_futtass_elobb_egy": "nincs előkészített visszaállítás — futtass előbb egy megosztás-visszaállítást", - "err.backup.a_z_megosztas_fajljainak_visszaallitasa_sikertelen": [ - "a(z) „%s” megosztás fájljainak visszaállítása sikertelen: %w" - ], - "err.backup.a_frissites_elotti_mentes_lefutott_de": "a frissítés előtti mentés lefutott, de nem jött létre visszaállítható másolat", - "err.backup.adatathelyezes_folyamatban_a_mentes_most_nem": "adatáthelyezés folyamatban — a mentés most nem indítható", - "err.backup.az_alkalmazas_meghajtoja_nem_hatarozhato_meg": "az alkalmazás meghajtója nem határozható meg", - "err.backup.az_alkalmazas_meghajtoja_nem_elerheto": "az alkalmazás meghajtója nem elérhető (%s)", - "err.backup.nincs_eleg_szabad_hely_a_menteshez": "nincs elég szabad hely a mentéshez a(z) %s meghajtón", - "err.backup.adatbazis_felderites_sikertelen": [ - "adatbázis-felderítés sikertelen: %w" - ], - "err.backup.adatbazis_mentes_sikertelen": [ - "adatbázis-mentés sikertelen (%s): %w" - ], - "err.backup.kotetmentes_sikertelen": [ - "kötetmentés sikertelen: %w" - ], - "err.backup.a_mentesi_egyseg_rogzitese_sikertelen": [ - "a mentési egység rögzítése sikertelen: %w" - ], - "err.backup.az_alkalmazas_mentesi_helye_nem_hatarozhato": "az alkalmazás mentési helye nem határozható meg", - "err.backup.nincs_masodlagos_fajlmasolat_ehhez_az_alkalmazashoz": "nincs másodlagos fájlmásolat ehhez az alkalmazáshoz", - "err.backup.a_masodlagos_meghajto_nincs_csatlakoztatva": "a másodlagos meghajtó nincs csatlakoztatva", - "err.backup.az_alkalmazas_meghajtoja_nincs_csatlakoztatva": "az alkalmazás meghajtója nincs csatlakoztatva", - "err.backup.az_alkalmazas_meghajtoja_le_van_szerelve": "az alkalmazás meghajtója le van szerelve", - "err.backup.a_2_mentes_regi_formatumu_futtass": "A 2. mentés régi formátumú — futtass előbb egy új másodlagos mentést.", - "err.backup.ennek_az_alkalmazasnak_az_adatai_nem": "ennek az alkalmazásnak az adatai nem ebből a másolatból állíthatók vissza", - "err.backup.a_masodlagos_masolatban_nincs_megnyithato_mentesi": "a másodlagos másolatban nincs megnyitható mentési egység ehhez az alkalmazáshoz", - "err.backup.fajlmasolas_sikertelen": [ - "fájlmásolás sikertelen: %w" - ], - "err.backup.fajl_visszaallitva_de_az_alkalmazas_ujrainditasa": [ - "%d fájl visszaállítva, de az alkalmazás újraindítása sikertelen: %w" - ], - "err.backup.ervenytelen_alkalmazasnev": "érvénytelen alkalmazásnév", - "err.backup.a_torles_utvonala_kivul_esik_az": "a törlés útvonala kívül esik az ellenőrző mappán", - "err.backup.a_masolat_torlese_nem_sikerult": [ - "a másolat törlése nem sikerült: %w" - ], - "err.backup.nincs_ilyen_ellenorzo_masolat": "nincs ilyen ellenőrző másolat", - "err.backup.az_offsite_tarolo_nincs_elarvult_allapotban": "az offsite tároló nincs elárvult állapotban", - "err.backup.halozati_tarolo_nem_lehet_a_2": "Hálózati tároló nem lehet a 2. mentés célja — a fájltulajdonos-adatok megőrzése ott nem garantálható.", - "err.web.ervenytelen_csatlakoztatasi_nev_csak_betuk_szamok": "érvénytelen csatlakoztatási név (csak betűk, számok, _ és - engedélyezett)", - "err.web.nem_tamogatott_fajlrendszer_ext4_vagy_xfs": "nem támogatott fájlrendszer: %q (ext4 vagy xfs)", - "err.web.formazas_sikertelen": [ - "formázás sikertelen: %w" - ], - "err.web.formazas_kesz_de_az_uj_fajlrendszer": "formázás kész, de az új fájlrendszer-azonosító nem feloldható — frissítsen és használja a Csatolás funkciót", - "err.web.csatlakoztatas_sikertelen": [ - "csatlakoztatás sikertelen: %w" - ], - "err.web.a_kivalasztott_meghajtohoz_nem_talalhato_fajlrendszer": "a kiválasztott meghajtóhoz nem található fájlrendszer-azonosító (csak fájlrendszerrel rendelkező meghajtó csatolható)", - "err.web.a_formazas_allapota_nem_kerdezheto_le": [ - "a formázás állapota nem kérdezhető le: %w" - ], - "err.web.formazas_sikertelen_2": "formázás sikertelen: %s", - "err.web.a_formazas_nem_indult_el_az": "a formázás nem indult el az eszközön (%s)", - "err.web.leszereles_visszavonasa_sikertelen": [ - "leszerelés visszavonása sikertelen: %w" - ], - "err.web.utemezhetoseg_visszaallitasa_sikertelen": [ - "ütemezhetőség visszaállítása sikertelen: %w" - ], - "err.web.regisztracio_sikertelen": [ - "regisztráció sikertelen: %w" - ], - "err.web.ez_az_egyetlen_hasznalhato_tarhely_a": "ez az egyetlen használható tárhely — a leszerelés megtagadva (előbb adj hozzá vagy állíts be másik alapértelmezett meghajtót)", - "err.web.nyilvantartas_frissitese_sikertelen": [ - "nyilvántartás frissítése sikertelen: %w" - ], - "err.web.a_meghajto_leszerelese_sikertelen": [ - "a meghajtó leszerelése sikertelen: %w" - ], - "err.settings.a_kiszolgalo_neve_nem_lehet_ures": "a kiszolgáló neve nem lehet üres", - "err.settings.a_kiszolgalo_neve_legfeljebb_15_karakter": "a kiszolgáló neve legfeljebb 15 karakter lehet", - "err.settings.a_kiszolgalo_neve_csak_betut_szamot": "a kiszolgáló neve csak betűt, számot, kötőjelet és aláhúzást tartalmazhat", - "err.settings.a_megosztas_neve_nem_lehet_ures": "a megosztás neve nem lehet üres", - "err.settings.a_megosztas_neve_legfeljebb_15_karakter": "a megosztás neve legfeljebb 15 karakter lehet", - "err.settings.a_megosztas_neve_nem_tartalmazhat_perjelet": "a megosztás neve nem tartalmazhat perjelet vagy pontot", - "err.settings.a_megosztas_neve_nem_kezdodhet_alahuzassal": "a megosztás neve nem kezdődhet aláhúzással — ezek a nevek a rendszernek vannak fenntartva", - "err.settings.a_megosztas_neve_csak_betut_szamot": "a megosztás neve csak betűt, számot, kötőjelet és aláhúzást tartalmazhat", - "err.settings.mar_letezik_nevu_megosztas": "már létezik „%s” nevű megosztás", - "err.settings.a_z_megosztas_a_rendszer_resze": "a(z) „%s” megosztás a rendszer része, nem törölhető", - "err.settings.nincs_nevu_megosztas": "nincs „%s” nevű megosztás", - "err.settings.a_halozati_tarhely_nem_lehet_a": "a hálózati tárhely nem lehet a rendszermentés célja: %s", - "err.integrations.nincs_kezelo_a_z_integraciohoz": "nincs kezelő a(z) %s integrációhoz", - "err.integrations.a_szolgaltato_alkalmazas_nincs_telepitve": "a szolgáltató alkalmazás (%s) nincs telepítve", - "err.integrations.a_szolgaltato_alkalmazas_nem_fut": "a szolgáltató alkalmazás (%s) nem fut", - "err.integrations.a_celalkalmazas_nincs_telepitve": "a célalkalmazás (%s) nincs telepítve", - "err.integrations.a_celalkalmazas_nem_fut": "a célalkalmazás (%s) nem fut", - "err.integrations.integracio_alkalmazasa_sikertelen": [ - "integráció alkalmazása sikertelen: %w" - ], - "err.integrations.integracio_allapotanak_mentese_sikertelen": [ - "integráció állapotának mentése sikertelen: %w" - ], - "err.integrations.szolgaltato_stack_nem_talalhato": "szolgáltató stack %q nem található", - "err.integrations.onlyoffice_jwt_secret_nincs_beallitva_telepitsd": "OnlyOffice JWT_SECRET nincs beállítva — telepítsd újra az alkalmazást", - "err.integrations.filebrowser_config_olvasasi_hiba": [ - "FileBrowser config olvasási hiba: %w" - ], - "err.integrations.config_irasi_hiba": [ - "config írási hiba: %w" - ], - "err.integrations.config_atnevezesi_hiba": [ - "config átnevezési hiba: %w" - ], - "err.integrations.config_olvasasi_hiba": [ - "config olvasási hiba: %w" - ], - "err.integrations.onlyoffice_jwt_secret_nincs_beallitva": "OnlyOffice JWT_SECRET nincs beállítva", - "err.selfupdate.hianyos_registry_hitelesito_adatok_felhasznalonev_es": "hiányos registry hitelesítő adatok (felhasználónév és token együtt szükséges)", - "err.selfupdate.frissites_mar_folyamatban": "Frissítés már folyamatban", - "err.selfupdate.dev_verzio_nem_frissitheto": "Dev verzió nem frissíthető", - "err.selfupdate.mentes_fut_probalja_kesobb": "Mentés fut, próbálja később", - "err.selfupdate.a_frissites_nem_erheto_el_nincs": "A frissítés nem érhető el (nincs gazda-ügynök)", - "err.selfupdate.nincs_elerheto_frissites": "Nincs elérhető frissítés", - "err.selfupdate.docker_pull_hianyos_registry_hitelesito_adatok": "docker pull: hiányos registry hitelesítő adatok (felhasználónév és token együtt szükséges)", - "err.appexport.visszafejtes_sikertelen": [ - "visszafejtés sikertelen: %w" - ], - "err.appexport.a_z_kotet_adata_hianyzik_a": "a(z) %q kötet adata hiányzik a csomagból", - "err.appexport.a_z_adatkonyvtar_tartalma_hianyzik_a": "a(z) %q adatkönyvtár tartalma hiányzik a csomagból", - "err.appexport.ket_adatkonyvtar_azonos_nevvel_vegzodik_es": "két adatkönyvtár azonos névvel végződik (%q: %s és %s) — a csomag nem tudná megkülönböztetni őket", - "err.appexport.a_mentes_nem_tartalmaz_alkalmazasadatot_0": "a mentés nem tartalmaz alkalmazásadatot (0 adatkönyvtár, 0 kötet egy adattárolós alkalmazásnál)", - "err.appexport.jelszo_hibas_vagy_a_fajl_serult": "jelszó hibás vagy a fájl sérült", - "err.web.a_meghajtok_listaja_nem_olvashato": "a meghajtók listája nem olvasható", - "err.web.a_meghajto_nem_talalhato": "a meghajtó nem található", - "err.web.ez_a_mappa_nem_oszthato_meg": "Ez a mappa nem osztható meg.", - "err.notify.hub_nem_konfiguralt": "hub nem konfigurált", - "err.notify.hub_elerhetetlen": [ - "hub elérhetetlen: %w" - ], - "err.stacks.field_required_password": "a(z) %q mező kitöltése kötelező — használja a Generálás gombot vagy írjon be egy jelszót", - "err.stacks.field_required": "a(z) %q (%s) mező kitöltése kötelező", - "err.backup.offsite_over_quota": "A távoli mentés túllépte a tárhelykeretet (%d/%d GB) — törölj régi mentéseket vagy kérj nagyobb keretet.", - "err.stacks.not_enough_memory": [ - "Nincs elég memória az alkalmazás telepítéséhez. ", - "Szükséges: %d MB, Elérhető: %d MB ", - "(összesen: %d MB, ebből %d MB használt, %d MB rendszer számára fenntartva)" - ], - "err.stacks.memory_overcommit_warning": [ - "Az alkalmazások csúcsterhelése meghaladhatja a rendelkezésre álló memóriát. ", - "Normál használat mellett ez nem okoz problémát." - ], - "err.backup.not_installed_known_drive": [ - "a(z) %s nincs telepítve, ezért nincs hová visszaállítani az adatait. ", - "A mentése szerint az adatai itt voltak: %s. Telepítsd újra az alkalmazást (Alkalmazások) ", - "ugyanerre a helyre, utána ez a visszaállítás működni fog" - ], - "err.backup.not_installed": [ - "a(z) %s nincs telepítve, ezért nincs hová visszaállítani az adatait — ", - "telepítsd újra az alkalmazást (Alkalmazások), utána ez a visszaállítás működni fog" - ], - "err.backup.no_data_root": [ - "a(z) %s telepítve van, de a vezérlő nem tudja megállapítani, hová tartoznak az adatai ", - "(nincs beállítva rendszer-adatterület). Ellenőrizd a tárhely beállításait (Tárhely), utána indítsd újra a visszaállítást" - ], - "err.backup.no_registered_drive": [ - "nincs regisztrált adatmeghajtó, ezért nincs hová visszaállítani — ", - "a meghajtók megvannak, csak újra kell csatolni őket a Tárhely → Meghajtók oldalon, utána ", - "ez a visszaállítás működni fog" - ], - "err.backup.db_restore_and_rollback_failed": [ - "a(z) %s adatbázisának visszaállítása sikertelen, és a korábbi állapot visszatöltése sem sikerült. ", - "Az alkalmazást biztonsági okból LEÁLLÍTVA hagytuk, hogy az adatai ne sérüljenek tovább. ", - "Vedd fel velünk a kapcsolatot — %s" - ], - "err.backup.db_restore_failed_rolled_back": [ - "a(z) %s adatbázisának visszaállítása sikertelen — az adataid visszakerültek a visszaállítás előtti állapotba, ", - "az alkalmazás fut tovább. Ha újra megpróbálnád, előbb vedd fel velünk a kapcsolatot" - ], - "err.integrations.onlyoffice_no_subdomain": "OnlyOffice aldomain nem ismert", - "err.integrations.occ_failed": [ - "occ parancs sikertelen (%s): %v (kimenet: %s)" - ], - "err.integrations.occ_disable_failed": [ - "occ app:disable sikertelen: %v (kimenet: %s)" - ], - "err.notify.hub_error": "hub hiba (%d): %s", - "note.offsite.fail_quota": "A távoli mentés nem fért el a tárhelykereten belül", - "note.offsite.fail_orphaned": "A távoli tárhely egy korábbi, már nem elérhető kulccsal készült", - "note.offsite.fail_no_repo": "A távoli tárhelyen nincs mentési adattár", - "note.offsite.fail_no_units": "Nem volt mit menteni: egyetlen kijelölt alkalmazásnak sem található mentése", - "note.offsite.fail_transport": "A távoli tárhely nem érhető el (hálózat vagy bejelentkezés)", - "note.offsite.fail_unknown": "A távoli mentés ismeretlen okból nem sikerült", - "note.offsite.fail_head": "A távoli mentés nem sikerült", - "note.offsite.whole_unit_gap": "(a teljes alkalmazás — nincs helyi mentési egysége)", - "note.offsite.no_local_unit": "Ezek az alkalmazások NEM kerültek be a távoli mentésbe, mert még nincs helyi mentési egységük: %s. A következő mentés általában már elkészíti — ha a második futás után is itt szerepelnek, szólj az üzemeltetőnek.", - "note.offsite.not_installed": "Ezek az alkalmazások ki vannak jelölve távoli mentésre, de nincsenek telepítve, ezért nem menthetők: %s. Ha már nincs rájuk szükséged, vedd ki a kijelölésüket a Távoli mentés oldalon.", - "note.offsite.quota_partial": "Figyelmeztetés: a tárhelykeret miatt %d alkalmazásnál csak konfiguráció- és adatbázis-mentés készült: %s.", - "note.offsite.quota_usage": "A távoli mentés a keret %d%%-át használja (%d/%d GB).", - "note.tier2.reason_manual": "kézi választás", - "note.tier2.reason_other_drive": "másik adatmeghajtó", - "note.tier2.label_internal_ssd": "belső SSD (rendszer)", - "note.tier2.reason_no_second": "nincs 2. adatmeghajtó — csak az adatbázis/konfiguráció fér a belső SSD-re; a nagy fájlokhoz 2. meghajtó kell", - "note.tier2.same_disk": "a kiválasztott cél ugyanazon a fizikai lemezen van", - "note.tier2.ssd_partial": "A belső SSD-n csak a konfiguráció, adatbázis és a kötelező adatok férnek el — a választható tartalom nem került másolásra.", - "note.tier2.no_space_ssd": "nincs elég hely a belső SSD-n — a nagy fájlok off-drive mentéséhez 2. meghajtó (vagy távoli tárhely) szükséges", - "note.tier2.no_other_drive": "nincs másik fizikai meghajtó — a 2. mentéshez 2. meghajtó szükséges", - "note.tier2.unit_preserved": "A fő meghajtón lévő adatcsomag hiányos volt, ezért a másodlagos másolatban meglévő, teljes csomagot megőriztük. A másolat adatcsomagja ezért régebbi, mint ez a mentés.", - "note.restore.interrupted": "A visszaállítás megszakadt (a doboz újraindult) — indítsd el újra.", - "note.undo.present": [ - "a korábbi állapot mentése megvan: " - ], - "note.undo.partial": [ - "a korábbi állapot mentése RÉSZBEN van meg — megvan: ", - "; HIÁNYZIK: " - ], - "note.undo.absent": [ - "a korábbi állapot mentését NEM találjuk a helyén (", - ")" - ], - "note.undo.none": "a korábbi állapotról nem készült menthető másolat", - "note.reconstitute.copy_latest": "legutóbbi", - "note.reconstitute.held": [ - "a(z) %s adatainak visszaállítása %s-kor megszakadt, és a korábbi állapotot sem sikerült visszatölteni. ", - "Az alkalmazás biztonsági okból leállítva marad, hogy az adatai ne sérüljenek tovább. Vedd fel velünk a kapcsolatot" - ], - "note.restore.failed": [ - "A visszaállítás sikertelen: " - ], - "note.restore.full_failed": [ - "A teljes visszaállítás sikertelen: " - ], - "note.restore.recover_failed": [ - "A helyreállítás sikertelen: " - ], - "note.restore.shares_failed": [ - "A megosztások visszaállítása sikertelen: " - ], - "note.restore.shares_recover_failed": [ - "A megosztások helyreállítása sikertelen: " - ], - "note.restore.unit_failed": [ - "Visszaállítás sikertelen: " - ], - "note.restore.file_failed": [ - "Fájl-visszaállítás sikertelen: " - ], - "note.restore.full_unit_failed": [ - "Teljes visszaállítás sikertelen: " - ], - "note.restore.recovered_files": [ - "A(z) ", - " hiányzó fájljai helyreállítva az élő adatok közé." - ], - "note.restore.shares_prepared": "A megosztások visszaállítása elkészült — most helyreállíthatod az élő adatok közé.", - "note.restore.at_scratch": " ellenőrző mappába", - "note.restore.at_scratch_named": [ - " ellenőrző mappába: " - ], - "note.restore.scratch_full": [ - "A(z) ", - " teljes mentése visszaállítva", - " — a saját fájljaiddal együtt. A meglévő adatok változatlanok." - ], - "note.restore.scratch_state": [ - "A(z) ", - " beállításai és adatbázisa visszaállítva", - ". A saját fájljaid (dokumentumok, képek, feltöltések) NEM kerültek vissza — ez az ellenőrző visszaállítás csak az alkalmazás beállításait és adatbázisát hozza vissza. ", - "Ha a fájljaidra van szükséged, indítsd el a „Teljes visszaállítás előkészítése” lépést ezen az oldalon. A meglévő adatok változatlanok." - ], - "note.restore.files_count": "%d fájl", - "note.restore.and_volumes": " és %d adatkötet", - "note.restore.and_database": " és az adatbázis", - "note.restore.volumes_count": "%d adatkötet", - "note.restore.the_database": "az adatbázis", - "note.restore.dumps_at": [ - " (mentés: ", - ")" - ], - "note.restore.reconstituted": "A(z) %s: %s visszaállítva%s — az alkalmazás újraindult.", - "note.restore.no_db_in_backup": " FIGYELEM: ennek az alkalmazásnak VAN adatbázisa, de a mentés nem tartalmazott adatbázis-mentést, ezért az adatbázis NEM állt vissza. A visszaállítás előtti állapot mentése megvan: %s", - "note.restore.no_db_at_all": " Ennek az alkalmazásnak nincs adatbázisa.", - "note.restore.all_files_present": "Minden vizsgált fájl megvan a helyén.", - "note.restore.files_from_second": "%s: %d fájl visszaállítva a másodlagos másolatból.", - "note.restore.scratch_incomplete": "A visszaállítási másolat nem teljes — a legutóbbi letöltés nem fejeződött be. Indítsd újra a teljes visszaállítás előkészítését.", - "note.unit_restore_data": "A(z) %s: %s visszaállítva — az alkalmazás újraindult.", - "note.unit_restore_settings_only": "A(z) %s: a beállítások visszaálltak — az alkalmazás újraindult. FIGYELEM: ez a mentés csak a beállításokat tartalmazta, adatot nem. Az alkalmazás adatai NEM álltak vissza ebből a mentésből.", - "note.unit_restore_counts_unknown": "A(z) %s visszaállítása lefutott — az alkalmazás újraindult. Ehhez a mentéshez nem tartozik mentési egység, ezért nem tudjuk megmondani, mi állt vissza belőle. Ellenőrizd az alkalmazásban, hogy megvannak-e az adataid.", - "note.unit_restore_none_returned": "A(z) %s: FIGYELEM — a mentés %d adatkötetet és %d adatbázis-mentést sorol fel, de egyik sem állt vissza. Az adataid változatlanok maradtak. Kérj segítséget, mielőtt újra próbálod.", - "note.tier2_no_coverage": "Ennek az alkalmazásnak az adatai nem ebből a másolatból állíthatók vissza — az alkalmazás nem állt le. Használd a Visszaállítás indítása gombot a Biztonsági mentés → Visszaállítás oldalon.", - "note.tier2_unit_available": "Ennek az alkalmazásnak az adatai nem fájlokban, hanem az alkalmazás saját adatbázisában és köteteiben vannak — az alkalmazás nem állt le. Ezeket a mellette lévő „Teljes visszaállítás a másolatból” gombbal tudod visszahozni ugyanerről a másolatról. Figyelem: az a művelet FELÜLÍRJA a jelenlegi adatokat, míg ez a gomb csak a hiányzó fájlokat pótolja.", - "note.tier2_unit_not_covered": "Az alkalmazás adatbázisa és belső kötetei nem tartoznak ebbe a visszaállításba.", - "note.tier2_unit_restore_source": "A visszaállítás forrása a második meghajtón lévő másolat volt (%s).", - "note.tier2_unit_restore_source_unproven": "A visszaállítás forrása a második meghajtón lévő másolat volt (%s — ez az utolsó mentési kísérlet ideje; azt nem tudjuk igazolni, hogy az sikeres volt).", - "note.tier2_unit_action_label": "Teljes visszaállítás a másolatból", - "note.tier2_unit_confirm_base": "Ez a művelet FELÜLÍRJA az alkalmazás jelenlegi adatait – az adatbázisát és a belső köteteit is – a második meghajtón lévő másolattal. Ami a másolat óta keletkezett, elveszik.", - "note.tier2_unit_confirm_date_fmt": " A másolat kelte: %s.", - "note.tier2_unit_confirm_date_unproven_fmt": " A másolat kelte: %s – ez az utolsó mentési kísérlet ideje, azt nem tudjuk igazolni, hogy sikeres volt.", - "note.tier2_unit_confirm_contrast": " A mellette lévő „Fájlok visszaállítása” ezzel szemben csak a hiányzó fájlokat pótolja, és semmit nem ír felül. Az alkalmazás a művelet idejére leáll.", - "note.tier2_unit_stale_clause": " FIGYELEM: ennek a másolatnak az adatcsomagja régebbi, mint a legutóbbi mentés — a fő meghajtón lévő csomag hiányos volt, ezért a meglévő, teljes másolatot megőriztük. A visszaállítás a fent megadott csomagot használja.", - "note.tier2_unit_stale_notice_fmt": "A másolat adatcsomagja régebbi, mint a legutóbbi mentés (%s): a fő meghajtón lévő csomag hiányos volt, ezért a meglévő, teljes másolatot megőriztük." + "_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.single": [ + "Telepített alkalmazás nem fut: " + ], + "alert.deadapp.single_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.", + "err.stacks.migracio_mar_folyamatban": "migráció már folyamatban", + "err.stacks.alkalmazas_nem_talalhato": "alkalmazás nem található: %s", + "err.stacks.ismeretlen_migracios_hatokor": "ismeretlen migrációs hatókör: %s", + "err.stacks.migracios_naplo_irasa": [ + "migrációs napló írása: %w" + ], + "err.stacks.biztonsagi_mentes_folyamatban_probald_ujra": "biztonsági mentés folyamatban, próbáld újra", + "err.stacks.az_rsync_nem_erheto_el_a": "az rsync nem érhető el a rendszeren", + "err.stacks.a_cel_es_a_forras_tarolo": "a cél és a forrás tároló megegyezik", + "err.stacks.a_celtarolo_nem_elerheto_vagy_nem": "a céltároló nem elérhető vagy nem választható", + "err.stacks.utkozes_a_celtarolon_mar_letezik_ezeknek": "ütközés a céltárolón — már létezik ezeknek az alkalmazásoknak az adata: %s", + "err.stacks.nincs_eleg_hely_a_celtarolon_kb": "nincs elég hely a céltárolón (kb. %d GB szükséges, %.1f GB szabad)", + "err.stacks.ismeretlen_migracios_fazis": "ismeretlen migrációs fázis: %q", + "err.stacks.alkalmazas_leallitasa_sikertelen": [ + "alkalmazás leállítása sikertelen (%s): %w" + ], + "err.stacks.masolas_sikertelen_appdata": [ + "másolás sikertelen (%s appdata): %w" + ], + "err.stacks.masolas_sikertelen_mentes": [ + "másolás sikertelen (%s mentés): %w" + ], + "err.stacks.ugyfel_adatok_osszefesulese_sikertelen": [ + "ügyfél-adatok összefésülése sikertelen: %w" + ], + "err.stacks.ellenorzes_sikertelen_appdata": [ + "ellenőrzés sikertelen (%s appdata): %w" + ], + "err.stacks.ellenorzes_sikertelen_mentes": [ + "ellenőrzés sikertelen (%s mentés): %w" + ], + "err.stacks.ugyfel_adatok_ellenorzese_sikertelen": [ + "ügyfél-adatok ellenőrzése sikertelen: %w" + ], + "err.stacks.ujratelepites_sikertelen": [ + "újratelepítés sikertelen (%s): %w" + ], + "err.stacks.forras_torlese_sikertelen_appdata": [ + "forrás törlése sikertelen (%s appdata): %w" + ], + "err.stacks.forras_torlese_sikertelen_mentes": [ + "forrás törlése sikertelen (%s mentés): %w" + ], + "err.stacks.forras_nevter_olvasasa_sikertelen": [ + "forrás névtér olvasása sikertelen: %w" + ], + "err.stacks.forras_torlese_sikertelen": [ + "forrás törlése sikertelen (%s): %w" + ], + "err.stacks.cleanup_gate_a_z_egyseg_nincs": "cleanup gate: a(z) %q egység nincs ellenőrizve (állapot=%s)", + "err.stacks.cleanup_gate_a_z_alkalmazas_nincs": "cleanup gate: a(z) %q alkalmazás nincs újratelepítve (állapot=%s)", + "err.stacks.az_alkalmazas_nem_indult_el_az": "az alkalmazás nem indult el az új tárhelyen", + "err.stacks.fuggoben_levo_atvitel_maradt_pl": "%d függőben lévő átvitel maradt, pl. %q", + "err.stacks.a_forrasfajlnak_nincs_azonos_masolata_a": "a forrásfájlnak nincs azonos másolata a célon: %s", + "err.stacks.az_aldomain_nem_lehet_ures": "az aldomain nem lehet üres", + "err.stacks.az_aldomain_csak_kisbetuket_szamokat_es": "az aldomain csak kisbetűket, számokat és kötőjelet tartalmazhat, és nem kezdődhet/végződhet kötőjellel", + "err.stacks.a_z_aldomain_foglalt_rendszer_szamara": "a(z) %q aldomain foglalt rendszer számára", + "err.stacks.a_z_aldomain_mar_hasznalatban_van": "a(z) %q aldomain már használatban van egy másik alkalmazásban", + "err.stacks.a_beallitasok_nem_erhetok_el": "a beállítások nem érhetők el", + "err.stacks.a_halozati_megosztas_nincs_bekapcsolva": "a hálózati megosztás nincs bekapcsolva", + "err.stacks.a_megosztas_szolgaltatas_inditasa_sikertelen": [ + "a megosztás szolgáltatás indítása sikertelen: %w" + ], + "err.stacks.a_megosztas_leallitasa_sikertelen": [ + "a megosztás leállítása sikertelen: %w" + ], + "err.stacks.a_z_alkalmazas_nem_tamogatja_az": "a(z) %q alkalmazás nem támogatja az email-küldést", + "err.stacks.a_z_alkalmazas_nincs_telepitve": "a(z) %q alkalmazás nincs telepítve", + "err.backup.a_biztonsagi_mentes_elott_nem_sikerult": [ + "a biztonsági mentés előtt nem sikerült felderíteni az adatbázisokat: %w" + ], + "err.backup.a_biztonsagi_mentes_konyvtara_nem_hozhato": [ + "a biztonsági mentés könyvtára nem hozható létre: %w" + ], + "err.backup.a_jelenlegi_adatbazis_biztonsagi_mentese_sikertelen": [ + "a jelenlegi adatbázis biztonsági mentése sikertelen (%s): %w — a visszaállítás nem indult el" + ], + "err.backup.a_visszavonas_elott_nem_sikerult_felderiteni": [ + "a visszavonás előtt nem sikerült felderíteni az adatbázisokat: %w" + ], + "err.backup.a_visszavonashoz_szukseges_mentes_nem_talalhato": [ + "a visszavonáshoz szükséges mentés nem található (%s): %w" + ], + "err.backup.a_z_adatbazis_taroloja_nem_talalhato": "a(z) %s adatbázis-tárolója nem található a visszavonáshoz", + "err.backup.a_korabbi_allapot_visszaallitasa_sikertelen": [ + "a korábbi állapot visszaállítása sikertelen (%s): %w" + ], + "err.backup.egy_masik_mentesi_visszaallitasi_muvelet_mar": "egy másik mentési/visszaállítási művelet már fut", + "err.backup.nincs_elokeszitett_teljes_visszaallitas_futtass_elobb": "nincs előkészített teljes visszaállítás — futtass előbb egy teljes visszaállítást", + "err.backup.a_teljes_visszaallitas_hianyos_nincs_meg": "a teljes visszaállítás hiányos (%s nincs meg) — futtass előbb egy teljes visszaállítást", + "err.backup.a_pillanatkepben_nincs_mentesi_egyseg_a": "a pillanatképben nincs mentési egység — a visszaállítás nem indítható", + "err.backup.a_biztonsagi_mentes_nem_talalhato_a": "a biztonsági mentés nem található a lemezen — a visszaállítás biztonsági okból nem indult el", + "err.backup.az_adatbazis_szolgaltatas_nem_azonosithato_a": "Az adatbázis-szolgáltatás nem azonosítható a(z) %s alkalmazásban — a visszaállítás biztonsági okból nem indult el.", + "err.backup.a_z_leallitasa_elotti_jelolo_nem": [ + "a(z) %s leállítása előtti jelölő nem menthető: %w" + ], + "err.backup.a_z_fajljainak_visszaallitasa_sikertelen": [ + "a(z) %s fájljainak visszaállítása sikertelen: %w" + ], + "err.backup.a_z_adatkotetenek_visszaallitasa_sikertelen": [ + "a(z) %s adatkötetének visszaállítása sikertelen: %w" + ], + "err.backup.a_z_adatbazis_szolgaltatasanak_inditasa_sikertelen": [ + "a(z) %s adatbázis-szolgáltatásának indítása sikertelen: %w" + ], + "err.backup.a_z_ujrainditasa_sikertelen_a_fajlok": [ + "a(z) %s újraindítása sikertelen a fájlok visszaállítása után: %w" + ], + "err.backup.offbox_nincs_pillanatkep_a_z_alkalmazashoz": "offbox: nincs pillanatkép a(z) %s alkalmazáshoz", + "err.backup.offbox_a_z_pillanatkep_merete_ismeretlen": "offbox: a(z) %s pillanatkép mérete ismeretlen", + "err.backup.a_z_pillanatkepeben_nincs_mentesi_egyseg": "a(z) %s pillanatképében nincs mentési egység — a visszaállítás nem indítható", + "err.backup.a_pillanatkepben_nincs_mentesi_egyseg_backups": "a pillanatképben nincs mentési egység (backups/primary/%s)", + "err.backup.a_pillanatkep_egy_utvonala_a_nevteren": "a pillanatkép egy útvonala a névtéren kívülre mutat: %s", + "err.backup.a_pillanatkep_egy_utvonala_ervenytelen": "a pillanatkép egy útvonala érvénytelen (..): %s", + "err.backup.nem_egyseg_utvonal_a_fenntartott_backups": "nem-egység útvonal a fenntartott backups zónában: %s", + "err.backup.a_z_nincs_telepitve_elobb_allitsd": "a(z) %s nincs telepítve — előbb állítsd helyre az alkalmazást, utána az adatokat", + "err.backup.a_z_helyreallitasa_sikertelen": [ + "a(z) %s helyreállítása sikertelen: %w" + ], + "err.backup.nincs_elerheto_adatmeghajto_a_visszaallitashoz": "nincs elérhető adatmeghajtó a visszaállításhoz", + "err.backup.a_tavoli_mentes_nincs_beallitva": "a távoli mentés nincs beállítva", + "err.backup.nincs_eleg_szabad_hely_a_visszaallitashoz": "Nincs elég szabad hely a visszaállításhoz (%s szükséges, %s szabad).", + "err.backup.nincs_visszaallithato_megosztas_mentes": [ + "nincs visszaállítható megosztás-mentés: %w" + ], + "err.backup.a_megosztasok_visszaallitasa_sikertelen": [ + "a megosztások visszaállítása sikertelen: %w: %s" + ], + "err.backup.a_mentesben_nincs_megosztas_leiro": [ + "a mentésben nincs megosztás-leíró: %w" + ], + "err.backup.a_megosztas_leiro_olvashatatlan": [ + "a megosztás-leíró olvashatatlan: %w" + ], + "err.backup.nincs_elokeszitett_visszaallitas_futtass_elobb_egy": "nincs előkészített visszaállítás — futtass előbb egy megosztás-visszaállítást", + "err.backup.a_z_megosztas_fajljainak_visszaallitasa_sikertelen": [ + "a(z) „%s” megosztás fájljainak visszaállítása sikertelen: %w" + ], + "err.backup.a_frissites_elotti_mentes_lefutott_de": "a frissítés előtti mentés lefutott, de nem jött létre visszaállítható másolat", + "err.backup.adatathelyezes_folyamatban_a_mentes_most_nem": "adatáthelyezés folyamatban — a mentés most nem indítható", + "err.backup.az_alkalmazas_meghajtoja_nem_hatarozhato_meg": "az alkalmazás meghajtója nem határozható meg", + "err.backup.az_alkalmazas_meghajtoja_nem_elerheto": "az alkalmazás meghajtója nem elérhető (%s)", + "err.backup.nincs_eleg_szabad_hely_a_menteshez": "nincs elég szabad hely a mentéshez a(z) %s meghajtón", + "err.backup.adatbazis_felderites_sikertelen": [ + "adatbázis-felderítés sikertelen: %w" + ], + "err.backup.adatbazis_mentes_sikertelen": [ + "adatbázis-mentés sikertelen (%s): %w" + ], + "err.backup.kotetmentes_sikertelen": [ + "kötetmentés sikertelen: %w" + ], + "err.backup.a_mentesi_egyseg_rogzitese_sikertelen": [ + "a mentési egység rögzítése sikertelen: %w" + ], + "err.backup.az_alkalmazas_mentesi_helye_nem_hatarozhato": "az alkalmazás mentési helye nem határozható meg", + "err.backup.nincs_masodlagos_fajlmasolat_ehhez_az_alkalmazashoz": "nincs másodlagos fájlmásolat ehhez az alkalmazáshoz", + "err.backup.a_masodlagos_meghajto_nincs_csatlakoztatva": "a másodlagos meghajtó nincs csatlakoztatva", + "err.backup.az_alkalmazas_meghajtoja_nincs_csatlakoztatva": "az alkalmazás meghajtója nincs csatlakoztatva", + "err.backup.az_alkalmazas_meghajtoja_le_van_szerelve": "az alkalmazás meghajtója le van szerelve", + "err.backup.a_2_mentes_regi_formatumu_futtass": "A 2. mentés régi formátumú — futtass előbb egy új másodlagos mentést.", + "err.backup.ennek_az_alkalmazasnak_az_adatai_nem": "ennek az alkalmazásnak az adatai nem ebből a másolatból állíthatók vissza", + "err.backup.a_masodlagos_masolatban_nincs_megnyithato_mentesi": "a másodlagos másolatban nincs megnyitható mentési egység ehhez az alkalmazáshoz", + "err.backup.fajlmasolas_sikertelen": [ + "fájlmásolás sikertelen: %w" + ], + "err.backup.fajl_visszaallitva_de_az_alkalmazas_ujrainditasa": [ + "%d fájl visszaállítva, de az alkalmazás újraindítása sikertelen: %w" + ], + "err.backup.ervenytelen_alkalmazasnev": "érvénytelen alkalmazásnév", + "err.backup.a_torles_utvonala_kivul_esik_az": "a törlés útvonala kívül esik az ellenőrző mappán", + "err.backup.a_masolat_torlese_nem_sikerult": [ + "a másolat törlése nem sikerült: %w" + ], + "err.backup.nincs_ilyen_ellenorzo_masolat": "nincs ilyen ellenőrző másolat", + "err.backup.az_offsite_tarolo_nincs_elarvult_allapotban": "az offsite tároló nincs elárvult állapotban", + "err.backup.halozati_tarolo_nem_lehet_a_2": "Hálózati tároló nem lehet a 2. mentés célja — a fájltulajdonos-adatok megőrzése ott nem garantálható.", + "err.web.ervenytelen_csatlakoztatasi_nev_csak_betuk_szamok": "érvénytelen csatlakoztatási név (csak betűk, számok, _ és - engedélyezett)", + "err.web.nem_tamogatott_fajlrendszer_ext4_vagy_xfs": "nem támogatott fájlrendszer: %q (ext4 vagy xfs)", + "err.web.formazas_sikertelen": [ + "formázás sikertelen: %w" + ], + "err.web.formazas_kesz_de_az_uj_fajlrendszer": "formázás kész, de az új fájlrendszer-azonosító nem feloldható — frissítsen és használja a Csatolás funkciót", + "err.web.csatlakoztatas_sikertelen": [ + "csatlakoztatás sikertelen: %w" + ], + "err.web.a_kivalasztott_meghajtohoz_nem_talalhato_fajlrendszer": "a kiválasztott meghajtóhoz nem található fájlrendszer-azonosító (csak fájlrendszerrel rendelkező meghajtó csatolható)", + "err.web.a_formazas_allapota_nem_kerdezheto_le": [ + "a formázás állapota nem kérdezhető le: %w" + ], + "err.web.formazas_sikertelen_2": "formázás sikertelen: %s", + "err.web.a_formazas_nem_indult_el_az": "a formázás nem indult el az eszközön (%s)", + "err.web.leszereles_visszavonasa_sikertelen": [ + "leszerelés visszavonása sikertelen: %w" + ], + "err.web.utemezhetoseg_visszaallitasa_sikertelen": [ + "ütemezhetőség visszaállítása sikertelen: %w" + ], + "err.web.regisztracio_sikertelen": [ + "regisztráció sikertelen: %w" + ], + "err.web.ez_az_egyetlen_hasznalhato_tarhely_a": "ez az egyetlen használható tárhely — a leszerelés megtagadva (előbb adj hozzá vagy állíts be másik alapértelmezett meghajtót)", + "err.web.nyilvantartas_frissitese_sikertelen": [ + "nyilvántartás frissítése sikertelen: %w" + ], + "err.web.a_meghajto_leszerelese_sikertelen": [ + "a meghajtó leszerelése sikertelen: %w" + ], + "err.settings.a_kiszolgalo_neve_nem_lehet_ures": "a kiszolgáló neve nem lehet üres", + "err.settings.a_kiszolgalo_neve_legfeljebb_15_karakter": "a kiszolgáló neve legfeljebb 15 karakter lehet", + "err.settings.a_kiszolgalo_neve_csak_betut_szamot": "a kiszolgáló neve csak betűt, számot, kötőjelet és aláhúzást tartalmazhat", + "err.settings.a_megosztas_neve_nem_lehet_ures": "a megosztás neve nem lehet üres", + "err.settings.a_megosztas_neve_legfeljebb_15_karakter": "a megosztás neve legfeljebb 15 karakter lehet", + "err.settings.a_megosztas_neve_nem_tartalmazhat_perjelet": "a megosztás neve nem tartalmazhat perjelet vagy pontot", + "err.settings.a_megosztas_neve_nem_kezdodhet_alahuzassal": "a megosztás neve nem kezdődhet aláhúzással — ezek a nevek a rendszernek vannak fenntartva", + "err.settings.a_megosztas_neve_csak_betut_szamot": "a megosztás neve csak betűt, számot, kötőjelet és aláhúzást tartalmazhat", + "err.settings.mar_letezik_nevu_megosztas": "már létezik „%s” nevű megosztás", + "err.settings.a_z_megosztas_a_rendszer_resze": "a(z) „%s” megosztás a rendszer része, nem törölhető", + "err.settings.nincs_nevu_megosztas": "nincs „%s” nevű megosztás", + "err.settings.a_halozati_tarhely_nem_lehet_a": "a hálózati tárhely nem lehet a rendszermentés célja: %s", + "err.integrations.nincs_kezelo_a_z_integraciohoz": "nincs kezelő a(z) %s integrációhoz", + "err.integrations.a_szolgaltato_alkalmazas_nincs_telepitve": "a szolgáltató alkalmazás (%s) nincs telepítve", + "err.integrations.a_szolgaltato_alkalmazas_nem_fut": "a szolgáltató alkalmazás (%s) nem fut", + "err.integrations.a_celalkalmazas_nincs_telepitve": "a célalkalmazás (%s) nincs telepítve", + "err.integrations.a_celalkalmazas_nem_fut": "a célalkalmazás (%s) nem fut", + "err.integrations.integracio_alkalmazasa_sikertelen": [ + "integráció alkalmazása sikertelen: %w" + ], + "err.integrations.integracio_allapotanak_mentese_sikertelen": [ + "integráció állapotának mentése sikertelen: %w" + ], + "err.integrations.szolgaltato_stack_nem_talalhato": "szolgáltató stack %q nem található", + "err.integrations.onlyoffice_jwt_secret_nincs_beallitva_telepitsd": "OnlyOffice JWT_SECRET nincs beállítva — telepítsd újra az alkalmazást", + "err.integrations.filebrowser_config_olvasasi_hiba": [ + "FileBrowser config olvasási hiba: %w" + ], + "err.integrations.config_irasi_hiba": [ + "config írási hiba: %w" + ], + "err.integrations.config_atnevezesi_hiba": [ + "config átnevezési hiba: %w" + ], + "err.integrations.config_olvasasi_hiba": [ + "config olvasási hiba: %w" + ], + "err.integrations.onlyoffice_jwt_secret_nincs_beallitva": "OnlyOffice JWT_SECRET nincs beállítva", + "err.selfupdate.hianyos_registry_hitelesito_adatok_felhasznalonev_es": "hiányos registry hitelesítő adatok (felhasználónév és token együtt szükséges)", + "err.selfupdate.frissites_mar_folyamatban": "Frissítés már folyamatban", + "err.selfupdate.dev_verzio_nem_frissitheto": "Dev verzió nem frissíthető", + "err.selfupdate.mentes_fut_probalja_kesobb": "Mentés fut, próbálja később", + "err.selfupdate.a_frissites_nem_erheto_el_nincs": "A frissítés nem érhető el (nincs gazda-ügynök)", + "err.selfupdate.nincs_elerheto_frissites": "Nincs elérhető frissítés", + "err.selfupdate.docker_pull_hianyos_registry_hitelesito_adatok": "docker pull: hiányos registry hitelesítő adatok (felhasználónév és token együtt szükséges)", + "err.appexport.visszafejtes_sikertelen": [ + "visszafejtés sikertelen: %w" + ], + "err.appexport.a_z_kotet_adata_hianyzik_a": "a(z) %q kötet adata hiányzik a csomagból", + "err.appexport.a_z_adatkonyvtar_tartalma_hianyzik_a": "a(z) %q adatkönyvtár tartalma hiányzik a csomagból", + "err.appexport.ket_adatkonyvtar_azonos_nevvel_vegzodik_es": "két adatkönyvtár azonos névvel végződik (%q: %s és %s) — a csomag nem tudná megkülönböztetni őket", + "err.appexport.a_mentes_nem_tartalmaz_alkalmazasadatot_0": "a mentés nem tartalmaz alkalmazásadatot (0 adatkönyvtár, 0 kötet egy adattárolós alkalmazásnál)", + "err.appexport.jelszo_hibas_vagy_a_fajl_serult": "jelszó hibás vagy a fájl sérült", + "err.web.a_meghajtok_listaja_nem_olvashato": "a meghajtók listája nem olvasható", + "err.web.a_meghajto_nem_talalhato": "a meghajtó nem található", + "err.web.ez_a_mappa_nem_oszthato_meg": "Ez a mappa nem osztható meg.", + "err.notify.hub_nem_konfiguralt": "hub nem konfigurált", + "err.notify.hub_elerhetetlen": [ + "hub elérhetetlen: %w" + ], + "err.stacks.field_required_password": "a(z) %q mező kitöltése kötelező — használja a Generálás gombot vagy írjon be egy jelszót", + "err.stacks.field_required": "a(z) %q (%s) mező kitöltése kötelező", + "err.backup.offsite_over_quota": "A távoli mentés túllépte a tárhelykeretet (%d/%d GB) — törölj régi mentéseket vagy kérj nagyobb keretet.", + "err.stacks.not_enough_memory": [ + "Nincs elég memória az alkalmazás telepítéséhez. ", + "Szükséges: %d MB, Elérhető: %d MB ", + "(összesen: %d MB, ebből %d MB használt, %d MB rendszer számára fenntartva)" + ], + "err.stacks.memory_overcommit_warning": [ + "Az alkalmazások csúcsterhelése meghaladhatja a rendelkezésre álló memóriát. ", + "Normál használat mellett ez nem okoz problémát." + ], + "err.backup.not_installed_known_drive": [ + "a(z) %s nincs telepítve, ezért nincs hová visszaállítani az adatait. ", + "A mentése szerint az adatai itt voltak: %s. Telepítsd újra az alkalmazást (Alkalmazások) ", + "ugyanerre a helyre, utána ez a visszaállítás működni fog" + ], + "err.backup.not_installed": [ + "a(z) %s nincs telepítve, ezért nincs hová visszaállítani az adatait — ", + "telepítsd újra az alkalmazást (Alkalmazások), utána ez a visszaállítás működni fog" + ], + "err.backup.no_data_root": [ + "a(z) %s telepítve van, de a vezérlő nem tudja megállapítani, hová tartoznak az adatai ", + "(nincs beállítva rendszer-adatterület). Ellenőrizd a tárhely beállításait (Tárhely), utána indítsd újra a visszaállítást" + ], + "err.backup.no_registered_drive": [ + "nincs regisztrált adatmeghajtó, ezért nincs hová visszaállítani — ", + "a meghajtók megvannak, csak újra kell csatolni őket a Tárhely → Meghajtók oldalon, utána ", + "ez a visszaállítás működni fog" + ], + "err.backup.db_restore_and_rollback_failed": [ + "a(z) %s adatbázisának visszaállítása sikertelen, és a korábbi állapot visszatöltése sem sikerült. ", + "Az alkalmazást biztonsági okból LEÁLLÍTVA hagytuk, hogy az adatai ne sérüljenek tovább. ", + "Vedd fel velünk a kapcsolatot — %s" + ], + "err.backup.db_restore_failed_rolled_back": [ + "a(z) %s adatbázisának visszaállítása sikertelen — az adataid visszakerültek a visszaállítás előtti állapotba, ", + "az alkalmazás fut tovább. Ha újra megpróbálnád, előbb vedd fel velünk a kapcsolatot" + ], + "err.integrations.onlyoffice_no_subdomain": "OnlyOffice aldomain nem ismert", + "err.integrations.occ_failed": [ + "occ parancs sikertelen (%s): %v (kimenet: %s)" + ], + "err.integrations.occ_disable_failed": [ + "occ app:disable sikertelen: %v (kimenet: %s)" + ], + "err.notify.hub_error": "hub hiba (%d): %s", + "note.offsite.fail_quota": "A távoli mentés nem fért el a tárhelykereten belül", + "note.offsite.fail_orphaned": "A távoli tárhely egy korábbi, már nem elérhető kulccsal készült", + "note.offsite.fail_no_repo": "A távoli tárhelyen nincs mentési adattár", + "note.offsite.fail_no_units": "Nem volt mit menteni: egyetlen kijelölt alkalmazásnak sem található mentése", + "note.offsite.fail_transport": "A távoli tárhely nem érhető el (hálózat vagy bejelentkezés)", + "note.offsite.fail_unknown": "A távoli mentés ismeretlen okból nem sikerült", + "note.offsite.fail_head": "A távoli mentés nem sikerült", + "note.offsite.whole_unit_gap": "(a teljes alkalmazás — nincs helyi mentési egysége)", + "note.offsite.no_local_unit": "Ezek az alkalmazások NEM kerültek be a távoli mentésbe, mert még nincs helyi mentési egységük: %s. A következő mentés általában már elkészíti — ha a második futás után is itt szerepelnek, szólj az üzemeltetőnek.", + "note.offsite.not_installed": "Ezek az alkalmazások ki vannak jelölve távoli mentésre, de nincsenek telepítve, ezért nem menthetők: %s. Ha már nincs rájuk szükséged, vedd ki a kijelölésüket a Távoli mentés oldalon.", + "note.offsite.quota_partial": "Figyelmeztetés: a tárhelykeret miatt %d alkalmazásnál csak konfiguráció- és adatbázis-mentés készült: %s.", + "note.offsite.quota_usage": "A távoli mentés a keret %d%%-át használja (%d/%d GB).", + "note.tier2.reason_manual": "kézi választás", + "note.tier2.reason_other_drive": "másik adatmeghajtó", + "note.tier2.label_internal_ssd": "belső SSD (rendszer)", + "note.tier2.reason_no_second": "nincs 2. adatmeghajtó — csak az adatbázis/konfiguráció fér a belső SSD-re; a nagy fájlokhoz 2. meghajtó kell", + "note.tier2.same_disk": "a kiválasztott cél ugyanazon a fizikai lemezen van", + "note.tier2.ssd_partial": "A belső SSD-n csak a konfiguráció, adatbázis és a kötelező adatok férnek el — a választható tartalom nem került másolásra.", + "note.tier2.no_space_ssd": "nincs elég hely a belső SSD-n — a nagy fájlok off-drive mentéséhez 2. meghajtó (vagy távoli tárhely) szükséges", + "note.tier2.no_other_drive": "nincs másik fizikai meghajtó — a 2. mentéshez 2. meghajtó szükséges", + "note.tier2.unit_preserved": "A fő meghajtón lévő adatcsomag hiányos volt, ezért a másodlagos másolatban meglévő, teljes csomagot megőriztük. A másolat adatcsomagja ezért régebbi, mint ez a mentés.", + "note.restore.interrupted": "A visszaállítás megszakadt (a doboz újraindult) — indítsd el újra.", + "note.undo.present": [ + "a korábbi állapot mentése megvan: " + ], + "note.undo.partial": [ + "a korábbi állapot mentése RÉSZBEN van meg — megvan: ", + "; HIÁNYZIK: " + ], + "note.undo.absent": [ + "a korábbi állapot mentését NEM találjuk a helyén (", + ")" + ], + "note.undo.none": "a korábbi állapotról nem készült menthető másolat", + "note.reconstitute.copy_latest": "legutóbbi", + "note.reconstitute.held": [ + "a(z) %s adatainak visszaállítása %s-kor megszakadt, és a korábbi állapotot sem sikerült visszatölteni. ", + "Az alkalmazás biztonsági okból leállítva marad, hogy az adatai ne sérüljenek tovább. Vedd fel velünk a kapcsolatot" + ], + "note.restore.failed": [ + "A visszaállítás sikertelen: " + ], + "note.restore.full_failed": [ + "A teljes visszaállítás sikertelen: " + ], + "note.restore.recover_failed": [ + "A helyreállítás sikertelen: " + ], + "note.restore.shares_failed": [ + "A megosztások visszaállítása sikertelen: " + ], + "note.restore.shares_recover_failed": [ + "A megosztások helyreállítása sikertelen: " + ], + "note.restore.unit_failed": [ + "Visszaállítás sikertelen: " + ], + "note.restore.file_failed": [ + "Fájl-visszaállítás sikertelen: " + ], + "note.restore.full_unit_failed": [ + "Teljes visszaállítás sikertelen: " + ], + "note.restore.recovered_files": [ + "A(z) ", + " hiányzó fájljai helyreállítva az élő adatok közé." + ], + "note.restore.shares_prepared": "A megosztások visszaállítása elkészült — most helyreállíthatod az élő adatok közé.", + "note.restore.at_scratch": " ellenőrző mappába", + "note.restore.at_scratch_named": [ + " ellenőrző mappába: " + ], + "note.restore.scratch_full": [ + "A(z) ", + " teljes mentése visszaállítva", + " — a saját fájljaiddal együtt. A meglévő adatok változatlanok." + ], + "note.restore.scratch_state": [ + "A(z) ", + " beállításai és adatbázisa visszaállítva", + ". A saját fájljaid (dokumentumok, képek, feltöltések) NEM kerültek vissza — ez az ellenőrző visszaállítás csak az alkalmazás beállításait és adatbázisát hozza vissza. ", + "Ha a fájljaidra van szükséged, indítsd el a „Teljes visszaállítás előkészítése” lépést ezen az oldalon. A meglévő adatok változatlanok." + ], + "note.restore.files_count": "%d fájl", + "note.restore.and_volumes": " és %d adatkötet", + "note.restore.and_database": " és az adatbázis", + "note.restore.volumes_count": "%d adatkötet", + "note.restore.the_database": "az adatbázis", + "note.restore.dumps_at": [ + " (mentés: ", + ")" + ], + "note.restore.reconstituted": "A(z) %s: %s visszaállítva%s — az alkalmazás újraindult.", + "note.restore.no_db_in_backup": " FIGYELEM: ennek az alkalmazásnak VAN adatbázisa, de a mentés nem tartalmazott adatbázis-mentést, ezért az adatbázis NEM állt vissza. A visszaállítás előtti állapot mentése megvan: %s", + "note.restore.no_db_at_all": " Ennek az alkalmazásnak nincs adatbázisa.", + "note.restore.all_files_present": "Minden vizsgált fájl megvan a helyén.", + "note.restore.files_from_second": "%s: %d fájl visszaállítva a másodlagos másolatból.", + "note.restore.scratch_incomplete": "A visszaállítási másolat nem teljes — a legutóbbi letöltés nem fejeződött be. Indítsd újra a teljes visszaállítás előkészítését.", + "note.unit_restore_data": "A(z) %s: %s visszaállítva — az alkalmazás újraindult.", + "note.unit_restore_settings_only": "A(z) %s: a beállítások visszaálltak — az alkalmazás újraindult. FIGYELEM: ez a mentés csak a beállításokat tartalmazta, adatot nem. Az alkalmazás adatai NEM álltak vissza ebből a mentésből.", + "note.unit_restore_counts_unknown": "A(z) %s visszaállítása lefutott — az alkalmazás újraindult. Ehhez a mentéshez nem tartozik mentési egység, ezért nem tudjuk megmondani, mi állt vissza belőle. Ellenőrizd az alkalmazásban, hogy megvannak-e az adataid.", + "note.unit_restore_none_returned": "A(z) %s: FIGYELEM — a mentés %d adatkötetet és %d adatbázis-mentést sorol fel, de egyik sem állt vissza. Az adataid változatlanok maradtak. Kérj segítséget, mielőtt újra próbálod.", + "note.tier2_no_coverage": "Ennek az alkalmazásnak az adatai nem ebből a másolatból állíthatók vissza — az alkalmazás nem állt le. Használd a Visszaállítás indítása gombot a Biztonsági mentés → Visszaállítás oldalon.", + "note.tier2_unit_available": "Ennek az alkalmazásnak az adatai nem fájlokban, hanem az alkalmazás saját adatbázisában és köteteiben vannak — az alkalmazás nem állt le. Ezeket a mellette lévő „Teljes visszaállítás a másolatból” gombbal tudod visszahozni ugyanerről a másolatról. Figyelem: az a művelet FELÜLÍRJA a jelenlegi adatokat, míg ez a gomb csak a hiányzó fájlokat pótolja.", + "note.tier2_unit_not_covered": "Az alkalmazás adatbázisa és belső kötetei nem tartoznak ebbe a visszaállításba.", + "note.tier2_unit_restore_source": "A visszaállítás forrása a második meghajtón lévő másolat volt (%s).", + "note.tier2_unit_restore_source_unproven": "A visszaállítás forrása a második meghajtón lévő másolat volt (%s — ez az utolsó mentési kísérlet ideje; azt nem tudjuk igazolni, hogy az sikeres volt).", + "note.tier2_unit_action_label": "Teljes visszaállítás a másolatból", + "note.tier2_unit_confirm_base": "Ez a művelet FELÜLÍRJA az alkalmazás jelenlegi adatait – az adatbázisát és a belső köteteit is – a második meghajtón lévő másolattal. Ami a másolat óta keletkezett, elveszik.", + "note.tier2_unit_confirm_date_fmt": " A másolat kelte: %s.", + "note.tier2_unit_confirm_date_unproven_fmt": " A másolat kelte: %s – ez az utolsó mentési kísérlet ideje, azt nem tudjuk igazolni, hogy sikeres volt.", + "note.tier2_unit_confirm_contrast": " A mellette lévő „Fájlok visszaállítása” ezzel szemben csak a hiányzó fájlokat pótolja, és semmit nem ír felül. Az alkalmazás a művelet idejére leáll.", + "note.tier2_unit_stale_clause": " FIGYELEM: ennek a másolatnak az adatcsomagja régebbi, mint a legutóbbi mentés — a fő meghajtón lévő csomag hiányos volt, ezért a meglévő, teljes másolatot megőriztük. A visszaállítás a fent megadott csomagot használja.", + "note.tier2_unit_stale_notice_fmt": "A másolat adatcsomagja régebbi, mint a legutóbbi mentés (%s): a fő meghajtón lévő csomag hiányos volt, ezért a meglévő, teljes másolatot megőriztük.", + "event.health_critical": "Rendszer állapot kritikus (volt: %s)", + "event.health_degraded": "Rendszer állapot romlott (volt: %s)", + "event.health_recovered": "Rendszer állapot helyreállt: %s (volt: %s)", + "event.db_dump_completed": "Adatbázis mentés elkészült", + "event.controller_updated": "Controller frissítve: %s → %s", + "event.controller_update_failed": "Controller frissítés sikertelen: %s → %s", + "event.controller_started": "Controller elindult (%s)", + "event.storage_disconnected": "Meghajtó váratlanul leválasztva: %s", + "event.backup_target_absent": "A rendszermentés meghajtója nem érhető el: %s (%s)", + "event.backup_target_restored": "A rendszermentés meghajtója újra elérhető: %s (%s)", + "event.storage_reconnected": "Meghajtó újra csatlakoztatva: %s", + "event.app_deployed": "Alkalmazás telepítve: %s", + "event.app_deploy_started": "Alkalmazás telepítése elindult: %s", + "event.app_deploy_failed": "Alkalmazás telepítése nem sikerült: %s", + "event.app_removed": "Alkalmazás eltávolítva: %s", + "event.crossdrive_completed": "Másodlagos mentés elkészült: %s", + "event.crossdrive_failed": "Másodlagos mentés sikertelen: %s", + "event.disaster_recovery_started": "Katasztrófa helyreállítás elindítva (%d alkalmazás)", + "event.disaster_recovery_completed": "Katasztrófa helyreállítás befejezve (%d sikeres, %d sikertelen)" }