diff --git a/README.md b/README.md index aeb9eff..ccb4b70 100644 --- a/README.md +++ b/README.md @@ -32,10 +32,12 @@ setup. orphaned temporary keys are left on remote hosts. - **Scriptable CLI:** All core features are available as command-line arguments, making Keymaster perfect for automation. -- **Multi-Language Support:** The TUI is fully internationalized, with support - for English, German, and even Old English. - **Flexible Backend:** Start with the default zero-config SQLite database, and migrate to PostgreSQL or MySQL as your needs grow. +- **Multi-Language Support:** The TUI is fully internationalized. We are actively + looking for translators! You can see the current status and contribute here: + +[![Translation status](https://weblate.stargazer.at/widget/keymaster/multi-auto.svg)](https://weblate.stargazer.at/engage/keymaster/) ## The Interface @@ -151,6 +153,7 @@ keymaster import /path/to/authorized_keys ```bash keymaster export-ssh-client-config ~/.ssh/config +``` - **Database Management:** @@ -232,6 +235,16 @@ Keymaster is different. It's built on a simple premise: It's designed for sysadmins and developers who want a straightforward, reliable way to control SSH access without the overhead. It's powerful enough for a fleet but simple enough for a home lab. +## Contributing + +Keymaster is an open-source project, and contributions are always welcome! Whether it's reporting a bug, submitting a feature request, or writing code, we appreciate your help. + +We are particularly looking for help with **translations**. If you speak a language other than English, you can easily contribute through our [Weblate project](https://weblate.stargazer.at/engage/keymaster/). + +Please read our [**Contributing Guidelines**](CONTRIBUTING.md) for details on our code conventions and the development process. All contributors are expected to follow our [**Code of Conduct**](CODE_OF_CONDUCT.md). + +--- + ## License This project is licensed under the MIT License - see the `LICENSE` file for details. For a detailed list of third-party dependencies and their license texts, please see the `NOTICE.md` file. diff --git a/internal/i18n/locales/active.art-x-ang.yaml b/internal/i18n/locales/active.art-x-ang.yaml index 81029d4..55817e5 100644 --- a/internal/i18n/locales/active.art-x-ang.yaml +++ b/internal/i18n/locales/active.art-x-ang.yaml @@ -22,7 +22,7 @@ dashboard.title: "Cǣġhealdend" dashboard.subtitle: "Ān unġesandboda SSH cǣġgerefa þe þæt ƿeorc dēþ." dashboard.system_status: "Systemstede" dashboard.deployment_status: "Sendungstede" -dashboard.hosts_current_key: " Ƿeardas mid nīƿlīċum cǣġe: %d" +dashboard.hosts_current_key: "Ƿeardas mid nīƿlīċum cǣġe: %d" dashboard.hosts_past_keys: "Ƿeardas mid ealdum cǣġ(um): %d" dashboard.system_key.not_generated: "Nā Ġeċeafd" dashboard.system_key.active: "Dǣdlīċ (Endebyrdnes #%d)" @@ -40,13 +40,13 @@ language.select: "Ċēos sprǣċe:" language.help: "ūp/niþer: faran enter: ċēosan q/esc: eftfaran" # Account form translations -account_form.username_label: "Brūcendnama: " +account_form.username_label: "Brūcendnama:" account_form.username_placeholder: "brūcend" -account_form.hostname_label: "Ƿeardnama: " +account_form.hostname_label: "Ƿeardnama:" account_form.hostname_placeholder: "ƿƿƿ.bisene.com" -account_form.label_label: "Mearcung (ġif þū ƿilt): " +account_form.label_label: "Mearcung (ġif þū ƿilt):" account_form.label_placeholder: "ƿyrhtaƿeb-01" -account_form.tags_label: "Mearcan (mid commum): " +account_form.tags_label: "Mearcan (mid commum):" account_form.tags_placeholder: "ġesċeaft:db,stede:nyc" account_form.edit_title: "Rǣdmann Rihtan" account_form.add_title: "Nīƿne Rǣdmann Eċan" @@ -63,11 +63,12 @@ accounts.empty: "Nān rǣdmann ġefunden. Cnuc 'a' tō eċenne." accounts.empty_filtered: "Nān rǣdmann þīnum sīfe ġefēreþ." accounts.filtering: "Sīfe: %s▮" accounts.filter_active: "Sīfe: %s (cnuc 'esc' tō clǣnsiġenne)" -accounts.filter_hint: "Cnuc / tō sīfenne..." -accounts.footer: "(a)eċan (e)rihtan (d)ādilgian (t)ƿendan (v)ġetrēoƿian (i)inbringan (q)forlǣtan" +accounts.filter_hint: "Cnuc / tō sīfenne…" +accounts.footer: "(a)eċan (e)rihtan (d)ādilgian (t)ƿendan (v)ġetrēoƿian (i)inbringan + (q)forlǣtan" accounts.list_title: "Rǣdmenn" accounts.status.modified_success: "Ġesundfullīċe ġerihted rǣdmann." -accounts.status.trust_attempt: "Fandian tō ġetrēoƿiġenne ƿeard %s..." +accounts.status.trust_attempt: "Fandian tō ġetrēoƿiġenne ƿeard %s…" accounts.status.import_assigned: "Ġeseted %d inbrohtne cǣgan." accounts.status.import_skipped_assign: "Inbrohte %d nīƿe cǣgan būtan settunge." accounts.status.delete_cancelled: "Ādilgung ācierred." @@ -77,16 +78,18 @@ accounts.status.verify_success: "Ġesundfullīċe ġetrēoƿod ƿeardcǣġ for % accounts.status.import_fail: "Inbring mislāmp: %v" accounts.status.import_no_new: "Nān nīƿ cǣġ ġefunden tō inbrengenne. Oferēode %d ġelīċe." accounts.status.toggle_success: "Ġeƿended stede for: %s" -accounts.status.verify_start: "Ġefæstnian ƿeardcǣġ for %s..." -accounts.status.import_start: "Inbringende cǣgan fram %s..." +accounts.status.verify_start: "Ġefæstnian ƿeardcǣġ for %s…" +accounts.status.import_start: "Inbringende cǣgan fram %s…" accounts.import_confirm.title: "✨ Feorrancumende Inbringes Ġetrēoƿung" accounts.import_confirm.found_keys: "Ġefunden %d nīƿe opene cǣgan on þǣm feorran ƿearde:" accounts.import_confirm.question: "Settan þās %d nīƿan cǣgan tō þissum rǣdmenn? (ġ/n)" accounts.delete_confirm.title: "ðŸ—'ï¸ Ādilgunges Ġetrēoƿung" -accounts.delete_confirm.question: "Eart þū ġeƿiss þæt þū ƿilt ādilgian þone rǣdmann\n\n%s?" +accounts.delete_confirm.question: "Eart þū ġeƿiss þæt þū ƿilt ādilgian þone rǣdmann + %s?" accounts.delete_confirm.yes: "Ġēa, Ādilgian" accounts.delete_confirm.no: "Nā, Ācierran" -accounts.delete_confirm.help: "(ƿinstra/sƿīðra tō farenne, enter tō ġetrēoƿiġenne, esc tō ācierrenne)" +accounts.delete_confirm.help: "(ƿinstra/sƿīðra tō farenne, enter tō ġetrēoƿiġenne, + esc tō ācierrenne)" accounts.tags: "Mearcan: %s" # Public keys overview translations @@ -96,8 +99,9 @@ public_keys.empty: "Nān open cǣġ ġefunden. Cnuc 'a' tō eċenne." public_keys.empty_filtered: "Nān cǣġ þīnum sīfe ġefēreþ." public_keys.filtering: "Sīfe: %s▮" public_keys.filter_active: "Sīfe: %s (cnuc 'esc' tō clǣnsiġenne)" -public_keys.filter_hint: "Cnuc / tō sīfenne..." -public_keys.footer: "(a)eċan (c)ġeċīeġan (d)ādilgian (g)eorþlīċ ƿendan (u)brūcung (q)forlǣtan" +public_keys.filter_hint: "Cnuc / tō sīfenne…" +public_keys.footer: "(a)eċan (c)ġeċīeġan (d)ādilgian (g)eorþlīċ ƿendan (u)brūcung + (q)forlǣtan" public_keys.list_title: "Opene Cǣgan" public_keys.detail_comment: "Smeaung: %s" public_keys.detail_algorithm: "Cræft: %s" @@ -112,11 +116,14 @@ public_keys.status.toggle_success: "Ġeƿended eorþlīċ stede for cǣġ: %s" public_keys.status.copy_success: "Ġeċīeġed cǣġ '%s' tō clipbordum." public_keys.status.copy_failed: "Mislāmp tō ġeċīeġenne tō clipbordum: %s" public_keys.delete_confirm.title: "ðŸ—'ï¸ Ādilgunges Ġetrēoƿung" -public_keys.delete_confirm.question: "Eart þū ġeƿiss þæt þū ƿilt ādilgian þone openan cǣġ\n\n%s?" -public_keys.delete_confirm.warning: "Þis ƿile hit āfeormianne fram eallum rǣdmannum þe hit is ġeseted tō." +public_keys.delete_confirm.question: "Eart þū ġeƿiss þæt þū ƿilt ādilgian þone openan + cǣġ\n\n%s?" +public_keys.delete_confirm.warning: "Þis ƿile hit āfeormianne fram eallum rǣdmannum + þe hit is ġeseted tō." public_keys.delete_confirm.yes: "Ġēa, Ādilgian" public_keys.delete_confirm.no: "Nā, Ācierran" -public_keys.delete_confirm.help: "(ƿinstra/sƿīðra tō farenne, enter tō ġetrēoƿiġenne, esc tō ācierrenne)" +public_keys.delete_confirm.help: "(ƿinstra/sƿīðra tō farenne, enter tō ġetrēoƿiġenne, + esc tō ācierrenne)" public_keys.usage_report.title: "Cǣġes Brūcunges Ġeƿrit for: %s" public_keys.usage_report.not_assigned: "Þes cǣġ nis ġeseted tō nānum rǣdmannum." public_keys.usage_report.assigned_to: "Þes cǣġ is ġeseted tō þissum rǣdmannum:" @@ -125,19 +132,25 @@ public_keys.usage_report.help: "(esc oþþe q tō eftfarenne)" # Public key form translations public_key_form.add_title: "Nīƿne Openan Cǣġ Eċan" public_key_form.prompt: "Clīfan Openan Cǣġ: " -public_key_form.placeholder: "ssh-ed25519 AAAA... brūcend@ƿeard" -public_key_form.checkbox_unchecked: "[ ] Settan sƿā Eorþlīċne Cǣġ (bēoþ ġesended tō eallum rǣdmannum)" -public_key_form.checkbox_checked: "[x] Settan sƿā Eorþlīċne Cǣġ (bēoþ ġesended tō eallum rǣdmannum)" -public_key_form.help: "(tab tō farenne, space/enter tō ƿendenne, enter on innġange tō sendenne, esc tō ācierrenne)" +public_key_form.placeholder: "ssh-ed25519 AAAA… brūcend@ƿeard" +public_key_form.checkbox_unchecked: "[ ] Settan sƿā Eorþlīċne Cǣġ (bēoþ ġesended tō + eallum rǣdmannum)" +public_key_form.checkbox_checked: "[x] Settan sƿā Eorþlīċne Cǣġ (bēoþ ġesended tō + eallum rǣdmannum)" +public_key_form.help: "(tab tō farenne, space/enter tō ƿendenne, enter on innġange + tō sendenne, esc tō ācierrenne)" public_key_form.error: "Gedƿola: %v" -public_key_form.info: "Clīfan rihte SSH openan cǣġ. Þū miht settan hit sƿā eorþlīċ tō sendenne tō eallum rǣdmannum." +public_key_form.info: "Clīfan rihte SSH openan cǣġ. Þū miht settan hit sƿā eorþlīċ + tō sendenne tō eallum rǣdmannum." # Assign keys view translations -assign_keys.help_bar_accounts: "j/k ūp/niþer: faran enter: ċēosan /: sēċan esc: eftfaran q: forlǣtan" -assign_keys.help_bar_keys: "j/k ūp/niþer: faran space: ƿendan /: sēċan esc: eftfaran q: forlǣtan" +assign_keys.help_bar_accounts: "j/k ūp/niþer: faran enter: ċēosan /: sēċan esc: + eftfaran q: forlǣtan" +assign_keys.help_bar_keys: "j/k ūp/niþer: faran space: ƿendan /: sēċan esc: + eftfaran q: forlǣtan" assign_keys.accounts_title: "Rǣdmenn" assign_keys.no_accounts: "Nān rǣdmann ġefunden. Cnuc 'a' tō eċenne." -assign_keys.search_hint: "Cnuc / tō sēċenne..." +assign_keys.search_hint: "Cnuc / tō sēċenne…" assign_keys.keys_title: "Cǣgan Settan for: %s" assign_keys.keys_title_short: "Cǣgan" assign_keys.no_keys: "Nān open cǣġ ġefunden. Cnuc 'a' tō eċenne." @@ -160,33 +173,45 @@ assign_keys.global_marker: "🌠" # Rotate key translations rotate_key.title: "Systemcǣġes Ġerihte" rotate_key.confirm_rotate_title: "âš ï¸ Ġetrēoƿ Systemcǣġes Ġehƿyrfunge" -rotate_key.confirm_rotate_question: "Eart þū ġeƿiss þæt þū ƿilt ġehƿyrfan þone systemcǣġ?\n\nÞis ƿile ċeafiġan nīƿne cǣġ.\n\nÞis dǣd hrīneþ EALLE ġerihtode ƿeardas.\nÞū sċeoldest sendan þone nīƿan cǣġ tō þīnum flote hrædlīċe æfter ġehƿyrfunge.\n\nFara forþ ġif þū ƿāst hƿæt þū dēst!" +rotate_key.confirm_rotate_question: "Eart þū ġeƿiss þæt þū ƿilt ġehƿyrfan þone systemcǣġ?\n\ + \nÞis ƿile ċeafiġan nīƿne cǣġ.\n\nÞis dǣd hrīneþ EALLE ġerihtode ƿeardas.\nÞū sċeoldest + sendan þone nīƿan cǣġ tō þīnum flote hrædlīċe æfter ġehƿyrfunge.\n\nFara forþ ġif + þū ƿāst hƿæt þū dēst!" rotate_key.confirm_generate_title: "âš™ï¸ Ċeafian Fyrste Systemcǣġ" -rotate_key.confirm_generate_question: "Nān Cǣġhealdend systemcǣġ ġefunden.\n\nÞes cǣġ is nēodbeþearf for Cǣġhealdend tō ġebindenne tō ġerihtode ƿeardas.\n\nƿilt þū ċeafiġan ānne nū?" -rotate_key.checking: "Sēċende for ġesittendan systemcǣġe..." -rotate_key.generating: "Ċeafiġende nīƿ ed25519 cǣġġepar, ġebīd þē..." -rotate_key.generated: "✅ Ġesundfullīċe ġeċeafod and ġehealden systemcǣġ mid endebyrdnesse #%d." +rotate_key.confirm_generate_question: "Nān Cǣġhealdend systemcǣġ ġefunden.\n\nÞes + cǣġ is nēodbeþearf for Cǣġhealdend tō ġebindenne tō ġerihtode ƿeardas.\n\nƿilt þū + ċeafiġan ānne nū?" +rotate_key.checking: "Sēċende for ġesittendan systemcǣġe…" +rotate_key.generating: "Ċeafiġende nīƿ ed25519 cǣġġepar, ġebīd þē…" +rotate_key.generated: "✅ Ġesundfullīċe ġeċeafod and ġehealden systemcǣġ mid endebyrdnesse + #%d." rotate_key.bootstrap_header: "🚨 GRUNDLECGINGE DǢD NĒDBEÞEARF 🚨" -rotate_key.bootstrap_body: "Þū mōst nū mid handa eċan þone folġendan openan cǣġ tō þǣre `authorized_keys` truman for ǣlċne rǣdmann þe þū þencest tō ġerihtenne mid Cǣġhealdend:" -rotate_key.rotating: "Ġehƿyrfende systemcǣġ, ġebīd þē..." -rotate_key.rotated: "✅ Ġesundfullīċe ġehƿyrfed systemcǣġ. Se nīƿa dǣdlīċa cǣġ is endebyrdnes #%d." +rotate_key.bootstrap_body: "Þū mōst nū mid handa eċan þone folġendan openan cǣġ tō + þǣre `authorized_keys` truman for ǣlċne rǣdmann þe þū þencest tō ġerihtenne mid + Cǣġhealdend:" +rotate_key.rotating: "Ġehƿyrfende systemcǣġ, ġebīd þē…" +rotate_key.rotated: "✅ Ġesundfullīċe ġehƿyrfed systemcǣġ. Se nīƿa dǣdlīċa cǣġ is + endebyrdnes #%d." rotate_key.deploy_reminder: "Send tō þīnum flote tō settenne þone nīƿan cǣġ." rotate_key.error_generate: "mislāmp tō ċeafiġenne systemcǣġ: %w" rotate_key.error_save: "mislāmp tō healdenne systemcǣġ tō dætafate: %w" -rotate_key.error_save_rotated: "mislāmp tō healdenne ġehƿyrfedne systemcǣġ tō dætafate: %w" +rotate_key.error_save_rotated: "mislāmp tō healdenne ġehƿyrfedne systemcǣġ tō dætafate: + %w" rotate_key.error: "Gedƿola: %v" rotate_key.yes_rotate: "Ġēa, Ġehƿyrfan" rotate_key.no_cancel: "Nā, Ācierran" rotate_key.yes_generate: "Ġēa, Ċeafian" -rotate_key.help_modal: "(ƿinstra/sƿīðra tō farenne, enter tō ġetrēoƿiġenne, esc tō ācierrenne)" +rotate_key.help_modal: "(ƿinstra/sƿīðra tō farenne, enter tō ġetrēoƿiġenne, esc tō + ācierrenne)" rotate_key.help_done: "Cnuc 'q' tō eftfarenne tō þǣm fruman menu." # Tags view translations tags_view.title: "Rǣdmenn be Mearcum" -tags_view.empty: "Nān mearca oþþe rǣdmenn ġefunden. Eċ mearcan tō rǣdmannum tō sēonne hīe hēr." +tags_view.empty: "Nān mearca oþþe rǣdmenn ġefunden. Eċ mearcan tō rǣdmannum tō sēonne + hīe hēr." tags_view.filtering: "Sīfe: %s▮" tags_view.filter_active: "Sīfe: %s (cnuc 'esc' tō clǣnsiġenne)" -tags_view.filter_hint: "Cnuc / tō sīfenne..." +tags_view.filter_hint: "Cnuc / tō sīfenne…" tags_view.footer: "(enter) ūtbrǣdan/tosāman /: sīfe esc: eftfaran q: forlǣtan" # Deployment view translations @@ -198,32 +223,32 @@ deploy.title: "🚀 Sendan tō Flote" deploy.select_account: "🚀 Sendung: Ċēos Rǣdmann" deploy.select_tag: "🚀 Sendung: Ċēos Mearc" deploy.show_keys: "Gewritu cyninges bebodes for %s" -deploy.deploying_fleet: "🚀 Sendende tō Flote..." -deploy.deploying: "🚀 Sendende..." +deploy.deploying_fleet: "🚀 Sendende tō Flote…" +deploy.deploying: "🚀 Sendende…" deploy.complete: "✅ Sendung Fullfremmed" deploy.failed: "ðŸ'¥ Sendung Mislāmp" deploy.no_accounts: "Nān dǣdlīċ rǣdmann ġefunden. Eċ ānne oþþe ġeġearƿa ġesittendne." deploy.no_tags: "Nān mearca ġefunden in nānum rǣdmannum." deploy.no_accounts_tag: "Nān dǣdlīċ rǣdmann mid mearca '%s' tō sendenne tō." -deploy.starting_fleet: "Onġinnende flotesendunge..." -deploy.starting_tag: "Onġinnende sendunge tō mearca '%s'..." -deploy.deploying_to: "Sendende tō %s..." +deploy.starting_fleet: "Onġinnende flotesendunge…" +deploy.starting_tag: "Onġinnende sendunge tō mearca '%s'…" +deploy.deploying_to: "Sendende tō %s…" deploy.success: "Ġesundfullīċe ġesended tō %s and ġeniƿod endebyrdnes tō #%d." deploy.fleet_complete: "Flotesendung fullfremmed." deploy.summary: "\n\nEndebyrsnūng: %d ġesundfullīċe, %d mislāmpe." deploy.failed_accounts: "\n\nMislāmpode Sendungas:\n%s" -deploy.pending: "bīdende..." +deploy.pending: "bīdende…" deploy.failed_short: "mislāmp" deploy.success_short: "ġesund" deploy.help_menu: "j/k oþþe ūp/niþer: faran enter: ċēosan q: forlǣtan" deploy.help_select: "enter: ċēosan esc: eftfaran" deploy.help_keys: "c: ġeċīeġan esc: eftfaran" -deploy.help_wait: "(Ġebīd þē...)" +deploy.help_wait: "(Ġebīd þē…)" deploy.help_complete: "(cnuc enter oþþe esc tō forþfarenne)" deploy.help_failed: "esc: eftfaran" deploy.filtering: "Sīfe: %s▮" deploy.filter_active: "Sīfe: %s (cnuc 'esc' tō clǣnsiġenne)" -deploy.filter_hint: "Cnuc / tō sīfenne..." +deploy.filter_hint: "Cnuc / tō sīfenne…" deploy.status.copy_success: "Ġeċīeġed authorized_keys innung tō clipbordum." deploy.status.copy_failed: "Mislāmp tō ġeċīeġenne tō clipbordum: %s" @@ -246,53 +271,59 @@ audit.tui.menu.audit_tag: "Smeagan be Mearca" audit.tui.menu.toggle_mode: "Mōd" audit.tui.mode_strict: "Strang (full ġeliċnunge)" audit.tui.mode_serial: "Endebyrdnes (hēafod ānlīċe)" -audit.tui.help_menu: "j/k oþþe ūp/niþer: faran enter: ċēosan m: ƿendan mōd q: forlǣtan" +audit.tui.help_menu: "j/k oþþe ūp/niþer: faran enter: ċēosan m: ƿendan mōd q: + forlǣtan" audit.tui.select_account: "Ž Smeagung: Ċēos Rǣdmann" audit.tui.select_tag: "Ž Smeagung: Ċēos Mearc" -audit.tui.auditing_fleet: "Ž Smeagende Flote..." -audit.tui.auditing: "Ž Smeagende..." +audit.tui.auditing_fleet: "🔎 Smeagende Flote…" +audit.tui.auditing: "🔎 Smeagende…" audit.tui.complete: "✅ Smeagung Fullfremmed" audit.tui.failed: "ðŸ'¥ Smeagung Mislāmp" audit.tui.no_accounts: "Nān dǣdlīċ rǣdmann ġefunden. Eċ ānne oþþe ġeġearƿa ġesittendne." audit.tui.no_tags: "Nān mearca ġefunden in nānum rǣdmannum." audit.tui.no_accounts_tag: "Nān dǣdlīċ rǣdmann mid mearca '%s' tō smeagenne." -audit.tui.starting_fleet: "Onġinnende flotesmeagunge..." -audit.tui.starting_tag: "Onġinnende smeagunge for mearca '%s'..." +audit.tui.starting_fleet: "Onġinnende flotesmeagunge…" +audit.tui.starting_tag: "Onġinnende smeagunge for mearca '%s'…" audit.tui.ok_short: "ġōd" audit.tui.failed_short: "drīf" -audit.tui.pending: "bīdende..." +audit.tui.pending: "bīdende…" audit.tui.summary: "\n\nEndebyrsnūng: %d ġōd, %d drīf." audit.tui.failed_accounts: "\n\nDrīf Ġefunden:\n%s" audit.tui.help_select: "enter: ċēosan esc: eftfaran" -audit.tui.help_wait: "(Ġebīd þē...)" +audit.tui.help_wait: "(Ġebīd þē…)" audit.tui.help_complete: "(cnuc enter oþþe esc tō forþfarenne)" audit.tui.help_failed: "esc: eftfaran" audit.tui.filtering: "Sīfe: %s▮" audit.tui.filter_active: "Sīfe: %s (cnuc 'esc' tō clǣnsiġenne)" -audit.tui.filter_hint: "Cnuc / tō sīfenne..." +audit.tui.filter_hint: "Cnuc / tō sīfenne…" # CLI specific translations - keeping technical error messages closer to modern for debugging # Audit CLI command audit.cli_error_get_accounts: "Gedƿola fōnde rǣdmenn: %v" audit.error_not_deployed: "ƿeard næfþ ġiet bēon ġesended tō (endebyrdnes is 0)" audit.error_get_serial_key: "ne mihte fōn systemcǣġ %d fram dætafate: %v" -audit.error_no_serial_key: "dætafates unġeliċnes: nān systemcǣġ ġefunden for endebyrdnes %d" +audit.error_no_serial_key: "dætafates unġeliċnes: nān systemcǣġ ġefunden for endebyrdnes + %d" audit.error_connection_failed: "ġebindung mislāmp brūcende cǣġendebyrdnes %d: %w" audit.error_read_remote_file: "ne mihte rǣdan feorran authorized_keys: %w" -audit.error_generate_expected: "ne mihte ċeafiġan ġeƿenedne cǣgan innung for ġeliċnunge: %w" -audit.error_drift_detected: "innunges drīf ġefunden. Feorran truma ne ġefēreþ þǣre ġeƿenedan ġescēadƿīsnesse" +audit.error_generate_expected: "ne mihte ċeafiġan ġeƿenedne cǣgan innung for ġeliċnunge: + %w" +audit.error_drift_detected: "innunges drīf ġefunden. Feorran truma ne ġefēreþ þǣre + ġeƿenedan ġescēadƿīsnesse" # Rotate Key CLI command -rotate_key.cli_rotating: "âš™ï¸ Ġehƿyrfende systemcǣġ..." +rotate_key.cli_rotating: "âš™ï¸ Ġehƿyrfende systemcǣġ…" rotate_key.cli_error_generate: "Gedƿola ċeafiġende nīƿne systemcǣġ: %w" rotate_key.cli_error_save: "Gedƿola healdende ġehƿyrfedne cǣġ tō dætafate: %w" -rotate_key.cli_rotated_success: "\n✅ Ġesundfullīċe ġehƿyrfed systemcǣġ. Se nīƿa dǣdlīċa cǣġ is endebyrdnes #%d." -rotate_key.cli_deploy_reminder: "Ryne 'keymaster deploy' tō settenne þone nīƿan cǣġ tō þīnum flote." +rotate_key.cli_rotated_success: "\n✅ Ġesundfullīċe ġehƿyrfed systemcǣġ. Se nīƿa + dǣdlīċa cǣġ is endebyrdnes #%d." +rotate_key.cli_deploy_reminder: "Ryne 'keymaster deploy' tō settenne þone nīƿan cǣġ + tō þīnum flote." # Import CLI command -import.start: "ðŸ Inbrengde cǣgan fram %s..." +import.start: "ðŸ Inbrengde cǣgan fram %s…" import.skip_invalid_line: " - Oferfarende unriht cǣġlīne: %s" -import.skip_empty_comment: " - Oferfarende cǣġ mid īdele smeagunge: %.20s..." +import.skip_empty_comment: " - Oferfarende cǣġ mid īdele smeagunge: %.20s…" import.skip_duplicate: " - Oferfarende ġelīċne cǣġ (smeagung ġesitteþ): %s" import.error_opening_file: "Gedƿola openende truman: %v" import.error_adding_key: " - Gedƿola eċende cǣġ '%s': %v" @@ -308,14 +339,16 @@ export_ssh_config.error_write_file: "Gedƿola ƿrītende truman: %v" export_ssh_config.success: "✅ Ġesundfullīċe ūtsended SSH ġescēadƿīsnes tō %s" # Bootstrap workflow translations -bootstrap.creating_session: "Ċeafiġende Grundlecginges Ġesittan..." +bootstrap.creating_session: "Ċeafiġende Grundlecginges Ġesittan…" bootstrap.title: "Grundlecgan Nīƿne Ƿeard" -bootstrap.step1_description: "Clīf þās bebodu on þǣm ġemǣrceda ƿearde tō settenne þone þrāgwīlan cǣġ:" +bootstrap.step1_description: "Clīf þās bebodu on þǣm ġemǣrceda ƿearde tō settenne + þone þrāgwīlan cǣġ:" bootstrap.step1_copy_hint: "Cnuc 'c' tō ġeċīeġenne bebod tō clipbordum" bootstrap.step1_copied: "✠Bebod ġeċīeġed tō clipbordum!" bootstrap.step1_help: "Cnuc Enter tō forþfarenne, ESC tō ācierrenne" bootstrap.confirm_title: "Ġetrēoƿ Cǣġes Settunge" -bootstrap.confirm_description: "Hæfst þū ġesundfullīċe ġeclīfed and ġeƿyrhted þæt bebod on þǣm ġemǣrceda ƿearde?" +bootstrap.confirm_description: "Hæfst þū ġesundfullīċe ġeclīfed and ġeƿyrhted þæt + bebod on þǣm ġemǣrceda ƿearde?" bootstrap.confirm_yes: "Ġēa, iċ sette hit" bootstrap.confirm_no: "Ċeafa nīƿ bebod" bootstrap.confirm_help: "(ƿinstra/sƿīðra tō farenne, enter tō ġetrēoƿiġenne)" @@ -323,15 +356,16 @@ bootstrap.testing_title: "Fandiġende Ġebindunge" bootstrap.testing_success: "Ġebindunges fandung ġelāmp!" bootstrap.testing_retry_help: "(cnuc r tō edfarenne, tab tō eftfarenne, esc tō ācierrenne)" bootstrap.select_keys_title: "Ċēos Cǣgan tō Sendenne" -bootstrap.select_keys_description: "Ċēos hwelċe opene cǣgan tō settenne tō þissum rǣdmenn:" +bootstrap.select_keys_description: "Ċēos hwelċe opene cǣgan tō settenne tō þissum + rǣdmenn:" bootstrap.select_keys_help: "(j/k tō farenne, space tō ƿendenne, enter tō forþfarenne)" bootstrap.confirm_deploy_title: "Ġetrēoƿ Sendunge" bootstrap.will_replace_temp_key: "Ƿile ġeēdnīƿian þrāgwīlne cǣġ mid systemcǣġe" bootstrap.deploy: "Sendan" bootstrap.back: "Eftfaran" bootstrap.confirm_deploy_help: "(ƿinstra/sƿīðra tō farenne, enter tō ġetrēoƿiġenne)" -bootstrap.deploying_title: "Sendende..." -bootstrap.deploying_progress: "Sendende cǣgan and fullfremmiende rǣdmannes ġesċeaft..." +bootstrap.deploying_title: "Sendende…" +bootstrap.deploying_progress: "Sendende cǣgan and fullfremmiende rǣdmannes ġesċeaft…" bootstrap.deploying_help: "(ġebīd þē, ne ġebreċ þū)" bootstrap.success_title: "Grundlecging Fullfremmed" bootstrap.success_message: "Ġesundfullīċe grundlecgod %s@%s" @@ -348,37 +382,45 @@ bootstrap.error_options: "Ċēos hū tō forþfarenne:" bootstrap.error_retry: "Edfandian" bootstrap.error_regenerate: "Ċeafian Nīƿne Cǣġ" bootstrap.error_cancel: "Ācierran Grundlecginge" -bootstrap.error_help: "(ƿinstra/sƿīðra tō farenne, enter tō ġetrēoƿiġenne, esc tō ācierrenne)" +bootstrap.error_help: "(ƿinstra/sƿīðra tō farenne, enter tō ġetrēoƿiġenne, esc tō + ācierrenne)" # Config config.error_init_db: "gedƿola onġinnende dætafate: %w" # Deploy CLI command deploy.cli_account_not_found: "Rǣdmann '%s' nā ġefunden oþþe nis dǣdlīċ." -deploy.error_get_bootstrap_key: "mislāmp tō fōnne dǣdlīċan systemcǣġ for grundlecginge: %w" +deploy.error_get_bootstrap_key: "mislāmp tō fōnne dǣdlīċan systemcǣġ for grundlecginge: + %w" deploy.error_no_bootstrap_key: "nān dǣdlīċ systemcǣġ ġefunden for grundlecginge" deploy.error_get_serial_key: "mislāmp tō fōnne systemcǣġ mid endebyrdnesse %d: %v" -deploy.error_no_serial_key: "dætafates unġeliċnes: nān systemcǣġ ġefunden for endebyrdnes %d" -deploy.error_get_active_key_for_serial: "ne mihte ġefōn dǣdlīċne systemcǣġ for endebyrdnes niƿunge" +deploy.error_no_serial_key: "dætafates unġeliċnes: nān systemcǣġ ġefunden for endebyrdnes + %d" +deploy.error_get_active_key_for_serial: "ne mihte ġefōn dǣdlīċne systemcǣġ for endebyrdnes + niƿunge" deploy.error_connection_failed: "ġebindung mislāmp: %w" -deploy.error_no_bootstrap_key_tui: "nān dǣdlīċ systemcǣġ ġefunden for grundlecginge. Ċeafa ānne" -deploy.error_no_serial_key_tui: "dætafates unġeliċnes: nān systemcǣġ ġefunden for endebyrdnes %d on rǣdmenn %s" +deploy.error_no_bootstrap_key_tui: "nān dǣdlīċ systemcǣġ ġefunden for grundlecginge. + Ċeafa ānne" +deploy.error_no_serial_key_tui: "dætafates unġeliċnes: nān systemcǣġ ġefunden for + endebyrdnes %d on rǣdmenn %s" deploy.error_connection_failed_tui: "mislāmp tō ġebindenne tō %s: %w" -deploy.error_get_serial_for_status: "sendung ġelāmp, ac ne mihte fōn nīƿe endebyrdnes for stede ærendungan: %w" +deploy.error_get_serial_for_status: "sendung ġelāmp, ac ne mihte fōn nīƿe endebyrdnes + for stede ærendungan: %w" deploy.error_deployment_failed: "sendung mislāmp: %w" # Trust Host CLI command -trust_host.retrieving_key: "Fandian tō ġefeccenne ƿeardcǣġ fram %s..." +trust_host.retrieving_key: "Fandian tō ġefeccenne ƿeardcǣġ fram %s…" trust_host.error_get_key: "Ne mihte fōn ƿeardcǣġ: %v" trust_host.authenticity_warning_1: "Sēo sōþfæstnes ƿeardes '%s' ne mæġ bēon ġefæstned." trust_host.authenticity_warning_2: "%s cǣġes fingramearca is %s." trust_host.confirm_prompt: "Eart þū ġeƿiss þæt þū ƿilt forþfaran ġebindende (ġēa/nā)? " trust_host.not_trusted_abort: "Ƿeardcǣġ nā ġetrēoƿod. Forlǣtende." trust_host.error_save_key: "Mislāmp tō healdenne ƿeardcǣġ tō dætafate: %v" -trust_host.added_success: "Ƿarnung: Ēcneslīċe ġeeċed '%s' (ġesċeaft %s) tō þǣre ġecunnena ƿearda rǣdinge." +trust_host.added_success: "Ƿarnung: Ēcneslīċe ġeeċed '%s' (ġesċeaft %s) tō þǣre ġecunnena + ƿearda rǣdinge." # Parallel task messages (used by runParallelTasks) parallel_task.no_accounts: "Nān dǣdlīċ rǣdmann for %s." -parallel_task.start_message: "🚀 Onġinnende %s tō %d rǣdmann(um)..." +parallel_task.start_message: "🚀 Onġinnende %s tō %d rǣdmann(um)…" parallel_task.deploy_success_message: "✅ Ġesundfullīċe ġesended tō %s" parallel_task.deploy_fail_message: "ðŸ'¥ Mislāmp tō sendenne tō %s: %v" parallel_task.audit_success_message: "✅ ĠŌD: %s" @@ -390,10 +432,38 @@ migrate.cli_error_flags: "Gedƿola: --type and --dsn tācn sind nēodbeþearf fo migrate.cli_starting_backup: "📦 Ċeafiġende in-ġemynde ġeheald fram fruman dætafate..." migrate.cli_error_backup: "Gedƿola ūtsendende dæta fram fruman dætafate: %v" migrate.cli_backup_success: "✅ Ġeheald fullfremmed." -migrate.cli_connecting_target: "🔗 Ġebindende tō ġemǣrcedan %s dætafate and ƿyrcende flutunga..." -migrate.cli_error_connect: "Gedƿola ġebindende oþþe flutende ġemǣrcedne dætafate: %v" +migrate.cli_connecting_target: "🔗 Ġebindende tō ġemǣrcedan %s dætafate and ƿyrcende + flutunga..." +migrate.cli_error_connect: "Gedƿola ġebindende oþþe flutende ġemǣrcedne dætafate: + %v" migrate.cli_connect_success: "✅ Ġebindung and flutunga ġesundfullīċe." migrate.cli_starting_restore: "🔄 Edstaðoliġende dæta in ġemǣrcedan dætafate..." migrate.cli_error_restore: "Gedƿola inbringende dæta in ġemǣrcedan dætafate: %v" migrate.cli_success: "🎉 Flutung fullfremmed! Eall dæta is ġefluten tō þǣm nīƿan dætafate." -migrate.cli_next_steps: "➡️ Nīehsta Stæpe: Ġeēdnīƿa þīne .keymaster.yaml tō tācnienne þone nīƿan dætafate." \ No newline at end of file +migrate.cli_next_steps: "➡️ Nīehsta Stæpe: Ġeēdnīƿa þīne .keymaster.yaml tō tācnienne + þone nīƿan dætafate." +accounts.delete_confirm.help_checkbox: '(↑/↓/tab: fære, rum/ingang: swica ceacboxe, + ingang on Ġēa: fōrþgange, āflīe: forlæt)' +accounts.delete_confirm.decommission: Þās searocræftas forscrīfaþ ānfealdlīce + SSH cæglocan fram feorran hūshlōdan +accounts.decommission_confirm.title: 🚮 Adōn gerec +accounts.decommission_confirm.question: "Hū wille þū ādōn þæt gereord?\n\n%s?" +accounts.decommission_confirm.cancel: Forlǣt +accounts.key_selection.cancel: Forlǣt +accounts.decommission_confirm.delete_only: Ādīle ān +accounts.decommission_confirm.decommission: ālēcgan +accounts.decommission_confirm.help: (Winestra/Swiðra tō fēranne, inngang tō + trumianne, flēam tō forlætanne) +accounts.status.decommission_cancelled: Adōn forwyrned. +accounts.status.decommission_success: 'Þæt gereord is gesænelice adilegode: %s' +accounts.status.decommission_failed: 'Afellan tō forlætenne ræd: %v' +accounts.status.decommission_partial: 'Tō dǣle ālȳsed %s - dæt databōc ādīlged, ac + feorrbeorhtung ālȳsnes fēoll: %v' +accounts.key_selection.title: 🔑 Cēos Sēo Swutelunglocan tō Habban +accounts.key_selection.question: "Hwylce SSH hlýstlas sculon bēon GEHEALDENE on %s?\n\ + Gemyne: Cyninges hlýst biþ symle ādōn on dǣdum." +accounts.key_selection.no_keys: Nā cǣgas fundne sind for þissum gerecenunge. +accounts.key_selection.continue: Gefaraþ forþ +accounts.key_selection.help: '(↑/↓: færeld, rým/ingang: swītlian, tāb: tō cnappum, + ā: geheald eall, n: geheald nān, forlæt: bæc)' +audit.tui.fleet_complete: Sciphere geendod. diff --git a/internal/i18n/locales/active.de.yaml b/internal/i18n/locales/active.de.yaml index 5b8d8ae..a2ee6eb 100644 --- a/internal/i18n/locales/active.de.yaml +++ b/internal/i18n/locales/active.de.yaml @@ -24,29 +24,30 @@ dashboard.system_status: "Systemstatus" dashboard.system_key.not_generated: "Nicht erzeugt" dashboard.deployment_status: "Deployment-Status" dashboard.hosts_current_key: "Hosts mit aktuellem Schlüssel: %d" -dashboard.hosts_past_keys: " Hosts mit alten Schlüsseln: %d" +dashboard.hosts_past_keys: "Hosts mit alten Schlüsseln: %d" dashboard.system_key.active: "Aktiv (Seriennr. %d)" dashboard.security_posture: "Sicherheitsstatus" -dashboard.accounts: " Verwaltete Konten: %d (%d aktiv)" -dashboard.key_type_spread: " Schlüssel-Typen: %s" +dashboard.accounts: "Verwaltete Konten: %d (%d aktiv)" +dashboard.key_type_spread: "Schlüssel-Typen: %s" dashboard.public_keys: "Öffentliche Schlüssel: %d (%d global)" -dashboard.system_key: " Systemschlüssel: %s" +dashboard.system_key: "Systemschlüssel: %s" dashboard.recent_activity: "Letzte Aktivitäten" dashboard.no_recent_activity: "Keine letzten Aktivitäten." -dashboard.footer: "j/k hoch/runter: navigieren enter: auswählen q: beenden L: Sprache" +dashboard.footer: "j/k hoch/runter: navigieren enter: auswählen q: beenden L: + Sprache" # Language selection view language.select: "Sprache auswählen:" language.help: "up/down: navigieren enter: auswählen q/esc: zurück" # Account form translations -account_form.username_label: "Benutzername: " +account_form.username_label: "Benutzername:" account_form.username_placeholder: "benutzer" -account_form.hostname_label: "Hostname: " +account_form.hostname_label: "Hostname:" account_form.hostname_placeholder: "www.beispiel.de" -account_form.label_label: "Bezeichnung (optional): " +account_form.label_label: "Bezeichnung (optional):" account_form.label_placeholder: "prod-web-01" -account_form.tags_label: "Tags (kommagetrennt): " +account_form.tags_label: "Tags (kommagetrennt):" account_form.tags_placeholder: "rolle:db,dc:berlin" account_form.edit_title: "Konto bearbeiten" account_form.add_title: "Neues Konto hinzufügen" @@ -63,11 +64,12 @@ accounts.empty: "Keine Konten gefunden. Mit 'a' hinzufügen." accounts.empty_filtered: "Keine Konten passen zum Filter." accounts.filtering: "Filter: %s█" accounts.filter_active: "Filter: %s (mit 'esc' löschen)" -accounts.filter_hint: "Mit / filtern..." -accounts.footer: "a: hinzufügen e: bearbeiten d: löschen t: umschalten v: verifizieren i: importieren q: beenden" +accounts.filter_hint: "Mit / filtern…" +accounts.footer: "a: hinzufügen e: bearbeiten d: löschen t: umschalten v: verifizieren + i: importieren q: beenden" accounts.list_title: "Konten" accounts.status.modified_success: "Konto erfolgreich geändert." -accounts.status.trust_attempt: "Versuche, Host %s zu vertrauen..." +accounts.status.trust_attempt: "Versuche, Host %s zu vertrauen…" accounts.status.import_assigned: "%d importierte Schlüssel zugewiesen." accounts.status.import_skipped_assign: "%d neue Schlüssel importiert, ohne sie zuzuweisen." accounts.status.delete_cancelled: "Löschen abgebrochen." @@ -75,36 +77,46 @@ accounts.status.delete_success: "Konto gelöscht: %s" accounts.status.verify_fail: "Fehler beim Verifizieren von Host %s: %v" accounts.status.verify_success: "Host-Schlüssel für %s erfolgreich vertraut." accounts.status.import_fail: "Import fehlgeschlagen: %v" -accounts.status.import_no_new: "Keine neuen Schlüssel zum Importieren gefunden. %d Duplikate übersprungen." -accounts.status.toggle_success: "Status für %s umgeschaltet." -accounts.status.verify_start: "Verifiziere Host-Schlüssel für %s..." -accounts.status.import_start: "Importiere Schlüssel von %s..." +accounts.status.import_no_new: "Keine neuen Schlüssel zum Importieren gefunden. %d + Duplikate übersprungen." +accounts.status.toggle_success: "Status umgeschaltet für: %s" +accounts.status.verify_start: "Verifiziere Host-Schlüssel für %s…" +accounts.status.import_start: "Importiere Schlüssel von %s…" accounts.import_confirm.title: "✨ Remote-Import Bestätigung" -accounts.import_confirm.found_keys: "%d neue öffentliche Schlüssel auf dem Remote-Host gefunden:" -accounts.import_confirm.question: "Diese %d neuen Schlüssel diesem Konto zuweisen? (j/n)" +accounts.import_confirm.found_keys: "%d neue öffentliche Schlüssel auf dem Remote-Host + gefunden:" +accounts.import_confirm.question: "Diese %d neuen Schlüssel diesem Konto zuweisen? + (j/n)" accounts.delete_confirm.title: "🗑️ Löschen bestätigen" -accounts.delete_confirm.question: "Sind Sie sicher, dass Sie das Konto %s löschen möchten?" +accounts.delete_confirm.question: "Sind Sie sicher, dass Sie das Konto %s löschen + möchten?" accounts.delete_confirm.yes: "Ja, löschen" accounts.delete_confirm.no: "Nein, abbrechen" accounts.delete_confirm.help: "(links/rechts navigieren, Enter bestätigen, Esc abbrechen)" -accounts.delete_confirm.help_checkbox: "(↑/↓/Tab: navigieren, Leer/Enter: Checkbox umschalten, Enter bei Ja: weiter, Esc: abbrechen)" +accounts.delete_confirm.help_checkbox: "(↑/↓/Tab: navigieren, Leer/Enter: Checkbox + umschalten, Enter bei Ja: weiter, Esc: abbrechen)" accounts.delete_confirm.decommission: "SSH-Schlüssel auch vom Remote-Host entfernen" accounts.decommission_confirm.title: "🚮 Konto entfernen" accounts.decommission_confirm.question: "Wie möchten Sie das Konto\n\n%s\n\nentfernen?" accounts.decommission_confirm.cancel: "Abbrechen" accounts.decommission_confirm.delete_only: "Nur löschen" accounts.decommission_confirm.decommission: "Außer Betrieb nehmen" -accounts.decommission_confirm.help: "(links/rechts navigieren, Enter bestätigen, Esc abbrechen)" +accounts.decommission_confirm.help: "(links/rechts navigieren, Enter bestätigen, Esc + abbrechen)" accounts.status.decommission_cancelled: "Außerbetriebnahme abgebrochen." accounts.status.decommission_success: "Konto erfolgreich außer Betrieb genommen: %s" -accounts.status.decommission_failed: "Konto konnte nicht außer Betrieb genommen werden: %v" -accounts.status.decommission_partial: "%s teilweise außer Betrieb genommen - Datenbank gelöscht, aber Remote-Bereinigung fehlgeschlagen: %v" +accounts.status.decommission_failed: "Konto konnte nicht außer Betrieb genommen werden: + %v" +accounts.status.decommission_partial: "%s teilweise außer Betrieb genommen - Datenbank + gelöscht, aber Remote-Bereinigung fehlgeschlagen: %v" accounts.key_selection.title: "🔑 SSH-Schlüssel zum Behalten auswählen" -accounts.key_selection.question: "Welche SSH-Schlüssel sollen auf %s BEHALTEN werden?\nHinweis: Der Systemschlüssel wird bei der Außerbetriebnahme immer entfernt." +accounts.key_selection.question: "Welche SSH-Schlüssel sollen auf %s BEHALTEN werden?\n\ + Hinweis: Der Systemschlüssel wird bei der Außerbetriebnahme immer entfernt." accounts.key_selection.no_keys: "Keine Schlüssel für dieses Konto gefunden." accounts.key_selection.cancel: "Abbrechen" accounts.key_selection.continue: "Weiter" -accounts.key_selection.help: "(↑/↓: navigieren, Leer/Enter: umschalten, Tab: zu Buttons, a: alle behalten, n: keine behalten, Esc: zurück)" +accounts.key_selection.help: "(↑/↓: navigieren, Leer/Enter: umschalten, Tab: zu Buttons, + a: alle behalten, n: keine behalten, Esc: zurück)" accounts.tags: "Tags: %s" # Public keys overview translations @@ -114,8 +126,9 @@ public_keys.empty: "Keine öffentlichen Schlüssel gefunden. Mit 'a' hinzufügen public_keys.empty_filtered: "Keine Schlüssel passen zum Filter." public_keys.filtering: "Filter: %s█" public_keys.filter_active: "Filter: %s (mit 'esc' löschen)" -public_keys.filter_hint: "Mit / filtern..." -public_keys.footer: "a: hinzufügen c: kopieren d: löschen g: global umschalten u: verwendung anzeigen q: beenden" +public_keys.filter_hint: "Mit / filtern…" +public_keys.footer: "a: hinzufügen c: kopieren d: löschen g: global umschalten u: + verwendung anzeigen q: beenden" public_keys.list_title: "Öffentliche Schlüssel" public_keys.detail_comment: "Kommentar: %s" public_keys.detail_algorithm: "Algorithmus: %s" @@ -130,11 +143,14 @@ public_keys.status.toggle_success: "Globaler Status für Schlüssel umgeschaltet public_keys.status.copy_success: "Schlüssel '%s' in Zwischenablage kopiert." public_keys.status.copy_failed: "Fehler beim Kopieren in Zwischenablage: %s" public_keys.delete_confirm.title: "🗑️ Löschen bestätigen" -public_keys.delete_confirm.question: "Sind Sie sicher, dass Sie den öffentlichen Schlüssel\n\n%s\n\nlöschen möchten?" -public_keys.delete_confirm.warning: "Dadurch wird er von allen Konten entfernt, denen er zugewiesen ist." +public_keys.delete_confirm.question: "Sind Sie sicher, dass Sie den öffentlichen Schlüssel\n\ + \n%s\n\nlöschen möchten?" +public_keys.delete_confirm.warning: "Dadurch wird er von allen Konten entfernt, denen + er zugewiesen ist." public_keys.delete_confirm.yes: "Ja, löschen" public_keys.delete_confirm.no: "Nein, abbrechen" -public_keys.delete_confirm.help: "(links/rechts navigieren, Enter bestätigen, Esc abbrechen)" +public_keys.delete_confirm.help: "(links/rechts navigieren, Enter bestätigen, Esc + abbrechen)" public_keys.usage_report.title: "📜 Verwendungsbericht für Schlüssel: %s" public_keys.usage_report.not_assigned: "Dieser Schlüssel ist keinen Konten zugewiesen." public_keys.usage_report.assigned_to: "Dieser Schlüssel ist den folgenden Konten zugewiesen:" @@ -143,23 +159,30 @@ public_keys.usage_report.help: "(esc oder q zum Zurückkehren)" # Public key form translations public_key_form.add_title: "Neuen öffentlichen Schlüssel hinzufügen" public_key_form.prompt: "Öffentlichen Schlüssel einfügen: " -public_key_form.placeholder: "ssh-ed25519 AAAA... benutzer@host" -public_key_form.checkbox_unchecked: "[ ] Als globalen Schlüssel setzen (wird allen Konten zugewiesen)" -public_key_form.checkbox_checked: "[x] Als globalen Schlüssel setzen (wird allen Konten zugewiesen)" -public_key_form.help: "(tab zum Navigieren, Leertaste/Enter für Checkbox, Enter zum Speichern, esc zum Abbrechen)" +public_key_form.placeholder: "ssh-ed25519 AAAA… benutzer@host" +public_key_form.checkbox_unchecked: "[ ] Als globalen Schlüssel setzen (wird allen + Konten zugewiesen)" +public_key_form.checkbox_checked: "[x] Als globalen Schlüssel setzen (wird allen Konten + zugewiesen)" +public_key_form.help: "(tab zum Navigieren, Leertaste/Enter für Checkbox, Enter zum + Speichern, esc zum Abbrechen)" public_key_form.error: "Fehler: %v" -public_key_form.info: "Fügen Sie einen gültigen SSH-Öffentlichen Schlüssel ein. Sie können ihn als global setzen, um ihn allen Konten zuzuweisen." +public_key_form.info: "Fügen Sie einen gültigen SSH-Öffentlichen Schlüssel ein. Sie + können ihn als global setzen, um ihn allen Konten zuzuweisen." # Assign keys view translations -assign_keys.help_bar_accounts: "j/k hoch/runter: navigieren enter: auswählen /: suchen esc: zurück q: beenden" -assign_keys.help_bar_keys: "j/k hoch/runter: navigieren leertaste: umschalten /: suchen esc: zurück q: beenden" +assign_keys.help_bar_accounts: "j/k hoch/runter: navigieren enter: auswählen /: + suchen esc: zurück q: beenden" +assign_keys.help_bar_keys: "j/k hoch/runter: navigieren leertaste: umschalten \ + \ /: suchen esc: zurück q: beenden" assign_keys.accounts_title: "Konten" assign_keys.no_accounts: "Keine Konten gefunden. Mit 'a' hinzufügen." -assign_keys.search_hint: "Mit / suchen..." +assign_keys.search_hint: "Mit / suchen…" assign_keys.keys_title: "Schlüssel zuweisen für: %s" assign_keys.keys_title_short: "Schlüssel" assign_keys.no_keys: "Keine öffentlichen Schlüssel gefunden. Mit 'a' hinzufügen." -assign_keys.select_account_prompt: "Wählen Sie links ein Konto aus, um dessen Schlüssel zu verwalten." +assign_keys.select_account_prompt: "Wählen Sie links ein Konto aus, um dessen Schlüssel + zu verwalten." assign_keys.filtering: "Filter: %s█" assign_keys.filter_active: "Filter: %s (esc zum Löschen)" assign_keys.status.selected_account: "Konto %s ausgewählt, %d Schlüssel zugewiesen" @@ -167,7 +190,8 @@ assign_keys.status.unassign_attempt: "Versuche, Schlüssel zu entfernen: %s" assign_keys.status.unassign_error: "Fehler beim Entfernen des Schlüssels: %v" assign_keys.status.unassign_success: "Schlüssel entfernt: %s" assign_keys.status.assign_attempt: "Versuche, Schlüssel zuzuweisen: %s" -assign_keys.status.assign_error_deleted: "Fehler: Schlüssel wurde möglicherweise gelöscht: %v" +assign_keys.status.assign_error_deleted: "Fehler: Schlüssel wurde möglicherweise gelöscht: + %v" assign_keys.status.assign_error: "Fehler beim Zuweisen des Schlüssels: %v" assign_keys.status.assign_success: "Schlüssel zugewiesen: %s" assign_keys.key_item_format: "%s%s %s%s (%s)" @@ -178,33 +202,46 @@ assign_keys.global_marker: "🌍 " # Rotate key translations rotate_key.title: "Systemschlüssel-Verwaltung" rotate_key.confirm_rotate_title: "⚠️ Systemschlüssel-Rotation bestätigen" -rotate_key.confirm_rotate_question: "Sind Sie sicher, dass Sie den Systemschlüssel rotieren möchten?\n\nEs wird ein neues Schlüsselpaar erstellt.\n\nDiese Aktion betrifft ALLE verwalteten Hosts.\nSie sollten den neuen Schlüssel unmittelbar nach der Rotation auf Ihre Flotte ausrollen.\n\nFahren Sie nur fort, wenn Sie wissen, was Sie tun!" +rotate_key.confirm_rotate_question: "Sind Sie sicher, dass Sie den Systemschlüssel + rotieren möchten?\n\nEs wird ein neues Schlüsselpaar erstellt.\n\nDiese Aktion betrifft + ALLE verwalteten Hosts.\nSie sollten den neuen Schlüssel unmittelbar nach der Rotation + auf Ihre Flotte ausrollen.\n\nFahren Sie nur fort, wenn Sie wissen, was Sie tun!" rotate_key.confirm_generate_title: "⚙️ Initialen Systemschlüssel erzeugen" -rotate_key.confirm_generate_question: "Kein Keymaster-Systemschlüssel gefunden.\n\nDieser Schlüssel ist erforderlich, damit Keymaster sich mit verwalteten Hosts verbinden kann.\n\nMöchten Sie jetzt einen erzeugen?" -rotate_key.checking: "Prüfe auf vorhandenen Systemschlüssel..." -rotate_key.generating: "Neues ed25519-Schlüsselpaar wird erzeugt, bitte warten..." -rotate_key.generated: "✅ Systemschlüssel mit Seriennummer #%d erfolgreich erzeugt und gespeichert." +rotate_key.confirm_generate_question: "Kein Keymaster-Systemschlüssel gefunden.\n\n\ + Dieser Schlüssel ist erforderlich, damit Keymaster sich mit verwalteten Hosts verbinden + kann.\n\nMöchten Sie jetzt einen erzeugen?" +rotate_key.checking: "Prüfe auf vorhandenen Systemschlüssel…" +rotate_key.generating: "Neues ed25519-Schlüsselpaar wird erzeugt, bitte warten…" +rotate_key.generated: "✅ Systemschlüssel mit Seriennummer #%d erfolgreich erzeugt + und gespeichert." rotate_key.bootstrap_header: "🚨 BOOTSTRAP-AKTION ERFORDERLICH 🚨" -rotate_key.bootstrap_body: "Sie müssen den folgenden öffentlichen Schlüssel manuell zur `authorized_keys`-Datei jedes zu verwaltenden Kontos hinzufügen:" -rotate_key.rotating: "Systemschlüssel wird rotiert, bitte warten..." -rotate_key.rotated: "✅ Systemschlüssel erfolgreich rotiert. Der neue aktive Schlüssel hat die Seriennummer #%d." -rotate_key.deploy_reminder: "Rollen Sie den neuen Schlüssel auf Ihre Flotte aus, um ihn zu aktivieren." +rotate_key.bootstrap_body: "Sie müssen den folgenden öffentlichen Schlüssel manuell + zur `authorized_keys`-Datei jedes zu verwaltenden Kontos hinzufügen:" +rotate_key.rotating: "Systemschlüssel wird rotiert, bitte warten…" +rotate_key.rotated: "✅ Systemschlüssel erfolgreich rotiert. Der neue aktive Schlüssel + hat die Seriennummer #%d." +rotate_key.deploy_reminder: "Rollen Sie den neuen Schlüssel auf Ihre Flotte aus, um + ihn zu aktivieren." rotate_key.error_generate: "Fehler beim Erzeugen des Systemschlüssels: %w" -rotate_key.error_save: "Fehler beim Speichern des Systemschlüssels in der Datenbank: %w" -rotate_key.error_save_rotated: "Fehler beim Speichern des rotierten Systemschlüssels in der Datenbank: %w" +rotate_key.error_save: "Fehler beim Speichern des Systemschlüssels in der Datenbank: + %w" +rotate_key.error_save_rotated: "Fehler beim Speichern des rotierten Systemschlüssels + in der Datenbank: %w" rotate_key.error: "Fehler: %v" rotate_key.yes_rotate: "Ja, rotieren" rotate_key.no_cancel: "Nein, abbrechen" rotate_key.yes_generate: "Ja, erzeugen" -rotate_key.help_modal: "(links/rechts zum Navigieren, Enter zum Bestätigen, Esc zum Abbrechen)" +rotate_key.help_modal: "(links/rechts zum Navigieren, Enter zum Bestätigen, Esc zum + Abbrechen)" rotate_key.help_done: "Mit 'q' zum Hauptmenü zurückkehren." # Tags view translations tags_view.title: "Konten nach Tag" -tags_view.empty: "Keine Tags oder Konten gefunden. Fügen Sie Tags zu Konten hinzu, um sie hier anzuzeigen." +tags_view.empty: "Keine Tags oder Konten gefunden. Fügen Sie Tags zu Konten hinzu, + um sie hier anzuzeigen." tags_view.filtering: "Filter: %s█" tags_view.filter_active: "Filter: %s (mit 'esc' löschen)" -tags_view.filter_hint: "Mit / filtern..." +tags_view.filter_hint: "Mit / filtern…" tags_view.footer: "(enter) auf-/zuklappen /: filtern esc: zurück q: beenden" # Deployment view translations @@ -216,32 +253,33 @@ deploy.title: "🚀 Auf Flotte ausrollen" deploy.select_account: "🚀 Konto auswählen" deploy.select_tag: "🚀 Tag auswählen" deploy.show_keys: "📄 authorized_keys für %s" -deploy.deploying_fleet: "🚀 Ausrollen auf Flotte..." -deploy.deploying: "🚀 Ausrollen..." +deploy.deploying_fleet: "🚀 Ausrollen auf Flotte…" +deploy.deploying: "🚀 Ausrollen…" deploy.complete: "✅ Ausrollen abgeschlossen" deploy.failed: "💥 Ausrollen fehlgeschlagen" -deploy.no_accounts: "Keine aktiven Konten gefunden. Bitte fügen Sie eines hinzu oder aktivieren Sie ein bestehendes." +deploy.no_accounts: "Keine aktiven Konten gefunden. Bitte fügen Sie eines hinzu oder + aktivieren Sie ein bestehendes." deploy.no_tags: "Keine Tags in Konten gefunden." deploy.no_accounts_tag: "Keine aktiven Konten mit Tag '%s' zum Ausrollen gefunden." -deploy.starting_fleet: "Starte Ausrollen auf Flotte..." -deploy.starting_tag: "Starte Ausrollen für Tag '%s'..." -deploy.deploying_to: "Rolle aus auf %s..." +deploy.starting_fleet: "Starte Ausrollen auf Flotte…" +deploy.starting_tag: "Starte Ausrollen für Tag '%s'…" +deploy.deploying_to: "Rolle aus auf %s…" deploy.success: "Erfolgreich ausgerollt auf %s und Seriennummer auf #%d aktualisiert." deploy.fleet_complete: "Ausrollen auf Flotte abgeschlossen." deploy.summary: "\n\nZusammenfassung: %d erfolgreich, %d fehlgeschlagen." deploy.failed_accounts: "\n\nFehlgeschlagene Ausrollungen:\n%s" -deploy.pending: "ausstehend..." +deploy.pending: "ausstehend…" deploy.failed_short: "fehlgeschlagen" deploy.success_short: "erfolgreich" deploy.help_menu: "j/k oder hoch/runter: navigieren enter: auswählen q: beenden" deploy.help_select: "enter: auswählen esc: zurück" deploy.help_keys: "c: kopieren esc: zurück" -deploy.help_wait: "(Bitte warten...)" +deploy.help_wait: "(Bitte warten…)" deploy.help_complete: "(enter oder esc zum Fortfahren)" deploy.help_failed: "esc: zurück" deploy.filtering: "Filter: %s█" deploy.filter_active: "Filter: %s (mit 'esc' löschen)" -deploy.filter_hint: "Mit / filtern..." +deploy.filter_hint: "Mit / filtern…" deploy.status.copy_success: "Inhalt von authorized_keys in die Zwischenablage kopiert." deploy.status.copy_failed: "Fehler beim Kopieren in die Zwischenablage: %s" @@ -264,55 +302,68 @@ audit.tui.menu.audit_tag: "Nach Tag auditieren" audit.tui.menu.toggle_mode: "Modus" audit.tui.mode_strict: "Strict (vollständiger Vergleich)" audit.tui.mode_serial: "Serial (nur Header)" -audit.tui.help_menu: "j/k oder hoch/runter: navigieren enter: auswählen m: Modus umschalten q: beenden" +audit.tui.help_menu: "j/k oder hoch/runter: navigieren enter: auswählen m: Modus + umschalten q: beenden" audit.tui.select_account: "🔎 Audit: Konto auswählen" audit.tui.select_tag: "🔎 Audit: Tag auswählen" -audit.tui.auditing_fleet: "🔎 Flotte wird auditiert..." -audit.tui.auditing: "🔎 Auditing..." +audit.tui.auditing_fleet: "🔎 Flotte wird auditiert…" +audit.tui.auditing: "🔎 Auditiere…" audit.tui.complete: "✅ Audit abgeschlossen" audit.tui.failed: "💥 Audit fehlgeschlagen" -audit.tui.no_accounts: "Keine aktiven Konten gefunden. Bitte fügen Sie eines hinzu oder aktivieren Sie ein bestehendes." +audit.tui.no_accounts: "Keine aktiven Konten gefunden. Bitte fügen Sie eines hinzu + oder aktivieren Sie ein bestehendes." audit.tui.no_tags: "Keine Tags in Konten gefunden." audit.tui.no_accounts_tag: "Keine aktiven Konten mit Tag '%s' zum Auditieren." -audit.tui.starting_fleet: "Starte Flotten-Audit..." -audit.tui.starting_tag: "Starte Audit für Tag '%s'..." +audit.tui.starting_fleet: "Starte Flotten-Audit…" +audit.tui.starting_tag: "Starte Audit für Tag '%s'…" audit.tui.ok_short: "ok" -audit.tui.failed_short: "drift" -audit.tui.pending: "ausstehend..." +audit.tui.failed_short: "Abweichung" +audit.tui.pending: "ausstehend…" audit.tui.summary: "\n\nZusammenfassung: %d ok, %d drift." audit.tui.failed_accounts: "\n\nDrift erkannt:\n%s" audit.tui.help_select: "enter: auswählen esc: zurück" -audit.tui.help_wait: "(Bitte warten...)" +audit.tui.help_wait: "(Bitte warten…)" audit.tui.help_complete: "(enter oder esc zum Fortfahren)" audit.tui.help_failed: "esc: zurück" audit.tui.filtering: "Filter: %s█" audit.tui.filter_active: "Filter: %s (mit 'esc' löschen)" -audit.tui.filter_hint: "Mit / filtern..." +audit.tui.filter_hint: "Mit / filtern…" # CLI specific translations # Audit CLI command -audit.cli_error_get_accounts: "Fehler beim Abrufen der Konten: %v" +audit.cli_error_get_accounts: "Fehler beim Abruf der Konten: %v" audit.error_not_deployed: "Host wurde noch nicht ausgerollt (Seriennummer ist 0)" -audit.error_get_serial_key: "Systemschlüssel %d konnte nicht aus DB gelesen werden: %w" -audit.error_no_serial_key: "DB-Inkonsistenz: kein Systemschlüssel für Seriennummer %d gefunden" -audit.error_connection_failed: "Verbindung mit Schlüssel-Seriennummer %d fehlgeschlagen: %w" -audit.error_read_remote_file: "authorized_keys auf dem Host konnte nicht gelesen werden: %w" -audit.error_generate_expected: "Erwarteter authorized_keys-Inhalt konnte nicht generiert werden: %w" -audit.error_drift_detected: "Konfigurationsdrift erkannt. Remote-Datei stimmt nicht mit der erwarteten Konfiguration überein" +audit.error_get_serial_key: "Systemschlüssel %d konnte nicht aus DB gelesen werden: + %w" +audit.error_no_serial_key: "DB-Inkonsistenz: kein Systemschlüssel für Seriennummer + %d gefunden" +audit.error_connection_failed: "Verbindung mit Schlüssel-Seriennummer %d fehlgeschlagen: + %w" +audit.error_read_remote_file: "authorized_keys auf dem Host konnte nicht gelesen werden: + %w" +audit.error_generate_expected: "Erwarteter authorized_keys-Inhalt konnte nicht generiert + werden: %w" +audit.error_drift_detected: "Konfigurationsdrift erkannt. Remote-Datei stimmt nicht + mit der erwarteten Konfiguration überein" # Rotate Key CLI command -rotate_key.cli_rotating: "⚙️ Systemschlüssel wird rotiert..." -rotate_key.cli_error_generate: "Fehler beim Generieren des neuen Systemschlüssels: %v" -rotate_key.cli_error_save: "Fehler beim Speichern des rotierten Schlüssels in der Datenbank: %v" -rotate_key.cli_rotated_success: "\n✅ Systemschlüssel erfolgreich rotiert. Der neue aktive Schlüssel hat die Seriennummer #%d." -rotate_key.cli_deploy_reminder: "Führen Sie 'keymaster deploy' aus, um den neuen Schlüssel auf Ihre Flotte anzuwenden." +rotate_key.cli_rotating: "⚙️ Systemschlüssel wird rotiert…" +rotate_key.cli_error_generate: "Fehler beim Generieren des neuen Systemschlüssels: + %v" +rotate_key.cli_error_save: "Fehler beim Speichern des rotierten Schlüssels in der + Datenbank: %v" +rotate_key.cli_rotated_success: "\n✅ Systemschlüssel erfolgreich rotiert. Der neue + aktive Schlüssel hat die Seriennummer #%d." +rotate_key.cli_deploy_reminder: "Führen Sie 'keymaster deploy' aus, um den neuen Schlüssel + auf Ihre Flotte anzuwenden." # Import CLI command -import.start: "🔑 Schlüssel werden aus %s importiert..." +import.start: "🔑 Schlüssel werden aus %s importiert…" import.skip_invalid_line: " - Ungültige Schlüsselzeile übersprungen: %v" -import.skip_empty_comment: " - Schlüssel mit leerem Kommentar übersprungen: %.20s..." -import.skip_duplicate: " - Duplizierten Schlüssel übersprungen (Kommentar existiert): %s" +import.skip_empty_comment: " - Schlüssel mit leerem Kommentar übersprungen: %.20s…" +import.skip_duplicate: " - Duplizierten Schlüssel übersprungen (Kommentar existiert): + %s" import.error_opening_file: "Fehler beim Öffnen der Datei: %v" import.error_adding_key: " - Fehler beim Hinzufügen des Schlüssels '%s': %v" import.imported_key: " + Schlüssel importiert: %s" @@ -328,7 +379,7 @@ export_ssh_config.success: "✅ SSH-Konfiguration erfolgreich exportiert nach %s # Parallel task messages (used by runParallelTasks) parallel_task.no_accounts: "Keine aktiven Konten für %s." -parallel_task.start_message: "🚀 Starte %s für %d Konto(en)..." +parallel_task.start_message: "🚀 Starte %s für %d Konto(en)…" parallel_task.deploy_success_message: "✅ Erfolgreich auf %s ausgerollt" parallel_task.deploy_fail_message: "💥 Fehler beim Ausrollen auf %s: %v" parallel_task.audit_success_message: "✅ OK: %s" @@ -336,14 +387,16 @@ parallel_task.audit_fail_message: "🚨 Drift auf %s erkannt: %v" parallel_task.complete_message: "%s abgeschlossen." # Bootstrap workflow translations -bootstrap.creating_session: "Bootstrap-Sitzung wird erstellt..." +bootstrap.creating_session: "Bootstrap-Sitzung wird erstellt…" bootstrap.title: "Neuen Host bootstrappen" -bootstrap.step1_description: "Führen Sie den folgenden Befehl auf dem Ziel-Host aus, um den temporären Schlüssel zu installieren:" +bootstrap.step1_description: "Führen Sie den folgenden Befehl auf dem Ziel-Host aus, + um den temporären Schlüssel zu installieren:" bootstrap.step1_copy_hint: "'c' drücken zum Kopieren des Befehls in die Zwischenablage" bootstrap.step1_copied: "✓ Befehl in Zwischenablage kopiert!" bootstrap.step1_help: "Enter zum Fortfahren, Esc zum Abbrechen" bootstrap.confirm_title: "Schlüssel-Installation bestätigen" -bootstrap.confirm_description: "Haben Sie den Befehl erfolgreich auf dem Ziel-Host eingefügt und ausgeführt?" +bootstrap.confirm_description: "Haben Sie den Befehl erfolgreich auf dem Ziel-Host + eingefügt und ausgeführt?" bootstrap.confirm_yes: "Ja, ich habe ihn installiert" bootstrap.confirm_no: "Neuen Befehl generieren" bootstrap.confirm_help: "(links/rechts zum Navigieren, Enter zum Bestätigen)" @@ -353,29 +406,39 @@ bootstrap.testing_retry_help: "(r zum Wiederholen, Tab zum Zurückgehen, Esc zum bootstrap.verify_hostkey_title: "Server Host-Key Verifikation" bootstrap.verify_hostkey_retrieving: "Server Host-Key wird abgerufen..." bootstrap.verify_hostkey_error_retrieving: "Fehler beim Abrufen des Host-Keys: %s" -bootstrap.verify_hostkey_error_retry_help: "'r' zum Wiederholen, 'Tab' zum Zurückgehen oder 'Esc' zum Abbrechen" +bootstrap.verify_hostkey_error_retry_help: "'r' zum Wiederholen, 'Tab' zum Zurückgehen + oder 'Esc' zum Abbrechen" bootstrap.verify_hostkey_server_key: "Server Host-Key:" bootstrap.verify_hostkey_type_label: "Typ" bootstrap.verify_hostkey_invalid_format: "❌ Ungültiges Host-Key Format" -bootstrap.verify_hostkey_fingerprint_error: "❌ Fingerprint konnte nicht berechnet werden" -bootstrap.verify_hostkey_warning: "⚠️ Bitte überprüfen Sie, ob dieser Fingerprint mit dem Host-Key Ihres Servers übereinstimmt." -bootstrap.verify_hostkey_check_command: "Sie können ihn auf dem Server überprüfen mit:" +bootstrap.verify_hostkey_fingerprint_error: "❌ Fingerprint konnte nicht berechnet + werden" +bootstrap.verify_hostkey_warning: "⚠️ Bitte überprüfen Sie, ob dieser Fingerprint + mit dem Host-Key Ihres Servers übereinstimmt." +bootstrap.verify_hostkey_check_command: "Sie können ihn auf dem Server überprüfen + mit:" bootstrap.verify_hostkey_ssh_keygen: "ssh-keygen -lf /etc/ssh/ssh_host_*_key.pub" bootstrap.verify_hostkey_accept: "✓ Akzeptieren & Fortfahren" bootstrap.verify_hostkey_reject: "✗ Ablehnen" -bootstrap.verify_hostkey_copy_hint: "'c' drücken, um Verify-Befehl in die Zwischenablage zu kopieren" +bootstrap.verify_hostkey_copy_hint: "'c' drücken, um Verify-Befehl in die Zwischenablage + zu kopieren" bootstrap.verify_hostkey_copied: "✓ Verify-Befehl in Zwischenablage kopiert!" -bootstrap.verify_hostkey_help: "Pfeile/h/l: navigieren • Enter: auswählen • c: Verify-Befehl kopieren • r: wiederholen • Tab: zurück • Esc: abbrechen" +bootstrap.verify_hostkey_help: "Pfeile/h/l: navigieren • Enter: auswählen • c: Verify-Befehl + kopieren • r: wiederholen • Tab: zurück • Esc: abbrechen" bootstrap.select_keys_title: "Schlüssel zum Ausrollen auswählen" -bootstrap.select_keys_description: "Wählen Sie, welche öffentlichen Schlüssel diesem Konto zugewiesen werden sollen:" -bootstrap.select_keys_help: "(j/k zum Navigieren, Leertaste zum Umschalten, Enter zum Fortfahren)" +bootstrap.select_keys_description: "Wählen Sie, welche öffentlichen Schlüssel diesem + Konto zugewiesen werden sollen:" +bootstrap.select_keys_help: "(j/k zum Navigieren, Leertaste zum Umschalten, Enter + zum Fortfahren)" bootstrap.confirm_deploy_title: "Ausrollen bestätigen" -bootstrap.will_replace_temp_key: "Wird temporären Schlüssel durch Systemschlüssel ersetzen" +bootstrap.will_replace_temp_key: "Wird temporären Schlüssel durch Systemschlüssel + ersetzen" bootstrap.deploy: "Ausrollen" bootstrap.back: "Zurück" bootstrap.confirm_deploy_help: "(links/rechts zum Navigieren, Enter zum Bestätigen)" -bootstrap.deploying_title: "Wird ausgerollt..." -bootstrap.deploying_progress: "Schlüssel werden ausgerollt und Konto-Einrichtung wird abgeschlossen..." +bootstrap.deploying_title: "Wird ausgerollt…" +bootstrap.deploying_progress: "Schlüssel werden ausgerollt und Konto-Einrichtung wird + abgeschlossen…" bootstrap.deploying_help: "(bitte warten, nicht unterbrechen)" bootstrap.success_title: "Bootstrap abgeschlossen" bootstrap.success_message: "%s@%s erfolgreich gebootstrappt" @@ -392,43 +455,62 @@ bootstrap.error_options: "Wählen Sie, wie Sie fortfahren möchten:" bootstrap.error_retry: "Wiederholen" bootstrap.error_regenerate: "Neuen Schlüssel generieren" bootstrap.error_cancel: "Bootstrap abbrechen" -bootstrap.error_help: "(links/rechts zum Navigieren, Enter zum Bestätigen, Esc zum Abbrechen)" +bootstrap.error_help: "(links/rechts zum Navigieren, Enter zum Bestätigen, Esc zum + Abbrechen)" # Config config.error_init_db: "Fehler beim Initialisieren der Datenbank: %w" # Deploy CLI command deploy.cli_account_not_found: "Konto '%s' nicht gefunden oder nicht aktiv." -deploy.error_get_bootstrap_key: "Fehler beim Abrufen des aktiven Systemschlüssels für Bootstrap: %w" +deploy.error_get_bootstrap_key: "Fehler beim Abrufen des aktiven Systemschlüssels + für Bootstrap: %w" deploy.error_no_bootstrap_key: "Kein aktiver Systemschlüssel für Bootstrap gefunden" -deploy.error_get_serial_key: "Fehler beim Abrufen des Systemschlüssels mit Seriennummer %d: %w" -deploy.error_no_serial_key: "DB-Inkonsistenz: kein Systemschlüssel für Seriennummer %d gefunden" -deploy.error_get_active_key_for_serial: "Aktiver Systemschlüssel für Seriennummer-Update konnte nicht abgerufen werden" +deploy.error_get_serial_key: "Fehler beim Abrufen des Systemschlüssels mit Seriennummer + %d: %w" +deploy.error_no_serial_key: "DB-Inkonsistenz: kein Systemschlüssel für Seriennummer + %d gefunden" +deploy.error_get_active_key_for_serial: "Aktiver Systemschlüssel für Seriennummer-Update + konnte nicht abgerufen werden" deploy.error_connection_failed: "Verbindung fehlgeschlagen: %w" -deploy.error_no_bootstrap_key_tui: "Kein aktiver Systemschlüssel für Bootstrap gefunden. Bitte einen erzeugen" -deploy.error_no_serial_key_tui: "DB-Inkonsistenz: kein Systemschlüssel für Seriennummer %d auf Konto %s gefunden" +deploy.error_no_bootstrap_key_tui: "Kein aktiver Systemschlüssel für Bootstrap gefunden. + Bitte einen erzeugen" +deploy.error_no_serial_key_tui: "DB-Inkonsistenz: kein Systemschlüssel für Seriennummer + %d auf Konto %s gefunden" deploy.error_connection_failed_tui: "Verbindung zu %s fehlgeschlagen: %w" -deploy.error_get_serial_for_status: "Deployment erfolgreich, aber neue Seriennummer konnte für Statusmeldung nicht gelesen werden: %w" +deploy.error_get_serial_for_status: "Deployment erfolgreich, aber neue Seriennummer + konnte für Statusmeldung nicht gelesen werden: %w" deploy.error_deployment_failed: "Deployment fehlgeschlagen: %w" # Trust Host CLI command -trust_host.retrieving_key: "Versuche, Host-Schlüssel von %s abzurufen..." +trust_host.retrieving_key: "Versuche, Host-Schlüssel von %s abzurufen…" trust_host.error_get_key: "Host-Schlüssel konnte nicht abgerufen werden: %v" -trust_host.authenticity_warning_1: "Die Authentizität des Hosts '%s' kann nicht festgestellt werden." +trust_host.authenticity_warning_1: "Die Authentizität des Hosts '%s' kann nicht festgestellt + werden." trust_host.authenticity_warning_2: "%s Schlüssel-Fingerabdruck ist %s." -trust_host.confirm_prompt: "Sind Sie sicher, dass Sie die Verbindung fortsetzen möchten (ja/nein)? " +trust_host.confirm_prompt: "Sind Sie sicher, dass Sie die Verbindung fortsetzen möchten + (ja/nein)? " trust_host.not_trusted_abort: "Host-Schlüssel nicht vertraut. Abbruch." -trust_host.error_save_key: "Fehler beim Speichern des Host-Schlüssels in der Datenbank: %v" -trust_host.added_success: "Warnung: '%s' (Typ %s) wurde dauerhaft zur Liste der bekannten Hosts hinzugefügt." +trust_host.error_save_key: "Fehler beim Speichern des Host-Schlüssels in der Datenbank: + %v" +trust_host.added_success: "Warnung: '%s' (Typ %s) wurde dauerhaft zur Liste der bekannten + Hosts hinzugefügt." # Migrate CLI command migrate.cli_error_flags: "Fehler: --type und --dsn Flags sind für die Migration erforderlich." migrate.cli_starting_backup: "📦 Erstelle In-Memory-Backup von der Quelldatenbank..." -migrate.cli_error_backup: "Fehler beim Exportieren der Daten aus der Quelldatenbank: %v" +migrate.cli_error_backup: "Fehler beim Exportieren der Daten aus der Quelldatenbank: + %v" migrate.cli_backup_success: "✅ Backup abgeschlossen." -migrate.cli_connecting_target: "🔗 Verbinde mit Zieldatenbank (%s) und führe Migrationen aus..." -migrate.cli_error_connect: "Fehler beim Verbinden oder Migrieren der Zieldatenbank: %v" +migrate.cli_connecting_target: "🔗 Verbinde mit Zieldatenbank (%s) und führe Migrationen + aus..." +migrate.cli_error_connect: "Fehler beim Verbinden oder Migrieren der Zieldatenbank: + %v" migrate.cli_connect_success: "✅ Verbindung und Migrationen erfolgreich." migrate.cli_starting_restore: "🔄 Stelle Daten in der Zieldatenbank wieder her..." -migrate.cli_error_restore: "Fehler beim Importieren der Daten in die Zieldatenbank: %v" -migrate.cli_success: "🎉 Migration abgeschlossen! Alle Daten wurden in die neue Datenbank verschoben." -migrate.cli_next_steps: "➡️ Nächster Schritt: Aktualisieren Sie Ihre .keymaster.yaml, um auf die neue Datenbank zu verweisen." +migrate.cli_error_restore: "Fehler beim Importieren der Daten in die Zieldatenbank: + %v" +migrate.cli_success: "🎉 Migration abgeschlossen! Alle Daten wurden in die neue Datenbank + verschoben." +migrate.cli_next_steps: "➡️ Nächster Schritt: Aktualisieren Sie Ihre .keymaster.yaml, + um auf die neue Datenbank zu verweisen." +audit.tui.fleet_complete: Audit der Flotte komplett. diff --git a/internal/i18n/locales/active.en.yaml b/internal/i18n/locales/active.en.yaml index 124f860..eab6fb0 100644 --- a/internal/i18n/locales/active.en.yaml +++ b/internal/i18n/locales/active.en.yaml @@ -28,8 +28,8 @@ dashboard.system_key.active: "Active (Serial #%d)" dashboard.security_posture: "Security Posture" dashboard.accounts: "Managed Accounts: %d (%d active)" dashboard.key_type_spread: "Key-Type Spread: %s" -dashboard.public_keys: " Public Keys: %d (%d global)" -dashboard.system_key: " System Key: %s" +dashboard.public_keys: "Public Keys: %d (%d global)" +dashboard.system_key: "System Key: %s" dashboard.recent_activity: "Recent Activity" dashboard.no_recent_activity: "No recent activity." dashboard.footer: "j/k up/down: navigate enter: select q: quit L: language" @@ -40,13 +40,13 @@ language.help: "up/down: navigate enter: select q/esc: back" # Account form translations -account_form.username_label: "Username: " +account_form.username_label: "Username:" account_form.username_placeholder: "user" -account_form.hostname_label: "Hostname: " +account_form.hostname_label: "Hostname:" account_form.hostname_placeholder: "www.example.com" -account_form.label_label: "Label (optional): " +account_form.label_label: "Label (optional):" account_form.label_placeholder: "prod-web-01" -account_form.tags_label: "Tags (comma-separated): " +account_form.tags_label: "Tags (comma-separated):" account_form.tags_placeholder: "role:db,dc:nyc" account_form.edit_title: "Edit Account" account_form.add_title: "Add New Account" @@ -63,11 +63,11 @@ accounts.empty: "No accounts found. Press 'a' to add one." accounts.empty_filtered: "No accounts match your filter." accounts.filtering: "Filter: %s█" accounts.filter_active: "Filter: %s (press 'esc' to clear)" -accounts.filter_hint: "Press / to filter..." +accounts.filter_hint: "Press / to filter…" accounts.footer: "(a)dd (e)dit (d)elete (t)oggle (v)erify (i)mport (q)uit" accounts.list_title: "Accounts" accounts.status.modified_success: "Successfully modified account." -accounts.status.trust_attempt: "Attempting to trust host %s..." +accounts.status.trust_attempt: "Attempting to trust host %s…" accounts.status.import_assigned: "Assigned %d imported keys." accounts.status.import_skipped_assign: "Imported %d new keys without assigning." accounts.status.delete_cancelled: "Deletion cancelled." @@ -77,8 +77,8 @@ accounts.status.verify_success: "Successfully trusted host key for %s." accounts.status.import_fail: "Import failed: %v" accounts.status.import_no_new: "No new keys found to import. Skipped %d duplicates." accounts.status.toggle_success: "Toggled status for: %s" -accounts.status.verify_start: "Verifying host key for %s..." -accounts.status.import_start: "Importing keys from %s..." +accounts.status.verify_start: "Verifying host key for %s…" +accounts.status.import_start: "Importing keys from %s…" accounts.import_confirm.title: "✨ Remote Import Confirmation" accounts.import_confirm.found_keys: "Found %d new public keys on the remote host:" accounts.import_confirm.question: "Assign these %d new keys to this account? (y/n)" @@ -114,7 +114,7 @@ public_keys.empty: "No public keys found. Press 'a' to add one." public_keys.empty_filtered: "No keys match your filter." public_keys.filtering: "Filter: %s█" public_keys.filter_active: "Filter: %s (press 'esc' to clear)" -public_keys.filter_hint: "Press / to filter..." +public_keys.filter_hint: "Press / to filter…" public_keys.footer: "(a)dd (c)opy (d)elete (g)lobal toggle (u)sage (q)uit" public_keys.list_title: "Public Keys" public_keys.detail_comment: "Comment: %s" @@ -143,7 +143,7 @@ public_keys.usage_report.help: "(esc or q to go back)" # Public key form translations public_key_form.add_title: "Add New Public Key" public_key_form.prompt: "Paste Public Key: " -public_key_form.placeholder: "ssh-ed25519 AAAA... user@host" +public_key_form.placeholder: "ssh-ed25519 AAAA… user@host" public_key_form.checkbox_unchecked: "[ ] Set as Global Key (will be deployed to all accounts)" public_key_form.checkbox_checked: "[x] Set as Global Key (will be deployed to all accounts)" public_key_form.help: "(tab to navigate, space/enter to toggle checkbox, enter on input to submit, esc to cancel)" @@ -155,7 +155,7 @@ assign_keys.help_bar_accounts: "j/k up/down: navigate enter: select /: searc assign_keys.help_bar_keys: "j/k up/down: navigate space: toggle /: search esc: back q: quit" assign_keys.accounts_title: "Accounts" assign_keys.no_accounts: "No accounts found. Press 'a' to add one." -assign_keys.search_hint: "Press / to search..." +assign_keys.search_hint: "Press / to search…" assign_keys.keys_title: "Assign Keys for: %s" assign_keys.keys_title_short: "Keys" assign_keys.no_keys: "No public keys found. Press 'a' to add one." @@ -181,12 +181,12 @@ rotate_key.confirm_rotate_title: "⚠️ Confirm System Key Rotation" rotate_key.confirm_rotate_question: "Are you sure you want to rotate the system key?\n\nThis will create a new key.\n\nThis action affects ALL managed hosts.\nYou should deploy the new key to your fleet immediately after rotation.\n\nOnly proceed if you know what you are doing!" rotate_key.confirm_generate_title: "⚙️ Generate Initial System Key" rotate_key.confirm_generate_question: "No Keymaster system key found.\n\nThis key is required for Keymaster to connect to managed hosts.\n\nWould you like to generate one now?" -rotate_key.checking: "Checking for existing system key..." -rotate_key.generating: "Generating new ed25519 key pair, please wait..." +rotate_key.checking: "Checking for existing system key…" +rotate_key.generating: "Generating new ed25519 key pair, please wait…" rotate_key.generated: "✅ Successfully generated and saved system key with serial #%d." rotate_key.bootstrap_header: "🚨 BOOTSTRAP ACTION REQUIRED 🚨" rotate_key.bootstrap_body: "You must now manually add the following public key to the `authorized_keys` file for every account you intend to manage with Keymaster:" -rotate_key.rotating: "Rotating system key, please wait..." +rotate_key.rotating: "Rotating system key, please wait…" rotate_key.rotated: "✅ Successfully rotated system key. The new active key is serial #%d." rotate_key.deploy_reminder: "Deploy to your fleet to apply the new key." rotate_key.error_generate: "failed to generate system key: %w" @@ -204,7 +204,7 @@ tags_view.title: "Accounts by Tag" tags_view.empty: "No tags or accounts found. Add tags to accounts to see them here." tags_view.filtering: "Filter: %s█" tags_view.filter_active: "Filter: %s (press 'esc' to clear)" -tags_view.filter_hint: "Press / to filter..." +tags_view.filter_hint: "Press / to filter…" tags_view.footer: "(enter) expand/collapse /: filter esc: back q: quit" # Deployment view translations @@ -216,32 +216,32 @@ deploy.title: "🚀 Deploy to Fleet" deploy.select_account: "🚀 Deploy: Select Account" deploy.select_tag: "🚀 Deploy: Select Tag" deploy.show_keys: "📄 authorized_keys for %s" -deploy.deploying_fleet: "🚀 Deploying to Fleet..." -deploy.deploying: "🚀 Deploying..." +deploy.deploying_fleet: "🚀 Deploying to Fleet…" +deploy.deploying: "🚀 Deploying…" deploy.complete: "✅ Deployment Complete" deploy.failed: "💥 Deployment Failed" deploy.no_accounts: "No active accounts found. Please add one or enable an existing one." deploy.no_tags: "No tags found in any accounts." deploy.no_accounts_tag: "No active accounts with tag '%s' to deploy to." -deploy.starting_fleet: "Starting fleet deployment..." -deploy.starting_tag: "Starting deployment to tag '%s'..." -deploy.deploying_to: "Deploying to %s..." +deploy.starting_fleet: "Starting fleet deployment…" +deploy.starting_tag: "Starting deployment to tag '%s'…" +deploy.deploying_to: "Deploying to %s…" deploy.success: "Successfully deployed to %s and updated serial to #%d." deploy.fleet_complete: "Fleet deployment complete." deploy.summary: "\n\nSummary: %d successful, %d failed." deploy.failed_accounts: "\n\nFailed Deployments:\n%s" -deploy.pending: "pending..." +deploy.pending: "pending…" deploy.failed_short: "failed" deploy.success_short: "success" deploy.help_menu: "j/k or up/down: navigate enter: select q: quit" deploy.help_select: "enter: select esc: back" deploy.help_keys: "c: copy esc: back" -deploy.help_wait: "(Please wait...)" +deploy.help_wait: "(Please wait…)" deploy.help_complete: "(press enter or esc to continue)" deploy.help_failed: "esc: back" deploy.filtering: "Filter: %s█" deploy.filter_active: "Filter: %s (press 'esc' to clear)" -deploy.filter_hint: "Press / to filter..." +deploy.filter_hint: "Press / to filter…" deploy.status.copy_success: "Copied authorized_keys content to clipboard." deploy.status.copy_failed: "Failed to copy to clipboard: %s" @@ -267,28 +267,28 @@ audit.tui.mode_serial: "Serial (header only)" audit.tui.help_menu: "j/k or up/down: navigate enter: select m: toggle mode q: quit" audit.tui.select_account: "🔎 Audit: Select Account" audit.tui.select_tag: "🔎 Audit: Select Tag" -audit.tui.auditing_fleet: "🔎 Auditing Fleet..." -audit.tui.auditing: "🔎 Auditing..." +audit.tui.auditing_fleet: "🔎 Auditing Fleet…" +audit.tui.auditing: "🔎 Auditing…" audit.tui.complete: "✅ Audit Complete" audit.tui.failed: "💥 Audit Failed" audit.tui.no_accounts: "No active accounts found. Please add one or enable an existing one." audit.tui.no_tags: "No tags found in any accounts." audit.tui.no_accounts_tag: "No active accounts with tag '%s' to audit." -audit.tui.starting_fleet: "Starting fleet audit..." -audit.tui.starting_tag: "Starting audit for tag '%s'..." +audit.tui.starting_fleet: "Starting fleet audit…" +audit.tui.starting_tag: "Starting audit for tag '%s'…" audit.tui.ok_short: "ok" audit.tui.failed_short: "drift" -audit.tui.pending: "pending..." +audit.tui.pending: "pending…" audit.tui.summary: "\n\nSummary: %d ok, %d drift." audit.tui.failed_accounts: "\n\nDrift Detected:\n%s" audit.tui.help_select: "enter: select esc: back" -audit.tui.help_wait: "(Please wait...)" +audit.tui.help_wait: "(Please wait…)" audit.tui.help_complete: "(press enter or esc to continue)" audit.tui.help_failed: "esc: back" audit.tui.filtering: "Filter: %s█" audit.tui.filter_active: "Filter: %s (press 'esc' to clear)" -audit.tui.filter_hint: "Press / to filter..." - +audit.tui.filter_hint: "Press / to filter…" +audit.tui.fleet_complete: "Fleet audit complete." # CLI specific translations # Audit CLI command @@ -302,16 +302,16 @@ audit.error_generate_expected: "could not generate expected keys content for com audit.error_drift_detected: "content drift detected. Remote file does not match the expected configuration" # Rotate Key CLI command -rotate_key.cli_rotating: "⚙️ Rotating system key..." +rotate_key.cli_rotating: "⚙️ Rotating system key…" rotate_key.cli_error_generate: "Error generating new system key: %w" rotate_key.cli_error_save: "Error saving rotated key to database: %w" rotate_key.cli_rotated_success: "\n✅ Successfully rotated system key. The new active key is serial #%d." rotate_key.cli_deploy_reminder: "Run 'keymaster deploy' to apply the new key to your fleet." # Import CLI command -import.start: "🔑 Importing keys from %s..." +import.start: "🔑 Importing keys from %s…" import.skip_invalid_line: " - Skipping invalid key line: %s" -import.skip_empty_comment: " - Skipping key with empty comment: %.20s..." +import.skip_empty_comment: " - Skipping key with empty comment: %.20s…" import.skip_duplicate: " - Skipping duplicate key (comment exists): %s" import.error_opening_file: "Error opening file: %v" import.error_adding_key: " - Error adding key '%s': %v" @@ -327,7 +327,7 @@ export_ssh_config.error_write_file: "Error writing file: %v" export_ssh_config.success: "✅ Successfully exported SSH config to %s" # Bootstrap workflow translations -bootstrap.creating_session: "Creating Bootstrap Session..." +bootstrap.creating_session: "Creating Bootstrap Session…" bootstrap.title: "Bootstrap New Host" bootstrap.step1_description: "Paste the following command on the target host to install the temporary key:" bootstrap.step1_copy_hint: "Press 'c' to copy command to clipboard" @@ -349,8 +349,8 @@ bootstrap.will_replace_temp_key: "Will replace temporary key with system key" bootstrap.deploy: "Deploy" bootstrap.back: "Back" bootstrap.confirm_deploy_help: "(left/right to navigate, enter to confirm)" -bootstrap.deploying_title: "Deploying..." -bootstrap.deploying_progress: "Deploying keys and finalizing account setup..." +bootstrap.deploying_title: "Deploying…" +bootstrap.deploying_progress: "Deploying keys and finalizing account setup…" bootstrap.deploying_help: "(please wait, do not interrupt)" bootstrap.success_title: "Bootstrap Complete" bootstrap.success_message: "Successfully bootstrapped %s@%s" @@ -387,7 +387,7 @@ deploy.error_get_serial_for_status: "deployment succeeded, but could not get new deploy.error_deployment_failed: "deployment failed: %w" # Trust Host CLI command -trust_host.retrieving_key: "Attempting to retrieve host key from %s..." +trust_host.retrieving_key: "Attempting to retrieve host key from %s…" trust_host.error_get_key: "Could not get host key: %v" trust_host.authenticity_warning_1: "The authenticity of host '%s' can't be established." trust_host.authenticity_warning_2: "%s key fingerprint is %s." @@ -398,7 +398,7 @@ trust_host.added_success: "Warning: Permanently added '%s' (type %s) to the list # Parallel task messages (used by runParallelTasks) parallel_task.no_accounts: "No active accounts for %s." -parallel_task.start_message: "🚀 Starting %s to %d account(s)..." +parallel_task.start_message: "🚀 Starting %s to %d account(s)…" parallel_task.deploy_success_message: "✅ Successfully deployed to %s" parallel_task.deploy_fail_message: "💥 Failed to deploy to %s: %v" parallel_task.audit_success_message: "✅ OK: %s" diff --git a/internal/tui/tui.go b/internal/tui/tui.go index 7797e8a..e134366 100644 --- a/internal/tui/tui.go +++ b/internal/tui/tui.go @@ -455,26 +455,65 @@ func (m menuModel) View(data dashboardData, width, height int) string { if data.systemKeySerial > 0 { sysKeyStatus = successStyle.Render(i18n.T("dashboard.system_key.active", data.systemKeySerial)) } - dashboardItems = append(dashboardItems, lipgloss.JoinVertical(lipgloss.Left, - i18n.T("dashboard.accounts", data.accountCount, data.activeAccountCount), // - i18n.T("dashboard.public_keys", data.publicKeyCount, data.globalKeyCount), // - i18n.T("dashboard.system_key", sysKeyStatus), // - )) + + // --- Refactored Status Items with dynamic padding --- + // Define labels and values separately to calculate padding + statusItems := []struct { + label string + value string + }{ + {i18n.T("dashboard.accounts"), fmt.Sprintf("%d (%d active)", data.accountCount, data.activeAccountCount)}, + {i18n.T("dashboard.public_keys"), fmt.Sprintf("%d (%d global)", data.publicKeyCount, data.globalKeyCount)}, + {i18n.T("dashboard.system_key"), sysKeyStatus}, + } + + // Extract just the label part of the string (before the first '%') + var labelsOnly []string + for _, item := range statusItems { + labelPart := item.label + if idx := strings.Index(labelPart, "%"); idx != -1 { + labelPart = labelPart[:idx] + } + labelsOnly = append(labelsOnly, labelPart) + } + + // Find the longest label to align all values + maxLabelLen := 0 + for _, label := range labelsOnly { + if len(label) > maxLabelLen { + maxLabelLen = len(label) + } + } + + for i, label := range labelsOnly { + padding := strings.Repeat(" ", maxLabelLen-len(label)) + dashboardItems = append(dashboardItems, fmt.Sprintf("%s%s%s", label, padding, statusItems[i].value)) + } // Deployment Status dashboardItems = append(dashboardItems, "", "", paneTitleStyle.Render(i18n.T("dashboard.deployment_status")), "") - currentKeyLine := successStyle.Render(i18n.T("dashboard.hosts_current_key", data.hostsUpToDate)) - pastKeysLine := i18n.T("dashboard.hosts_past_keys", data.hostsOutdated) + currentKeyLine := i18n.T("dashboard.hosts_current_key", data.hostsUpToDate) + pastKeysLine := i18n.T("dashboard.hosts_past_keys", data.hostsOutdated) // This line no longer needs manual padding + + // Apply styles after calculating layout + styledCurrentKeyLine := successStyle.Render(currentKeyLine) if data.hostsOutdated > 0 { pastKeysLine = specialStyle.Render(pastKeysLine) } - dashboardItems = append(dashboardItems, currentKeyLine, pastKeysLine) + // Find the longest line to align the second line + maxDeployLen := lipgloss.Width(styledCurrentKeyLine) + deployPadding := "" + if maxDeployLen > lipgloss.Width(pastKeysLine) { + deployPadding = strings.Repeat(" ", maxDeployLen-lipgloss.Width(pastKeysLine)) + } + dashboardItems = append(dashboardItems, styledCurrentKeyLine, deployPadding+pastKeysLine) // Security Posture dashboardItems = append(dashboardItems, "", "", paneTitleStyle.Render(i18n.T("dashboard.security_posture")), "") var postureItems []string // Add color to key algo breakdown - postureItems = append(postureItems, lipgloss.JoinHorizontal(lipgloss.Left, i18n.T("dashboard.key_type_spread", ""), data.keyAlgoBreakdown)) + keyTypeLabel := i18n.T("dashboard.key_type_spread", "") + postureItems = append(postureItems, lipgloss.JoinHorizontal(lipgloss.Left, keyTypeLabel, data.keyAlgoBreakdown)) dashboardItems = append(dashboardItems, postureItems...) // Recent Activity (moved down)