From bb46e295d1483060e36db3b0b50e9d3635c813f9 Mon Sep 17 00:00:00 2001 From: Softer Date: Fri, 24 Apr 2026 13:19:01 +0300 Subject: [PATCH 01/61] Fix OVMF paths in README QEMU examples (#4472) All three qemu-system-x86_64 examples in README pointed both -drive if=pflash entries at the same file (/usr/share/ovmf/x64/OVMF.4m.fd). OVMF needs CODE for the first pflash and VARS for the second; as written, EFI NVRAM is not initialized correctly. The path /usr/share/ovmf/x64/ is also stale - the ovmf package has been replaced by edk2-ovmf, which installs under /usr/share/edk2/x64/. Fix both in the three examples (testimage loop section, base Boot ISO block, espeakup variant). --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 1447c263..75631b6b 100644 --- a/README.md +++ b/README.md @@ -179,7 +179,7 @@ This can be done by installing `pacman -S arch-install-scripts util-linux` local # losetup --partscan --show ./testimage.img # pip install --upgrade archinstall # python -m archinstall --script guided - # qemu-system-x86_64 -enable-kvm -machine q35,accel=kvm -device intel-iommu -cpu host -m 4096 -boot order=d -drive file=./testimage.img,format=raw -drive if=pflash,format=raw,readonly,file=/usr/share/ovmf/x64/OVMF.4m.fd -drive if=pflash,format=raw,readonly,file=/usr/share/ovmf/x64/OVMF.4m.fd + # qemu-system-x86_64 -enable-kvm -machine q35,accel=kvm -device intel-iommu -cpu host -m 4096 -boot order=d -drive file=./testimage.img,format=raw -drive if=pflash,format=raw,readonly,file=/usr/share/edk2/x64/OVMF_CODE.4m.fd -drive if=pflash,format=raw,readonly,file=/usr/share/edk2/x64/OVMF_VARS.4m.fd This will create a *20 GB* `testimage.img` and create a loop device which we can use to format and install to.
`archinstall` is installed and executed in [guided mode](#docs-todo). Once the installation is complete, ~~you can use qemu/kvm to boot the test media.~~
@@ -199,8 +199,8 @@ You may want to boot an ISO image in a VM to test `archinstall` in there. qemu-system-x86_64 -enable-kvm \ -machine q35,accel=kvm -device intel-iommu \ -cpu host -m 4096 -boot order=d \ --drive if=pflash,format=raw,readonly,file=/usr/share/ovmf/x64/OVMF.4m.fd \ --drive if=pflash,format=raw,readonly,file=/usr/share/ovmf/x64/OVMF.4m.fd \ +-drive if=pflash,format=raw,readonly,file=/usr/share/edk2/x64/OVMF_CODE.4m.fd \ +-drive if=pflash,format=raw,readonly,file=/usr/share/edk2/x64/OVMF_VARS.4m.fd \ -drive file=./archlinux-2025.12.01-x86_64.iso,format=raw ``` @@ -209,8 +209,8 @@ HINT: For espeakup support qemu-system-x86_64 -enable-kvm \ -machine q35,accel=kvm -device intel-iommu \ -cpu host -m 4096 -boot order=d \ --drive if=pflash,format=raw,readonly,file=/usr/share/ovmf/x64/OVMF.4m.fd \ --drive if=pflash,format=raw,readonly,file=/usr/share/ovmf/x64/OVMF.4m.fd \ +-drive if=pflash,format=raw,readonly,file=/usr/share/edk2/x64/OVMF_CODE.4m.fd \ +-drive if=pflash,format=raw,readonly,file=/usr/share/edk2/x64/OVMF_VARS.4m.fd \ -drive file=./archlinux-2025.12.01-x86_64.iso,format=raw \ -device intel-hda -device hda-duplex,audiodev=snd0 \ -audiodev pa,id=snd0,server=/run/user/1000/pulse/native From c8e5c85e20db591617cee0fce173b259b213acd6 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 26 Apr 2026 18:35:31 +1000 Subject: [PATCH 02/61] Update dependency ruff to v0.15.12 (#4476) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index dd42c642..5d34f793 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,7 +37,7 @@ dev = [ "mypy==1.20.2", "flake8==7.3.0", "pre-commit==4.6.0", - "ruff==0.15.11", + "ruff==0.15.12", "pylint==4.0.5", "pytest==9.0.3", ] From efda78cad5a076ac1a39695c82a8ab93ed7c9342 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 26 Apr 2026 18:36:10 +1000 Subject: [PATCH 03/61] Update pre-commit hook astral-sh/ruff-pre-commit to v0.15.12 (#4477) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a496557e..7ee79056 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,7 +1,7 @@ default_stages: ['pre-commit'] repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.15.11 + rev: v0.15.12 hooks: # fix unused imports and sort them - id: ruff From 3c4c87bdd6e05d246da2762d91983d9fa1ff0bda Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 26 Apr 2026 19:03:16 +1000 Subject: [PATCH 04/61] Update dependency arch/python-cryptography to v47 (#4478) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 5d34f793..95d85144 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,7 +20,7 @@ classifiers = [ dependencies = [ "pyparted==3.13.0", "pydantic==2.12.5", - "cryptography==46.0.7", + "cryptography==47.0.0", "textual==8.2.4", "markdown-it-py==4.0.0", "linkify-it-py==2.1.0", From d836ab0a6690c13a6d7a97f62e30837a2873b864 Mon Sep 17 00:00:00 2001 From: Softer Date: Sun, 26 Apr 2026 12:14:23 +0300 Subject: [PATCH 05/61] Extend validate_bootloader_layout with UEFI-dependent checks (#4474) * Extend validate_bootloader_layout with UEFI-dependent checks Add is_uefi parameter and three new validations: Systemd-boot, Efistub and rEFInd require UEFI; Efistub additionally requires a FAT boot partition. Move the rEFInd UEFI-only check out of GlobalMenu so guided.py and Installer silent-install paths get the same coverage. * Encapsulate UEFI-only flag in Bootloader enum Replace module-level _UEFI_ONLY_BOOTLOADERS tuple with an is_uefi_only() method on the Bootloader enum, mirroring the existing has_uki_support() / has_removable_support() pattern. * Drop is_uefi parameter from validate_bootloader_layout The UEFI flag is a constant system fact for the run, so the validator retrieves it via SysInfo.has_uefi() directly instead of having every caller pass it in. Updates all three call sites in global_menu.py, installer.py and guided.py, and removes the now-unused SysInfo import from guided.py. --- archinstall/lib/bootloader/utils.py | 31 ++++++++++++++++++++++++---- archinstall/lib/global_menu.py | 5 ----- archinstall/lib/models/bootloader.py | 7 +++++++ 3 files changed, 34 insertions(+), 9 deletions(-) diff --git a/archinstall/lib/bootloader/utils.py b/archinstall/lib/bootloader/utils.py index a732f4e1..ed761722 100644 --- a/archinstall/lib/bootloader/utils.py +++ b/archinstall/lib/bootloader/utils.py @@ -2,6 +2,7 @@ from dataclasses import dataclass from enum import Enum, auto from pathlib import Path +from archinstall.lib.hardware import SysInfo from archinstall.lib.models.bootloader import Bootloader, BootloaderConfiguration from archinstall.lib.models.device import DiskLayoutConfiguration @@ -9,6 +10,8 @@ from archinstall.lib.models.device import DiskLayoutConfiguration class BootloaderValidationFailureKind(Enum): LimineNonFatBoot = auto() LimineLayout = auto() + BootloaderRequiresUefi = auto() + EfistubNonFatBoot = auto() @dataclass(frozen=True) @@ -29,12 +32,32 @@ def validate_bootloader_layout( if not (bootloader_config and disk_config): return None - if bootloader_config.bootloader == Bootloader.Limine: - boot_part = next( - (p for m in disk_config.device_modifications if (p := m.get_boot_partition())), - None, + bootloader = bootloader_config.bootloader + + if bootloader == Bootloader.NO_BOOTLOADER: + return None + + if bootloader.is_uefi_only() and not SysInfo.has_uefi(): + return BootloaderValidationFailure( + kind=BootloaderValidationFailureKind.BootloaderRequiresUefi, + description=f'{bootloader.value} requires a UEFI system.', ) + boot_part = next( + (p for m in disk_config.device_modifications if (p := m.get_boot_partition())), + None, + ) + + if bootloader == Bootloader.Efistub: + # The UEFI firmware reads the kernel directly from the boot partition, + # which must be FAT. + if boot_part and (boot_part.fs_type is None or not boot_part.fs_type.is_fat()): + return BootloaderValidationFailure( + kind=BootloaderValidationFailureKind.EfistubNonFatBoot, + description='Efistub does not support booting with a non-FAT boot partition.', + ) + + if bootloader == Bootloader.Limine: # Limine reads its config and kernels from the boot partition, which # must be FAT. if boot_part and (boot_part.fs_type is None or not boot_part.fs_type.is_fat()): diff --git a/archinstall/lib/global_menu.py b/archinstall/lib/global_menu.py index 3d94f31e..e327b388 100644 --- a/archinstall/lib/global_menu.py +++ b/archinstall/lib/global_menu.py @@ -461,8 +461,6 @@ class GlobalMenu(AbstractMenu[None]): if not bootloader_config or bootloader_config.bootloader == Bootloader.NO_BOOTLOADER: return None - bootloader = bootloader_config.bootloader - if disk_config := self._item_group.find_by_key('disk_config').value: for layout in disk_config.device_modifications: if root_partition := layout.get_root_partition(): @@ -490,9 +488,6 @@ class GlobalMenu(AbstractMenu[None]): if efi_partition.fs_type is None or not efi_partition.fs_type.is_fat(): return 'ESP must be formatted as a FAT filesystem' - if bootloader == Bootloader.Refind and not self._uefi: - return 'rEFInd can only be used on UEFI systems' - if failure := validate_bootloader_layout(bootloader_config, disk_config): return failure.description diff --git a/archinstall/lib/models/bootloader.py b/archinstall/lib/models/bootloader.py index 68c0bf98..5d8d0d80 100644 --- a/archinstall/lib/models/bootloader.py +++ b/archinstall/lib/models/bootloader.py @@ -25,6 +25,13 @@ class Bootloader(Enum): case _: return False + def is_uefi_only(self) -> bool: + match self: + case Bootloader.Systemd | Bootloader.Efistub | Bootloader.Refind: + return True + case _: + return False + def json(self) -> str: return self.value From 9e05260df55ed3a2ad3a3546521919317dda94c5 Mon Sep 17 00:00:00 2001 From: Softer Date: Mon, 27 Apr 2026 02:24:51 +0300 Subject: [PATCH 06/61] Fix argv injection in _create_user and gpasswd loop (#4473) * Fix argv injection in _create_user and gpasswd loop Use argv list with run() instead of f-string interpolation into SysCommand, add debug logging on failure. * Extract _chroot_argv helper and harden user/file ops Address svartkanin's review on #4473: factor the ['arch-chroot', '-S', str(self.target), ...] boilerplate into a private Installer._chroot_argv() helper, and migrate the seven existing argv-form call sites to it (useradd, gpasswd, chpasswd, chsh, chown, snapper-create-config, grub-install). Two related hardening tweaks while in the area: - Raise gpasswd failure log from debug() to warn(). The group-add loop has no return-False feedback channel for the caller, so a silent debug() means a half-configured user looks like a successful install. - Add `--` end-of-options separator for useradd and chown so a username or path starting with `-` cannot smuggle flags. The TUI validates usernames, but parse_arguments() in models/users.py does not, so config.json is the residual hole; this closes it for these two sites at zero cost. --- archinstall/lib/installer.py | 46 +++++++++++++++--------------------- 1 file changed, 19 insertions(+), 27 deletions(-) diff --git a/archinstall/lib/installer.py b/archinstall/lib/installer.py index 19560d75..e596f7fe 100644 --- a/archinstall/lib/installer.py +++ b/archinstall/lib/installer.py @@ -739,6 +739,9 @@ class Installer: return self.run_command(cmd, peek_output=peek_output) + def _chroot_argv(self, *args: str) -> list[str]: + return ['arch-chroot', '-S', str(self.target), *args] + def drop_to_shell(self) -> None: subprocess.check_call(f'arch-chroot {self.target}', shell=True) @@ -987,17 +990,7 @@ class Installer: } for config_name, mountpoint in snapper.items(): - command = [ - 'arch-chroot', - '-S', - str(self.target), - 'snapper', - '--no-dbus', - '-c', - config_name, - 'create-config', - mountpoint, - ] + command = self._chroot_argv('snapper', '--no-dbus', '-c', config_name, 'create-config', mountpoint) try: SysCommand(command, peek_output=True) @@ -1336,13 +1329,7 @@ class Installer: boot_dir = Path('/boot') - command = [ - 'arch-chroot', - '-S', - str(self.target), - 'grub-install', - '--debug', - ] + command = self._chroot_argv('grub-install', '--debug') if SysInfo.has_uefi(): if not efi_partition: @@ -1922,16 +1909,17 @@ class Installer: if not handled_by_plugin: info(f'Creating user {user.username}') - cmd = 'useradd -m' + cmd = self._chroot_argv('useradd', '-m') if user.sudo: - cmd += ' -G wheel' + cmd += ['-G', 'wheel'] - cmd += f' {user.username}' + cmd += ['--', user.username] try: - self.arch_chroot(cmd) - except SysCallError as err: + run(cmd) + except CalledProcessError as err: + debug(f'Error creating user {user.username}: {err}') raise SystemError(f'Could not create user inside installation: {err}') for plugin in plugins.values(): @@ -1942,7 +1930,11 @@ class Installer: self.set_user_password(user) for group in user.groups: - self.arch_chroot(f'gpasswd -a {user.username} {group}') + cmd = self._chroot_argv('gpasswd', '-a', user.username, group) + try: + run(cmd) + except CalledProcessError as err: + warn(f'Failed to add {user.username} to group {group}: {err}') if user.sudo: self.enable_sudo(user) @@ -1957,7 +1949,7 @@ class Installer: return False input_data = f'{user.username}:{enc_password}'.encode() - cmd = ['arch-chroot', '-S', str(self.target), 'chpasswd', '--encrypted'] + cmd = self._chroot_argv('chpasswd', '--encrypted') try: run(cmd, input_data=input_data) @@ -1969,7 +1961,7 @@ class Installer: def user_set_shell(self, user: str, shell: str) -> bool: info(f'Setting shell for {user} to {shell}') - cmd = ['arch-chroot', '-S', str(self.target), 'chsh', '-s', shell, user] + cmd = self._chroot_argv('chsh', '-s', shell, user) try: run(cmd) return True @@ -1979,7 +1971,7 @@ class Installer: def chown(self, owner: str, path: str, options: list[str] | None = None) -> bool: options = options or [] - cmd = ['arch-chroot', '-S', str(self.target), 'chown', *options, owner, path] + cmd = self._chroot_argv('chown', *options, '--', owner, path) try: run(cmd) return True From 210329ed808628cf11fa3a8543d8f5da33c5c4a5 Mon Sep 17 00:00:00 2001 From: Softer Date: Mon, 27 Apr 2026 02:30:20 +0300 Subject: [PATCH 07/61] Add console font selection to Locales menu (#4469) * Add console font selection to Locales menu Add a 4th menu item "Console font" to the Locales configuration, allowing users to select a console font for the target system. The selected font is written to /etc/vconsole.conf. If a terminus font (ter-*) is selected, the terminus-font package is automatically installed on the target system. * Switch list_console_fonts to pathlib and add @lru_cache Address svartkanin's review on #4469. Replace os.listdir + chained removesuffix with Path.glob('*.gz') + split('.')[0], and cache the result via lru_cache - the kbd consolefonts directory is static at runtime so re-scanning on every menu reopen was wasted I/O. --- archinstall/lib/installer.py | 8 +++--- archinstall/lib/locale/locale_menu.py | 31 +++++++++++++++++++++- archinstall/lib/locale/utils.py | 10 +++++++ archinstall/lib/models/locale.py | 11 +++++++- archinstall/locales/base.pot | 3 +++ archinstall/locales/uk/LC_MESSAGES/base.po | 3 +++ 6 files changed, 59 insertions(+), 7 deletions(-) diff --git a/archinstall/lib/installer.py b/archinstall/lib/installer.py index e596f7fe..ff530df0 100644 --- a/archinstall/lib/installer.py +++ b/archinstall/lib/installer.py @@ -1982,12 +1982,10 @@ class Installer: def set_vconsole(self, locale_config: LocaleConfiguration) -> None: # use the already set kb layout kb_vconsole: str = locale_config.kb_layout - # this is the default used in ISO other option for hdpi screens TER16x32 - # can be checked using - # zgrep "CONFIG_FONT" /proc/config.gz - # https://wiki.archlinux.org/title/Linux_console#Fonts + font_vconsole = locale_config.console_font - font_vconsole = 'default8x16' + if font_vconsole.startswith('ter-'): + self.pacman.strap(['terminus-font']) # Ensure /etc exists vconsole_dir: Path = self.target / 'etc' diff --git a/archinstall/lib/locale/locale_menu.py b/archinstall/lib/locale/locale_menu.py index c03824a1..dc743fd3 100644 --- a/archinstall/lib/locale/locale_menu.py +++ b/archinstall/lib/locale/locale_menu.py @@ -1,6 +1,6 @@ from typing import override -from archinstall.lib.locale.utils import list_keyboard_languages, list_locales, set_kb_layout +from archinstall.lib.locale.utils import list_console_fonts, list_keyboard_languages, list_locales, set_kb_layout from archinstall.lib.menu.abstract_menu import AbstractSubMenu from archinstall.lib.menu.helpers import Selection from archinstall.lib.models.locale import LocaleConfiguration @@ -47,6 +47,13 @@ class LocaleMenu(AbstractSubMenu[LocaleConfiguration]): preview_action=lambda item: item.get_value(), key='sys_enc', ), + MenuItem( + text=tr('Console font'), + action=select_console_font, + value=self._locale_conf.console_font, + preview_action=lambda item: item.get_value(), + key='console_font', + ), ] @override @@ -140,3 +147,25 @@ async def select_kb_layout(preset: str | None = None) -> str | None: return preset case _: raise ValueError('Unhandled return type') + + +async def select_console_font(preset: str | None = None) -> str | None: + fonts = list_console_fonts() + + items = [MenuItem(f, value=f) for f in fonts] + group = MenuItemGroup(items, sort_items=False) + group.set_focus_by_value(preset) + + result = await Selection[str]( + header=tr('Console font'), + group=group, + enable_filter=True, + ).show() + + match result.type_: + case ResultType.Selection: + return result.get_value() + case ResultType.Skip: + return preset + case _: + raise ValueError('Unhandled return type') diff --git a/archinstall/lib/locale/utils.py b/archinstall/lib/locale/utils.py index e7229160..497e1fcb 100644 --- a/archinstall/lib/locale/utils.py +++ b/archinstall/lib/locale/utils.py @@ -1,3 +1,6 @@ +from functools import lru_cache +from pathlib import Path + from archinstall.lib.command import SysCommand from archinstall.lib.exceptions import ServiceException, SysCallError from archinstall.lib.output import error @@ -26,6 +29,13 @@ def list_locales() -> list[str]: return locales +@lru_cache +def list_console_fonts() -> list[str]: + directory = Path('/usr/share/kbd/consolefonts') + fonts = {path.name.split('.')[0] for path in directory.glob('*.gz')} + return sorted(fonts) + + def list_x11_keyboard_languages() -> list[str]: return ( SysCommand( diff --git a/archinstall/lib/models/locale.py b/archinstall/lib/models/locale.py index 15dee2f6..8f580989 100644 --- a/archinstall/lib/models/locale.py +++ b/archinstall/lib/models/locale.py @@ -10,6 +10,11 @@ class LocaleConfiguration: kb_layout: str sys_lang: str sys_enc: str + # this is the default used in ISO other option for hdpi screens TER16x32 + # can be checked using + # zgrep "CONFIG_FONT" /proc/config.gz + # https://wiki.archlinux.org/title/Linux_console#Font + console_font: str = 'default8x16' @classmethod def default(cls) -> Self: @@ -23,12 +28,14 @@ class LocaleConfiguration: 'kb_layout': self.kb_layout, 'sys_lang': self.sys_lang, 'sys_enc': self.sys_enc, + 'console_font': self.console_font, } def preview(self) -> str: output = '{}: {}\n'.format(tr('Keyboard layout'), self.kb_layout) output += '{}: {}\n'.format(tr('Locale language'), self.sys_lang) - output += '{}: {}'.format(tr('Locale encoding'), self.sys_enc) + output += '{}: {}\n'.format(tr('Locale encoding'), self.sys_enc) + output += '{}: {}'.format(tr('Console font'), self.console_font) return output def _load_config(self, args: dict[str, str]) -> None: @@ -38,6 +45,8 @@ class LocaleConfiguration: self.sys_enc = args['sys_enc'] if 'kb_layout' in args: self.kb_layout = args['kb_layout'] + if 'console_font' in args: + self.console_font = args['console_font'] @classmethod def parse_arg(cls, args: dict[str, Any]) -> Self: diff --git a/archinstall/locales/base.pot b/archinstall/locales/base.pot index f707ec7a..a0c4c5b7 100644 --- a/archinstall/locales/base.pot +++ b/archinstall/locales/base.pot @@ -252,6 +252,9 @@ msgstr "" msgid "Locale encoding" msgstr "" +msgid "Console font" +msgstr "" + msgid "Drive(s)" msgstr "" diff --git a/archinstall/locales/uk/LC_MESSAGES/base.po b/archinstall/locales/uk/LC_MESSAGES/base.po index a178ac2d..ffab244f 100644 --- a/archinstall/locales/uk/LC_MESSAGES/base.po +++ b/archinstall/locales/uk/LC_MESSAGES/base.po @@ -256,6 +256,9 @@ msgstr "Мова локалізації" msgid "Locale encoding" msgstr "Кодування локалізації" +msgid "Console font" +msgstr "Шрифт консолі" + msgid "Drive(s)" msgstr "Диск(и)" From 3795bfc21a87224f9dd8e983a5a736b58cda06ad Mon Sep 17 00:00:00 2001 From: utuhiro78 <34818411+utuhiro78@users.noreply.github.com> Date: Mon, 27 Apr 2026 08:32:10 +0900 Subject: [PATCH 08/61] Update Japanese translation (#4482) --- archinstall/locales/ja/LC_MESSAGES/base.mo | Bin 68172 -> 72857 bytes archinstall/locales/ja/LC_MESSAGES/base.po | 230 ++++++++++++++++++++- 2 files changed, 219 insertions(+), 11 deletions(-) diff --git a/archinstall/locales/ja/LC_MESSAGES/base.mo b/archinstall/locales/ja/LC_MESSAGES/base.mo index 3ad3d1a276e7039db33fe23806d6b3fcdd109ba2..bfdce38d646ec876e4516067ceaba61ea4c87c39 100644 GIT binary patch delta 17972 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 delta 13924 zcmajlcX$=my2tU^sRTlY5L!s+ozMvo2!vij4K*Mg=|y_ml8A_ig`uhxkq*+f2na~A z(xiz>Mj#)=Z+Fg*$={&k6Eg$PqNx;piFQ zI0dk1PRFSl=s0DItCpj(yd3fupf2?#BukRmE{~ViGpTURVS-U>7`rxv*SS z$H|EGae(7^ot7l(unl#=9M#MXhGIX;JFz1c=I-rq0%pe_t(Pz><$D;00W}<_4TfVd zc1NDa>4O^JtC$BT`O99%nM0xxt;XEw!yI@3GvhhTgg0#YA?m_Gaps9KqApwrb>Yg` z6q{osoQrw!9G1d|SQMjcI!ZrB?&<)cwg zJ`E#qA@Xp}CS*088_4Q8(eb9-5erbBg?fOs@vJ|CaE?%+%@S6}SPofErwwW?XJCFj zi&~;bsHMqV*Oc?422c?-@Fu8%cSFtO80&P@11~_$%qMldj#HjwrR{JEHC0zp7rcX& zF*LzEaV^x^HMb5&J;_|u4Y#AF{y1tWZlY%Bi7jVg9krJVqS{M&N%Z>Eu)d1A-~tTA zov0=G8g=49)aLvd^`zHPH-2u*dFq=dE^CcP-6+YHyI~2+Ls8fD&LhziFG5Z2D%2G1 zM&0N;)Mh!2TJyW88+sDWfbyZ%v^Z+56R`?TKz#=`qOSK8^#DN)%+zN?2Ih5glW5l$ zH5E=p)PNFDBW#8&x6=lb@O{)2UqsE&W7HITc!e~;+^BLftcY<~83&;TxCC3_&)9?e zJH;C@{ZvfH!nhZ;Y0^Pc zGoT?@mU1e_;zrc@mrxJ#0K+giiTPJWRu)#TTQp|G>R21=V+QnM2Yd_ly|{>DF+U4A z23;J08C#q4N1^tD*ZLlYQeJ|AxC(Wjb*-8IStMUjp$|?2=1n)8gxzo&s$)88)7(Z4 zpiW!I>5CIk{Vrh-)weU>lO?Dn{SkxkH`D;GViexSf|%Lc-aL63)Cu*GZ;q3Uy764p z2}@D0;U45%=NDvOI%Sg05{$%f%9~JoU^iw#57Vaevtco;fF&>)GoyDhNeIbI)Cgx| zFn)-d!lkGu*o2vID@NcxY=*zu`XU`odj-^|x)y3CJEC6K9+(p+psxF_(d#TG(Tz7? zA^ZaMBtK(0Ot2FD;DL1!82{NFJS?_wn^)7gBwyJHo~X_!Or z|8|l*R2;IVV_wQnF%olkah#G^9(ALRsNJ206>t;A;w97!W$$XHwjgQ=%Ag*wChB_i ztz9vg`#YmZwAK?)BcFzPJ!V@!w(ToX7ut;4#XC?-vm0~ce(SFoK>0Rmjqjpv`~o#& zk$eQS1m)11jU=8#PnLx0I05sci`j7_YKHcq*8Vi=hIddi6~Ze}5u>pawnbfc9cm!E zun6wKF_@0Su}gP-|49yYH&gW+@`gEgP@Aj%E9R}3g_@D2sFANlJ;|4-0UyPFcnvj! zt$LUlO~HJW`=d5vDysiX)Z4bahu3VTZB%F~51~eW4E4JFhT2r2JE^0>Cpq6H*mqcrNzMo%!cn>wT;r-18i(yO3ai}$)jyiuCCg2_{h|a5KMxro6?|%Y`ZqO0+ zBt!fSJP~TDKDOocs3-c`mj6KYe~zUvVu1M_l7N*dkHg})9yK#3ZGGTC^8u=krMSN{ zn4~n$L#_Qj)C?R$jrb4Lje-Z6O<52%utd~Mw8LgN1Z(1UY=HMs1FkXH{Nf%d36fJJP4F2O#zsTThzFoLE=2X)iE2;BQW!GS3@jG4rcE#-rl4Nq{-_z)fo1R# zX2I}bW@+*dWBzr)8dS)xs1Z-WSX_p>(Mj9>48tf#4>vPV8nsDlqP_=pF*CMD&0H_k zz+Oit!z6|`71}KAY{PKWlc%CCyc{+19hen=z-;&{X2tt+6{aRGNzp*-28s#_> zaUk;h$GM7{>UyKiOb)^Fls~|ZxECv8B;R2s!|^sF;T&ftszdBp^GD)a*q-tgtcEqm zInH<-f&9<8$&U=!he~~jhM;ETFlt85qh{<+)Dk_n?ST`__ahfFlU}C;NhLZ|$FevO z<8VG|M=th0@W`H1F<=3bGAY)%~;exrlSV30JW5Bu^1k<<$Ks(?|-gI=C{@W zY(t0rSQ>LpHW#dmdV*G{wQO(giJF0-s2LiI8t4Yp7xf#|fYVWL*G*flGR6FJL2Ycw z{hf&k;>SM7vLjzLY~GK|LUs2MnoT`>6a>er*5{G2t*3{#(g4X7V(-HI6~ zKfzlV__i6)T}-Cjd?x?$0zW~vl~d*&^NC)K(UjA@B#glcnq{W=4P?VRtFbT^dDpxy zjc^I&&dBUKkC91nCckI?APIWkak!_`71`L%Rn%+R>jU#8Jd1fLKgI%>bGDgjZ!F0m zDjK1tY>RaVMp532+C&#I27_F4VsTV`ZB%_vYZ_{^tw5c>4>d!VP!Dt+wZ!3b{PO)L z8BRqF9E@vFuT#Wa^V+mTU1%Pr;8xTo$}!J;a))DX$}_A>P&2p{Yh&zu{*J^UsMmTE z>N{~3Lp1j{NpypI{t7-C3(Tg8L|wQrmd6^H8T(@djz(SR9n|?Bqh@3$YDO+u0~VTr z7De@oNA*j=j#~SvB<=7tw#RZGnsO@YLO-LX^lxm8xjr(RC>wX_FN4|EK3;#KtO22V&zVu3|wvo*v>%AHUrj=(S+hl!Yq1@Jq}g6XJ%+{5aa zZ?Tz~wpf{RFY7$i+jS7j;giM8zow!jTU#S*iTSZNY5-o;6fZyxbTwAOgP4HNung8& zYCg>aQ3D=}y51Cw!Z}tS>V79s*L$?oYm&U5n2xnj1L$JQW3V{oxfqVSFb5t$ZKiXm z8-^}3H_U>%VNuirB%*( ze%E7n)DnD+TJwjf3q`Fm1FV92@(!2}hoJ`iHfpI>+V&IJS>L{!B)W0K)%KSTrcgeE zI-&X+^Ve-})aKcMnu!aj0Xl0qPsOvvQomY9CndxM#qxXLv$#@!0 zp>}Jl_2yr#q~RFK2QeD!eP({!^}=S9H=$m$r&tbSH<%lCM=i~K)a(2Ow#7^v&41wN zj@>AKi4ol2iQMEkt+4`j#Me<5_!Tv^k5HQ~^JX)((O8snoGo|9Hk8NO@&SCG@@3SG zUfaSSBX}IY#d$sgQ$A<@!%6Ow=!VW#Gew0kE5&*kh;1+%CgTz8hrO}N7iP0Az}b{n zU>2;gjg5n;f*a$~p1>BC6@EU5S^6xX}C8B1sHFiMnblY$N`%rNclht9r zx!_9dPx%%W$K_)E>Bu`S2-f6Gwb!XT}<9jmJRRn_&nhq0Udn5_mz?K?M+eVO~AUi5!Y~k z=TDN{xbT1($VMzpc?8eukRLqt-{rW^oD~H#e??D#u|SY+=iN za2n;YSOA})W;ovo^Vjf)*pG6~ljaYV2`6dQlx?9x7d(V@@dD<=sGm%IB@CzB5M!|| zYJh2&3)i7$WG`mL?@`zL#g=cNX7&l{$qSt_|E;K&mqZOyu^6sIeen)sb`1I13?vdO zP%evwFa@=AlTd5=5mv*mPy_o5b)$m6nD0v*>Os4qW_Sz^K<@$)T`1sJbAcGFO}P{5 zN#|lY{0`Oc0Y+lZ)26;G>V{oVPo9ce``NaBx2?Zo{R0I$=WL{q*Mwb}l{ zI4p41{N3IG6Dco5&A@rolV(0=ZrA|xQ*MtM=y23P7o#3vw{5?T8hFO@=E;j-UG0up zBtvO9f@QJx1@ni*0MrGxqb__MOJSai=1;47sQL|99nWET%yr33aUyDF`eFl|g_`;k z7>y4wo0lZ_Z@d9m9QEY$P*ZdaqcGyK`4Ck_-LN~>$9GUOa}=9k&UEv4KnkjT18NiA zwFdoezN~pLhWZ%vszV})p0tB)n2sALufd|&>53V^WNc4)C2A?2qNcRWRrA+u1Jrd= zQT0B|i-)aOQ8VKFVIHL9AI!fhI#8k4YYOJVnfNX)MO~=EHS?ED6U+fcjv1&+e%5BXT(dQ`dmBl9=jrx;H8sWrpj<^z@+^?H^-JzydxU^`Rq zb>@)h2AeP#ui;?4jXI(0WAm5FAk;z#9TNH8&F{MfJ%E?$42jN6qhb6GYbAH8QE9`_zuqXbF&9KW0^Bq}% zH7K9Jk(kNx_-AYiYANpHXI_#=BpwfcKJ!BzR|RRVJ++v z3zlL&$`?^<`~i&l?2g4yo9+$NCj7ygBZJ4kBq^x+ji^2J7{_5kn8*J%?ZAwb>t*DDk(bNspZXYv z^92Xbu=Q^=GD=O{4z0t{4;-z z`4I`TJk0-e8h2568+ZS!!#9+5e1b;_JyAh!R*C#0BHrI>e()jmf0$yZe}njyax7jW zMibpQ@8uCnvX-c*3ax)HB^;0F*wLi^pPTs^!HF9&KM_m58tV``KBa%UNu5&UI`jr* zv^T5lui>qsEkAWFiA>}VZC^EeU*X3HD!wI>$&=}%-TyJ=R0X*Xty=}k2Z?uxzX|P) zTUbCHI9d|HoWF`X9j6JsYC5u0x09Gk@CN&j7VJNN(wdV7kYymw5QFH%%iurw?l?Y^ zI(%)NBGl`xsDcT$UXXzBoTS3%cJ{mBZ=a~6rwTF znOLQs9J}p>Qph8z*Y3|x-8dqK{1&030d36*9TjPNOk5$FQT_w7(e@U3CH#_dH=+c2 zKlA*oF%>#W82o?Nx3+b>HBNitI-&RaV`4hNC*6s#eZD2{LFmZAh077`2p#&)_wi@^ zkz@PM!GeVU{x58$J}x@ia$+EHz!aVGeBHtkAbMg;~CFDKvIF2X$&mY1Gmndu?#!@v%^&HKKcd5&PU*Vg?Lh@*$ zKaq>_NZdn&bB%VCb<`#f*t`bTp>4Ro#r!A>ZK=PHRW*J5CjHNmht4Z#+;1;XjF#m@ zIOW2$1>1U^P>*~mQHK0yqWQntPN@UOapEgt2JtiPRk=og{NDEUMw6T;-l4-QH0qd7 z+@tOze~Fu5U*ZSqvf+1xj?I+yH$XSycU5qVqV6b`KplMQ{l6P}lg}f+LtG@UgL5_i zzflOGpkpAB%b)R|>8bn9*8hVOh<&zglT}n>&`()U!I3;+idcg z#8q2Xw@jR0oG=G_{ui9D zTlrYf_CB%6ULyumWO zQ|*kT{M6Q+!{J0-+8*E`Y{XzY5ogIW5sBnKU`zeZ)vnJ=MMFAwAlI>jJe0bprs#Y@ z`8s(V_95Dn4tJ7}^o*;B&#oE;A*h-#m>(^U5+BVaJ^)F-_Hc)e!_>uBiVluIp z_>IujA4P~` zM0R`g`k0x>K>TSh^cUDfgj3-xkxrk|uS2!uwoA$DanLGvzJB_qN=DygYda&I=+6P@Y6N5NlBGOum}@AKPEdrmSNM zv4ZGIZ0pC5CNxy!BpnIXq15%T`A*a1l&1W0zr<2kka(SbMTxHo9a{}fS6lWMGiG!> ziFw^i^<#XeYTOF;ovc$Y!0k|PihH)+V&CHWBLm!shHtpb8g_GYH2TVY+$hixD7fM z_1)>v+T%9qblX>}^S}V#&aUGFeQ~cm4e+(^)!*Yz?GxcU*E?%qXmtOP!$uFU>E`R- z*`3h8vF}O$f*xPr0Y3-22}5FilZFiUxN*aZxqXLq@?9Nv%Humb;zqE0bnJ7t^!OOJ z64=aN40v!GnAD8r$2=HoJp+`GXa{&9k3n^6gvP zInWK*lE~mkIPlG*fy=#ShtCmLwc%prKR@U;k8}CHA1y-lID_7Tc3$M)QzF1w% z*K|#g$31Z;r~B60QEvNn>)q7#MSOkMkM_8|mgexi`0TjH_shm!9yjWIq&sibtLW1y^`=*pR+%%)LnW(uthD87CHd z+#)L@eC>XE802<79qy^=j<{RE-Ftd~8+#_j_x#KukFUb{Z#|xR?q3%g`x;(s6X@o+ zoyYzBa!ub~mum&M-(M;2KDaX6xBTk+0lpE}uZQ}2-W}_4yWFqu>wLeO$G7IeccELJ zWeD9?FUa$YXMS>sr}O-+A)Z~KEqe~_*}w0|`Dvk^(jC)NXaBx?&mVJEr%zp&o;uS> z>N{%eu#tmopXibU#`cL$PkTQ-EiFBDV|wc9^fX_3+Kf`s|Ic~zGi31ei~6sN{nt73 wKg!_gll8y)=)_mI%?$HY3kpk5 Date: Mon, 27 Apr 2026 02:35:34 +0300 Subject: [PATCH 09/61] Remove disabled translation-check workflow (#4483) The workflow has been fully commented out since #2119 (2023-09-28) when translation handling was reworked. Because the file has no `on:` triggers, GitHub Actions creates a failed workflow run for every push, polluting the Actions tab without affecting PR check-runs. --- .github/workflows/translation-check.yaml | 28 ------------------------ 1 file changed, 28 deletions(-) delete mode 100644 .github/workflows/translation-check.yaml diff --git a/.github/workflows/translation-check.yaml b/.github/workflows/translation-check.yaml deleted file mode 100644 index 3cd4d14c..00000000 --- a/.github/workflows/translation-check.yaml +++ /dev/null @@ -1,28 +0,0 @@ -#on: -# push: -# paths: -# - 'archinstall/locales/**' -# pull_request: -# paths: -# - 'archinstall/locales/**' -#name: Verify local_generate script was run on translation changes -#jobs: -# translation-check: -# runs-on: ubuntu-latest -# container: -# image: archlinux/archlinux:latest -# steps: -# - uses: actions/checkout@v4 -# - run: pacman --noconfirm -Syu python git diffutils -# - name: Verify all translation scripts are up to date -# run: | -# cd .. -# cp -r archinstall archinstall_orig -# cd archinstall/archinstall/locales -# bash locales_generator.sh 1> /dev/null -# cd ../../.. -# git diff \ -# --quiet --no-index --name-only \ -# archinstall_orig/archinstall/locales \ -# archinstall/archinstall/locales \ -# || (echo "Translation files have not been updated after translation, please run ./locales_generator.sh once more and commit" && exit 1) From 074dfbb1784a374f6f2a548a2c027b1f942b153d Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Mon, 27 Apr 2026 08:44:14 +0200 Subject: [PATCH 10/61] Adding a reference to why AUR is not bundled as an option in archinstall (#4468) --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 75631b6b..95d15702 100644 --- a/README.md +++ b/README.md @@ -219,6 +219,10 @@ qemu-system-x86_64 -enable-kvm \ # FAQ +## AUR + +`archinstall` will not offer or bundle AUR helpers or AUR packages due to a current consensus. This is not any individual developers decision. The reasons and discussions for this stance on the topic can be found on our mailing list thread: [(optional) AUR helper in archinstall](https://lists.archlinux.org/archives/list/arch-dev-public@lists.archlinux.org/thread/VYOULH2GOJLFM2BXOFLWH3D754YXFPSL/). + ## Keyring out-of-date For a description of the problem see https://archinstall.archlinux.page/help/known_issues.html#keyring-is-out-of-date-2213 and discussion in issue https://github.com/archlinux/archinstall/issues/2213. From 9010ccf9eb3369f713d60cefc0b17e619f23c143 Mon Sep 17 00:00:00 2001 From: codefiles <11915375+codefiles@users.noreply.github.com> Date: Tue, 28 Apr 2026 03:08:19 -0400 Subject: [PATCH 11/61] Add kernel enum (#4489) --- archinstall/lib/args.py | 3 ++- archinstall/lib/general/system_menu.py | 17 ++++++----------- archinstall/lib/global_menu.py | 3 ++- archinstall/lib/installer.py | 5 +++-- archinstall/lib/models/package_types.py | 12 ++++++++++++ archinstall/tui/ui/menu_item.py | 4 ++-- 6 files changed, 27 insertions(+), 17 deletions(-) create mode 100644 archinstall/lib/models/package_types.py diff --git a/archinstall/lib/args.py b/archinstall/lib/args.py index 79441a06..77545736 100644 --- a/archinstall/lib/args.py +++ b/archinstall/lib/args.py @@ -21,6 +21,7 @@ from archinstall.lib.models.device import DiskEncryption, DiskLayoutConfiguratio from archinstall.lib.models.locale import LocaleConfiguration from archinstall.lib.models.mirrors import MirrorConfiguration from archinstall.lib.models.network import NetworkConfiguration +from archinstall.lib.models.package_types import DEFAULT_KERNEL from archinstall.lib.models.packages import Repository from archinstall.lib.models.pacman import PacmanConfiguration from archinstall.lib.models.profile import ProfileConfiguration @@ -71,7 +72,7 @@ class ArchConfig: auth_config: AuthenticationConfiguration | None = None swap: ZramConfiguration | None = None hostname: str = 'archlinux' - kernels: list[str] = field(default_factory=lambda: ['linux']) + kernels: list[str] = field(default_factory=lambda: [DEFAULT_KERNEL.value]) ntp: bool = True packages: list[str] = field(default_factory=list) pacman_config: PacmanConfiguration = field(default_factory=PacmanConfiguration.default) diff --git a/archinstall/lib/general/system_menu.py b/archinstall/lib/general/system_menu.py index 5c4d4634..7fa0d1f2 100644 --- a/archinstall/lib/general/system_menu.py +++ b/archinstall/lib/general/system_menu.py @@ -3,29 +3,24 @@ from typing import assert_never from archinstall.lib.hardware import GfxDriver, SysInfo from archinstall.lib.menu.helpers import Confirmation, Selection from archinstall.lib.models.application import ZramAlgorithm, ZramConfiguration +from archinstall.lib.models.package_types import DEFAULT_KERNEL, Kernel from archinstall.lib.translationhandler import tr from archinstall.tui.ui.menu_item import MenuItem, MenuItemGroup from archinstall.tui.ui.result import ResultType -async def select_kernel(preset: list[str] = []) -> list[str]: +async def select_kernel(preset: list[Kernel] = []) -> list[Kernel]: """ Asks the user to select a kernel for system. :return: The string as a selected kernel :rtype: string """ - kernels = ['linux', 'linux-lts', 'linux-zen', 'linux-hardened'] - default_kernel = 'linux' + group = MenuItemGroup.from_enum(Kernel, sort_items=True, preset=preset) + group.set_default_by_value(DEFAULT_KERNEL) + group.set_focus_by_value(DEFAULT_KERNEL) - items = [MenuItem(k, value=k) for k in kernels] - - group = MenuItemGroup(items, sort_items=True) - group.set_default_by_value(default_kernel) - group.set_focus_by_value(default_kernel) - group.set_selected_by_value(preset) - - result = await Selection[str]( + result = await Selection[Kernel]( group, header=tr('Select which kernel(s) to install'), allow_skip=True, diff --git a/archinstall/lib/global_menu.py b/archinstall/lib/global_menu.py index e327b388..3bfefce7 100644 --- a/archinstall/lib/global_menu.py +++ b/archinstall/lib/global_menu.py @@ -22,6 +22,7 @@ from archinstall.lib.models.device import DiskLayoutConfiguration, DiskLayoutTyp from archinstall.lib.models.locale import LocaleConfiguration from archinstall.lib.models.mirrors import MirrorConfiguration from archinstall.lib.models.network import NetworkConfiguration, NicType +from archinstall.lib.models.package_types import DEFAULT_KERNEL from archinstall.lib.models.packages import Repository from archinstall.lib.models.pacman import PacmanConfiguration from archinstall.lib.models.profile import ProfileConfiguration @@ -103,7 +104,7 @@ class GlobalMenu(AbstractMenu[None]): ), MenuItem( text=tr('Kernels'), - value=['linux'], + value=[DEFAULT_KERNEL], action=select_kernel, preview_action=self._prev_kernel, mandatory=True, diff --git a/archinstall/lib/installer.py b/archinstall/lib/installer.py index ff530df0..ca98fdb9 100644 --- a/archinstall/lib/installer.py +++ b/archinstall/lib/installer.py @@ -48,6 +48,7 @@ from archinstall.lib.models.device import ( from archinstall.lib.models.locale import LocaleConfiguration from archinstall.lib.models.mirrors import MirrorConfiguration from archinstall.lib.models.network import Nic +from archinstall.lib.models.package_types import DEFAULT_KERNEL, Kernel from archinstall.lib.models.packages import Repository from archinstall.lib.models.pacman import PacmanConfiguration from archinstall.lib.models.users import User @@ -60,7 +61,7 @@ from archinstall.lib.plugins import plugins from archinstall.lib.translationhandler import tr # Any package that the Installer() is responsible for (optional and the default ones) -__packages__ = ['base', 'sudo', 'linux-firmware', 'linux', 'linux-lts', 'linux-zen', 'linux-hardened'] +__packages__ = ['base', 'sudo', 'linux-firmware'] + [k.value for k in Kernel] # Additional packages that are installed if the user is running the Live ISO with accessibility tools enabled __accessibility_packages__ = ['brltty', 'espeakup', 'alsa-utils'] @@ -80,7 +81,7 @@ class Installer: It also wraps :py:func:`~archinstall.Installer.pacstrap` among other things. """ self._base_packages = base_packages or __packages__[:3] - self.kernels = kernels or ['linux'] + self.kernels = kernels or [DEFAULT_KERNEL.value] self._disk_config = disk_config self._disk_encryption = disk_config.disk_encryption or DiskEncryption(EncryptionType.NO_ENCRYPTION) diff --git a/archinstall/lib/models/package_types.py b/archinstall/lib/models/package_types.py new file mode 100644 index 00000000..e314c30a --- /dev/null +++ b/archinstall/lib/models/package_types.py @@ -0,0 +1,12 @@ +from enum import StrEnum, auto +from typing import Final + + +class Kernel(StrEnum): + LINUX = auto() + LINUX_LTS = 'linux-lts' + LINUX_ZEN = 'linux-zen' + LINUX_HARDENED = 'linux-hardened' + + +DEFAULT_KERNEL: Final = Kernel.LINUX diff --git a/archinstall/tui/ui/menu_item.py b/archinstall/tui/ui/menu_item.py index ed5a16ec..4c10e275 100644 --- a/archinstall/tui/ui/menu_item.py +++ b/archinstall/tui/ui/menu_item.py @@ -1,4 +1,4 @@ -from collections.abc import Awaitable, Callable +from collections.abc import Awaitable, Callable, Iterable from dataclasses import dataclass, field from enum import Enum from functools import cached_property @@ -148,7 +148,7 @@ class MenuItemGroup: cls, enum_cls: type[Enum], sort_items: bool = False, - preset: Enum | None = None, + preset: Iterable[Enum] | Enum | None = None, ) -> Self: items = [MenuItem(elem.value, value=elem) for elem in enum_cls] group = cls(items, sort_items=sort_items) From 8cc35f41d8da44b1665e3f8b41a10bb22bb0b1c7 Mon Sep 17 00:00:00 2001 From: Franco Castillo Date: Tue, 28 Apr 2026 04:08:39 -0300 Subject: [PATCH 12/61] Update Spanish translation (#4490) Signed-off-by: Franco Castillo --- archinstall/locales/es/LC_MESSAGES/base.mo | Bin 65086 -> 67046 bytes archinstall/locales/es/LC_MESSAGES/base.po | 200 +++++++++------------ 2 files changed, 88 insertions(+), 112 deletions(-) diff --git a/archinstall/locales/es/LC_MESSAGES/base.mo b/archinstall/locales/es/LC_MESSAGES/base.mo index 14e916ef66dffeec86c2631fab615c138248130d..5701d45472451d2476f3d5d88f44cf536839ce2b 100644 GIT binary patch delta 16896 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 roibA Date: Tue, 28 Apr 2026 12:22:01 +0300 Subject: [PATCH 13/61] Show install summary when configuration is valid (#4475) * Show install summary when configuration is valid Previously the Install menu preview was empty when everything was valid, leaving the user with no "ready" signal. Now show "Ready to install" plus a two-column summary of the current configuration. - New _install_summary() composes an aligned key/value table with rows for disks+FS+LUKS, bootloader, kernel, profile, greeter, package count, network, locale and timezone. Column width adapts to the longest translated label so translations keep the alignment. - Rows whose underlying config is not set are skipped rather than rendered as empty. - base.pot / uk base.po: add new msgids for summary labels. * Move install summary into ConfigurationOutput The summary helper that renders the install preview's two-column configuration overview lived in GlobalMenu. Move it to ConfigurationOutput.as_summary() so it sits alongside the JSON output methods that share the same role. The preview now syncs menu state to ArchConfig and delegates rendering. --- archinstall/lib/configuration.py | 66 ++++++++++++++++++++++ archinstall/lib/global_menu.py | 8 ++- archinstall/locales/base.pot | 15 +++++ archinstall/locales/uk/LC_MESSAGES/base.po | 15 +++++ 4 files changed, 102 insertions(+), 2 deletions(-) diff --git a/archinstall/lib/configuration.py b/archinstall/lib/configuration.py index 39d39511..0f22190e 100644 --- a/archinstall/lib/configuration.py +++ b/archinstall/lib/configuration.py @@ -10,6 +10,8 @@ from archinstall.lib.args import ArchConfig from archinstall.lib.crypt import encrypt from archinstall.lib.menu.helpers import Confirmation, Selection from archinstall.lib.menu.util import get_password, prompt_dir +from archinstall.lib.models.bootloader import Bootloader +from archinstall.lib.models.network import NetworkConfiguration from archinstall.lib.output import debug, logger, warn from archinstall.lib.translationhandler import tr from archinstall.tui.ui.menu_item import MenuItem, MenuItemGroup @@ -58,6 +60,70 @@ class ConfigurationOutput: debug(' -- Chosen configuration --') debug(self.user_config_to_json()) + def as_summary(self) -> str: + """ + Render a concise two-column summary of the current configuration. + + The left column holds section labels, the right column holds values. + Column width adapts to the longest translated label so translations + do not break the alignment. Rows whose underlying config is not set + are skipped. + + Returns an empty string if nothing meaningful to show. + """ + rows: list[tuple[str, str]] = [] + + disk_config = self._config.disk_config + if disk_config and disk_config.device_modifications: + disk_parts: list[str] = [] + for mod in disk_config.device_modifications: + path = str(mod.device_path) + root_part = mod.get_root_partition() + flags: list[str] = [] + if root_part and root_part.fs_type: + flags.append(root_part.fs_type.value) + if disk_config.disk_encryption: + flags.append(tr('LUKS')) + disk_parts.append(f'{path} ({" + ".join(flags)})' if flags else path) + rows.append((tr('Disks'), ', '.join(disk_parts))) + + bl_config = self._config.bootloader_config + if bl_config and bl_config.bootloader != Bootloader.NO_BOOTLOADER: + rows.append((tr('Bootloader'), bl_config.bootloader.value)) + + kernels = self._config.kernels + if kernels: + rows.append((tr('Kernel'), ', '.join(kernels))) + + profile_config = self._config.profile_config + if profile_config and profile_config.profile: + names = profile_config.profile.current_selection_names() + rows.append((tr('Profile'), ', '.join(names) if names else profile_config.profile.name)) + if profile_config.greeter: + rows.append((tr('Greeter'), profile_config.greeter.value)) + + packages = self._config.packages + if packages: + rows.append((tr('Packages'), str(len(packages)))) + + net_config = self._config.network_config + if isinstance(net_config, NetworkConfiguration): + rows.append((tr('Network'), net_config.type.display_msg())) + + locale_config = self._config.locale_config + if locale_config: + rows.append((tr('Locale'), locale_config.sys_lang)) + + tz = self._config.timezone + if tz: + rows.append((tr('Timezone'), tz)) + + if not rows: + return '' + + label_width = max(len(label) for label, _ in rows) + 2 + return '\n'.join(f'{label:<{label_width}}{value}' for label, value in rows) + async def confirm_config(self) -> bool: header = f'{tr("The specified configuration will be applied")}. ' header += tr('Would you like to continue?') + '\n' diff --git a/archinstall/lib/global_menu.py b/archinstall/lib/global_menu.py index 3bfefce7..cdd820a9 100644 --- a/archinstall/lib/global_menu.py +++ b/archinstall/lib/global_menu.py @@ -6,7 +6,7 @@ from archinstall.lib.args import ArchConfig from archinstall.lib.authentication.authentication_menu import AuthenticationMenu from archinstall.lib.bootloader.bootloader_menu import BootloaderMenu from archinstall.lib.bootloader.utils import validate_bootloader_layout -from archinstall.lib.configuration import save_config +from archinstall.lib.configuration import ConfigurationOutput, save_config from archinstall.lib.disk.disk_menu import DiskLayoutConfigurationMenu from archinstall.lib.general.general_menu import select_hostname, select_ntp, select_timezone from archinstall.lib.general.system_menu import select_kernel, select_swap @@ -504,7 +504,11 @@ class GlobalMenu(AbstractMenu[None]): if error := self._validate_bootloader(): return tr(f'Invalid configuration: {error}') - return None + self.sync_all_to_config() + summary = ConfigurationOutput(self._arch_config).as_summary() + if summary: + return f'{tr("Ready to install")}\n\n{summary}' + return tr('Ready to install') def _prev_profile(self, item: MenuItem) -> str | None: profile_config: ProfileConfiguration | None = item.value diff --git a/archinstall/locales/base.pot b/archinstall/locales/base.pot index a0c4c5b7..b7e1184b 100644 --- a/archinstall/locales/base.pot +++ b/archinstall/locales/base.pot @@ -1248,6 +1248,21 @@ msgstr "" msgid "Invalid configuration: {error}" msgstr "" +msgid "Ready to install" +msgstr "" + +msgid "Disks" +msgstr "" + +msgid "Packages" +msgstr "" + +msgid "Network" +msgstr "" + +msgid "Locale" +msgstr "" + msgid "Type" msgstr "" diff --git a/archinstall/locales/uk/LC_MESSAGES/base.po b/archinstall/locales/uk/LC_MESSAGES/base.po index ffab244f..88bf9ede 100644 --- a/archinstall/locales/uk/LC_MESSAGES/base.po +++ b/archinstall/locales/uk/LC_MESSAGES/base.po @@ -1222,6 +1222,21 @@ msgstr "Модель" msgid "Invalid configuration: {error}" msgstr "Неправильна конфігурація: {error}" +msgid "Ready to install" +msgstr "Готово до встановлення" + +msgid "Disks" +msgstr "Диски" + +msgid "Packages" +msgstr "Пакети" + +msgid "Network" +msgstr "Мережа" + +msgid "Locale" +msgstr "Локаль" + msgid "Type" msgstr "Тип" From 1b7a32a3b36cefc06452e15db42283004ae9b38c Mon Sep 17 00:00:00 2001 From: Hassaan Amin <164820466+HASSAAN-AMIN@users.noreply.github.com> Date: Tue, 28 Apr 2026 03:03:49 -0700 Subject: [PATCH 14/61] Fix typos in known issues documentation (#4488) First Edit : - "effort" is singular so use has not have Second Edit : - we use "an" before vowel sounds (an old ) Third Edit : - use Sometimes because sometimes means occasionally and Some times means some period or era Fourth Edit : - Plural of ISO is ISOs not ISO's --- docs/help/known_issues.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/help/known_issues.rst b/docs/help/known_issues.rst index bf039dcb..90945894 100644 --- a/docs/help/known_issues.rst +++ b/docs/help/known_issues.rst @@ -22,7 +22,7 @@ Restarting ``systemd-timesyncd.service`` might work but most often you need to c Waiting for Arch Linux keyring sync (archlinux-keyring-wkd-sync) to complete. `#2679`_ ------------------------------ -The ``archlinux-keyring-wkd-sync.service`` or ``archlinux-keyring-wkd-sync.timer`` can hang "indefinitely" some times. +The ``archlinux-keyring-wkd-sync.service`` or ``archlinux-keyring-wkd-sync.timer`` can hang "indefinitely" sometimes. This is usually due to an inability to reach the key servers, or a slow connection towards key servers. The script ``/usr/bin/archlinux-keyring-wkd-sync`` can be run manually, to verify if it's executing slowly or not. @@ -65,7 +65,7 @@ ARM, 32bit and other CPU types error out `#1686`_, `#2185`_ This is a bit of a catch-all known issue. Officially `x86_64`_ is only supported by Arch Linux. -Hence little effort have been put into supporting other platforms. +Hence little effort has been put into supporting other platforms. In theory, other architectures should work but small quirks might arise. @@ -79,10 +79,10 @@ Missing key-issues tend to be that the `archlinux-keyring`_ package is out of da There is an attempt from upstream to fix this issue, and it's the `archlinux-keyring-wkd-sync.service`_ The service starts almost immediately during boot, and if network is not configured in time — the service will fail. -Subsequently the ``archinstall`` run might operate on a old keyring despite there being an update service for this. +Subsequently the ``archinstall`` run might operate on an old keyring despite there being an update service for this. There is really no way to reliably over time work around this issue in ``archinstall``. -Instead, efforts to the upstream service should be considered the way forward. And/or keys not expiring between a sane amount of ISO's. +Instead, efforts to the upstream service should be considered the way forward. And/or keys not expiring between a sane amount of ISOs. .. note:: From cb0f3a6eba4007fa2d84780451b34afa7d31dfa3 Mon Sep 17 00:00:00 2001 From: Softer Date: Tue, 28 Apr 2026 13:17:40 +0300 Subject: [PATCH 15/61] Warn when no network configuration is selected (#4408) * Make network configuration mandatory with explicit "No network" option * Add network configuration recommendation hint and disable sorting * Warn when network selection was skipped instead of making it mandatory Replace the mandatory network_config menu requirement with a yellow warning on the final confirmation screen, shown only when the user skipped the network menu entirely. Explicitly picking "No network configuration" is treated as a conscious choice and does not trigger the warning. - Drop mandatory=True from the network_config global menu item - Rename NicType.NONE menu label from "No network" to "No network configuration" (an installed system still has an NIC; only the install-time configuration is absent) - Add ConfigurationOutput.get_install_warnings() and render them in confirm_config when show_install_warnings=True - guided.py and minimal.py enable the warning on the confirm screen - Reuse the existing "No network configuration" msgid in .pot; add the warning string; update Ukrainian translations accordingly * Drop NicType.NONE from this PR The NONE option introduced earlier in this branch is being removed per review feedback (#4408). Whether to add explicit "None" options across multiple menus (greeter, gfx driver, network, bootloader) is now being discussed in #4464 and should land as a separate change there. This PR keeps only the warning-on-confirm behaviour: when the user skipped the network menu entirely (network_config is None), a yellow warning is shown on the final confirmation screen. --- archinstall/lib/configuration.py | 21 ++++++++++++++++++++- archinstall/lib/network/network_menu.py | 7 +++++-- archinstall/locales/base.pot | 8 ++++++++ archinstall/locales/uk/LC_MESSAGES/base.po | 6 ++++++ archinstall/scripts/guided.py | 2 +- archinstall/scripts/minimal.py | 2 +- 6 files changed, 41 insertions(+), 5 deletions(-) diff --git a/archinstall/lib/configuration.py b/archinstall/lib/configuration.py index 0f22190e..3e9135bf 100644 --- a/archinstall/lib/configuration.py +++ b/archinstall/lib/configuration.py @@ -124,10 +124,13 @@ class ConfigurationOutput: label_width = max(len(label) for label, _ in rows) + 2 return '\n'.join(f'{label:<{label_width}}{value}' for label, value in rows) - async def confirm_config(self) -> bool: + async def confirm_config(self, show_install_warnings: bool = False) -> bool: header = f'{tr("The specified configuration will be applied")}. ' header += tr('Would you like to continue?') + '\n' + if show_install_warnings: + header += self._render_install_warnings() + group = MenuItemGroup.yes_no() group.set_preview_for_all(lambda x: self.user_config_to_json()) @@ -145,6 +148,22 @@ class ConfigurationOutput: return True + def get_install_warnings(self) -> list[str]: + warnings: list[str] = [] + + if not isinstance(self._config.network_config, NetworkConfiguration): + warnings.append(tr('Warning: no network configuration selected. Network will need to be set up manually on the installed system.')) + + return warnings + + def _render_install_warnings(self) -> str: + warnings = self.get_install_warnings() + + if not warnings: + return '' + + return '\n' + '\n'.join(f'[yellow]{w}[/]' for w in warnings) + '\n' + def _is_valid_path(self, dest_path: Path) -> bool: dest_path_ok = dest_path.exists() and dest_path.is_dir() if not dest_path_ok: diff --git a/archinstall/lib/network/network_menu.py b/archinstall/lib/network/network_menu.py index bcf54698..2e95a82e 100644 --- a/archinstall/lib/network/network_menu.py +++ b/archinstall/lib/network/network_menu.py @@ -172,14 +172,17 @@ async def select_network(preset: NetworkConfiguration | None) -> NetworkConfigur """ items = [MenuItem(n.display_msg(), value=n) for n in NicType] - group = MenuItemGroup(items, sort_items=True) + group = MenuItemGroup(items, sort_items=False) if preset: group.set_selected_by_value(preset.type) + header = tr('Choose network configuration') + '\n' + header += tr('Recommended: Network Manager for desktop, Manual for server') + '\n' + result = await Selection[NicType]( group, - header=tr('Choose network configuration'), + header=header, allow_reset=True, allow_skip=True, ).show() diff --git a/archinstall/locales/base.pot b/archinstall/locales/base.pot index b7e1184b..f0b311d4 100644 --- a/archinstall/locales/base.pot +++ b/archinstall/locales/base.pot @@ -2192,6 +2192,14 @@ msgstr "" msgid "Choose network configuration" msgstr "" +msgid "Recommended: Network Manager for desktop, Manual for server" +msgstr "" + +msgid "" +"Warning: no network configuration selected. Network will need to be set up " +"manually on the installed system." +msgstr "" + msgid "No packages found" msgstr "" diff --git a/archinstall/locales/uk/LC_MESSAGES/base.po b/archinstall/locales/uk/LC_MESSAGES/base.po index 88bf9ede..a13d825c 100644 --- a/archinstall/locales/uk/LC_MESSAGES/base.po +++ b/archinstall/locales/uk/LC_MESSAGES/base.po @@ -2133,6 +2133,12 @@ msgstr "Оберіть інтерфейс" msgid "Choose network configuration" msgstr "Оберіть конфігурацію мережі" +msgid "Recommended: Network Manager for desktop, Manual for server" +msgstr "Рекомендовано: Network Manager для робочого столу, ручне налаштування для сервера" + +msgid "Warning: no network configuration selected. Network will need to be set up manually on the installed system." +msgstr "Попередження: конфігурацію мережі не обрано. Мережу доведеться налаштувати вручну на встановленій системі." + msgid "No packages found" msgstr "Пакети не знайдено" diff --git a/archinstall/scripts/guided.py b/archinstall/scripts/guided.py index 5cdcdad9..21228480 100644 --- a/archinstall/scripts/guided.py +++ b/archinstall/scripts/guided.py @@ -226,7 +226,7 @@ def main(arch_config_handler: ArchConfigHandler | None = None) -> None: if not arch_config_handler.args.silent: aborted = False - res: bool = tui.run(config.confirm_config) + res: bool = tui.run(lambda: config.confirm_config(show_install_warnings=True)) if not res: debug('Installation aborted') diff --git a/archinstall/scripts/minimal.py b/archinstall/scripts/minimal.py index 4e897142..681d4266 100644 --- a/archinstall/scripts/minimal.py +++ b/archinstall/scripts/minimal.py @@ -77,7 +77,7 @@ async def main(arch_config_handler: ArchConfigHandler | None = None) -> None: if not arch_config_handler.args.silent: aborted = False - res: bool = tui.run(config.confirm_config) + res: bool = tui.run(lambda: config.confirm_config(show_install_warnings=True)) if not res: debug('Installation aborted') From e1efa34d8a009b52de4bce3eb37f28bdda3a239d Mon Sep 17 00:00:00 2001 From: Softer Date: Tue, 28 Apr 2026 14:01:05 +0300 Subject: [PATCH 16/61] Install mkinitcpio explicitly in base packages (#4484) Prevents pacstrap from picking a different initramfs provider (dracut, booster) when running from a non-Arch host. Fixes #4368 --- archinstall/lib/installer.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/archinstall/lib/installer.py b/archinstall/lib/installer.py index ca98fdb9..c28f37c4 100644 --- a/archinstall/lib/installer.py +++ b/archinstall/lib/installer.py @@ -61,7 +61,12 @@ from archinstall.lib.plugins import plugins from archinstall.lib.translationhandler import tr # Any package that the Installer() is responsible for (optional and the default ones) -__packages__ = ['base', 'sudo', 'linux-firmware'] + [k.value for k in Kernel] +# https://github.com/archlinux/archinstall/issues/4368 +# mkinitcpio is listed explicitly so pacstrap installs it deterministically. Otherwise +# pacman picks the first initramfs provider from the host's pacman.conf, which on non-Arch +# hosts (EndeavourOS prefers dracut, etc.) breaks the installer's mkinitcpio() and +# _config_uki() methods that assume mkinitcpio is present in the chroot. +__packages__ = ['base', 'sudo', 'linux-firmware', 'mkinitcpio'] + [k.value for k in Kernel] # Additional packages that are installed if the user is running the Live ISO with accessibility tools enabled __accessibility_packages__ = ['brltty', 'espeakup', 'alsa-utils'] @@ -80,7 +85,7 @@ class Installer: `Installer()` is the wrapper for most basic installation steps. It also wraps :py:func:`~archinstall.Installer.pacstrap` among other things. """ - self._base_packages = base_packages or __packages__[:3] + self._base_packages = base_packages or __packages__[:4] self.kernels = kernels or [DEFAULT_KERNEL.value] self._disk_config = disk_config From 8a929f603fc5c99f8fd59e9e150c1a702e9a4165 Mon Sep 17 00:00:00 2001 From: Softer Date: Tue, 28 Apr 2026 14:02:32 +0300 Subject: [PATCH 17/61] Fix sway+nvidia confirmation dialog (#4481) (#4485) * Fix sway+nvidia confirmation dialog (#4481) Two bugs in the Sway+Nvidia driver confirmation: 1. The boolean was inverted - confirming "yes, I'm okay with issues" reverted the driver to the previous choice instead of keeping it. 2. The warning triggered for any Nvidia driver, including the open-source nouveau driver which is officially supported by Sway. Add GfxDriver.is_nvidia_proprietary() and is_nvidia_nouveau() methods so the warning fires only for nvidia-open-dkms (proprietary userspace). * Address review feedback (#4485) - Drop is_nvidia_nouveau() helper. It is not called anywhere yet; can be re-added when a consumer lands. - Collapse the Sway+Nvidia confirmation result handling into a single expression now that allow_skip=False guarantees a boolean answer. --- archinstall/lib/hardware.py | 13 +++++++++++++ archinstall/lib/profile/profile_menu.py | 5 ++--- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/archinstall/lib/hardware.py b/archinstall/lib/hardware.py index 09b3eb5f..2cfdb706 100644 --- a/archinstall/lib/hardware.py +++ b/archinstall/lib/hardware.py @@ -68,6 +68,19 @@ class GfxDriver(Enum): case _: return False + def is_nvidia_proprietary(self) -> bool: + """ + True for Nvidia drivers that ship proprietary userspace components. + Currently only NvidiaOpenKernel (nvidia-open-dkms): open kernel module + paired with proprietary userspace. NvidiaOpenSource (nouveau) is fully + open and works with Sway, so it is excluded. + """ + match self: + case GfxDriver.NvidiaOpenKernel: + return True + case _: + return False + def packages_text(self) -> str: pkg_names = [p.value for p in self.gfx_packages()] text = tr('Installed packages') + ':\n' diff --git a/archinstall/lib/profile/profile_menu.py b/archinstall/lib/profile/profile_menu.py index cb560c27..115c9fc0 100644 --- a/archinstall/lib/profile/profile_menu.py +++ b/archinstall/lib/profile/profile_menu.py @@ -95,7 +95,7 @@ class ProfileMenu(AbstractSubMenu[ProfileConfiguration]): driver = await select_driver(preset=preset) if driver and 'Sway' in profile.current_selection_names(): - if driver.is_nvidia(): + if driver.is_nvidia_proprietary(): header = tr('The proprietary Nvidia driver is not supported by Sway.') + '\n' header += tr('It is likely that you will run into issues, are you okay with that?') + '\n' @@ -105,8 +105,7 @@ class ProfileMenu(AbstractSubMenu[ProfileConfiguration]): preset=False, ).show() - if result.get_value(): - return preset + return driver if result.get_value() else preset return driver From c4d68855cb6d95e370d05c806200f4ea23362116 Mon Sep 17 00:00:00 2001 From: correctmost <134317971+correctmost@users.noreply.github.com> Date: Tue, 28 Apr 2026 07:34:33 -0400 Subject: [PATCH 18/61] Replace time.time with time.monotonic to avoid clock drift issues (#4492) Using time.time() can be inaccurate if the system clock gets updated in between calls. --- archinstall/lib/installer.py | 4 ++-- archinstall/lib/networking.py | 10 +++++----- archinstall/lib/pacman/pacman.py | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/archinstall/lib/installer.py b/archinstall/lib/installer.py index c28f37c4..7bbfbae5 100644 --- a/archinstall/lib/installer.py +++ b/archinstall/lib/installer.py @@ -189,10 +189,10 @@ class Installer: if not skip_ntp: info(tr('Waiting for time sync (timedatectl show) to complete.')) - started_wait = time.time() + started_wait = time.monotonic() notified = False while True: - if not notified and time.time() - started_wait > 5: + if not notified and time.monotonic() - started_wait > 5: notified = True warn(tr('Time synchronization not completing, while you wait - check the docs for workarounds: https://archinstall.readthedocs.io/')) diff --git a/archinstall/lib/networking.py b/archinstall/lib/networking.py index ebf59990..e950a697 100644 --- a/archinstall/lib/networking.py +++ b/archinstall/lib/networking.py @@ -46,12 +46,12 @@ class DownloadTimer: self.previous_handler = signal.signal(signal.SIGALRM, self.raise_timeout) # type: ignore[assignment] self.previous_timer = signal.alarm(self.timeout) - self.start_time = time.time() + self.start_time = time.monotonic() return self def __exit__(self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None) -> None: if self.start_time: - time_delta = time.time() - self.start_time + time_delta = time.monotonic() - self.start_time signal.alarm(0) self.time = time_delta if self.timeout > 0: @@ -165,7 +165,7 @@ def build_icmp(payload: bytes) -> bytes: def ping(hostname: str, timeout: int = 5) -> int: watchdog = select.epoll() - started = time.time() + started = time.monotonic() random_identifier = f'archinstall-{random.randint(1000, 9999)}'.encode() # Create a raw socket (requires root, which should be fine on archiso) @@ -180,7 +180,7 @@ def ping(hostname: str, timeout: int = 5) -> int: # Gracefully wait for X amount of time # for a ICMP response or exit with no latency - while latency == -1 and time.time() - started < timeout: + while latency == -1 and time.monotonic() - started < timeout: try: for _fileno, _event in watchdog.poll(0.1): response, _ = icmp_socket.recvfrom(1024) @@ -188,7 +188,7 @@ def ping(hostname: str, timeout: int = 5) -> int: # Check if it's an Echo Reply (ICMP type 0) if icmp_type == 0 and response[-len(random_identifier) :] == random_identifier: - latency = round((time.time() - started) * 1000) + latency = round((time.monotonic() - started) * 1000) break except OSError as e: debug(f'Error: {e}') diff --git a/archinstall/lib/pacman/pacman.py b/archinstall/lib/pacman/pacman.py index 5427214c..83f8b40d 100644 --- a/archinstall/lib/pacman/pacman.py +++ b/archinstall/lib/pacman/pacman.py @@ -29,11 +29,11 @@ class Pacman: if pacman_db_lock.exists(): warn(tr('Pacman is already running, waiting maximum 10 minutes for it to terminate.')) - started = time.time() + started = time.monotonic() while pacman_db_lock.exists(): time.sleep(0.25) - if time.time() - started > (60 * 10): + if time.monotonic() - started > (60 * 10): error(tr('Pre-existing pacman lock never exited. Please clean up any existing pacman sessions before using archinstall.')) sys.exit(1) From ccbc53d5763e502a8b513338542eaf1e3ad08403 Mon Sep 17 00:00:00 2001 From: utuhiro78 <34818411+utuhiro78@users.noreply.github.com> Date: Wed, 29 Apr 2026 12:44:11 +0900 Subject: [PATCH 19/61] Update Japanese translation (#4498) --- archinstall/locales/ja/LC_MESSAGES/base.mo | Bin 72857 -> 73655 bytes archinstall/locales/ja/LC_MESSAGES/base.po | 24 +++++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/archinstall/locales/ja/LC_MESSAGES/base.mo b/archinstall/locales/ja/LC_MESSAGES/base.mo index bfdce38d646ec876e4516067ceaba61ea4c87c39..043a6f2b917c747b6be6e64d592ac7d5e9d89d70 100644 GIT binary patch delta 15284 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

;rAE8qI#J2nQ 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 Date: Wed, 29 Apr 2026 08:06:10 +0200 Subject: [PATCH 20/61] Danish translation (#4493) * Danish translation * updated base.po and created base.mo --------- Co-authored-by: Steen Rabol --- archinstall/locales/base.pot | 8 +- archinstall/locales/da/LC_MESSAGES/base.mo | Bin 0 -> 64584 bytes archinstall/locales/da/LC_MESSAGES/base.po | 2224 ++++++++++++++++++++ 3 files changed, 2228 insertions(+), 4 deletions(-) create mode 100644 archinstall/locales/da/LC_MESSAGES/base.mo create mode 100644 archinstall/locales/da/LC_MESSAGES/base.po diff --git a/archinstall/locales/base.pot b/archinstall/locales/base.pot index f0b311d4..4e9e0e9f 100644 --- a/archinstall/locales/base.pot +++ b/archinstall/locales/base.pot @@ -644,8 +644,8 @@ msgid "" msgstr "" msgid "" -"Provides a selection of desktop environments and tiling window managers, e." -"g. gnome, kde, sway" +"Provides a selection of desktop environments and tiling window managers, " +"e.g. gnome, kde, sway" msgstr "" msgid "Select your desired desktop environment" @@ -1349,8 +1349,8 @@ msgid "Disable Copy-on-Write" msgstr "" msgid "" -"Provides a selection of desktop environments and tiling window managers, e." -"g. GNOME, KDE Plasma, Sway" +"Provides a selection of desktop environments and tiling window managers, " +"e.g. GNOME, KDE Plasma, Sway" msgstr "" #, python-brace-format diff --git a/archinstall/locales/da/LC_MESSAGES/base.mo b/archinstall/locales/da/LC_MESSAGES/base.mo new file mode 100644 index 0000000000000000000000000000000000000000..b0cb0718a097bd1fbad1dd0bff026305ea0f7018 GIT binary patch literal 64584 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 literal 0 HcmV?d00001 diff --git a/archinstall/locales/da/LC_MESSAGES/base.po b/archinstall/locales/da/LC_MESSAGES/base.po new file mode 100644 index 00000000..cc54891d --- /dev/null +++ b/archinstall/locales/da/LC_MESSAGES/base.po @@ -0,0 +1,2224 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"POT-Creation-Date: \n" +"PO-Revision-Date: \n" +"Last-Translator: \n" +"Language-Team: \n" +"Language: de\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +msgid "[!] A log file has been created here: {} {}" +msgstr "[!] En logfil er blevet oprettet her: {} {}" + +msgid " Please submit this issue (and file) to https://github.com/archlinux/archinstall/issues" +msgstr " Indsend venligst denne fejl (og filen) til https://github.com/archlinux/archinstall/issues" + +msgid "Do you really want to abort?" +msgstr "Vil du virkelig afbryde?" + +msgid "And one more time for verification: " +msgstr "Og én gang til for bekræftelse: " + +msgid "Would you like to use swap on zram?" +msgstr "Vil du bruge swap på zram?" + +msgid "Desired hostname for the installation: " +msgstr "Ønsket værtsnavn for installationen: " + +msgid "Username for required superuser with sudo privileges: " +msgstr "Brugernavn til den påkrævede superbruger med sudo-rettigheder: " + +msgid "Any additional users to install (leave blank for no users): " +msgstr "Eventuelle yderligere brugere der skal oprettes (lad feltet være tomt for ingen): " + +msgid "Should this user be a superuser (sudoer)?" +msgstr "Skal denne bruger være superbruger (sudoer)?" + +msgid "Select a timezone" +msgstr "Vælg en tidszone" + +msgid "Would you like to use GRUB as a bootloader instead of systemd-boot?" +msgstr "Vil du bruge GRUB som bootloader i stedet for systemd-boot?" + +msgid "Choose a bootloader" +msgstr "Vælg en bootloader" + +msgid "Choose an audio server" +msgstr "Vælg en lydserver" + +msgid "Only packages such as base, base-devel, linux, linux-firmware, efibootmgr and optional profile packages are installed." +msgstr "Kun pakker som base, base-devel, linux, linux-firmware, efibootmgr og valgfrie profilpakker installeres." + +msgid "Note: base-devel is no longer installed by default. Add it here if you need build tools." +msgstr "Bemærk: base-devel installeres ikke længere som standard. Tilføj den her, hvis du har brug for byggeværktøjer." + +msgid "If you desire a web browser, such as firefox or chromium, you may specify it in the following prompt." +msgstr "Hvis du ønsker en webbrowser, f.eks. firefox eller chromium, kan du angive den i næste prompt." + +msgid "Write additional packages to install (space separated, leave blank to skip): " +msgstr "Skriv yderligere pakker der skal installeres (adskilt med mellemrum, lad feltet være tomt for at springe over): " + +msgid "Copy ISO network configuration to installation" +msgstr "Kopiér ISO-netværkskonfigurationen til installationen" + +msgid "Use NetworkManager (necessary for configuring internet graphically in GNOME and KDE)" +msgstr "Brug NetworkManager (nødvendigt for grafisk internetkonfiguration i GNOME og KDE)" + +msgid "Select one network interface to configure" +msgstr "Vælg én netværksgrænseflade der skal konfigureres" + +msgid "Select which mode to configure for \"{}\" or skip to use default mode \"{}\"" +msgstr "Vælg hvilken tilstand der skal konfigureres for \"{}\", eller spring over for at bruge standardtilstanden \"{}\"" + +#, python-brace-format +msgid "Enter the IP and subnet for {} (example: 192.168.0.5/24): " +msgstr "Indtast IP og subnet for {} (eksempel: 192.168.0.5/24): " + +msgid "Enter your gateway (router) IP address or leave blank for none: " +msgstr "Indtast IP-adressen på din gateway (router), eller lad feltet være tomt for ingen: " + +msgid "Enter your DNS servers (space separated, blank for none): " +msgstr "Indtast dine DNS-servere (adskilt med mellemrum, tomt for ingen): " + +msgid "Select which filesystem your main partition should use" +msgstr "Vælg hvilket filsystem din hovedpartition skal bruge" + +msgid "Current partition layout" +msgstr "Nuværende partitionslayout" + +msgid "" +"Select what to do with\n" +"{}" +msgstr "" +"Vælg hvad der skal gøres med\n" +"{}" + +msgid "Enter a desired filesystem type for the partition" +msgstr "Indtast den ønskede filsystemtype for partitionen" + +msgid "Enter the start location (in parted units: s, GB, %, etc. ; default: {}): " +msgstr "Indtast startplaceringen (i parted-enheder: s, GB, %, osv.; standard: {}): " + +msgid "Enter the end location (in parted units: s, GB, %, etc. ; ex: {}): " +msgstr "Indtast slutplaceringen (i parted-enheder: s, GB, %, osv.; f.eks.: {}): " + +msgid "{} contains queued partitions, this will remove those, are you sure?" +msgstr "{} indeholder planlagte partitioner. Dette vil fjerne dem. Er du sikker?" + +msgid "" +"{}\n" +"\n" +"Select by index which partitions to delete" +msgstr "" +"{}\n" +"\n" +"Vælg efter indeks hvilke partitioner der skal slettes" + +msgid "" +"{}\n" +"\n" +"Select by index which partition to mount where" +msgstr "" +"{}\n" +"\n" +"Vælg efter indeks hvilken partition der skal monteres hveller" + +msgid " * Partition mount-points are relative to inside the installation, the boot would be /boot as an example." +msgstr " * Partitionens monteringspunkter er relative inde i installationen; boot vil f.eks. være /boot." + +msgid "Select where to mount partition (leave blank to remove mountpoint): " +msgstr "Vælg hvor partitionen skal monteres (lad feltet være tomt for at fjerne monteringspunktet): " + +msgid "" +"{}\n" +"\n" +"Select which partition to mask for formatting" +msgstr "" +"{}\n" +"\n" +"Vælg hvilken partition der skal markeres til formatering" + +msgid "" +"{}\n" +"\n" +"Select which partition to mark as encrypted" +msgstr "" +"{}\n" +"\n" +"Vælg hvilken partition der skal mark som krypteret" + +msgid "" +"{}\n" +"\n" +"Select which partition to mark as bootable" +msgstr "" +"{}\n" +"\n" +"Vælg hvilken partition der skal mark som bootbar" + +msgid "" +"{}\n" +"\n" +"Select which partition to set a filesystem on" +msgstr "" +"{}\n" +"\n" +"Vælg hvilken partition der skal set en filsystem on" + +msgid "Enter a desired filesystem type for the partition: " +msgstr "Indtast den ønskede filsystemtype for partitionen: " + +msgid "Archinstall language" +msgstr "Archinstall-sprog" + +msgid "Wipe all selected drives and use a best-effort default partition layout" +msgstr "Slet alle valgte drev og brug et bedst muligt standardpartitionslayout" + +msgid "Select what to do with each individual drive (followed by partition usage)" +msgstr "Vælg hvad der skal gøres med hvert enkelt drev (efterfulgt af partitionsbrug)" + +msgid "Select what you wish to do with the selected block devices" +msgstr "Vælg hvad du vil gøre med de valgte blokenheder" + +msgid "This is a list of pre-programmed profiles, they might make it easier to install things like desktop environments" +msgstr "Dette er en liste over forudprogrammerede profiler, som kan gøre det lettere at installere ting som skrivebordsmiljøer" + +msgid "Select keyboard layout" +msgstr "Vælg tastaturlayout" + +msgid "Select one of the regions to download packages from" +msgstr "Vælg en af regionerne som pakker skal hentes fra" + +msgid "Select one or more hard drives to use and configure" +msgstr "Vælg en eller flere harddiske der skal bruges og konfigureres" + +msgid "For the best compatibility with your AMD hardware, you may want to use either the all open-source or AMD / ATI options." +msgstr "For bedst kompatibilitet med din AMD-hardware kan du bruge enten alle open source-drivere eller AMD/ATI-valgene." + +msgid "For the best compatibility with your Intel hardware, you may want to use either the all open-source or Intel options.\n" +msgstr "For bedst kompatibilitet med din Intel-hardware kan du bruge enten alle open source-drivere eller Intel-valgene.\n" + +msgid "For the best compatibility with your Nvidia hardware, you may want to use the Nvidia proprietary driver.\n" +msgstr "For bedst kompatibilitet med din Nvidia-hardware kan du bruge Nvidias proprietære driver.\n" + +msgid "" +"\n" +"\n" +"Select a graphics driver or leave blank to install all open-source drivers" +msgstr "" +"\n" +"\n" +"Vælg en grafikdriver, eller lad feltet være tomt for at installere alle open source-drivere" + +msgid "All open-source (default)" +msgstr "Alle open source-drivere (standard)" + +msgid "Choose which kernels to use or leave blank for default \"{}\"" +msgstr "Vælg hvilke kerner der skal bruges, eller lad feltet være tomt for standard \"{}\"" + +msgid "Choose which locale language to use" +msgstr "Vælg hvilket lokaliseringssprog der skal bruges" + +msgid "Choose which locale encoding to use" +msgstr "Vælg hvilken lokaliseringskodning der skal bruges" + +msgid "Select one of the values shown below: " +msgstr "Vælg en af værdierne nedenfor: " + +msgid "Select one or more of the options below: " +msgstr "Vælg en eller flere af mulighederne nedenfor: " + +msgid "Adding partition...." +msgstr "Tilføjer partition...." + +msgid "You need to enter a valid fs-type in order to continue. See `man parted` for valid fs-type's." +msgstr "Du skal indtaste en gyldig fs-type for at fortsætte. Se `man parted` for gyldige fs-typer." + +msgid "Error: Listing profiles on URL \"{}\" resulted in:" +msgstr "Fejl: Liste over profiler fra URL \"{}\" gav:" + +msgid "Error: Could not decode \"{}\" result as JSON:" +msgstr "Fejl: Kunne ikke afkode resultatet \"{}\" som JSON:" + +msgid "Keyboard layout" +msgstr "Tastaturlayout" + +msgid "Mirror region" +msgstr "Spejlregion" + +msgid "Locale language" +msgstr "Lokaliseringssprog" + +msgid "Locale encoding" +msgstr "Lokaliseringskodning" + +msgid "Console font" +msgstr "Konsolskrifttype" + +msgid "Drive(s)" +msgstr "Drev" + +msgid "Disk layout" +msgstr "Disklayout" + +msgid "Encryption password" +msgstr "Krypteringsadgangskode" + +msgid "Swap" +msgstr "Swap" + +msgid "Bootloader" +msgstr "Bootloader" + +msgid "Root password" +msgstr "Root-adgangskode" + +msgid "Superuser account" +msgstr "Superbrugerkonto" + +msgid "User account" +msgstr "Brugerkonto" + +msgid "Profile" +msgstr "Profil" + +msgid "Audio" +msgstr "Lyd" + +msgid "Kernels" +msgstr "Kerner" + +msgid "Additional packages" +msgstr "Yderligere pakker" + +msgid "Network configuration" +msgstr "Netværkskonfiguration" + +msgid "Automatic time sync (NTP)" +msgstr "Automatisk tidssynkronisering (NTP)" + +msgid "Install ({} config(s) missing)" +msgstr "Installer ({} konfiguration(er) mangler)" + +msgid "" +"You decided to skip harddrive selection\n" +"and will use whatever drive-setup is mounted at {} (experimental)\n" +"WARNING: Archinstall won't check the suitability of this setup\n" +"Do you wish to continue?" +msgstr "" +"Du valgte at springe harddiskvalg over\n" +"og vil bruge den drevopsætning der er monteret på {} (eksperimentelt)\n" +"ADVARSEL: Archinstall kontrollerer ikke om denne opsætning er egnet\n" +"Vil du fortsætte?" + +msgid "Re-using partition instance: {}" +msgstr "Genbruger partitionsinstans: {}" + +msgid "Create a new partition" +msgstr "Create en new partition" + +msgid "Delete a partition" +msgstr "Slet en partition" + +msgid "Clear/Delete all partitions" +msgstr "Clear/Slet all partitioner" + +msgid "Assign mount-point for a partition" +msgstr "Tildel monteringspunkt til en partition" + +msgid "Mark/Unmark a partition to be formatted (wipes data)" +msgstr "Markér/fjern markering en partition skal være fellermateret (wipes data)" + +msgid "Mark/Unmark a partition as encrypted" +msgstr "Markér/fjern markering en partition som krypteret" + +msgid "Mark/Unmark a partition as bootable (automatic for /boot)" +msgstr "Markér/fjern markering af en partition som bootbar (automatisk for /boot)" + +msgid "Set desired filesystem for a partition" +msgstr "Angiv ønsket filsystem for en partition" + +msgid "Abort" +msgstr "Afbryd" + +msgid "Hostname" +msgstr "Værtsnavn" + +msgid "Not configured, unavailable unless setup manually" +msgstr "Ikke konfigureret, utilgængelig medmindre det sættes op manuelt" + +msgid "Timezone" +msgstr "Tidszone" + +msgid "Set/Modify the below options" +msgstr "Angiv/ændr indstillingerne nedenfor" + +msgid "Install" +msgstr "Installer" + +msgid "" +"Use ESC to skip\n" +"\n" +msgstr "" +"Brug ESC for at springe over\n" +"\n" + +msgid "Suggest partition layout" +msgstr "Foreslå partitionslayout" + +msgid "Enter a password: " +msgstr "Indtast en adgangskode: " + +msgid "Enter a encryption password for {}" +msgstr "Indtast en krypteringsadgangskode for {}" + +msgid "Enter disk encryption password (leave blank for no encryption): " +msgstr "Indtast adgangskode til diskkryptering (lad feltet være tomt for ingen kryptering): " + +msgid "Create a required super-user with sudo privileges: " +msgstr "Create en required super-bruger med sudo-rettigheder: " + +msgid "Enter root password (leave blank to disable root): " +msgstr "Indtast root-adgangskode (lad feltet være tomt for at deaktivere root): " + +msgid "Password for user \"{}\": " +msgstr "Adgangskode for brugeren \"{}\": " + +msgid "Verifying that additional packages exist (this might take a few seconds)" +msgstr "Kontrollerer at de yderligere pakker findes (det kan tage et par sekunder)" + +msgid "Would you like to use automatic time synchronization (NTP) with the default time servers?\n" +msgstr "Vil du bruge automatisk tidssynkronisering (NTP) med standardtidserverne?\n" + +msgid "" +"Hardware time and other post-configuration steps might be required in order for NTP to work.\n" +"For more information, please check the Arch wiki" +msgstr "" +"Hardwaretid og andre efterkonfigurationstrin kan være nødvendige for at NTP virker.\n" +"Se Arch-wikien for mere information" + +msgid "Enter a username to create an additional user (leave blank to skip): " +msgstr "Indtast et brugernavn for at oprette en ekstra bruger (lad feltet være tomt for at springe over): " + +msgid "Use ESC to skip\n" +msgstr "Brug ESC for at springe over\n" + +msgid "" +"\n" +" Choose an object from the list, and select one of the available actions for it to execute" +msgstr "" +"\n" +" Vælg et objekt fra listen, og vælg en af de tilgængelige handlinger der skal udføres" + +msgid "Cancel" +msgstr "Annuller" + +msgid "Confirm and exit" +msgstr "Bekræft og afslut" + +msgid "Add" +msgstr "Tilføj" + +msgid "Copy" +msgstr "Kopiér" + +msgid "Edit" +msgstr "Redigér" + +msgid "Delete" +msgstr "Slet" + +msgid "Select an action for '{}'" +msgstr "Vælg en handling for '{}'" + +msgid "Copy to new key:" +msgstr "Kopiér til ny nøgle:" + +msgid "Unknown nic type: {}. Possible values are {}" +msgstr "Ukendt NIC-type: {}. Mulige værdier er {}" + +msgid "" +"\n" +"This is your chosen configuration:" +msgstr "" +"\n" +"Dette er din valgte konfiguration:" + +msgid "Pacman is already running, waiting maximum 10 minutes for it to terminate." +msgstr "Pacman kører allerede. Venter højst 10 minutter på at den afsluttes." + +msgid "Pre-existing pacman lock never exited. Please clean up any existing pacman sessions before using archinstall." +msgstr "En eksisterende pacman-lås blev aldrig afsluttet. Ryd op i eventuelle eksisterende pacman-sessioner før du bruger archinstall." + +msgid "Choose which optional additional repositories to enable" +msgstr "Vælg hvilke valgfrie ekstra pakkearkiver der skal aktiveres" + +msgid "Add a user" +msgstr "Tilføj en bruger" + +msgid "Change password" +msgstr "Skift adgangskode" + +msgid "Promote/Demote user" +msgstr "Forfrem/degrader bruger" + +msgid "Delete User" +msgstr "Slet bruger" + +msgid "" +"\n" +"Define a new user\n" +msgstr "" +"\n" +"Definér en ny bruger\n" + +msgid "User Name : " +msgstr "Brugernavn: " + +msgid "Should {} be a superuser (sudoer)?" +msgstr "Skal {} være superbruger (sudoer)?" + +msgid "Define users with sudo privilege: " +msgstr "Definér brugere med sudo-rettigheder: " + +msgid "No network configuration" +msgstr "Ingen netværkskonfiguration" + +msgid "Set desired subvolumes on a btrfs partition" +msgstr "Set desired underenheds on en btrfs partition" + +msgid "" +"{}\n" +"\n" +"Select which partition to set subvolumes on" +msgstr "" +"{}\n" +"\n" +"Vælg hvilken partition der skal set underenheds on" + +msgid "Manage btrfs subvolumes for current partition" +msgstr "Administrér btrfs-underenheder for den aktuelle partition" + +msgid "No configuration" +msgstr "Ingen konfiguration" + +msgid "Save user configuration" +msgstr "Gem brugerkonfiguration" + +msgid "Save user credentials" +msgstr "Gem brugerlegitimationsoplysninger" + +msgid "Save disk layout" +msgstr "Gem disklayout" + +msgid "Save all" +msgstr "Gem alt" + +msgid "Choose which configuration to save" +msgstr "Vælg hvilken konfiguration der skal gemmes" + +msgid "Enter a directory for the configuration(s) to be saved: " +msgstr "Indtast en mappe hvor konfigurationen/konfigurationerne skal gemmes: " + +msgid "Not a valid directory: {}" +msgstr "Ikke en gyldig mappe: {}" + +msgid "The password you are using seems to be weak," +msgstr "Adgangskoden du bruger ser ud til at være svag," + +msgid "are you sure you want to use it?" +msgstr "er du sikker på at du vil bruge den?" + +msgid "Optional repositories" +msgstr "Valgfrie pakkearkiver" + +msgid "Save configuration" +msgstr "Gem konfiguration" + +msgid "Missing configurations:\n" +msgstr "Manglende konfigurationer:\n" + +msgid "Either root-password or at least 1 superuser must be specified" +msgstr "Enten root-adgangskode eller mindst 1 superbruger skal angives" + +msgid "Manage superuser accounts: " +msgstr "Administrér superbrugerkonti: " + +msgid "Manage ordinary user accounts: " +msgstr "Administrér almindelige brugerkonti: " + +msgid " Subvolume :{:16}" +msgstr " Underenhed :{:16}" + +msgid " mounted at {:16}" +msgstr " montereed at {:16}" + +msgid " with option {}" +msgstr " med mulighed {}" + +msgid "" +"\n" +" Fill the desired values for a new subvolume \n" +msgstr "" +"\n" +" Udfyld de ønskede værdier for en ny underenhed \n" + +msgid "Subvolume name " +msgstr "Subvolume navn " + +msgid "Subvolume mountpoint" +msgstr "Subvolume monteringspunkt" + +msgid "Subvolume options" +msgstr "Subvolume muligheds" + +msgid "Save" +msgstr "Gem" + +msgid "Subvolume name :" +msgstr "Subvolume navn :" + +msgid "Select a mount point :" +msgstr "Vælg en montere point :" + +msgid "Select the desired subvolume options " +msgstr "Vælg desired underenhed muligheds " + +msgid "Define users with sudo privilege, by username: " +msgstr "Define brugers med sudo privilege, by brugernavn: " + +#, python-brace-format +msgid "[!] A log file has been created here: {}" +msgstr "[!] A log fil has been created here: {}" + +msgid "Would you like to use BTRFS subvolumes with a default structure?" +msgstr "Vil du bruge BTRFS-underenheder med en standardstruktur?" + +msgid "Would you like to use BTRFS compression?" +msgstr "Vil du bruge BTRFS-komprimering?" + +msgid "Would you like to create a separate partition for /home?" +msgstr "Vil du oprette en separat partition til /home?" + +msgid "The selected drives do not have the minimum capacity required for an automatic suggestion\n" +msgstr "De valgte drev har ikke den minimumskapacitet der kræves for et automatisk forslag\n" + +msgid "Minimum capacity for /home partition: {}GB\n" +msgstr "Minimumskapacitet for /home-partition: {} GB\n" + +msgid "Minimum capacity for Arch Linux partition: {}GB" +msgstr "Minimumskapacitet for Arch Linux-partition: {} GB" + +msgid "Continue" +msgstr "Fortsæt" + +msgid "yes" +msgstr "ja" + +msgid "no" +msgstr "nej" + +msgid "set: {}" +msgstr "angivet: {}" + +msgid "Manual configuration setting must be a list" +msgstr "Manuel konfigurationsindstilling skal være en liste" + +msgid "No iface specified for manual configuration" +msgstr "Ingen grænseflade angivet til manuel konfiguration" + +msgid "Manual nic configuration with no auto DHCP requires an IP address" +msgstr "Manuel NIC-konfiguration uden automatisk DHCP kræver en IP-adresse" + +msgid "Add interface" +msgstr "Tilføj grænseflade" + +msgid "Edit interface" +msgstr "Redigér grænseflade" + +msgid "Delete interface" +msgstr "Slet grænseflade" + +msgid "Select interface to add" +msgstr "Vælg grænseflade der skal tilføjes" + +msgid "Manual configuration" +msgstr "Manuel konfiguration" + +msgid "Mark/Unmark a partition as compressed (btrfs only)" +msgstr "Markér/fjern markering en partition som komprimeret (btrfs only)" + +msgid "The password you are using seems to be weak, are you sure you want to use it?" +msgstr "Adgangskoden du bruger ser ud til at være svag. Er du sikker på at du vil bruge den?" + +msgid "Provides a selection of desktop environments and tiling window managers, e.g. gnome, kde, sway" +msgstr "Giver et udvalg af skrivebordsmiljøer og flisebaserede vindueshåndterere, f.eks. gnome, kde, sway" + +msgid "Select your desired desktop environment" +msgstr "Vælg dit ønskede skrivebordsmiljø" + +msgid "A very basic installation that allows you to customize Arch Linux as you see fit." +msgstr "En meget grundlæggende installation, der lader dig tilpasse Arch Linux som du ønsker." + +msgid "Provides a selection of various server packages to install and enable, e.g. httpd, nginx, mariadb" +msgstr "Giver et udvalg af forskellige serverpakker der kan installeres og aktiveres, f.eks. httpd, nginx, mariadb" + +msgid "Choose which servers to install, if none then a minimal installation will be done" +msgstr "Vælg hvilke servere der skal installeres. Hvis ingen vælges, udføres en minimal installation" + +msgid "Installs a minimal system as well as xorg and graphics drivers." +msgstr "Installerer et minimalt system samt xorg og grafikdrivere." + +msgid "Press Enter to continue." +msgstr "Tryk Enter for at fortsætte." + +msgid "Would you like to chroot into the newly created installation and perform post-installation configuration?" +msgstr "Vil du chroote ind i den nyoprettede installation og udføre efterkonfiguration?" + +msgid "Are you sure you want to reset this setting?" +msgstr "Er du sikker på at du vil nulstille denne indstilling?" + +msgid "Select one or more hard drives to use and configure\n" +msgstr "Vælg en eller flere harddiske der skal bruges og konfigureres\n" + +msgid "Any modifications to the existing setting will reset the disk layout!" +msgstr "Enhver ændring af den eksisterende indstilling vil nulstille disklayoutet!" + +msgid "If you reset the harddrive selection this will also reset the current disk layout. Are you sure?" +msgstr "Hvis du nulstiller harddiskvalget, nulstilles det nuværende disklayout også. Er du sikker?" + +msgid "Save and exit" +msgstr "Gem og afslut" + +msgid "" +"{}\n" +"contains queued partitions, this will remove those, are you sure?" +msgstr "" +"{}\n" +"indeholder planlagte partitioner. Dette vil fjerne dem. Er du sikker?" + +msgid "No audio server" +msgstr "Ingen lydserver" + +msgid "(default)" +msgstr "(standard)" + +msgid "Use ESC to skip" +msgstr "Brug ESC for at springe over" + +msgid "" +"Use CTRL+C to reset current selection\n" +"\n" +msgstr "" +"Brug CTRL+C for at nulstille det aktuelle valg\n" +"\n" + +msgid "Copy to: " +msgstr "Kopiér til: " + +msgid "Edit: " +msgstr "Redigér: " + +msgid "Key: " +msgstr "Nøgle: " + +msgid "Edit {}: " +msgstr "Redigér {}: " + +msgid "Add: " +msgstr "Tilføj: " + +msgid "Value: " +msgstr "Værdi: " + +msgid "You can skip selecting a drive and partitioning and use whatever drive-setup is mounted at /mnt (experimental)" +msgstr "You can springe over selecting en drev og partitioning og use whatever drev-setup is montereed at /mnt (experimental)" + +msgid "Select one of the disks or skip and use /mnt as default" +msgstr "Vælg en af diskene, eller spring over og brug /mnt som standard" + +msgid "Select which partitions to mark for formatting:" +msgstr "Vælg hvilke partitioner der skal markeres til formatering:" + +msgid "Use HSM to unlock encrypted drive" +msgstr "Brug HSM til at låse krypteret drev op" + +msgid "Device" +msgstr "Enhed" + +msgid "Size" +msgstr "Størrelse" + +msgid "Free space" +msgstr "Ledig plads" + +msgid "Bus-type" +msgstr "Bustype" + +msgid "Either root-password or at least 1 user with sudo privileges must be specified" +msgstr "Enten root-adgangskode eller mindst 1 bruger med sudo-rettigheder skal angives" + +msgid "Enter username (leave blank to skip): " +msgstr "Indtast brugernavn (lad feltet være tomt for at springe over): " + +msgid "The username you entered is invalid. Try again" +msgstr "The brugernavn you entered is ingyldig. Try again" + +msgid "Should \"{}\" be a superuser (sudo)?" +msgstr "Skal \"{}\" være superbruger (sudo)?" + +msgid "Select which partitions to encrypt" +msgstr "Vælg hvilke partitioner der skal krypteres" + +msgid "very weak" +msgstr "meget svag" + +msgid "weak" +msgstr "svag" + +msgid "moderate" +msgstr "moderat" + +msgid "strong" +msgstr "stærk" + +msgid "Add subvolume" +msgstr "Tilføj underenhed" + +msgid "Edit subvolume" +msgstr "Redigér underenhed" + +msgid "Delete subvolume" +msgstr "Slet underenhed" + +msgid "Configured {} interfaces" +msgstr "Configured {} grænseflades" + +msgid "This option enables the number of parallel downloads that can occur during installation" +msgstr "Denne indstilling aktiverer antallet af parallelle downloads under installationen" + +msgid "" +"Enter the number of parallel downloads to be enabled.\n" +" (Enter a value between 1 to {})\n" +"Note:" +msgstr "" +"Indtast antallet af parallelle downloads der skal aktiveres.\n" +" (Indtast en værdi mellem 1 og {})\n" +"Bemærk:" + +msgid " - Maximum value : {} ( Allows {} parallel downloads, allows {} downloads at a time )" +msgstr " - Maksimumsværdi : {} (tillader {} parallelle downloads, {} downloads ad gangen)" + +msgid " - Minimum value : 1 ( Allows 1 parallel download, allows 2 downloads at a time )" +msgstr " - Minimumsværdi : 1 (tillader 1 parallel download, 2 downloads ad gangen)" + +msgid " - Disable/Default : 0 ( Disables parallel downloading, allows only 1 download at a time )" +msgstr " - Deaktiver/standard : 0 (deaktiverer parallel download og tillader kun 1 download ad gangen)" + +#, python-brace-format +msgid "Invalid input! Try again with a valid input [1 to {max_downloads}, or 0 to disable]" +msgstr "Ugyldigt input! Prøv igen med et gyldigt input [1 til {max_downloads}, eller 0 for at deaktivere]" + +msgid "Parallel Downloads" +msgstr "Parallelle downloads" + +msgid "Pacman" +msgstr "Pacman" + +msgid "Color" +msgstr "Farve" + +#, python-brace-format +msgid "Enter the number of parallel downloads (1-{})" +msgstr "Indtast antallet af parallelle downloads (1-{})" + +msgid "Enable colored output for pacman" +msgstr "Aktivér farvet output for pacman" + +msgid "ESC to skip" +msgstr "ESC for at springe over" + +msgid "CTRL+C to reset" +msgstr "CTRL+C for at nulstille" + +msgid "TAB to select" +msgstr "TAB for at vælge" + +msgid "[Default value: 0] > " +msgstr "[Standard værdi: 0] > " + +msgid "To be able to use this translation, please install a font manually that supports the language." +msgstr "For at kunne bruge denne oversættelse skal du manuelt installere en skrifttype der understøtter sproget." + +msgid "The font should be stored as {}" +msgstr "Skrifttypen skal gemmes som {}" + +msgid "Archinstall requires root privileges to run. See --help for more." +msgstr "Archinstall kræver root-rettigheder for at køre. Se --help for mere information." + +msgid "Select an execution mode" +msgstr "Vælg en execution tilstand" + +#, python-brace-format +msgid "Unable to fetch profile from specified url: {}" +msgstr "Kan ikke hente profil fra den angivne URL: {}" + +#, python-brace-format +msgid "Profiles must have unique name, but profile definitions with duplicate name found: {}" +msgstr "Profils must have unique navn, but profil definitions med duplicate navn found: {}" + +msgid "Select one or more devices to use and configure" +msgstr "Vælg en eller flere enheder der skal bruges og konfigureres" + +msgid "If you reset the device selection this will also reset the current disk layout. Are you sure?" +msgstr "Hvis du nulstiller enhedsvalget, nulstilles det aktuelle disklayout også. Er du sikker?" + +msgid "Existing Partitions" +msgstr "Existing Partitioner" + +msgid "Select a partitioning option" +msgstr "Vælg en partitioning mulighed" + +msgid "Enter the root directory of the mounted devices: " +msgstr "Indtast root-mappen for de monterede enheder: " + +#, python-brace-format +msgid "Minimum capacity for /home partition: {}GiB\n" +msgstr "Minimumskapacitet for /home-partition: {} GiB\n" + +#, python-brace-format +msgid "Minimum capacity for Arch Linux partition: {}GiB" +msgstr "Minimumskapacitet for Arch Linux-partition: {} GiB" + +msgid "This is a list of pre-programmed profiles_bck, they might make it easier to install things like desktop environments" +msgstr "Dette er en liste over forudprogrammerede profiles_bck, som kan gøre det lettere at installere ting som skrivebordsmiljøer" + +msgid "Current profile selection" +msgstr "Nuværende profil selection" + +msgid "Remove all newly added partitions" +msgstr "Remove all newly tilføjed partitioner" + +msgid "Assign mountpoint" +msgstr "Assign monteringspunkt" + +msgid "Mark/Unmark to be formatted (wipes data)" +msgstr "Markér/fjern markering skal være fellermateret (wipes data)" + +msgid "Mark/Unmark as bootable" +msgstr "Markér/fjern markering som bootbar" + +msgid "Change filesystem" +msgstr "Ændr filsystem" + +msgid "Mark/Unmark as compressed" +msgstr "Markér/fjern markering som komprimeret" + +msgid "Set subvolumes" +msgstr "Set underenheds" + +msgid "Delete partition" +msgstr "Slet partition" + +msgid "Partition" +msgstr "Partition" + +msgid "This partition is currently encrypted, to format it a filesystem has to be specified" +msgstr "Denne partition er i øjeblikket krypteret. For at formatere den skal der angives et filsystem." + +msgid "Partition mount-points are relative to inside the installation, the boot would be /boot as an example." +msgstr "Partitionens monteringspunkter er relative inde i installationen; boot vil f.eks. være /boot." + +msgid "If mountpoint /boot is set, then the partition will also be marked as bootable." +msgstr "Hvis monteringspunktet /boot angives, markeres partitionen også som bootbar." + +msgid "Mountpoint: " +msgstr "Monteringspunkt: " + +msgid "Current free sectors on device {}:" +msgstr "Nuværende free sectellers on enhed {}:" + +msgid "Total sectors: {}" +msgstr "Total sectellers: {}" + +msgid "Enter the start sector (default: {}): " +msgstr "Indtast start secteller (standard: {}): " + +msgid "Enter the end sector of the partition (percentage or block number, default: {}): " +msgstr "Indtast partitionens slutsektor (procent eller bloknummer, standard: {}): " + +msgid "This will remove all newly added partitions, continue?" +msgstr "Dette vil fjerne alle nyligt tilføjede partitioner. Fortsæt?" + +#, python-brace-format +msgid "Partition management: {}" +msgstr "Partitionshåndtering: {}" + +#, python-brace-format +msgid "Total length: {}" +msgstr "Samlet længde: {}" + +msgid "Encryption type" +msgstr "Krypteringstype" + +msgid "Iteration time" +msgstr "Iterationstid" + +msgid "Enter iteration time for LUKS encryption (in milliseconds)" +msgstr "Indtast iterationstid for LUKS-kryptering (i millisekunder)" + +msgid "Higher values increase security but slow down boot time" +msgstr "Higher værdis increase security but slow down boot time" + +msgid "Default: 10000ms, Recommended range: 1000-60000" +msgstr "Standard: 10000 ms, anbefalet område: 1000-60000" + +msgid "Iteration time cannot be empty" +msgstr "Iterationstid må ikke være tom" + +msgid "Iteration time must be at least 100ms" +msgstr "Iterationstid skal være mindst 100 ms" + +msgid "Iteration time must be at most 120000ms" +msgstr "Iterationstid må højst være 120000 ms" + +msgid "Please enter a valid number" +msgstr "Indtast venligst et gyldigt tal" + +msgid "Partitions" +msgstr "Partitioner" + +msgid "No HSM devices available" +msgstr "No HSM enheds available" + +msgid "Partitions to be encrypted" +msgstr "Partitioner skal være krypteret" + +msgid "Select disk encryption option" +msgstr "Vælg disk kryptering mulighed" + +msgid "Select a FIDO2 device to use for HSM" +msgstr "Vælg en FIDO2-enhed der skal bruges til HSM" + +msgid "Use a best-effort default partition layout" +msgstr "Use en best-effellert standard partition layout" + +msgid "Manual Partitioning" +msgstr "Manuel partitionering" + +msgid "Pre-mounted configuration" +msgstr "Pre-montereed konfiguration" + +msgid "Unknown" +msgstr "Ukendt" + +msgid "Partition encryption" +msgstr "Partition kryptering" + +#, python-brace-format +msgid " ! Formatting {} in " +msgstr " ! Fellermatting {} in " + +#, fuzzy +msgid "← Back" +msgstr " Tilbage" + +msgid "Disk encryption" +msgstr "Disk kryptering" + +msgid "Configuration" +msgstr "Konfiguration" + +msgid "Password" +msgstr "Adgangskode" + +msgid "All settings will be reset, are you sure?" +msgstr "Alle indstillinger nulstilles. Er du sikker?" + +msgid "Back" +msgstr "Tilbage" + +msgid "Please chose which greeter to install for the chosen profiles: {}" +msgstr "Vælg hvilken login-skærm der skal installeres for de valgte profiler: {}" + +#, python-brace-format +msgid "Environment type: {}" +msgstr "Miljøtype: {}" + +msgid "The proprietary Nvidia driver is not supported by Sway. It is likely that you will run into issues, are you okay with that?" +msgstr "Den proprietære Nvidia-driver understøttes ikke af Sway. Du vil sandsynligvis støde på problemer. Er det i orden?" + +msgid "Installed packages" +msgstr "Installered pakker" + +msgid "Add profile" +msgstr "Tilføj profil" + +msgid "Edit profile" +msgstr "Redigér profil" + +msgid "Delete profile" +msgstr "Slet profil" + +msgid "Profile name: " +msgstr "Profil navn: " + +msgid "The profile name you entered is already in use. Try again" +msgstr "The profil navn you entered is already in use. Try again" + +msgid "Packages to be install with this profile (space separated, leave blank to skip): " +msgstr "Pakker der skal installeres med denne profil (adskilt med mellemrum, lad feltet være tomt for at springe over): " + +msgid "Services to be enabled with this profile (space separated, leave blank to skip): " +msgstr "Tjenester der skal aktiveres med denne profil (adskilt med mellemrum, lad feltet være tomt for at springe over): " + +msgid "Should this profile be enabled for installation?" +msgstr "Skal denne profil aktiveres til installation?" + +msgid "Create your own" +msgstr "Opret din egen" + +msgid "" +"\n" +"Select a graphics driver or leave blank to install all open-source drivers" +msgstr "" +"\n" +"Vælg en grafikdriver, eller lad feltet være tomt for at installere alle open source-drivere" + +msgid "Sway needs access to your seat (collection of hardware devices i.e. keyboard, mouse, etc)" +msgstr "Sway skal have adgang til dit seat (samling af hardwareenheder, dvs. tastatur, mus osv.)" + +msgid "" +"\n" +"\n" +"Choose an option to give Sway access to your hardware" +msgstr "" +"\n" +"\n" +"Vælg en mulighed for at give Sway adgang til din hardware" + +msgid "Graphics driver" +msgstr "Grafikdriver" + +msgid "Greeter" +msgstr "Login-skærm" + +msgid "Please chose which greeter to install" +msgstr "Vælg hvilken login-skærm der skal installeres" + +msgid "This is a list of pre-programmed default_profiles" +msgstr "Dette er en liste over forudprogrammerede default_profiles" + +msgid "Disk configuration" +msgstr "Diskkonfiguration" + +msgid "Profiles" +msgstr "Profiler" + +msgid "Finding possible directories to save configuration files ..." +msgstr "Finder mulige mapper til at gemme konfigurationsfiler ..." + +msgid "Select directory (or directories) for saving configuration files" +msgstr "Vælg mappe (eller mapper) til lagring af konfigurationsfiler" + +msgid "Add a custom mirror" +msgstr "Tilføj en brugerdefineret spejl" + +msgid "Change custom mirror" +msgstr "Ændr brugerdefineret spejl" + +msgid "Delete custom mirror" +msgstr "Slet brugerdefineret spejl" + +msgid "Enter name (leave blank to skip): " +msgstr "Indtast navn (lad feltet være tomt for at springe over): " + +msgid "Enter url (leave blank to skip): " +msgstr "Indtast URL (lad feltet være tomt for at springe over): " + +msgid "Select signature check option" +msgstr "Vælg signatur kontrol mulighed" + +msgid "Select signature option" +msgstr "Vælg signatur mulighed" + +msgid "Custom mirrors" +msgstr "Brugerdefinerede spejle" + +msgid "Defined" +msgstr "Defineret" + +msgid "Save user configuration (including disk layout)" +msgstr "Gem bruger konfiguration (including disk layout)" + +msgid "" +"Enter a directory for the configuration(s) to be saved (tab completion enabled)\n" +"Save directory: " +msgstr "" +"Indtast en mappe hvor konfigurationen/konfigurationerne skal gemmes (tab-fuldførelse aktiveret)\n" +"Gemmemappe: " + +msgid "" +"Do you want to save {} configuration file(s) in the following location?\n" +"\n" +"{}" +msgstr "" +"Vil du gemme {} konfigurationsfil(er) på følgende placering?\n" +"\n" +"{}" + +msgid "Saving {} configuration files to {}" +msgstr "Gemmer {} konfigurationsfiler til {}" + +msgid "Mirrors" +msgstr "Spejle" + +msgid "Mirror regions" +msgstr "Mirreller regioner" + +msgid " - Maximum value : {} ( Allows {} parallel downloads, allows {max_downloads+1} downloads at a time )" +msgstr " - Maksimumsværdi : {} (tillader {} parallelle downloads, {max_downloads+1} downloads ad gangen)" + +msgid "Invalid input! Try again with a valid input [1 to {}, or 0 to disable]" +msgstr "Ugyldigt input! Prøv igen med et gyldigt input [1 til {}, eller 0 for at deaktivere]" + +msgid "Locales" +msgstr "Lokaliseringer" + +msgid "Use NetworkManager (necessary to configure internet graphically in GNOME and KDE)" +msgstr "Brug NetworkManager (nødvendigt for at konfigurere internet grafisk i GNOME og KDE)" + +msgid "Total: {} / {}" +msgstr "I alt: {} / {}" + +msgid "All entered values can be suffixed with a unit: B, KB, KiB, MB, MiB..." +msgstr "Alle indtastede værdier kan afsluttes med en enhed: B, KB, KiB, MB, MiB..." + +msgid "If no unit is provided, the value is interpreted as sectors" +msgstr "If no unit is provided, værdi is interpreted som sectellers" + +msgid "Enter start (default: sector {}): " +msgstr "Indtast start (standard: secteller {}): " + +msgid "Enter end (default: {}): " +msgstr "Indtast end (standard: {}): " + +msgid "Unable to determine fido2 devices. Is libfido2 installed?" +msgstr "Kan ikke registrere fido2-enheder. Er libfido2 installeret?" + +msgid "Path" +msgstr "Sti" + +msgid "Manufacturer" +msgstr "Producent" + +msgid "Product" +msgstr "Produkt" + +#, python-brace-format +msgid "Invalid configuration: {error}" +msgstr "Ugyldig konfiguration: {error}" + +msgid "Ready to install" +msgstr "Klar til installation" + +msgid "Disks" +msgstr "Diske" + +msgid "Packages" +msgstr "Pakker" + +msgid "Network" +msgstr "Netværk" + +msgid "Locale" +msgstr "Lokalisering" + +msgid "Type" +msgstr "Type" + +msgid "This option enables the number of parallel downloads that can occur during package downloads" +msgstr "Denne indstilling aktiverer antallet af parallelle downloads under pakkehentning" + +msgid "" +"Enter the number of parallel downloads to be enabled.\n" +"\n" +"Note:\n" +msgstr "" +"Indtast antallet af parallelle downloads der skal aktiveres.\n" +"\n" +"Bemærk:\n" + +#, python-brace-format +msgid " - Maximum recommended value : {} ( Allows {} parallel downloads at a time )" +msgstr " - Maksimal anbefalet værdi : {} (tillader {} parallelle downloads ad gangen)" + +msgid " - Disable/Default : 0 ( Disables parallel downloading, allows only 1 download at a time )\n" +msgstr " - Deaktiver/standard : 0 (deaktiverer parallel download og tillader kun 1 download ad gangen)\n" + +msgid "Invalid input! Try again with a valid input [or 0 to disable]" +msgstr "Ugyldigt input! Prøv igen med et gyldigt input [eller 0 for at deaktivere]" + +msgid "Hyprland needs access to your seat (collection of hardware devices i.e. keyboard, mouse, etc)" +msgstr "Hyprland skal have adgang til dit seat (samling af hardwareenheder, dvs. tastatur, mus osv.)" + +msgid "" +"\n" +"\n" +"Choose an option to give Hyprland access to your hardware" +msgstr "" +"\n" +"\n" +"Vælg en mulighed for at give Hyprland adgang til din hardware" + +msgid "All entered values can be suffixed with a unit: %, B, KB, KiB, MB, MiB..." +msgstr "Alle indtastede værdier kan afsluttes med en enhed: %, B, KB, KiB, MB, MiB..." + +msgid "Would you like to use unified kernel images?" +msgstr "Vil du use unified kerne images?" + +msgid "Unified kernel images" +msgstr "Unified kerne images" + +msgid "Waiting for time sync (timedatectl show) to complete." +msgstr "Venter på at tidssynkronisering (timedatectl show) bliver fuldført." + +msgid "Time syncronization not completing, while you wait - check the docs for workarounds: https://archinstall.readthedocs.io/" +msgstr "Tidssynkronisering fuldføres ikke. Mens du venter, kan du se dokumentationen for løsninger: https://archinstall.readthedocs.io/" + +msgid "Skipping waiting for automatic time sync (this can cause issues if time is out of sync during installation)" +msgstr "Springer ventetid på automatisk tidssynkronisering over (det kan give problemer, hvis tiden ikke er synkroniseret under installationen)" + +msgid "Waiting for Arch Linux keyring sync (archlinux-keyring-wkd-sync) to complete." +msgstr "Venter på at Arch Linux-nøgleringssynkronisering (archlinux-keyring-wkd-sync) bliver fuldført." + +msgid "Selected profiles: " +msgstr "Vælged profils: " + +msgid "Time synchronization not completing, while you wait - check the docs for workarounds: https://archinstall.readthedocs.io/" +msgstr "Tidssynkronisering fuldføres ikke. Mens du venter, kan du se dokumentationen for løsninger: https://archinstall.readthedocs.io/" + +msgid "Mark/Unmark as nodatacow" +msgstr "Markér/fjern markering som nodatacow" + +msgid "Would you like to use compression or disable CoW?" +msgstr "Vil du use komprimering eller deaktivere CoW?" + +msgid "Use compression" +msgstr "Use komprimering" + +msgid "Disable Copy-on-Write" +msgstr "Deaktiver Copy-on-Write" + +msgid "Provides a selection of desktop environments and tiling window managers, e.g. GNOME, KDE Plasma, Sway" +msgstr "Giver et udvalg af skrivebordsmiljøer og flisebaserede vindueshåndterere, f.eks. GNOME, KDE Plasma, Sway" + +#, python-brace-format +msgid "Configuration type: {}" +msgstr "Konfiguration type: {}" + +msgid "LVM configuration type" +msgstr "LVM konfiguration type" + +msgid "LVM disk encryption with more than 2 partitions is currently not supported" +msgstr "LVM disk kryptering med mellere than 2 partitioner is aktuellely not suppellerted" + +msgid "Use NetworkManager (necessary to configure internet graphically in GNOME and KDE Plasma)" +msgstr "Brug NetworkManager (nødvendigt for at konfigurere internet grafisk i GNOME og KDE Plasma)" + +msgid "Select a LVM option" +msgstr "Vælg en LVM mulighed" + +msgid "Partitioning" +msgstr "Partitionering" + +msgid "Logical Volume Management (LVM)" +msgstr "Logisk diskenhedshåndtering (LVM)" + +msgid "Physical volumes" +msgstr "Fysiske diskenheder" + +msgid "Volumes" +msgstr "Diskenheder" + +msgid "LVM volumes" +msgstr "LVM-diskenheder" + +msgid "LVM volumes to be encrypted" +msgstr "LVM volumes skal være krypteret" + +msgid "Select which LVM volumes to encrypt" +msgstr "Vælg hvilke LVM-diskenheder der skal krypteres" + +msgid "Default layout" +msgstr "Standard layout" + +msgid "No Encryption" +msgstr "Ingen kryptering" + +msgid "LUKS" +msgstr "LUKS" + +msgid "LVM on LUKS" +msgstr "LVM på LUKS" + +msgid "LUKS on LVM" +msgstr "LUKS på LVM" + +msgid "Yes" +msgstr "Ja" + +msgid "No" +msgstr "Nej" + +msgid "Archinstall help" +msgstr "Archinstallere help" + +msgid " (default)" +msgstr " (standard)" + +msgid "Press Ctrl+h for help" +msgstr "Tryk Ctrl+h for hjælp" + +msgid "Choose an option to give Sway access to your hardware" +msgstr "Vælg en mulighed for at give Sway adgang til din hardware" + +msgid "Seat access" +msgstr "Seat-adgang" + +msgid "Mountpoint" +msgstr "Monteringspunkt" + +msgid "HSM" +msgstr "HSM" + +msgid "Enter disk encryption password (leave blank for no encryption)" +msgstr "Indtast adgangskode til diskkryptering (lad feltet være tomt for ingen kryptering)" + +msgid "Disk encryption password" +msgstr "Disk kryptering adgangskode" + +msgid "Partition - New" +msgstr "Partition - ny" + +msgid "Filesystem" +msgstr "Filsystem" + +msgid "Invalid size" +msgstr "Ugyldig størrelse" + +msgid "Start (default: sector {}): " +msgstr "Start (standard: secteller {}): " + +msgid "End (default: {}): " +msgstr "End (standard: {}): " + +msgid "Subvolume name" +msgstr "Subvolume navn" + +msgid "Disk configuration type" +msgstr "Disk konfiguration type" + +msgid "Root mount directory" +msgstr "Root-monteringsmappe" + +msgid "Select language" +msgstr "Vælg sprog" + +msgid "Write additional packages to install (space separated, leave blank to skip)" +msgstr "Skriv yderligere pakker der skal installeres (adskilt med mellemrum, lad feltet være tomt for at springe over)" + +msgid "Invalid download number" +msgstr "Ugyldigt downloadantal" + +msgid "Number downloads" +msgstr "Antal downloads" + +msgid "The username you entered is invalid" +msgstr "The brugernavn you entered is ingyldig" + +msgid "Username" +msgstr "Brugernavn" + +#, python-brace-format +msgid "Should \"{}\" be a superuser (sudo)?\n" +msgstr "Skal \"{}\" være superbruger (sudo)?\n" + +msgid "Interfaces" +msgstr "Grænseflader" + +msgid "You need to enter a valid IP in IP-config mode" +msgstr "Du skal indtaste en gyldig IP i IP-konfigurationstilstand" + +msgid "Modes" +msgstr "Tilstande" + +msgid "IP address" +msgstr "IP-adresse" + +msgid "Enter your gateway (router) IP address (leave blank for none)" +msgstr "Indtast IP-adressen på din gateway (router) (lad feltet være tomt for ingen)" + +msgid "Gateway address" +msgstr "Gateway-adresse" + +msgid "Enter your DNS servers with space separated (leave blank for none)" +msgstr "Indtast dine DNS-servere adskilt med mellemrum (lad feltet være tomt for ingen)" + +msgid "DNS servers" +msgstr "DNS-servere" + +msgid "Configure interfaces" +msgstr "Configure grænseflades" + +msgid "Kernel" +msgstr "Kerne" + +msgid "UEFI is not detected and some options are disabled" +msgstr "UEFI is not detected og some muligheds are deaktiveret" + +msgid "Info" +msgstr "Info" + +msgid "The proprietary Nvidia driver is not supported by Sway." +msgstr "The proprietary Nvidia drevr is not suppellerted by Sway." + +msgid "It is likely that you will run into issues, are you okay with that?" +msgstr "Du vil sandsynligvis støde på problemer. Er det i orden?" + +msgid "Main profile" +msgstr "Main profil" + +msgid "Confirm password" +msgstr "Confirm adgangskode" + +msgid "The confirmation password did not match, please try again" +msgstr "The confirmation adgangskode did not match, please try again" + +msgid "Not a valid directory" +msgstr "Not en gyldig mappe" + +msgid "Would you like to continue?" +msgstr "Vil du fellertsætte?" + +msgid "Directory" +msgstr "Mappe" + +msgid "Enter a directory for the configuration(s) to be saved (tab completion enabled)" +msgstr "Indtast en mappe hvor konfigurationen/konfigurationerne skal gemmes (tab-fuldførelse aktiveret)" + +#, python-brace-format +msgid "Do you want to save the configuration file(s) to {}?" +msgstr "Vil du gem konfiguration fil(s) to {}?" + +msgid "Enabled" +msgstr "Aktiveret" + +msgid "Disabled" +msgstr "Deaktiveret" + +msgid "Please submit this issue (and file) to https://github.com/archlinux/archinstall/issues" +msgstr "Indsend venligst denne fejl (og filen) til https://github.com/archlinux/archinstall/issues" + +msgid "Mirror name" +msgstr "Spejlnavn" + +msgid "Url" +msgstr "URL" + +msgid "Select signature check" +msgstr "Vælg signatur kontrol" + +msgid "Select execution mode" +msgstr "Vælg execution tilstand" + +msgid "Press ? for help" +msgstr "Tryk ? for hjælp" + +msgid "Choose an option to give Hyprland access to your hardware" +msgstr "Vælg en mulighed for at give Hyprland adgang til din hardware" + +msgid "Additional repositories" +msgstr "Yderligere pakkearkiver" + +msgid "NTP" +msgstr "NTP" + +msgid "Swap on zram" +msgstr "Swap på zram" + +msgid "Name" +msgstr "Navn" + +msgid "Signature check" +msgstr "Signaturkontrol" + +#, python-brace-format +msgid "Selected free space segment on device {}:" +msgstr "Vælged ledig plads segment on enhed {}:" + +#, python-brace-format +msgid "Size: {} / {}" +msgstr "Størrelse: {} / {}" + +#, python-brace-format +msgid "Size (default: {}): " +msgstr "Størrelse (standard: {}): " + +msgid "HSM device" +msgstr "HSM-enhed" + +msgid "Some packages could not be found in the repository" +msgstr "Nogle pakker kunne ikke findes i pakkearkivet" + +msgid "User" +msgstr "Bruger" + +msgid "The specified configuration will be applied" +msgstr "Den angivne konfiguration vil blive anvendt" + +msgid "Wipe" +msgstr "Slet" + +msgid "Mark/Unmark as XBOOTLDR" +msgstr "Markér/fjern markering som XBOOTLDR" + +msgid "Loading packages..." +msgstr "Indlæser pakker..." + +msgid "Select any packages from the below list that should be installed additionally" +msgstr "Vælg eventuelle pakker fra listen nedenfor som også skal installeres" + +msgid "Add a custom repository" +msgstr "Tilføj en brugerdefineret pakkearkiv" + +msgid "Change custom repository" +msgstr "Ændr brugerdefineret pakkearkiv" + +msgid "Delete custom repository" +msgstr "Slet brugerdefineret pakkearkiv" + +msgid "Repository name" +msgstr "Arkivnavn" + +msgid "Add a custom server" +msgstr "Tilføj en brugerdefineret server" + +msgid "Change custom server" +msgstr "Ændr brugerdefineret server" + +msgid "Delete custom server" +msgstr "Slet brugerdefineret server" + +msgid "Server url" +msgstr "Server-URL" + +msgid "Select regions" +msgstr "Vælg regioner" + +msgid "Add custom servers" +msgstr "Tilføj brugerdefineret servers" + +msgid "Add custom repository" +msgstr "Tilføj brugerdefineret pakkearkiv" + +msgid "Loading mirror regions..." +msgstr "Loading spejl regioner..." + +msgid "Mirrors and repositories" +msgstr "Mirrellers og pakkearkiver" + +msgid "Selected mirror regions" +msgstr "Vælged spejl regioner" + +msgid "Custom servers" +msgstr "Brugerdefinerede servere" + +msgid "Custom repositories" +msgstr "Brugerdefinerede pakkearkiver" + +msgid "Only ASCII characters are supported" +msgstr "Only ASCII characters are suppellerted" + +msgid "Show help" +msgstr "Vis hjælp" + +msgid "Exit help" +msgstr "Afslut hjælp" + +msgid "Preview scroll up" +msgstr "Forhåndsvisning rul op" + +msgid "Preview scroll down" +msgstr "Forhåndsvisning rul ned" + +msgid "Move up" +msgstr "Flyt op" + +msgid "Move down" +msgstr "Flyt ned" + +msgid "Move right" +msgstr "Flyt til højre" + +msgid "Move left" +msgstr "Flyt til venstre" + +msgid "Jump to entry" +msgstr "Hop til post" + +msgid "Skip selection (if available)" +msgstr "Spring over selection (if available)" + +msgid "Reset selection (if available)" +msgstr "Nulstil valg (hvis tilgængeligt)" + +msgid "Select on single select" +msgstr "Vælg on single select" + +msgid "Select on multi select" +msgstr "Vælg on multi select" + +msgid "Reset" +msgstr "Nulstil" + +msgid "Skip selection menu" +msgstr "Spring over selection menu" + +msgid "Start search mode" +msgstr "Start search tilstand" + +msgid "Exit search mode" +msgstr "Exit search tilstand" + +msgid "General" +msgstr "Generelt" + +msgid "Navigation" +msgstr "Navigation" + +msgid "Selection" +msgstr "Valg" + +msgid "Search" +msgstr "Søg" + +msgid "Down" +msgstr "Ned" + +msgid "Up" +msgstr "Op" + +msgid "Confirm" +msgstr "Bekræft" + +msgid "Focus right" +msgstr "Fokus til højre" + +msgid "Focus left" +msgstr "Fokus til venstre" + +msgid "Toggle" +msgstr "Slå til/fra" + +msgid "Show/Hide help" +msgstr "Vis/skjul hjælp" + +msgid "Quit" +msgstr "Afslut" + +msgid "First" +msgstr "Første" + +msgid "Last" +msgstr "Sidste" + +msgid "Select" +msgstr "Vælg" + +msgid "Page Up" +msgstr "Side op" + +msgid "Page Down" +msgstr "Side ned" + +msgid "Page up" +msgstr "Side op" + +msgid "Page down" +msgstr "Side ned" + +msgid "Page Left" +msgstr "Side venstre" + +msgid "Page Right" +msgstr "Side højre" + +msgid "Cursor up" +msgstr "Markør op" + +msgid "Cursor down" +msgstr "Markør ned" + +msgid "Cursor right" +msgstr "Markør højre" + +msgid "Cursor left" +msgstr "Markør venstre" + +msgid "Top" +msgstr "Top" + +msgid "Bottom" +msgstr "Bund" + +msgid "Home" +msgstr "Hjem" + +msgid "End" +msgstr "Slut" + +msgid "Toggle option" +msgstr "Toggle mulighed" + +msgid "Scroll Up" +msgstr "Rul op" + +msgid "Scroll Down" +msgstr "Rul ned" + +msgid "Scroll Left" +msgstr "Rul til venstre" + +msgid "Scroll Right" +msgstr "Rul til højre" + +msgid "Scroll Home" +msgstr "Rul til start" + +msgid "Scroll End" +msgstr "Rul til slut" + +msgid "Focus Next" +msgstr "Fokusér næste" + +msgid "Focus Previous" +msgstr "Fokusér forrige" + +msgid "Copy selected text" +msgstr "Kopiér markeret tekst" + +msgid "labwc needs access to your seat (collection of hardware devices i.e. keyboard, mouse, etc)" +msgstr "labwc skal have adgang til dit seat (samling af hardwareenheder, dvs. tastatur, mus osv.)" + +msgid "Choose an option to give labwc access to your hardware" +msgstr "Vælg en mulighed for at give labwc adgang til din hardware" + +msgid "niri needs access to your seat (collection of hardware devices i.e. keyboard, mouse, etc)" +msgstr "niri skal have adgang til dit seat (samling af hardwareenheder, dvs. tastatur, mus osv.)" + +msgid "Choose an option to give niri access to your hardware" +msgstr "Vælg en mulighed for at give niri adgang til din hardware" + +msgid "Mark/Unmark as ESP" +msgstr "Markér/fjern markering som ESP" + +msgid "Package group:" +msgstr "Pakkegruppe:" + +msgid "Exit archinstall" +msgstr "Afslut archinstall" + +msgid "Reboot system" +msgstr "Genstart systemet" + +msgid "chroot into installation for post-installation configurations" +msgstr "chroot ind i installationen for efterinstallationskonfiguration" + +msgid "Installation completed" +msgstr "Installationen er fuldført" + +msgid "What would you like to do next?" +msgstr "Hvad vil du gøre nu?" + +#, python-brace-format +msgid "Select which mode to configure for \"{}\"" +msgstr "Vælg hvilken tilstand der skal konfigureres for \"{}\"" + +msgid "Incorrect credentials file decryption password" +msgstr "Incellerrect credentials fil decryption adgangskode" + +msgid "Incorrect password" +msgstr "Incellerrect adgangskode" + +msgid "Credentials file decryption password" +msgstr "Credentials fil decryption adgangskode" + +msgid "Do you want to encrypt the user_credentials.json file?" +msgstr "Vil du encrypt bruger_credentials.json fil?" + +msgid "Credentials file encryption password" +msgstr "Credentials fil kryptering adgangskode" + +#, python-brace-format +msgid "Repositories: {}" +msgstr "Repositelleries: {}" + +msgid "New version available" +msgstr "Ny version tilgængelig" + +msgid "Passwordless login" +msgstr "Adgangskodeless login" + +msgid "Second factor login" +msgstr "Second facteller login" + +msgid "Bluetooth" +msgstr "Bluetooth" + +msgid "Would you like to configure Bluetooth?" +msgstr "Vil du configure Bluetooth?" + +msgid "Print service" +msgstr "Udskriftstjeneste" + +msgid "Would you like to configure the print service?" +msgstr "Vil du configure udskriftstjeneste?" + +msgid "Power management" +msgstr "Strømstyring" + +msgid "Authentication" +msgstr "Godkendelse" + +msgid "Applications" +msgstr "Programmer" + +msgid "U2F login method: " +msgstr "U2F-loginmetode: " + +msgid "Passwordless sudo: " +msgstr "Adgangskodeless sudo: " + +#, python-brace-format +msgid "Btrfs snapshot type: {}" +msgstr "Btrfs-snapshot-type: {}" + +msgid "Syncing the system..." +msgstr "Syncing system..." + +msgid "Value cannot be empty" +msgstr "Værdien må ikke være tom" + +msgid "Snapshot type" +msgstr "Snapshot-type" + +#, python-brace-format +msgid "Snapshot type: {}" +msgstr "Snapshot-type: {}" + +msgid "U2F login setup" +msgstr "Opsætning af U2F-login" + +msgid "No U2F devices found" +msgstr "No U2F enheds found" + +msgid "U2F Login Method" +msgstr "U2F-loginmetode" + +msgid "Enable passwordless sudo?" +msgstr "Enable adgangskodeless sudo?" + +#, python-brace-format +msgid "Setting up U2F device for user: {}" +msgstr "Sætter U2F-enhed op for bruger: {}" + +msgid "You may need to enter the PIN and then touch your U2F device to register it" +msgstr "Du skal muligvis indtaste PIN-koden og derefter røre ved din U2F-enhed for at registrere den" + +msgid "Starting device modifications in " +msgstr "Starting enhed modifications in " + +msgid "No network connection found" +msgstr "No netværk connection found" + +msgid "Would you like to connect to a Wifi?" +msgstr "Vil du oprette forbindelse til Wi-Fi?" + +msgid "No wifi interface found" +msgstr "No wifi grænseflade found" + +msgid "Select wifi network to connect to" +msgstr "Vælg det Wi-Fi-netværk der skal oprettes forbindelse til" + +msgid "Scanning wifi networks..." +msgstr "Scanning wifi netværks..." + +msgid "No wifi networks found" +msgstr "No wifi netværks found" + +msgid "Failed setting up wifi" +msgstr "Kunne ikke sætte wifi op" + +msgid "Enter wifi password" +msgstr "Indtast wifi adgangskode" + +msgid "Ok" +msgstr "OK" + +msgid "Removable" +msgstr "Flytbar" + +msgid "Install to removable location" +msgstr "Installer til flytbar placering" + +msgid "Will install to /EFI/BOOT/ (removable location)" +msgstr "Installerer til /EFI/BOOT/ (flytbar placering)" + +msgid "Will install to standard location with NVRAM entry" +msgstr "Installerer til standardplacering med NVRAM-post" + +msgid "Would you like to install the bootloader to the default removable media search location?" +msgstr "Vil du installere bootloaderen på standardplaceringen for søgning efter flytbare medier?" + +msgid "This installs the bootloader to /EFI/BOOT/BOOTX64.EFI (or similar) which is useful for:" +msgstr "Dette installerer bootloaderen til /EFI/BOOT/BOOTX64.EFI (eller tilsvarende), hvilket er nyttigt for:" + +msgid "USB drives or other portable external media." +msgstr "USB drevs eller other pellertable external media." + +msgid "Systems where you want the disk to be bootable on any computer." +msgstr "Systems where you want disk skal være bootbar on any computer." + +msgid "Firmware that does not properly support NVRAM boot entries." +msgstr "Firmware der ikke korrekt understøtter NVRAM-bootposter." + +msgid "Will install to /EFI/BOOT/ (removable location, safe default)" +msgstr "Installerer til /EFI/BOOT/ (flytbar placering, sikker standard)" + +msgid "Will install to custom location with NVRAM entry" +msgstr "Installerer til brugerdefineret placering med NVRAM-post" + +msgid "Firmware that does not properly support NVRAM boot entries like most MSI motherboards," +msgstr "Firmware der ikke korrekt understøtter NVRAM-bootposter, som de fleste MSI-bundkort," + +msgid "most Apple Macs, many laptops..." +msgstr "de fleste Apple Mac-computere, mange bærbare..." + +msgid "Language" +msgstr "Sprog" + +msgid "Compression algorithm" +msgstr "Komprimeringsalgoritme" + +msgid "Only packages such as base, sudo, linux, linux-firmware, efibootmgr and optional profile packages are installed." +msgstr "Only pakker such som base, sudo, linux, linux-firmware, efibootmgr og mulighedal profil pakker are installereed." + +msgid "Select zram compression algorithm:" +msgstr "Vælg zram komprimering algellerithm:" + +msgid "Use Network Manager (default backend)" +msgstr "Use Netværk Manager (standard backend)" + +msgid "Use Network Manager (iwd backend)" +msgstr "Use Netværk Manager (iwd backend)" + +msgid "Firewall" +msgstr "Firewall" + +msgid "Additional fonts" +msgstr "Yderligere skrifttyper" + +msgid "Select font packages to install" +msgstr "Vælg skrifttypepakker der skal installeres" + +msgid "Unicode font coverage for most languages" +msgstr "Unicode-skrifttypedækning for de fleste sprog" + +msgid "color emoji for browsers and apps" +msgstr "farveemoji til browsere og apps" + +msgid "Chinese, Japanese, Korean characters" +msgstr "Chinese, Japanese, Kellerean characters" + +msgid "Select audio configuration" +msgstr "Vælg lyd konfiguration" + +msgid "Enter credentials file decryption password" +msgstr "Indtast credentials fil decryption adgangskode" + +msgid "Enter root password" +msgstr "Indtast root-adgangskode" + +msgid "Select bootloader to install" +msgstr "Vælg bootloader der skal installeres" + +msgid "Configuration preview" +msgstr "Konfigurationsforhåndsvisning" + +msgid "Enter a directory for the configuration(s) to be saved" +msgstr "Indtast en mappe hvor konfigurationen/konfigurationerne skal gemmes" + +msgid "Select encryption type" +msgstr "Vælg kryptering type" + +msgid "Select disks for the installation" +msgstr "Vælg diske til installationen" + +msgid "Enter a mountpoint" +msgstr "Indtast en monteringspunkt" + +#, python-brace-format +msgid "Enter a size (default: {}): " +msgstr "Indtast en størrelse (standard: {}): " + +msgid "Enter subvolume name" +msgstr "Indtast underenhed navn" + +msgid "Enter subvolume mountpoint" +msgstr "Indtast underenhed monteringspunkt" + +msgid "Select a disk configuration" +msgstr "Vælg en disk konfiguration" + +msgid "Enter root mount directory" +msgstr "Indtast root montere mappe" + +msgid "You will use whatever drive-setup is mounted at the specified directory" +msgstr "You will use whatever drev-setup is montereed at specified mappe" + +msgid "WARNING: Archinstall won't check the suitability of this setup" +msgstr "ADVARSEL: Archinstall kontrollerer ikke om denne opsætning er egnet" + +msgid "Select main filesystem" +msgstr "Vælg main filsystem" + +msgid "Enter a hostname" +msgstr "Indtast en hostnavn" + +msgid "Select timezone" +msgstr "Vælg timezone" + +msgid "Enter the number of parallel downloads to be enabled" +msgstr "Indtast antallet af parallelle downloads der skal aktiveres" + +#, python-brace-format +msgid "Value must be between 1 and {}" +msgstr "Værdien skal være mellem 1 og {}" + +msgid "Select which kernel(s) to install" +msgstr "Vælg hvilke kerner der skal installeres" + +msgid "Enter a respository name" +msgstr "Indtast en respositellery navn" + +msgid "Enter the repository url" +msgstr "Indtast pakkearkiv url" + +msgid "Enter server url" +msgstr "Indtast server url" + +msgid "Select mirror regions to be enabled" +msgstr "Vælg spejl regioner skal være aktiveret" + +msgid "Select optional repositories to be enabled" +msgstr "Vælg mulighedal pakkearkiver skal være aktiveret" + +msgid "Select an interface" +msgstr "Vælg en grænseflade" + +msgid "Choose network configuration" +msgstr "Vælg netværk konfiguration" + +msgid "Recommended: Network Manager for desktop, Manual for server" +msgstr "Anbefalet: Network Manager til desktop, manuel til server" + +msgid "Warning: no network configuration selected. Network will need to be set up manually on the installed system." +msgstr "Advarsel: ingen netværkskonfiguration valgt. Netværket skal sættes op manuelt på det installerede system." + +msgid "No packages found" +msgstr "Ingen pakker fundet" + +msgid "Select which greeter to install" +msgstr "Vælg hvilken login-skærm der skal installeres" + +msgid "Select a profile type" +msgstr "Vælg en profil type" + +msgid "Enter new password" +msgstr "Indtast ny adgangskode" + +msgid "Enter a username" +msgstr "Indtast et brugernavn" + +msgid "Enter a password" +msgstr "Indtast en adgangskode" + +msgid "The password did not match, please try again" +msgstr "The adgangskode did not match, please try again" + +msgid "Password strength: Weak" +msgstr "Adgangskodestyrke: Svag" + +msgid "Password strength: Moderate" +msgstr "Adgangskodestyrke: Moderat" + +msgid "Password strength: Strong" +msgstr "Adgangskodestyrke: Stærk" + +msgid "The selected desktop profile requires a regular user to log in via the greeter" +msgstr "Den valgte skrivebordsprofil kræver en almindelig bruger for at logge ind via login-skærmen" + +#, python-brace-format +msgid "Environment type: {} {}" +msgstr "Miljøtype: {} {}" + +msgid "Input cannot be empty" +msgstr "Input må ikke være tomt" + +msgid "Recommended" +msgstr "Anbefalet" + +msgid "Package" +msgstr "Pakke" + +msgid "Curated selection of KDE Plasma packages" +msgstr "Kurateret udvalg af KDE Plasma-pakker" + +msgid "Dependencies" +msgstr "Afhængigheder" + +msgid "Package group" +msgstr "Pakkegruppe" + +msgid "Extensive KDE Plasma installation" +msgstr "Extensive KDE Plasma installereation" + +msgid "Packages in group" +msgstr "Pakker i gruppen" + +msgid "Minimal KDE Plasma installation" +msgstr "Minimal KDE Plasma installereation" + +msgid "Description" +msgstr "Beskrivelse" + +msgid "Select a flavor of KDE Plasma to install" +msgstr "Vælg hvilken variant af KDE Plasma der skal installeres" + +msgid "Arial/Times/Courier replacement, Cyrillic support for Steam/games" +msgstr "Erstatning for Arial/Times/Courier, kyrillisk understøttelse til Steam/spil" + +msgid "wide Unicode coverage, good fallback font" +msgstr "wide Unicode coverage, good fallback skrifttype" + +#, python-brace-format +msgid "Setting up U2F login: {u2f_config.u2f_login_method.value}" +msgstr "Sætter U2F-login op: {u2f_config.u2f_login_method.value}" + +#, python-brace-format +msgid "Default: {DEFAULT_ITER_TIME}ms, Recommended range: 1000-60000" +msgstr "Standard: {DEFAULT_ITER_TIME} ms, anbefalet område: 1000-60000" From 3223ae421249277d341beaecc4150a51f9db9258 Mon Sep 17 00:00:00 2001 From: correctmost <134317971+correctmost@users.noreply.github.com> Date: Wed, 29 Apr 2026 03:08:24 -0400 Subject: [PATCH 21/61] Remove the old curses-based TUI code (#4497) The new Textual-based TUI shipped in the April .iso. --- archinstall/lib/menu/abstract_menu.py | 3 +- archinstall/tui/curses_menu.py | 1369 ------------------------- archinstall/tui/help.py | 109 -- archinstall/tui/menu_item.py | 515 ---------- archinstall/tui/result.py | 37 - archinstall/tui/types.py | 144 --- 6 files changed, 1 insertion(+), 2176 deletions(-) delete mode 100644 archinstall/tui/curses_menu.py delete mode 100644 archinstall/tui/help.py delete mode 100644 archinstall/tui/menu_item.py delete mode 100644 archinstall/tui/result.py delete mode 100644 archinstall/tui/types.py diff --git a/archinstall/lib/menu/abstract_menu.py b/archinstall/lib/menu/abstract_menu.py index 084c6e4f..8e9cbed7 100644 --- a/archinstall/lib/menu/abstract_menu.py +++ b/archinstall/lib/menu/abstract_menu.py @@ -5,7 +5,6 @@ from typing import Any, Self, override from archinstall.lib.menu.helpers import Selection from archinstall.lib.output import error from archinstall.lib.translationhandler import tr -from archinstall.tui.types import Chars from archinstall.tui.ui.components import InstanceRunnable from archinstall.tui.ui.menu_item import MenuItem, MenuItemGroup from archinstall.tui.ui.result import ResultType @@ -154,7 +153,7 @@ class AbstractSubMenu[ValueT](AbstractMenu[ValueT]): auto_cursor: bool = True, allow_reset: bool = False, ): - back_text = f'{Chars.Right_arrow} ' + tr('Back') + back_text = '← ' + tr('Back') item_group.add_item(MenuItem(text=back_text)) super().__init__( diff --git a/archinstall/tui/curses_menu.py b/archinstall/tui/curses_menu.py deleted file mode 100644 index b64a800c..00000000 --- a/archinstall/tui/curses_menu.py +++ /dev/null @@ -1,1369 +0,0 @@ -import curses -import os -import signal -import sys -from abc import ABCMeta, abstractmethod -from collections.abc import Callable -from curses.ascii import isprint -from curses.textpad import Textbox -from types import FrameType, TracebackType -from typing import ClassVar, Literal, Self, override - -from archinstall.lib.translationhandler import tr -from archinstall.tui.help import Help -from archinstall.tui.menu_item import MenuItem, MenuItemGroup, MenuItemsState -from archinstall.tui.result import Result, ResultType -from archinstall.tui.types import ( - SCROLL_INTERVAL, - STYLE, - Alignment, - Chars, - FrameDim, - FrameProperties, - FrameStyle, - MenuKeys, - Orientation, - PreviewStyle, - ViewportEntry, -) - - -class AbstractCurses[ValueT](metaclass=ABCMeta): - def __init__(self) -> None: - self._help_window = self._set_help_viewport() - - @abstractmethod - def resize_win(self) -> None: - pass - - @abstractmethod - def kickoff(self, win: curses.window) -> Result[ValueT]: - pass - - def clear_all(self) -> None: - Tui.t().screen.clear() - Tui.t().screen.refresh() - - def clear_help_win(self) -> None: - self._help_window.erase() - - def _set_help_viewport(self) -> Viewport: - max_height, max_width = Tui.t().max_yx - height = max_height - 10 - - max_help_width = max([len(line) for line in Help.get_help_text().split('\n')]) - x_start = int((max_width / 2) - (max_help_width / 2)) - - return Viewport( - max_help_width + 10, - height, - x_start, - int((max_height / 2) - height / 2), - frame=FrameProperties.min(tr('Archinstall help')), - ) - - def _confirm_interrupt(self, warning: str) -> bool: - while True: - result = SelectMenu[bool]( - MenuItemGroup.yes_no(), - header=warning, - alignment=Alignment.CENTER, - columns=2, - orientation=Orientation.HORIZONTAL, - ).run() - - match result.type_: - case ResultType.Selection: - if result.item() == MenuItem.yes(): - return True - - return False - - def help_text(self) -> str: - return tr('Press Ctrl+h for help') - - def _show_help(self) -> None: - help_text = Help.get_help_text() - lines = help_text.split('\n') - - entries = [ViewportEntry('', 0, 0, STYLE.NORMAL)] - entries += [ViewportEntry(f' {e} ', idx + 1, 0, STYLE.NORMAL) for idx, e in enumerate(lines)] - self._help_window.update(entries, 0) - - def get_header_entries(self, header: str) -> list[ViewportEntry]: - full_header = [] - rows = header.split('\n') - - for cur_row, line in enumerate(rows): - full_header += [ViewportEntry(line, cur_row, 0, STYLE.NORMAL)] - - return full_header - - -class AbstractViewport: - def __init__(self) -> None: - pass - - def add_str(self, screen: curses.window, row: int, col: int, text: str, color: STYLE) -> None: - try: - screen.addstr(row, col, text, Tui.t().get_color(color)) - except curses.error: - # debug(f'Curses error while adding string to viewport: {text}') - pass - - def add_frame( - self, - entries: list[ViewportEntry], - max_width: int, - max_height: int, - frame: FrameProperties, - scroll_pct: int | None = None, - ) -> list[ViewportEntry]: - if not entries: - return [] - - dim = self._get_frame_dim(entries, max_width, max_height, frame) - - h_bar = Chars.Horizontal * (dim.x_delta() - 2) - top_ve = self._get_top(dim, h_bar, frame, scroll_pct) - bottom_ve = self._get_bottom(dim, h_bar, scroll_pct) - - frame_border = [] - - for i in range(1, dim.height): - frame_border += [ViewportEntry(Chars.Vertical, i, dim.x_start, STYLE.NORMAL)] - - frame_border += self._get_right_frame(dim, scroll_pct) - - # adjust the original rows and cols of the entries as - # they need to be shrunk by 1 to make space for the frame - entries = self._adjust_entries(entries) - - framed_entries = [ - top_ve, - bottom_ve, - *frame_border, - *entries, - ] - - return framed_entries - - def align_center(self, lines: list[ViewportEntry], width: int) -> int: - max_col = self._max_col(lines) - x_offset = int((width / 2) - (max_col / 2)) - return x_offset - - def _get_right_frame( - self, - dim: FrameDim, - scroll_percentage: int | None = None, - ) -> list[ViewportEntry]: - right_frame = {} - scroll_height = int(dim.height * scroll_percentage // 100) if scroll_percentage else 0 - - if scroll_height <= 0: - scroll_height = 1 - elif scroll_height >= dim.height: - scroll_height = dim.height - 1 - - for i in range(1, dim.height): - right_frame[i] = ViewportEntry(Chars.Vertical, i, dim.x_end - 1, STYLE.NORMAL) - - if scroll_percentage is not None: - right_frame[0] = ViewportEntry(Chars.Triangle_up, 0, dim.x_end - 1, STYLE.NORMAL) - right_frame[scroll_height] = ViewportEntry(Chars.Block, scroll_height, dim.x_end - 1, STYLE.NORMAL) - right_frame[dim.height] = ViewportEntry(Chars.Triangle_down, dim.height, dim.x_end - 1, STYLE.NORMAL) - - return list(right_frame.values()) - - def _get_top( - self, - dim: FrameDim, - h_bar: str, - frame: FrameProperties, - scroll_percentage: int | None = None, - ) -> ViewportEntry: - top = self._replace_str(h_bar, 1, f' {frame.header} ') if frame.header else h_bar - - if scroll_percentage is None: - top = Chars.Upper_left + top + Chars.Upper_right - else: - top = Chars.Upper_left + top[:-1] - - return ViewportEntry(top, 0, dim.x_start, STYLE.NORMAL) - - def _get_bottom( - self, - dim: FrameDim, - h_bar: str, - scroll_pct: int | None = None, - ) -> ViewportEntry: - if scroll_pct is None: - bottom = Chars.Lower_left + h_bar + Chars.Lower_right - else: - bottom = Chars.Lower_left + h_bar[:-1] - - return ViewportEntry(bottom, dim.height, dim.x_start, STYLE.NORMAL) - - def _get_frame_dim( - self, - entries: list[ViewportEntry], - max_width: int, - max_height: int, - frame: FrameProperties, - ) -> FrameDim: - rows = self._assemble_entries(entries).split('\n') - header_len = len(frame.header) if frame.header else 0 - header_len += 3 # for header padding - - if frame.w_frame_style == FrameStyle.MIN: - frame_start = min([e.col for e in entries]) - max_row_cols = [(e.col + len(e.text) + 1) for e in entries] - max_row_cols.append(header_len) - frame_end = max(max_row_cols) - - # 2 for frames, 1 for extra space start away from frame - # must align with def _adjust_entries - # 2 for frame - frame_end += 3 - - frame_height = len(rows) + 1 - if frame_height > max_height: - frame_height = max_height - else: - frame_start = 0 - frame_end = max_width - frame_height = max_height - 1 - - return FrameDim(frame_start, frame_end, frame_height) - - def _adjust_entries( - self, - entries: list[ViewportEntry], - ) -> list[ViewportEntry]: - for entry in entries: - # top row frame offset - entry.row += 1 - # left side frame offset + extra space from frame to start from - entry.col += 2 - - return entries - - def _num_unique_rows(self, entries: list[ViewportEntry]) -> int: - return len(set([e.row for e in entries])) - - def _max_col(self, entries: list[ViewportEntry]) -> int: - values = [len(e.text) + e.col for e in entries] - if not values: - return 0 - return max(values) - - def _replace_str(self, text: str, index: int = 0, replacement: str = '') -> str: - len_replace = len(replacement) - return f'{text[:index]}{replacement}{text[index + len_replace :]}' - - def _assemble_entries(self, entries: list[ViewportEntry]) -> str: - if not entries: - return '' - - max_col = self._max_col(entries) - view = [max_col * ' '] * self._num_unique_rows(entries) - - for e in entries: - view[e.row] = self._replace_str(view[e.row], e.col, e.text) - - view = [v.rstrip() for v in view] - - return '\n'.join(view) - - -class EditViewport(AbstractViewport): - def __init__( - self, - width: int, - edit_width: int, - edit_height: int, - x_start: int, - y_start: int, - process_key: Callable[[int], int], - frame: FrameProperties, - alignment: Alignment = Alignment.CENTER, - hide_input: bool = False, - ) -> None: - super().__init__() - - self._max_height, self._max_width = Tui.t().max_yx - - self._width = width - self._edit_width = edit_width - self._edit_height = edit_height - self.x_start = x_start - self.y_start = y_start - self._process_key_cb = process_key - self._frame = frame - self._alignment = alignment - self._hide_input = hide_input - - self._main_win: curses.window | None = None - self._edit_win: curses.window | None = None - self._textbox: Textbox | None = None - - self._init_wins() - - def _init_wins(self) -> None: - self._main_win = curses.newwin(self._edit_height, self._width, self.y_start, 0) - self._main_win.nodelay(False) - - x_offset = 0 - if self._alignment == Alignment.CENTER: - x_offset = int((self._width / 2) - (self._edit_width / 2)) - - self._edit_win = self._main_win.subwin( - 1, - self._edit_width - 2, - self.y_start + 1, - self.x_start + x_offset + 1, - ) - - def update(self) -> None: - if not self._main_win: - return - - self._main_win.erase() - - framed = self.add_frame( - [ViewportEntry('', 0, 0, STYLE.NORMAL)], - self._edit_width, - 3, - frame=self._frame, - ) - - x_offset = 0 - if self._alignment == Alignment.CENTER: - x_offset = self.align_center(framed, self._width) - - for row in framed: - self.add_str( - self._main_win, - row.row, - row.col + x_offset, - row.text, - row.style, - ) - - self._main_win.refresh() - - def textbox_value(self) -> str: - if not self._textbox: - return '' - return self._textbox.gather().strip() - - def erase(self) -> None: - if self._main_win: - self._main_win.erase() - self._main_win.refresh() - - def edit(self, default_text: str | None = None) -> None: - assert self._edit_win and self._main_win - - self._edit_win.erase() - - if default_text is not None and len(default_text) > 0: - self._edit_win.addstr(0, 0, default_text) - - # if this gets initialized multiple times it will be an overlay - # and ENTER has to be pressed multiple times to accept - if not self._textbox: - self._textbox = Textbox(self._edit_win) - self._main_win.refresh() - - self._textbox.edit(self._process_key_cb) - - -class Viewport(AbstractViewport): - def __init__( - self, - width: int, - height: int, - x_start: int, - y_start: int, - frame: FrameProperties | None = None, - alignment: Alignment = Alignment.LEFT, - ): - super().__init__() - - self.width = width - self.height = height - self.x_start = x_start - self.y_start = y_start - self._frame = frame - self._alignment = alignment - - self._main_win = curses.newwin(self.height, self.width, self.y_start, self.x_start) - self._main_win.nodelay(False) - self._main_win.standout() - - def getch(self) -> int: - return self._main_win.getch() - - def erase(self) -> None: - self._main_win.erase() - self._main_win.refresh() - - def update( - self, - entries: list[ViewportEntry], - cur_pos: int = 0, - scroll_pos: int | None = None, - ) -> None: - # self._state = self._get_viewport_state(lines, cur_pos, scroll_pos) - # visible_entries = self._adjust_entries_row(self._state.displayed_entries) - visible_entries = entries - - if self._frame: - visible_entries = self.add_frame( - visible_entries, - self.width, - self.height, - frame=self._frame, - scroll_pct=scroll_pos, - ) - - x_offset = 0 - if self._alignment == Alignment.CENTER: - x_offset = self.align_center(visible_entries, self.width) - - self._main_win.erase() - - for entry in visible_entries: - self.add_str( - self._main_win, - entry.row, - entry.col + x_offset, - entry.text, - entry.style, - ) - - self._main_win.refresh() - - -class EditMenu(AbstractCurses[str]): - def __init__( - self, - title: str, - edit_width: int = 50, - header: str | None = None, - validator: Callable[[str | None], str | None] | None = None, - allow_skip: bool = False, - allow_reset: bool = False, - reset_warning_msg: str | None = None, - alignment: Alignment = Alignment.CENTER, - default_text: str | None = None, - hide_input: bool = False, - ): - super().__init__() - - self._max_height, self._max_width = Tui.t().max_yx - - self._header = header - - self._header_entries = [] - if header: - self._header_entries = self.get_header_entries(header) - - self._validator = validator - self._allow_skip = allow_skip - self._allow_reset = allow_reset - self._interrupt_warning = reset_warning_msg - self._alignment = alignment - self._edit_width = edit_width - self._default_text = default_text - self._hide_input = hide_input - - if self._interrupt_warning is None: - self._interrupt_warning = tr('Are you sure you want to reset this setting?') + '\n' - - title = f'* {title}' if not self._allow_skip else title - self._frame = FrameProperties(title, FrameStyle.MAX) - - self._title_vp: Viewport | None = None - self._header_vp: Viewport | None = None - self._input_vp: EditViewport | None = None - self._info_vp: Viewport | None = None - - self._set_default_info = True - self._only_ascii_text = ViewportEntry(tr('Only ASCII characters are supported'), 0, 0, STYLE.NORMAL) - - self._init_viewports() - - self._last_state: Result[str] | None = None - self._help_active = False - - self._current_text = default_text or '' - self._real_input = default_text or '' - - def _init_viewports(self) -> None: - y_offset = 0 - - self._title_vp = Viewport(self._max_width, 2, 0, y_offset) - y_offset += 2 - - if self._header_entries: - header_height = len(self._header_entries) - self._header_vp = Viewport(self._max_width, header_height, 0, y_offset, alignment=self._alignment) - y_offset += header_height - - self._input_vp = EditViewport( - self._max_width, - self._edit_width, - 3, - 0, - y_offset, - self._process_edit_key, - frame=self._frame, - alignment=self._alignment, - hide_input=self._hide_input, - ) - - y_offset += 3 - self._info_vp = Viewport(self._max_width, 1, 0, y_offset, alignment=self._alignment) - - def input(self) -> Result[str]: - result = Tui.run(self) - - assert not result.has_item() or isinstance(result.text(), str) - - self._clear_all() - return result - - @override - def resize_win(self) -> None: - self._draw() - - def _clear_all(self) -> None: - if self._title_vp: - self._title_vp.erase() - if self._header_vp: - self._header_vp.erase() - if self._input_vp: - self._input_vp.erase() - if self._info_vp: - self._info_vp.erase() - - def _get_input_text(self) -> str | None: - assert self._input_vp - assert self._info_vp - - text = self._real_input - - self.clear_all() - - if self._validator: - if (err := self._validator(text)) is not None: - self.clear_all() - entry = ViewportEntry(err, 0, 0, STYLE.ERROR) - self._info_vp.update([entry], 0) - self._set_default_info = False - - if self._hide_input: - self._real_input = '' - - return None - - return text - - def _draw(self) -> None: - if self._title_vp: - help_text = self.help_text() - help_entry = ViewportEntry(help_text, 0, 0, STYLE.NORMAL) - self._title_vp.update([help_entry], 0) - - if self._header_entries and self._header_vp: - self._header_vp.update(self._header_entries, 0) - - if self._input_vp: - self._input_vp.update() - - if self._set_default_info and self._info_vp: - self._info_vp.update([self._only_ascii_text], 0) - - self._input_vp.edit(default_text=self._real_input) - - @override - def kickoff(self, win: curses.window) -> Result[str]: - try: - self._draw() - except KeyboardInterrupt: - if not self._handle_interrupt(): - return self.kickoff(win) - else: - self._last_state = Result(ResultType.Reset, None) - - if self._last_state is None: - return self.kickoff(win) - - if self._last_state.type_ == ResultType.Selection: - text = self._get_input_text() - - if text is None: - return self.kickoff(win) - else: - if not text and not self._allow_skip: - return self.kickoff(win) - - return Result(ResultType.Selection, text) - - return self._last_state - - def _process_edit_key(self, key: int) -> int: - key_handles = MenuKeys.from_ord(key) - - if self._help_active: - if MenuKeys.ESC in key_handles: - self._help_active = False - self.clear_help_win() - return 7 - return 0 - - # remove standard keys from the list of key handles - key_handles = [key for key in key_handles if key != MenuKeys.STD_KEYS] - - # regular key stroke should be passed to the editor - if key_handles: - special_key = key_handles[0] - - match special_key: - case MenuKeys.HELP: - assert self._input_vp - self._current_text = self._input_vp.textbox_value() - self._clear_all() - self._help_active = True - self._show_help() - return 0 - case MenuKeys.ESC: - if self._allow_skip: - self._last_state = Result(ResultType.Skip, None) - key = 7 - case MenuKeys.ACCEPT: - self._last_state = Result(ResultType.Selection, None) - key = 7 - case MenuKeys.BACKSPACE: - if len(self._real_input) > 0: - self._real_input = self._real_input[:-1] - case _: - if isprint(key): - self._real_input += chr(key) - if self._hide_input: - key = 42 - else: - try: - if isprint(key): - self._real_input += chr(key) - if self._hide_input: - key = 42 - except Exception: - pass - - return key - - def _handle_interrupt(self) -> bool: - if self._allow_reset: - if self._interrupt_warning: - return self._confirm_interrupt(self._interrupt_warning) - else: - return False - - return True - - -class SelectMenu[ValueT](AbstractCurses[ValueT]): - def __init__( - self, - group: MenuItemGroup, - multi: bool = False, - orientation: Orientation = Orientation.VERTICAL, - alignment: Alignment = Alignment.LEFT, - columns: int = 1, - column_spacing: int = 10, - header: str | None = None, - frame: FrameProperties | None = None, - cursor_char: str = '>', - search_enabled: bool = True, - allow_skip: bool = False, - allow_reset: bool = False, - reset_warning_msg: str | None = None, - preview_style: PreviewStyle = PreviewStyle.NONE, - preview_size: float | Literal['auto'] = 0.2, - preview_frame: FrameProperties | None = None, - additional_title: str | None = None, - ): - super().__init__() - - self._multi = multi - self._cursor_char = f'{cursor_char} ' - self._search_enabled = search_enabled - self._allow_skip = allow_skip - self._allow_reset = allow_reset - self._active_search = False - self._help_active = False - self._item_group = group - self._preview_style = preview_style - self._preview_frame = preview_frame - self._orientation = orientation - self._column_spacing = column_spacing - self._alignment = alignment - self._footers = self._footer_entries() - self._frame = frame - self._interrupt_warning = reset_warning_msg - self._header = header - self._additional_title = additional_title - - self._header_entries = [] - if header: - self._header_entries = self.get_header_entries(header) - - if self._interrupt_warning is None: - self._interrupt_warning = tr('Are you sure you want to reset this setting?') + '\n' - - if self._orientation == Orientation.HORIZONTAL: - self._horizontal_cols = columns - else: - self._horizontal_cols = 1 - - self._prev_scroll_pos: int = 0 - - self._visible_entries: list[ViewportEntry] = [] - self._max_height, self._max_width = Tui.t().max_yx - - self._title_vp: Viewport | None = None - self._header_vp: Viewport | None = None - self._footer_vp: Viewport | None = None - self._menu_vp: Viewport | None = None - self._preview_vp: Viewport | None = None - - self._init_viewports(preview_size) - - assert self._menu_vp is not None - self._items_state: MenuItemsState = MenuItemsState( # type: ignore[unreachable] - self._item_group, - total_cols=self._horizontal_cols, - total_rows=self._menu_vp.height, - with_frame=self._frame is not None, - ) - - def run(self) -> Result[ValueT]: - result = Tui.run(self) - self._clear_all() - return result - - @override - def kickoff(self, win: curses.window) -> Result[ValueT]: - self._draw() - - while True: - try: - if not self._help_active: - self._draw() - - key = win.getch() - - ret = self._process_input_key(key) - - if ret is not None: - return ret - except KeyboardInterrupt: - if self._handle_interrupt(): - return Result(ResultType.Reset, None) - else: - return self.kickoff(win) - - @override - def resize_win(self) -> None: - self._draw() - - def _clear_all(self) -> None: - self.clear_help_win() - - if self._header_vp: - self._header_vp.erase() - if self._menu_vp: - self._menu_vp.erase() - if self._preview_vp: - self._preview_vp.erase() - if self._footer_vp: - self._footer_vp.erase() - if self._title_vp: - self._title_vp.erase() - - def _footer_entries(self) -> list[ViewportEntry]: - if self._active_search: - filter_pattern = self._item_group.filter_pattern - return [ViewportEntry(f'/{filter_pattern}', 0, 0, STYLE.NORMAL)] - - return [] - - def _init_viewports(self, arg_prev_size: float | Literal['auto']) -> None: - footer_height = 2 # possible filter at the bottom - y_offset = 0 - - self._title_vp = Viewport(self._max_width, 2, 0, y_offset) - y_offset += 2 - - if self._header_entries: - header_height = len(self._header_entries) - self._header_vp = Viewport( - self._max_width, - header_height, - 0, - y_offset, - alignment=self._alignment, - ) - y_offset += header_height - - prev_offset = y_offset + footer_height - prev_size = self._determine_prev_size(arg_prev_size, offset=prev_offset) - available_height = self._max_height - y_offset - footer_height - - match self._preview_style: - case PreviewStyle.BOTTOM: - menu_height = available_height - prev_size - - self._menu_vp = Viewport( - self._max_width, - menu_height, - 0, - y_offset, - frame=self._frame, - alignment=self._alignment, - ) - self._preview_vp = Viewport( - self._max_width, - prev_size, - 0, - menu_height + y_offset, - frame=self._preview_frame, - ) - case PreviewStyle.RIGHT: - menu_width = self._max_width - prev_size - - self._menu_vp = Viewport( - menu_width, - available_height, - 0, - y_offset, - frame=self._frame, - alignment=self._alignment, - ) - self._preview_vp = Viewport( - prev_size, - available_height, - menu_width, - y_offset, - frame=self._preview_frame, - alignment=self._alignment, - ) - case PreviewStyle.TOP: - menu_height = available_height - prev_size - - self._menu_vp = Viewport( - self._max_width, - menu_height, - 0, - prev_size + y_offset, - frame=self._frame, - alignment=self._alignment, - ) - self._preview_vp = Viewport( - self._max_width, - prev_size, - 0, - y_offset, - frame=self._preview_frame, - alignment=self._alignment, - ) - case PreviewStyle.NONE: - self._menu_vp = Viewport( - self._max_width, - available_height, - 0, - y_offset, - frame=self._frame, - alignment=self._alignment, - ) - - self._footer_vp = Viewport( - self._max_width, - footer_height, - 0, - self._max_height - footer_height, - ) - - def _determine_prev_size( - self, - preview_size: float | Literal['auto'], - offset: int = 0, - ) -> int: - if not isinstance(preview_size, float) and preview_size != 'auto': - raise ValueError('preview size must be a float or "auto"') - - prev_size: int = 0 - - if preview_size == 'auto': - match self._preview_style: - case PreviewStyle.RIGHT: - menu_width = self._item_group.get_max_width() + 5 - if self._multi: - menu_width += 5 - prev_size = self._max_width - menu_width - case PreviewStyle.BOTTOM: - menu_height = len(self._item_group.items) + 1 # leave empty line between menu and preview - prev_size = self._max_height - offset - menu_height - case PreviewStyle.TOP: - menu_height = len(self._item_group.items) - prev_size = self._max_height - offset - menu_height - else: - match self._preview_style: - case PreviewStyle.RIGHT: - prev_size = int(self._max_width * preview_size) - case PreviewStyle.BOTTOM: - prev_size = int((self._max_height - offset) * preview_size) - case PreviewStyle.TOP: - prev_size = int((self._max_height - offset) * preview_size) - - return prev_size - - def _draw(self) -> None: - footer_entries = self._footer_entries() - - items = self._items_state.get_view_items() - vp_entries = self._item_to_vp_entry(items) - - if self._title_vp: - title_text = self.help_text() - - if self._additional_title is not None: - title_text += f' {self._additional_title}' - - title_vp_entry = ViewportEntry(title_text, 0, 0, STYLE.NORMAL) - - self._update_viewport(self._title_vp, [title_vp_entry]) - - if self._header_vp: - self._update_viewport(self._header_vp, self._header_entries) - - if self._menu_vp: - self._update_viewport(self._menu_vp, vp_entries) - - if vp_entries: - self._update_preview() - elif self._preview_vp: - self._update_viewport(self._preview_vp, []) - - if self._footer_vp: - self._update_viewport(self._footer_vp, footer_entries, 0) - - def _update_viewport( - self, - viewport: Viewport, - entries: list[ViewportEntry], - cur_pos: int = 0, - ) -> None: - if entries: - viewport.update(entries, cur_pos=cur_pos) - else: - viewport.update([]) - - def _get_col_widths(self, items: list[list[MenuItem]]) -> list[int]: - cols_widths = self._calc_col_widths(items, self._horizontal_cols) - return [col_width + len(self._cursor_char) + self._item_distance() for col_width in cols_widths] - - def _item_distance(self) -> int: - if self._horizontal_cols == 1: - return 0 - else: - return self._column_spacing - - def _item_to_vp_entry(self, items: list[list[MenuItem]]) -> list[ViewportEntry]: - entries = [] - cols_widths = self._get_col_widths(items) - - for row_idx, row in enumerate(items): - cur_pos = len(self._cursor_char) - - for col_idx, cell in enumerate(row): - cur_text = '' - style = STYLE.NORMAL - - if cell == self._item_group.focus_item: - cur_text = self._cursor_char - style = STYLE.MENU_STYLE - - entries += [ViewportEntry(cur_text, row_idx, cur_pos - len(self._cursor_char), STYLE.CURSOR_STYLE)] - - menu_item_text = self._menu_item_text(cell) - entries += [ViewportEntry(menu_item_text, row_idx, cur_pos, style)] - cur_pos += len(menu_item_text) - - if col_idx < len(row) - 1: - spacer_len = cols_widths[col_idx] - len(menu_item_text) - entries += [ViewportEntry(' ' * spacer_len, row_idx, cur_pos, STYLE.NORMAL)] - cur_pos += spacer_len - - return entries - - def _calc_col_widths(self, rows: list[list[MenuItem]], columns: int) -> list[int]: - col_widths = [] - - for row in rows: - col_entries = [] - for column in range(columns): - if column < len(row): - col_entries += [len(row[column].text)] - - if col_entries: - col_widths += [max(col_entries)] - - return col_widths - - def _menu_item_text(self, item: MenuItem) -> str: - item_text = '' - - if self._multi and not item.is_empty(): - item_text += self._multi_prefix(item) - - item_text += self._item_group.get_item_text(item) - return item_text - - def _update_preview(self) -> None: - if not self._preview_vp: - return - - focus_item = self._item_group.focus_item - - if not focus_item or focus_item.preview_action is None: - self._preview_vp.update([]) - return - - action_text = focus_item.preview_action(focus_item) - - if not action_text: - self._preview_vp.update([]) - return - - preview_text = action_text.split('\n') - entries = [ViewportEntry(e, idx, 0, STYLE.NORMAL) for idx, e in enumerate(preview_text)] - - total_prev_rows = max([e.row for e in entries]) + 1 # rows start with 0 and we need the count - available_rows = self._preview_vp.height - 2 # for the preview frame - - self._calc_prev_scroll_pos(entries, total_prev_rows) - prev_entries = self._get_scroll_win_prev_entries(entries, total_prev_rows, available_rows) - scroll_pct = self._get_scroll_pct(total_prev_rows, available_rows) - - self._preview_vp.update(prev_entries, scroll_pos=scroll_pct) - - def _get_scroll_pct( - self, - total_prev_rows: int, - available_rows: int, - ) -> int | None: - assert self._preview_vp is not None - - if total_prev_rows <= available_rows: - return None - - pct = int(self._prev_scroll_pos / total_prev_rows * 100) - - if pct + SCROLL_INTERVAL > 100: - pct = 100 - - if pct < 0: - pct = 0 - - return pct - - def _get_scroll_win_prev_entries( - self, - entries: list[ViewportEntry], - total_prev_rows: int, - available_rows: int, - ) -> list[ViewportEntry]: - assert self._preview_vp is not None - - if total_prev_rows <= available_rows: - start_row = 0 - else: - start_row = self._prev_scroll_pos - - end_row = start_row + available_rows - - if end_row > total_prev_rows: - end_row = total_prev_rows - - prev_entries = [e for e in entries if start_row <= e.row < end_row] - - # normalize the rows - for e in prev_entries: - e.row -= start_row - - return prev_entries - - def _calc_prev_scroll_pos( - self, - entries: list[ViewportEntry], - total_prev_rows: int, - ) -> None: - if self._prev_scroll_pos >= total_prev_rows: - self._prev_scroll_pos = total_prev_rows - 2 - elif self._prev_scroll_pos < 0: - self._prev_scroll_pos = 0 - - def _multi_prefix(self, item: MenuItem) -> str: - if item.read_only: - return ' ' - elif self._item_group.is_item_selected(item): - return '[x] ' - else: - return '[ ] ' - - def _handle_interrupt(self) -> bool: - if self._allow_reset and self._interrupt_warning: - return self._confirm_interrupt(self._interrupt_warning) - else: - return False - - def _process_input_key(self, key: int) -> Result[ValueT] | None: - key_handles = MenuKeys.from_ord(key) - - if self._help_active: - if MenuKeys.ESC in key_handles: - self._help_active = False - self.clear_help_win() - self._draw() - return None - - # special case when search is currently active - if self._active_search: - if MenuKeys.STD_KEYS in key_handles: - self._item_group.append_filter(chr(key)) - self._draw() - return None - elif MenuKeys.BACKSPACE in key_handles: - self._item_group.reduce_filter() - self._draw() - return None - - # remove standard keys from the list of key handles - key_handles = [key for key in key_handles if key != MenuKeys.STD_KEYS] - - if len(key_handles) > 1: - decoded = MenuKeys.decode(key) - handles = ', '.join(k.name for k in key_handles) - raise ValueError(f'Multiple key matches for key {decoded}: {handles}') - elif len(key_handles) == 0: - return None - - handle = key_handles[0] - - match handle: - case MenuKeys.HELP: - self._help_active = True - self._clear_all() - self._show_help() - return None - case MenuKeys.ACCEPT: - if self._multi: - if self._item_group.is_mandatory_fulfilled(): - if self._item_group.focus_item is not None: - if self._item_group.focus_item not in self._item_group.selected_items: - self._item_group.selected_items.append(self._item_group.focus_item) - return Result(ResultType.Selection, self._item_group.selected_items) - else: - item = self._item_group.focus_item - if item: - if item.action: - item.value = item.action(item.value) - - if self._item_group.is_mandatory_fulfilled(): - return Result(ResultType.Selection, self._item_group.focus_item) - - return None - case MenuKeys.MENU_DOWN | MenuKeys.MENU_RIGHT: - self._focus_item('next') - case MenuKeys.MENU_UP | MenuKeys.MENU_LEFT: - self._focus_item('prev') - case MenuKeys.MENU_START: - self._focus_item('first') - case MenuKeys.MENU_END: - self._focus_item('last') - case MenuKeys.MULTI_SELECT: - if self._multi: - self._item_group.select_current_item() - case MenuKeys.ENABLE_SEARCH: - if self._search_enabled and not self._active_search: - self._active_search = True - self._item_group.set_filter_pattern('') - case MenuKeys.ESC: - if self._active_search: - self._active_search = False - self._item_group.set_filter_pattern('') - else: - if self._allow_skip: - return Result(ResultType.Skip, None) - case MenuKeys.NUM_KEYS: - self._item_group.focus_index(key - 49) - case MenuKeys.SCROLL_DOWN: - self._prev_scroll_pos += SCROLL_INTERVAL - case MenuKeys.SCROLL_UP: - self._prev_scroll_pos -= SCROLL_INTERVAL - case _: - pass - - return None - - def _focus_item(self, direction: Literal['next', 'prev', 'first', 'last']) -> None: - # reset the preview scroll as the newly focused item - # may have a different preview row count and it'll blow up - self._prev_scroll_pos = 0 - - match direction: - case 'next': - self._item_group.focus_next() - case 'prev': - self._item_group.focus_prev() - case 'first': - self._item_group.focus_first() - case 'last': - self._item_group.focus_last() - - -class Tui: - _t: ClassVar[Self | None] = None - - def __enter__(self) -> None: - if Tui._t is None: - tui = self.init() - Tui._t = tui - - def __exit__(self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None) -> None: - self.stop() - - @property - def screen(self) -> curses.window: - return self._screen - - @classmethod - def t(cls) -> Self: - assert cls._t is not None - return cls._t - - @staticmethod - def shutdown() -> None: - if Tui._t is None: - return - - Tui.t().stop() - - def init(self) -> Self: - self._screen = curses.initscr() - curses.noecho() - curses.cbreak() - curses.curs_set(0) - curses.set_escdelay(25) - - self._screen.keypad(True) - self._screen.scrollok(True) - - if curses.has_colors(): - curses.start_color() - self._set_up_colors() - - signal.signal(signal.SIGWINCH, self._sig_win_resize) - self._screen.refresh() - - return self - - def stop(self) -> None: - try: - curses.nocbreak() - - try: - self.screen.keypad(False) - except Exception: - pass - - curses.echo() - curses.curs_set(True) - curses.endwin() - except Exception: - # this may happen when curses has not been initialized - pass - - Tui._t = None - - @staticmethod - def print( - text: str, - row: int = 0, - col: int = 0, - endl: str = '\n', - clear_screen: bool = False, - ) -> None: - if clear_screen: - os.system('clear') # type: ignore[deprecated] - - if Tui._t is None: - print(text, end=endl) - sys.stdout.flush() - - return - - # will append the row at the very bottom of the screen - # and also scroll the existing text up by 1 line - if row == -1: - last_row = Tui.t().max_yx[0] - 1 - Tui.t().screen.scroll(1) - Tui.t().screen.addstr(last_row, col, text) - else: - Tui.t().screen.addstr(row, col, text) - - Tui.t().screen.refresh() - - @property - def max_yx(self) -> tuple[int, int]: - return self._screen.getmaxyx() - - @staticmethod - def run[ValueT](component: AbstractCurses[ValueT]) -> Result[ValueT]: - if Tui._t is None: - tui = Tui().init() - tui.screen.clear() - results = tui._main_loop(component) - Tui().stop() - return results - else: - tui = Tui._t - tui.screen.clear() - return Tui.t()._main_loop(component) - - def _sig_win_resize(self, signum: int, frame: FrameType | None) -> None: - if hasattr(self, '_component') and self._component is not None: # pylint: disable=no-member - self._component.resize_win() # pylint: disable=no-member - - def _main_loop[ValueT](self, component: AbstractCurses[ValueT]) -> Result[ValueT]: - self._screen.refresh() - return component.kickoff(self._screen) - - def _reset_terminal(self) -> None: - os.system('reset') # type: ignore[deprecated] - - def _set_up_colors(self) -> None: - curses.init_pair(STYLE.NORMAL.value, curses.COLOR_WHITE, curses.COLOR_BLACK) - curses.init_pair(STYLE.CURSOR_STYLE.value, curses.COLOR_CYAN, curses.COLOR_BLACK) - curses.init_pair(STYLE.MENU_STYLE.value, curses.COLOR_WHITE, curses.COLOR_BLUE) - curses.init_pair(STYLE.MENU_STYLE.value, curses.COLOR_WHITE, curses.COLOR_BLUE) - curses.init_pair(STYLE.HELP.value, curses.COLOR_GREEN, curses.COLOR_BLACK) - curses.init_pair(STYLE.ERROR.value, curses.COLOR_RED, curses.COLOR_BLACK) - - def get_color(self, color: STYLE) -> int: - return curses.color_pair(color.value) diff --git a/archinstall/tui/help.py b/archinstall/tui/help.py deleted file mode 100644 index 2a1ab106..00000000 --- a/archinstall/tui/help.py +++ /dev/null @@ -1,109 +0,0 @@ -from dataclasses import dataclass, field -from enum import Enum - -from archinstall.lib.translationhandler import tr - - -class HelpTextGroupId(Enum): - GENERAL = 'General' - NAVIGATION = 'Navigation' - SELECTION = 'Selection' - SEARCH = 'Search' - - -@dataclass -class HelpText: - description: str - keys: list[str] = field(default_factory=list) - - -@dataclass -class HelpGroup: - group_id: HelpTextGroupId - group_entries: list[HelpText] - - def get_desc_width(self) -> int: - return max([len(e.description) for e in self.group_entries]) - - def get_key_width(self) -> int: - return max([len(', '.join(e.keys)) for e in self.group_entries]) - - -class Help: - # the groups needs to be classmethods not static methods - # because they rely on the DeferredTranslation setup first; - # if they are static methods, they will be called before the - # translation setup is done - - @staticmethod - def general() -> HelpGroup: - return HelpGroup( - group_id=HelpTextGroupId.GENERAL, - group_entries=[ - HelpText(tr('Show help'), ['Ctrl+h']), - HelpText(tr('Exit help'), ['Esc']), - ], - ) - - @staticmethod - def navigation() -> HelpGroup: - return HelpGroup( - group_id=HelpTextGroupId.NAVIGATION, - group_entries=[ - HelpText(tr('Preview scroll up'), ['PgUp']), - HelpText(tr('Preview scroll down'), ['PgDown']), - HelpText(tr('Move up'), ['k', '↑']), - HelpText(tr('Move down'), ['j', '↓']), - HelpText(tr('Move right'), ['l', '→']), - HelpText(tr('Move left'), ['h', '←']), - HelpText(tr('Jump to entry'), ['1..9']), - ], - ) - - @staticmethod - def selection() -> HelpGroup: - return HelpGroup( - group_id=HelpTextGroupId.SELECTION, - group_entries=[ - HelpText(tr('Skip selection (if available)'), ['Esc']), - HelpText(tr('Reset selection (if available)'), ['Ctrl+c']), - HelpText(tr('Select on single select'), ['Enter']), - HelpText(tr('Select on multi select'), ['Space', 'Tab']), - HelpText(tr('Reset'), ['Ctrl-C']), - HelpText(tr('Skip selection menu'), ['Esc']), - ], - ) - - @staticmethod - def search() -> HelpGroup: - return HelpGroup( - group_id=HelpTextGroupId.SEARCH, - group_entries=[ - HelpText(tr('Start search mode'), ['/']), - HelpText(tr('Exit search mode'), ['Esc']), - ], - ) - - @staticmethod - def get_help_text() -> str: - help_output = '' - help_texts = [ - Help.general(), - Help.navigation(), - Help.selection(), - Help.search(), - ] - max_desc_width = max([help.get_desc_width() for help in help_texts]) + 2 - max_key_width = max([help.get_key_width() for help in help_texts]) - - for help_group in help_texts: - help_output += f'{help_group.group_id.value}\n' - divider_len = max_desc_width + max_key_width - help_output += '-' * divider_len + '\n' - - for entry in help_group.group_entries: - help_output += entry.description.ljust(max_desc_width, ' ') + ', '.join(entry.keys) + '\n' - - help_output += '\n' - - return help_output diff --git a/archinstall/tui/menu_item.py b/archinstall/tui/menu_item.py deleted file mode 100644 index 418a4409..00000000 --- a/archinstall/tui/menu_item.py +++ /dev/null @@ -1,515 +0,0 @@ -from collections.abc import Callable -from dataclasses import dataclass, field -from enum import Enum -from functools import cached_property -from typing import Any, ClassVar, Self, override - -from archinstall.lib.translationhandler import tr -from archinstall.lib.utils.encoding import unicode_ljust - - -@dataclass -class MenuItem: - text: str - value: Any | None = None - action: Callable[[Any], Any] | None = None - enabled: bool = True - read_only: bool = False - mandatory: bool = False - dependencies: list[str | Callable[[], bool]] = field(default_factory=list) - dependencies_not: list[str] = field(default_factory=list) - display_action: Callable[[Any], str] | None = None - preview_action: Callable[[Self], str | None] | None = None - key: str | None = None - - _id: str = '' - _yes: ClassVar[Self | None] = None - _no: ClassVar[Self | None] = None - - def __post_init__(self) -> None: - if self.key is not None: - self._id = self.key - else: - self._id = str(id(self)) - - @override - def __hash__(self) -> int: - return hash(self._id) - - def get_id(self) -> str: - return self._id - - def get_value(self) -> Any: - assert self.value is not None - return self.value - - @classmethod - def yes(cls, action: Callable[[Any], Any] | None = None) -> Self: - if cls._yes is None: - cls._yes = cls(tr('Yes'), value=True, key='yes', action=action) - - return cls._yes - - @classmethod - def no(cls, action: Callable[[Any], Any] | None = None) -> Self: - if cls._no is None: - cls._no = cls(tr('No'), value=False, key='no', action=action) - - return cls._no - - def is_empty(self) -> bool: - return self.text == '' or self.text is None - - def has_value(self) -> bool: - if self.value is None: - return False - elif isinstance(self.value, list) and len(self.value) == 0: - return False - elif isinstance(self.value, dict) and len(self.value) == 0: - return False - else: - return True - - def get_display_value(self) -> str | None: - if self.display_action is not None: - return self.display_action(self.value) - - return None - - -class MenuItemGroup: - def __init__( - self, - menu_items: list[MenuItem], - focus_item: MenuItem | None = None, - default_item: MenuItem | None = None, - sort_items: bool = False, - sort_case_sensitive: bool = True, - checkmarks: bool = False, - ) -> None: - if len(menu_items) < 1: - raise ValueError('Menu must have at least one item') - - if sort_items: - if sort_case_sensitive: - menu_items = sorted(menu_items, key=lambda x: x.text) - else: - menu_items = sorted(menu_items, key=lambda x: x.text.lower()) - - self._filter_pattern: str = '' - self._checkmarks: bool = checkmarks - - self._menu_items: list[MenuItem] = menu_items - self.focus_item: MenuItem | None = focus_item - self.selected_items: list[MenuItem] = [] - self.default_item: MenuItem | None = default_item - - if not focus_item: - self.focus_first() - - if self.focus_item not in self.items: - raise ValueError(f'Selected item not in menu: {self.focus_item}') - - def add_item(self, item: MenuItem) -> None: - self._menu_items.append(item) - del self.items # resetting the cache - - def find_by_id(self, item_id: str) -> MenuItem: - for item in self._menu_items: - if item.get_id() == item_id: - return item - - raise ValueError(f'No item found for id: {item_id}') - - def find_by_key(self, key: str) -> MenuItem: - for item in self._menu_items: - if item.key == key: - return item - - raise ValueError(f'No item found for key: {key}') - - def get_enabled_items(self) -> list[MenuItem]: - return [it for it in self.items if self.is_enabled(it)] - - @classmethod - def yes_no(cls) -> Self: - return cls( - [MenuItem.yes(), MenuItem.no()], - sort_items=True, - ) - - @classmethod - def from_enum( - cls, - enum_cls: type[Enum], - sort_items: bool = False, - preset: Enum | None = None, - ) -> Self: - items = [MenuItem(elem.value, value=elem) for elem in enum_cls] - group = cls(items, sort_items=sort_items) - - if preset is not None: - group.set_selected_by_value(preset) - - return group - - def set_preview_for_all(self, action: Callable[[Any], str | None]) -> None: - for item in self.items: - item.preview_action = action - - def set_focus_by_value(self, value: Any) -> None: - for item in self._menu_items: - if item.value == value: - self.focus_item = item - break - - def set_default_by_value(self, value: Any) -> None: - for item in self._menu_items: - if item.value == value: - self.default_item = item - break - - def set_selected_by_value(self, values: Any | list[Any] | None) -> None: - if values is None: - return - - if not isinstance(values, list): - values = [values] - - for item in self._menu_items: - if item.value in values: - self.selected_items.append(item) - - if values: - self.set_focus_by_value(values[0]) - - def get_focused_index(self) -> int | None: - items = self.get_enabled_items() - - if self.focus_item and items: - try: - return items.index(self.focus_item) - except ValueError: - # on large menus (15k+) when filtering very quickly - # the index search is too slow while the items are reduced - # by the filter and it will blow up as it cannot find the - # focus item - pass - - return None - - def index_focus(self) -> int | None: - if self.focus_item and self.items: - try: - return self.items.index(self.focus_item) - except ValueError: - # on large menus (15k+) when filtering very quickly - # the index search is too slow while the items are reduced - # by the filter and it will blow up as it cannot find the - # focus item - pass - - return None - - @property - def size(self) -> int: - return len(self.items) - - def get_max_width(self) -> int: - # use the menu_items not the items here otherwise the preview - # will get resized all the time when a filter is applied - return max([len(self.get_item_text(item)) for item in self._menu_items]) - - @cached_property - def _max_items_text_width(self) -> int: - return max([len(item.text) for item in self._menu_items]) - - def get_item_text(self, item: MenuItem) -> str: - if item.is_empty(): - return '' - - max_width = self._max_items_text_width - display_text = item.get_display_value() - - default_text = self._default_suffix(item) - - text = unicode_ljust(str(item.text), max_width, ' ') - spacing = ' ' * 4 - - if display_text: - text = f'{text}{spacing}{display_text}' - elif self._checkmarks: - from archinstall.tui.types import Chars - - if item.has_value(): - if item.get_value() is not False: - text = f'{text}{spacing}{Chars.Check}' - else: - text = item.text - - if default_text: - text = f'{text} {default_text}' - - return text.rstrip(' ') - - def _default_suffix(self, item: MenuItem) -> str: - if self.default_item == item: - return tr(' (default)') - return '' - - def set_action_for_all(self, action: Callable[[Any], Any]) -> None: - for item in self.items: - item.action = action - - @cached_property - def items(self) -> list[MenuItem]: - pattern = self._filter_pattern.lower() - items = filter(lambda item: item.is_empty() or pattern in item.text.lower(), self._menu_items) - l_items = sorted(items, key=self._items_score) - return l_items - - def _items_score(self, item: MenuItem) -> int: - pattern = self._filter_pattern.lower() - if item.text.lower().startswith(pattern): - return 0 - return 1 - - @property - def filter_pattern(self) -> str: - return self._filter_pattern - - def has_filter(self) -> bool: - return self._filter_pattern != '' - - def set_filter_pattern(self, pattern: str) -> None: - self._filter_pattern = pattern - del self.items # resetting the cache - self.focus_first() - - def append_filter(self, pattern: str) -> None: - self._filter_pattern += pattern - del self.items # resetting the cache - self.focus_first() - - def reduce_filter(self) -> None: - self._filter_pattern = self._filter_pattern[:-1] - del self.items # resetting the cache - self.focus_first() - - def _reload_focus_item(self) -> None: - if len(self.items) > 0: - if self.focus_item not in self.items: - self.focus_first() - else: - self.focus_item = None - - def is_item_selected(self, item: MenuItem) -> bool: - return item in self.selected_items - - def select_current_item(self) -> None: - if self.focus_item: - if self.focus_item in self.selected_items: - self.selected_items.remove(self.focus_item) - else: - self.selected_items.append(self.focus_item) - - def focus_index(self, index: int) -> None: - enabled = self.get_enabled_items() - self.focus_item = enabled[index] - - def focus_first(self) -> None: - if len(self.items) == 0: - return - - first_item: MenuItem | None = self.items[0] - - if first_item and not self._is_selectable(first_item): - first_item = self._find_next_selectable_item(self.items, first_item, 1) - - if first_item is not None: - self.focus_item = first_item - - def focus_last(self) -> None: - if len(self.items) == 0: - return - - last_item: MenuItem | None = self.items[-1] - - if last_item and not self._is_selectable(last_item): - last_item = self._find_next_selectable_item(self.items, last_item, -1) - - if last_item is not None: - self.focus_item = last_item - - def focus_prev(self, skip_empty: bool = True) -> None: - # e.g. when filter shows no items - if self.focus_item is None: - return - - item = self._find_next_selectable_item(self.items, self.focus_item, -1) - - if item is not None: - self.focus_item = item - - def focus_next(self, skip_not_enabled: bool = True) -> None: - # e.g. when filter shows no items - if self.focus_item is None: - return - - item = self._find_next_selectable_item(self.items, self.focus_item, 1) - - if item is not None: - self.focus_item = item - - def _find_next_selectable_item( - self, - items: list[MenuItem], - start_item: MenuItem, - direction: int, - ) -> MenuItem | None: - start_index = self.items.index(start_item) - n = len(items) - - current_index = start_index - for _ in range(n): - current_index = (current_index + direction) % n - - if self._is_selectable(items[current_index]): - return items[current_index] - - return None - - def is_mandatory_fulfilled(self) -> bool: - for item in self._menu_items: - if item.mandatory and not item.value: - return False - return True - - def max_item_width(self) -> int: - spaces = [len(str(it.text)) for it in self.items] - if spaces: - return max(spaces) - return 0 - - def _is_selectable(self, item: MenuItem) -> bool: - if item.is_empty(): - return False - elif item.read_only: - return False - - return self.is_enabled(item) - - def is_enabled(self, item: MenuItem) -> bool: - if not item.enabled: - return False - - for dep in item.dependencies: - if isinstance(dep, str): - item = self.find_by_key(dep) - if not item.value or not self.is_enabled(item): - return False - else: - return dep() - - for dep_not in item.dependencies_not: - item = self.find_by_key(dep_not) - if item.value is not None: - return False - - return True - - -class MenuItemsState: - def __init__( - self, - item_group: MenuItemGroup, - total_cols: int, - total_rows: int, - with_frame: bool, - ) -> None: - self._item_group = item_group - self._total_cols = total_cols - self._total_rows = total_rows - 2 if with_frame else total_rows - - self._prev_row_idx: int = -1 - self._prev_visible_rows: list[int] = [] - self._view_items: list[list[MenuItem]] = [] - - def _determine_focus_row(self) -> int | None: - focus_index = self._item_group.index_focus() - - if focus_index is None: - return None - - row_index = focus_index // self._total_cols - return row_index - - def get_view_items(self) -> list[list[MenuItem]]: - enabled_items = self._item_group.get_enabled_items() - focus_row_idx = self._determine_focus_row() - - if focus_row_idx is None: - return [] - - start, end = 0, 0 - - if len(self._view_items) == 0 or self._prev_row_idx == -1 or focus_row_idx == 0: # initial setup - if focus_row_idx < self._total_rows: - start = 0 - end = self._total_rows - elif focus_row_idx > len(enabled_items) - self._total_rows: - start = len(enabled_items) - self._total_rows - end = len(enabled_items) - else: - start = focus_row_idx - end = focus_row_idx + self._total_rows - elif len(enabled_items) <= self._total_rows: # the view can handle all items - start = 0 - end = self._total_rows - elif not self._item_group.has_filter() and focus_row_idx in self._prev_visible_rows: # focus is in the same view - self._prev_row_idx = focus_row_idx - return self._view_items - else: - if self._item_group.has_filter(): - start = focus_row_idx - end = focus_row_idx + self._total_rows - else: - delta = focus_row_idx - self._prev_row_idx - - if delta > 0: # cursor is on the bottom most row - start = focus_row_idx - self._total_rows + 1 - end = focus_row_idx + 1 - else: # focus is on the top most row - start = focus_row_idx - end = focus_row_idx + self._total_rows - - self._view_items = self._get_view_items(enabled_items, start, end) - self._prev_visible_rows = list(range(start, end)) - self._prev_row_idx = focus_row_idx - - return self._view_items - - def _get_view_items( - self, - items: list[MenuItem], - start_row: int, - total_rows: int, - ) -> list[list[MenuItem]]: - groups: list[list[MenuItem]] = [] - nr_items = self._total_cols * min(total_rows, len(items)) - - for x in range(start_row, nr_items, self._total_cols): - groups.append( - items[x : x + self._total_cols], - ) - - return groups - - def _max_visible_items(self) -> int: - return self._total_cols * self._total_rows - - def _remaining_next_spots(self) -> int: - return self._max_visible_items() - self._prev_row_idx - - def _remaining_prev_spots(self) -> int: - return self._max_visible_items() - self._remaining_next_spots() diff --git a/archinstall/tui/result.py b/archinstall/tui/result.py deleted file mode 100644 index 13f12794..00000000 --- a/archinstall/tui/result.py +++ /dev/null @@ -1,37 +0,0 @@ -from dataclasses import dataclass -from enum import Enum, auto - -from archinstall.tui.menu_item import MenuItem - - -class ResultType(Enum): - Selection = auto() - Skip = auto() - Reset = auto() - - -@dataclass -class Result[ValueT]: - type_: ResultType - _item: MenuItem | list[MenuItem] | str | None - - def has_item(self) -> bool: - return self._item is not None - - def get_value(self) -> ValueT: - return self.item().get_value() # type: ignore[no-any-return] - - def get_values(self) -> list[ValueT]: - return [i.get_value() for i in self.items()] - - def item(self) -> MenuItem: - assert self._item is not None and isinstance(self._item, MenuItem) - return self._item - - def items(self) -> list[MenuItem]: - assert self._item is not None and isinstance(self._item, list) - return self._item - - def text(self) -> str: - assert self._item is not None and isinstance(self._item, str) - return self._item diff --git a/archinstall/tui/types.py b/archinstall/tui/types.py deleted file mode 100644 index d57123ae..00000000 --- a/archinstall/tui/types.py +++ /dev/null @@ -1,144 +0,0 @@ -import curses -from dataclasses import dataclass -from enum import Enum, auto -from typing import Self - -SCROLL_INTERVAL = 10 - - -class STYLE(Enum): - NORMAL = 1 - CURSOR_STYLE = 2 - MENU_STYLE = 3 - HELP = 4 - ERROR = 5 - - -class MenuKeys(Enum): - # latin keys - STD_KEYS = frozenset(range(32, 127)) - # numbers - NUM_KEYS = frozenset(range(49, 58)) - # Menu up: up, k - MENU_UP = frozenset({259, 107}) - # Menu down: down, j - MENU_DOWN = frozenset({258, 106}) - # Menu left: left, h - MENU_LEFT = frozenset({260, 104}) - # Menu right: right, l - MENU_RIGHT = frozenset({261, 108}) - # Menu start: home CTRL-a - MENU_START = frozenset({262, 1}) - # Menu end: end CTRL-e - MENU_END = frozenset({360, 5}) - # Enter - ACCEPT = frozenset({10}) - # Selection: space, tab - MULTI_SELECT = frozenset({32, 9}) - # Search: / - ENABLE_SEARCH = frozenset({47}) - # ESC - ESC = frozenset({27}) - # BACKSPACE (search) - BACKSPACE = frozenset({127, 263}) - # Help view: ctrl+h - HELP = frozenset({8}) - # Scroll up: PGUP - SCROLL_UP = frozenset({339}) - # Scroll down: PGDOWN - SCROLL_DOWN = frozenset({338}) - - @classmethod - def from_ord(cls, key: int) -> list[Self]: - matches: list[Self] = [] - - for group in cls: - if key in group.value: - matches.append(group) - - return matches - - @classmethod - def decode(cls, key: int) -> str: - byte_str = curses.keyname(key) - return byte_str.decode('utf-8') - - -class FrameStyle(Enum): - MAX = auto() - MIN = auto() - - -@dataclass -class FrameProperties: - header: str - w_frame_style: FrameStyle = FrameStyle.MAX - h_frame_style: FrameStyle = FrameStyle.MAX - - @classmethod - def max(cls, header: str) -> Self: - return cls( - header, - FrameStyle.MAX, - FrameStyle.MAX, - ) - - @classmethod - def min(cls, header: str) -> Self: - return cls( - header, - FrameStyle.MIN, - FrameStyle.MIN, - ) - - -class Orientation(Enum): - VERTICAL = auto() - HORIZONTAL = auto() - - -class PreviewStyle(Enum): - NONE = auto() - BOTTOM = auto() - RIGHT = auto() - TOP = auto() - - -# https://www.compart.com/en/unicode/search?q=box+drawings#characters -# https://en.wikipedia.org/wiki/Box-drawing_characters -class Chars: - Horizontal = '─' - Vertical = '│' - Upper_left = '┌' - Upper_right = '┐' - Lower_left = '└' - Lower_right = '┘' - Block = '█' - Triangle_up = '▲' - Triangle_down = '▼' - Check = '+' - Cross = 'x' - Right_arrow = '←' - - -@dataclass -class ViewportEntry: - text: str - row: int - col: int - style: STYLE - - -class Alignment(Enum): - LEFT = auto() - CENTER = auto() - - -@dataclass -class FrameDim: - x_start: int - x_end: int - height: int - - def x_delta(self) -> int: - return self.x_end - self.x_start From 05560d29af9852899d95878b1026e7ffbd2ae760 Mon Sep 17 00:00:00 2001 From: correctmost <134317971+correctmost@users.noreply.github.com> Date: Wed, 29 Apr 2026 07:54:59 -0400 Subject: [PATCH 22/61] Add basic Pyrefly configuration to pyproject.toml (#4494) This makes it easier to use the Pyrefly type checker with various IDEs. --- pyproject.toml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 95d85144..1add6027 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -172,6 +172,23 @@ score = false [tool.pylint.variables] init-import = true +[tool.pyrefly] +python-version = "3.14" + +replace-imports-with-any = [ + "parted", # pyparted doesn't have type hints +] + +[tool.pyrefly.errors] +# Enable some additional rules that are disabled by default +implicit-abstract-class = true +missing-override-decorator = true +missing-source = true +not-required-key-access = true +open-unpacking = true +unannotated-parameter = true +unused-ignore = true + [tool.ruff] target-version = "py314" line-length = 160 From 3b026cb603b3e53000a9a84b0d2682b95d458fa0 Mon Sep 17 00:00:00 2001 From: Softer Date: Thu, 30 Apr 2026 00:33:21 +0300 Subject: [PATCH 23/61] Fix broken localization in U2F, LUKS, and gfx driver preview (#4500) --- archinstall/lib/authentication/authentication_handler.py | 2 +- archinstall/lib/disk/encryption_menu.py | 2 +- archinstall/lib/profile/profile_menu.py | 2 +- archinstall/locales/base.pot | 8 ++++++++ 4 files changed, 11 insertions(+), 3 deletions(-) diff --git a/archinstall/lib/authentication/authentication_handler.py b/archinstall/lib/authentication/authentication_handler.py index a0b8e132..9a99ba02 100644 --- a/archinstall/lib/authentication/authentication_handler.py +++ b/archinstall/lib/authentication/authentication_handler.py @@ -81,7 +81,7 @@ class AuthenticationHandler: install_session.pacman.strap('pam-u2f') - print(tr(f'Setting up U2F login: {u2f_config.u2f_login_method.value}')) + print(tr('Setting up U2F login: {}').format(u2f_config.u2f_login_method.value)) # https://developers.yubico.com/pam-u2f/ u2f_auth_file = install_session.target / 'etc/u2f_mappings' diff --git a/archinstall/lib/disk/encryption_menu.py b/archinstall/lib/disk/encryption_menu.py index 39c631cd..d66ccf92 100644 --- a/archinstall/lib/disk/encryption_menu.py +++ b/archinstall/lib/disk/encryption_menu.py @@ -375,7 +375,7 @@ async def select_lvm_vols_to_encrypt( async def select_iteration_time(preset: int | None = None) -> int | None: header = tr('Enter iteration time for LUKS encryption (in milliseconds)') + '\n' header += tr('Higher values increase security but slow down boot time') + '\n' - header += tr(f'Default: {DEFAULT_ITER_TIME}ms, Recommended range: 1000-60000') + '\n' + header += tr('Default: {}ms, Recommended range: 1000-60000').format(DEFAULT_ITER_TIME) + '\n' def validate_iter_time(value: str) -> str | None: try: diff --git a/archinstall/lib/profile/profile_menu.py b/archinstall/lib/profile/profile_menu.py index 115c9fc0..0bdc9b0b 100644 --- a/archinstall/lib/profile/profile_menu.py +++ b/archinstall/lib/profile/profile_menu.py @@ -113,7 +113,7 @@ class ProfileMenu(AbstractSubMenu[ProfileConfiguration]): if item.value: driver = item.get_value().value packages = item.get_value().packages_text() - return f'Driver: {driver}\n{packages}' + return f'{tr("Graphics driver")}: {driver}\n{packages}' return None def _prev_greeter(self, item: MenuItem) -> str | None: diff --git a/archinstall/locales/base.pot b/archinstall/locales/base.pot index 4e9e0e9f..31f5e196 100644 --- a/archinstall/locales/base.pot +++ b/archinstall/locales/base.pot @@ -2285,3 +2285,11 @@ msgstr "" #, python-brace-format msgid "Default: {DEFAULT_ITER_TIME}ms, Recommended range: 1000-60000" msgstr "" + +#, python-brace-format +msgid "Setting up U2F login: {}" +msgstr "" + +#, python-brace-format +msgid "Default: {}ms, Recommended range: 1000-60000" +msgstr "" From 6fefa3b6f54a16832249605bfb5d1d4cb444d06c Mon Sep 17 00:00:00 2001 From: aronmr-1 Date: Wed, 29 Apr 2026 21:43:01 +0000 Subject: [PATCH 24/61] Improved Finnish language (#4501) --- archinstall/locales/fi/LC_MESSAGES/base.mo | Bin 40494 -> 51470 bytes archinstall/locales/fi/LC_MESSAGES/base.po | 379 +++++++++++---------- archinstall/locales/languages.json | 2 +- 3 files changed, 206 insertions(+), 175 deletions(-) diff --git a/archinstall/locales/fi/LC_MESSAGES/base.mo b/archinstall/locales/fi/LC_MESSAGES/base.mo index f6edf647b6322606d65af4662c1a27b16e8a6eee..43b527d57e25ff1c6ff6397f4403d58f5721cdc1 100644 GIT binary patch delta 19895 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 M?6&#aD@r^3Kd8N9ltEbzc90p2gd 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 N^_wkxU%nX|@E_nwMkoLP diff --git a/archinstall/locales/fi/LC_MESSAGES/base.po b/archinstall/locales/fi/LC_MESSAGES/base.po index 7651bb13..2580f222 100644 --- a/archinstall/locales/fi/LC_MESSAGES/base.po +++ b/archinstall/locales/fi/LC_MESSAGES/base.po @@ -3,7 +3,7 @@ msgstr "" "Project-Id-Version: \n" "POT-Creation-Date: \n" "PO-Revision-Date: \n" -"Last-Translator: Aleksi Puhakainen \n" +"Last-Translator: Aron R, Github aronmr-1\n" "Language-Team: \n" "Language: fi\n" "MIME-Version: 1.0\n" @@ -55,7 +55,7 @@ msgid "Only packages such as base, base-devel, linux, linux-firmware, efibootmgr msgstr "Vain paketit, kuten base, base-devel, linux, linux-firmware, efibootmgr ja valinnaiset profiili-paketit, asennetaan." msgid "Note: base-devel is no longer installed by default. Add it here if you need build tools." -msgstr "" +msgstr "Huomio: base-devel ei enään asenneta oletuksena. Lisää se tähän jos tarvitset työkaluja pakettejen rakentamiseen" msgid "If you desire a web browser, such as firefox or chromium, you may specify it in the following prompt." msgstr "Jos haluat selaimen, kuten firefox tai chromium, voit valita sen seuraavassa kohdassa." @@ -257,6 +257,9 @@ msgstr "Kieli" msgid "Locale encoding" msgstr "Kieli-koodaus" +msgid "Console font" +msgstr "Päätteen kirjaintyyli" + msgid "Drive(s)" msgstr "Levyasemat" @@ -645,7 +648,7 @@ msgid "Select interface to add" msgstr "Valitse lisättävä liitäntä" msgid "Manual configuration" -msgstr "Käsin määrittely" +msgstr "Manuaalinen konfiguraatio" msgid "Mark/Unmark a partition as compressed (btrfs only)" msgstr "Merkitse/poista osio pakatuksi (vain btrfs)" @@ -828,7 +831,7 @@ msgid "Pacman" msgstr "" msgid "Color" -msgstr "" +msgstr "Väri" #, fuzzy, python-brace-format msgid "Enter the number of parallel downloads (1-{})" @@ -967,28 +970,28 @@ msgid "Encryption type" msgstr "Salaustyyppi" msgid "Iteration time" -msgstr "" +msgstr "Iteraatioaika" msgid "Enter iteration time for LUKS encryption (in milliseconds)" -msgstr "" +msgstr "Anna iteraatioaika LUKS salaukselle (millisekunneissa)" msgid "Higher values increase security but slow down boot time" -msgstr "" +msgstr "Suuremmat arvot parantavat tietosuojaa mutta hidastavat käynnistysaikaa" msgid "Default: 10000ms, Recommended range: 1000-60000" -msgstr "" +msgstr "Oletusarvo: 10000ms, suositeltu 1000-60000ms" msgid "Iteration time cannot be empty" -msgstr "" +msgstr "Iteraatioaika ei voi olla tyhjä" msgid "Iteration time must be at least 100ms" -msgstr "" +msgstr "Iteraatioajan on oltava ainakin 100ms" msgid "Iteration time must be at most 120000ms" -msgstr "" +msgstr "Iteraatioaika saa olla enintään 120000ms" msgid "Please enter a valid number" -msgstr "" +msgstr "Ole hyvä ja syötä kelpaava numero" msgid "Partitions" msgstr "Osiot" @@ -1223,6 +1226,23 @@ msgstr "Tuote" msgid "Invalid configuration: {error}" msgstr "Virheellinen määritys: {error}" +msgid "Ready to install" +msgstr "Valmis asentamaan" + +msgid "Disks" +msgstr "Levyasemat" + +#, fuzzy +msgid "Packages" +msgstr "Määritetty {} liittymää" + +msgid "Network" +msgstr "Verkko" + +#, fuzzy +msgid "Locale" +msgstr "Alueet" + msgid "Type" msgstr "Tyyppi" @@ -1312,7 +1332,7 @@ msgid "LVM configuration type" msgstr "Ei määritystä" msgid "LVM disk encryption with more than 2 partitions is currently not supported" -msgstr "" +msgstr "LVM levyaseman salaus enemmällä kuin kahdella osiolla ei ole tällä hetkellä tuettu" #, fuzzy msgid "Use NetworkManager (necessary to configure internet graphically in GNOME and KDE Plasma)" @@ -1327,13 +1347,13 @@ msgid "Partitioning" msgstr "Osio" msgid "Logical Volume Management (LVM)" -msgstr "" +msgstr "Looginen taltiohallinta (engl. 'Logical Volume Management', LVM)" msgid "Physical volumes" -msgstr "" +msgstr "Fyysiset taltiot" msgid "Volumes" -msgstr "" +msgstr "Taltiot" #, fuzzy msgid "LVM volumes" @@ -1366,10 +1386,10 @@ msgstr "" #, fuzzy msgid "Yes" -msgstr "kyllä" +msgstr "Kyllä" msgid "No" -msgstr "" +msgstr "Ei" #, fuzzy msgid "Archinstall help" @@ -1380,7 +1400,7 @@ msgid " (default)" msgstr "(oletus)" msgid "Press Ctrl+h for help" -msgstr "" +msgstr "Paina Ctrl+h saadaksesi apua" #, fuzzy msgid "Choose an option to give Sway access to your hardware" @@ -1390,7 +1410,7 @@ msgstr "" "Valitse asetus antaaksesi Swaylle pääsyn laitteistoosi" msgid "Seat access" -msgstr "" +msgstr "Istunnon pääsy (seat)" #, fuzzy msgid "Mountpoint" @@ -1416,7 +1436,7 @@ msgid "Filesystem" msgstr "Vaihda tiedostojärjestelmä" msgid "Invalid size" -msgstr "" +msgstr "Epäkelpo koko" #, fuzzy msgid "Start (default: sector {}): " @@ -1435,7 +1455,7 @@ msgid "Disk configuration type" msgstr "Kiintolevyn määritys" msgid "Root mount directory" -msgstr "" +msgstr "Juuren (Root) liittämishakemisto (mount directory)" #, fuzzy msgid "Select language" @@ -1446,10 +1466,10 @@ msgid "Write additional packages to install (space separated, leave blank to ski msgstr "Kirjoita asennettavat lisäpaketit (välilyönnillä erotettuna, ohita vaihe jättämällä tyhjäksi): " msgid "Invalid download number" -msgstr "" +msgstr "Epäkelpo numero lataukseen" msgid "Number downloads" -msgstr "" +msgstr "Numero latauksia" #, fuzzy msgid "The username you entered is invalid" @@ -1515,14 +1535,14 @@ msgstr "Sway ei tue patentoitua Nvidia-ohjainta. Todennäköistä, että kohtaat #, fuzzy msgid "Main profile" -msgstr "Muokkaa profiilia" +msgstr "Pääprofiili" #, fuzzy msgid "Confirm password" -msgstr "Vaihda salasana" +msgstr "Vahvista salasana" msgid "The confirmation password did not match, please try again" -msgstr "" +msgstr "Salasanat eivät vastannet, ole hyvä ja koita uudestaan" #, fuzzy msgid "Not a valid directory" @@ -1533,7 +1553,7 @@ msgid "Would you like to continue?" msgstr "Haluatko käyttää BTRFS-pakkausta?" msgid "Directory" -msgstr "" +msgstr "Hakemisto" #, fuzzy msgid "Enter a directory for the configuration(s) to be saved (tab completion enabled)" @@ -1574,7 +1594,7 @@ msgid "Select execution mode" msgstr "Valitse suoritustila" msgid "Press ? for help" -msgstr "" +msgstr "Paina ? saadaksesi apua" #, fuzzy msgid "Choose an option to give Hyprland access to your hardware" @@ -1591,7 +1611,7 @@ msgid "NTP" msgstr "" msgid "Swap on zram" -msgstr "" +msgstr "Swap zram:illa (Swap on zram)" msgid "Name" msgstr "Nimi" @@ -1617,17 +1637,17 @@ msgid "HSM device" msgstr "Laite" msgid "Some packages could not be found in the repository" -msgstr "" +msgstr "Jotkut paketit eivät löytyneet repositoriosta" #, fuzzy msgid "User" msgstr "Käyttäjänimi : " msgid "The specified configuration will be applied" -msgstr "" +msgstr "Käytetään valittua konfiguraatiota" msgid "Wipe" -msgstr "" +msgstr "Pyyhi" #, fuzzy msgid "Mark/Unmark as XBOOTLDR" @@ -1638,7 +1658,7 @@ msgid "Loading packages..." msgstr "Lisäpaketit" msgid "Select any packages from the below list that should be installed additionally" -msgstr "" +msgstr "Valitse alla olevalta listalta asennettavat lisäpaketit" #, fuzzy msgid "Add a custom repository" @@ -1669,7 +1689,7 @@ msgid "Delete custom server" msgstr "Poista mukautettu peilipaikka" msgid "Server url" -msgstr "" +msgstr "Palvelimen url" #, fuzzy msgid "Select regions" @@ -1704,19 +1724,19 @@ msgid "Custom repositories" msgstr "Valinnaiset arkistot" msgid "Only ASCII characters are supported" -msgstr "" +msgstr "Ainoastaan ASCII hahmot tuetaan" msgid "Show help" msgstr "Näytä apua" msgid "Exit help" -msgstr "" +msgstr "Poistu avusta" msgid "Preview scroll up" -msgstr "" +msgstr "Esikatsele ylöspäin scrollaamista" msgid "Preview scroll down" -msgstr "" +msgstr "Esikatsele alaspäin scrollaamista" msgid "Move up" msgstr "Liikku ylös" @@ -1731,13 +1751,13 @@ msgid "Move left" msgstr "Liiku vasemmalle" msgid "Jump to entry" -msgstr "" +msgstr "Hyppää kohtaan" msgid "Skip selection (if available)" -msgstr "" +msgstr "Hyppää valinnan yli (jos saatavilla)" msgid "Reset selection (if available)" -msgstr "" +msgstr "Nollaa valinta (jos saatavilla)" #, fuzzy msgid "Select on single select" @@ -1748,20 +1768,20 @@ msgid "Select on multi select" msgstr "Valitse aikavyöhyke" msgid "Reset" -msgstr "" +msgstr "Nollaa" #, fuzzy msgid "Skip selection menu" msgstr "Valitse suoritustila" msgid "Start search mode" -msgstr "" +msgstr "Aloita hakutila" msgid "Exit search mode" -msgstr "" +msgstr "Poistu hakutilasta" msgid "General" -msgstr "" +msgstr "Yleinen" #, fuzzy msgid "Navigation" @@ -1772,13 +1792,13 @@ msgid "Selection" msgstr "Valitse allekirjoituksen asetus" msgid "Search" -msgstr "" +msgstr "Haku" msgid "Down" -msgstr "" +msgstr "Alas" msgid "Up" -msgstr "" +msgstr "Ylös" #, fuzzy msgid "Confirm" @@ -1793,47 +1813,47 @@ msgid "Focus left" msgstr "Liiku vasemmalle" msgid "Toggle" -msgstr "" +msgstr "Vaihda" #, fuzzy msgid "Show/Hide help" msgstr "Näytä apua" msgid "Quit" -msgstr "" +msgstr "Poistu" msgid "First" -msgstr "" +msgstr "Ensimmäinen" msgid "Last" -msgstr "" +msgstr "Viimeinen" #, fuzzy msgid "Select" msgstr "Valitse allekirjoituksen asetus" msgid "Page Up" -msgstr "" +msgstr "Liiku ylös" msgid "Page Down" -msgstr "" +msgstr "Liiku alas" msgid "Page up" -msgstr "" +msgstr "Liiku ylös" #, fuzzy msgid "Page down" -msgstr "Liikku alas" +msgstr "Liiku alas" msgid "Page Left" -msgstr "" +msgstr "Liiku vasemalle" #, fuzzy msgid "Page Right" msgstr "Liiku oikealle" msgid "Cursor up" -msgstr "" +msgstr "Kursori ylös" #, fuzzy msgid "Cursor down" @@ -1848,48 +1868,48 @@ msgid "Cursor left" msgstr "Liiku vasemmalle" msgid "Top" -msgstr "" +msgstr "Yläosa" msgid "Bottom" -msgstr "" +msgstr "Pohja" msgid "Home" -msgstr "" +msgstr "Rivin alku" #, fuzzy msgid "End" -msgstr "Käytössä" +msgstr "Rivin loppu" #, fuzzy msgid "Toggle option" msgstr "Alitaltion valinnat" msgid "Scroll Up" -msgstr "" +msgstr "Scrollaa ylös" msgid "Scroll Down" -msgstr "" +msgstr "Scrollaa alas" msgid "Scroll Left" -msgstr "" +msgstr "Scrollaa vasemmalle" msgid "Scroll Right" -msgstr "" +msgstr "Scrollaa oikealle" msgid "Scroll Home" -msgstr "" +msgstr "Scrollaa rivin alkuun" msgid "Scroll End" -msgstr "" +msgstr "Scrollaa rivin loppuun" msgid "Focus Next" -msgstr "" +msgstr "Kohdista seuraava" msgid "Focus Previous" -msgstr "" +msgstr "Kohdista edeltävä" msgid "Copy selected text" -msgstr "" +msgstr "Kopioi valittu teksti" #, fuzzy msgid "labwc needs access to your seat (collection of hardware devices i.e. keyboard, mouse, etc)" @@ -1900,33 +1920,33 @@ msgid "Choose an option to give labwc access to your hardware" msgstr "" "\n" "\n" -"Valitse asetus antaaksesi Swaylle pääsyn laitteistoosi" +"Valitse asetus antaaksesi labwc:lle pääsyn laitteistoosi" #, fuzzy msgid "niri needs access to your seat (collection of hardware devices i.e. keyboard, mouse, etc)" -msgstr "Sway tarvitsee pääsyn istuntoon (kokoelma laitteistoja, kuten näppäimistö, hiiri jne.)" +msgstr "niri tarvitsee pääsyn istuntoon (kokoelma laitteistoja, kuten näppäimistö, hiiri jne.)" #, fuzzy msgid "Choose an option to give niri access to your hardware" msgstr "" "\n" "\n" -"Valitse asetus antaaksesi Swaylle pääsyn laitteistoosi" +"Valitse asetus antaaksesi nirin pääsyn laitteistoosi" #, fuzzy msgid "Mark/Unmark as ESP" msgstr "Merkitse/poista käynnistyvä" msgid "Package group:" -msgstr "" +msgstr "Pakettiryhmä:" #, fuzzy msgid "Exit archinstall" -msgstr "Archinstall kieli" +msgstr "Poistu archinstall:ista" #, fuzzy msgid "Reboot system" -msgstr "Vaihda tiedostojärjestelmä" +msgstr "Käynnistä järjestelmä uudelleen" #, fuzzy msgid "chroot into installation for post-installation configurations" @@ -1937,7 +1957,7 @@ msgstr "Lataus valmis" #, fuzzy msgid "What would you like to do next?" -msgstr "Haluatko käyttää BTRFS-pakkausta?" +msgstr "Mitä haluaisit tehdä seuraavaksi?" #, fuzzy, python-brace-format msgid "Select which mode to configure for \"{}\"" @@ -1945,11 +1965,11 @@ msgstr "Valitse, mikä tila määritetään \"{}\", tai ohita ja käytä \"{}\" #, fuzzy msgid "Incorrect credentials file decryption password" -msgstr "Salauksen salasana" +msgstr "Väärä salasana salausta varten" #, fuzzy msgid "Incorrect password" -msgstr "Root salasana" +msgstr "Väärä salasana" #, fuzzy msgid "Credentials file decryption password" @@ -1957,10 +1977,7 @@ msgstr "Salauksen salasana" #, fuzzy msgid "Do you want to encrypt the user_credentials.json file?" -msgstr "" -"Haluatko tallentaa {} asetustiedostoa seuraavaan paikkaan?\n" -"\n" -"{}" +msgstr "Haluatko salata user_crendentials.json tiedoston?" #, fuzzy msgid "Credentials file encryption password" @@ -1976,10 +1993,10 @@ msgstr "Ei HSM-laitteita saatavilla" #, fuzzy msgid "Passwordless login" -msgstr "Salasana" +msgstr "Salasanaton kirjautuminen" msgid "Second factor login" -msgstr "" +msgstr "Toisen faktorin kirjautuminen" msgid "Bluetooth" msgstr "" @@ -1989,67 +2006,67 @@ msgid "Would you like to configure Bluetooth?" msgstr "Haluatko käyttää BTRFS-pakkausta?" msgid "Print service" -msgstr "" +msgstr "Printtauspalvelu" #, fuzzy msgid "Would you like to configure the print service?" -msgstr "Haluatko käyttää BTRFS-pakkausta?" +msgstr "Haluatko konfiguroida printtauspalvelun" #, fuzzy msgid "Power management" -msgstr "Osioiden hallinta: {}" +msgstr "Tehohallinta" msgid "Authentication" msgstr "Todennus" msgid "Applications" -msgstr "" +msgstr "Applikaatiot" msgid "U2F login method: " -msgstr "" +msgstr "U2F kirjautumismenetelmä" msgid "Passwordless sudo: " msgstr "Salasanaton sudo" #, python-brace-format msgid "Btrfs snapshot type: {}" -msgstr "" +msgstr "Btrfs tilankuvien tyyppi: {}" msgid "Syncing the system..." -msgstr "" +msgstr "Järjestelmän synkronointi" msgid "Value cannot be empty" -msgstr "" +msgstr "Arvo ei voi olla tyhjä" msgid "Snapshot type" -msgstr "" +msgstr "Tilannekuvan tyyppi" #, fuzzy, python-brace-format msgid "Snapshot type: {}" -msgstr "Ympäristötyyppi: {}" +msgstr "Tilannekuvan tyyppi: {}" msgid "U2F login setup" -msgstr "" +msgstr "U2F kirjautumisen konfigurointi" msgid "No U2F devices found" -msgstr "" +msgstr "U2F laitteita ei löydetty" msgid "U2F Login Method" -msgstr "" +msgstr "U2F kirjautumisen menetelmä" #, fuzzy msgid "Enable passwordless sudo?" -msgstr "Anna salasana: " +msgstr "Aktivoi salasanaton sudo?" #, fuzzy, python-brace-format msgid "Setting up U2F device for user: {}" msgstr "Valitse HSM:ään käytettävä FIDO2-laite" msgid "You may need to enter the PIN and then touch your U2F device to register it" -msgstr "" +msgstr "Voit joutua syöttämään PIN:in ja koskettaa U2F laitettasi rekisteröidäksesi sen" msgid "Starting device modifications in " -msgstr "" +msgstr "Aloitetaan laitteen muokkausta " #, fuzzy msgid "No network connection found" @@ -2057,116 +2074,116 @@ msgstr "Ei verkkoasetuksia" #, fuzzy msgid "Would you like to connect to a Wifi?" -msgstr "Haluatko käyttää BTRFS-pakkausta?" +msgstr "Haluaisitko liittyä langattomaan verkkoon?" #, fuzzy msgid "No wifi interface found" -msgstr "Määritetty {} liittymää" +msgstr "Langattomia liittymiä ei löydetty" #, fuzzy msgid "Select wifi network to connect to" -msgstr "Valitse yksi määritettävä verkkoliitäntä" +msgstr "Valitse wifi verkko johon yhdistää" msgid "Scanning wifi networks..." -msgstr "" +msgstr "Etsitään wifiyhteyksiä..." #, fuzzy msgid "No wifi networks found" msgstr "Ei verkkoasetuksia" msgid "Failed setting up wifi" -msgstr "" +msgstr "Langattoman verkon määrittäminen epäonnistui" #, fuzzy msgid "Enter wifi password" -msgstr "Anna salasana: " +msgstr "Anna wifi salasana: " msgid "Ok" msgstr "" msgid "Removable" -msgstr "" +msgstr "Irrotettava" msgid "Install to removable location" -msgstr "" +msgstr "Asenna irrotettavaan sijaintiin" msgid "Will install to /EFI/BOOT/ (removable location)" -msgstr "" +msgstr "Asennetaan sijaintiin /EFI/BOOT (irrotettava sijainti)" msgid "Will install to standard location with NVRAM entry" -msgstr "" +msgstr "Asennetaan oletuskohteeseen NVRAM kohteella" msgid "Would you like to install the bootloader to the default removable media search location?" -msgstr "" +msgstr "Haluatko asentaa käynnistyslataajan (bootloader) irrotettavan median oletushakukohteeseen?" msgid "This installs the bootloader to /EFI/BOOT/BOOTX64.EFI (or similar) which is useful for:" -msgstr "" +msgstr "Tämä asentaa käynnistyslataajan sijaintiin /EFI/BOOT/BOOTX64.EFI (tai vastaava) joka on hyödyllistä seuraaville:" msgid "USB drives or other portable external media." -msgstr "" +msgstr "USB levyt ja muu kannettava ulkoinen media." msgid "Systems where you want the disk to be bootable on any computer." -msgstr "" +msgstr "Järjestelmät joissa haluat että levy käynnistyvä millä tahansa koneella." msgid "Firmware that does not properly support NVRAM boot entries." -msgstr "" +msgstr "Firmware joka ei kunnolla tue NVRAM käynnistyskohteita." msgid "Will install to /EFI/BOOT/ (removable location, safe default)" -msgstr "" +msgstr "Asennetaan sijaintiin /EFI/BOOT (irrotettava sijainti, varma oletusarvo)" msgid "Will install to custom location with NVRAM entry" -msgstr "" +msgstr "Asennetaan omaan sijaintiin NVRAM kohteella" msgid "Firmware that does not properly support NVRAM boot entries like most MSI motherboards," -msgstr "" +msgstr "Firmware joka ei kunnolla tue NVRAM käynnistyskohteita kuten useimmat MSI emolevyt," msgid "most Apple Macs, many laptops..." -msgstr "" +msgstr "useimmat Apple Macit, monet kannettavat tietokoneet..." #, fuzzy msgid "Language" msgstr "Kieli" msgid "Compression algorithm" -msgstr "" +msgstr "Kompressioalgoritmi" #, fuzzy msgid "Only packages such as base, sudo, linux, linux-firmware, efibootmgr and optional profile packages are installed." -msgstr "Vain paketit, kuten base, base-devel, linux, linux-firmware, efibootmgr ja valinnaiset profiili-paketit, asennetaan." +msgstr "Vain paketit, kuten base, sudo, linux, linux-firmware, efibootmgr ja valinnaiset profiili-paketit ovat asennettu." #, fuzzy msgid "Select zram compression algorithm:" -msgstr "Valitse liitoskohta :" +msgstr "Valitse zram kompression algoritmi:" msgid "Use Network Manager (default backend)" -msgstr "" +msgstr "Käytä Network Manager (oletus backendi)" msgid "Use Network Manager (iwd backend)" -msgstr "" +msgstr "Käytä Network Manager (iwd backendi)" msgid "Firewall" -msgstr "" +msgstr "Palomuuri" #, fuzzy msgid "Additional fonts" -msgstr "Valinnaiset arkistot" +msgstr "Lisäfontit" #, fuzzy msgid "Select font packages to install" -msgstr "Valitse asennettava käyttöliittymä" +msgstr "Valitse asennettavat fonttipaketit" msgid "Unicode font coverage for most languages" -msgstr "" +msgstr "Unicode fontin kattavuus useimmille kielille" msgid "color emoji for browsers and apps" -msgstr "" +msgstr "väri emojit selaimille ja sovelluksille" msgid "Chinese, Japanese, Korean characters" -msgstr "" +msgstr "Kiinalaiset, japanilaiset, korealaiset kirjaimet" #, fuzzy msgid "Select audio configuration" -msgstr "Tallenna käyttäjän määritykset" +msgstr "Valitse äänen konfiguraatio" #, fuzzy msgid "Enter credentials file decryption password" @@ -2174,14 +2191,14 @@ msgstr "Salauksen salasana" #, fuzzy msgid "Enter root password" -msgstr "Anna salasana: " +msgstr "Anna juuren (root) salasana: " msgid "Select bootloader to install" -msgstr "" +msgstr "Valitse asennettava käynnistyslataaja" #, fuzzy msgid "Configuration preview" -msgstr "Asetukset" +msgstr "Konfiguraation esikatselu" #, fuzzy msgid "Enter a directory for the configuration(s) to be saved" @@ -2193,7 +2210,7 @@ msgstr "Salaustyyppi" #, fuzzy msgid "Select disks for the installation" -msgstr "Valitse hostname asennukselle: " +msgstr "Valitse levyasemat asennukselle" #, fuzzy msgid "Enter a mountpoint" @@ -2201,7 +2218,7 @@ msgstr "Valitse liitoskohta :" #, fuzzy, python-brace-format msgid "Enter a size (default: {}): " -msgstr "Anna loppu (oletus: {}): " +msgstr "Anna koko (oletus: {}): " #, fuzzy msgid "Enter subvolume name" @@ -2216,20 +2233,20 @@ msgid "Select a disk configuration" msgstr "Kiintolevyn määritys" msgid "Enter root mount directory" -msgstr "" +msgstr "Syötä juuren (root) liitoshakemisto" msgid "You will use whatever drive-setup is mounted at the specified directory" -msgstr "" +msgstr "Käytät mitä tahansa levykonfiguraatiota joka on liitetty valittuun hakemistoon" msgid "WARNING: Archinstall won't check the suitability of this setup" -msgstr "" +msgstr "VAROITUS: Archinstall ei tarkista konfiguraation soveltuvuutta" #, fuzzy msgid "Select main filesystem" -msgstr "Vaihda tiedostojärjestelmä" +msgstr "Vaihda päätiedostojärjestelmä" msgid "Enter a hostname" -msgstr "" +msgstr "Syötä isäntänimi (hostname)" #, fuzzy msgid "Select timezone" @@ -2244,22 +2261,22 @@ msgstr "" #, python-brace-format msgid "Value must be between 1 and {}" -msgstr "" +msgstr "Arvon on oltava yhden ja {} välillä" #, fuzzy msgid "Select which kernel(s) to install" -msgstr "Valitse asennettava käyttöliittymä" +msgstr "Valitse asennettavat ytimet (kernel)" #, fuzzy msgid "Enter a respository name" -msgstr "Pakettivarasto" +msgstr "Syötä pakettivaraston nimi" #, fuzzy msgid "Enter the repository url" -msgstr "Vaihda mukautettu peilipaikka" +msgstr "Syötä pakettivarason url" msgid "Enter server url" -msgstr "" +msgstr "Syötä palvelimen url" #, fuzzy msgid "Select mirror regions to be enabled" @@ -2271,92 +2288,106 @@ msgstr "Valitse, valinnaiset lisävarastot jotka otetaan käyttöön" #, fuzzy msgid "Select an interface" -msgstr "Poista liitäntä" +msgstr "Valitse liitäntä" #, fuzzy msgid "Choose network configuration" -msgstr "Ei verkkoasetuksia" +msgstr "Valitse verkkokonfiguraatio" + +msgid "Recommended: Network Manager for desktop, Manual for server" +msgstr "Suositeltu: Network Manager työpöydälle, manuaali palvelinta varten" + +msgid "Warning: no network configuration selected. Network will need to be set up manually on the installed system." +msgstr "Varoitus: verkkokonfiguraatiota ei löytynyt. Verkon on asetettava manuaalisesti asennetulla järjestelmällä." #, fuzzy msgid "No packages found" -msgstr "Määritetty {} liittymää" +msgstr "Paketteja ei löytynyt" #, fuzzy msgid "Select which greeter to install" -msgstr "Valitse asennettava käyttöliittymä" +msgstr "Valitse asennettava tervehtijä (greeter)" #, fuzzy msgid "Select a profile type" -msgstr "Valitut profiilit: " +msgstr "Valitse profiilityyppi" #, fuzzy msgid "Enter new password" -msgstr "Anna salasana: " +msgstr "Anna uusi salasana: " msgid "Enter a username" -msgstr "" +msgstr "Anna käyttäjänimi" #, fuzzy msgid "Enter a password" msgstr "Anna salasana: " msgid "The password did not match, please try again" -msgstr "" +msgstr "Salasanat eivät vastanneet, ole hyvä ja yritä uudelleen" msgid "Password strength: Weak" -msgstr "" +msgstr "Salasanan vahvuus on heikko" msgid "Password strength: Moderate" -msgstr "" +msgstr "Salasanan vahvuus on keskiverto" msgid "Password strength: Strong" -msgstr "" +msgstr "Salasanan vahvuus on vahva" msgid "The selected desktop profile requires a regular user to log in via the greeter" -msgstr "" +msgstr "Valittu työpöydän profiili vaati tavallisen käyttäjän kirjautumisen tervehtijällä" #, fuzzy, python-brace-format msgid "Environment type: {} {}" msgstr "Ympäristötyyppi: {}" msgid "Input cannot be empty" -msgstr "" +msgstr "Syöttö ei voi olla tyhjä" msgid "Recommended" -msgstr "" +msgstr "Suositeltu" msgid "Package" -msgstr "" +msgstr "Paketti" msgid "Curated selection of KDE Plasma packages" -msgstr "" +msgstr "Kuratoitu valikoima KDE Plasma paketeista" msgid "Dependencies" -msgstr "" +msgstr "Riippuvuudet" #, fuzzy msgid "Package group" -msgstr "Määritetty {} liittymää" +msgstr "Pakettiryhmä" msgid "Extensive KDE Plasma installation" -msgstr "" +msgstr "Laaja KDE Plasman asennus" msgid "Packages in group" -msgstr "" +msgstr "Paketit ryhmässä" msgid "Minimal KDE Plasma installation" -msgstr "" +msgstr "Minimaalinen KDE Plasma asennus" #, fuzzy msgid "Description" -msgstr "Levyn salaus" +msgstr "Kuvaus" #, fuzzy msgid "Select a flavor of KDE Plasma to install" -msgstr "Valitse asennettava käyttöliittymä" +msgstr "Valitse asennettavan KDE Plasman maku" msgid "Arial/Times/Courier replacement, Cyrillic support for Steam/games" -msgstr "" +msgstr "Arial/Times/Courier korvaus, kyrillisen kirjaimiston tuki Steam/pelejä varten" msgid "wide Unicode coverage, good fallback font" -msgstr "" +msgstr "laaja Unicode kattavuus, hyvä varafontti " + +#, python-brace-format +msgid "Setting up U2F login: {u2f_config.u2f_login_method.value}" +msgstr "Määritellään U2F kirjautuminen: {u2f_config.u2f_login_method.value}" + +#, python-brace-format +msgid "Default: {DEFAULT_ITER_TIME}ms, Recommended range: 1000-60000" +msgstr "Oletus: {DEFAULT_ITER_TIME}ms, suositeltu väli: 1000-60000" diff --git a/archinstall/locales/languages.json b/archinstall/locales/languages.json index 2dbcf3b4..22c9c9ec 100644 --- a/archinstall/locales/languages.json +++ b/archinstall/locales/languages.json @@ -42,7 +42,7 @@ {"abbr": "fo", "lang": "Faroese"}, {"abbr": "fa", "lang": "Persian"}, {"abbr": "fj", "lang": "Fijian"}, - {"abbr": "fi", "lang": "Finnish"}, + {"abbr": "fi", "lang": "Finnish", "translated_lang": "Suomi"}, {"abbr": "fr", "lang": "French", "translated_lang": "Français"}, {"abbr": "fy", "lang": "Western Frisian"}, {"abbr": "ff", "lang": "Fulah"}, From b936fa11e39faad818fc06a3e0a9e0d3d35b281e Mon Sep 17 00:00:00 2001 From: correctmost <134317971+correctmost@users.noreply.github.com> Date: Wed, 29 Apr 2026 19:22:45 -0400 Subject: [PATCH 25/61] Fix some inconsistent-return-statements Pylint warnings (#4503) --- archinstall/lib/authentication/authentication_handler.py | 2 +- archinstall/lib/installer.py | 2 +- archinstall/lib/output.py | 2 +- archinstall/tui/ui/components.py | 6 +++--- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/archinstall/lib/authentication/authentication_handler.py b/archinstall/lib/authentication/authentication_handler.py index 9a99ba02..3a3b8cd4 100644 --- a/archinstall/lib/authentication/authentication_handler.py +++ b/archinstall/lib/authentication/authentication_handler.py @@ -53,7 +53,7 @@ class AuthenticationHandler: def _add_u2f_entry(self, file: Path, entry: str) -> None: if not file.exists(): debug(f'File does not exist: {file}') - return None + return content = file.read_text().splitlines() diff --git a/archinstall/lib/installer.py b/archinstall/lib/installer.py index 7bbfbae5..44a10eb2 100644 --- a/archinstall/lib/installer.py +++ b/archinstall/lib/installer.py @@ -402,7 +402,7 @@ class Installer: def _mount_luks_partition(self, part_mod: PartitionModification, luks_handler: Luks2) -> None: if not luks_handler.mapper_dev: - return None + return if part_mod.fs_type == FilesystemType.BTRFS and part_mod.btrfs_subvols: # Only mount BTRFS subvolumes that have mountpoints specified diff --git a/archinstall/lib/output.py b/archinstall/lib/output.py index e01da3eb..bc4bb113 100644 --- a/archinstall/lib/output.py +++ b/archinstall/lib/output.py @@ -133,7 +133,7 @@ class Journald: try: import systemd.journal # type: ignore[import-not-found] except ModuleNotFoundError: - return None + return log_adapter = logging.getLogger('archinstall') log_fmt = logging.Formatter('[%(levelname)s]: %(message)s') diff --git a/archinstall/tui/ui/components.py b/archinstall/tui/ui/components.py index c51d35f9..da6dc66b 100644 --- a/archinstall/tui/ui/components.py +++ b/archinstall/tui/ui/components.py @@ -350,7 +350,7 @@ class OptionListScreen(BaseScreen[ValueT]): def _set_preview(self, item_id: str) -> None: if self._preview_location is None: - return None + return preview_widget = self.query_one('#preview_content', Label) item = self._group.find_by_id(item_id) @@ -530,7 +530,7 @@ class SelectListScreen(BaseScreen[ValueT]): selection_list = self.query_one(SelectionList) if not selection_list.has_focus or event.key != 'enter': - return None + return if len(self._selected_items) < 1: index = selection_list.highlighted @@ -743,7 +743,7 @@ class ConfirmationScreen(BaseScreen[ValueT]): if self._is_btn_focus(): item = self._group.focus_item if not item: - return None + return _ = self.dismiss(Result(ResultType.Selection, _item=item)) From dc7d9cf0a2c0c78eab7716ed25dabae9cacd3d8e Mon Sep 17 00:00:00 2001 From: Softer Date: Thu, 30 Apr 2026 02:23:08 +0300 Subject: [PATCH 26/61] Improve Ukrainian translation (#4502) --- archinstall/locales/uk/LC_MESSAGES/base.mo | Bin 83620 -> 87112 bytes archinstall/locales/uk/LC_MESSAGES/base.po | 62 +++++++++++++-------- 2 files changed, 39 insertions(+), 23 deletions(-) diff --git a/archinstall/locales/uk/LC_MESSAGES/base.mo b/archinstall/locales/uk/LC_MESSAGES/base.mo index 1cb241b953ad7bfa04c89f0cc971028c6c9b569b..7c0230e6ed8ccd09d2990c53220d643d0f3325d4 100644 GIT binary patch delta 17180 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 bAEm>xORTH2Xw++@Xb}|5%5M~HPA&FdBKRr@ delta 14616 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 Date: Thu, 30 Apr 2026 16:37:37 +0900 Subject: [PATCH 27/61] Update Japanese translation (#4504) --- archinstall/locales/ja/LC_MESSAGES/base.mo | Bin 73655 -> 73831 bytes archinstall/locales/ja/LC_MESSAGES/base.po | 10 +++++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/archinstall/locales/ja/LC_MESSAGES/base.mo b/archinstall/locales/ja/LC_MESSAGES/base.mo index 043a6f2b917c747b6be6e64d592ac7d5e9d89d70..96b625c6736d9cfbb9a41f4b467788c4be643657 100644 GIT binary patch delta 14785 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 A)Bpeg delta 14719 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 OXTrlqx#J(+uKYjUoeA0i diff --git a/archinstall/locales/ja/LC_MESSAGES/base.po b/archinstall/locales/ja/LC_MESSAGES/base.po index 9c568f86..1e0f52e1 100644 --- a/archinstall/locales/ja/LC_MESSAGES/base.po +++ b/archinstall/locales/ja/LC_MESSAGES/base.po @@ -2051,7 +2051,7 @@ msgid "Enter credentials file decryption password" msgstr "認証情報ファイルの復号パスワードを入力" msgid "Enter root password" -msgstr "ルートパスワードを入力" +msgstr "root パスワードを入力" msgid "Select bootloader to install" msgstr "インストールするブートローダーを選択" @@ -2222,3 +2222,11 @@ msgstr "U2F ログインの設定: {u2f_config.u2f_login_method.value}" #, python-brace-format msgid "Default: {DEFAULT_ITER_TIME}ms, Recommended range: 1000-60000" msgstr "デフォルト: {DEFAULT_ITER_TIME}ms, 推奨範囲: 1000-60000" + +#, python-brace-format +msgid "Setting up U2F login: {}" +msgstr "U2F ログインの設定: {}" + +#, python-brace-format +msgid "Default: {}ms, Recommended range: 1000-60000" +msgstr "デフォルト: {}ms, 推奨範囲: 1000-60000" From dd349540112456008643c706673fe6e87e0fc87b Mon Sep 17 00:00:00 2001 From: Softer Date: Sun, 3 May 2026 03:33:48 +0300 Subject: [PATCH 28/61] Remove dead Cutefish desktop profile (#4514) The cutefish package was never in the official Arch repos (AUR only) and the upstream project is abandoned. Installing this profile would always fail with a pacman "not found" error. --- .../default_profiles/desktops/cutefish.py | 26 ------------------- 1 file changed, 26 deletions(-) delete mode 100644 archinstall/default_profiles/desktops/cutefish.py diff --git a/archinstall/default_profiles/desktops/cutefish.py b/archinstall/default_profiles/desktops/cutefish.py deleted file mode 100644 index b014c8ca..00000000 --- a/archinstall/default_profiles/desktops/cutefish.py +++ /dev/null @@ -1,26 +0,0 @@ -from typing import override - -from archinstall.default_profiles.profile import DisplayServerType, GreeterType, Profile, ProfileType - - -class CutefishProfile(Profile): - def __init__(self) -> None: - super().__init__( - 'Cutefish', - ProfileType.DesktopEnv, - support_gfx_driver=True, - display_server=DisplayServerType.Xorg, - ) - - @property - @override - def packages(self) -> list[str]: - return [ - 'cutefish', - 'noto-fonts', - ] - - @property - @override - def default_greeter_type(self) -> GreeterType: - return GreeterType.Sddm From b18cc572163591ac13348dcfa83295ac16539274 Mon Sep 17 00:00:00 2001 From: Daniel Girtler Date: Mon, 4 May 2026 07:54:42 +1000 Subject: [PATCH 29/61] Pull `ui` module one level up (#4515) * Pull the UI module one level up * Update --- archinstall/default_profiles/desktop.py | 4 ++-- archinstall/default_profiles/desktops/hyprland.py | 4 ++-- archinstall/default_profiles/desktops/labwc.py | 4 ++-- archinstall/default_profiles/desktops/niri.py | 4 ++-- archinstall/default_profiles/desktops/plasma.py | 4 ++-- archinstall/default_profiles/desktops/sway.py | 4 ++-- archinstall/default_profiles/server.py | 4 ++-- archinstall/lib/applications/application_menu.py | 4 ++-- archinstall/lib/args.py | 2 +- archinstall/lib/authentication/authentication_menu.py | 4 ++-- archinstall/lib/bootloader/bootloader_menu.py | 4 ++-- archinstall/lib/configuration.py | 4 ++-- archinstall/lib/disk/disk_menu.py | 4 ++-- archinstall/lib/disk/encryption_menu.py | 4 ++-- archinstall/lib/disk/partitioning_menu.py | 4 ++-- archinstall/lib/disk/subvolume_menu.py | 2 +- archinstall/lib/general/general_menu.py | 4 ++-- archinstall/lib/general/system_menu.py | 4 ++-- archinstall/lib/global_menu.py | 4 ++-- archinstall/lib/locale/locale_menu.py | 4 ++-- archinstall/lib/menu/abstract_menu.py | 6 +++--- archinstall/lib/menu/helpers.py | 6 +++--- archinstall/lib/menu/list_manager.py | 4 ++-- archinstall/lib/menu/menu_helper.py | 2 +- archinstall/lib/menu/util.py | 4 ++-- archinstall/lib/mirror/mirror_menu.py | 4 ++-- archinstall/lib/network/network_menu.py | 4 ++-- archinstall/lib/network/wifi_handler.py | 6 +++--- archinstall/lib/packages/packages.py | 4 ++-- archinstall/lib/pacman/pacman_menu.py | 4 ++-- archinstall/lib/profile/profile_menu.py | 4 ++-- archinstall/lib/user/user_menu.py | 4 ++-- archinstall/main.py | 2 +- archinstall/scripts/guided.py | 2 +- archinstall/scripts/minimal.py | 2 +- archinstall/scripts/only_hd.py | 2 +- archinstall/tui/{ui => }/components.py | 4 ++-- archinstall/tui/{ui => }/menu_item.py | 0 archinstall/tui/{ui => }/result.py | 2 +- archinstall/tui/ui/__init__.py | 0 40 files changed, 71 insertions(+), 71 deletions(-) rename archinstall/tui/{ui => }/components.py (99%) rename archinstall/tui/{ui => }/menu_item.py (100%) rename archinstall/tui/{ui => }/result.py (97%) delete mode 100644 archinstall/tui/ui/__init__.py diff --git a/archinstall/default_profiles/desktop.py b/archinstall/default_profiles/desktop.py index 7ffe31a8..19681bc7 100644 --- a/archinstall/default_profiles/desktop.py +++ b/archinstall/default_profiles/desktop.py @@ -4,8 +4,8 @@ from archinstall.default_profiles.profile import DisplayServerType, GreeterType, from archinstall.lib.menu.helpers import Selection from archinstall.lib.output import info from archinstall.lib.profile.profiles_handler import profile_handler -from archinstall.tui.ui.menu_item import MenuItem, MenuItemGroup -from archinstall.tui.ui.result import ResultType +from archinstall.tui.menu_item import MenuItem, MenuItemGroup +from archinstall.tui.result import ResultType if TYPE_CHECKING: from archinstall.lib.installer import Installer diff --git a/archinstall/default_profiles/desktops/hyprland.py b/archinstall/default_profiles/desktops/hyprland.py index fa0bc581..533afce4 100644 --- a/archinstall/default_profiles/desktops/hyprland.py +++ b/archinstall/default_profiles/desktops/hyprland.py @@ -4,8 +4,8 @@ from archinstall.default_profiles.desktops import SeatAccess from archinstall.default_profiles.profile import CustomSetting, DisplayServerType, GreeterType, Profile, ProfileType from archinstall.lib.menu.helpers import Selection from archinstall.lib.translationhandler import tr -from archinstall.tui.ui.menu_item import MenuItem, MenuItemGroup -from archinstall.tui.ui.result import ResultType +from archinstall.tui.menu_item import MenuItem, MenuItemGroup +from archinstall.tui.result import ResultType class HyprlandProfile(Profile): diff --git a/archinstall/default_profiles/desktops/labwc.py b/archinstall/default_profiles/desktops/labwc.py index bf4a3297..49e8c437 100644 --- a/archinstall/default_profiles/desktops/labwc.py +++ b/archinstall/default_profiles/desktops/labwc.py @@ -4,8 +4,8 @@ from archinstall.default_profiles.desktops import SeatAccess from archinstall.default_profiles.profile import CustomSetting, DisplayServerType, GreeterType, Profile, ProfileType from archinstall.lib.menu.helpers import Selection from archinstall.lib.translationhandler import tr -from archinstall.tui.ui.menu_item import MenuItem, MenuItemGroup -from archinstall.tui.ui.result import ResultType +from archinstall.tui.menu_item import MenuItem, MenuItemGroup +from archinstall.tui.result import ResultType class LabwcProfile(Profile): diff --git a/archinstall/default_profiles/desktops/niri.py b/archinstall/default_profiles/desktops/niri.py index 347290fe..8bfda797 100644 --- a/archinstall/default_profiles/desktops/niri.py +++ b/archinstall/default_profiles/desktops/niri.py @@ -4,8 +4,8 @@ from archinstall.default_profiles.desktops import SeatAccess from archinstall.default_profiles.profile import CustomSetting, DisplayServerType, GreeterType, Profile, ProfileType from archinstall.lib.menu.helpers import Selection from archinstall.lib.translationhandler import tr -from archinstall.tui.ui.menu_item import MenuItem, MenuItemGroup -from archinstall.tui.ui.result import ResultType +from archinstall.tui.menu_item import MenuItem, MenuItemGroup +from archinstall.tui.result import ResultType class NiriProfile(Profile): diff --git a/archinstall/default_profiles/desktops/plasma.py b/archinstall/default_profiles/desktops/plasma.py index e90f8a27..66a5ae9a 100644 --- a/archinstall/default_profiles/desktops/plasma.py +++ b/archinstall/default_profiles/desktops/plasma.py @@ -5,8 +5,8 @@ from archinstall.default_profiles.profile import CustomSetting, DisplayServerTyp from archinstall.lib.menu.helpers import Selection from archinstall.lib.packages.packages import available_package, package_group_info from archinstall.lib.translationhandler import tr -from archinstall.tui.ui.menu_item import MenuItem, MenuItemGroup -from archinstall.tui.ui.result import ResultType +from archinstall.tui.menu_item import MenuItem, MenuItemGroup +from archinstall.tui.result import ResultType class PlasmaFlavor(StrEnum): diff --git a/archinstall/default_profiles/desktops/sway.py b/archinstall/default_profiles/desktops/sway.py index 5d222307..d137d03b 100644 --- a/archinstall/default_profiles/desktops/sway.py +++ b/archinstall/default_profiles/desktops/sway.py @@ -4,8 +4,8 @@ from archinstall.default_profiles.desktops import SeatAccess from archinstall.default_profiles.profile import CustomSetting, DisplayServerType, GreeterType, Profile, ProfileType from archinstall.lib.menu.helpers import Selection from archinstall.lib.translationhandler import tr -from archinstall.tui.ui.menu_item import MenuItem, MenuItemGroup -from archinstall.tui.ui.result import ResultType +from archinstall.tui.menu_item import MenuItem, MenuItemGroup +from archinstall.tui.result import ResultType class SwayProfile(Profile): diff --git a/archinstall/default_profiles/server.py b/archinstall/default_profiles/server.py index 6833731f..bdd3d079 100644 --- a/archinstall/default_profiles/server.py +++ b/archinstall/default_profiles/server.py @@ -4,8 +4,8 @@ from archinstall.default_profiles.profile import Profile, ProfileType, SelectRes from archinstall.lib.menu.helpers import Selection from archinstall.lib.output import info from archinstall.lib.profile.profiles_handler import profile_handler -from archinstall.tui.ui.menu_item import MenuItem, MenuItemGroup -from archinstall.tui.ui.result import ResultType +from archinstall.tui.menu_item import MenuItem, MenuItemGroup +from archinstall.tui.result import ResultType if TYPE_CHECKING: from archinstall.lib.installer import Installer diff --git a/archinstall/lib/applications/application_menu.py b/archinstall/lib/applications/application_menu.py index 990dcda7..99e48726 100644 --- a/archinstall/lib/applications/application_menu.py +++ b/archinstall/lib/applications/application_menu.py @@ -17,8 +17,8 @@ from archinstall.lib.models.application import ( PrintServiceConfiguration, ) from archinstall.lib.translationhandler import tr -from archinstall.tui.ui.menu_item import MenuItem, MenuItemGroup -from archinstall.tui.ui.result import ResultType +from archinstall.tui.menu_item import MenuItem, MenuItemGroup +from archinstall.tui.result import ResultType class ApplicationMenu(AbstractSubMenu[ApplicationConfiguration]): diff --git a/archinstall/lib/args.py b/archinstall/lib/args.py index 77545736..8d78f7e6 100644 --- a/archinstall/lib/args.py +++ b/archinstall/lib/args.py @@ -30,7 +30,7 @@ from archinstall.lib.output import debug, error, logger, warn from archinstall.lib.plugins import load_plugin from archinstall.lib.translationhandler import Language, tr, translation_handler from archinstall.lib.version import get_version -from archinstall.tui.ui.components import tui +from archinstall.tui.components import tui @p_dataclass diff --git a/archinstall/lib/authentication/authentication_menu.py b/archinstall/lib/authentication/authentication_menu.py index 6bead8ca..5453099f 100644 --- a/archinstall/lib/authentication/authentication_menu.py +++ b/archinstall/lib/authentication/authentication_menu.py @@ -9,8 +9,8 @@ from archinstall.lib.models.users import Password, User from archinstall.lib.output import FormattedOutput from archinstall.lib.translationhandler import tr from archinstall.lib.user.user_menu import select_users -from archinstall.tui.ui.menu_item import MenuItem, MenuItemGroup -from archinstall.tui.ui.result import ResultType +from archinstall.tui.menu_item import MenuItem, MenuItemGroup +from archinstall.tui.result import ResultType class AuthenticationMenu(AbstractSubMenu[AuthenticationConfiguration]): diff --git a/archinstall/lib/bootloader/bootloader_menu.py b/archinstall/lib/bootloader/bootloader_menu.py index 37eaeb8d..d32a5a21 100644 --- a/archinstall/lib/bootloader/bootloader_menu.py +++ b/archinstall/lib/bootloader/bootloader_menu.py @@ -5,8 +5,8 @@ from archinstall.lib.menu.abstract_menu import AbstractSubMenu from archinstall.lib.menu.helpers import Confirmation, Selection from archinstall.lib.models.bootloader import Bootloader, BootloaderConfiguration from archinstall.lib.translationhandler import tr -from archinstall.tui.ui.menu_item import MenuItem, MenuItemGroup -from archinstall.tui.ui.result import ResultType +from archinstall.tui.menu_item import MenuItem, MenuItemGroup +from archinstall.tui.result import ResultType class BootloaderMenu(AbstractSubMenu[BootloaderConfiguration]): diff --git a/archinstall/lib/configuration.py b/archinstall/lib/configuration.py index 3e9135bf..aba835d9 100644 --- a/archinstall/lib/configuration.py +++ b/archinstall/lib/configuration.py @@ -14,8 +14,8 @@ from archinstall.lib.models.bootloader import Bootloader from archinstall.lib.models.network import NetworkConfiguration from archinstall.lib.output import debug, logger, warn from archinstall.lib.translationhandler import tr -from archinstall.tui.ui.menu_item import MenuItem, MenuItemGroup -from archinstall.tui.ui.result import ResultType +from archinstall.tui.menu_item import MenuItem, MenuItemGroup +from archinstall.tui.result import ResultType class ConfigurationOutput: diff --git a/archinstall/lib/disk/disk_menu.py b/archinstall/lib/disk/disk_menu.py index 9743ee78..26aa145d 100644 --- a/archinstall/lib/disk/disk_menu.py +++ b/archinstall/lib/disk/disk_menu.py @@ -37,8 +37,8 @@ from archinstall.lib.models.device import ( ) from archinstall.lib.output import FormattedOutput, debug from archinstall.lib.translationhandler import tr -from archinstall.tui.ui.menu_item import MenuItem, MenuItemGroup -from archinstall.tui.ui.result import ResultType +from archinstall.tui.menu_item import MenuItem, MenuItemGroup +from archinstall.tui.result import ResultType @dataclass diff --git a/archinstall/lib/disk/encryption_menu.py b/archinstall/lib/disk/encryption_menu.py index d66ccf92..f5d4f095 100644 --- a/archinstall/lib/disk/encryption_menu.py +++ b/archinstall/lib/disk/encryption_menu.py @@ -19,8 +19,8 @@ from archinstall.lib.models.device import ( from archinstall.lib.models.users import Password from archinstall.lib.output import FormattedOutput from archinstall.lib.translationhandler import tr -from archinstall.tui.ui.menu_item import MenuItem, MenuItemGroup -from archinstall.tui.ui.result import ResultType +from archinstall.tui.menu_item import MenuItem, MenuItemGroup +from archinstall.tui.result import ResultType class DiskEncryptionMenu(AbstractSubMenu[DiskEncryption]): diff --git a/archinstall/lib/disk/partitioning_menu.py b/archinstall/lib/disk/partitioning_menu.py index 7e07d776..a524cf99 100644 --- a/archinstall/lib/disk/partitioning_menu.py +++ b/archinstall/lib/disk/partitioning_menu.py @@ -21,8 +21,8 @@ from archinstall.lib.models.device import ( ) from archinstall.lib.output import FormattedOutput from archinstall.lib.translationhandler import tr -from archinstall.tui.ui.menu_item import MenuItem, MenuItemGroup -from archinstall.tui.ui.result import ResultType +from archinstall.tui.menu_item import MenuItem, MenuItemGroup +from archinstall.tui.result import ResultType class FreeSpace: diff --git a/archinstall/lib/disk/subvolume_menu.py b/archinstall/lib/disk/subvolume_menu.py index 0f8da9e7..94854b3c 100644 --- a/archinstall/lib/disk/subvolume_menu.py +++ b/archinstall/lib/disk/subvolume_menu.py @@ -6,7 +6,7 @@ from archinstall.lib.menu.list_manager import ListManager from archinstall.lib.menu.util import prompt_dir from archinstall.lib.models.device import SubvolumeModification from archinstall.lib.translationhandler import tr -from archinstall.tui.ui.result import ResultType +from archinstall.tui.result import ResultType class SubvolumeMenu(ListManager[SubvolumeModification]): diff --git a/archinstall/lib/general/general_menu.py b/archinstall/lib/general/general_menu.py index 257b61e5..be5498d8 100644 --- a/archinstall/lib/general/general_menu.py +++ b/archinstall/lib/general/general_menu.py @@ -4,8 +4,8 @@ from archinstall.lib.locale.utils import list_timezones from archinstall.lib.menu.helpers import Confirmation, Input, Selection from archinstall.lib.output import warn from archinstall.lib.translationhandler import Language, tr -from archinstall.tui.ui.menu_item import MenuItem, MenuItemGroup -from archinstall.tui.ui.result import ResultType +from archinstall.tui.menu_item import MenuItem, MenuItemGroup +from archinstall.tui.result import ResultType class PostInstallationAction(Enum): diff --git a/archinstall/lib/general/system_menu.py b/archinstall/lib/general/system_menu.py index 7fa0d1f2..82dc4cdb 100644 --- a/archinstall/lib/general/system_menu.py +++ b/archinstall/lib/general/system_menu.py @@ -5,8 +5,8 @@ from archinstall.lib.menu.helpers import Confirmation, Selection from archinstall.lib.models.application import ZramAlgorithm, ZramConfiguration from archinstall.lib.models.package_types import DEFAULT_KERNEL, Kernel from archinstall.lib.translationhandler import tr -from archinstall.tui.ui.menu_item import MenuItem, MenuItemGroup -from archinstall.tui.ui.result import ResultType +from archinstall.tui.menu_item import MenuItem, MenuItemGroup +from archinstall.tui.result import ResultType async def select_kernel(preset: list[Kernel] = []) -> list[Kernel]: diff --git a/archinstall/lib/global_menu.py b/archinstall/lib/global_menu.py index cdd820a9..eac936bd 100644 --- a/archinstall/lib/global_menu.py +++ b/archinstall/lib/global_menu.py @@ -32,8 +32,8 @@ from archinstall.lib.packages.packages import list_available_packages, select_ad from archinstall.lib.pacman.config import PacmanConfig from archinstall.lib.pacman.pacman_menu import PacmanMenu from archinstall.lib.translationhandler import Language, tr, translation_handler -from archinstall.tui.ui.components import tui -from archinstall.tui.ui.menu_item import MenuItem, MenuItemGroup +from archinstall.tui.components import tui +from archinstall.tui.menu_item import MenuItem, MenuItemGroup class GlobalMenu(AbstractMenu[None]): diff --git a/archinstall/lib/locale/locale_menu.py b/archinstall/lib/locale/locale_menu.py index dc743fd3..add9fc2d 100644 --- a/archinstall/lib/locale/locale_menu.py +++ b/archinstall/lib/locale/locale_menu.py @@ -5,8 +5,8 @@ from archinstall.lib.menu.abstract_menu import AbstractSubMenu from archinstall.lib.menu.helpers import Selection from archinstall.lib.models.locale import LocaleConfiguration from archinstall.lib.translationhandler import tr -from archinstall.tui.ui.menu_item import MenuItem, MenuItemGroup -from archinstall.tui.ui.result import ResultType +from archinstall.tui.menu_item import MenuItem, MenuItemGroup +from archinstall.tui.result import ResultType class LocaleMenu(AbstractSubMenu[LocaleConfiguration]): diff --git a/archinstall/lib/menu/abstract_menu.py b/archinstall/lib/menu/abstract_menu.py index 8e9cbed7..590cabf1 100644 --- a/archinstall/lib/menu/abstract_menu.py +++ b/archinstall/lib/menu/abstract_menu.py @@ -5,9 +5,9 @@ from typing import Any, Self, override from archinstall.lib.menu.helpers import Selection from archinstall.lib.output import error from archinstall.lib.translationhandler import tr -from archinstall.tui.ui.components import InstanceRunnable -from archinstall.tui.ui.menu_item import MenuItem, MenuItemGroup -from archinstall.tui.ui.result import ResultType +from archinstall.tui.components import InstanceRunnable +from archinstall.tui.menu_item import MenuItem, MenuItemGroup +from archinstall.tui.result import ResultType CONFIG_KEY = '__config__' diff --git a/archinstall/lib/menu/helpers.py b/archinstall/lib/menu/helpers.py index 96361765..b512c956 100644 --- a/archinstall/lib/menu/helpers.py +++ b/archinstall/lib/menu/helpers.py @@ -4,9 +4,9 @@ from typing import Any, Literal, override from textual.validation import ValidationResult, Validator from archinstall.lib.translationhandler import tr -from archinstall.tui.ui.components import InputInfo, InputScreen, LoadingScreen, NotifyScreen, OptionListScreen, SelectListScreen, TableSelectionScreen -from archinstall.tui.ui.menu_item import MenuItemGroup -from archinstall.tui.ui.result import Result, ResultType +from archinstall.tui.components import InputInfo, InputScreen, LoadingScreen, NotifyScreen, OptionListScreen, SelectListScreen, TableSelectionScreen +from archinstall.tui.menu_item import MenuItemGroup +from archinstall.tui.result import Result, ResultType class Selection[ValueT]: diff --git a/archinstall/lib/menu/list_manager.py b/archinstall/lib/menu/list_manager.py index 54683f54..3b612162 100644 --- a/archinstall/lib/menu/list_manager.py +++ b/archinstall/lib/menu/list_manager.py @@ -4,8 +4,8 @@ from typing import cast from archinstall.lib.menu.helpers import Selection from archinstall.lib.menu.menu_helper import MenuHelper from archinstall.lib.translationhandler import tr -from archinstall.tui.ui.menu_item import MenuItem, MenuItemGroup -from archinstall.tui.ui.result import ResultType +from archinstall.tui.menu_item import MenuItem, MenuItemGroup +from archinstall.tui.result import ResultType class ListManager[ValueT]: diff --git a/archinstall/lib/menu/menu_helper.py b/archinstall/lib/menu/menu_helper.py index 47e04fb8..6ca3b953 100644 --- a/archinstall/lib/menu/menu_helper.py +++ b/archinstall/lib/menu/menu_helper.py @@ -1,5 +1,5 @@ from archinstall.lib.output import FormattedOutput -from archinstall.tui.ui.menu_item import MenuItem, MenuItemGroup +from archinstall.tui.menu_item import MenuItem, MenuItemGroup class MenuHelper[ValueT]: diff --git a/archinstall/lib/menu/util.py b/archinstall/lib/menu/util.py index ff3dea47..10edfd49 100644 --- a/archinstall/lib/menu/util.py +++ b/archinstall/lib/menu/util.py @@ -5,8 +5,8 @@ from pathlib import Path from archinstall.lib.menu.helpers import Confirmation, Input from archinstall.lib.models.users import Password, PasswordStrength from archinstall.lib.translationhandler import tr -from archinstall.tui.ui.components import InputInfo, InputInfoType, tui -from archinstall.tui.ui.result import ResultType +from archinstall.tui.components import InputInfo, InputInfoType, tui +from archinstall.tui.result import ResultType async def get_password( diff --git a/archinstall/lib/mirror/mirror_menu.py b/archinstall/lib/mirror/mirror_menu.py index 3e899cca..5158bffd 100644 --- a/archinstall/lib/mirror/mirror_menu.py +++ b/archinstall/lib/mirror/mirror_menu.py @@ -15,8 +15,8 @@ from archinstall.lib.models.mirrors import ( from archinstall.lib.models.packages import Repository from archinstall.lib.output import FormattedOutput from archinstall.lib.translationhandler import tr -from archinstall.tui.ui.menu_item import MenuItem, MenuItemGroup -from archinstall.tui.ui.result import ResultType +from archinstall.tui.menu_item import MenuItem, MenuItemGroup +from archinstall.tui.result import ResultType class CustomMirrorRepositoriesList(ListManager[CustomRepository]): diff --git a/archinstall/lib/network/network_menu.py b/archinstall/lib/network/network_menu.py index 2e95a82e..120af19e 100644 --- a/archinstall/lib/network/network_menu.py +++ b/archinstall/lib/network/network_menu.py @@ -6,8 +6,8 @@ from archinstall.lib.menu.list_manager import ListManager from archinstall.lib.models.network import NetworkConfiguration, Nic, NicType from archinstall.lib.networking import list_interfaces from archinstall.lib.translationhandler import tr -from archinstall.tui.ui.menu_item import MenuItem, MenuItemGroup -from archinstall.tui.ui.result import ResultType +from archinstall.tui.menu_item import MenuItem, MenuItemGroup +from archinstall.tui.result import ResultType class ManualNetworkConfig(ListManager[Nic]): diff --git a/archinstall/lib/network/wifi_handler.py b/archinstall/lib/network/wifi_handler.py index 0546310b..4c858601 100644 --- a/archinstall/lib/network/wifi_handler.py +++ b/archinstall/lib/network/wifi_handler.py @@ -9,9 +9,9 @@ from archinstall.lib.models.network import WifiConfiguredNetwork, WifiNetwork from archinstall.lib.network.wpa_supplicant import WpaSupplicantConfig from archinstall.lib.output import debug from archinstall.lib.translationhandler import tr -from archinstall.tui.ui.components import ConfirmationScreen, InputScreen, InstanceRunnable, LoadingScreen, NotifyScreen, TableSelectionScreen, tui -from archinstall.tui.ui.menu_item import MenuItem, MenuItemGroup -from archinstall.tui.ui.result import Result, ResultType +from archinstall.tui.components import ConfirmationScreen, InputScreen, InstanceRunnable, LoadingScreen, NotifyScreen, TableSelectionScreen, tui +from archinstall.tui.menu_item import MenuItem, MenuItemGroup +from archinstall.tui.result import Result, ResultType @dataclass diff --git a/archinstall/lib/packages/packages.py b/archinstall/lib/packages/packages.py index 3572c6c4..dcc4da9e 100644 --- a/archinstall/lib/packages/packages.py +++ b/archinstall/lib/packages/packages.py @@ -6,8 +6,8 @@ from archinstall.lib.models.packages import AvailablePackage, LocalPackage, Pack from archinstall.lib.output import debug from archinstall.lib.pacman.pacman import Pacman from archinstall.lib.translationhandler import tr -from archinstall.tui.ui.menu_item import MenuItem, MenuItemGroup -from archinstall.tui.ui.result import ResultType +from archinstall.tui.menu_item import MenuItem, MenuItemGroup +from archinstall.tui.result import ResultType def installed_package(package: str) -> LocalPackage | None: diff --git a/archinstall/lib/pacman/pacman_menu.py b/archinstall/lib/pacman/pacman_menu.py index 010c3f6d..16ad6d2b 100644 --- a/archinstall/lib/pacman/pacman_menu.py +++ b/archinstall/lib/pacman/pacman_menu.py @@ -5,8 +5,8 @@ from archinstall.lib.menu.helpers import Confirmation, Input from archinstall.lib.models.pacman import PacmanConfiguration from archinstall.lib.pathnames import PACMAN_CONF from archinstall.lib.translationhandler import tr -from archinstall.tui.ui.menu_item import MenuItem, MenuItemGroup -from archinstall.tui.ui.result import ResultType +from archinstall.tui.menu_item import MenuItem, MenuItemGroup +from archinstall.tui.result import ResultType class PacmanMenu(AbstractSubMenu[PacmanConfiguration]): diff --git a/archinstall/lib/profile/profile_menu.py b/archinstall/lib/profile/profile_menu.py index 0bdc9b0b..d3b35c82 100644 --- a/archinstall/lib/profile/profile_menu.py +++ b/archinstall/lib/profile/profile_menu.py @@ -7,8 +7,8 @@ from archinstall.lib.menu.abstract_menu import AbstractSubMenu from archinstall.lib.menu.helpers import Confirmation, Selection from archinstall.lib.models.profile import ProfileConfiguration from archinstall.lib.translationhandler import tr -from archinstall.tui.ui.menu_item import MenuItem, MenuItemGroup -from archinstall.tui.ui.result import ResultType +from archinstall.tui.menu_item import MenuItem, MenuItemGroup +from archinstall.tui.result import ResultType class ProfileMenu(AbstractSubMenu[ProfileConfiguration]): diff --git a/archinstall/lib/user/user_menu.py b/archinstall/lib/user/user_menu.py index e7d77aab..8e6e48fd 100644 --- a/archinstall/lib/user/user_menu.py +++ b/archinstall/lib/user/user_menu.py @@ -6,8 +6,8 @@ from archinstall.lib.menu.list_manager import ListManager from archinstall.lib.menu.util import get_password from archinstall.lib.models.users import User from archinstall.lib.translationhandler import tr -from archinstall.tui.ui.menu_item import MenuItem -from archinstall.tui.ui.result import ResultType +from archinstall.tui.menu_item import MenuItem +from archinstall.tui.result import ResultType class UserList(ListManager[User]): diff --git a/archinstall/main.py b/archinstall/main.py index cf0e42f6..505652a4 100644 --- a/archinstall/main.py +++ b/archinstall/main.py @@ -18,7 +18,7 @@ from archinstall.lib.packages.util import check_version_upgrade from archinstall.lib.pacman.pacman import Pacman from archinstall.lib.translationhandler import tr, translation_handler from archinstall.lib.utils.util import running_from_iso -from archinstall.tui.ui.components import tui +from archinstall.tui.components import tui def _log_sys_info() -> None: diff --git a/archinstall/scripts/guided.py b/archinstall/scripts/guided.py index 21228480..899683f2 100644 --- a/archinstall/scripts/guided.py +++ b/archinstall/scripts/guided.py @@ -22,7 +22,7 @@ from archinstall.lib.output import debug, error, info from archinstall.lib.packages.util import check_version_upgrade from archinstall.lib.profile.profiles_handler import profile_handler from archinstall.lib.translationhandler import tr -from archinstall.tui.ui.components import tui +from archinstall.tui.components import tui def show_menu( diff --git a/archinstall/scripts/minimal.py b/archinstall/scripts/minimal.py index 681d4266..ee4b49c7 100644 --- a/archinstall/scripts/minimal.py +++ b/archinstall/scripts/minimal.py @@ -12,7 +12,7 @@ from archinstall.lib.network.network_handler import install_network_config from archinstall.lib.output import debug, error, info from archinstall.lib.profile.profiles_handler import profile_handler from archinstall.lib.translationhandler import tr -from archinstall.tui.ui.components import tui +from archinstall.tui.components import tui def perform_installation(arch_config_handler: ArchConfigHandler) -> None: diff --git a/archinstall/scripts/only_hd.py b/archinstall/scripts/only_hd.py index afb1f919..5df72965 100644 --- a/archinstall/scripts/only_hd.py +++ b/archinstall/scripts/only_hd.py @@ -10,7 +10,7 @@ from archinstall.lib.installer import Installer from archinstall.lib.menu.util import delayed_warning from archinstall.lib.output import debug, error from archinstall.lib.translationhandler import tr -from archinstall.tui.ui.components import tui +from archinstall.tui.components import tui def show_menu(arch_config_handler: ArchConfigHandler) -> None: diff --git a/archinstall/tui/ui/components.py b/archinstall/tui/components.py similarity index 99% rename from archinstall/tui/ui/components.py rename to archinstall/tui/components.py index da6dc66b..f92364bb 100644 --- a/archinstall/tui/ui/components.py +++ b/archinstall/tui/components.py @@ -21,8 +21,8 @@ from textual.worker import WorkerCancelled from archinstall.lib.output import debug from archinstall.lib.translationhandler import tr -from archinstall.tui.ui.menu_item import MenuItem, MenuItemGroup -from archinstall.tui.ui.result import Result, ResultType +from archinstall.tui.menu_item import MenuItem, MenuItemGroup +from archinstall.tui.result import Result, ResultType ValueT = TypeVar('ValueT') diff --git a/archinstall/tui/ui/menu_item.py b/archinstall/tui/menu_item.py similarity index 100% rename from archinstall/tui/ui/menu_item.py rename to archinstall/tui/menu_item.py diff --git a/archinstall/tui/ui/result.py b/archinstall/tui/result.py similarity index 97% rename from archinstall/tui/ui/result.py rename to archinstall/tui/result.py index 6258bccf..a2232a4f 100644 --- a/archinstall/tui/ui/result.py +++ b/archinstall/tui/result.py @@ -2,7 +2,7 @@ from dataclasses import dataclass from enum import Enum, auto from typing import Self, cast -from archinstall.tui.ui.menu_item import MenuItem +from archinstall.tui.menu_item import MenuItem class ResultType(Enum): diff --git a/archinstall/tui/ui/__init__.py b/archinstall/tui/ui/__init__.py deleted file mode 100644 index e69de29b..00000000 From 528c27fbe1ce8223cdb5738bb9f310afcb2a737f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 4 May 2026 08:14:56 +1000 Subject: [PATCH 30/61] Update dependency arch/python-textual to v8.2.5 (#4517) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 1add6027..358c4ceb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,7 +21,7 @@ dependencies = [ "pyparted==3.13.0", "pydantic==2.12.5", "cryptography==47.0.0", - "textual==8.2.4", + "textual==8.2.5", "markdown-it-py==4.0.0", "linkify-it-py==2.1.0", ] From b5501d9507d824851318df36ed361097c18336d1 Mon Sep 17 00:00:00 2001 From: aronmr-1 Date: Sun, 3 May 2026 22:16:16 +0000 Subject: [PATCH 31/61] Improved Finnish translation further (#4516) --- archinstall/locales/fi/LC_MESSAGES/base.mo | Bin 51470 -> 65318 bytes archinstall/locales/fi/LC_MESSAGES/base.po | 685 ++++++++------------- 2 files changed, 270 insertions(+), 415 deletions(-) diff --git a/archinstall/locales/fi/LC_MESSAGES/base.mo b/archinstall/locales/fi/LC_MESSAGES/base.mo index 43b527d57e25ff1c6ff6397f4403d58f5721cdc1..287472f9243167a877f4f390b3564534ffe7bfd0 100644 GIT binary patch delta 25053 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 delta 14635 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 " -msgstr "[oletusarvo: 0] > " +msgstr "[Oletusarvo: 0] > " msgid "To be able to use this translation, please install a font manually that supports the language." -msgstr "Jotta voit käyttää tätä käännöstä, asenna käsin fontti, joka tukee tätä kieltä." +msgstr "Jotta voit käyttää tätä käännöstä, asenna kieltä tukeva fontti manuaalisesti" msgid "The font should be stored as {}" msgstr "Fontti tulee tallentaa {}" msgid "Archinstall requires root privileges to run. See --help for more." -msgstr "Archinstall vaatii toimiakseen root oikeudet. Katso --help ja saat lisätietoja." +msgstr "Archinstall vaatii rootin oikeudet toimiakseen. Katso --help ja saat lisätietoja." msgid "Select an execution mode" msgstr "Valitse suoritustila" @@ -873,7 +877,7 @@ msgstr "Profiilia ei voi hakea määritetystä URL-osoitteesta: {}" #, python-brace-format msgid "Profiles must have unique name, but profile definitions with duplicate name found: {}" -msgstr "Profiilin nimi on oltava yksilöllinen, mutta määrityksistä löytyy päällekkäinen nimi: {}" +msgstr "Profiilin nimi on oltava yksilöllinen, mutta profiilien määrityksistä löytyy päällekkäinen nimi: {}" msgid "Select one or more devices to use and configure" msgstr "Valitse yksi tai useampi laite määritettäväksi" @@ -888,18 +892,18 @@ msgid "Select a partitioning option" msgstr "Valitse osiointivaihtoehto" msgid "Enter the root directory of the mounted devices: " -msgstr "Anna root hahemisto liitetylle laitteelle: " +msgstr "Anna root hakemisto liitetylle laitteelle: " #, python-brace-format msgid "Minimum capacity for /home partition: {}GiB\n" -msgstr "Minimi tila /home osiolle: {}GiB\n" +msgstr "Minimi kapasiteetti /home osiolle: {}GiB\n" #, python-brace-format msgid "Minimum capacity for Arch Linux partition: {}GiB" msgstr "Minimi tila Arch Linux osiolle: {}GiB" msgid "This is a list of pre-programmed profiles_bck, they might make it easier to install things like desktop environments" -msgstr "Lista esiohjelmoiduista profiles_bck paketeista, joka voivat helpottaa asioita, kuten työpöydän asentamista" +msgstr "Tämä on lista esiohjelmoiduista profiles_bck instansseista, jotka voivat helpottaa työpöydäympäristön asentamista tai vastaavaa" msgid "Current profile selection" msgstr "Nykyinen profiilivalinta" @@ -997,7 +1001,7 @@ msgid "Partitions" msgstr "Osiot" msgid "No HSM devices available" -msgstr "Ei HSM-laitteita saatavilla" +msgstr "HSM-laitteita ei saatavilla" msgid "Partitions to be encrypted" msgstr "Salattavat osiot" @@ -1009,7 +1013,7 @@ msgid "Select a FIDO2 device to use for HSM" msgstr "Valitse HSM:ään käytettävä FIDO2-laite" msgid "Use a best-effort default partition layout" -msgstr "Käytä parasta mahdollista oletusosion asettelua" +msgstr "Käytä parhaan edellytyksen oletusosioinnin asettelua" msgid "Manual Partitioning" msgstr "Osiointi käsin" @@ -1046,14 +1050,14 @@ msgid "Back" msgstr "Takaisin" msgid "Please chose which greeter to install for the chosen profiles: {}" -msgstr "Valitse asennettava käyttöliittymä valituille profiileille: {}" +msgstr "Valitse asennettava tervehtijä valituille profiileille: {}" #, python-brace-format msgid "Environment type: {}" msgstr "Ympäristötyyppi: {}" msgid "The proprietary Nvidia driver is not supported by Sway. It is likely that you will run into issues, are you okay with that?" -msgstr "Sway ei tue patentoitua Nvidia-ohjainta. Todennäköistä, että kohtaat joitain ongelmia, kelpaako se silti sinulle?" +msgstr "Sway ei tue patentoitua Nvidia-ohjainta. On todennäköistä että kohtaat joitain ongelmia, kelpaako se silti sinulle?" msgid "Installed packages" msgstr "Asennetut paketit" @@ -1090,7 +1094,7 @@ msgid "" "Select a graphics driver or leave blank to install all open-source drivers" msgstr "" "\n" -"Valitse näytönohjaimen ohjain tai jätä tyhjäksi niin kaikki asennetaan avoimen lähdekoodin ohjaimilla" +"Valitse näytönohjaimen ajuri tai jätä tyhjäksi asentaaksesi ainoastaan kaikki avoimen lähdekoodin ajurit" msgid "Sway needs access to your seat (collection of hardware devices i.e. keyboard, mouse, etc)" msgstr "Sway tarvitsee pääsyn istuntoon (kokoelma laitteistoja, kuten näppäimistö, hiiri jne.)" @@ -1105,13 +1109,13 @@ msgstr "" "Valitse asetus antaaksesi Swaylle pääsyn laitteistoosi" msgid "Graphics driver" -msgstr "Näytönohjaimen ohjain" +msgstr "Näytönohjaimen ajuri" msgid "Greeter" -msgstr "Käyttöliittymä" +msgstr "Tervehtijä" msgid "Please chose which greeter to install" -msgstr "Valitse asennettava käyttöliittymä" +msgstr "Valitse asennettava tervehtijä" msgid "This is a list of pre-programmed default_profiles" msgstr "Tämä on lista esiohjelmoiduista default_profiles" @@ -1123,25 +1127,25 @@ msgid "Profiles" msgstr "Profiilit" msgid "Finding possible directories to save configuration files ..." -msgstr "Etsitään hakemistoja asetustiedostojen tallentamiseen ..." +msgstr "Etsitään mahdollisia hakemistoja asetustiedostojen tallentamiseen ..." msgid "Select directory (or directories) for saving configuration files" msgstr "Valitse hakemisto (tai hakemistot) asetustiedostojen tallentamiseen" msgid "Add a custom mirror" -msgstr "Lisää mukautettu peilipaikka" +msgstr "Lisää mukautettu peili" msgid "Change custom mirror" -msgstr "Vaihda mukautettu peilipaikka" +msgstr "Vaihda mukautettu peili" msgid "Delete custom mirror" -msgstr "Poista mukautettu peilipaikka" +msgstr "Poista mukautettu peili" msgid "Enter name (leave blank to skip): " -msgstr "Anna nimi (jätä tyhjäksi ja ohita): " +msgstr "Anna nimi (jätä tyhjäksi ohittaaksesi): " msgid "Enter url (leave blank to skip): " -msgstr "Anna url (jätä tyhjäksi ja ohita): " +msgstr "Anna url (jätä tyhjäksi ohittaaksesi): " msgid "Select signature check option" msgstr "Valitse allekirjoituksen tarkistusvaihtoehto" @@ -1150,19 +1154,19 @@ msgid "Select signature option" msgstr "Valitse allekirjoituksen asetus" msgid "Custom mirrors" -msgstr "Mukautetut peilipaikat" +msgstr "Mukautetut peilit" msgid "Defined" msgstr "Määritelty" msgid "Save user configuration (including disk layout)" -msgstr "Tallenna käyttäjän asetukset (myös levyasettelu)" +msgstr "Tallenna käyttäjän asetukset (sisältäen levyasettelun)" msgid "" "Enter a directory for the configuration(s) to be saved (tab completion enabled)\n" "Save directory: " msgstr "" -"Anna hakemisto tallennettaville määrityksille (välilehden viimeistely käytössä)\n" +"Anna hakemisto konfiguraation(oiden) tallentamista varten (tab-viimeistely käytössä)\n" "Tallennushakemisto: " msgid "" @@ -1170,30 +1174,30 @@ msgid "" "\n" "{}" msgstr "" -"Haluatko tallentaa {} asetustiedostoa seuraavaan paikkaan?\n" +"Haluatko tallentaa {} asennustiedostot seuraavaan paikkaan?\n" "\n" "{}" msgid "Saving {} configuration files to {}" -msgstr "Tallennetaan {} määritystiedostoa {}" +msgstr "Tallennetaan {} konfiguraatiotiedostot kohteeseen {}" msgid "Mirrors" -msgstr "Peilipaikat" +msgstr "Peilit" msgid "Mirror regions" -msgstr "Peilipaikan maa" +msgstr "Peilin maa" msgid " - Maximum value : {} ( Allows {} parallel downloads, allows {max_downloads+1} downloads at a time )" msgstr " - Suurin arvo : {} ( sallii {} rinnakkaista latausta, sallii {max_downloads+1} latausta kerralla )" msgid "Invalid input! Try again with a valid input [1 to {}, or 0 to disable]" -msgstr "Virheellinen arvo! Yritä uudelleen kelvollisella arvolla [1 - {}, tai 0 on poistettu]" +msgstr "Virheellinen arvo! Yritä uudelleen kelvollisella arvolla [1 - {}, 0 vastaa pois päältä]" msgid "Locales" msgstr "Alueet" msgid "Use NetworkManager (necessary to configure internet graphically in GNOME and KDE)" -msgstr "Käytä NetworkManager -ohjelmaa (Internetin määritys graafisesti GNOME ja KDE)" +msgstr "Käytä NetworkManager (Internetin määritys graafisesti GNOMEssa ja KDEssa)" msgid "Total: {} / {}" msgstr "Yhteensä: {} / {}" @@ -1202,7 +1206,7 @@ msgid "All entered values can be suffixed with a unit: B, KB, KiB, MB, MiB..." msgstr "Kaikki annetut arvot voidaan liittää käyttäen yksikköjä: B, KB, KiB, MB, MiB..." msgid "If no unit is provided, the value is interpreted as sectors" -msgstr "Jos yksikköä ei ole annettu, arvo tulkitaan sektoreiksi" +msgstr "Jos yksikköä ei ole annettu, arvo tulkitaan sektoreina" msgid "Enter start (default: sector {}): " msgstr "Anna alku (oletus: sektori {}): " @@ -1211,7 +1215,7 @@ msgid "Enter end (default: {}): " msgstr "Anna loppu (oletus: {}): " msgid "Unable to determine fido2 devices. Is libfido2 installed?" -msgstr "Fido2-laitteita ei voida määrittää. Onko libfido2 asennettu?" +msgstr "Fido2-laitteita ei voitu määrittää. Onko libfido2 asennettu?" msgid "Path" msgstr "Polku" @@ -1232,14 +1236,12 @@ msgstr "Valmis asentamaan" msgid "Disks" msgstr "Levyasemat" -#, fuzzy msgid "Packages" msgstr "Määritetty {} liittymää" msgid "Network" msgstr "Verkko" -#, fuzzy msgid "Locale" msgstr "Alueet" @@ -1283,68 +1285,65 @@ msgstr "" msgid "All entered values can be suffixed with a unit: %, B, KB, KiB, MB, MiB..." msgstr "Kaikki annetut arvot voidaan liittää käyttäen yksikköjä: B, KB, KiB, MB, MiB..." +# unsure msgid "Would you like to use unified kernel images?" -msgstr "Haluatko käyttää Unified Kernel Images, joka voidaan käynnistää suoraan UEFI-ohjelmistosta?" +msgstr "Haluatko käyttää unified kernel images?" +# unsure msgid "Unified kernel images" -msgstr "UKI Unified Kernel Images" +msgstr "Unified Kernel Images" msgid "Waiting for time sync (timedatectl show) to complete." -msgstr "Odotetaan ajan synkronoinnin (timedatectl show) päättymistä." +msgstr "Odotetaan ajan synkronointia (timedatectl show) päättymistä." msgid "Time syncronization not completing, while you wait - check the docs for workarounds: https://archinstall.readthedocs.io/" -msgstr "Ajan synkronointi ei ole valmis, kun odotat voit tarkistaa kiertotapoja: https://archinstall.readthedocs.io/" +msgstr "Ajan synkronointi ei ole valmis, odottaessasi voit tarkistaa kiertotapoja: https://archinstall.readthedocs.io/" msgid "Skipping waiting for automatic time sync (this can cause issues if time is out of sync during installation)" -msgstr "Autom. ajan synkronoinnin odottaminen ohitetaan (voi aiheuttaa ongelmia, jos aikaa ei ole synkronoitu asennuksen aikana)" +msgstr "Ohitetaan autom. ajan synkronoinnin (voi aiheuttaa ongelmia, jos aika ei ole synkronoitu asennuksen aikana)" msgid "Waiting for Arch Linux keyring sync (archlinux-keyring-wkd-sync) to complete." -msgstr "Odotetaan Arch Linux avainten synkronoinnin valmistumista (archlinux-keyring-wkd-sync)." +msgstr "Odotetaan Arch Linux avaimien synkronointia (archlinux-keyring-wkd-sync)." msgid "Selected profiles: " msgstr "Valitut profiilit: " msgid "Time synchronization not completing, while you wait - check the docs for workarounds: https://archinstall.readthedocs.io/" -msgstr "Ajan synkronointi ei ole valmis, kun odotat voit tarkistaa kiertotapoja: https://archinstall.readthedocs.io/" +msgstr "Ajan synkronointi ei ole valmistumassa, odottaessasi voit tarkistaa kiertotapoja: https://archinstall.readthedocs.io/" msgid "Mark/Unmark as nodatacow" -msgstr "Merkitse/poista nodatacow merkintä" +msgstr "Lisää/poista nodatacow merkintä" msgid "Would you like to use compression or disable CoW?" -msgstr "Haluatko käyttää pakkausta vai poistaa CoW:n käytöstä?" +msgstr "Haluatko käyttää kompressiota vai poistaa CoW:n käytöstä?" msgid "Use compression" -msgstr "Käytä pakkausta" +msgstr "Käytä kompressiota" msgid "Disable Copy-on-Write" msgstr "Poista kopiointi kirjoittamisen yhteydessä" -#, fuzzy msgid "Provides a selection of desktop environments and tiling window managers, e.g. GNOME, KDE Plasma, Sway" -msgstr "Tarjoaa valikoiman erilaisia työpöytiä ja ikkunointia esim. gnome, kde, sway" +msgstr "Tarjoaa valikoiman erilaisia työpöytäympäristöjä ja järjesteleviä ikkunaympäristöjä esim. gnome, kde, sway" -#, fuzzy, python-brace-format +#, python-brace-format msgid "Configuration type: {}" -msgstr "Asetukset" +msgstr "Konfiguraation tyyppi: {}" -#, fuzzy msgid "LVM configuration type" -msgstr "Ei määritystä" +msgstr "LVM konfiguraation tyyppi" msgid "LVM disk encryption with more than 2 partitions is currently not supported" msgstr "LVM levyaseman salaus enemmällä kuin kahdella osiolla ei ole tällä hetkellä tuettu" -#, fuzzy msgid "Use NetworkManager (necessary to configure internet graphically in GNOME and KDE Plasma)" -msgstr "Käytä NetworkManager -ohjelmaa (Internetin määritys graafisesti GNOME ja KDE)" +msgstr "Käytä NetworkManager (Internetin määritys graafisesti GNOME:ssa ja KDE Plasma:ssa)" -#, fuzzy msgid "Select a LVM option" -msgstr "Valitse aikavyöhyke" +msgstr "Valitse LVM optio" -#, fuzzy msgid "Partitioning" -msgstr "Osio" +msgstr "Osiointi" msgid "Logical Volume Management (LVM)" msgstr "Looginen taltiohallinta (engl. 'Logical Volume Management', LVM)" @@ -1355,115 +1354,93 @@ msgstr "Fyysiset taltiot" msgid "Volumes" msgstr "Taltiot" -#, fuzzy msgid "LVM volumes" -msgstr "Aseta alitaltiot" +msgstr "LVM taltiot" -#, fuzzy msgid "LVM volumes to be encrypted" -msgstr "Salattavat osiot" +msgstr "Salattavat LVM taltiot" -#, fuzzy msgid "Select which LVM volumes to encrypt" -msgstr "Valitse salattavat osiot" +msgstr "Valitse salattavat LVM taltiot" -#, fuzzy msgid "Default layout" -msgstr "Levyn asettelu" +msgstr "Oletusasettelu" -#, fuzzy msgid "No Encryption" -msgstr "Salaustyyppi" +msgstr "Ei salausta" msgid "LUKS" -msgstr "" +msgstr "LUKS" msgid "LVM on LUKS" -msgstr "" +msgstr "LVM LUKS:in päällä (LVM on LUKS)" msgid "LUKS on LVM" -msgstr "" +msgstr "LUKS LVM:än päällä (LUKS on LVM)" -#, fuzzy msgid "Yes" msgstr "Kyllä" msgid "No" msgstr "Ei" -#, fuzzy msgid "Archinstall help" -msgstr "Archinstall kieli" +msgstr "Archinstall apu" -#, fuzzy msgid " (default)" msgstr "(oletus)" msgid "Press Ctrl+h for help" msgstr "Paina Ctrl+h saadaksesi apua" -#, fuzzy msgid "Choose an option to give Sway access to your hardware" -msgstr "" -"\n" -"\n" -"Valitse asetus antaaksesi Swaylle pääsyn laitteistoosi" +msgstr "Valitse asetus antaaksesi Swaylle pääsyn laitteistoosi" msgid "Seat access" msgstr "Istunnon pääsy (seat)" -#, fuzzy msgid "Mountpoint" msgstr "Liitoskohta: " msgid "HSM" -msgstr "" +msgstr "HSM" -#, fuzzy msgid "Enter disk encryption password (leave blank for no encryption)" msgstr "Anna levyn salauksen salasana (jätä tyhjäksi, jos et salaa): " -#, fuzzy msgid "Disk encryption password" msgstr "Salauksen salasana" -#, fuzzy msgid "Partition - New" -msgstr "Osio" +msgstr "Osio - Uusi" -#, fuzzy msgid "Filesystem" -msgstr "Vaihda tiedostojärjestelmä" +msgstr "Tiedostojärjestelmä" msgid "Invalid size" msgstr "Epäkelpo koko" -#, fuzzy msgid "Start (default: sector {}): " -msgstr "Anna alku (oletus: sektori {}): " +msgstr "Alku (oletus: sektori {}): " -#, fuzzy msgid "End (default: {}): " -msgstr "Anna loppu (oletus: {}): " +msgstr "Loppu (oletus: {}): " -#, fuzzy msgid "Subvolume name" msgstr "Alitaltion nimi " -#, fuzzy +# unsure msgid "Disk configuration type" -msgstr "Kiintolevyn määritys" +msgstr "Levyn konfiguraatiotyyppi" msgid "Root mount directory" -msgstr "Juuren (Root) liittämishakemisto (mount directory)" +msgstr "Root:in liittämishakemisto (mount directory)" -#, fuzzy msgid "Select language" -msgstr "Kieli" +msgstr "Valitse kieli" -#, fuzzy msgid "Write additional packages to install (space separated, leave blank to skip)" -msgstr "Kirjoita asennettavat lisäpaketit (välilyönnillä erotettuna, ohita vaihe jättämällä tyhjäksi): " +msgstr "Kirjoita asennettavat lisäpaketit (välilyönnillä erotettuna, ohita jättämällä tyhjäksi): " msgid "Invalid download number" msgstr "Epäkelpo numero lataukseen" @@ -1471,21 +1448,19 @@ msgstr "Epäkelpo numero lataukseen" msgid "Number downloads" msgstr "Numero latauksia" -#, fuzzy msgid "The username you entered is invalid" msgstr "Antamasi käyttäjänimi ei kelpaa. Yritä uudelleen" -#, fuzzy msgid "Username" msgstr "Käyttäjänimi : " -#, fuzzy, python-brace-format +#, python-brace-format msgid "Should \"{}\" be a superuser (sudo)?\n" -msgstr "Pitäisikö \"{}\" olla superkäyttäjä (sudo)?" +msgstr "Pitäisikö \"{}\" olla superkäyttäjä (sudo)?\n" -#, fuzzy +# unsure msgid "Interfaces" -msgstr "Lisää liitäntä" +msgstr "Verkkoliitännät (interfaces)" msgid "You need to enter a valid IP in IP-config mode" msgstr "Sinun on annettava kelvollinen IP-osoite IP-määritystilassa" @@ -1494,121 +1469,97 @@ msgid "Modes" msgstr "Tilat" msgid "IP address" -msgstr "IP osoite" +msgstr "IP-osoite" -#, fuzzy msgid "Enter your gateway (router) IP address (leave blank for none)" -msgstr "Anna yhdyskäytävän (reitittimen) IP-osoite tai jätä tyhjäksi: " +msgstr "Anna yhdyskäytävän (reitittimen) IP-osoite (tai jätä tyhjäksi): " msgid "Gateway address" msgstr "Yhdyskäytävän osoite" -#, fuzzy msgid "Enter your DNS servers with space separated (leave blank for none)" msgstr "Anna DNS-osoitteet (välilyönti erottaa kaksi osoitetta toisistaan, tyhjä ei mitään): " -#, fuzzy msgid "DNS servers" -msgstr "Ei audiopalvelinta" +msgstr "DNS-palvelimet" -#, fuzzy msgid "Configure interfaces" -msgstr "Määritetty {} liittymää" +msgstr "Määritä laitteet" -#, fuzzy msgid "Kernel" -msgstr "Kernelit" +msgstr "Ydin" msgid "UEFI is not detected and some options are disabled" -msgstr "UEFIa ei tunnisteta ja jotkin asetukset on poistettu käytöstä" +msgstr "UEFIa ei tunnisteta ja jotkin asetukset ovat poistettu käytöstä" msgid "Info" msgstr "Informaatio" -#, fuzzy msgid "The proprietary Nvidia driver is not supported by Sway." -msgstr "Sway ei tue patentoitua Nvidia-ohjainta. Todennäköistä, että kohtaat joitain ongelmia, kelpaako se silti sinulle?" +msgstr "Sway ei tue patentoitua Nvidia-ohjainta." -#, fuzzy msgid "It is likely that you will run into issues, are you okay with that?" -msgstr "Sway ei tue patentoitua Nvidia-ohjainta. Todennäköistä, että kohtaat joitain ongelmia, kelpaako se silti sinulle?" +msgstr "On todennäköistä, että kohtaat joitain ongelmia, kelpaako se silti sinulle?" -#, fuzzy msgid "Main profile" msgstr "Pääprofiili" -#, fuzzy msgid "Confirm password" msgstr "Vahvista salasana" msgid "The confirmation password did not match, please try again" msgstr "Salasanat eivät vastannet, ole hyvä ja koita uudestaan" -#, fuzzy msgid "Not a valid directory" msgstr "Hakemisto ei ole kelvollinen: {}" -#, fuzzy msgid "Would you like to continue?" -msgstr "Haluatko käyttää BTRFS-pakkausta?" +msgstr "Haluatko jatkaa?" msgid "Directory" msgstr "Hakemisto" -#, fuzzy msgid "Enter a directory for the configuration(s) to be saved (tab completion enabled)" msgstr "" -"Anna hakemisto tallennettaville määrityksille (välilehden viimeistely käytössä)\n" +"Anna hakemisto konfiguraation(oiden) tallentamista varten (tab viimeistely käytössä)\n" "Tallennushakemisto: " -#, fuzzy, python-brace-format +#, python-brace-format msgid "Do you want to save the configuration file(s) to {}?" -msgstr "" -"Haluatko tallentaa {} asetustiedostoa seuraavaan paikkaan?\n" -"\n" -"{}" +msgstr "Haluatko tallentaa asetustiedoston(tot) kohteeseen {}?" msgid "Enabled" msgstr "Käytössä" msgid "Disabled" -msgstr "Pois Käytostä" +msgstr "Pois käytostä" -#, fuzzy msgid "Please submit this issue (and file) to https://github.com/archlinux/archinstall/issues" -msgstr " Lähetä tämä ongelma (ja tiedosto) osoitteeseen https://github.com/archlinux/archinstall/issues" +msgstr "Lähetä tämä ongelma (ja tiedosto) osoitteeseen https://github.com/archlinux/archinstall/issues" -#, fuzzy msgid "Mirror name" -msgstr "Pakettivarasto" +msgstr "Peilin nimi" msgid "Url" -msgstr "" +msgstr "Url" -#, fuzzy msgid "Select signature check" msgstr "Valitse allekirjoituksen tarkistusvaihtoehto" -#, fuzzy msgid "Select execution mode" msgstr "Valitse suoritustila" msgid "Press ? for help" msgstr "Paina ? saadaksesi apua" -#, fuzzy msgid "Choose an option to give Hyprland access to your hardware" -msgstr "" -"\n" -"\n" -"Valitse asetus antaaksesi Hyprlandille pääsyn laitteistoosi" +msgstr "Valitse asetus antaaksesi Hyprlandille pääsyn laitteistoosi" -#, fuzzy msgid "Additional repositories" msgstr "Valinnaiset arkistot" msgid "NTP" -msgstr "" +msgstr "NTP" msgid "Swap on zram" msgstr "Swap zram:illa (Swap on zram)" @@ -1616,32 +1567,30 @@ msgstr "Swap zram:illa (Swap on zram)" msgid "Name" msgstr "Nimi" -#, fuzzy msgid "Signature check" msgstr "Valitse allekirjoituksen tarkistusvaihtoehto" -#, fuzzy, python-brace-format +# unsure +#, python-brace-format msgid "Selected free space segment on device {}:" -msgstr "Laitteen nykyiset vapaat sektorit {}:" +msgstr "Valittiin vapaan tilan osa laitteella {}:" -#, fuzzy, python-brace-format +#, python-brace-format msgid "Size: {} / {}" -msgstr "Yhteensä: {} / {}" +msgstr "Koko: {} / {}" -#, fuzzy, python-brace-format +#, python-brace-format msgid "Size (default: {}): " -msgstr "Anna loppu (oletus: {}): " +msgstr "Koko (oletus: {}): " -#, fuzzy msgid "HSM device" -msgstr "Laite" +msgstr "HSM laite" msgid "Some packages could not be found in the repository" msgstr "Jotkut paketit eivät löytyneet repositoriosta" -#, fuzzy msgid "User" -msgstr "Käyttäjänimi : " +msgstr "Käyttäjä" msgid "The specified configuration will be applied" msgstr "Käytetään valittua konfiguraatiota" @@ -1649,85 +1598,68 @@ msgstr "Käytetään valittua konfiguraatiota" msgid "Wipe" msgstr "Pyyhi" -#, fuzzy msgid "Mark/Unmark as XBOOTLDR" -msgstr "Merkitse/poista käynnistyvä" +msgstr "Lisää/poista merkintö XBOOTLDRina" -#, fuzzy msgid "Loading packages..." -msgstr "Lisäpaketit" +msgstr "Ladataan paketteja..." msgid "Select any packages from the below list that should be installed additionally" msgstr "Valitse alla olevalta listalta asennettavat lisäpaketit" -#, fuzzy msgid "Add a custom repository" -msgstr "Lisää mukautettu peilipaikka" +msgstr "Lisää mukautettu repositorio" -#, fuzzy msgid "Change custom repository" -msgstr "Vaihda mukautettu peilipaikka" +msgstr "Vaihda mukautettu repositorio" -#, fuzzy msgid "Delete custom repository" -msgstr "Poista mukautettu peilipaikka" +msgstr "Poista mukautettu repositorio" -#, fuzzy msgid "Repository name" -msgstr "Pakettivarasto" +msgstr "Repositorion nimi" -#, fuzzy msgid "Add a custom server" -msgstr "Lisää mukautettu peilipaikka" +msgstr "Lisää mukautettu palvelin" -#, fuzzy msgid "Change custom server" -msgstr "Vaihda mukautettu peilipaikka" +msgstr "Vaihda mukautettu palvelin" -#, fuzzy msgid "Delete custom server" -msgstr "Poista mukautettu peilipaikka" +msgstr "Poista mukautettu palvelin" msgid "Server url" msgstr "Palvelimen url" -#, fuzzy msgid "Select regions" -msgstr "Valitse allekirjoituksen asetus" +msgstr "Valitse alueet" -#, fuzzy msgid "Add custom servers" -msgstr "Lisää mukautettu peilipaikka" +msgstr "Lisää mukautetut palvelimet" -#, fuzzy msgid "Add custom repository" -msgstr "Lisää mukautettu peilipaikka" +msgstr "Lisää mukautettu repositorio" -#, fuzzy msgid "Loading mirror regions..." -msgstr "Peilipaikan maa" +msgstr "Ladataan peilialueita..." -#, fuzzy msgid "Mirrors and repositories" -msgstr "Valinnaiset arkistot" +msgstr "Peilit ja repositoriot" -#, fuzzy msgid "Selected mirror regions" -msgstr "Peilipaikan maa" +msgstr "Valitut peilin aluuet" -#, fuzzy msgid "Custom servers" -msgstr "Mukautetut peilipaikat" +msgstr "Mukautetut palvelimet" -#, fuzzy msgid "Custom repositories" -msgstr "Valinnaiset arkistot" +msgstr "Mukautetut repositoriot" msgid "Only ASCII characters are supported" -msgstr "Ainoastaan ASCII hahmot tuetaan" +msgstr "Ainoastaan ASCII hahmot ovat tuetut" msgid "Show help" -msgstr "Näytä apua" +msgstr "Näytä apu" msgid "Exit help" msgstr "Poistu avusta" @@ -1739,10 +1671,10 @@ msgid "Preview scroll down" msgstr "Esikatsele alaspäin scrollaamista" msgid "Move up" -msgstr "Liikku ylös" +msgstr "Liiku ylös" msgid "Move down" -msgstr "Liikku alas" +msgstr "Liiku alas" msgid "Move right" msgstr "Liiku oikealle" @@ -1759,20 +1691,17 @@ msgstr "Hyppää valinnan yli (jos saatavilla)" msgid "Reset selection (if available)" msgstr "Nollaa valinta (jos saatavilla)" -#, fuzzy msgid "Select on single select" -msgstr "Valitse allekirjoituksen tarkistusvaihtoehto" +msgstr "Valinta yksittöisellä valinnalla" -#, fuzzy msgid "Select on multi select" -msgstr "Valitse aikavyöhyke" +msgstr "Valinta multivalinnalla" msgid "Reset" msgstr "Nollaa" -#, fuzzy msgid "Skip selection menu" -msgstr "Valitse suoritustila" +msgstr "Hyppää valintavalikon yli" msgid "Start search mode" msgstr "Aloita hakutila" @@ -1783,13 +1712,11 @@ msgstr "Poistu hakutilasta" msgid "General" msgstr "Yleinen" -#, fuzzy msgid "Navigation" -msgstr "Tallenna määritykset" +msgstr "Navigaatio" -#, fuzzy msgid "Selection" -msgstr "Valitse allekirjoituksen asetus" +msgstr "Valinta" msgid "Search" msgstr "Haku" @@ -1800,24 +1727,20 @@ msgstr "Alas" msgid "Up" msgstr "Ylös" -#, fuzzy msgid "Confirm" -msgstr "Vahvista ja poistu" +msgstr "Vahvista" -#, fuzzy msgid "Focus right" -msgstr "Liiku oikealle" +msgstr "Kohdista oikealle" -#, fuzzy msgid "Focus left" -msgstr "Liiku vasemmalle" +msgstr "Kohdista vasemmalle" msgid "Toggle" msgstr "Vaihda" -#, fuzzy msgid "Show/Hide help" -msgstr "Näytä apua" +msgstr "Näytä/piilota apu" msgid "Quit" msgstr "Poistu" @@ -1828,44 +1751,38 @@ msgstr "Ensimmäinen" msgid "Last" msgstr "Viimeinen" -#, fuzzy msgid "Select" -msgstr "Valitse allekirjoituksen asetus" +msgstr "Valitse" msgid "Page Up" -msgstr "Liiku ylös" +msgstr "Page up" msgid "Page Down" -msgstr "Liiku alas" +msgstr "Page down" msgid "Page up" -msgstr "Liiku ylös" +msgstr "Page up" -#, fuzzy msgid "Page down" -msgstr "Liiku alas" +msgstr "Page down" msgid "Page Left" -msgstr "Liiku vasemalle" +msgstr "Sivu vasemmalle" -#, fuzzy msgid "Page Right" -msgstr "Liiku oikealle" +msgstr "Sivu oikealle" msgid "Cursor up" msgstr "Kursori ylös" -#, fuzzy msgid "Cursor down" -msgstr "Liikku alas" +msgstr "Kursori alas" -#, fuzzy msgid "Cursor right" -msgstr "Liiku oikealle" +msgstr "Kursori oikealle" -#, fuzzy msgid "Cursor left" -msgstr "Liiku vasemmalle" +msgstr "Kursori vasemmalle" msgid "Top" msgstr "Yläosa" @@ -1874,15 +1791,13 @@ msgid "Bottom" msgstr "Pohja" msgid "Home" -msgstr "Rivin alku" +msgstr "Home" -#, fuzzy msgid "End" -msgstr "Rivin loppu" +msgstr "End" -#, fuzzy msgid "Toggle option" -msgstr "Alitaltion valinnat" +msgstr "Vaihda optio" msgid "Scroll Up" msgstr "Scrollaa ylös" @@ -1911,87 +1826,66 @@ msgstr "Kohdista edeltävä" msgid "Copy selected text" msgstr "Kopioi valittu teksti" -#, fuzzy msgid "labwc needs access to your seat (collection of hardware devices i.e. keyboard, mouse, etc)" -msgstr "Sway tarvitsee pääsyn istuntoon (kokoelma laitteistoja, kuten näppäimistö, hiiri jne.)" +msgstr "labwc tarvitsee pääsyn istuntoon (kokoelma laitteistoja, kuten näppäimistö, hiiri jne.)" -#, fuzzy msgid "Choose an option to give labwc access to your hardware" -msgstr "" -"\n" -"\n" -"Valitse asetus antaaksesi labwc:lle pääsyn laitteistoosi" +msgstr "Valitse asetus antaaksesi labwc:lle pääsyn laitteistoosi" -#, fuzzy msgid "niri needs access to your seat (collection of hardware devices i.e. keyboard, mouse, etc)" msgstr "niri tarvitsee pääsyn istuntoon (kokoelma laitteistoja, kuten näppäimistö, hiiri jne.)" -#, fuzzy msgid "Choose an option to give niri access to your hardware" -msgstr "" -"\n" -"\n" -"Valitse asetus antaaksesi nirin pääsyn laitteistoosi" +msgstr "Valitse asetus antaaksesi nirin pääsyn laitteistoosi" -#, fuzzy msgid "Mark/Unmark as ESP" msgstr "Merkitse/poista käynnistyvä" msgid "Package group:" msgstr "Pakettiryhmä:" -#, fuzzy msgid "Exit archinstall" msgstr "Poistu archinstall:ista" -#, fuzzy msgid "Reboot system" msgstr "Käynnistä järjestelmä uudelleen" -#, fuzzy msgid "chroot into installation for post-installation configurations" msgstr "Haluatko chroot:in uuteen asennukseen ja suorittaa asennuksen jälkeiset asetukset?" msgid "Installation completed" -msgstr "Lataus valmis" +msgstr "Asennus valmis" -#, fuzzy msgid "What would you like to do next?" msgstr "Mitä haluaisit tehdä seuraavaksi?" -#, fuzzy, python-brace-format +#, python-brace-format msgid "Select which mode to configure for \"{}\"" -msgstr "Valitse, mikä tila määritetään \"{}\", tai ohita ja käytä \"{}\" oletusta." +msgstr "Valitse tila jolle tehdä konfiguraatio \"{}\"" -#, fuzzy +# unsure msgid "Incorrect credentials file decryption password" -msgstr "Väärä salasana salausta varten" +msgstr "Väärä salasana pääsytietojen salausta varten" -#, fuzzy msgid "Incorrect password" msgstr "Väärä salasana" -#, fuzzy msgid "Credentials file decryption password" -msgstr "Salauksen salasana" +msgstr "Pääsytietotiedoston salauksen salasana" -#, fuzzy msgid "Do you want to encrypt the user_credentials.json file?" msgstr "Haluatko salata user_crendentials.json tiedoston?" -#, fuzzy msgid "Credentials file encryption password" -msgstr "Salauksen salasana" +msgstr "Pääsytietotiedoston salauksen salasana" -#, fuzzy, python-brace-format +#, python-brace-format msgid "Repositories: {}" -msgstr "Pakettivarasto" +msgstr "Repositoriot: {}" -#, fuzzy msgid "New version available" -msgstr "Ei HSM-laitteita saatavilla" +msgstr "Uusi versio saatavilla" -#, fuzzy msgid "Passwordless login" msgstr "Salasanaton kirjautuminen" @@ -1999,20 +1893,17 @@ msgid "Second factor login" msgstr "Toisen faktorin kirjautuminen" msgid "Bluetooth" -msgstr "" +msgstr "Bluetooth" -#, fuzzy msgid "Would you like to configure Bluetooth?" -msgstr "Haluatko käyttää BTRFS-pakkausta?" +msgstr "Haluatko konfiguroida Bluetooth:in?" msgid "Print service" msgstr "Printtauspalvelu" -#, fuzzy msgid "Would you like to configure the print service?" msgstr "Haluatko konfiguroida printtauspalvelun" -#, fuzzy msgid "Power management" msgstr "Tehohallinta" @@ -2041,7 +1932,7 @@ msgstr "Arvo ei voi olla tyhjä" msgid "Snapshot type" msgstr "Tilannekuvan tyyppi" -#, fuzzy, python-brace-format +#, python-brace-format msgid "Snapshot type: {}" msgstr "Tilannekuvan tyyppi: {}" @@ -2054,13 +1945,12 @@ msgstr "U2F laitteita ei löydetty" msgid "U2F Login Method" msgstr "U2F kirjautumisen menetelmä" -#, fuzzy msgid "Enable passwordless sudo?" msgstr "Aktivoi salasanaton sudo?" -#, fuzzy, python-brace-format +#, python-brace-format msgid "Setting up U2F device for user: {}" -msgstr "Valitse HSM:ään käytettävä FIDO2-laite" +msgstr "Määritetään U2F laitetta käyttäjälle: {}" msgid "You may need to enter the PIN and then touch your U2F device to register it" msgstr "Voit joutua syöttämään PIN:in ja koskettaa U2F laitettasi rekisteröidäksesi sen" @@ -2068,38 +1958,32 @@ msgstr "Voit joutua syöttämään PIN:in ja koskettaa U2F laitettasi rekisterö msgid "Starting device modifications in " msgstr "Aloitetaan laitteen muokkausta " -#, fuzzy msgid "No network connection found" -msgstr "Ei verkkoasetuksia" +msgstr "Verkkoyhteyttä ei löydetty" -#, fuzzy msgid "Would you like to connect to a Wifi?" msgstr "Haluaisitko liittyä langattomaan verkkoon?" -#, fuzzy msgid "No wifi interface found" msgstr "Langattomia liittymiä ei löydetty" -#, fuzzy msgid "Select wifi network to connect to" -msgstr "Valitse wifi verkko johon yhdistää" +msgstr "Valitse wifiverkko johon yhdistää" msgid "Scanning wifi networks..." msgstr "Etsitään wifiyhteyksiä..." -#, fuzzy msgid "No wifi networks found" -msgstr "Ei verkkoasetuksia" +msgstr "Wifiverkkoja ei löytynyt" msgid "Failed setting up wifi" msgstr "Langattoman verkon määrittäminen epäonnistui" -#, fuzzy msgid "Enter wifi password" -msgstr "Anna wifi salasana: " +msgstr "Anna wifi salasana" msgid "Ok" -msgstr "" +msgstr "Ok" msgid "Removable" msgstr "Irrotettava" @@ -2140,18 +2024,15 @@ msgstr "Firmware joka ei kunnolla tue NVRAM käynnistyskohteita kuten useimmat M msgid "most Apple Macs, many laptops..." msgstr "useimmat Apple Macit, monet kannettavat tietokoneet..." -#, fuzzy msgid "Language" msgstr "Kieli" msgid "Compression algorithm" msgstr "Kompressioalgoritmi" -#, fuzzy msgid "Only packages such as base, sudo, linux, linux-firmware, efibootmgr and optional profile packages are installed." msgstr "Vain paketit, kuten base, sudo, linux, linux-firmware, efibootmgr ja valinnaiset profiili-paketit ovat asennettu." -#, fuzzy msgid "Select zram compression algorithm:" msgstr "Valitse zram kompression algoritmi:" @@ -2164,11 +2045,9 @@ msgstr "Käytä Network Manager (iwd backendi)" msgid "Firewall" msgstr "Palomuuri" -#, fuzzy msgid "Additional fonts" msgstr "Lisäfontit" -#, fuzzy msgid "Select font packages to install" msgstr "Valitse asennettavat fonttipaketit" @@ -2181,59 +2060,48 @@ msgstr "väri emojit selaimille ja sovelluksille" msgid "Chinese, Japanese, Korean characters" msgstr "Kiinalaiset, japanilaiset, korealaiset kirjaimet" -#, fuzzy msgid "Select audio configuration" msgstr "Valitse äänen konfiguraatio" -#, fuzzy msgid "Enter credentials file decryption password" -msgstr "Salauksen salasana" +msgstr "Syötä pääsytietotiedoston salauksen salasana" -#, fuzzy msgid "Enter root password" -msgstr "Anna juuren (root) salasana: " +msgstr "Anna rootin salasana" msgid "Select bootloader to install" msgstr "Valitse asennettava käynnistyslataaja" -#, fuzzy msgid "Configuration preview" msgstr "Konfiguraation esikatselu" -#, fuzzy msgid "Enter a directory for the configuration(s) to be saved" msgstr "Anna hakemisto tallennettaville määrityksille: " -#, fuzzy msgid "Select encryption type" msgstr "Salaustyyppi" -#, fuzzy msgid "Select disks for the installation" msgstr "Valitse levyasemat asennukselle" -#, fuzzy msgid "Enter a mountpoint" msgstr "Valitse liitoskohta :" -#, fuzzy, python-brace-format +#, python-brace-format msgid "Enter a size (default: {}): " msgstr "Anna koko (oletus: {}): " -#, fuzzy msgid "Enter subvolume name" -msgstr "Alitaltion nimi " +msgstr "Anna alitaltion nimi " -#, fuzzy msgid "Enter subvolume mountpoint" -msgstr "Alitaltion liitoskohta" +msgstr "Anna alitaltion liitoskohta" -#, fuzzy msgid "Select a disk configuration" msgstr "Kiintolevyn määritys" msgid "Enter root mount directory" -msgstr "Syötä juuren (root) liitoshakemisto" +msgstr "Syötä juuren liitoshakemisto" msgid "You will use whatever drive-setup is mounted at the specified directory" msgstr "Käytät mitä tahansa levykonfiguraatiota joka on liitetty valittuun hakemistoon" @@ -2241,56 +2109,43 @@ msgstr "Käytät mitä tahansa levykonfiguraatiota joka on liitetty valittuun ha msgid "WARNING: Archinstall won't check the suitability of this setup" msgstr "VAROITUS: Archinstall ei tarkista konfiguraation soveltuvuutta" -#, fuzzy msgid "Select main filesystem" msgstr "Vaihda päätiedostojärjestelmä" msgid "Enter a hostname" msgstr "Syötä isäntänimi (hostname)" -#, fuzzy msgid "Select timezone" msgstr "Valitse aikavyöhyke" -#, fuzzy msgid "Enter the number of parallel downloads to be enabled" -msgstr "" -"Anna rinnakkaisten latausten määrä.\n" -"\n" -"Huomaa:\n" +msgstr "Anna rinnakkaisten latausten määrä" #, python-brace-format msgid "Value must be between 1 and {}" msgstr "Arvon on oltava yhden ja {} välillä" -#, fuzzy msgid "Select which kernel(s) to install" msgstr "Valitse asennettavat ytimet (kernel)" -#, fuzzy msgid "Enter a respository name" msgstr "Syötä pakettivaraston nimi" -#, fuzzy msgid "Enter the repository url" msgstr "Syötä pakettivarason url" msgid "Enter server url" msgstr "Syötä palvelimen url" -#, fuzzy msgid "Select mirror regions to be enabled" -msgstr "Peilipaikan maa" +msgstr "Valitse käytössä olevien peilien alueet" -#, fuzzy msgid "Select optional repositories to be enabled" -msgstr "Valitse, valinnaiset lisävarastot jotka otetaan käyttöön" +msgstr "Valitse valinnaiset repositoriot jotka otetaan käyttöön" -#, fuzzy msgid "Select an interface" msgstr "Valitse liitäntä" -#, fuzzy msgid "Choose network configuration" msgstr "Valitse verkkokonfiguraatio" @@ -2300,26 +2155,21 @@ msgstr "Suositeltu: Network Manager työpöydälle, manuaali palvelinta varten" msgid "Warning: no network configuration selected. Network will need to be set up manually on the installed system." msgstr "Varoitus: verkkokonfiguraatiota ei löytynyt. Verkon on asetettava manuaalisesti asennetulla järjestelmällä." -#, fuzzy msgid "No packages found" msgstr "Paketteja ei löytynyt" -#, fuzzy msgid "Select which greeter to install" msgstr "Valitse asennettava tervehtijä (greeter)" -#, fuzzy msgid "Select a profile type" msgstr "Valitse profiilityyppi" -#, fuzzy msgid "Enter new password" msgstr "Anna uusi salasana: " msgid "Enter a username" msgstr "Anna käyttäjänimi" -#, fuzzy msgid "Enter a password" msgstr "Anna salasana: " @@ -2338,7 +2188,7 @@ msgstr "Salasanan vahvuus on vahva" msgid "The selected desktop profile requires a regular user to log in via the greeter" msgstr "Valittu työpöydän profiili vaati tavallisen käyttäjän kirjautumisen tervehtijällä" -#, fuzzy, python-brace-format +#, python-brace-format msgid "Environment type: {} {}" msgstr "Ympäristötyyppi: {}" @@ -2352,12 +2202,11 @@ msgid "Package" msgstr "Paketti" msgid "Curated selection of KDE Plasma packages" -msgstr "Kuratoitu valikoima KDE Plasma paketeista" +msgstr "Kuratoitu valikoima KDE Plasma paketteja" msgid "Dependencies" msgstr "Riippuvuudet" -#, fuzzy msgid "Package group" msgstr "Pakettiryhmä" @@ -2370,11 +2219,9 @@ msgstr "Paketit ryhmässä" msgid "Minimal KDE Plasma installation" msgstr "Minimaalinen KDE Plasma asennus" -#, fuzzy msgid "Description" msgstr "Kuvaus" -#, fuzzy msgid "Select a flavor of KDE Plasma to install" msgstr "Valitse asennettavan KDE Plasman maku" @@ -2391,3 +2238,11 @@ msgstr "Määritellään U2F kirjautuminen: {u2f_config.u2f_login_method.value}" #, python-brace-format msgid "Default: {DEFAULT_ITER_TIME}ms, Recommended range: 1000-60000" msgstr "Oletus: {DEFAULT_ITER_TIME}ms, suositeltu väli: 1000-60000" + +#, python-brace-format +msgid "Setting up U2F login: {}" +msgstr "Määritetään U2F laitetta käyttäjälle: {}" + +#, python-brace-format +msgid "Default: {}ms, Recommended range: 1000-60000" +msgstr "Oletusarvo: 10000ms, suositeltu 1000-60000ms" From ba7dbeadfc69bebb493b0531909224be136c265a Mon Sep 17 00:00:00 2001 From: Daniel Girtler Date: Mon, 4 May 2026 14:45:43 +1000 Subject: [PATCH 32/61] Refactor profile seat access selection (#4457) * Refactor profile seat access selection * Update --- .../default_profiles/desktops/__init__.py | 6 ---- .../default_profiles/desktops/hyprland.py | 30 +++-------------- .../default_profiles/desktops/labwc.py | 30 +++-------------- archinstall/default_profiles/desktops/niri.py | 30 +++-------------- archinstall/default_profiles/desktops/sway.py | 30 +++-------------- .../default_profiles/desktops/utils.py | 33 +++++++++++++++++++ 6 files changed, 49 insertions(+), 110 deletions(-) create mode 100644 archinstall/default_profiles/desktops/utils.py diff --git a/archinstall/default_profiles/desktops/__init__.py b/archinstall/default_profiles/desktops/__init__.py index d91a0f3a..e69de29b 100644 --- a/archinstall/default_profiles/desktops/__init__.py +++ b/archinstall/default_profiles/desktops/__init__.py @@ -1,6 +0,0 @@ -from enum import Enum - - -class SeatAccess(Enum): - seatd = 'seatd' - polkit = 'polkit' diff --git a/archinstall/default_profiles/desktops/hyprland.py b/archinstall/default_profiles/desktops/hyprland.py index 533afce4..0bf46b96 100644 --- a/archinstall/default_profiles/desktops/hyprland.py +++ b/archinstall/default_profiles/desktops/hyprland.py @@ -1,11 +1,7 @@ from typing import override -from archinstall.default_profiles.desktops import SeatAccess +from archinstall.default_profiles.desktops.utils import select_seat_access from archinstall.default_profiles.profile import CustomSetting, DisplayServerType, GreeterType, Profile, ProfileType -from archinstall.lib.menu.helpers import Selection -from archinstall.lib.translationhandler import tr -from archinstall.tui.menu_item import MenuItem, MenuItemGroup -from archinstall.tui.result import ResultType class HyprlandProfile(Profile): @@ -49,26 +45,8 @@ class HyprlandProfile(Profile): return [pref] return [] - async def _select_seat_access(self) -> None: - # need to activate seat service and add to seat group - header = tr('Hyprland needs access to your seat (collection of hardware devices i.e. keyboard, mouse, etc)') - header += '\n' + tr('Choose an option to give Hyprland access to your hardware') + '\n' - - items = [MenuItem(s.value, value=s) for s in SeatAccess] - group = MenuItemGroup(items, sort_items=True) - - default = self.custom_settings.get(CustomSetting.SeatAccess, None) - group.set_default_by_value(default) - - result = await Selection[SeatAccess]( - group, - header=header, - allow_skip=False, - ).show() - - if result.type_ == ResultType.Selection: - self.custom_settings[CustomSetting.SeatAccess] = result.get_value().value - @override async def do_on_select(self) -> None: - await self._select_seat_access() + default = self.custom_settings.get(CustomSetting.SeatAccess, None) + seat_access = await select_seat_access(self.name, default) + self.custom_settings[CustomSetting.SeatAccess] = seat_access.value diff --git a/archinstall/default_profiles/desktops/labwc.py b/archinstall/default_profiles/desktops/labwc.py index 49e8c437..48fe344e 100644 --- a/archinstall/default_profiles/desktops/labwc.py +++ b/archinstall/default_profiles/desktops/labwc.py @@ -1,11 +1,7 @@ from typing import override -from archinstall.default_profiles.desktops import SeatAccess +from archinstall.default_profiles.desktops.utils import select_seat_access from archinstall.default_profiles.profile import CustomSetting, DisplayServerType, GreeterType, Profile, ProfileType -from archinstall.lib.menu.helpers import Selection -from archinstall.lib.translationhandler import tr -from archinstall.tui.menu_item import MenuItem, MenuItemGroup -from archinstall.tui.result import ResultType class LabwcProfile(Profile): @@ -43,26 +39,8 @@ class LabwcProfile(Profile): return [pref] return [] - async def _select_seat_access(self) -> None: - # need to activate seat service and add to seat group - header = tr('labwc needs access to your seat (collection of hardware devices i.e. keyboard, mouse, etc)') - header += '\n' + tr('Choose an option to give labwc access to your hardware') + '\n' - - items = [MenuItem(s.value, value=s) for s in SeatAccess] - group = MenuItemGroup(items, sort_items=True) - - default = self.custom_settings.get(CustomSetting.SeatAccess, None) - group.set_default_by_value(default) - - result = await Selection[SeatAccess]( - group, - header=header, - allow_skip=False, - ).show() - - if result.type_ == ResultType.Selection: - self.custom_settings[CustomSetting.SeatAccess] = result.get_value().value - @override async def do_on_select(self) -> None: - await self._select_seat_access() + default = self.custom_settings.get(CustomSetting.SeatAccess, None) + seat_access = await select_seat_access(self.name, default) + self.custom_settings[CustomSetting.SeatAccess] = seat_access.value diff --git a/archinstall/default_profiles/desktops/niri.py b/archinstall/default_profiles/desktops/niri.py index 8bfda797..4d955fbf 100644 --- a/archinstall/default_profiles/desktops/niri.py +++ b/archinstall/default_profiles/desktops/niri.py @@ -1,11 +1,7 @@ from typing import override -from archinstall.default_profiles.desktops import SeatAccess +from archinstall.default_profiles.desktops.utils import select_seat_access from archinstall.default_profiles.profile import CustomSetting, DisplayServerType, GreeterType, Profile, ProfileType -from archinstall.lib.menu.helpers import Selection -from archinstall.lib.translationhandler import tr -from archinstall.tui.menu_item import MenuItem, MenuItemGroup -from archinstall.tui.result import ResultType class NiriProfile(Profile): @@ -51,26 +47,8 @@ class NiriProfile(Profile): return [pref] return [] - async def _select_seat_access(self) -> None: - # need to activate seat service and add to seat group - header = tr('niri needs access to your seat (collection of hardware devices i.e. keyboard, mouse, etc)') - header += '\n' + tr('Choose an option to give niri access to your hardware') + '\n' - - items = [MenuItem(s.value, value=s) for s in SeatAccess] - group = MenuItemGroup(items, sort_items=True) - - default = self.custom_settings.get(CustomSetting.SeatAccess, None) - group.set_default_by_value(default) - - result = await Selection[SeatAccess]( - group, - header=header, - allow_skip=False, - ).show() - - if result.type_ == ResultType.Selection: - self.custom_settings[CustomSetting.SeatAccess] = result.get_value().value - @override async def do_on_select(self) -> None: - await self._select_seat_access() + default = self.custom_settings.get(CustomSetting.SeatAccess, None) + seat_access = await select_seat_access(self.name, default) + self.custom_settings[CustomSetting.SeatAccess] = seat_access.value diff --git a/archinstall/default_profiles/desktops/sway.py b/archinstall/default_profiles/desktops/sway.py index d137d03b..7a038dc0 100644 --- a/archinstall/default_profiles/desktops/sway.py +++ b/archinstall/default_profiles/desktops/sway.py @@ -1,11 +1,7 @@ from typing import override -from archinstall.default_profiles.desktops import SeatAccess +from archinstall.default_profiles.desktops.utils import select_seat_access from archinstall.default_profiles.profile import CustomSetting, DisplayServerType, GreeterType, Profile, ProfileType -from archinstall.lib.menu.helpers import Selection -from archinstall.lib.translationhandler import tr -from archinstall.tui.menu_item import MenuItem, MenuItemGroup -from archinstall.tui.result import ResultType class SwayProfile(Profile): @@ -53,26 +49,8 @@ class SwayProfile(Profile): return [pref] return [] - async def _select_seat_access(self) -> None: - # need to activate seat service and add to seat group - header = tr('Sway needs access to your seat (collection of hardware devices i.e. keyboard, mouse, etc)') - header += '\n' + tr('Choose an option to give Sway access to your hardware') + '\n' - - items = [MenuItem(s.value, value=s) for s in SeatAccess] - group = MenuItemGroup(items, sort_items=True) - - default = self.custom_settings.get(CustomSetting.SeatAccess, None) - group.set_default_by_value(default) - - result = await Selection[SeatAccess]( - group, - header=header, - allow_skip=False, - ).show() - - if result.type_ == ResultType.Selection: - self.custom_settings[CustomSetting.SeatAccess] = result.get_value().value - @override async def do_on_select(self) -> None: - await self._select_seat_access() + default = self.custom_settings.get(CustomSetting.SeatAccess, None) + seat_access = await select_seat_access(self.name, default) + self.custom_settings[CustomSetting.SeatAccess] = seat_access.value diff --git a/archinstall/default_profiles/desktops/utils.py b/archinstall/default_profiles/desktops/utils.py new file mode 100644 index 00000000..03e85aa5 --- /dev/null +++ b/archinstall/default_profiles/desktops/utils.py @@ -0,0 +1,33 @@ +from enum import Enum + +from archinstall.lib.menu.helpers import Selection +from archinstall.lib.translationhandler import tr +from archinstall.tui.menu_item import MenuItem, MenuItemGroup +from archinstall.tui.result import ResultType + + +class SeatAccess(Enum): + seatd = 'seatd' + polkit = 'polkit' + + +async def select_seat_access(profile_name: str, default: str | None) -> SeatAccess: + header = tr('{} needs access to your seat').format(profile_name) + header += f' ({tr("collection of hardware devices i.e. keyboard, mouse")})' + '\n' + header += tr('Choose an option how to give {} access to your hardware').format(profile_name) + + items = [MenuItem(s.value, value=s) for s in SeatAccess] + group = MenuItemGroup(items, sort_items=True) + + group.set_default_by_value(default) + + result = await Selection[SeatAccess]( + group, + header=header, + allow_skip=False, + ).show() + + if result.type_ == ResultType.Selection: + return result.get_value() + else: + raise ValueError('Unexpected result type from seat access selection') From 11607851c481935b256b792aafac7e82c1b27179 Mon Sep 17 00:00:00 2001 From: Softer Date: Wed, 6 May 2026 07:26:59 +0300 Subject: [PATCH 33/61] Keep standalone initramfs for grub-btrfs when UKI is enabled (#4513) * Keep standalone initramfs for grub-btrfs when UKI is enabled Fixes #4505 * Move keep_initramfs logic into add_bootloader() The grub-btrfs snapshot detection was in guided.py, forcing custom script authors to replicate it. Since Installer already holds disk_config, the check belongs inside add_bootloader(). --- archinstall/lib/installer.py | 24 ++++++++++++++++++------ archinstall/scripts/guided.py | 6 +++++- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/archinstall/lib/installer.py b/archinstall/lib/installer.py index 44a10eb2..801eaee2 100644 --- a/archinstall/lib/installer.py +++ b/archinstall/lib/installer.py @@ -1754,6 +1754,7 @@ class Installer: self, root: PartitionModification | LvmVolume, efi_partition: PartitionModification | None, + keep_initramfs: bool = False, ) -> None: if not efi_partition or not efi_partition.mountpoint: raise ValueError(f'Could not detect ESP at mountpoint {self.target}') @@ -1777,11 +1778,11 @@ class Installer: config = preset.read_text().splitlines(True) for index, line in enumerate(config): - # Avoid storing redundant image file if m := image_re.match(line): - image = self.target / m.group(2) - image.unlink(missing_ok=True) - config[index] = '#' + m.group(1) + if not keep_initramfs: + image = self.target / m.group(2) + image.unlink(missing_ok=True) + config[index] = '#' + m.group(1) elif m := uki_re.match(line): if diff_mountpoint: config[index] = m.group(2) + diff_mountpoint + m.group(3) @@ -1800,7 +1801,12 @@ class Installer: if not self.mkinitcpio(['-P']): error('Error generating initramfs (continuing anyway)') - def add_bootloader(self, bootloader: Bootloader, uki_enabled: bool = False, bootloader_removable: bool = False) -> None: + def add_bootloader( + self, + bootloader: Bootloader, + uki_enabled: bool = False, + bootloader_removable: bool = False, + ) -> None: """ Adds a bootloader to the installation instance. Archinstall supports one of five types: @@ -1849,7 +1855,13 @@ class Installer: bootloader_removable = False if uki_enabled: - self._config_uki(root, efi_partition) + keep_initramfs = ( + bootloader == Bootloader.Grub + and self._disk_config.has_default_btrfs_vols() + and self._disk_config.btrfs_options is not None + and self._disk_config.btrfs_options.snapshot_config is not None + ) + self._config_uki(root, efi_partition, keep_initramfs) match bootloader: case Bootloader.Systemd: diff --git a/archinstall/scripts/guided.py b/archinstall/scripts/guided.py index 899683f2..aab02cfd 100644 --- a/archinstall/scripts/guided.py +++ b/archinstall/scripts/guided.py @@ -115,7 +115,11 @@ def perform_installation( installation.setup_swap(algo=config.swap.algorithm) if config.bootloader_config and config.bootloader_config.bootloader != Bootloader.NO_BOOTLOADER: - installation.add_bootloader(config.bootloader_config.bootloader, config.bootloader_config.uki, config.bootloader_config.removable) + installation.add_bootloader( + config.bootloader_config.bootloader, + config.bootloader_config.uki, + config.bootloader_config.removable, + ) if config.network_config: install_network_config( From dcc38fe9aa886241fe0b6c069f417d154af20d80 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 6 May 2026 15:25:45 +1000 Subject: [PATCH 34/61] Update dependency arch/python-cryptography to v48 (#4520) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 358c4ceb..28e91c2a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,7 +20,7 @@ classifiers = [ dependencies = [ "pyparted==3.13.0", "pydantic==2.12.5", - "cryptography==47.0.0", + "cryptography==48.0.0", "textual==8.2.5", "markdown-it-py==4.0.0", "linkify-it-py==2.1.0", From 7b5dddf34bfcf3c4101e89cd18d53750e4aa2a38 Mon Sep 17 00:00:00 2001 From: Softer Date: Thu, 7 May 2026 03:17:13 +0300 Subject: [PATCH 35/61] Fix truncated package metadata in additional packages preview (#3580) (#4510) * Fix truncated package metadata in additional packages preview (#3580) * Simplify package info fix: use rstrip and CSS wrap instead of env var plumbing * Apply preview text wrap conditionally via wrap_preview parameter * Add missing wrap-preview CSS to OptionListScreen and pass parameter through SelectMenu --- archinstall/lib/menu/helpers.py | 4 ++++ archinstall/lib/models/packages.py | 1 - archinstall/lib/packages/packages.py | 7 ++++--- archinstall/tui/components.py | 24 ++++++++++++++++++++++-- 4 files changed, 30 insertions(+), 6 deletions(-) diff --git a/archinstall/lib/menu/helpers.py b/archinstall/lib/menu/helpers.py index b512c956..947f3c55 100644 --- a/archinstall/lib/menu/helpers.py +++ b/archinstall/lib/menu/helpers.py @@ -20,6 +20,7 @@ class Selection[ValueT]: preview_location: Literal['right', 'bottom'] | None = None, multi: bool = False, enable_filter: bool = False, + wrap_preview: bool = False, ): self._header = header self._title = title @@ -29,6 +30,7 @@ class Selection[ValueT]: self._preview_location = preview_location self._multi = multi self._enable_filter = enable_filter + self._wrap_preview = wrap_preview async def show(self) -> Result[ValueT]: if self._multi: @@ -39,6 +41,7 @@ class Selection[ValueT]: allow_reset=self._allow_reset, preview_location=self._preview_location, enable_filter=self._enable_filter, + wrap_preview=self._wrap_preview, ).run() else: result = await OptionListScreen[ValueT]( @@ -49,6 +52,7 @@ class Selection[ValueT]: allow_reset=self._allow_reset, preview_location=self._preview_location, enable_filter=self._enable_filter, + wrap_preview=self._wrap_preview, ).run() if result.type_ == ResultType.Reset: diff --git a/archinstall/lib/models/packages.py b/archinstall/lib/models/packages.py index 6ba6e134..205d653e 100644 --- a/archinstall/lib/models/packages.py +++ b/archinstall/lib/models/packages.py @@ -132,7 +132,6 @@ class AvailablePackage(BaseModel): def longest_key(self) -> int: return max(len(key) for key in self.model_dump().keys()) - # return all package info line by line def info(self) -> str: output = '' for key, value in self.model_dump().items(): diff --git a/archinstall/lib/packages/packages.py b/archinstall/lib/packages/packages.py index dcc4da9e..a7422986 100644 --- a/archinstall/lib/packages/packages.py +++ b/archinstall/lib/packages/packages.py @@ -14,7 +14,7 @@ def installed_package(package: str) -> LocalPackage | None: try: package_info = [] for line in Pacman.run(f'-Q --info {package}'): - package_info.append(line.decode().strip()) + package_info.append(line.decode().rstrip()) return _parse_package_output(package_info, LocalPackage) except SysCallError: @@ -53,7 +53,7 @@ def available_package(package: str) -> AvailablePackage | None: try: package_info: list[str] = [] for line in Pacman.run(f'-S --info {package}'): - package_info.append(line.decode().strip()) + package_info.append(line.decode().rstrip()) return _parse_package_output(package_info, AvailablePackage) except SysCallError: @@ -79,7 +79,7 @@ def list_available_packages( debug(f'Failed to sync Arch Linux package database: {e}') for line in Pacman.run('-S --info'): - dec_line = line.decode().strip() + dec_line = line.decode().rstrip() current_package.append(dec_line) if dec_line.startswith('Validated'): @@ -187,6 +187,7 @@ async def select_additional_packages( multi=True, preview_location='right', enable_filter=True, + wrap_preview=True, ).show() match pck_result.type_: diff --git a/archinstall/tui/components.py b/archinstall/tui/components.py index f92364bb..f732c21f 100644 --- a/archinstall/tui/components.py +++ b/archinstall/tui/components.py @@ -200,6 +200,11 @@ class OptionListScreen(BaseScreen[ValueT]): color: white; text-style: bold; } + + .wrap-preview { + width: 100%; + height: auto; + } """ def __init__( @@ -211,6 +216,7 @@ class OptionListScreen(BaseScreen[ValueT]): allow_reset: bool = False, preview_location: Literal['right', 'bottom'] | None = None, enable_filter: bool = False, + wrap_preview: bool = False, ): super().__init__(allow_skip, allow_reset) self._group = group @@ -218,6 +224,7 @@ class OptionListScreen(BaseScreen[ValueT]): self._title = title self._preview_location = preview_location self._filter = enable_filter + self._wrap_preview = wrap_preview self._show_frame = False self._options = self._get_options() @@ -280,7 +287,10 @@ class OptionListScreen(BaseScreen[ValueT]): with Container(): yield option_list yield Rule(orientation=rule_orientation) - yield ScrollableContainer(Label('', id='preview_content', markup=False)) + preview_label = Label('', id='preview_content', markup=False) + if self._wrap_preview: + preview_label.add_class('wrap-preview') + yield ScrollableContainer(preview_label) if self._filter: yield Input(placeholder='/filter', id='filter-input') @@ -433,6 +443,11 @@ class SelectListScreen(BaseScreen[ValueT]): color: white; text-style: bold; } + + .wrap-preview { + width: 100%; + height: auto; + } """ def __init__( @@ -443,6 +458,7 @@ class SelectListScreen(BaseScreen[ValueT]): allow_reset: bool = False, preview_location: Literal['right', 'bottom'] | None = None, enable_filter: bool = False, + wrap_preview: bool = False, ): super().__init__(allow_skip, allow_reset) self._group = group @@ -450,6 +466,7 @@ class SelectListScreen(BaseScreen[ValueT]): self._preview_location = preview_location self._show_frame = False self._filter = enable_filter + self._wrap_preview = wrap_preview self._selected_items: list[MenuItem] = self._group.selected_items self._options: list[Selection[MenuItem]] = self._get_selections() @@ -510,7 +527,10 @@ class SelectListScreen(BaseScreen[ValueT]): with Container(): yield selection_list yield Rule(orientation=rule_orientation) - yield ScrollableContainer(Label('', id='preview_content', markup=False)) + preview_label = Label('', id='preview_content', markup=False) + if self._wrap_preview: + preview_label.add_class('wrap-preview') + yield ScrollableContainer(preview_label) if self._filter: yield Input(placeholder='/filter', id='filter-input') From ef1cfe7b56a962ccccb4e14646f0767f490a7c2b Mon Sep 17 00:00:00 2001 From: Softer Date: Thu, 7 May 2026 03:18:56 +0300 Subject: [PATCH 36/61] Add share-log subcommand to upload install.log to paste.rs (#4511) * Add --share-log flag to upload install.log to paste.rs * Apply ruff-format to share_log.py * Rework share-log per review: subcommand, TUI confirmation, truncate large logs * Replace curl/SysCommand with urllib.request, remove defensive try/except Per review: use stdlib urllib instead of shelling out to curl, drop unnecessary try/except around TUI confirmation, remove tempfile (content is passed directly as bytes). * Fix TUI imports after ui module was pulled one level up (#4515) * Decouple share_install_log from TUI module * Add unit tests for share_install_log function * Update docs to use share-log subcommand syntax --- .github/ISSUE_TEMPLATE/01_bug.yml | 4 +- README.md | 4 +- archinstall/lib/args.py | 1 - archinstall/lib/output.py | 50 ++++++++++++++++ archinstall/main.py | 24 +++++++- docs/help/report_bug.rst | 2 +- tests/test_share_log.py | 94 +++++++++++++++++++++++++++++++ 7 files changed, 170 insertions(+), 9 deletions(-) create mode 100644 tests/test_share_log.py diff --git a/.github/ISSUE_TEMPLATE/01_bug.yml b/.github/ISSUE_TEMPLATE/01_bug.yml index ab40754f..92061f64 100644 --- a/.github/ISSUE_TEMPLATE/01_bug.yml +++ b/.github/ISSUE_TEMPLATE/01_bug.yml @@ -41,8 +41,8 @@ body: attributes: value: > **Note**: Assuming you have network connectivity, - you can easily post the installation log using the following command: - `curl -F'file=@/var/log/archinstall/install.log' https://0x0.st` + you can easily upload the installation log and get a shareable URL by running: + `archinstall share-log` - type: textarea id: freeform diff --git a/README.md b/README.md index 95d15702..4e6847f4 100644 --- a/README.md +++ b/README.md @@ -101,9 +101,9 @@ If you come across any issues, kindly submit your issue here on GitHub or post y When submitting an issue, please: * Provide the stacktrace of the output if applicable * Attach the `/var/log/archinstall/install.log` to the issue ticket. This helps us help you! - * To extract the log from the ISO image, one way is to use
+ * To upload the log from the ISO image and get a shareable URL, run
```shell - curl -F'file=@/var/log/archinstall/install.log' https://0x0.st + archinstall share-log ``` diff --git a/archinstall/lib/args.py b/archinstall/lib/args.py index 8d78f7e6..d8aaaa83 100644 --- a/archinstall/lib/args.py +++ b/archinstall/lib/args.py @@ -431,7 +431,6 @@ class ArchConfigHandler: default=False, help='Enabled verbose options', ) - return parser def _parse_args(self) -> Arguments: diff --git a/archinstall/lib/output.py b/archinstall/lib/output.py index bc4bb113..3a972dfb 100644 --- a/archinstall/lib/output.py +++ b/archinstall/lib/output.py @@ -1,6 +1,8 @@ import logging import os import sys +import urllib.error +import urllib.request from collections.abc import Callable from dataclasses import asdict, is_dataclass from datetime import UTC, datetime @@ -333,3 +335,51 @@ def log( if level != logging.DEBUG: print(text) + + +def share_install_log( + paste_url: str = 'https://paste.rs', + max_size: int = 10 * 1024 * 1024, + confirm: Callable[[str], bool] = lambda _: True, +) -> int: + log_path = logger.path + + if not log_path.exists(): + info(f'Log file not found: {log_path}') + return 1 + + size = log_path.stat().st_size + if size == 0: + info(f'Log file is empty: {log_path}') + return 1 + + if size > max_size: + info(f'Log file exceeds {max_size} bytes, uploading last {max_size} bytes') + content = log_path.read_bytes()[-max_size:] + else: + content = log_path.read_bytes() + + header = f'About to upload {log_path} ({len(content)} bytes) to {paste_url}\n\n' + header += 'The log may contain hostname, mirror URLs, package list and partition layout.\n' + header += 'The uploaded paste is public.\n\n' + header += 'Continue?' + + if not confirm(header): + info('Cancelled.') + return 1 + + try: + req = urllib.request.Request(paste_url, data=content) + with urllib.request.urlopen(req) as response: + url = response.read().decode().strip() + except urllib.error.URLError as e: + info(f'Upload failed: {e}') + return 1 + + if not url.startswith('http'): + info(f'Unexpected response from {paste_url}: {url[:200]!r}') + return 1 + + # raw print so the URL is pipe-friendly (no ANSI colors, no log prefix) + print(url) + return 0 diff --git a/archinstall/main.py b/archinstall/main.py index 505652a4..9b91681b 100644 --- a/archinstall/main.py +++ b/archinstall/main.py @@ -11,14 +11,16 @@ from pathlib import Path from archinstall.lib.args import ArchConfigHandler from archinstall.lib.disk.utils import disk_layouts from archinstall.lib.hardware import SysInfo +from archinstall.lib.menu.helpers import Confirmation from archinstall.lib.network.wifi_handler import WifiHandler from archinstall.lib.networking import ping -from archinstall.lib.output import debug, error, info, warn +from archinstall.lib.output import debug, error, info, share_install_log, warn from archinstall.lib.packages.util import check_version_upgrade from archinstall.lib.pacman.pacman import Pacman from archinstall.lib.translationhandler import tr, translation_handler from archinstall.lib.utils.util import running_from_iso from archinstall.tui.components import tui +from archinstall.tui.menu_item import MenuItemGroup def _log_sys_info() -> None: @@ -73,12 +75,28 @@ def _list_scripts() -> str: return '\n'.join(lines) +def _tui_confirm(header: str) -> bool: + async def _ask() -> bool: + result = await Confirmation( + group=MenuItemGroup.yes_no(), + header=header, + allow_skip=False, + preset=False, + ).show() + return result.get_value() + + return tui.run(_ask) + + def run() -> int: """ This can either be run as the compiled and installed application: python setup.py install OR straight as a module: python -m archinstall In any case we will be attempting to load the provided script to be run from the scripts/ folder """ + if 'share-log' in sys.argv: + return share_install_log(confirm=_tui_confirm) + arch_config_handler = ArchConfigHandler() if '--help' in sys.argv or '-h' in sys.argv: @@ -141,8 +159,8 @@ def _error_message(exc: Exception) -> None: Archinstall experienced the above error. If you think this is a bug, please report it to https://github.com/archlinux/archinstall and include the log file "/var/log/archinstall/install.log". - Hint: To extract the log from a live ISO - curl -F 'file=@/var/log/archinstall/install.log' https://0x0.st + Hint: To upload the log and get a shareable URL, run + archinstall share-log """ ) warn(text) diff --git a/docs/help/report_bug.rst b/docs/help/report_bug.rst index bacaeb4c..b11027ab 100644 --- a/docs/help/report_bug.rst +++ b/docs/help/report_bug.rst @@ -15,7 +15,7 @@ When submitting a help ticket, please include the :code:`/var/log/archinstall/in It can be found both on the live ISO but also in the installed filesystem if the base packages were strapped in. .. tip:: - | An easy way to submit logs is ``curl -F 'file=@/var/log/archinstall/install.log' https://0x0.st``. + | An easy way to submit logs is ``archinstall share-log``, which uploads ``install.log`` to paste.rs and prints a shareable URL. | Use caution when submitting other log files, but ``archinstall`` pledges to keep ``install.log`` safe for posting publicly! There are additional log files under ``/var/log/archinstall/`` that can be useful: diff --git a/tests/test_share_log.py b/tests/test_share_log.py new file mode 100644 index 00000000..c1c3ca84 --- /dev/null +++ b/tests/test_share_log.py @@ -0,0 +1,94 @@ +# pylint: disable=redefined-outer-name +import urllib.error +from io import BytesIO +from pathlib import Path +from unittest.mock import MagicMock, patch + +import pytest + +from archinstall.lib.output import share_install_log + + +@pytest.fixture() +def log_file(tmp_path: Path) -> Path: + log_dir = tmp_path / 'archinstall' + log_dir.mkdir() + return log_dir / 'install.log' + + +def _fake_logger(log_file: Path) -> MagicMock: + mock = MagicMock() + mock.path = log_file + return mock + + +def test_file_not_found(tmp_path: Path) -> None: + missing = tmp_path / 'no-such' / 'install.log' + with patch('archinstall.lib.output.logger', _fake_logger(missing)): + assert share_install_log() == 1 + + +def test_empty_file(log_file: Path) -> None: + log_file.write_bytes(b'') + with patch('archinstall.lib.output.logger', _fake_logger(log_file)): + assert share_install_log() == 1 + + +def test_user_cancels(log_file: Path) -> None: + log_file.write_text('some log content') + with patch('archinstall.lib.output.logger', _fake_logger(log_file)): + assert share_install_log(confirm=lambda _: False) == 1 + + +def test_successful_upload(log_file: Path) -> None: + log_file.write_text('some log content') + fake_response = BytesIO(b'https://paste.rs/abc.def') + + with ( + patch('archinstall.lib.output.logger', _fake_logger(log_file)), + patch('urllib.request.urlopen', return_value=fake_response) as mock_open, + ): + result = share_install_log() + + assert result == 0 + req = mock_open.call_args[0][0] + assert req.data == b'some log content' + + +def test_truncation(log_file: Path) -> None: + max_size = 100 + content = b'A' * 50 + b'B' * 80 + log_file.write_bytes(content) + fake_response = BytesIO(b'https://paste.rs/abc.def') + + with ( + patch('archinstall.lib.output.logger', _fake_logger(log_file)), + patch('urllib.request.urlopen', return_value=fake_response) as mock_open, + ): + result = share_install_log(max_size=max_size) + + assert result == 0 + req = mock_open.call_args[0][0] + assert len(req.data) == max_size + assert req.data == content[-max_size:] + + +def test_network_error(log_file: Path) -> None: + log_file.write_text('some log content') + + with ( + patch('archinstall.lib.output.logger', _fake_logger(log_file)), + patch('urllib.request.urlopen', side_effect=urllib.error.URLError('no network')), + ): + assert share_install_log() == 1 + + +def test_unexpected_response(log_file: Path) -> None: + log_file.write_text('some log content') + fake_response = BytesIO(b'ERROR: something went wrong') + + with ( + patch('archinstall.lib.output.logger', _fake_logger(log_file)), + patch('urllib.request.urlopen', return_value=fake_response), + ): + assert share_install_log() == 1 From b0b7983af2b0cabce282e576ed3a20010e20fb2c Mon Sep 17 00:00:00 2001 From: Softer Date: Thu, 7 May 2026 03:20:09 +0300 Subject: [PATCH 37/61] Fix bspwm black screen: add provision() delegation and default configs (#4518) * Fix bspwm black screen: add provision() delegation and default configs * Move TYPE_CHECKING imports to top level in bspwm profile --- archinstall/default_profiles/desktop.py | 6 ++++++ archinstall/default_profiles/desktops/bspwm.py | 10 ++++++++++ 2 files changed, 16 insertions(+) diff --git a/archinstall/default_profiles/desktop.py b/archinstall/default_profiles/desktop.py index 19681bc7..daf70831 100644 --- a/archinstall/default_profiles/desktop.py +++ b/archinstall/default_profiles/desktop.py @@ -9,6 +9,7 @@ from archinstall.tui.result import ResultType if TYPE_CHECKING: from archinstall.lib.installer import Installer + from archinstall.lib.models.users import User class DesktopProfile(Profile): @@ -88,6 +89,11 @@ class DesktopProfile(Profile): for profile in self.current_selection: profile.post_install(install_session) + @override + def provision(self, install_session: Installer, users: list[User]) -> None: + for profile in self.current_selection: + profile.provision(install_session, users) + @override def install(self, install_session: Installer) -> None: # Install common packages for all desktop environments diff --git a/archinstall/default_profiles/desktops/bspwm.py b/archinstall/default_profiles/desktops/bspwm.py index f8c2a7b2..8886298a 100644 --- a/archinstall/default_profiles/desktops/bspwm.py +++ b/archinstall/default_profiles/desktops/bspwm.py @@ -1,6 +1,8 @@ from typing import override from archinstall.default_profiles.profile import DisplayServerType, GreeterType, Profile, ProfileType +from archinstall.lib.installer import Installer +from archinstall.lib.models.users import User class BspwmProfile(Profile): @@ -27,3 +29,11 @@ class BspwmProfile(Profile): @override def default_greeter_type(self) -> GreeterType: return GreeterType.Lightdm + + @override + def provision(self, install_session: Installer, users: list[User]) -> None: + for user in users: + install_session.arch_chroot('mkdir -p ~/.config/bspwm ~/.config/sxhkd', run_as=user.username) + install_session.arch_chroot('cp /usr/share/doc/bspwm/examples/bspwmrc ~/.config/bspwm/', run_as=user.username) + install_session.arch_chroot('cp /usr/share/doc/bspwm/examples/sxhkdrc ~/.config/sxhkd/', run_as=user.username) + install_session.arch_chroot('chmod +x ~/.config/bspwm/bspwmrc', run_as=user.username) From 4265be6e4386a2d8b00436aacbef1e37d2ebe2d9 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 7 May 2026 21:56:30 +1000 Subject: [PATCH 38/61] Update pre-commit hook pre-commit/mirrors-mypy to v2 (#4525) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7ee79056..6cf16130 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -31,7 +31,7 @@ repos: args: [--config=.flake8] fail_fast: true - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.20.2 + rev: v2.0.0 hooks: - id: mypy args: [ From af106eb23838fdee05e1025ff259533f7a965be5 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 7 May 2026 21:57:10 +1000 Subject: [PATCH 39/61] Update dependency mypy to v2 (#4523) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 28e91c2a..9fee23fa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,7 +34,7 @@ Source = "https://github.com/archlinux/archinstall" [project.optional-dependencies] log = ["systemd_python==235"] dev = [ - "mypy==1.20.2", + "mypy==2.0.0", "flake8==7.3.0", "pre-commit==4.6.0", "ruff==0.15.12", From 2de7254b21ddd4d554b9933c8131133658fed9cf Mon Sep 17 00:00:00 2001 From: Lena Pastwa Date: Fri, 8 May 2026 03:28:36 +0200 Subject: [PATCH 40/61] Update Polish translation (#4529) --- archinstall/locales/base.pot | 11 + archinstall/locales/pl/LC_MESSAGES/base.mo | Bin 62517 -> 67521 bytes archinstall/locales/pl/LC_MESSAGES/base.po | 237 ++++++++++++++++++++- 3 files changed, 247 insertions(+), 1 deletion(-) diff --git a/archinstall/locales/base.pot b/archinstall/locales/base.pot index 31f5e196..0b08df29 100644 --- a/archinstall/locales/base.pot +++ b/archinstall/locales/base.pot @@ -2293,3 +2293,14 @@ msgstr "" #, python-brace-format msgid "Default: {}ms, Recommended range: 1000-60000" msgstr "" + +#, python-brace-format +msgid "{} needs access to your seat" +msgstr "" + +msgid "collection of hardware devices i.e. keyboard, mouse" +msgstr "" + +#, python-brace-format +msgid "Choose an option how to give {} access to your hardware" +msgstr "" diff --git a/archinstall/locales/pl/LC_MESSAGES/base.mo b/archinstall/locales/pl/LC_MESSAGES/base.mo index 4a57d37caea35cb2f39e0904bfcdd33b192b6be0..0ab34f9eca17057f5f6a378cba00b090d27ae98a 100644 GIT binary patch delta 18125 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 Date: Mon, 11 May 2026 06:35:53 +1000 Subject: [PATCH 41/61] Enhance config types and summary (#4532) * Enhance config types and summary * Update --- archinstall/lib/args.py | 165 ++++++++++++++++++----- archinstall/lib/configuration.py | 76 +++-------- archinstall/lib/models/application.py | 52 ++++++- archinstall/lib/models/authentication.py | 21 ++- archinstall/lib/models/bootloader.py | 17 ++- archinstall/lib/models/config.py | 12 ++ archinstall/lib/models/device.py | 37 ++++- archinstall/lib/models/locale.py | 15 ++- archinstall/lib/models/mirrors.py | 23 +++- archinstall/lib/models/network.py | 8 +- archinstall/lib/models/pacman.py | 12 +- archinstall/lib/models/profile.py | 24 +++- archinstall/lib/output.py | 32 ++++- archinstall/tui/components.py | 7 - archinstall/tui/rich.py | 25 ++++ 15 files changed, 411 insertions(+), 115 deletions(-) create mode 100644 archinstall/lib/models/config.py create mode 100644 archinstall/tui/rich.py diff --git a/archinstall/lib/args.py b/archinstall/lib/args.py index d8aaaa83..efe9baa2 100644 --- a/archinstall/lib/args.py +++ b/archinstall/lib/args.py @@ -6,6 +6,7 @@ import urllib.error import urllib.parse from argparse import ArgumentParser, Namespace from dataclasses import dataclass, field +from enum import StrEnum from pathlib import Path from typing import Any, Self from urllib.request import Request, urlopen @@ -17,6 +18,7 @@ from archinstall.lib.menu.util import get_password from archinstall.lib.models.application import ApplicationConfiguration, ZramConfiguration from archinstall.lib.models.authentication import AuthenticationConfiguration from archinstall.lib.models.bootloader import Bootloader, BootloaderConfiguration +from archinstall.lib.models.config import SubConfig from archinstall.lib.models.device import DiskEncryption, DiskLayoutConfiguration from archinstall.lib.models.locale import LocaleConfiguration from archinstall.lib.models.mirrors import MirrorConfiguration @@ -57,6 +59,81 @@ class Arguments: verbose: bool = False +class ArchConfigType(StrEnum): + VERSION = 'version' + SCRIPT = 'script' + LOCALE_CONFIG = 'locale_config' + ARCHINSTALL_LANGUAGE = 'archinstall_language' + DISK_CONFIG = 'disk_config' + PROFILE_CONFIG = 'profile_config' + MIRROR_CONFIG = 'mirror_config' + NETWORK_CONFIG = 'network_config' + BOOTLOADER_CONFIG = 'bootloader_config' + APP_CONFIG = 'app_config' + AUTH_CONFIG = 'auth_config' + SWAP = 'swap' + USERS = 'users' + ROOT_ENC_PASSWORD = 'root_enc_password' + ENCRYPTION_PASSWORD = 'encryption_password' + HOSTNAME = 'hostname' + KERNELS = 'kernels' + NTP = 'ntp' + TIMEZONE = 'timezone' + SERVICES = 'services' + PACKAGES = 'packages' + PACMAN_CONFIG = 'pacman_config' + CUSTOM_COMMANDS = 'custom_commands' + + def text(self) -> str: + match self: + case ArchConfigType.ARCHINSTALL_LANGUAGE: + return tr('ArchInstall Language') + case ArchConfigType.VERSION: + return tr('Version') + case ArchConfigType.SCRIPT: + return tr('Installation Script') + case ArchConfigType.LOCALE_CONFIG: + return tr('Locales') + case ArchConfigType.DISK_CONFIG: + return tr('Disk configuration') + case ArchConfigType.PROFILE_CONFIG: + return tr('Profile') + case ArchConfigType.MIRROR_CONFIG: + return tr('Mirrors and repositories') + case ArchConfigType.NETWORK_CONFIG: + return tr('Network') + case ArchConfigType.BOOTLOADER_CONFIG: + return tr('Bootloader') + case ArchConfigType.APP_CONFIG: + return tr('Application') + case ArchConfigType.AUTH_CONFIG: + return tr('Authentication') + case ArchConfigType.SWAP: + return tr('Swap') + case ArchConfigType.HOSTNAME: + return tr('Hostname') + case ArchConfigType.KERNELS: + return tr('Kernels') + case ArchConfigType.NTP: + return tr('Automatic time sync (NTP)') + case ArchConfigType.TIMEZONE: + return tr('Timezone') + case ArchConfigType.SERVICES: + return tr('Services') + case ArchConfigType.PACKAGES: + return tr('Additional packages') + case ArchConfigType.PACMAN_CONFIG: + return tr('Pacman') + case ArchConfigType.CUSTOM_COMMANDS: + return tr('Custom commands') + case ArchConfigType.USERS: + return tr('Users') + case ArchConfigType.ROOT_ENC_PASSWORD: + return tr('Root encrypted password') + case ArchConfigType.ENCRYPTION_PASSWORD: + return tr('Disk encryption password') + + @dataclass class ArchConfig: version: str | None = None @@ -80,58 +157,84 @@ class ArchConfig: services: list[str] = field(default_factory=list) custom_commands: list[str] = field(default_factory=list) - def unsafe_config(self) -> dict[str, Any]: - config: dict[str, list[UserSerialization] | str | None] = {} + def unsafe_config(self) -> dict[ArchConfigType, Any]: + config: dict[ArchConfigType, list[UserSerialization] | str | None] = {} if self.auth_config: if self.auth_config.users: - config['users'] = [user.json() for user in self.auth_config.users] + config[ArchConfigType.USERS] = [user.json() for user in self.auth_config.users] if self.auth_config.root_enc_password: - config['root_enc_password'] = self.auth_config.root_enc_password.enc_password + config[ArchConfigType.ROOT_ENC_PASSWORD] = self.auth_config.root_enc_password.enc_password if self.disk_config: disk_encryption = self.disk_config.disk_encryption if disk_encryption and disk_encryption.encryption_password: - config['encryption_password'] = disk_encryption.encryption_password.plaintext + config[ArchConfigType.ENCRYPTION_PASSWORD] = disk_encryption.encryption_password.plaintext return config - def safe_config(self) -> dict[str, Any]: - config: Any = { - 'version': self.version, - 'script': self.script, - 'archinstall-language': self.archinstall_language.json(), - 'hostname': self.hostname, - 'kernels': self.kernels, - 'ntp': self.ntp, - 'packages': self.packages, - 'pacman_config': self.pacman_config.json(), - 'swap': self.swap, - 'timezone': self.timezone, - 'services': self.services, - 'custom_commands': self.custom_commands, - 'bootloader_config': self.bootloader_config.json() if self.bootloader_config else None, - 'app_config': self.app_config.json() if self.app_config else None, - 'auth_config': self.auth_config.json() if self.auth_config else None, + def safe_config(self) -> dict[ArchConfigType, Any]: + base_config: dict[ArchConfigType, Any] = { + ArchConfigType.VERSION: self.version, + ArchConfigType.SCRIPT: self.script, + ArchConfigType.ARCHINSTALL_LANGUAGE: self.archinstall_language.json(), } - if self.locale_config: - config['locale_config'] = self.locale_config.json() + base_config.update(self.plain_cfg()) + sub_config = self.sub_cfg() - if self.disk_config: - config['disk_config'] = self.disk_config.json() + for config_type, value in sub_config.items(): + if not hasattr(value, 'json'): + raise ValueError(f'Config value for {config_type} must implement json() method') + base_config[config_type] = value.json() - if self.profile_config: - config['profile_config'] = self.profile_config.json() + return base_config + + def plain_cfg(self) -> dict[ArchConfigType, str | list[str] | bool]: + return { + ArchConfigType.HOSTNAME: self.hostname, + ArchConfigType.KERNELS: self.kernels, + ArchConfigType.NTP: self.ntp, + ArchConfigType.TIMEZONE: self.timezone, + ArchConfigType.SERVICES: self.services, + ArchConfigType.PACKAGES: self.packages, + ArchConfigType.CUSTOM_COMMANDS: self.custom_commands, + } + + def sub_cfg(self) -> dict[ArchConfigType, SubConfig]: + cfg: dict[ArchConfigType, SubConfig] = { + ArchConfigType.PACMAN_CONFIG: self.pacman_config, + } if self.mirror_config: - config['mirror_config'] = self.mirror_config.json() + cfg[ArchConfigType.MIRROR_CONFIG] = self.mirror_config + + if self.bootloader_config: + cfg[ArchConfigType.BOOTLOADER_CONFIG] = self.bootloader_config + + if self.disk_config: + cfg[ArchConfigType.DISK_CONFIG] = self.disk_config + + if self.swap: + cfg[ArchConfigType.SWAP] = self.swap + + if self.auth_config: + cfg[ArchConfigType.AUTH_CONFIG] = self.auth_config + + if self.locale_config: + cfg[ArchConfigType.LOCALE_CONFIG] = self.locale_config + + if self.profile_config: + cfg[ArchConfigType.PROFILE_CONFIG] = self.profile_config if self.network_config: - config['network_config'] = self.network_config.json() + cfg[ArchConfigType.NETWORK_CONFIG] = self.network_config - return config + if self.app_config: + cfg[ArchConfigType.APP_CONFIG] = self.app_config + + return cfg @classmethod def from_config(cls, args_config: dict[str, Any], args: Arguments) -> Self: diff --git a/archinstall/lib/configuration.py b/archinstall/lib/configuration.py index aba835d9..c40ef161 100644 --- a/archinstall/lib/configuration.py +++ b/archinstall/lib/configuration.py @@ -6,13 +6,12 @@ from typing import Any from pydantic import TypeAdapter -from archinstall.lib.args import ArchConfig +from archinstall.lib.args import ArchConfig, ArchConfigType from archinstall.lib.crypt import encrypt from archinstall.lib.menu.helpers import Confirmation, Selection from archinstall.lib.menu.util import get_password, prompt_dir -from archinstall.lib.models.bootloader import Bootloader from archinstall.lib.models.network import NetworkConfiguration -from archinstall.lib.output import debug, logger, warn +from archinstall.lib.output import as_key_value_pair, debug, logger, warn from archinstall.lib.translationhandler import tr from archinstall.tui.menu_item import MenuItem, MenuItemGroup from archinstall.tui.result import ResultType @@ -45,15 +44,15 @@ class ConfigurationOutput: def user_config_to_json(self) -> str: config = self._config.safe_config() - adapter = TypeAdapter(dict[str, Any]) + adapter = TypeAdapter(dict[ArchConfigType, Any]) python_dict = adapter.dump_python(config) return json.dumps(python_dict, indent=4, sort_keys=True) def user_credentials_to_json(self) -> str: - config = self._config.unsafe_config() + cfg = self._config.unsafe_config() - adapter = TypeAdapter(dict[str, Any]) - python_dict = adapter.dump_python(config) + adapter = TypeAdapter(dict[ArchConfigType, Any]) + python_dict = adapter.dump_python(cfg) return json.dumps(python_dict, indent=4, sort_keys=True) def write_debug(self) -> None: @@ -64,65 +63,24 @@ class ConfigurationOutput: """ Render a concise two-column summary of the current configuration. - The left column holds section labels, the right column holds values. - Column width adapts to the longest translated label so translations - do not break the alignment. Rows whose underlying config is not set - are skipped. - Returns an empty string if nothing meaningful to show. """ - rows: list[tuple[str, str]] = [] + cfg: dict[str, str | list[str] | bool] = {} - disk_config = self._config.disk_config - if disk_config and disk_config.device_modifications: - disk_parts: list[str] = [] - for mod in disk_config.device_modifications: - path = str(mod.device_path) - root_part = mod.get_root_partition() - flags: list[str] = [] - if root_part and root_part.fs_type: - flags.append(root_part.fs_type.value) - if disk_config.disk_encryption: - flags.append(tr('LUKS')) - disk_parts.append(f'{path} ({" + ".join(flags)})' if flags else path) - rows.append((tr('Disks'), ', '.join(disk_parts))) + for key, value in self._config.plain_cfg().items(): + cfg[key.text()] = value - bl_config = self._config.bootloader_config - if bl_config and bl_config.bootloader != Bootloader.NO_BOOTLOADER: - rows.append((tr('Bootloader'), bl_config.bootloader.value)) + for config_type, obj in self._config.sub_cfg().items(): + if not hasattr(obj, 'summary'): + continue - kernels = self._config.kernels - if kernels: - rows.append((tr('Kernel'), ', '.join(kernels))) + summary = obj.summary() + if summary: + cfg[config_type.text()] = summary - profile_config = self._config.profile_config - if profile_config and profile_config.profile: - names = profile_config.profile.current_selection_names() - rows.append((tr('Profile'), ', '.join(names) if names else profile_config.profile.name)) - if profile_config.greeter: - rows.append((tr('Greeter'), profile_config.greeter.value)) + simple_summary = as_key_value_pair(cfg, ignore_empty=True) - packages = self._config.packages - if packages: - rows.append((tr('Packages'), str(len(packages)))) - - net_config = self._config.network_config - if isinstance(net_config, NetworkConfiguration): - rows.append((tr('Network'), net_config.type.display_msg())) - - locale_config = self._config.locale_config - if locale_config: - rows.append((tr('Locale'), locale_config.sys_lang)) - - tz = self._config.timezone - if tz: - rows.append((tr('Timezone'), tz)) - - if not rows: - return '' - - label_width = max(len(label) for label, _ in rows) + 2 - return '\n'.join(f'{label:<{label_width}}{value}' for label, value in rows) + return simple_summary async def confirm_config(self, show_install_warnings: bool = False) -> bool: header = f'{tr("The specified configuration will be applied")}. ' diff --git a/archinstall/lib/models/application.py b/archinstall/lib/models/application.py index 04d1a342..1e97a784 100644 --- a/archinstall/lib/models/application.py +++ b/archinstall/lib/models/application.py @@ -1,7 +1,8 @@ from dataclasses import dataclass from enum import StrEnum, auto -from typing import Any, NotRequired, Self, TypedDict +from typing import Any, NotRequired, Self, TypedDict, override +from archinstall.lib.models.config import SubConfig from archinstall.lib.translationhandler import tr @@ -168,7 +169,7 @@ class FontsConfiguration: @dataclass(frozen=True) -class ZramConfiguration: +class ZramConfiguration(SubConfig): enabled: bool algorithm: ZramAlgorithm = ZramAlgorithm.ZSTD @@ -181,9 +182,28 @@ class ZramConfiguration: algo = arg.get('algorithm', arg.get('algo', ZramAlgorithm.ZSTD.value)) return cls(enabled=enabled, algorithm=ZramAlgorithm(algo)) + @override + def json(self) -> dict[str, bool | str]: + return { + 'enabled': self.enabled, + 'algorithm': self.algorithm.value, + } + + @override + def summary(self) -> list[str] | None: + out: list[str] = [] + + if self.enabled: + out.append(tr('Zram enabled')) + + out.append(tr('Zram algorithm {}').format(self.algorithm)) + return out + + return None + @dataclass -class ApplicationConfiguration: +class ApplicationConfiguration(SubConfig): bluetooth_config: BluetoothConfiguration | None = None audio_config: AudioConfiguration | None = None power_management_config: PowerManagementConfiguration | None = None @@ -223,6 +243,7 @@ class ApplicationConfiguration: return app_config + @override def json(self) -> ApplicationSerialization: config: ApplicationSerialization = {} @@ -245,3 +266,28 @@ class ApplicationConfiguration: config['fonts_config'] = self.fonts_config.json() return config + + @override + def summary(self) -> list[str]: + out: list[str] = [] + + if self.bluetooth_config and self.bluetooth_config.enabled: + out.append(tr('Bluetooth enabled')) + + if self.audio_config: + out.append(tr('Audio server "{}"').format(self.audio_config.audio)) + + if self.power_management_config: + out.append(tr('Power management "{}"').format(self.power_management_config.power_management)) + + if self.print_service_config and self.print_service_config.enabled: + out.append(tr('Print service enabled')) + + if self.firewall_config: + out.append(tr('Firewall "{}"').format(self.firewall_config.firewall)) + + if self.fonts_config and self.fonts_config.fonts: + fonts = ', '.join(f.value for f in self.fonts_config.fonts) + out.append(tr('Extra fonts "{}"').format(fonts)) + + return out diff --git a/archinstall/lib/models/authentication.py b/archinstall/lib/models/authentication.py index 51120479..37261e7c 100644 --- a/archinstall/lib/models/authentication.py +++ b/archinstall/lib/models/authentication.py @@ -1,7 +1,8 @@ from dataclasses import dataclass, field from enum import Enum -from typing import Any, NotRequired, Self, TypedDict +from typing import Any, NotRequired, Self, TypedDict, override +from archinstall.lib.models.config import SubConfig from archinstall.lib.models.users import Password, User from archinstall.lib.translationhandler import tr @@ -58,7 +59,7 @@ class U2FLoginConfiguration: @dataclass -class AuthenticationConfiguration: +class AuthenticationConfiguration(SubConfig): root_enc_password: Password | None = None users: list[User] = field(default_factory=list) u2f_config: U2FLoginConfiguration | None = None @@ -75,6 +76,7 @@ class AuthenticationConfiguration: return auth_config + @override def json(self) -> AuthenticationSerialization: config: AuthenticationSerialization = {} @@ -83,6 +85,21 @@ class AuthenticationConfiguration: return config + @override + def summary(self) -> list[str]: + out: list[str] = [] + + if self.root_enc_password: + out.append(tr('Root password set')) + + if self.users: + out.append(tr('Configured {} user(s)').format(len(self.users))) + + if self.u2f_config: + out.append(tr('U2F set up')) + + return out + def has_superuser(self) -> bool: return any(u.sudo for u in self.users) diff --git a/archinstall/lib/models/bootloader.py b/archinstall/lib/models/bootloader.py index 5d8d0d80..0dbd4268 100644 --- a/archinstall/lib/models/bootloader.py +++ b/archinstall/lib/models/bootloader.py @@ -1,8 +1,9 @@ import sys from dataclasses import dataclass from enum import Enum -from typing import Any, Self +from typing import Any, Self, override +from archinstall.lib.models.config import SubConfig from archinstall.lib.output import warn from archinstall.lib.translationhandler import tr @@ -60,14 +61,26 @@ class Bootloader(Enum): @dataclass -class BootloaderConfiguration: +class BootloaderConfiguration(SubConfig): bootloader: Bootloader uki: bool = False removable: bool = True + @override def json(self) -> dict[str, Any]: return {'bootloader': self.bootloader.json(), 'uki': self.uki, 'removable': self.removable} + @override + def summary(self) -> list[str]: + out = [tr('Bootloader "{}"').format(self.bootloader.value)] + + if self.uki: + out.append(tr('UKI enabled')) + if self.removable: + out.append(tr('Removable')) + + return out + @classmethod def parse_arg(cls, config: dict[str, Any], skip_boot: bool) -> Self: bootloader = Bootloader.from_arg(config.get('bootloader', ''), skip_boot) diff --git a/archinstall/lib/models/config.py b/archinstall/lib/models/config.py new file mode 100644 index 00000000..45835bc0 --- /dev/null +++ b/archinstall/lib/models/config.py @@ -0,0 +1,12 @@ +from abc import ABC, abstractmethod +from typing import Any + + +class SubConfig(ABC): + @abstractmethod + def json(self) -> Any: + pass + + @abstractmethod + def summary(self) -> str | list[str] | None: + pass diff --git a/archinstall/lib/models/device.py b/archinstall/lib/models/device.py index 7fa05407..a21f3c6f 100644 --- a/archinstall/lib/models/device.py +++ b/archinstall/lib/models/device.py @@ -12,6 +12,7 @@ from parted import Disk, Geometry, Partition from pydantic import BaseModel, Field, ValidationInfo, field_serializer, field_validator from archinstall.lib.hardware import SysInfo +from archinstall.lib.models.config import SubConfig from archinstall.lib.models.users import Password from archinstall.lib.output import debug from archinstall.lib.translationhandler import tr @@ -34,6 +35,15 @@ class DiskLayoutType(Enum): case DiskLayoutType.Pre_mount: return tr('Pre-mounted configuration') + def short_msg(self) -> str: + match self: + case DiskLayoutType.Default: + return tr('Default') + case DiskLayoutType.Manual: + return tr('Manual') + case DiskLayoutType.Pre_mount: + return tr('Pre-mount') + class _DiskLayoutConfigurationSerialization(TypedDict): config_type: str @@ -45,7 +55,7 @@ class _DiskLayoutConfigurationSerialization(TypedDict): @dataclass -class DiskLayoutConfiguration: +class DiskLayoutConfiguration(SubConfig): config_type: DiskLayoutType device_modifications: list[DeviceModification] = field(default_factory=list) lvm_config: LvmConfiguration | None = None @@ -55,6 +65,7 @@ class DiskLayoutConfiguration: # used for pre-mounted config mountpoint: Path | None = None + @override def json(self) -> _DiskLayoutConfigurationSerialization: if self.config_type == DiskLayoutType.Pre_mount: return { @@ -78,6 +89,28 @@ class DiskLayoutConfiguration: return config + @override + def summary(self) -> list[str]: + out = [tr('{} layout').format(self.config_type.short_msg())] + + devices = set(mod.device_path for mod in self.device_modifications) + + if devices: + dev_str = ', '.join(str(d) for d in devices) + out.append(tr('Devices {}').format(dev_str)) + + if self.lvm_config is not None: + out.append(tr('LVM set up')) + + if self.disk_encryption is not None: + out.append(tr('{} encryption').format(self.disk_encryption.encryption_type.type_to_text())) + + if self.btrfs_options is not None: + if self.btrfs_options.snapshot_config: + out.append(tr('Btrfs snapshot "{}"').format(self.btrfs_options.snapshot_config.snapshot_type)) + + return out + @classmethod def parse_arg( cls, @@ -1321,7 +1354,7 @@ class _SnapshotConfigSerialization(TypedDict): type: str -class SnapshotType(Enum): +class SnapshotType(StrEnum): Snapper = 'Snapper' Timeshift = 'Timeshift' diff --git a/archinstall/lib/models/locale.py b/archinstall/lib/models/locale.py index 8f580989..26ab3cc0 100644 --- a/archinstall/lib/models/locale.py +++ b/archinstall/lib/models/locale.py @@ -1,12 +1,13 @@ from dataclasses import dataclass -from typing import Any, Self +from typing import Any, Self, override from archinstall.lib.locale.utils import get_kb_layout +from archinstall.lib.models.config import SubConfig from archinstall.lib.translationhandler import tr @dataclass -class LocaleConfiguration: +class LocaleConfiguration(SubConfig): kb_layout: str sys_lang: str sys_enc: str @@ -23,6 +24,7 @@ class LocaleConfiguration: layout = 'us' return cls(layout, 'en_US.UTF-8', 'UTF-8') + @override def json(self) -> dict[str, str]: return { 'kb_layout': self.kb_layout, @@ -31,6 +33,15 @@ class LocaleConfiguration: 'console_font': self.console_font, } + @override + def summary(self) -> list[str]: + return [ + tr('Keyboard layout "{}"').format(self.kb_layout), + tr('Locale language "{}"').format(self.sys_lang), + tr('Locale encoding "{}"').format(self.sys_enc), + tr('Console font "{}"').format(self.console_font), + ] + def preview(self) -> str: output = '{}: {}\n'.format(tr('Keyboard layout'), self.kb_layout) output += '{}: {}\n'.format(tr('Locale language'), self.sys_lang) diff --git a/archinstall/lib/models/mirrors.py b/archinstall/lib/models/mirrors.py index 0a596c13..3ded2a9a 100644 --- a/archinstall/lib/models/mirrors.py +++ b/archinstall/lib/models/mirrors.py @@ -9,9 +9,11 @@ from typing import TYPE_CHECKING, Any, Self, TypedDict, override from pydantic import BaseModel, ValidationInfo, field_validator, model_validator +from archinstall.lib.models.config import SubConfig from archinstall.lib.models.packages import Repository from archinstall.lib.networking import DownloadTimer, ping from archinstall.lib.output import debug +from archinstall.lib.translationhandler import tr if TYPE_CHECKING: from archinstall.lib.mirror.mirror_handler import MirrorListHandler @@ -236,7 +238,7 @@ class _MirrorConfigurationSerialization(TypedDict): @dataclass -class MirrorConfiguration: +class MirrorConfiguration(SubConfig): mirror_regions: list[MirrorRegion] = field(default_factory=list) custom_servers: list[CustomServer] = field(default_factory=list) optional_repositories: list[Repository] = field(default_factory=list) @@ -250,6 +252,7 @@ class MirrorConfiguration: def custom_server_urls(self) -> str: return '\n'.join(s.url for s in self.custom_servers) + @override def json(self) -> _MirrorConfigurationSerialization: regions = {} for m in self.mirror_regions: @@ -262,6 +265,24 @@ class MirrorConfiguration: 'custom_repositories': [c.json() for c in self.custom_repositories], } + @override + def summary(self) -> list[str]: + out: list[str] = [] + + if self.mirror_regions: + out.append(tr('Mirror regions "{}"').format(', '.join(m.name for m in self.mirror_regions))) + + if self.optional_repositories: + out.append(tr('Optional repositories "{}"').format(', '.join(r.value for r in self.optional_repositories))) + + if self.custom_servers: + out.append(tr('Custom servers set up')) + + if self.custom_repositories: + out.append(tr('Custom repositories set up')) + + return out + def custom_servers_config(self) -> str: config = '' diff --git a/archinstall/lib/models/network.py b/archinstall/lib/models/network.py index 71f3dca2..9e61cb56 100644 --- a/archinstall/lib/models/network.py +++ b/archinstall/lib/models/network.py @@ -3,6 +3,7 @@ from dataclasses import dataclass, field from enum import Enum from typing import NotRequired, Self, TypedDict, override +from archinstall.lib.models.config import SubConfig from archinstall.lib.output import debug from archinstall.lib.translationhandler import tr @@ -103,10 +104,11 @@ class _NetworkConfigurationSerialization(TypedDict): @dataclass -class NetworkConfiguration: +class NetworkConfiguration(SubConfig): type: NicType nics: list[Nic] = field(default_factory=list) + @override def json(self) -> _NetworkConfigurationSerialization: config: _NetworkConfigurationSerialization = {'type': self.type.value} if self.nics: @@ -114,6 +116,10 @@ class NetworkConfiguration: return config + @override + def summary(self) -> str: + return self.type.display_msg() + @classmethod def parse_arg(cls, config: _NetworkConfigurationSerialization) -> Self | None: nic_type = config.get('type', None) diff --git a/archinstall/lib/models/pacman.py b/archinstall/lib/models/pacman.py index 5d271ea9..004dfbc3 100644 --- a/archinstall/lib/models/pacman.py +++ b/archinstall/lib/models/pacman.py @@ -1,6 +1,7 @@ from dataclasses import dataclass -from typing import Self, TypedDict +from typing import Self, TypedDict, override +from archinstall.lib.models.config import SubConfig from archinstall.lib.translationhandler import tr @@ -10,7 +11,7 @@ class PacmanConfigSerialization(TypedDict): @dataclass -class PacmanConfiguration: +class PacmanConfiguration(SubConfig): parallel_downloads: int = 5 color: bool = True @@ -18,12 +19,19 @@ class PacmanConfiguration: def default(cls) -> Self: return cls() + @override def json(self) -> PacmanConfigSerialization: return { 'parallel_downloads': self.parallel_downloads, 'color': self.color, } + @override + def summary(self) -> str | None: + if self.color: + return tr('Color enabled') + return None + def preview(self) -> str: color_str = str(self.color) output = '{}: {}\n'.format(tr('Parallel Downloads'), self.parallel_downloads) diff --git a/archinstall/lib/models/profile.py b/archinstall/lib/models/profile.py index e7ae8c8c..fa890a1e 100644 --- a/archinstall/lib/models/profile.py +++ b/archinstall/lib/models/profile.py @@ -1,8 +1,10 @@ from dataclasses import dataclass -from typing import TYPE_CHECKING, Self, TypedDict +from typing import TYPE_CHECKING, Self, TypedDict, override from archinstall.default_profiles.profile import GreeterType, Profile from archinstall.lib.hardware import GfxDriver +from archinstall.lib.models.config import SubConfig +from archinstall.lib.translationhandler import tr if TYPE_CHECKING: from archinstall.lib.profile.profiles_handler import ProfileSerialization @@ -15,11 +17,12 @@ class _ProfileConfigurationSerialization(TypedDict): @dataclass -class ProfileConfiguration: +class ProfileConfiguration(SubConfig): profile: Profile | None = None gfx_driver: GfxDriver | None = None greeter: GreeterType | None = None + @override def json(self) -> _ProfileConfigurationSerialization: from archinstall.lib.profile.profiles_handler import profile_handler @@ -29,6 +32,23 @@ class ProfileConfiguration: 'greeter': self.greeter.value if self.greeter else None, } + @override + def summary(self) -> list[str] | None: + out: list[str] = [] + + if self.profile: + out.append(self.profile.name) + + if self.gfx_driver: + out.append(tr('{} grphics driver').format(self.gfx_driver.value)) + + if self.greeter: + out.append(tr('{} greeter').format(self.greeter.value)) + + return out + + return None + @classmethod def parse_arg(cls, arg: _ProfileConfigurationSerialization) -> Self: from archinstall.lib.profile.profiles_handler import profile_handler diff --git a/archinstall/lib/output.py b/archinstall/lib/output.py index 3a972dfb..86a47ed1 100644 --- a/archinstall/lib/output.py +++ b/archinstall/lib/output.py @@ -11,6 +11,7 @@ from pathlib import Path from typing import TYPE_CHECKING, Any from archinstall.lib.utils.encoding import unicode_ljust, unicode_rjust +from archinstall.tui.rich import BaseRichTable if TYPE_CHECKING: from _typeshed import DataclassInstance @@ -20,7 +21,7 @@ class FormattedOutput: @staticmethod def _get_values( o: DataclassInstance, - class_formatter: str | Callable | None = None, # type: ignore[type-arg] + class_formatter: str | Callable | None = None, # type: ignore[type-arg] # pyright: ignore[reportMissingTypeArgument] filter_list: list[str] = [], ) -> dict[str, Any]: """ @@ -129,6 +130,35 @@ class FormattedOutput: return output +def as_key_value_pair( + entries: dict[str, str | list[str] | bool], + ignore_empty: bool = True, +) -> str: + """ + Formats key-values as a Rich Table: + key1 : value1 + key2 : value2 + ... + """ + table = BaseRichTable() + table.add_column('key', style='bold', no_wrap=True) + table.add_column('value', style='white', max_width=70) + + for label, value in entries.items(): + if ignore_empty and not value: + continue + + if isinstance(value, bool): + value = 'Yes' if value else 'No' + + if isinstance(value, list): + value = '\n '.join(str(val) for val in value) + + table.add_row(label.title(), f': {value}') + + return table.stringify() + + class Journald: @staticmethod def log(message: str, level: int = logging.DEBUG) -> None: diff --git a/archinstall/tui/components.py b/archinstall/tui/components.py index f732c21f..9fecf3c6 100644 --- a/archinstall/tui/components.py +++ b/archinstall/tui/components.py @@ -350,11 +350,6 @@ class OptionListScreen(BaseScreen[ValueT]): ] ) - # debug(f'Index: {index}') - # debug(f'Region: {option_list.region}') - # debug(f'Scroll offset: {option_list.scroll_offset}') - # debug(f'Target_Y: {target_y}') - self.app.cursor_position = Offset(option_list.region.x, target_y) self.app.refresh() @@ -1163,8 +1158,6 @@ class TableSelectionScreen(BaseScreen[ValueT]): ] ) - debug(f'Setting cursor to target_y: {target_y}') - self.app.cursor_position = Offset(data_table.region.x, target_y) self.app.refresh() diff --git a/archinstall/tui/rich.py b/archinstall/tui/rich.py new file mode 100644 index 00000000..055ec22c --- /dev/null +++ b/archinstall/tui/rich.py @@ -0,0 +1,25 @@ +from io import StringIO + +from rich import box +from rich.console import Console +from rich.table import Table as RichTable + + +class BaseRichTable(RichTable): + def __init__(self) -> None: + super().__init__( + box=box.SIMPLE, + show_header=False, + pad_edge=False, + show_edge=False, + ) + + def stringify(self) -> str: + string_io = StringIO() + buf = Console(file=string_io, highlight=False) + buf.print(self) + + _ = string_io.seek(0) + output = string_io.read() + + return output From f7a6f70fc858280888fce47c01839e0a407b2c20 Mon Sep 17 00:00:00 2001 From: stinga11 Date: Tue, 12 May 2026 18:21:15 -0400 Subject: [PATCH 42/61] Replace terminal and file manager in Budgie profile (#4533) I think it was a mistake to have made the previous changes to KDE Apps. In retrospect, it seemed like a good idea since the Budgie developer had done it that way in Fedora, which is the distro the developer uses as a reference for Budgie. When you start using it, you realize there's no bridge between the desktop and the KDE Apps, and things like Dolphin recognizes icons and themes, requiring some rather annoying manual configurations. Then, if you want to change them again, you have to change those configurations again. Files don't open by default with the apps either; you have to configure them for that to work. At first, I thought the Budgie packager for Arch had forgotten some stray dependency, but with some free time, I tested it with Fedora 44, and to my surprise, it has exactly the same problems, which is completely unacceptable for a final stable release. I suppose he'll make the necessary changes in the near future, but right now, it's a disaster. --- archinstall/default_profiles/desktops/budgie.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/archinstall/default_profiles/desktops/budgie.py b/archinstall/default_profiles/desktops/budgie.py index f156a53b..da147851 100644 --- a/archinstall/default_profiles/desktops/budgie.py +++ b/archinstall/default_profiles/desktops/budgie.py @@ -18,12 +18,13 @@ class BudgieProfile(Profile): return [ 'materia-gtk-theme', 'budgie', - 'konsole', - 'dolphin', + 'mate-terminal', + 'nemo', + 'nemo-fileroller', 'papirus-icon-theme', ] @property @override def default_greeter_type(self) -> GreeterType: - return GreeterType.Sddm + return GreeterType.LightdmSlick From 3dec5025c303d73c2258872bbba3fd4cbfb5b2d6 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 13 May 2026 08:21:44 +1000 Subject: [PATCH 43/61] Update dependency arch/python-pydantic to v2.13.4 (#4534) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 9fee23fa..3bbc7acb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,7 +19,7 @@ classifiers = [ ] dependencies = [ "pyparted==3.13.0", - "pydantic==2.12.5", + "pydantic==2.13.4", "cryptography==48.0.0", "textual==8.2.5", "markdown-it-py==4.0.0", From b95321d38ca7db8f3e0432279bfbd76ba143bb89 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 13 May 2026 08:33:42 +1000 Subject: [PATCH 44/61] Update dependency mypy to v2.1.0 (#4535) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 3bbc7acb..13e0933f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,7 +34,7 @@ Source = "https://github.com/archlinux/archinstall" [project.optional-dependencies] log = ["systemd_python==235"] dev = [ - "mypy==2.0.0", + "mypy==2.1.0", "flake8==7.3.0", "pre-commit==4.6.0", "ruff==0.15.12", From 13944f3ccad8ad6ec7de26791a65eab9aed872d1 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 13 May 2026 08:54:05 +1000 Subject: [PATCH 45/61] Update pre-commit hook pre-commit/mirrors-mypy to v2.1.0 (#4538) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6cf16130..c573ae04 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -31,7 +31,7 @@ repos: args: [--config=.flake8] fail_fast: true - repo: https://github.com/pre-commit/mirrors-mypy - rev: v2.0.0 + rev: v2.1.0 hooks: - id: mypy args: [ From b81fe955f0c789e371b96ed03c79642d4c607f82 Mon Sep 17 00:00:00 2001 From: HADEON <52324046+h8d13@users.noreply.github.com> Date: Fri, 15 May 2026 03:57:31 +0000 Subject: [PATCH 46/61] Add IWD standalone option to network configuration + fix NM_IWD (#4528) * Add iwd standalone option to network configuration Adds NicType.IWD as a third option alongside NM and NM_IWD: install iwd, write /etc/iwd/main.conf with EnableNetworkConfiguration=true and NameResolvingService=systemd, enable iwd.service + systemd-resolved.service. iwd handles DHCP itself and resolved picks up its DNS via the symlink, so no NetworkManager pulled in. Also fills in parse_arg cases for NM_IWD and IWD so config files round-trip both nic types. Assisted-By: Flint * wire up resolv stub and networkd service * exclude virtual devices, dont harcode iface name instead match 'ether' similar pattern to .network files shipped in /etc/systemd/network * use dedent and rename menu option --- archinstall/lib/models/network.py | 7 ++++ archinstall/lib/network/network_handler.py | 42 ++++++++++++++++++++++ archinstall/lib/network/network_menu.py | 2 ++ 3 files changed, 51 insertions(+) diff --git a/archinstall/lib/models/network.py b/archinstall/lib/models/network.py index 9e61cb56..57a579c3 100644 --- a/archinstall/lib/models/network.py +++ b/archinstall/lib/models/network.py @@ -12,6 +12,7 @@ class NicType(Enum): ISO = 'iso' NM = 'nm' NM_IWD = 'nm_iwd' + IWD = 'iwd' MANUAL = 'manual' def display_msg(self) -> str: @@ -22,6 +23,8 @@ class NicType(Enum): return tr('Use Network Manager (default backend)') case NicType.NM_IWD: return tr('Use Network Manager (iwd backend)') + case NicType.IWD: + return tr('Use standalone iwd') case NicType.MANUAL: return tr('Manual configuration') @@ -131,6 +134,10 @@ class NetworkConfiguration(SubConfig): return cls(NicType.ISO) case NicType.NM: return cls(NicType.NM) + case NicType.NM_IWD: + return cls(NicType.NM_IWD) + case NicType.IWD: + return cls(NicType.IWD) case NicType.MANUAL: nics_arg = config.get('nics', []) if nics_arg: diff --git a/archinstall/lib/network/network_handler.py b/archinstall/lib/network/network_handler.py index 13548584..09d2750c 100644 --- a/archinstall/lib/network/network_handler.py +++ b/archinstall/lib/network/network_handler.py @@ -1,3 +1,5 @@ +import textwrap + from archinstall.lib.installer import Installer from archinstall.lib.models.network import NetworkConfiguration, NicType from archinstall.lib.models.profile import ProfileConfiguration @@ -32,6 +34,13 @@ def install_network_config( _configure_nm_iwd(installation) installation.disable_service('iwd.service') + case NicType.IWD: + installation.add_additional_packages(['iwd']) + _configure_iwd_standalone(installation) + installation.enable_service('iwd.service') + installation.enable_service('systemd-networkd.service') + installation.enable_service('systemd-resolved.service') + case NicType.MANUAL: for nic in network_config.nics: installation.configure_nic(nic) @@ -45,3 +54,36 @@ def _configure_nm_iwd(installation: Installer) -> None: iwd_backend_conf = nm_conf_dir / 'wifi_backend.conf' _ = iwd_backend_conf.write_text('[device]\nwifi.backend=iwd\n') + + +def _configure_iwd_standalone(installation: Installer) -> None: + # iwd manages wireless only; systemd-networkd handles wired DHCP. + iwd_conf_dir = installation.target / 'etc/iwd' + iwd_conf_dir.mkdir(parents=True, exist_ok=True) + + main_conf = iwd_conf_dir / 'main.conf' + main_conf_content = textwrap.dedent("""\ + [General] + EnableNetworkConfiguration=true + + [Network] + NameResolvingService=systemd + """) + _ = main_conf.write_text(main_conf_content) + + networkd_dir = installation.target / 'etc/systemd/network' + networkd_dir.mkdir(parents=True, exist_ok=True) + wired_conf = networkd_dir / '20-wired.network' + wired_conf_content = textwrap.dedent("""\ + [Match] + Type=ether + Kind=!* + + [Network] + DHCP=yes + """) + _ = wired_conf.write_text(wired_conf_content) + + resolv = installation.target / 'etc/resolv.conf' + resolv.unlink(missing_ok=True) + resolv.symlink_to('/run/systemd/resolve/stub-resolv.conf') diff --git a/archinstall/lib/network/network_menu.py b/archinstall/lib/network/network_menu.py index 120af19e..88c26dba 100644 --- a/archinstall/lib/network/network_menu.py +++ b/archinstall/lib/network/network_menu.py @@ -202,6 +202,8 @@ async def select_network(preset: NetworkConfiguration | None) -> NetworkConfigur return NetworkConfiguration(NicType.NM) case NicType.NM_IWD: return NetworkConfiguration(NicType.NM_IWD) + case NicType.IWD: + return NetworkConfiguration(NicType.IWD) case NicType.MANUAL: preset_nics = preset.nics if preset else [] nics = await ManualNetworkConfig(tr('Configure interfaces'), preset_nics).show() From 74a1066661f928690c39437598292719f192c16c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 15 May 2026 13:58:00 +1000 Subject: [PATCH 47/61] Update dependency ruff to v0.15.13 (#4540) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 13e0933f..636c8017 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,7 +37,7 @@ dev = [ "mypy==2.1.0", "flake8==7.3.0", "pre-commit==4.6.0", - "ruff==0.15.12", + "ruff==0.15.13", "pylint==4.0.5", "pytest==9.0.3", ] From e48ca45b0bc8be6a093bf624b951455c031d3de0 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 15 May 2026 16:30:33 +1000 Subject: [PATCH 48/61] Update pre-commit hook astral-sh/ruff-pre-commit to v0.15.13 (#4541) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c573ae04..91fd6f33 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,7 +1,7 @@ default_stages: ['pre-commit'] repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.15.12 + rev: v0.15.13 hooks: # fix unused imports and sort them - id: ruff From 516a61d8af56031930d8f527119b358960962cd6 Mon Sep 17 00:00:00 2001 From: Daniel Girtler Date: Fri, 15 May 2026 21:38:17 +1000 Subject: [PATCH 49/61] Enhance log sharing capability (#4526) --- .pre-commit-config.yaml | 1 + archinstall/lib/args.py | 19 ++++-- archinstall/lib/output.py | 64 ++++++++++---------- archinstall/main.py | 43 ++++++++++---- pyproject.toml | 1 + tests/test_share_log.py | 121 ++++++++++++++++++++++++-------------- 6 files changed, 158 insertions(+), 91 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 91fd6f33..de2bcef4 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -41,6 +41,7 @@ repos: additional_dependencies: - pydantic - pytest + - hypothesis - cryptography - textual - repo: local diff --git a/archinstall/lib/args.py b/archinstall/lib/args.py index efe9baa2..14aff6c6 100644 --- a/archinstall/lib/args.py +++ b/archinstall/lib/args.py @@ -6,7 +6,7 @@ import urllib.error import urllib.parse from argparse import ArgumentParser, Namespace from dataclasses import dataclass, field -from enum import StrEnum +from enum import Enum, StrEnum from pathlib import Path from typing import Any, Self from urllib.request import Request, urlopen @@ -35,6 +35,10 @@ from archinstall.lib.version import get_version from archinstall.tui.components import tui +class SubCommand(Enum): + SHARE_LOG = 'share-log' + + @p_dataclass class Arguments: config: Path | None = None @@ -58,6 +62,8 @@ class Arguments: advanced: bool = False verbose: bool = False + command: SubCommand | None = None + class ArchConfigType(StrEnum): VERSION = 'version' @@ -365,13 +371,13 @@ class ArchConfig: class ArchConfigHandler: def __init__(self) -> None: self._parser: ArgumentParser = self._define_arguments() - args: Arguments = self._parse_args() - self._args = args + self._add_sub_parsers() + self._args: Arguments = self._parse_args() config = self._parse_config() try: - self._config = ArchConfig.from_config(config, args) + self._config = ArchConfig.from_config(config, self._args) self._config.version = get_version() except ValueError as err: warn(str(err)) @@ -397,8 +403,13 @@ class ArchConfigHandler: def print_help(self) -> None: self._parser.print_help() + def _add_sub_parsers(self) -> None: + subparsers = self._parser.add_subparsers(dest='command', help='Available subcommands') + _ = subparsers.add_parser(SubCommand.SHARE_LOG.value, help='Upload log file to public server') + def _define_arguments(self) -> ArgumentParser: parser = ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter) + parser.add_argument( '-v', '--version', diff --git a/archinstall/lib/output.py b/archinstall/lib/output.py index 86a47ed1..bc9bc7a3 100644 --- a/archinstall/lib/output.py +++ b/archinstall/lib/output.py @@ -185,6 +185,10 @@ class Logger: def path(self) -> Path: return self._path / 'install.log' + @path.setter + def path(self, value: Path) -> None: + self._path = value + @property def directory(self) -> Path: return self._path @@ -212,6 +216,17 @@ class Logger: level_name = logging.getLevelName(level) f.write(f'[{ts}] - {level_name} - {content}\n') + def get_content(self, max_bytes: int | None = None) -> bytes: + content = self.path.read_bytes() + + if max_bytes is not None: + size = self.path.stat().st_size + + if size > max_bytes: + content = content[-max_bytes:] + + return content + logger = Logger() @@ -295,6 +310,11 @@ def _stylize_output( return f'\033[{ansi}m{text}\033[0m' +def _timestamp() -> str: + now = datetime.now(tz=UTC) + return now.strftime('%Y-%m-%d %H:%M:%S') + + def info( *msgs: str, level: int = logging.INFO, @@ -306,11 +326,6 @@ def info( log(*msgs, level=level, fg=fg, bg=bg, reset=reset, font=font) -def _timestamp() -> str: - now = datetime.now(tz=UTC) - return now.strftime('%Y-%m-%d %H:%M:%S') - - def debug( *msgs: str, level: int = logging.DEBUG, @@ -368,35 +383,20 @@ def log( def share_install_log( - paste_url: str = 'https://paste.rs', - max_size: int = 10 * 1024 * 1024, - confirm: Callable[[str], bool] = lambda _: True, -) -> int: + paste_url: str, + max_bytes: int | None = None, +) -> str | None: log_path = logger.path if not log_path.exists(): info(f'Log file not found: {log_path}') - return 1 + return None - size = log_path.stat().st_size - if size == 0: + content = logger.get_content(max_bytes=max_bytes) + + if len(content) == 0: info(f'Log file is empty: {log_path}') - return 1 - - if size > max_size: - info(f'Log file exceeds {max_size} bytes, uploading last {max_size} bytes') - content = log_path.read_bytes()[-max_size:] - else: - content = log_path.read_bytes() - - header = f'About to upload {log_path} ({len(content)} bytes) to {paste_url}\n\n' - header += 'The log may contain hostname, mirror URLs, package list and partition layout.\n' - header += 'The uploaded paste is public.\n\n' - header += 'Continue?' - - if not confirm(header): - info('Cancelled.') - return 1 + return None try: req = urllib.request.Request(paste_url, data=content) @@ -404,12 +404,10 @@ def share_install_log( url = response.read().decode().strip() except urllib.error.URLError as e: info(f'Upload failed: {e}') - return 1 + return None if not url.startswith('http'): info(f'Unexpected response from {paste_url}: {url[:200]!r}') - return 1 + return None - # raw print so the URL is pipe-friendly (no ANSI colors, no log prefix) - print(url) - return 0 + return url diff --git a/archinstall/main.py b/archinstall/main.py index 9b91681b..f30b01d3 100644 --- a/archinstall/main.py +++ b/archinstall/main.py @@ -8,13 +8,13 @@ import time import traceback from pathlib import Path -from archinstall.lib.args import ArchConfigHandler +from archinstall.lib.args import ArchConfigHandler, SubCommand from archinstall.lib.disk.utils import disk_layouts from archinstall.lib.hardware import SysInfo from archinstall.lib.menu.helpers import Confirmation from archinstall.lib.network.wifi_handler import WifiHandler from archinstall.lib.networking import ping -from archinstall.lib.output import debug, error, info, share_install_log, warn +from archinstall.lib.output import debug, error, info, logger, share_install_log, warn from archinstall.lib.packages.util import check_version_upgrade from archinstall.lib.pacman.pacman import Pacman from archinstall.lib.translationhandler import tr, translation_handler @@ -75,17 +75,36 @@ def _list_scripts() -> str: return '\n'.join(lines) -def _tui_confirm(header: str) -> bool: - async def _ask() -> bool: +def _share_log_command() -> None: + paste_url: str = 'https://paste.rs' + log_path = logger.path + max_size = 10 * 1024 * 1024 # max supported size by paste.rs + content = logger.get_content(max_bytes=max_size).decode() + + header = tr('About to upload "{}" to the publicly accessible {}').format(log_path, paste_url) + '\n\n' + header += tr('Do you want to continue?') + + group = MenuItemGroup.yes_no() + group.set_preview_for_all(lambda _: content) + + async def _confirm() -> bool: result = await Confirmation( - group=MenuItemGroup.yes_no(), header=header, allow_skip=False, - preset=False, + group=group, + preview_header='Log content', + preview_location='bottom', ).show() return result.get_value() - return tui.run(_ask) + result = tui.run(_confirm) + + if result is True: + res = share_install_log(paste_url=paste_url, max_bytes=max_size) + if res is not None: + info(tr('Log uploaded successfully. URL: {}').format(res)) + else: + error(tr('Failed to upload log.')) def run() -> int: @@ -94,15 +113,19 @@ def run() -> int: OR straight as a module: python -m archinstall In any case we will be attempting to load the provided script to be run from the scripts/ folder """ - if 'share-log' in sys.argv: - return share_install_log(confirm=_tui_confirm) - arch_config_handler = ArchConfigHandler() if '--help' in sys.argv or '-h' in sys.argv: arch_config_handler.print_help() return 0 + match arch_config_handler.args.command: + case SubCommand.SHARE_LOG: + _share_log_command() + exit(0) + case None: + pass + script = arch_config_handler.get_script() if script == 'list': diff --git a/pyproject.toml b/pyproject.toml index 636c8017..133bda54 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,6 +40,7 @@ dev = [ "ruff==0.15.13", "pylint==4.0.5", "pytest==9.0.3", + "hypothesis>=6.152.4", ] doc = ["sphinx"] diff --git a/tests/test_share_log.py b/tests/test_share_log.py index c1c3ca84..a6706da8 100644 --- a/tests/test_share_log.py +++ b/tests/test_share_log.py @@ -1,94 +1,127 @@ # pylint: disable=redefined-outer-name +import string import urllib.error from io import BytesIO from pathlib import Path -from unittest.mock import MagicMock, patch +from unittest.mock import patch import pytest +from hypothesis import HealthCheck, given, settings +from hypothesis import strategies as st from archinstall.lib.output import share_install_log +urls = st.builds( + '{}://{}.{}/{}'.format, + st.sampled_from(['http', 'https']), + st.text(alphabet=string.ascii_lowercase, min_size=3, max_size=10), + st.sampled_from(['com', 'net', 'org', 'rs']), + st.text(alphabet=string.ascii_lowercase + string.digits, min_size=0, max_size=8), +) -@pytest.fixture() +max_bytes = st.one_of(st.none(), st.integers(min_value=1, max_value=130)) + +random_paths = st.lists( + st.text( + alphabet=string.ascii_lowercase + string.digits, + min_size=1, + max_size=10, + ), + min_size=1, + max_size=5, +).map(lambda parts: Path(*parts)) + + +@pytest.fixture def log_file(tmp_path: Path) -> Path: log_dir = tmp_path / 'archinstall' log_dir.mkdir() return log_dir / 'install.log' -def _fake_logger(log_file: Path) -> MagicMock: - mock = MagicMock() - mock.path = log_file - return mock +# def _fake_logger(log_file: Path) -> MagicMock: +# mock = MagicMock() +# mock.path = log_file +# return mock -def test_file_not_found(tmp_path: Path) -> None: - missing = tmp_path / 'no-such' / 'install.log' - with patch('archinstall.lib.output.logger', _fake_logger(missing)): - assert share_install_log() == 1 +@given(paste_url=urls, max_byte=max_bytes, sub_path=random_paths) +@settings(max_examples=3, suppress_health_check=[HealthCheck.function_scoped_fixture]) +def test_file_not_found( + tmp_path: Path, + sub_path: Path, + paste_url: str, + max_byte: int | None, +) -> None: + missing_log = tmp_path / sub_path / 'install.log' + + with patch('archinstall.lib.output.logger._path', new=missing_log): + assert share_install_log(paste_url, max_byte) is None -def test_empty_file(log_file: Path) -> None: +@given(paste_url=urls, max_byte=max_bytes) +@settings(max_examples=3, suppress_health_check=[HealthCheck.function_scoped_fixture]) +def test_empty_file(log_file: Path, paste_url: str, max_byte: int | None) -> None: log_file.write_bytes(b'') - with patch('archinstall.lib.output.logger', _fake_logger(log_file)): - assert share_install_log() == 1 + + with patch('archinstall.lib.output.logger._path', new=log_file.parent): + # with patch('archinstall.lib.output.logger', _fake_logger(log_file)): + assert share_install_log(paste_url, max_byte) is None -def test_user_cancels(log_file: Path) -> None: +@given(paste_url=urls, resp_url=urls, max_byte=max_bytes) +@settings(max_examples=3, suppress_health_check=[HealthCheck.function_scoped_fixture]) +def test_successful_upload(log_file: Path, resp_url: str, paste_url: str, max_byte: int | None) -> None: log_file.write_text('some log content') - with patch('archinstall.lib.output.logger', _fake_logger(log_file)): - assert share_install_log(confirm=lambda _: False) == 1 - - -def test_successful_upload(log_file: Path) -> None: - log_file.write_text('some log content') - fake_response = BytesIO(b'https://paste.rs/abc.def') + fake_response = BytesIO(resp_url.encode()) with ( - patch('archinstall.lib.output.logger', _fake_logger(log_file)), - patch('urllib.request.urlopen', return_value=fake_response) as mock_open, + patch('archinstall.lib.output.logger._path', new=log_file.parent), + patch('urllib.request.urlopen', return_value=fake_response), ): - result = share_install_log() - - assert result == 0 - req = mock_open.call_args[0][0] - assert req.data == b'some log content' + result = share_install_log(paste_url, max_byte) + assert result == resp_url -def test_truncation(log_file: Path) -> None: - max_size = 100 +@given(paste_url=urls, resp_url=urls, max_byte=max_bytes) +@settings(max_examples=3, suppress_health_check=[HealthCheck.function_scoped_fixture]) +def test_truncation(log_file: Path, resp_url: str, paste_url: str, max_byte: int | None) -> None: content = b'A' * 50 + b'B' * 80 log_file.write_bytes(content) - fake_response = BytesIO(b'https://paste.rs/abc.def') + fake_response = BytesIO(resp_url.encode()) + + exptected_byte = len(content) if max_byte is None else max_byte with ( - patch('archinstall.lib.output.logger', _fake_logger(log_file)), + patch('archinstall.lib.output.logger._path', new=log_file.parent), patch('urllib.request.urlopen', return_value=fake_response) as mock_open, ): - result = share_install_log(max_size=max_size) - - assert result == 0 - req = mock_open.call_args[0][0] - assert len(req.data) == max_size - assert req.data == content[-max_size:] + _ = share_install_log(paste_url, max_byte) + req = mock_open.call_args[0][0] + assert len(req.data) == exptected_byte + assert req.data == content[-exptected_byte:] -def test_network_error(log_file: Path) -> None: +@given(paste_url=urls, max_byte=max_bytes) +@settings(max_examples=3, suppress_health_check=[HealthCheck.function_scoped_fixture]) +def test_network_error(log_file: Path, paste_url: str, max_byte: int | None) -> None: log_file.write_text('some log content') with ( - patch('archinstall.lib.output.logger', _fake_logger(log_file)), + patch('archinstall.lib.output.logger._path', new=log_file.parent), patch('urllib.request.urlopen', side_effect=urllib.error.URLError('no network')), ): - assert share_install_log() == 1 + assert share_install_log(paste_url, max_byte) is None -def test_unexpected_response(log_file: Path) -> None: +@given(paste_url=urls, max_byte=max_bytes) +@settings(max_examples=3, suppress_health_check=[HealthCheck.function_scoped_fixture]) +def test_unexpected_response(log_file: Path, paste_url: str, max_byte: int | None) -> None: log_file.write_text('some log content') fake_response = BytesIO(b'ERROR: something went wrong') with ( - patch('archinstall.lib.output.logger', _fake_logger(log_file)), + patch('archinstall.lib.output.logger._path', new=log_file.parent), patch('urllib.request.urlopen', return_value=fake_response), ): - assert share_install_log() == 1 + assert share_install_log(paste_url, max_byte) is None From 42d9113611e51473a9bc32d30cb4b9dd6c1963cf Mon Sep 17 00:00:00 2001 From: utuhiro78 <34818411+utuhiro78@users.noreply.github.com> Date: Sat, 16 May 2026 08:29:27 +0900 Subject: [PATCH 50/61] Update Japanese translation (#4544) --- archinstall/locales/ja/LC_MESSAGES/base.mo | Bin 73831 -> 77304 bytes archinstall/locales/ja/LC_MESSAGES/base.po | 173 ++++++++++++++++++++- 2 files changed, 170 insertions(+), 3 deletions(-) diff --git a/archinstall/locales/ja/LC_MESSAGES/base.mo b/archinstall/locales/ja/LC_MESSAGES/base.mo index 96b625c6736d9cfbb9a41f4b467788c4be643657..7d6b89f35c6740d4de8175f15239ebd5fca9d0df 100644 GIT binary patch delta 17999 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 delta 15307 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 Date: Sun, 17 May 2026 03:26:30 +0200 Subject: [PATCH 51/61] Update Italian translation + fix a string (#4545) * Update Italian translation * Added new strings * Improved existing translations * Fix wrong string + Italian translation * Mispelled "respository" -> "repository" --- archinstall/lib/mirror/mirror_menu.py | 2 +- archinstall/locales/base.pot | 159 ++++++++ archinstall/locales/it/LC_MESSAGES/base.mo | Bin 64821 -> 70130 bytes archinstall/locales/it/LC_MESSAGES/base.po | 426 ++++++++++++++++----- 4 files changed, 495 insertions(+), 92 deletions(-) diff --git a/archinstall/lib/mirror/mirror_menu.py b/archinstall/lib/mirror/mirror_menu.py index 5158bffd..275c4df8 100644 --- a/archinstall/lib/mirror/mirror_menu.py +++ b/archinstall/lib/mirror/mirror_menu.py @@ -65,7 +65,7 @@ class CustomMirrorRepositoriesList(ListManager[CustomRepository]): async def _add_custom_repository(self, preset: CustomRepository | None = None) -> CustomRepository | None: edit_result = await Input( - header=tr('Enter a respository name'), + header=tr('Enter a repository name'), allow_skip=True, default_value=preset.name if preset else None, ).show() diff --git a/archinstall/locales/base.pot b/archinstall/locales/base.pot index 0b08df29..e1681526 100644 --- a/archinstall/locales/base.pot +++ b/archinstall/locales/base.pot @@ -2304,3 +2304,162 @@ msgstr "" #, python-brace-format msgid "Choose an option how to give {} access to your hardware" msgstr "" + +#, python-brace-format +msgid "About to upload \"{}\" to the publicly accessible {}" +msgstr "" + +msgid "Do you want to continue?" +msgstr "" + +#, python-brace-format +msgid "Log uploaded successfully. URL: {}" +msgstr "" + +msgid "Failed to upload log." +msgstr "" + +msgid "ArchInstall Language" +msgstr "" + +msgid "Version" +msgstr "" + +msgid "Installation Script" +msgstr "" + +msgid "Application" +msgstr "" + +msgid "Services" +msgstr "" + +msgid "Custom commands" +msgstr "" + +msgid "Users" +msgstr "" + +msgid "Root encrypted password" +msgstr "" + +msgid "Zram enabled" +msgstr "" + +#, python-brace-format +msgid "Zram algorithm {}" +msgstr "" + +msgid "Bluetooth enabled" +msgstr "" + +#, python-brace-format +msgid "Audio server \"{}\"" +msgstr "" + +#, python-brace-format +msgid "Power management \"{}\"" +msgstr "" + +msgid "Print service enabled" +msgstr "" + +#, python-brace-format +msgid "Firewall \"{}\"" +msgstr "" + +#, python-brace-format +msgid "Extra fonts \"{}\"" +msgstr "" + +msgid "Root password set" +msgstr "" + +#, python-brace-format +msgid "Configured {} user(s)" +msgstr "" + +msgid "U2F set up" +msgstr "" + +#, python-brace-format +msgid "Bootloader \"{}\"" +msgstr "" + +msgid "UKI enabled" +msgstr "" + +msgid "Default" +msgstr "" + +msgid "Manual" +msgstr "" + +msgid "Pre-mount" +msgstr "" + +#, python-brace-format +msgid "{} layout" +msgstr "" + +#, python-brace-format +msgid "Devices {}" +msgstr "" + +msgid "LVM set up" +msgstr "" + +#, python-brace-format +msgid "{} encryption" +msgstr "" + +#, python-brace-format +msgid "Btrfs snapshot \"{}\"" +msgstr "" + +#, python-brace-format +msgid "Keyboard layout \"{}\"" +msgstr "" + +#, python-brace-format +msgid "Locale language \"{}\"" +msgstr "" + +#, python-brace-format +msgid "Locale encoding \"{}\"" +msgstr "" + +#, python-brace-format +msgid "Console font \"{}\"" +msgstr "" + +#, python-brace-format +msgid "Mirror regions \"{}\"" +msgstr "" + +#, python-brace-format +msgid "Optional repositories \"{}\"" +msgstr "" + +msgid "Custom servers set up" +msgstr "" + +msgid "Custom repositories set up" +msgstr "" + +msgid "Use standalone iwd" +msgstr "" + +msgid "Color enabled" +msgstr "" + +#, python-brace-format +msgid "{} grphics driver" +msgstr "" + +#, python-brace-format +msgid "{} greeter" +msgstr "" + +msgid "Enter a repository name" +msgstr "" diff --git a/archinstall/locales/it/LC_MESSAGES/base.mo b/archinstall/locales/it/LC_MESSAGES/base.mo index 79d934d42a09dd31cd159321dea69366608193a5..a4166e67fea987d29fb2178c4d252f454b7e95dc 100644 GIT binary patch delta 20840 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, Van Matten\n" "Language: it\n" @@ -56,7 +56,7 @@ msgid "Only packages such as base, base-devel, linux, linux-firmware, efibootmgr msgstr "Vengono installati solo pacchetti come base, base-devel, linux, linux-firmware, efibootmgr e pacchetti di profilo opzionali." msgid "Note: base-devel is no longer installed by default. Add it here if you need build tools." -msgstr "Nota: base-devel non è più installato di default. Aggiungilo qui se ti servono i build tools." +msgstr "Nota: base-devel non è più installato come predefinito. Aggiungilo qui se ti servono i build tools." msgid "If you desire a web browser, such as firefox or chromium, you may specify it in the following prompt." msgstr "Se desideri un browser web, come Firefox o Chromium, puoi specificarlo nel seguente prompt." @@ -118,7 +118,7 @@ msgid "" msgstr "" "{}\n" "\n" -"Seleziona per indice quali partizioni eliminare" +"Seleziona per indice le partizioni da eliminare" msgid "" "{}\n" @@ -127,13 +127,13 @@ msgid "" msgstr "" "{}\n" "\n" -"Seleziona per indice quale partizione montare dove" +"Seleziona per indice la partizione dove montare" msgid " * Partition mount-points are relative to inside the installation, the boot would be /boot as an example." -msgstr " * I punti di mount della partizione sono relativi all'interno dell'installazione, per esempio l'avvio sarebbe /boot." +msgstr " * I punti di montaggio della partizione sono relativi all'interno dell'installazione, per esempio l'avvio sarebbe /boot." msgid "Select where to mount partition (leave blank to remove mountpoint): " -msgstr "Seleziona dove montare la partizione (lascia vuoto per rimuovere il punto di mount): " +msgstr "Seleziona dove montare la partizione (lascia vuoto per rimuovere il punto di montaggio): " msgid "" "{}\n" @@ -142,7 +142,7 @@ msgid "" msgstr "" "{}\n" "\n" -"Seleziona quale partizione mascherare per la formattazione" +"Seleziona la partizione da mascherare per la formattazione" msgid "" "{}\n" @@ -151,7 +151,7 @@ msgid "" msgstr "" "{}\n" "\n" -"Seleziona quale partizione contrassegnare come crittografata" +"Seleziona la partizione da contrassegnare come crittografata" msgid "" "{}\n" @@ -160,7 +160,7 @@ msgid "" msgstr "" "{}\n" "\n" -"Seleziona quale partizione contrassegnare come avviabile" +"Seleziona la partizione da contrassegnare come avviabile" msgid "" "{}\n" @@ -169,7 +169,7 @@ msgid "" msgstr "" "{}\n" "\n" -"Seleziona su quale partizione impostare un filesystem" +"Seleziona la partizione su cui impostare un filesystem" msgid "Enter a desired filesystem type for the partition: " msgstr "Inserisci un tipo di filesystem desiderato per la partizione: " @@ -187,7 +187,7 @@ msgid "Select what you wish to do with the selected block devices" msgstr "Seleziona cosa desideri fare con i dispositivi a blocchi selezionati" msgid "This is a list of pre-programmed profiles, they might make it easier to install things like desktop environments" -msgstr "Questo è un elenco di profili pre-programmati, che potrebbero semplificare l'installazione di elementi come gli ambienti desktop" +msgstr "Questo è un elenco di profili preprogrammati, che potrebbero semplificare l'installazione di elementi come gli ambienti desktop" msgid "Select keyboard layout" msgstr "Seleziona il layout della tastiera" @@ -196,7 +196,7 @@ msgid "Select one of the regions to download packages from" msgstr "Seleziona una delle regioni da cui scaricare i pacchetti" msgid "Select one or more hard drives to use and configure" -msgstr "Seleziona uno o più dischi rigidi da utilizzare e configurare" +msgstr "Seleziona uno o più unità da utilizzare e configurare" msgid "For the best compatibility with your AMD hardware, you may want to use either the all open-source or AMD / ATI options." msgstr "Per la migliore compatibilità con il tuo hardware AMD, potresti voler utilizzare tutte le opzioni open source o AMD / ATI." @@ -220,13 +220,13 @@ msgid "All open-source (default)" msgstr "Tutti gli open source (predefinito)" msgid "Choose which kernels to use or leave blank for default \"{}\"" -msgstr "Scegli quali kernel usare o lascia vuoto per il predefinito \"{}\"" +msgstr "Scegli i kernel da usare o lascia vuoto per il predefinito \"{}\"" msgid "Choose which locale language to use" -msgstr "Scegli quale lingua locale utilizzare" +msgstr "Scegli la lingua da usare" msgid "Choose which locale encoding to use" -msgstr "Scegli quale codifica locale utilizzare" +msgstr "Scegli la codifica da usare" msgid "Select one of the values shown below: " msgstr "Seleziona uno dei valori mostrati di seguito: " @@ -235,7 +235,7 @@ msgid "Select one or more of the options below: " msgstr "Seleziona una o più delle seguenti opzioni " msgid "Adding partition...." -msgstr "Aggiungendo la partizione...." +msgstr "Aggiunta della partizione in corso..." msgid "You need to enter a valid fs-type in order to continue. See `man parted` for valid fs-type's." msgstr "Devi inserire un tipo di filesystem valido per continuare. Vedi `man parted` per tipi di filesystem validi." @@ -253,13 +253,16 @@ msgid "Mirror region" msgstr "Regione dei mirror" msgid "Locale language" -msgstr "Lingua locale" +msgstr "Lingua" msgid "Locale encoding" -msgstr "Codifica locale" +msgstr "Codifica" + +msgid "Console font" +msgstr "Font della console" msgid "Drive(s)" -msgstr "Dischi" +msgstr "Unità" msgid "Disk layout" msgstr "Layout del disco" @@ -301,7 +304,7 @@ msgid "Automatic time sync (NTP)" msgstr "Sincronizzazione automatica dell'ora (NTP)" msgid "Install ({} config(s) missing)" -msgstr "Installa ({} configurazioni mancanti)" +msgstr "Installa ({} configurazione/i mancante/i)" msgid "" "You decided to skip harddrive selection\n" @@ -309,7 +312,7 @@ msgid "" "WARNING: Archinstall won't check the suitability of this setup\n" "Do you wish to continue?" msgstr "" -"Hai deciso di saltare la selezione del disco rigido\n" +"Hai deciso di saltare la selezione dell'unità\n" "e utilizzerà qualsiasi configurazione dell'unità montata su {} (sperimentale)\n" "ATTENZIONE: Archinstall non verificherà l'idoneità di questa configurazione\n" "Vuoi continuare?" @@ -327,7 +330,7 @@ msgid "Clear/Delete all partitions" msgstr "Cancella/Elimina tutte le partizioni" msgid "Assign mount-point for a partition" -msgstr "Assegna punto di mount per una partizione" +msgstr "Assegna punto di montaggio per una partizione" msgid "Mark/Unmark a partition to be formatted (wipes data)" msgstr "Seleziona/Deseleziona una partizione da formattare (cancella i dati)" @@ -376,7 +379,7 @@ msgid "Enter a encryption password for {}" msgstr "Inserisci una password di crittografia per {}" msgid "Enter disk encryption password (leave blank for no encryption): " -msgstr "Inserisci la password di crittografia del disco (lasciare vuoto per nessuna crittografia): " +msgstr "Inserisci la password di crittografia del disco (lascia vuoto per nessuna crittografia): " msgid "Create a required super-user with sudo privileges: " msgstr "Crea un superuser richiesto con privilegi sudo: " @@ -454,7 +457,7 @@ msgid "Pre-existing pacman lock never exited. Please clean up any existing pacma msgstr "Il lock di pacman preesistente non è mai terminato. Rimuovi ogni sessione pacman esistente prima di usare archinstall." msgid "Choose which optional additional repositories to enable" -msgstr "Scegli quali repository aggiuntivi facoltativi abilitare" +msgstr "Scegli i repository aggiuntivi facoltativi da abilitare" msgid "Add a user" msgstr "Aggiungi un utente" @@ -476,7 +479,7 @@ msgstr "" "Definisci un nuovo utente\n" msgid "User Name : " -msgstr "Nome utente : " +msgstr "Nome utente: " msgid "Should {} be a superuser (sudoer)?" msgstr "{} dovrebbe essere un superuser (sudoer)?" @@ -497,7 +500,7 @@ msgid "" msgstr "" "{}\n" "\n" -"Seleziona su quale partizione impostare i sottovolumi" +"Seleziona la partizione su cui impostare i sottovolumi" msgid "Manage btrfs subvolumes for current partition" msgstr "Gestisci i sottovolumi btrfs per la partizione corrente" @@ -518,7 +521,7 @@ msgid "Save all" msgstr "Salva tutto" msgid "Choose which configuration to save" -msgstr "Scegli quale configurazione salvare" +msgstr "Scegli la configurazione da salvare" msgid "Enter a directory for the configuration(s) to be saved: " msgstr "Inserisci una cartella in cui salvare le configurazioni: " @@ -570,7 +573,7 @@ msgid "Subvolume name " msgstr "Nome del sottovolume " msgid "Subvolume mountpoint" -msgstr "Punto di mount del sottovolume" +msgstr "Punto di montaggio del sottovolume" msgid "Subvolume options" msgstr "Opzioni del sottovolume" @@ -579,10 +582,10 @@ msgid "Save" msgstr "Salva" msgid "Subvolume name :" -msgstr "Nome del sottovolume :" +msgstr "Nome del sottovolume:" msgid "Select a mount point :" -msgstr "Seleziona un punto di mount:" +msgstr "Seleziona un punto di montaggio:" msgid "Select the desired subvolume options " msgstr "Seleziona le opzioni del sottovolume desiderate " @@ -607,10 +610,10 @@ msgid "The selected drives do not have the minimum capacity required for an auto msgstr "Le unità selezionate non hanno la capacità minima richiesta per un suggerimento automatico\n" msgid "Minimum capacity for /home partition: {}GB\n" -msgstr "Capacità minima per la partizione /home: {}GB\n" +msgstr "Capacità minima per la partizione /home: {} GB\n" msgid "Minimum capacity for Arch Linux partition: {}GB" -msgstr "Capacità minima per la partizione Arch Linux: {}GB" +msgstr "Capacità minima per la partizione Arch Linux: {} GB" msgid "Continue" msgstr "Continua" @@ -661,13 +664,13 @@ msgid "Select your desired desktop environment" msgstr "Seleziona l'ambiente desktop desiderato" msgid "A very basic installation that allows you to customize Arch Linux as you see fit." -msgstr "Un'installazione molto semplice che ti consente di personalizzare Arch Linux come meglio credi." +msgstr "Un'installazione molto semplificata che ti consente di personalizzare Arch Linux come meglio credi." msgid "Provides a selection of various server packages to install and enable, e.g. httpd, nginx, mariadb" msgstr "Fornisce una selezione di vari pacchetti server da installare e abilitare, per esempio httpd, nginx, mariadb" msgid "Choose which servers to install, if none then a minimal installation will be done" -msgstr "Scegli quali server installare, se nessuno verrà eseguita un'installazione minima" +msgstr "Scegli i server da installare, se non ne sarà selezionato nessuno, verrà eseguita un'installazione minima" msgid "Installs a minimal system as well as xorg and graphics drivers." msgstr "Installa un sistema minimo oltre a xorg e driver grafici." @@ -682,13 +685,13 @@ msgid "Are you sure you want to reset this setting?" msgstr "Sei sicuro di voler ripristinare questa impostazione?" msgid "Select one or more hard drives to use and configure\n" -msgstr "Seleziona uno o più dischi rigidi da utilizzare e configurare\n" +msgstr "Seleziona uno o più unità da utilizzare e configurare\n" msgid "Any modifications to the existing setting will reset the disk layout!" msgstr "Qualsiasi modifica all'impostazione esistente ripristinerà il layout del disco!" msgid "If you reset the harddrive selection this will also reset the current disk layout. Are you sure?" -msgstr "Se si ripristina la selezione del disco rigido, verrà ripristinato anche il layout del disco corrente. Sei sicuro?" +msgstr "Se ripristini la selezione dell'unità, verrà ripristinato anche il layout del disco corrente. Sei sicuro?" msgid "Save and exit" msgstr "Salva ed esci" @@ -741,7 +744,7 @@ msgid "Select one of the disks or skip and use /mnt as default" msgstr "Seleziona uno dei dischi o salta e usa /mnt come predefinito" msgid "Select which partitions to mark for formatting:" -msgstr "Seleziona quali partizioni contrassegnare per la formattazione:" +msgstr "Seleziona le partizioni da contrassegnare per la formattazione:" msgid "Use HSM to unlock encrypted drive" msgstr "Utilizza HSM per sbloccare l'unità crittografata" @@ -798,14 +801,14 @@ msgid "Configured {} interfaces" msgstr "Interfacce {} configurate" msgid "This option enables the number of parallel downloads that can occur during installation" -msgstr "Questa opzione consente di impostare il numero di download paralleli che possono avvenire durante l'installazione" +msgstr "Questa opzione consente di impostare il numero di download in parallelo che possono avvenire durante l'installazione" msgid "" "Enter the number of parallel downloads to be enabled.\n" " (Enter a value between 1 to {})\n" "Note:" msgstr "" -"Inserisci il numero di download paralleli da abilitare.\n" +"Inserisci il numero di download in parallelo da abilitare.\n" " (Inserisci un valore compreso tra 1 e {})\n" "Nota:" @@ -816,20 +819,33 @@ msgid " - Minimum value : 1 ( Allows 1 parallel download, allows 2 downloads a msgstr " - Valore minimo : 1 ( Consente 1 download parallelo, consente 2 download alla volta )" msgid " - Disable/Default : 0 ( Disables parallel downloading, allows only 1 download at a time )" -msgstr " - Disabilita/Predefinito : 0 ( Disabilita il download parallelo, consente solo 1 download alla volta )" +msgstr " - Disabilita/Predefinito : 0 ( Disabilita il download in parallelo, consente solo 1 download alla volta )" #, python-brace-format msgid "Invalid input! Try again with a valid input [1 to {max_downloads}, or 0 to disable]" msgstr "Input non valido! Riprova con un input valido [da 1 a {max_downloads}, o 0 per disabilitare]." msgid "Parallel Downloads" -msgstr "Download paralleli" +msgstr "Download in parallelo" + +msgid "Pacman" +msgstr "Pacman" + +msgid "Color" +msgstr "Colore" + +#, python-brace-format +msgid "Enter the number of parallel downloads (1-{})" +msgstr "Inserisci il numero di download in parallelo (1-{})" + +msgid "Enable colored output for pacman" +msgstr "Attiva l'output colorato di pacman" msgid "ESC to skip" msgstr "ESC per saltare" msgid "CTRL+C to reset" -msgstr "CTRL+C per resettare" +msgstr "CTRL+C per ripristinare" msgid "TAB to select" msgstr "TAB per selezionare" @@ -861,27 +877,27 @@ msgid "Select one or more devices to use and configure" msgstr "Seleziona uno o più dispositivi da utilizzare e configurare" msgid "If you reset the device selection this will also reset the current disk layout. Are you sure?" -msgstr "Se si ripristina la selezione del disco rigido, verrà ripristinato anche il layout del disco corrente. Sei sicuro?" +msgstr "Se ripristini la selezione del dispositivo, verrà ripristinato anche il layout del disco corrente. Sei sicuro?" msgid "Existing Partitions" msgstr "Partizioni esistenti" msgid "Select a partitioning option" -msgstr "Selezione opzione di partizionamento" +msgstr "Seleziona un'opzione di partizionamento" msgid "Enter the root directory of the mounted devices: " msgstr "Inserisci la cartella principale dei dispositivi montati: " #, python-brace-format msgid "Minimum capacity for /home partition: {}GiB\n" -msgstr "Capacità minima per la partizione /home: {}GiB\n" +msgstr "Capacità minima per la partizione /home: {} GiB\n" #, python-brace-format msgid "Minimum capacity for Arch Linux partition: {}GiB" -msgstr "Capacità minima per la partizione Arch Linux: {}GiB" +msgstr "Capacità minima per la partizione Arch Linux: {} GiB" msgid "This is a list of pre-programmed profiles_bck, they might make it easier to install things like desktop environments" -msgstr "Questo è un elenco di profili pre-programmati, che potrebbero semplificare l'installazione di elementi come gli ambienti desktop" +msgstr "Questo è un elenco di profiles_bck preprogrammati, che potrebbero semplificare l'installazione di elementi come gli ambienti desktop" msgid "Current profile selection" msgstr "Selezione profilo corrente" @@ -890,7 +906,7 @@ msgid "Remove all newly added partitions" msgstr "Elimina tutte le partizioni appena aggiunte" msgid "Assign mountpoint" -msgstr "Assegna punto di mount" +msgstr "Assegna punto di montaggio" msgid "Mark/Unmark to be formatted (wipes data)" msgstr "Seleziona/Deseleziona come da formattare (cancella i dati)" @@ -917,13 +933,13 @@ msgid "This partition is currently encrypted, to format it a filesystem has to b msgstr "Questa partizione è attualmente crittografata, per formattarla è necessario specificare un filesystem" msgid "Partition mount-points are relative to inside the installation, the boot would be /boot as an example." -msgstr "I punti di mount della partizione sono relativi all'interno dell'installazione, l'avvio sarebbe per esempio /boot." +msgstr "I punti di montaggio della partizione sono relativi all'interno dell'installazione, l'avvio sarebbe per esempio /boot." msgid "If mountpoint /boot is set, then the partition will also be marked as bootable." -msgstr "Se il punto di mount /boot è impostato, anche la partizione sarà contrassegnata come avviabile." +msgstr "Se il punto di montaggio /boot è impostato, anche la partizione sarà contrassegnata come avviabile." msgid "Mountpoint: " -msgstr "Punto di mount: " +msgstr "Punto di montaggio: " msgid "Current free sectors on device {}:" msgstr "Settori attualmente liberi sul dispositivo {}:" @@ -985,7 +1001,7 @@ msgid "Partitions to be encrypted" msgstr "Partizioni da crittografare" msgid "Select disk encryption option" -msgstr "Selezione opzione per crittografia disco" +msgstr "Seleziona un'opzione per la crittografia del disco" msgid "Select a FIDO2 device to use for HSM" msgstr "Seleziona un dispositivo FIDO2 da utilizzare per HSM" @@ -1028,7 +1044,7 @@ msgid "Back" msgstr "Indietro" msgid "Please chose which greeter to install for the chosen profiles: {}" -msgstr "Scegli quale greeter installare per i profili scelti: {}" +msgstr "Scegli il greeter da installare per i profili scelti: {}" #, python-brace-format msgid "Environment type: {}" @@ -1075,7 +1091,7 @@ msgstr "" "Seleziona un driver grafico o lascia vuoto per installare tutti i driver open source" msgid "Sway needs access to your seat (collection of hardware devices i.e. keyboard, mouse, etc)" -msgstr "Sway ha bisogno dell’accesso al tuo posto (insieme di dispositivi hardware, ad esempio tastiera, mouse, ecc.)" +msgstr "Sway richiede l’accesso al tuo posto (insieme di dispositivi hardware, ad esempio tastiera, mouse, ecc.)" msgid "" "\n" @@ -1093,13 +1109,13 @@ msgid "Greeter" msgstr "Greeter" msgid "Please chose which greeter to install" -msgstr "Scegli quale greeter installare" +msgstr "Scegli il greeter da installare" msgid "This is a list of pre-programmed default_profiles" msgstr "Questo è un elenco di default_profiles preprogrammati" msgid "Disk configuration" -msgstr "Configurazione disco" +msgstr "Configurazione del disco" msgid "Profiles" msgstr "Profili" @@ -1144,7 +1160,7 @@ msgid "" "Enter a directory for the configuration(s) to be saved (tab completion enabled)\n" "Save directory: " msgstr "" -"Inserisci una cartella in cui salvare le configurazioni (completamento col tasto tab abilitato)\n" +"Inserisci una cartella in cui salvare le configurazioni (completamento col tasto tab attivo)\n" "Cartella di salvataggio: " msgid "" @@ -1166,7 +1182,7 @@ msgid "Mirror regions" msgstr "Regione dei mirror" msgid " - Maximum value : {} ( Allows {} parallel downloads, allows {max_downloads+1} downloads at a time )" -msgstr " - Valore massimo : {} ( Consente {} download paralleli, consente {max_downloads+1} downloads alla volta )" +msgstr " - Valore massimo : {} ( Consente {} download in parallelo, consente {max_downloads+1} download alla volta )" msgid "Invalid input! Try again with a valid input [1 to {}, or 0 to disable]" msgstr "Input non valido! Riprova con un input valido [da 1 a {}, o 0 per disabilitare]." @@ -1208,33 +1224,48 @@ msgstr "Prodotto" msgid "Invalid configuration: {error}" msgstr "Configurazione non valida: {error}" +msgid "Ready to install" +msgstr "Pronto per l'installazione" + +msgid "Disks" +msgstr "Dischi" + +msgid "Packages" +msgstr "Pacchetti" + +msgid "Network" +msgstr "Rete" + +msgid "Locale" +msgstr "Localizzazione" + msgid "Type" msgstr "Tipo" msgid "This option enables the number of parallel downloads that can occur during package downloads" -msgstr "Questa opzione consente di impostare il numero di download paralleli che possono avvenire durante l'installazione" +msgstr "Questa opzione consente di impostare il numero di download in parallelo che possono avvenire durante l'installazione" msgid "" "Enter the number of parallel downloads to be enabled.\n" "\n" "Note:\n" msgstr "" -"Inserisci il numero di download paralleli da abilitare.\n" +"Inserisci il numero di download in parallelo da abilitare.\n" "\n" "Nota:\n" #, python-brace-format msgid " - Maximum recommended value : {} ( Allows {} parallel downloads at a time )" -msgstr " - Valore massimo raccomandato : {} ( Consente {} download paralleli)" +msgstr " - Valore massimo raccomandato : {} ( Consente {} download in parallelo)" msgid " - Disable/Default : 0 ( Disables parallel downloading, allows only 1 download at a time )\n" -msgstr " - Disabilita/Predefinito : 0 ( Disabilita il download parallelo, consente solo 1 download alla volta )\n" +msgstr " - Disabilita/Predefinito : 0 ( Disabilita il download in parallelo, consente solo 1 download alla volta )\n" msgid "Invalid input! Try again with a valid input [or 0 to disable]" msgstr "Input non valido! Riprova con un input valido [0 per disabilitare]." msgid "Hyprland needs access to your seat (collection of hardware devices i.e. keyboard, mouse, etc)" -msgstr "Hyprland ha bisogno dell’accesso al tuo posto (insieme di dispositivi hardware, ad esempio tastiera, mouse, ecc.)" +msgstr "Hyprland richiede l’accesso al tuo posto (insieme di dispositivi hardware, ad esempio tastiera, mouse, ecc.)" msgid "" "\n" @@ -1270,7 +1301,7 @@ msgid "Selected profiles: " msgstr "Profili selezionati: " msgid "Time synchronization not completing, while you wait - check the docs for workarounds: https://archinstall.readthedocs.io/" -msgstr "La sincronizzazione dell’orario non si sta completando, in attesa, leggi la documentazione alla ricerca di una soluzione: https://archinstall.readthedocs.io/" +msgstr "La sincronizzazione dell’orario non si sta completando, mentre aspetti leggi la documentazione alla ricerca di una soluzione: https://archinstall.readthedocs.io/" msgid "Mark/Unmark as nodatacow" msgstr "Seleziona/Deseleziona come nodatacow" @@ -1322,7 +1353,7 @@ msgid "LVM volumes to be encrypted" msgstr "Volumi LVM da crittografare" msgid "Select which LVM volumes to encrypt" -msgstr "Seleziona volumi LVM da crittografare" +msgstr "Seleziona i volumi LVM da crittografare" msgid "Default layout" msgstr "Layout predefinito" @@ -1361,7 +1392,7 @@ msgid "Seat access" msgstr "Accesso al posto" msgid "Mountpoint" -msgstr "Punto di mount" +msgstr "Punto di montaggio" msgid "HSM" msgstr "HSM" @@ -1479,7 +1510,7 @@ msgid "Directory" msgstr "Cartella" msgid "Enter a directory for the configuration(s) to be saved (tab completion enabled)" -msgstr "Inserisci una cartella in cui salvare le configurazioni (completamento col tasto tab abilitato)" +msgstr "Inserisci una cartella in cui salvare le configurazioni (completamento col tasto tab attivo)" #, python-brace-format msgid "Do you want to save the configuration file(s) to {}?" @@ -1513,7 +1544,7 @@ msgid "Choose an option to give Hyprland access to your hardware" msgstr "Scegli un’opzione per concedere a Hyprland l'accesso al tuo hardware" msgid "Additional repositories" -msgstr "Repository aggiuntive" +msgstr "Repository aggiuntivi" msgid "NTP" msgstr "NTP" @@ -1543,7 +1574,7 @@ msgid "HSM device" msgstr "Dispositivo HSM" msgid "Some packages could not be found in the repository" -msgstr "Alcuni pacchetti non sono stati trovati nella repository" +msgstr "Alcuni pacchetti non sono stati trovati nel repository" msgid "User" msgstr "Utente" @@ -1564,16 +1595,16 @@ msgid "Select any packages from the below list that should be installed addition msgstr "Seleziona dalla lista sotto i pacchetti aggiuntivi da installare" msgid "Add a custom repository" -msgstr "Aggiungi una repository personalizzata" +msgstr "Aggiungi un repository personalizzato" msgid "Change custom repository" -msgstr "Cambia repository personalizzata" +msgstr "Cambia repository personalizzato" msgid "Delete custom repository" -msgstr "Elimina repository personalizzata" +msgstr "Elimina repository personalizzato" msgid "Repository name" -msgstr "Nome repository" +msgstr "Nome del repository" msgid "Add a custom server" msgstr "Aggiungi un server personalizzato" @@ -1594,7 +1625,7 @@ msgid "Add custom servers" msgstr "Aggiungi server personalizzati" msgid "Add custom repository" -msgstr "Aggiungi repository personalizzate" +msgstr "Aggiungi repository personalizzato" msgid "Loading mirror regions..." msgstr "Caricamento regioni dei mirror in corso..." @@ -1609,7 +1640,7 @@ msgid "Custom servers" msgstr "Server personalizzati" msgid "Custom repositories" -msgstr "Repository personalizzate" +msgstr "Repository personalizzati" msgid "Only ASCII characters are supported" msgstr "Sono supportati solo caratteri ASCII" @@ -1756,7 +1787,7 @@ msgid "Toggle option" msgstr "Opzioni di attivazione" msgid "Scroll Up" -msgstr "Scorri Su" +msgstr "Scorri su" msgid "Scroll Down" msgstr "Scorri giù" @@ -1783,13 +1814,13 @@ msgid "Copy selected text" msgstr "Copia il testo selezionato" msgid "labwc needs access to your seat (collection of hardware devices i.e. keyboard, mouse, etc)" -msgstr "labwc ha bisogno dell’accesso al tuo posto (insieme di dispositivi hardware, ad esempio tastiera, mouse, ecc.)" +msgstr "labwc richiede l’accesso al tuo posto (insieme di dispositivi hardware, ad esempio tastiera, mouse, ecc.)" msgid "Choose an option to give labwc access to your hardware" msgstr "Scegli un’opzione per concedere a labwc l’accesso al tuo hardware" msgid "niri needs access to your seat (collection of hardware devices i.e. keyboard, mouse, etc)" -msgstr "niri ha bisogno dell’accesso al tuo posto (insieme di dispositivi hardware, ad esempio tastiera, mouse, ecc.)" +msgstr "niri richiede l’accesso al tuo posto (insieme di dispositivi hardware, ad esempio tastiera, mouse, ecc.)" msgid "Choose an option to give niri access to your hardware" msgstr "Scegli un’opzione per concedere a niri l’accesso al tuo hardware" @@ -1832,7 +1863,7 @@ msgid "Do you want to encrypt the user_credentials.json file?" msgstr "Vuoi criptare il file di user_credentials.json?" msgid "Credentials file encryption password" -msgstr "Password di crittazione del file delle credenziali" +msgstr "Password di crittografia del file delle credenziali" #, python-brace-format msgid "Repositories: {}" @@ -1917,7 +1948,7 @@ msgid "No network connection found" msgstr "Nessuna connessione di rete trovata" msgid "Would you like to connect to a Wifi?" -msgstr "Desideri connetterti ad una Wi-FI?" +msgstr "Desideri connetterti ad una Wi-Fi?" msgid "No wifi interface found" msgstr "Nessuna interfaccia Wi-Fi trovata" @@ -2000,11 +2031,26 @@ msgstr "Usa NetworkManager (backend iwd)" msgid "Firewall" msgstr "Firewall" +msgid "Additional fonts" +msgstr "Font aggiuntivi" + +msgid "Select font packages to install" +msgstr "Seleziona i pacchetti di font da installare" + +msgid "Unicode font coverage for most languages" +msgstr "Copertura Unicode del font per la maggior parte delle lingue" + +msgid "color emoji for browsers and apps" +msgstr "emoji a colori per browser e app" + +msgid "Chinese, Japanese, Korean characters" +msgstr "Caratteri cinesi, giapponesi, coreani" + msgid "Select audio configuration" msgstr "Seleziona la configurazione audio" msgid "Enter credentials file decryption password" -msgstr "Inserici la password di decrittazione del file delle credenziali" +msgstr "Inserisci la password di decrittazione del file delle credenziali" msgid "Enter root password" msgstr "Inserisci la password di root" @@ -2025,7 +2071,7 @@ msgid "Select disks for the installation" msgstr "Seleziona il disco per l'installazione" msgid "Enter a mountpoint" -msgstr "Inserisci un punto di mount" +msgstr "Inserisci un punto di montaggio" #, python-brace-format msgid "Enter a size (default: {}): " @@ -2035,7 +2081,7 @@ msgid "Enter subvolume name" msgstr "Inserisci il nome del sottovolume" msgid "Enter subvolume mountpoint" -msgstr "Inserisci il punto di mount del sottovolume" +msgstr "Inserisci il punto di montaggio del sottovolume" msgid "Select a disk configuration" msgstr "Seleziona una configurazione del disco" @@ -2047,7 +2093,7 @@ msgid "You will use whatever drive-setup is mounted at the specified directory" msgstr "Userai qualunque configurazione del disco che sia montata nella cartella specificata" msgid "WARNING: Archinstall won't check the suitability of this setup" -msgstr "ATTENZIONE: Archinstall non verificherà la compatibilità di di questa configurazione" +msgstr "ATTENZIONE: Archinstall non verificherà la compatibilità di questa configurazione" msgid "Select main filesystem" msgstr "Seleziona il filesystem principale" @@ -2066,22 +2112,22 @@ msgid "Value must be between 1 and {}" msgstr "Il valore deve essere tra 1 e {}" msgid "Select which kernel(s) to install" -msgstr "Scegli quali kernel installare" +msgstr "Seleziona i kernel da installare" msgid "Enter a respository name" msgstr "Inserisci il nome di un repository" msgid "Enter the repository url" -msgstr "Inserisci l'URL della repository" +msgstr "Inserisci l'URL del repository" msgid "Enter server url" msgstr "Inserisci l'URL del server" msgid "Select mirror regions to be enabled" -msgstr "Seleziona quali regioni dei mirror abilitare" +msgstr "Seleziona le regioni dei mirror da abilitare" msgid "Select optional repositories to be enabled" -msgstr "Seleziona quali repository aggiuntive opzionali abilitare" +msgstr "Seleziona i repository opzionali da abilitare" msgid "Select an interface" msgstr "Seleziona un'interfaccia" @@ -2089,11 +2135,17 @@ msgstr "Seleziona un'interfaccia" msgid "Choose network configuration" msgstr "Scegli la configurazione di rete" +msgid "Recommended: Network Manager for desktop, Manual for server" +msgstr "Raccomandato: Network Manager per computer, Manuale per server" + +msgid "Warning: no network configuration selected. Network will need to be set up manually on the installed system." +msgstr "Attenzione: nessuna configurazione di rete selezionata. La rete dovrà essere impostata manualmente sul sistema installato." + msgid "No packages found" msgstr "Nessun pacchetto trovato" msgid "Select which greeter to install" -msgstr "Seleziona quale greeter installare" +msgstr "Seleziona il greeter da installare" msgid "Select a profile type" msgstr "Seleziona un tipo di profilo" @@ -2126,6 +2178,9 @@ msgstr "Il profilo desktop selezionato richiede un utente regolare per accedere msgid "Environment type: {} {}" msgstr "Tipo di ambiente: {} {}" +msgid "Input cannot be empty" +msgstr "Il campo non può essere vuoto" + msgid "Recommended" msgstr "Raccomandato" @@ -2156,5 +2211,194 @@ msgstr "Descrizione" msgid "Select a flavor of KDE Plasma to install" msgstr "Seleziona la configurazione di KDE Plasma da installare" -msgid "Input cannot be empty" -msgstr "Il campo non può essere vuoto" +msgid "Arial/Times/Courier replacement, Cyrillic support for Steam/games" +msgstr "Sostituto di Arial/Times/Courier, supporta il cirillico per Steam/giochi" + +msgid "wide Unicode coverage, good fallback font" +msgstr "ampia copertura Unicode, ottimo font di riserva" + +#, python-brace-format +msgid "Setting up U2F login: {u2f_config.u2f_login_method.value}" +msgstr "Impostazione accesso U2F: {u2f_config.u2f_login_method.value}" + +#, python-brace-format +msgid "Default: {DEFAULT_ITER_TIME}ms, Recommended range: 1000-60000" +msgstr "Predefinito: {DEFAULT_ITER_TIME} ms, Intervallo consigliato: 1000-60000" + +#, python-brace-format +msgid "Setting up U2F login: {}" +msgstr "Impostazione accesso U2F: {}" + +#, python-brace-format +msgid "Default: {}ms, Recommended range: 1000-60000" +msgstr "Predefinito: {} ms, Intervallo consigliato: 1000-60000" + +#, python-brace-format +msgid "{} needs access to your seat" +msgstr "{} richiede l’accesso al tuo posto" + +msgid "collection of hardware devices i.e. keyboard, mouse" +msgstr "insieme di dispositivi hardware, ad esempio tastiera e mouse" + +#, python-brace-format +msgid "Choose an option how to give {} access to your hardware" +msgstr "Scegli un’opzione per concedere a {} l’accesso al tuo hardware" + +#, python-brace-format +msgid "About to upload \"{}\" to the publicly accessible {}" +msgstr "\"{}\" sta per essere caricato per essere pubblicamente accessibile {}" + +msgid "Do you want to continue?" +msgstr "Vuoi continuare?" + +#, python-brace-format +msgid "Log uploaded successfully. URL: {}" +msgstr "Log caricato con successo. URL: {}" + +msgid "Failed to upload log." +msgstr "Impossibile caricare il log." + +msgid "ArchInstall Language" +msgstr "Lingua di ArchInstall" + +msgid "Version" +msgstr "Versione" + +msgid "Installation Script" +msgstr "Script di installazione" + +msgid "Application" +msgstr "Applicazione" + +msgid "Services" +msgstr "Servizi" + +msgid "Custom commands" +msgstr "Comandi personalizzati" + +msgid "Users" +msgstr "Utenti" + +msgid "Root encrypted password" +msgstr "Password di root crittografata" + +msgid "Zram enabled" +msgstr "Zram attiva" + +#, python-brace-format +msgid "Zram algorithm {}" +msgstr "Algoritmo Zram {}" + +msgid "Bluetooth enabled" +msgstr "Bluetooth attivo" + +#, python-brace-format +msgid "Audio server \"{}\"" +msgstr "Server audio \"{}\"" + +#, python-brace-format +msgid "Power management \"{}\"" +msgstr "Gestione energetica \"{}\"" + +msgid "Print service enabled" +msgstr "Servizio di stampa attivo" + +#, python-brace-format +msgid "Firewall \"{}\"" +msgstr "Firewall \"{}\"" + +#, python-brace-format +msgid "Extra fonts \"{}\"" +msgstr "Font aggiuntivi \"{}\"" + +msgid "Root password set" +msgstr "Imposta la password di root" + +#, python-brace-format +msgid "Configured {} user(s)" +msgstr "{} utente/i configurato/i" + +msgid "U2F set up" +msgstr "Imposta U2F" + +#, python-brace-format +msgid "Bootloader \"{}\"" +msgstr "Bootloader \"{}\"" + +msgid "UKI enabled" +msgstr "UKI attiva" + +msgid "Default" +msgstr "Predefinito" + +msgid "Manual" +msgstr "Manuale" + +msgid "Pre-mount" +msgstr "Premontaggio" + +#, python-brace-format +msgid "{} layout" +msgstr "Layout {}" + +#, python-brace-format +msgid "Devices {}" +msgstr "Dispositivi {}" + +msgid "LVM set up" +msgstr "Imposta LVM" + +#, python-brace-format +msgid "{} encryption" +msgstr "Crittografia {}" + +#, python-brace-format +msgid "Btrfs snapshot \"{}\"" +msgstr "Snapshot Btrfs \"{}\"" + +#, python-brace-format +msgid "Keyboard layout \"{}\"" +msgstr "Layout della tastiera \"{}\"" + +#, python-brace-format +msgid "Locale language \"{}\"" +msgstr "Lingua \"{}\"" + +#, python-brace-format +msgid "Locale encoding \"{}\"" +msgstr "Codifica \"{}\"" + +#, python-brace-format +msgid "Console font \"{}\"" +msgstr "Font della console \"{}\"" + +#, python-brace-format +msgid "Mirror regions \"{}\"" +msgstr "Regioni dei mirror \"{}\"" + +#, python-brace-format +msgid "Optional repositories \"{}\"" +msgstr "Repository opzionali \"{}\"" + +msgid "Custom servers set up" +msgstr "Imposta server personalizzati" + +msgid "Custom repositories set up" +msgstr "Imposta repository personalizzati" + +msgid "Use standalone iwd" +msgstr "Usa iwd standalone" + +msgid "Color enabled" +msgstr "Colore attivo" + +#, python-brace-format +msgid "{} grphics driver" +msgstr "Driver grafici {}" + +#, python-brace-format +msgid "{} greeter" +msgstr "Greeter {}" + +msgid "Enter a repository name" +msgstr "Inserisci il nome di un repository" From 22bf6e3c35164e9e8d47ce1fac9c63994c43e7a5 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 18 May 2026 15:36:33 +1000 Subject: [PATCH 52/61] Update dependency arch/python-textual to v8.2.6 (#4546) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 133bda54..0f455c30 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,7 +21,7 @@ dependencies = [ "pyparted==3.13.0", "pydantic==2.13.4", "cryptography==48.0.0", - "textual==8.2.5", + "textual==8.2.6", "markdown-it-py==4.0.0", "linkify-it-py==2.1.0", ] From dccd0c8ccf8d3212dd398a015c0210a5c9cff77e Mon Sep 17 00:00:00 2001 From: Softer Date: Mon, 18 May 2026 13:10:56 +0300 Subject: [PATCH 53/61] Add translation CI validation (#4519) * Fix broken localization: tr(f-string) never matches translation catalog tr(f'Invalid configuration: {error}') evaluates the f-string before tr() runs, so xgettext extracts the literal placeholder as the msgid while runtime passes the formatted string - the two never match. Switch to tr('...{}').format(...) and update msgid in base.pot. * Add CI validation for translations and pot_tools dev utility Add translation-check workflow with two jobs: - validate-po: msgfmt --check on changed .po files, .mo sync warning, tr(f-string) anti-pattern grep on changed .py files - validate-pot: verify all tr() strings exist in base.pot when .py files change Workflow only triggers on .py/.po/.pot file changes. Add scripts/pot_tools.py developer utility (stats, list, add_missing) for managing base.pot. * Fix code style: use tabs and reformat xgettext arguments Align check_pot_freshness.py and pot_tools.py with project indentation (tabs) and ruff format requirements. Sorry :-) * Replace custom PO parser with msgcmp, drop pot_tools.py Address review feedback: use standard gettext msgcmp instead of hand-rolled parser for base.pot freshness check. Remove pot_tools.py that duplicated locales_generator.sh functionality. * Move translation checks into locales_generator.sh, simplify CI workflow Use msgcmp instead of diff for base.pot validation to avoid failing on legacy stale entries - the same cascading breakage that killed the original workflow (disabled 2023, removed in #4483). * Fix broken .po files: duplicate msgid in Hindi, missing format args in Finnish --- .github/workflows/translation-check.yaml | 22 +++++ archinstall/lib/global_menu.py | 2 +- archinstall/locales/base.pot | 2 +- archinstall/locales/fi/LC_MESSAGES/base.po | 4 +- archinstall/locales/hi/LC_MESSAGES/base.po | 3 - archinstall/locales/locales_generator.sh | 99 +++++++++++++++++----- 6 files changed, 106 insertions(+), 26 deletions(-) create mode 100644 .github/workflows/translation-check.yaml diff --git a/.github/workflows/translation-check.yaml b/.github/workflows/translation-check.yaml new file mode 100644 index 00000000..15be16cf --- /dev/null +++ b/.github/workflows/translation-check.yaml @@ -0,0 +1,22 @@ +name: Translation validation +on: + push: + paths: + - 'archinstall/**/*.py' + - 'archinstall/locales/**' + - '.github/workflows/translation-check.yaml' + pull_request: + paths: + - 'archinstall/**/*.py' + - 'archinstall/locales/**' + - '.github/workflows/translation-check.yaml' +jobs: + translations: + name: Validate translations + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + - name: Install gettext + run: sudo apt-get update && sudo apt-get install -y gettext + - name: Run translation checks + run: bash archinstall/locales/locales_generator.sh check diff --git a/archinstall/lib/global_menu.py b/archinstall/lib/global_menu.py index eac936bd..1e18890f 100644 --- a/archinstall/lib/global_menu.py +++ b/archinstall/lib/global_menu.py @@ -502,7 +502,7 @@ class GlobalMenu(AbstractMenu[None]): return text[:-1] # remove last new line if error := self._validate_bootloader(): - return tr(f'Invalid configuration: {error}') + return tr('Invalid configuration: {}').format(error) self.sync_all_to_config() summary = ConfigurationOutput(self._arch_config).as_summary() diff --git a/archinstall/locales/base.pot b/archinstall/locales/base.pot index e1681526..4db4554c 100644 --- a/archinstall/locales/base.pot +++ b/archinstall/locales/base.pot @@ -1245,7 +1245,7 @@ msgid "Product" msgstr "" #, python-brace-format -msgid "Invalid configuration: {error}" +msgid "Invalid configuration: {}" msgstr "" msgid "Ready to install" diff --git a/archinstall/locales/fi/LC_MESSAGES/base.po b/archinstall/locales/fi/LC_MESSAGES/base.po index 31cf6af2..9534151e 100644 --- a/archinstall/locales/fi/LC_MESSAGES/base.po +++ b/archinstall/locales/fi/LC_MESSAGES/base.po @@ -2190,7 +2190,7 @@ msgstr "Valittu työpöydän profiili vaati tavallisen käyttäjän kirjautumise #, python-brace-format msgid "Environment type: {} {}" -msgstr "Ympäristötyyppi: {}" +msgstr "Ympäristötyyppi: {} {}" msgid "Input cannot be empty" msgstr "Syöttö ei voi olla tyhjä" @@ -2245,4 +2245,4 @@ msgstr "Määritetään U2F laitetta käyttäjälle: {}" #, python-brace-format msgid "Default: {}ms, Recommended range: 1000-60000" -msgstr "Oletusarvo: 10000ms, suositeltu 1000-60000ms" +msgstr "Oletusarvo: {}ms, suositeltu 1000-60000ms" diff --git a/archinstall/locales/hi/LC_MESSAGES/base.po b/archinstall/locales/hi/LC_MESSAGES/base.po index 9d64d477..3cfe309f 100644 --- a/archinstall/locales/hi/LC_MESSAGES/base.po +++ b/archinstall/locales/hi/LC_MESSAGES/base.po @@ -1502,9 +1502,6 @@ msgstr "सक्षम" msgid "Disabled" msgstr "अक्षम" -msgid "Please submit this issue (and file) to https://github.com/archlinux/archinstall/issues" -msgstr "कृपया इस समस्या (और फ़ाइल) को https://github.com/archlinux/archinstall/issues पर सबमिट करें।" - msgid "Mirror name" msgstr "मिरर का नाम" diff --git a/archinstall/locales/locales_generator.sh b/archinstall/locales/locales_generator.sh index 7ce2ea19..18977dfc 100755 --- a/archinstall/locales/locales_generator.sh +++ b/archinstall/locales/locales_generator.sh @@ -1,48 +1,109 @@ #!/usr/bin/env bash set -euo pipefail -cd $(dirname "$0")/.. +cd "$(dirname "$0")/.." -function update_lang() { - file=${1} +usage() { + echo "Usage: ${0} " + echo "" + echo "Commands:" + echo " all Regenerate base.pot and update all languages" + echo " Regenerate base.pot and update a single language" + echo " check Run translation validation checks" + echo " -h, --help Show this help" +} +generate_pot() { + find . -type f -iname '*.py' | sort \ + | xargs xgettext --no-location --omit-header --keyword='tr' \ + -d base -o locales/base.pot +} + +update_lang() { + local file=${1} echo "Updating: ${file}" + local path path=$(dirname "${file}") msgmerge --quiet --no-location --width 512 --backup none --update "${file}" locales/base.pot msgfmt -o "${path}/base.mo" "${file}" } - -function generate_all() { +cmd_generate_all() { + generate_pot for file in $(find locales/ -name "base.po"); do update_lang "${file}" done } -function generate_single_lang() { - lang_file="locales/${1}/LC_MESSAGES/base.po" - +cmd_generate_single() { + local lang_file="locales/${1}/LC_MESSAGES/base.po" if [ ! -f "${lang_file}" ]; then echo "Language files not found: ${lang_file}" exit 1 fi - + generate_pot update_lang "${lang_file}" } +cmd_check_po_syntax() { + echo "Checking .po syntax..." + local failed=0 + while IFS= read -r po; do + if ! msgfmt --check --output-file=/dev/null "$po" 2>&1; then + echo "FAIL: $po" + failed=1 + fi + done < <(find locales/ -name '*.po') + if [ "$failed" -eq 1 ]; then + echo "ERROR: some .po files have syntax errors" >&2 + return 1 + fi + echo "All .po files passed syntax check." +} + +cmd_check_no_tr_fstring() { + echo "Checking for tr(f-string) anti-pattern..." + if grep -rnE "tr\(\s*f['\"]" . --include='*.py'; then + echo "ERROR: use tr('...{}').format(...) instead of tr(f'...')" >&2 + return 1 + fi + echo "No tr(f-string) anti-pattern found." +} + +cmd_check_pot_freshness() { + # msgcmp (not diff) because base.pot carries legacy stale entries from + # --join-existing; diff would always fail until a full cleanup is done. + echo "Checking base.pot for missing strings..." + find . -type f -iname '*.py' | sort \ + | xargs xgettext --no-location --omit-header --keyword='tr' \ + -d base -o /tmp/generated.pot + if ! msgcmp --use-untranslated locales/base.pot /tmp/generated.pot; then + echo "ERROR: base.pot is missing strings - run: locales_generator.sh all" >&2 + return 1 + fi + echo "base.pot contains all translatable strings." +} + +cmd_check() { + local failed=0 + cmd_check_po_syntax || failed=1 + cmd_check_no_tr_fstring || failed=1 + cmd_check_pot_freshness || failed=1 + if [ "$failed" -eq 1 ]; then + echo "Some translation checks failed." >&2 + exit 1 + fi + echo "All translation checks passed." +} if [ $# -eq 0 ]; then - echo "Usage: ${0} " - echo "Special case 'all' for builds all languages." + usage exit 1 fi -lang=${1} - -# Update the base file containing all translatable strings -find . -type f -iname "*.py" | xargs xgettext --join-existing --no-location --omit-header --keyword='tr' -d base -o locales/base.pot - -case "${lang}" in - "all") generate_all;; - *) generate_single_lang "${lang}" +case "${1}" in + check) cmd_check ;; + all) cmd_generate_all ;; + -h|--help) usage ;; + *) cmd_generate_single "${1}" ;; esac From 5fd2f9b62786fd8641dd0460ece0fc195b957a30 Mon Sep 17 00:00:00 2001 From: Clinton Phillips Date: Mon, 18 May 2026 23:36:28 -0400 Subject: [PATCH 54/61] fix: restrict EFI partition permissions with fmask/dmask=0077 (#4506) * fix: restrict EFI partition permissions with fmask/dmask=0077 Mount the ESP with fmask=0077 and dmask=0077 to prevent world-readable files like /efi/loader/random-seed. Closes #4241 * fix(efi): collapse fmask/dmask dedup to dict.fromkeys one-liner Per @Torxed's review feedback. Same semantics as the previous loop (dedupe by exact-string match) but shorter. dict.fromkeys preserves insertion order, where set() would not. * fix(efi): drop defensive list wrap per review The list() copy on line 378 was load-bearing only if options were mutated downstream, but the EFI branch reassigns options via dict.fromkeys() (line 381) and the non-EFI branch passes through to mount() without mutating. Drop the copy. --- archinstall/lib/installer.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/archinstall/lib/installer.py b/archinstall/lib/installer.py index 801eaee2..d0afefe7 100644 --- a/archinstall/lib/installer.py +++ b/archinstall/lib/installer.py @@ -375,7 +375,12 @@ class Installer: # it would be none if it's btrfs as the subvolumes will have the mountpoints defined if part_mod.mountpoint: target = self.target / part_mod.relative_mountpoint - mount(part_mod.dev_path, target, options=part_mod.mount_options) + options = part_mod.mount_options + + if part_mod.is_efi(): + options = list(dict.fromkeys(options + ['fmask=0077', 'dmask=0077'])) + + mount(part_mod.dev_path, target, options=options) elif part_mod.fs_type == FilesystemType.BTRFS: # Only mount BTRFS subvolumes that have mountpoints specified subvols_with_mountpoints = [sv for sv in part_mod.btrfs_subvols if sv.mountpoint is not None] From ac4bc442deb7f176751d08cbd1f22813c9bdc7fe Mon Sep 17 00:00:00 2001 From: Daniel Girtler Date: Wed, 20 May 2026 01:48:03 +1000 Subject: [PATCH 55/61] Refactor logging and formatting logic (#4542) * Refactor logging and formatting * Refactor logging and formatting * Refactor logging and formatting * Updaet --- archinstall/applications/audio.py | 2 +- archinstall/applications/bluetooth.py | 2 +- archinstall/applications/firewall.py | 2 +- archinstall/applications/fonts.py | 2 +- archinstall/applications/power_management.py | 2 +- archinstall/applications/print_service.py | 2 +- archinstall/default_profiles/desktop.py | 2 +- archinstall/default_profiles/server.py | 2 +- archinstall/lib/args.py | 2 +- .../authentication/authentication_handler.py | 2 +- .../lib/authentication/authentication_menu.py | 4 +- archinstall/lib/boot.py | 2 +- archinstall/lib/command.py | 2 +- archinstall/lib/configuration.py | 3 +- archinstall/lib/crypt.py | 2 +- archinstall/lib/disk/device_handler.py | 2 +- archinstall/lib/disk/disk_menu.py | 13 +- archinstall/lib/disk/encryption_menu.py | 6 +- archinstall/lib/disk/fido.py | 2 +- archinstall/lib/disk/filesystem.py | 2 +- archinstall/lib/disk/luks.py | 2 +- archinstall/lib/disk/lvm.py | 2 +- archinstall/lib/disk/partitioning_menu.py | 4 +- archinstall/lib/disk/utils.py | 2 +- archinstall/lib/general/general_menu.py | 2 +- archinstall/lib/global_menu.py | 8 +- archinstall/lib/hardware.py | 2 +- archinstall/lib/installer.py | 2 +- archinstall/lib/locale/utils.py | 2 +- archinstall/lib/{output.py => log.py} | 198 ++---------------- archinstall/lib/menu/abstract_menu.py | 2 +- archinstall/lib/menu/menu_helper.py | 4 +- archinstall/lib/mirror/mirror_handler.py | 2 +- archinstall/lib/mirror/mirror_menu.py | 4 +- archinstall/lib/models/bootloader.py | 2 +- archinstall/lib/models/device.py | 2 +- archinstall/lib/models/mirrors.py | 2 +- archinstall/lib/models/network.py | 2 +- archinstall/lib/network/wifi_handler.py | 2 +- archinstall/lib/network/wpa_supplicant.py | 2 +- archinstall/lib/networking.py | 2 +- archinstall/lib/packages/packages.py | 2 +- archinstall/lib/packages/util.py | 2 +- archinstall/lib/pacman/pacman.py | 2 +- archinstall/lib/plugins.py | 2 +- archinstall/lib/profile/profiles_handler.py | 2 +- archinstall/lib/translationhandler.py | 2 +- archinstall/lib/utils/format.py | 148 +++++++++++++ archinstall/lib/utils/util.py | 10 +- archinstall/main.py | 2 +- archinstall/scripts/guided.py | 2 +- archinstall/scripts/minimal.py | 2 +- archinstall/scripts/only_hd.py | 2 +- archinstall/tui/components.py | 2 +- tests/test_share_log.py | 16 +- 55 files changed, 253 insertions(+), 251 deletions(-) rename archinstall/lib/{output.py => log.py} (50%) create mode 100644 archinstall/lib/utils/format.py diff --git a/archinstall/applications/audio.py b/archinstall/applications/audio.py index b749fe67..e5d79718 100644 --- a/archinstall/applications/audio.py +++ b/archinstall/applications/audio.py @@ -1,9 +1,9 @@ from typing import TYPE_CHECKING from archinstall.lib.hardware import SysInfo +from archinstall.lib.log import debug from archinstall.lib.models.application import Audio, AudioConfiguration from archinstall.lib.models.users import User -from archinstall.lib.output import debug if TYPE_CHECKING: from archinstall.lib.installer import Installer diff --git a/archinstall/applications/bluetooth.py b/archinstall/applications/bluetooth.py index a0ffbc75..6bd4999e 100644 --- a/archinstall/applications/bluetooth.py +++ b/archinstall/applications/bluetooth.py @@ -1,6 +1,6 @@ from typing import TYPE_CHECKING -from archinstall.lib.output import debug +from archinstall.lib.log import debug if TYPE_CHECKING: from archinstall.lib.installer import Installer diff --git a/archinstall/applications/firewall.py b/archinstall/applications/firewall.py index e450dbc6..dadaa05b 100644 --- a/archinstall/applications/firewall.py +++ b/archinstall/applications/firewall.py @@ -1,7 +1,7 @@ from typing import TYPE_CHECKING +from archinstall.lib.log import debug from archinstall.lib.models.application import Firewall, FirewallConfiguration -from archinstall.lib.output import debug if TYPE_CHECKING: from archinstall.lib.installer import Installer diff --git a/archinstall/applications/fonts.py b/archinstall/applications/fonts.py index 3fa54c67..73eb326e 100644 --- a/archinstall/applications/fonts.py +++ b/archinstall/applications/fonts.py @@ -1,7 +1,7 @@ from typing import TYPE_CHECKING +from archinstall.lib.log import debug from archinstall.lib.models.application import FontsConfiguration -from archinstall.lib.output import debug if TYPE_CHECKING: from archinstall.lib.installer import Installer diff --git a/archinstall/applications/power_management.py b/archinstall/applications/power_management.py index b9f32b05..744411f3 100644 --- a/archinstall/applications/power_management.py +++ b/archinstall/applications/power_management.py @@ -1,7 +1,7 @@ from typing import TYPE_CHECKING +from archinstall.lib.log import debug from archinstall.lib.models.application import PowerManagement, PowerManagementConfiguration -from archinstall.lib.output import debug if TYPE_CHECKING: from archinstall.lib.installer import Installer diff --git a/archinstall/applications/print_service.py b/archinstall/applications/print_service.py index 3e4913f0..fbe28f89 100644 --- a/archinstall/applications/print_service.py +++ b/archinstall/applications/print_service.py @@ -1,6 +1,6 @@ from typing import TYPE_CHECKING -from archinstall.lib.output import debug +from archinstall.lib.log import debug if TYPE_CHECKING: from archinstall.lib.installer import Installer diff --git a/archinstall/default_profiles/desktop.py b/archinstall/default_profiles/desktop.py index daf70831..b70f9f11 100644 --- a/archinstall/default_profiles/desktop.py +++ b/archinstall/default_profiles/desktop.py @@ -1,8 +1,8 @@ from typing import TYPE_CHECKING, Self, override from archinstall.default_profiles.profile import DisplayServerType, GreeterType, Profile, ProfileType, SelectResult +from archinstall.lib.log import info from archinstall.lib.menu.helpers import Selection -from archinstall.lib.output import info from archinstall.lib.profile.profiles_handler import profile_handler from archinstall.tui.menu_item import MenuItem, MenuItemGroup from archinstall.tui.result import ResultType diff --git a/archinstall/default_profiles/server.py b/archinstall/default_profiles/server.py index bdd3d079..0e2506e0 100644 --- a/archinstall/default_profiles/server.py +++ b/archinstall/default_profiles/server.py @@ -1,8 +1,8 @@ from typing import TYPE_CHECKING, Self, override from archinstall.default_profiles.profile import Profile, ProfileType, SelectResult +from archinstall.lib.log import info from archinstall.lib.menu.helpers import Selection -from archinstall.lib.output import info from archinstall.lib.profile.profiles_handler import profile_handler from archinstall.tui.menu_item import MenuItem, MenuItemGroup from archinstall.tui.result import ResultType diff --git a/archinstall/lib/args.py b/archinstall/lib/args.py index 14aff6c6..e3729504 100644 --- a/archinstall/lib/args.py +++ b/archinstall/lib/args.py @@ -14,6 +14,7 @@ from urllib.request import Request, urlopen from pydantic.dataclasses import dataclass as p_dataclass from archinstall.lib.crypt import decrypt +from archinstall.lib.log import debug, error, logger, warn from archinstall.lib.menu.util import get_password from archinstall.lib.models.application import ApplicationConfiguration, ZramConfiguration from archinstall.lib.models.authentication import AuthenticationConfiguration @@ -28,7 +29,6 @@ from archinstall.lib.models.packages import Repository from archinstall.lib.models.pacman import PacmanConfiguration from archinstall.lib.models.profile import ProfileConfiguration from archinstall.lib.models.users import Password, User, UserSerialization -from archinstall.lib.output import debug, error, logger, warn from archinstall.lib.plugins import load_plugin from archinstall.lib.translationhandler import Language, tr, translation_handler from archinstall.lib.version import get_version diff --git a/archinstall/lib/authentication/authentication_handler.py b/archinstall/lib/authentication/authentication_handler.py index 3a3b8cd4..bae192fd 100644 --- a/archinstall/lib/authentication/authentication_handler.py +++ b/archinstall/lib/authentication/authentication_handler.py @@ -3,9 +3,9 @@ from pathlib import Path from typing import TYPE_CHECKING from archinstall.lib.command import SysCommandWorker +from archinstall.lib.log import debug, info from archinstall.lib.models.authentication import AuthenticationConfiguration, U2FLoginConfiguration, U2FLoginMethod from archinstall.lib.models.users import User -from archinstall.lib.output import debug, info from archinstall.lib.translationhandler import tr if TYPE_CHECKING: diff --git a/archinstall/lib/authentication/authentication_menu.py b/archinstall/lib/authentication/authentication_menu.py index 5453099f..497b925b 100644 --- a/archinstall/lib/authentication/authentication_menu.py +++ b/archinstall/lib/authentication/authentication_menu.py @@ -6,9 +6,9 @@ from archinstall.lib.menu.helpers import Confirmation, Selection from archinstall.lib.menu.util import get_password from archinstall.lib.models.authentication import AuthenticationConfiguration, U2FLoginConfiguration, U2FLoginMethod from archinstall.lib.models.users import Password, User -from archinstall.lib.output import FormattedOutput from archinstall.lib.translationhandler import tr from archinstall.lib.user.user_menu import select_users +from archinstall.lib.utils.format import as_table from archinstall.tui.menu_item import MenuItem, MenuItemGroup from archinstall.tui.result import ResultType @@ -65,7 +65,7 @@ class AuthenticationMenu(AbstractSubMenu[AuthenticationConfiguration]): users: list[User] | None = item.value if users: - return FormattedOutput.as_table(users) + return as_table(users) return None def _prev_root_pwd(self, item: MenuItem) -> str | None: diff --git a/archinstall/lib/boot.py b/archinstall/lib/boot.py index b1e8c888..e8fa4c88 100644 --- a/archinstall/lib/boot.py +++ b/archinstall/lib/boot.py @@ -6,7 +6,7 @@ from typing import ClassVar, Self from archinstall.lib.command import SysCommand, SysCommandWorker from archinstall.lib.exceptions import SysCallError -from archinstall.lib.output import error +from archinstall.lib.log import error class Boot: diff --git a/archinstall/lib/command.py b/archinstall/lib/command.py index b99854d2..b0d45754 100644 --- a/archinstall/lib/command.py +++ b/archinstall/lib/command.py @@ -11,7 +11,7 @@ from types import TracebackType from typing import Any, Self, override from archinstall.lib.exceptions import RequirementError, SysCallError -from archinstall.lib.output import debug, error, logger +from archinstall.lib.log import debug, error, logger from archinstall.lib.utils.encoding import clear_vt100_escape_codes diff --git a/archinstall/lib/configuration.py b/archinstall/lib/configuration.py index c40ef161..f0096872 100644 --- a/archinstall/lib/configuration.py +++ b/archinstall/lib/configuration.py @@ -8,11 +8,12 @@ from pydantic import TypeAdapter from archinstall.lib.args import ArchConfig, ArchConfigType from archinstall.lib.crypt import encrypt +from archinstall.lib.log import debug, logger, warn from archinstall.lib.menu.helpers import Confirmation, Selection from archinstall.lib.menu.util import get_password, prompt_dir from archinstall.lib.models.network import NetworkConfiguration -from archinstall.lib.output import as_key_value_pair, debug, logger, warn from archinstall.lib.translationhandler import tr +from archinstall.lib.utils.format import as_key_value_pair from archinstall.tui.menu_item import MenuItem, MenuItemGroup from archinstall.tui.result import ResultType diff --git a/archinstall/lib/crypt.py b/archinstall/lib/crypt.py index 99fa62d6..b7ad4edf 100644 --- a/archinstall/lib/crypt.py +++ b/archinstall/lib/crypt.py @@ -6,7 +6,7 @@ from pathlib import Path from cryptography.fernet import Fernet, InvalidToken from cryptography.hazmat.primitives.kdf.argon2 import Argon2id -from archinstall.lib.output import debug +from archinstall.lib.log import debug libcrypt = ctypes.CDLL('libcrypt.so') diff --git a/archinstall/lib/disk/device_handler.py b/archinstall/lib/disk/device_handler.py index 2e51d3d2..360cbfd1 100644 --- a/archinstall/lib/disk/device_handler.py +++ b/archinstall/lib/disk/device_handler.py @@ -15,6 +15,7 @@ from archinstall.lib.disk.utils import ( umount, ) from archinstall.lib.exceptions import DiskError, SysCallError, UnknownFilesystemFormat +from archinstall.lib.log import debug, error, info, log from archinstall.lib.models.device import ( DEFAULT_ITER_TIME, BDevice, @@ -35,7 +36,6 @@ from archinstall.lib.models.device import ( _PartitionInfo, ) from archinstall.lib.models.users import Password -from archinstall.lib.output import debug, error, info, log from archinstall.lib.pathnames import ARCHISO_MOUNTPOINT diff --git a/archinstall/lib/disk/disk_menu.py b/archinstall/lib/disk/disk_menu.py index 26aa145d..f8e8e2e3 100644 --- a/archinstall/lib/disk/disk_menu.py +++ b/archinstall/lib/disk/disk_menu.py @@ -5,6 +5,7 @@ from typing import override from archinstall.lib.disk.device_handler import device_handler from archinstall.lib.disk.encryption_menu import DiskEncryptionMenu from archinstall.lib.disk.partitioning_menu import manual_partitioning +from archinstall.lib.log import debug from archinstall.lib.menu.abstract_menu import AbstractSubMenu from archinstall.lib.menu.helpers import Confirmation, Notify, Selection, Table from archinstall.lib.menu.util import prompt_dir @@ -35,8 +36,8 @@ from archinstall.lib.models.device import ( Unit, _DeviceInfo, ) -from archinstall.lib.output import FormattedOutput, debug from archinstall.lib.translationhandler import tr +from archinstall.lib.utils.format import as_table from archinstall.tui.menu_item import MenuItem, MenuItemGroup from archinstall.tui.result import ResultType @@ -221,7 +222,7 @@ class DiskLayoutConfigurationMenu(AbstractSubMenu[DiskMenuConfig]): for mod in device_mods: # create partition table - partition_table = FormattedOutput.as_table(mod.partitions) + partition_table = as_table(mod.partitions) output_partition += f'{mod.device_path}: {mod.device.device_info.model}\n' output_partition += '{}: {}\n'.format(tr('Wipe'), mod.wipe) @@ -230,7 +231,7 @@ class DiskLayoutConfigurationMenu(AbstractSubMenu[DiskMenuConfig]): # create btrfs table btrfs_partitions = [p for p in mod.partitions if p.btrfs_subvols] for partition in btrfs_partitions: - output_btrfs += FormattedOutput.as_table(partition.btrfs_subvols) + '\n' + output_btrfs += as_table(partition.btrfs_subvols) + '\n' output = output_partition + output_btrfs return output.rstrip() @@ -246,12 +247,12 @@ class DiskLayoutConfigurationMenu(AbstractSubMenu[DiskMenuConfig]): output = '{}: {}\n'.format(tr('Configuration'), lvm_config.config_type.display_msg()) for vol_gp in lvm_config.vol_groups: - pv_table = FormattedOutput.as_table(vol_gp.pvs) + pv_table = as_table(vol_gp.pvs) output += '{}:\n{}'.format(tr('Physical volumes'), pv_table) output += f'\nVolume Group: {vol_gp.name}' - lvm_volumes = FormattedOutput.as_table(vol_gp.volumes) + lvm_volumes = as_table(vol_gp.volumes) output += '\n\n{}:\n{}'.format(tr('Volumes'), lvm_volumes) return output @@ -302,7 +303,7 @@ async def select_devices(preset: list[BDevice] | None = []) -> list[BDevice] | N dev = device_handler.get_device(device.path) if dev and dev.partition_infos: - return FormattedOutput.as_table(dev.partition_infos) + return as_table(dev.partition_infos) return None if preset is None: diff --git a/archinstall/lib/disk/encryption_menu.py b/archinstall/lib/disk/encryption_menu.py index f5d4f095..53aa73fe 100644 --- a/archinstall/lib/disk/encryption_menu.py +++ b/archinstall/lib/disk/encryption_menu.py @@ -17,8 +17,8 @@ from archinstall.lib.models.device import ( PartitionModification, ) from archinstall.lib.models.users import Password -from archinstall.lib.output import FormattedOutput from archinstall.lib.translationhandler import tr +from archinstall.lib.utils.format import as_table from archinstall.tui.menu_item import MenuItem, MenuItemGroup from archinstall.tui.result import ResultType @@ -199,7 +199,7 @@ class DiskEncryptionMenu(AbstractSubMenu[DiskEncryption]): def _prev_partitions(self, item: MenuItem) -> str | None: if item.value: output = tr('Partitions to be encrypted') + '\n' - output += FormattedOutput.as_table(item.value) + output += as_table(item.value) return output.rstrip() return None @@ -207,7 +207,7 @@ class DiskEncryptionMenu(AbstractSubMenu[DiskEncryption]): def _prev_lvm_vols(self, item: MenuItem) -> str | None: if item.value: output = tr('LVM volumes to be encrypted') + '\n' - output += FormattedOutput.as_table(item.value) + output += as_table(item.value) return output.rstrip() return None diff --git a/archinstall/lib/disk/fido.py b/archinstall/lib/disk/fido.py index 0538417f..6392b55c 100644 --- a/archinstall/lib/disk/fido.py +++ b/archinstall/lib/disk/fido.py @@ -4,9 +4,9 @@ from typing import ClassVar from archinstall.lib.command import SysCommand, SysCommandWorker from archinstall.lib.exceptions import SysCallError +from archinstall.lib.log import error, info from archinstall.lib.models.device import Fido2Device from archinstall.lib.models.users import Password -from archinstall.lib.output import error, info from archinstall.lib.utils.encoding import clear_vt100_escape_codes_from_str diff --git a/archinstall/lib/disk/filesystem.py b/archinstall/lib/disk/filesystem.py index 1afa8c6e..2c51b09d 100644 --- a/archinstall/lib/disk/filesystem.py +++ b/archinstall/lib/disk/filesystem.py @@ -13,6 +13,7 @@ from archinstall.lib.disk.lvm import ( lvm_vol_reduce, ) from archinstall.lib.disk.utils import udev_sync +from archinstall.lib.log import debug, info from archinstall.lib.models.device import ( DiskEncryption, DiskLayoutConfiguration, @@ -27,7 +28,6 @@ from archinstall.lib.models.device import ( Size, Unit, ) -from archinstall.lib.output import debug, info class FilesystemHandler: diff --git a/archinstall/lib/disk/luks.py b/archinstall/lib/disk/luks.py index 275222f8..5d62abc8 100644 --- a/archinstall/lib/disk/luks.py +++ b/archinstall/lib/disk/luks.py @@ -7,9 +7,9 @@ from types import TracebackType from archinstall.lib.command import SysCommand, SysCommandWorker, run from archinstall.lib.disk.utils import get_lsblk_info, umount from archinstall.lib.exceptions import DiskError, SysCallError +from archinstall.lib.log import debug, info from archinstall.lib.models.device import DEFAULT_ITER_TIME from archinstall.lib.models.users import Password -from archinstall.lib.output import debug, info from archinstall.lib.utils.util import generate_password diff --git a/archinstall/lib/disk/lvm.py b/archinstall/lib/disk/lvm.py index 34ae9a26..c2c5b6a6 100644 --- a/archinstall/lib/disk/lvm.py +++ b/archinstall/lib/disk/lvm.py @@ -7,6 +7,7 @@ from typing import Literal, overload from archinstall.lib.command import SysCommand, SysCommandWorker from archinstall.lib.disk.utils import udev_sync from archinstall.lib.exceptions import SysCallError +from archinstall.lib.log import debug from archinstall.lib.models.device import ( LvmGroupInfo, LvmPVInfo, @@ -17,7 +18,6 @@ from archinstall.lib.models.device import ( Size, Unit, ) -from archinstall.lib.output import debug def _lvm_info( diff --git a/archinstall/lib/disk/partitioning_menu.py b/archinstall/lib/disk/partitioning_menu.py index a524cf99..b8ddd231 100644 --- a/archinstall/lib/disk/partitioning_menu.py +++ b/archinstall/lib/disk/partitioning_menu.py @@ -19,8 +19,8 @@ from archinstall.lib.models.device import ( Size, Unit, ) -from archinstall.lib.output import FormattedOutput from archinstall.lib.translationhandler import tr +from archinstall.lib.utils.format import as_table from archinstall.tui.menu_item import MenuItem, MenuItemGroup from archinstall.tui.result import ResultType @@ -479,7 +479,7 @@ class PartitioningList(ListManager[DiskSegment]): sector_size = device_info.sector_size text = tr('Selected free space segment on device {}:').format(device_info.path) + '\n\n' - free_space_table = FormattedOutput.as_table([free_space]) + free_space_table = as_table([free_space]) prompt = text + free_space_table + '\n' max_sectors = free_space.length.format_size(Unit.sectors, sector_size) diff --git a/archinstall/lib/disk/utils.py b/archinstall/lib/disk/utils.py index 808ff451..900445e1 100644 --- a/archinstall/lib/disk/utils.py +++ b/archinstall/lib/disk/utils.py @@ -4,8 +4,8 @@ from pydantic import BaseModel from archinstall.lib.command import SysCommand from archinstall.lib.exceptions import DiskError, SysCallError +from archinstall.lib.log import debug, info, warn from archinstall.lib.models.device import LsblkInfo -from archinstall.lib.output import debug, info, warn class LsblkOutput(BaseModel): diff --git a/archinstall/lib/general/general_menu.py b/archinstall/lib/general/general_menu.py index be5498d8..dcf7d8aa 100644 --- a/archinstall/lib/general/general_menu.py +++ b/archinstall/lib/general/general_menu.py @@ -1,8 +1,8 @@ from enum import Enum from archinstall.lib.locale.utils import list_timezones +from archinstall.lib.log import warn from archinstall.lib.menu.helpers import Confirmation, Input, Selection -from archinstall.lib.output import warn from archinstall.lib.translationhandler import Language, tr from archinstall.tui.menu_item import MenuItem, MenuItemGroup from archinstall.tui.result import ResultType diff --git a/archinstall/lib/global_menu.py b/archinstall/lib/global_menu.py index 1e18890f..45a0e3b4 100644 --- a/archinstall/lib/global_menu.py +++ b/archinstall/lib/global_menu.py @@ -27,11 +27,11 @@ from archinstall.lib.models.packages import Repository from archinstall.lib.models.pacman import PacmanConfiguration from archinstall.lib.models.profile import ProfileConfiguration from archinstall.lib.network.network_menu import select_network -from archinstall.lib.output import FormattedOutput from archinstall.lib.packages.packages import list_available_packages, select_additional_packages from archinstall.lib.pacman.config import PacmanConfig from archinstall.lib.pacman.pacman_menu import PacmanMenu from archinstall.lib.translationhandler import Language, tr, translation_handler +from archinstall.lib.utils.format import as_table from archinstall.tui.components import tui from archinstall.tui.menu_item import MenuItem, MenuItemGroup @@ -299,7 +299,7 @@ class GlobalMenu(AbstractMenu[None]): if item.value: network_config: NetworkConfiguration = item.value if network_config.type == NicType.MANUAL: - output = FormattedOutput.as_table(network_config.nics) + output = as_table(network_config.nics) else: output = f'{tr("Network configuration")}:\n{network_config.type.display_msg()}' @@ -321,7 +321,7 @@ class GlobalMenu(AbstractMenu[None]): output += f'{tr("Root password")}: {auth_config.root_enc_password.hidden()}\n' if auth_config.users: - output += FormattedOutput.as_table(auth_config.users) + '\n' + output += as_table(auth_config.users) + '\n' if auth_config.u2f_config: u2f_config = auth_config.u2f_config @@ -612,7 +612,7 @@ class GlobalMenu(AbstractMenu[None]): if mirror_config.custom_repositories: title = tr('Custom repositories') - table = FormattedOutput.as_table(mirror_config.custom_repositories) + table = as_table(mirror_config.custom_repositories) output += f'{title}:\n\n{table}' return output.strip() diff --git a/archinstall/lib/hardware.py b/archinstall/lib/hardware.py index 2cfdb706..f1587a3c 100644 --- a/archinstall/lib/hardware.py +++ b/archinstall/lib/hardware.py @@ -6,8 +6,8 @@ from typing import Self from archinstall.lib.command import SysCommand from archinstall.lib.exceptions import SysCallError +from archinstall.lib.log import debug from archinstall.lib.networking import enrich_iface_types, list_interfaces -from archinstall.lib.output import debug from archinstall.lib.translationhandler import tr diff --git a/archinstall/lib/installer.py b/archinstall/lib/installer.py index d0afefe7..bf7d91ba 100644 --- a/archinstall/lib/installer.py +++ b/archinstall/lib/installer.py @@ -29,6 +29,7 @@ from archinstall.lib.exceptions import DiskError, HardwareIncompatibilityError, from archinstall.lib.hardware import SysInfo from archinstall.lib.linux_path import LPath from archinstall.lib.locale.utils import verify_keyboard_layout, verify_x11_keyboard_layout +from archinstall.lib.log import debug, error, info, log, logger, warn from archinstall.lib.mirror.mirror_handler import MirrorListHandler from archinstall.lib.models.application import ZramAlgorithm from archinstall.lib.models.bootloader import Bootloader, BootloaderConfiguration @@ -52,7 +53,6 @@ from archinstall.lib.models.package_types import DEFAULT_KERNEL, Kernel from archinstall.lib.models.packages import Repository from archinstall.lib.models.pacman import PacmanConfiguration from archinstall.lib.models.users import User -from archinstall.lib.output import debug, error, info, log, logger, warn from archinstall.lib.packages.packages import installed_package from archinstall.lib.pacman.config import PacmanConfig from archinstall.lib.pacman.pacman import Pacman diff --git a/archinstall/lib/locale/utils.py b/archinstall/lib/locale/utils.py index 497e1fcb..31946028 100644 --- a/archinstall/lib/locale/utils.py +++ b/archinstall/lib/locale/utils.py @@ -3,7 +3,7 @@ from pathlib import Path from archinstall.lib.command import SysCommand from archinstall.lib.exceptions import ServiceException, SysCallError -from archinstall.lib.output import error +from archinstall.lib.log import error from archinstall.lib.utils.util import running_from_iso diff --git a/archinstall/lib/output.py b/archinstall/lib/log.py similarity index 50% rename from archinstall/lib/output.py rename to archinstall/lib/log.py index bc9bc7a3..bd060e99 100644 --- a/archinstall/lib/output.py +++ b/archinstall/lib/log.py @@ -3,183 +3,18 @@ import os import sys import urllib.error import urllib.request -from collections.abc import Callable -from dataclasses import asdict, is_dataclass -from datetime import UTC, datetime from enum import Enum from pathlib import Path -from typing import TYPE_CHECKING, Any -from archinstall.lib.utils.encoding import unicode_ljust, unicode_rjust -from archinstall.tui.rich import BaseRichTable - -if TYPE_CHECKING: - from _typeshed import DataclassInstance - - -class FormattedOutput: - @staticmethod - def _get_values( - o: DataclassInstance, - class_formatter: str | Callable | None = None, # type: ignore[type-arg] # pyright: ignore[reportMissingTypeArgument] - filter_list: list[str] = [], - ) -> dict[str, Any]: - """ - the original values returned a dataclass as dict thru the call to some specific methods - this version allows thru the parameter class_formatter to call a dynamically selected formatting method. - Can transmit a filter list to the class_formatter, - """ - if class_formatter: - # if invoked per reference it has to be a standard function or a classmethod. - # A method of an instance does not make sense - if callable(class_formatter): - return class_formatter(o, filter_list) - # if is invoked by name we restrict it to a method of the class. No need to mess more - elif hasattr(o, class_formatter) and callable(getattr(o, class_formatter)): - func = getattr(o, class_formatter) - return func(filter_list) - - raise ValueError('Unsupported formatting call') - elif hasattr(o, 'table_data'): - return o.table_data() - elif hasattr(o, 'json'): - return o.json() - elif is_dataclass(o): - return asdict(o) - else: - return o.__dict__ # type: ignore[unreachable] - - @classmethod - def as_table( - cls, - obj: list[Any], - class_formatter: str | Callable | None = None, # type: ignore[type-arg] - filter_list: list[str] = [], - capitalize: bool = False, - ) -> str: - """variant of as_table (subtly different code) which has two additional parameters - filter which is a list of fields which will be shown - class_formatter a special method to format the outgoing data - - A general comment, the format selected for the output (a string where every data record is separated by newline) - is for compatibility with a print statement - As_table_filter can be a drop in replacement for as_table - """ - raw_data = [cls._get_values(o, class_formatter, filter_list) for o in obj] - - # determine the maximum column size - column_width: dict[str, int] = {} - for o in raw_data: - for k, v in o.items(): - if not filter_list or k in filter_list: - column_width.setdefault(k, 0) - column_width[k] = max([column_width[k], len(str(v)), len(k)]) - - if not filter_list: - filter_list = list(column_width.keys()) - - # create the header lines - output = '' - key_list = [] - for key in filter_list: - width = column_width[key] - key = key.replace('!', '').replace('_', ' ') - - if capitalize: - key = key.capitalize() - - key_list.append(unicode_ljust(key, width)) - - output += ' | '.join(key_list) + '\n' - output += '-' * len(output) + '\n' - - # create the data lines - for record in raw_data: - obj_data = [] - for key in filter_list: - width = column_width.get(key, len(key)) - value = record.get(key, '') - - if '!' in key: - value = '*' * len(value) - - if isinstance(value, (int, float)) or (isinstance(value, str) and value.isnumeric()): - obj_data.append(unicode_rjust(str(value), width)) - else: - obj_data.append(unicode_ljust(str(value), width)) - - output += ' | '.join(obj_data) + '\n' - - return output - - @staticmethod - def as_columns(entries: list[str], cols: int) -> str: - """ - Will format a list into a given number of columns - """ - chunks = [] - output = '' - - for i in range(0, len(entries), cols): - chunks.append(entries[i : i + cols]) - - for row in chunks: - out_fmt = '{: <30} ' * len(row) - output += out_fmt.format(*row) + '\n' - - return output - - -def as_key_value_pair( - entries: dict[str, str | list[str] | bool], - ignore_empty: bool = True, -) -> str: - """ - Formats key-values as a Rich Table: - key1 : value1 - key2 : value2 - ... - """ - table = BaseRichTable() - table.add_column('key', style='bold', no_wrap=True) - table.add_column('value', style='white', max_width=70) - - for label, value in entries.items(): - if ignore_empty and not value: - continue - - if isinstance(value, bool): - value = 'Yes' if value else 'No' - - if isinstance(value, list): - value = '\n '.join(str(val) for val in value) - - table.add_row(label.title(), f': {value}') - - return table.stringify() - - -class Journald: - @staticmethod - def log(message: str, level: int = logging.DEBUG) -> None: - try: - import systemd.journal # type: ignore[import-not-found] - except ModuleNotFoundError: - return - - log_adapter = logging.getLogger('archinstall') - log_fmt = logging.Formatter('[%(levelname)s]: %(message)s') - log_ch = systemd.journal.JournalHandler() - log_ch.setFormatter(log_fmt) - log_adapter.addHandler(log_ch) - log_adapter.setLevel(logging.DEBUG) - - log_adapter.log(level, message) +from archinstall.lib.utils.util import timestamp class Logger: - def __init__(self, path: Path = Path('/var/log/archinstall')) -> None: - self._path = path + def __init__(self, path: Path | None = None) -> None: + if path is None: + path = Path('/var/log/archinstall') + + self._path: Path = path @property def path(self) -> Path: @@ -212,7 +47,7 @@ class Logger: self._check_permissions() with self.path.open('a') as f: - ts = _timestamp() + ts = timestamp() level_name = logging.getLevelName(level) f.write(f'[{ts}] - {level_name} - {content}\n') @@ -310,9 +145,20 @@ def _stylize_output( return f'\033[{ansi}m{text}\033[0m' -def _timestamp() -> str: - now = datetime.now(tz=UTC) - return now.strftime('%Y-%m-%d %H:%M:%S') +def journal_log(message: str, level: int = logging.DEBUG) -> None: + try: + import systemd.journal # type: ignore[import-not-found] + except ModuleNotFoundError: + return + + log_adapter = logging.getLogger('archinstall') + log_fmt = logging.Formatter('[%(levelname)s]: %(message)s') + log_ch = systemd.journal.JournalHandler() + log_ch.setFormatter(log_fmt) + log_adapter.addHandler(log_ch) + log_adapter.setLevel(logging.DEBUG) + + log_adapter.log(level, message) def info( @@ -376,7 +222,7 @@ def log( if _supports_color(): text = _stylize_output(text, fg, bg, reset, font) - Journald.log(text, level=level) + journal_log(text, level=level) if level != logging.DEBUG: print(text) diff --git a/archinstall/lib/menu/abstract_menu.py b/archinstall/lib/menu/abstract_menu.py index 590cabf1..c37f5657 100644 --- a/archinstall/lib/menu/abstract_menu.py +++ b/archinstall/lib/menu/abstract_menu.py @@ -2,8 +2,8 @@ from enum import Enum from types import TracebackType from typing import Any, Self, override +from archinstall.lib.log import error from archinstall.lib.menu.helpers import Selection -from archinstall.lib.output import error from archinstall.lib.translationhandler import tr from archinstall.tui.components import InstanceRunnable from archinstall.tui.menu_item import MenuItem, MenuItemGroup diff --git a/archinstall/lib/menu/menu_helper.py b/archinstall/lib/menu/menu_helper.py index 6ca3b953..0d9a4032 100644 --- a/archinstall/lib/menu/menu_helper.py +++ b/archinstall/lib/menu/menu_helper.py @@ -1,4 +1,4 @@ -from archinstall.lib.output import FormattedOutput +from archinstall.lib.utils.format import as_table from archinstall.tui.menu_item import MenuItem, MenuItemGroup @@ -32,7 +32,7 @@ class MenuHelper[ValueT]: display_data: dict[str, ValueT | str | None] = {} if data: - table = FormattedOutput.as_table(data) + table = as_table(data) rows = table.split('\n') # these are the header rows of the table diff --git a/archinstall/lib/mirror/mirror_handler.py b/archinstall/lib/mirror/mirror_handler.py index ef0ba1a9..8117605d 100644 --- a/archinstall/lib/mirror/mirror_handler.py +++ b/archinstall/lib/mirror/mirror_handler.py @@ -2,10 +2,10 @@ import time import urllib.parse from pathlib import Path +from archinstall.lib.log import debug, info from archinstall.lib.models import MirrorRegion from archinstall.lib.models.mirrors import MirrorStatusEntryV3, MirrorStatusListV3 from archinstall.lib.networking import fetch_data_from_url -from archinstall.lib.output import debug, info from archinstall.lib.pathnames import MIRRORLIST diff --git a/archinstall/lib/mirror/mirror_menu.py b/archinstall/lib/mirror/mirror_menu.py index 275c4df8..6c5de14a 100644 --- a/archinstall/lib/mirror/mirror_menu.py +++ b/archinstall/lib/mirror/mirror_menu.py @@ -13,8 +13,8 @@ from archinstall.lib.models.mirrors import ( SignOption, ) from archinstall.lib.models.packages import Repository -from archinstall.lib.output import FormattedOutput from archinstall.lib.translationhandler import tr +from archinstall.lib.utils.format import as_table from archinstall.tui.menu_item import MenuItem, MenuItemGroup from archinstall.tui.result import ResultType @@ -281,7 +281,7 @@ class MirrorMenu(AbstractSubMenu[MirrorConfiguration]): return None custom_mirrors: list[CustomRepository] = item.value - output = FormattedOutput.as_table(custom_mirrors) + output = as_table(custom_mirrors) return output.strip() def _prev_custom_servers(self, item: MenuItem) -> str | None: diff --git a/archinstall/lib/models/bootloader.py b/archinstall/lib/models/bootloader.py index 0dbd4268..bd6bce7e 100644 --- a/archinstall/lib/models/bootloader.py +++ b/archinstall/lib/models/bootloader.py @@ -3,8 +3,8 @@ from dataclasses import dataclass from enum import Enum from typing import Any, Self, override +from archinstall.lib.log import warn from archinstall.lib.models.config import SubConfig -from archinstall.lib.output import warn from archinstall.lib.translationhandler import tr diff --git a/archinstall/lib/models/device.py b/archinstall/lib/models/device.py index a21f3c6f..a50f7d8e 100644 --- a/archinstall/lib/models/device.py +++ b/archinstall/lib/models/device.py @@ -12,9 +12,9 @@ from parted import Disk, Geometry, Partition from pydantic import BaseModel, Field, ValidationInfo, field_serializer, field_validator from archinstall.lib.hardware import SysInfo +from archinstall.lib.log import debug from archinstall.lib.models.config import SubConfig from archinstall.lib.models.users import Password -from archinstall.lib.output import debug from archinstall.lib.translationhandler import tr ENC_IDENTIFIER = 'ainst' diff --git a/archinstall/lib/models/mirrors.py b/archinstall/lib/models/mirrors.py index 3ded2a9a..1d678328 100644 --- a/archinstall/lib/models/mirrors.py +++ b/archinstall/lib/models/mirrors.py @@ -9,10 +9,10 @@ from typing import TYPE_CHECKING, Any, Self, TypedDict, override from pydantic import BaseModel, ValidationInfo, field_validator, model_validator +from archinstall.lib.log import debug from archinstall.lib.models.config import SubConfig from archinstall.lib.models.packages import Repository from archinstall.lib.networking import DownloadTimer, ping -from archinstall.lib.output import debug from archinstall.lib.translationhandler import tr if TYPE_CHECKING: diff --git a/archinstall/lib/models/network.py b/archinstall/lib/models/network.py index 57a579c3..bcbd288a 100644 --- a/archinstall/lib/models/network.py +++ b/archinstall/lib/models/network.py @@ -3,8 +3,8 @@ from dataclasses import dataclass, field from enum import Enum from typing import NotRequired, Self, TypedDict, override +from archinstall.lib.log import debug from archinstall.lib.models.config import SubConfig -from archinstall.lib.output import debug from archinstall.lib.translationhandler import tr diff --git a/archinstall/lib/network/wifi_handler.py b/archinstall/lib/network/wifi_handler.py index 4c858601..b5590343 100644 --- a/archinstall/lib/network/wifi_handler.py +++ b/archinstall/lib/network/wifi_handler.py @@ -5,9 +5,9 @@ from typing import assert_never, override from archinstall.lib.command import SysCommand from archinstall.lib.exceptions import SysCallError +from archinstall.lib.log import debug from archinstall.lib.models.network import WifiConfiguredNetwork, WifiNetwork from archinstall.lib.network.wpa_supplicant import WpaSupplicantConfig -from archinstall.lib.output import debug from archinstall.lib.translationhandler import tr from archinstall.tui.components import ConfirmationScreen, InputScreen, InstanceRunnable, LoadingScreen, NotifyScreen, TableSelectionScreen, tui from archinstall.tui.menu_item import MenuItem, MenuItemGroup diff --git a/archinstall/lib/network/wpa_supplicant.py b/archinstall/lib/network/wpa_supplicant.py index 935e9b4d..17ed3227 100644 --- a/archinstall/lib/network/wpa_supplicant.py +++ b/archinstall/lib/network/wpa_supplicant.py @@ -1,8 +1,8 @@ from dataclasses import dataclass, field from pathlib import Path +from archinstall.lib.log import debug from archinstall.lib.models.network import WifiNetwork -from archinstall.lib.output import debug @dataclass diff --git a/archinstall/lib/networking.py b/archinstall/lib/networking.py index e950a697..97701e89 100644 --- a/archinstall/lib/networking.py +++ b/archinstall/lib/networking.py @@ -13,7 +13,7 @@ from urllib.parse import urlencode from urllib.request import urlopen from archinstall.lib.exceptions import DownloadTimeout, SysCallError -from archinstall.lib.output import debug, error, info +from archinstall.lib.log import debug, error, info from archinstall.lib.pacman.pacman import Pacman diff --git a/archinstall/lib/packages/packages.py b/archinstall/lib/packages/packages.py index a7422986..5b276f76 100644 --- a/archinstall/lib/packages/packages.py +++ b/archinstall/lib/packages/packages.py @@ -1,9 +1,9 @@ from functools import lru_cache from archinstall.lib.exceptions import SysCallError +from archinstall.lib.log import debug from archinstall.lib.menu.helpers import Loading, Notify, Selection from archinstall.lib.models.packages import AvailablePackage, LocalPackage, PackageGroup, Repository -from archinstall.lib.output import debug from archinstall.lib.pacman.pacman import Pacman from archinstall.lib.translationhandler import tr from archinstall.tui.menu_item import MenuItem, MenuItemGroup diff --git a/archinstall/lib/packages/util.py b/archinstall/lib/packages/util.py index 335d312e..134592bb 100644 --- a/archinstall/lib/packages/util.py +++ b/archinstall/lib/packages/util.py @@ -1,6 +1,6 @@ from functools import lru_cache -from archinstall.lib.output import debug +from archinstall.lib.log import debug from archinstall.lib.packages.packages import check_package_upgrade diff --git a/archinstall/lib/pacman/pacman.py b/archinstall/lib/pacman/pacman.py index 83f8b40d..c78f6145 100644 --- a/archinstall/lib/pacman/pacman.py +++ b/archinstall/lib/pacman/pacman.py @@ -5,7 +5,7 @@ from pathlib import Path from archinstall.lib.command import SysCommand from archinstall.lib.exceptions import RequirementError -from archinstall.lib.output import error, info, warn +from archinstall.lib.log import error, info, warn from archinstall.lib.pathnames import PACMAN_CONF from archinstall.lib.plugins import plugins from archinstall.lib.translationhandler import tr diff --git a/archinstall/lib/plugins.py b/archinstall/lib/plugins.py index 919fcf23..ac8383b1 100644 --- a/archinstall/lib/plugins.py +++ b/archinstall/lib/plugins.py @@ -7,7 +7,7 @@ import urllib.request from importlib import metadata from pathlib import Path -from archinstall.lib.output import error, info, warn +from archinstall.lib.log import error, info, warn from archinstall.lib.version import get_version plugins = {} diff --git a/archinstall/lib/profile/profiles_handler.py b/archinstall/lib/profile/profiles_handler.py index c75f95c0..d937a520 100644 --- a/archinstall/lib/profile/profiles_handler.py +++ b/archinstall/lib/profile/profiles_handler.py @@ -9,9 +9,9 @@ from typing import TYPE_CHECKING, NotRequired, TypedDict from archinstall.default_profiles.profile import CustomSetting, GreeterType, Profile from archinstall.lib.hardware import GfxDriver, GfxPackage +from archinstall.lib.log import debug, error, info from archinstall.lib.models.profile import ProfileConfiguration from archinstall.lib.networking import fetch_data_from_url -from archinstall.lib.output import debug, error, info from archinstall.lib.translationhandler import tr if TYPE_CHECKING: diff --git a/archinstall/lib/translationhandler.py b/archinstall/lib/translationhandler.py index 093ca6de..c2736f35 100644 --- a/archinstall/lib/translationhandler.py +++ b/archinstall/lib/translationhandler.py @@ -9,7 +9,7 @@ from typing import override from archinstall.lib.command import SysCommand from archinstall.lib.exceptions import SysCallError -from archinstall.lib.output import debug +from archinstall.lib.log import debug from archinstall.lib.utils.util import running_from_iso diff --git a/archinstall/lib/utils/format.py b/archinstall/lib/utils/format.py new file mode 100644 index 00000000..2ac8c377 --- /dev/null +++ b/archinstall/lib/utils/format.py @@ -0,0 +1,148 @@ +from collections.abc import Callable +from dataclasses import asdict, is_dataclass +from typing import TYPE_CHECKING, Any + +from archinstall.lib.utils.encoding import unicode_ljust, unicode_rjust +from archinstall.tui.rich import BaseRichTable + +if TYPE_CHECKING: + from _typeshed import DataclassInstance + + +def as_key_value_pair( + entries: dict[str, str | list[str] | bool], + ignore_empty: bool = True, +) -> str: + """ + Formats key-values as a Rich Table: + key1 : value1 + key2 : value2 + ... + """ + table = BaseRichTable() + table.add_column('key', style='bold', no_wrap=True) + table.add_column('value', style='white', max_width=70) + + for label, value in entries.items(): + if ignore_empty and not value: + continue + + if isinstance(value, bool): + value = 'Yes' if value else 'No' + + if isinstance(value, list): + value = '\n '.join(str(val) for val in value) + + table.add_row(label.title(), f': {value}') + + return table.stringify() + + +def as_columns(entries: list[str], cols: int) -> str: + """ + Will format a list into a given number of columns + """ + chunks: list[list[str]] = [] + output = '' + + for i in range(0, len(entries), cols): + chunks.append(entries[i : i + cols]) + + for row in chunks: + out_fmt = '{: <30} ' * len(row) + output += out_fmt.format(*row) + '\n' + + return output + + +def _get_values( + o: DataclassInstance, + class_formatter: str | Callable | None = None, # type: ignore[type-arg] # pyright: ignore[reportMissingTypeArgument] + filter_list: list[str] = [], +) -> dict[str, Any]: + """ + the original values returned a dataclass as dict thru the call to some specific methods + this version allows thru the parameter class_formatter to call a dynamically selected formatting method. + Can transmit a filter list to the class_formatter, + """ + if class_formatter: + # if invoked per reference it has to be a standard function or a classmethod. + # A method of an instance does not make sense + if callable(class_formatter): + return class_formatter(o, filter_list) + # if is invoked by name we restrict it to a method of the class. No need to mess more + elif hasattr(o, class_formatter) and callable(getattr(o, class_formatter)): + func = getattr(o, class_formatter) + return func(filter_list) + + raise ValueError('Unsupported formatting call') + elif hasattr(o, 'table_data'): + return o.table_data() + elif hasattr(o, 'json'): + return o.json() + elif is_dataclass(o): + return asdict(o) + else: + return o.__dict__ # type: ignore[unreachable] + + +def as_table( + obj: list[Any], + class_formatter: str | Callable | None = None, # type: ignore[type-arg] + filter_list: list[str] = [], + capitalize: bool = False, +) -> str: + """variant of as_table (subtly different code) which has two additional parameters + filter which is a list of fields which will be shown + class_formatter a special method to format the outgoing data + + A general comment, the format selected for the output (a string where every data record is separated by newline) + is for compatibility with a print statement + As_table_filter can be a drop in replacement for as_table + """ + raw_data = [_get_values(o, class_formatter, filter_list) for o in obj] + + # determine the maximum column size + column_width: dict[str, int] = {} + for o in raw_data: + for k, v in o.items(): + if not filter_list or k in filter_list: + column_width.setdefault(k, 0) + column_width[k] = max([column_width[k], len(str(v)), len(k)]) + + if not filter_list: + filter_list = list(column_width.keys()) + + # create the header lines + output = '' + key_list = [] + for key in filter_list: + width = column_width[key] + key = key.replace('!', '').replace('_', ' ') + + if capitalize: + key = key.capitalize() + + key_list.append(unicode_ljust(key, width)) + + output += ' | '.join(key_list) + '\n' + output += '-' * len(output) + '\n' + + # create the data lines + for record in raw_data: + obj_data = [] + for key in filter_list: + width = column_width.get(key, len(key)) + value = record.get(key, '') + + if '!' in key: + value = '*' * len(value) + + if isinstance(value, (int, float)) or (isinstance(value, str) and value.isnumeric()): + obj_data.append(unicode_rjust(str(value), width)) + else: + obj_data.append(unicode_ljust(str(value), width)) + + output += ' | '.join(obj_data) + '\n' + + return output diff --git a/archinstall/lib/utils/util.py b/archinstall/lib/utils/util.py index 574cc8d5..f94ca18c 100644 --- a/archinstall/lib/utils/util.py +++ b/archinstall/lib/utils/util.py @@ -1,8 +1,14 @@ import secrets import string +from datetime import UTC, datetime -from archinstall.lib.output import FormattedOutput from archinstall.lib.pathnames import ARCHISO_MOUNTPOINT +from archinstall.lib.utils.format import as_columns + + +def timestamp() -> str: + now = datetime.now(tz=UTC) + return now.strftime('%Y-%m-%d %H:%M:%S') def running_from_iso() -> bool: @@ -36,7 +42,7 @@ def format_cols(items: list[str], header: str | None = None) -> str: else: col = 4 - text += FormattedOutput.as_columns(items, col) + text += as_columns(items, col) # remove whitespaces on each row text = '\n'.join(t.strip() for t in text.split('\n')) return text diff --git a/archinstall/main.py b/archinstall/main.py index f30b01d3..e38b3d48 100644 --- a/archinstall/main.py +++ b/archinstall/main.py @@ -11,10 +11,10 @@ from pathlib import Path from archinstall.lib.args import ArchConfigHandler, SubCommand from archinstall.lib.disk.utils import disk_layouts from archinstall.lib.hardware import SysInfo +from archinstall.lib.log import debug, error, info, logger, share_install_log, warn from archinstall.lib.menu.helpers import Confirmation from archinstall.lib.network.wifi_handler import WifiHandler from archinstall.lib.networking import ping -from archinstall.lib.output import debug, error, info, logger, share_install_log, warn from archinstall.lib.packages.util import check_version_upgrade from archinstall.lib.pacman.pacman import Pacman from archinstall.lib.translationhandler import tr, translation_handler diff --git a/archinstall/scripts/guided.py b/archinstall/scripts/guided.py index aab02cfd..6f484910 100644 --- a/archinstall/scripts/guided.py +++ b/archinstall/scripts/guided.py @@ -12,13 +12,13 @@ from archinstall.lib.disk.utils import disk_layouts from archinstall.lib.general.general_menu import PostInstallationAction, select_post_installation from archinstall.lib.global_menu import GlobalMenu from archinstall.lib.installer import Installer, accessibility_tools_in_use, run_custom_user_commands +from archinstall.lib.log import debug, error, info from archinstall.lib.menu.util import delayed_warning from archinstall.lib.mirror.mirror_handler import MirrorListHandler from archinstall.lib.models import Bootloader from archinstall.lib.models.device import DiskLayoutType, EncryptionType from archinstall.lib.models.users import User from archinstall.lib.network.network_handler import install_network_config -from archinstall.lib.output import debug, error, info from archinstall.lib.packages.util import check_version_upgrade from archinstall.lib.profile.profiles_handler import profile_handler from archinstall.lib.translationhandler import tr diff --git a/archinstall/scripts/minimal.py b/archinstall/scripts/minimal.py index ee4b49c7..c6eda03d 100644 --- a/archinstall/scripts/minimal.py +++ b/archinstall/scripts/minimal.py @@ -4,12 +4,12 @@ from archinstall.lib.configuration import ConfigurationOutput from archinstall.lib.disk.disk_menu import DiskLayoutConfigurationMenu from archinstall.lib.disk.filesystem import FilesystemHandler from archinstall.lib.installer import Installer +from archinstall.lib.log import debug, error, info from archinstall.lib.menu.util import delayed_warning from archinstall.lib.models import Bootloader from archinstall.lib.models.profile import ProfileConfiguration from archinstall.lib.models.users import Password, User from archinstall.lib.network.network_handler import install_network_config -from archinstall.lib.output import debug, error, info from archinstall.lib.profile.profiles_handler import profile_handler from archinstall.lib.translationhandler import tr from archinstall.tui.components import tui diff --git a/archinstall/scripts/only_hd.py b/archinstall/scripts/only_hd.py index 5df72965..c7349c51 100644 --- a/archinstall/scripts/only_hd.py +++ b/archinstall/scripts/only_hd.py @@ -7,8 +7,8 @@ from archinstall.lib.disk.filesystem import FilesystemHandler from archinstall.lib.disk.utils import disk_layouts from archinstall.lib.global_menu import GlobalMenu from archinstall.lib.installer import Installer +from archinstall.lib.log import debug, error from archinstall.lib.menu.util import delayed_warning -from archinstall.lib.output import debug, error from archinstall.lib.translationhandler import tr from archinstall.tui.components import tui diff --git a/archinstall/tui/components.py b/archinstall/tui/components.py index 9fecf3c6..efbb64f9 100644 --- a/archinstall/tui/components.py +++ b/archinstall/tui/components.py @@ -19,7 +19,7 @@ from textual.widgets.option_list import Option from textual.widgets.selection_list import Selection from textual.worker import WorkerCancelled -from archinstall.lib.output import debug +from archinstall.lib.log import debug from archinstall.lib.translationhandler import tr from archinstall.tui.menu_item import MenuItem, MenuItemGroup from archinstall.tui.result import Result, ResultType diff --git a/tests/test_share_log.py b/tests/test_share_log.py index a6706da8..1a19ba26 100644 --- a/tests/test_share_log.py +++ b/tests/test_share_log.py @@ -9,7 +9,7 @@ import pytest from hypothesis import HealthCheck, given, settings from hypothesis import strategies as st -from archinstall.lib.output import share_install_log +from archinstall.lib.log import share_install_log urls = st.builds( '{}://{}.{}/{}'.format, @@ -55,7 +55,7 @@ def test_file_not_found( ) -> None: missing_log = tmp_path / sub_path / 'install.log' - with patch('archinstall.lib.output.logger._path', new=missing_log): + with patch('archinstall.lib.log.logger._path', new=missing_log): assert share_install_log(paste_url, max_byte) is None @@ -64,8 +64,8 @@ def test_file_not_found( def test_empty_file(log_file: Path, paste_url: str, max_byte: int | None) -> None: log_file.write_bytes(b'') - with patch('archinstall.lib.output.logger._path', new=log_file.parent): - # with patch('archinstall.lib.output.logger', _fake_logger(log_file)): + with patch('archinstall.lib.log.logger._path', new=log_file.parent): + # with patch('archinstall.lib.log.logger', _fake_logger(log_file)): assert share_install_log(paste_url, max_byte) is None @@ -76,7 +76,7 @@ def test_successful_upload(log_file: Path, resp_url: str, paste_url: str, max_by fake_response = BytesIO(resp_url.encode()) with ( - patch('archinstall.lib.output.logger._path', new=log_file.parent), + patch('archinstall.lib.log.logger._path', new=log_file.parent), patch('urllib.request.urlopen', return_value=fake_response), ): result = share_install_log(paste_url, max_byte) @@ -93,7 +93,7 @@ def test_truncation(log_file: Path, resp_url: str, paste_url: str, max_byte: int exptected_byte = len(content) if max_byte is None else max_byte with ( - patch('archinstall.lib.output.logger._path', new=log_file.parent), + patch('archinstall.lib.log.logger._path', new=log_file.parent), patch('urllib.request.urlopen', return_value=fake_response) as mock_open, ): _ = share_install_log(paste_url, max_byte) @@ -108,7 +108,7 @@ def test_network_error(log_file: Path, paste_url: str, max_byte: int | None) -> log_file.write_text('some log content') with ( - patch('archinstall.lib.output.logger._path', new=log_file.parent), + patch('archinstall.lib.log.logger._path', new=log_file.parent), patch('urllib.request.urlopen', side_effect=urllib.error.URLError('no network')), ): assert share_install_log(paste_url, max_byte) is None @@ -121,7 +121,7 @@ def test_unexpected_response(log_file: Path, paste_url: str, max_byte: int | Non fake_response = BytesIO(b'ERROR: something went wrong') with ( - patch('archinstall.lib.output.logger._path', new=log_file.parent), + patch('archinstall.lib.log.logger._path', new=log_file.parent), patch('urllib.request.urlopen', return_value=fake_response), ): assert share_install_log(paste_url, max_byte) is None From e17e036f5429ad0e89b8aab7d788c9a8e0825d29 Mon Sep 17 00:00:00 2001 From: utuhiro78 <34818411+utuhiro78@users.noreply.github.com> Date: Thu, 21 May 2026 06:25:23 +0900 Subject: [PATCH 56/61] Update Japanese translation (#4550) --- archinstall/locales/ja/LC_MESSAGES/base.mo | Bin 77304 -> 35463 bytes archinstall/locales/ja/LC_MESSAGES/base.po | 4446 ++++++++++---------- 2 files changed, 2223 insertions(+), 2223 deletions(-) diff --git a/archinstall/locales/ja/LC_MESSAGES/base.mo b/archinstall/locales/ja/LC_MESSAGES/base.mo index 7d6b89f35c6740d4de8175f15239ebd5fca9d0df..0331225b8081e621bee9901c10316e2c4108c5d1 100644 GIT binary patch delta 9870 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 literal 77304 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 d(pz_r&2exk01Ri " -msgstr "[デフォルト値: 0] > " - -msgid "To be able to use this translation, please install a font manually that supports the language." -msgstr "この翻訳を使用できるようにするには、その言語をサポートするフォントを手動でインストールしてください。" - -msgid "The font should be stored as {}" -msgstr "フォントは {} として保存する必要があります" - -msgid "Archinstall requires root privileges to run. See --help for more." -msgstr "Archinstall を実行するには root 権限が必要です。詳細については --help を参照してください。" - -msgid "Select an execution mode" -msgstr "実行モードを選択" - -#, python-brace-format -msgid "Unable to fetch profile from specified url: {}" -msgstr "指定された URL からプロファイルを取得できません: {}" - -#, python-brace-format -msgid "Profiles must have unique name, but profile definitions with duplicate name found: {}" -msgstr "プロファイルには一意の名前が必要ですが、重複した名前のプロファイル定義が見つかりました: {}" - -msgid "Select one or more devices to use and configure" -msgstr "使用・設定する 1 つ以上のデバイスを選択" - -msgid "If you reset the device selection this will also reset the current disk layout. Are you sure?" -msgstr "デバイスの選択をリセットすると、現在のディスクレイアウトもリセットされます。よろしいですか?" - -msgid "Existing Partitions" -msgstr "既存のパーティション" - -msgid "Select a partitioning option" -msgstr "パーティション作成のオプションを選択" - -msgid "Enter the root directory of the mounted devices: " -msgstr "マウントしているデバイスのルートディレクトリを入力: " - -#, python-brace-format -msgid "Minimum capacity for /home partition: {}GiB\n" -msgstr "/home パーティションの最小容量: {}GiB\n" - -#, python-brace-format -msgid "Minimum capacity for Arch Linux partition: {}GiB" -msgstr "Arch Linux パーティションの最小容量: {}GiB" - -msgid "This is a list of pre-programmed profiles_bck, they might make it easier to install things like desktop environments" -msgstr "これは事前にプログラムされたプロファイルのリストです。デスクトップ環境などのインストールが簡単になる可能性があります" - -msgid "Current profile selection" -msgstr "現在のプロファイルの選択" - -msgid "Remove all newly added partitions" -msgstr "新しく追加されたパーティションをすべて削除" - -msgid "Assign mountpoint" -msgstr "マウントポイントを割り当てる" - -msgid "Mark/Unmark to be formatted (wipes data)" -msgstr "フォーマット対象としてマーク/マーク解除(データを消去)" - -msgid "Mark/Unmark as bootable" -msgstr "ブータブルとしてマーク/マーク解除" - -msgid "Change filesystem" -msgstr "ファイルシステムを変更" - -msgid "Mark/Unmark as compressed" -msgstr "圧縮対象としてマーク/マーク解除" - -msgid "Set subvolumes" -msgstr "サブボリュームを設定" - -msgid "Delete partition" -msgstr "パーティションを削除" - -msgid "Partition" -msgstr "パーティション" - -msgid "This partition is currently encrypted, to format it a filesystem has to be specified" -msgstr "このパーティションは現在暗号化されています。フォーマットするにはファイルシステムを指定してください" - -msgid "Partition mount-points are relative to inside the installation, the boot would be /boot as an example." -msgstr "パーティションのマウントポイントはインストールにおける相対的なもので、例として boot は /boot になります。" - -msgid "If mountpoint /boot is set, then the partition will also be marked as bootable." -msgstr "マウントポイントに /boot が設定された場合、パーティションはブータブルとしてマークされます。" - -msgid "Mountpoint: " -msgstr "マウントポイント: " - -msgid "Current free sectors on device {}:" -msgstr "デバイス {} の現在の空きセクター:" - -msgid "Total sectors: {}" -msgstr "総セクター数: {}" - -msgid "Enter the start sector (default: {}): " -msgstr "開始セクターを入力(デフォルト: {}): " - -msgid "Enter the end sector of the partition (percentage or block number, default: {}): " -msgstr "パーティションの終了セクターを入力(パーセンテージかブロック番号。デフォルト: {}): " - -msgid "This will remove all newly added partitions, continue?" -msgstr "これは新しく追加されたパーティションをすべて削除します。続けますか?" - -#, python-brace-format -msgid "Partition management: {}" -msgstr "パーティションを管理: {}" - -#, python-brace-format -msgid "Total length: {}" -msgstr "全体のサイズ: {}" - -msgid "Encryption type" -msgstr "暗号化のタイプ" - -msgid "Iteration time" -msgstr "イテレーション時間" - -msgid "Enter iteration time for LUKS encryption (in milliseconds)" -msgstr "LUKS 暗号化のイテレーション時間(ミリ秒単位)を入力" - -msgid "Higher values increase security but slow down boot time" -msgstr "値を大きくするとセキュリティは向上しますが、起動時間が遅くなります" - -msgid "Default: 10000ms, Recommended range: 1000-60000" -msgstr "デフォルト: 10000ms, 推奨範囲: 1000-60000" - -msgid "Iteration time cannot be empty" -msgstr "イテレーション時間は空にできません" - -msgid "Iteration time must be at least 100ms" -msgstr "イテレーション時間は最低 100ms にしてください" - -msgid "Iteration time must be at most 120000ms" -msgstr "イテレーション時間は最大 120000ms にしてください" - -msgid "Please enter a valid number" -msgstr "有効な数字を入力してください" - -msgid "Partitions" -msgstr "パーティション" - -msgid "No HSM devices available" -msgstr "利用可能な HSM デバイスがありません" - -msgid "Partitions to be encrypted" -msgstr "暗号化するパーティション" - -msgid "Select disk encryption option" -msgstr "ディスクの暗号化オプションを選択" - -msgid "Select a FIDO2 device to use for HSM" -msgstr "HSM に使用する FIDO2 デバイスを選択" - -msgid "Use a best-effort default partition layout" -msgstr "ベストエフォートのデフォルトパーティションレイアウトを使用" - -msgid "Manual Partitioning" -msgstr "手動でパーティションを作成" - -msgid "Pre-mounted configuration" -msgstr "現在のマウント設定" - -msgid "Unknown" -msgstr "不明" - -msgid "Partition encryption" -msgstr "パーティションの暗号化" - -#, python-brace-format -msgid " ! Formatting {} in " -msgstr " ! {} のフォーマットまで " - -msgid "← Back" -msgstr "← 戻る" - -msgid "Disk encryption" -msgstr "ディスクの暗号化" - -msgid "Configuration" -msgstr "設定" - -msgid "Password" -msgstr "パスワード" - -msgid "All settings will be reset, are you sure?" -msgstr "すべての設定がリセットされます。よろしいですか?" - -msgid "Back" -msgstr "戻る" - -msgid "Please chose which greeter to install for the chosen profiles: {}" -msgstr "選択したプロファイルにインストールするグリーターを選択: {}" - -#, python-brace-format -msgid "Environment type: {}" -msgstr "環境タイプ: {}" - -msgid "The proprietary Nvidia driver is not supported by Sway. It is likely that you will run into issues, are you okay with that?" -msgstr "プロプライエタリの Nvidia ドライバーは Sway ではサポートされていません。問題が発生する可能性がありますが、よろしいですか?" - -msgid "Installed packages" -msgstr "インストールするパッケージ" - -msgid "Add profile" -msgstr "プロファイルを追加" - -msgid "Edit profile" -msgstr "プロファイルを編集" - -msgid "Delete profile" -msgstr "プロファイルを削除" - -msgid "Profile name: " -msgstr "プロファイル名: " - -msgid "The profile name you entered is already in use. Try again" -msgstr "入力したプロファイル名はすでに使用されています。もう1度やり直してください" - -msgid "Packages to be install with this profile (space separated, leave blank to skip): " -msgstr "このプロファイルでインストールするパッケージ(スペースで区切る。無記入でスキップ): " - -msgid "Services to be enabled with this profile (space separated, leave blank to skip): " -msgstr "このプロファイルで有効にするサービス(スペースで区切る。未記入でスキップ): " - -msgid "Should this profile be enabled for installation?" -msgstr "このプロファイルのインストールを有効にしますか?" - -msgid "Create your own" -msgstr "自分で作成" - -msgid "" -"\n" -"Select a graphics driver or leave blank to install all open-source drivers" -msgstr "" -"\n" -"グラフィックドライバーを選択。無記入ですべてのオープンソースドライバーをインストール" - -msgid "Sway needs access to your seat (collection of hardware devices i.e. keyboard, mouse, etc)" -msgstr "Sway はお使いの Seat(キーボード、マウスなどのハードウェアデバイス群)にアクセスする必要があります" - -msgid "" -"\n" -"\n" -"Choose an option to give Sway access to your hardware" -msgstr "" -"\n" -"\n" -"Sway にハードウェアへのアクセスを許可するオプションを選択" - -msgid "Graphics driver" -msgstr "グラフィックドライバー" - -msgid "Greeter" -msgstr "グリーター" - -msgid "Please chose which greeter to install" -msgstr "インストールするグリーターを選択" - -msgid "This is a list of pre-programmed default_profiles" -msgstr "これは、事前にプログラムされた default_profile のリストです" - -msgid "Disk configuration" -msgstr "ディスクの設定" - -msgid "Profiles" -msgstr "プロファイル" - -msgid "Finding possible directories to save configuration files ..." -msgstr "設定ファイルを保存できるディレクトリを検索しています..." - -msgid "Select directory (or directories) for saving configuration files" -msgstr "設定ファイルを保存するディレクトリを選択" - -msgid "Add a custom mirror" -msgstr "カスタムミラーを追加" - -msgid "Change custom mirror" -msgstr "カスタムミラーを変更" - -msgid "Delete custom mirror" -msgstr "カスタムミラーを削除" - -msgid "Enter name (leave blank to skip): " -msgstr "名前を入力(無記入でスキップ): " - -msgid "Enter url (leave blank to skip): " -msgstr "URL を入力(未記入でスキップ): " - -msgid "Select signature check option" -msgstr "署名チェックのオプションを選択" - -msgid "Select signature option" -msgstr "署名オプションを選択" - -msgid "Custom mirrors" -msgstr "カスタムミラー" - -msgid "Defined" -msgstr "定義済み" - -msgid "Save user configuration (including disk layout)" -msgstr "ユーザー設定を保存(ディスクレイアウトを含む)" - -msgid "" -"Enter a directory for the configuration(s) to be saved (tab completion enabled)\n" -"Save directory: " -msgstr "" -"設定を保存するディレクトリを入力(Tab で補完可能)\n" -"保存ディレクトリ: " - -msgid "" -"Do you want to save {} configuration file(s) in the following location?\n" -"\n" -"{}" -msgstr "" -"{} 設定ファイルを次の場所に保存しますか?\n" -"\n" -"{}" - -msgid "Saving {} configuration files to {}" -msgstr "{} 設定ファイルを {} に保存" - -msgid "Mirrors" -msgstr "ミラー" - -msgid "Mirror regions" -msgstr "ミラーの地域" - -msgid " - Maximum value : {} ( Allows {} parallel downloads, allows {max_downloads+1} downloads at a time )" -msgstr " - 最大値 : {}({} 個の並列ダウンロードを許可し、1度に {max_downloads+1} 個のダウンロードを許可する)" - -msgid "Invalid input! Try again with a valid input [1 to {}, or 0 to disable]" -msgstr "無効な入力です!有効な入力でやり直してください [1 から {}、または 0 で無効]" - -msgid "Locales" -msgstr "ロケール" - -msgid "Use NetworkManager (necessary to configure internet graphically in GNOME and KDE)" -msgstr "ネットワークマネージャーを使用(GNOME と KDE でインターネットをグラフィカルに設定するのに必要)" - -msgid "Total: {} / {}" -msgstr "全体: {} / {}" - -msgid "All entered values can be suffixed with a unit: B, KB, KiB, MB, MiB..." -msgstr "入力したすべての値に、B、KB、KiB、MB、MiB などの単位を付けることができます。" - -msgid "If no unit is provided, the value is interpreted as sectors" -msgstr "単位が指定されていない場合、値はセクターとして解釈されます" - -msgid "Enter start (default: sector {}): " -msgstr "開始値を入力(デフォルト: セクター {}): " - -msgid "Enter end (default: {}): " -msgstr "終了値を入力(デフォルト: {}): " - -msgid "Unable to determine fido2 devices. Is libfido2 installed?" -msgstr "fido2 デバイスを特定できません。lifido2 はインストールされていますか?" - -msgid "Path" -msgstr "パス" - -msgid "Manufacturer" -msgstr "メーカー" - -msgid "Product" -msgstr "製品" - -#, python-brace-format -msgid "Invalid configuration: {error}" -msgstr "無効な設定: {error}" - -msgid "Ready to install" -msgstr "インストールの準備が完了" - -msgid "Disks" -msgstr "ディスク" - -msgid "Packages" -msgstr "パッケージ" - -msgid "Network" -msgstr "ネットワーク" - -msgid "Locale" -msgstr "ロケール" - -msgid "Type" -msgstr "タイプ" - -msgid "This option enables the number of parallel downloads that can occur during package downloads" -msgstr "このオプションは、パッケージのダウンロード中に実行できる並列ダウンロードの数を設定します" - -msgid "" -"Enter the number of parallel downloads to be enabled.\n" -"\n" -"Note:\n" -msgstr "" -"並列ダウンロードの数を入力してください。\n" -"\n" -"注意:\n" - -#, python-brace-format -msgid " - Maximum recommended value : {} ( Allows {} parallel downloads at a time )" -msgstr " - 最大推奨値 : {} (1度に {} 個の並列ダウンロードを許可する)" - -msgid " - Disable/Default : 0 ( Disables parallel downloading, allows only 1 download at a time )\n" -msgstr " - 無効/デフォルト : 0 (並列ダウンロードを無効にして、1度に 1 個のダウンロードのみ許可する)\n" - -msgid "Invalid input! Try again with a valid input [or 0 to disable]" -msgstr "無効な入力です!有効な入力でやり直してください(無効にする場合は 0)" - -msgid "Hyprland needs access to your seat (collection of hardware devices i.e. keyboard, mouse, etc)" -msgstr "Hyprland はお使いの Seat(キーボード、マウスなどのハードウェアデバイス群)にアクセスする必要があります" - -msgid "" -"\n" -"\n" -"Choose an option to give Hyprland access to your hardware" -msgstr "" -"\n" -"\n" -"Hyprland にハードウェアへのアクセスを許可するオプションを選択" - -msgid "All entered values can be suffixed with a unit: %, B, KB, KiB, MB, MiB..." -msgstr "入力したすべての値に、%、B、KB、KiB、MB、MiB などの単位を付けることができます。" - -msgid "Would you like to use unified kernel images?" -msgstr "Unified カーネルイメージを使用しますか?" - -msgid "Unified kernel images" -msgstr "Unified カーネルイメージ" - -msgid "Waiting for time sync (timedatectl show) to complete." -msgstr "時刻の同期(timedatectl show)が完了するのを待機しています。" - -msgid "Time syncronization not completing, while you wait - check the docs for workarounds: https://archinstall.readthedocs.io/" -msgstr "待機中に時刻同期が完了しません - 回避策についてはドキュメントを確認してください: https://archinstall.readthedocs.io/" - -msgid "Skipping waiting for automatic time sync (this can cause issues if time is out of sync during installation)" -msgstr "自動での時刻同期の待機をスキップします(インストール中に時刻が同期していない場合は、問題が発生する可能性があります)" - -msgid "Waiting for Arch Linux keyring sync (archlinux-keyring-wkd-sync) to complete." -msgstr "Arch Linux キーリングの同期(archlinux-keyring-wkd-sync)が完了するのを待っています。" - -msgid "Selected profiles: " -msgstr "選択したプロファイル: " - -msgid "Time synchronization not completing, while you wait - check the docs for workarounds: https://archinstall.readthedocs.io/" -msgstr "待機中に時刻同期が完了しません - 回避策についてはドキュメントを確認してください: https://archinstall.readthedocs.io/" - -msgid "Mark/Unmark as nodatacow" -msgstr "nodatacow としてマーク/マーク解除" - -msgid "Would you like to use compression or disable CoW?" -msgstr "圧縮を使用しますか、それとも CoW を無効にしますか?" - -msgid "Use compression" -msgstr "圧縮する" - -msgid "Disable Copy-on-Write" -msgstr "コピーオンライトを無効にする" - -msgid "Provides a selection of desktop environments and tiling window managers, e.g. GNOME, KDE Plasma, Sway" -msgstr "デスクトップ環境とタイルウィンドウマネージャーの選択を提供します。例: GNOME, KDE Plasma, Sway" - -#, python-brace-format -msgid "Configuration type: {}" -msgstr "設定タイプ: {}" - -msgid "LVM configuration type" -msgstr "LVM の設定タイプ" - -msgid "LVM disk encryption with more than 2 partitions is currently not supported" -msgstr "パーティションが2個を超える場合の LVM ディスク暗号化は、現在サポートしていません" - -msgid "Use NetworkManager (necessary to configure internet graphically in GNOME and KDE Plasma)" -msgstr "ネットワークマネージャーを使用(GNOME と KDE Plasma でインターネットをグラフィカルに設定するのに必要)" - -msgid "Select a LVM option" -msgstr "LVM のオプションを選択" - -msgid "Partitioning" -msgstr "パーティションを作成" - -msgid "Logical Volume Management (LVM)" -msgstr "論理ボリューム管理(LVM)" - -msgid "Physical volumes" -msgstr "物理ボリューム" - -msgid "Volumes" -msgstr "ボリューム" - -msgid "LVM volumes" -msgstr "LVM ボリューム" - -msgid "LVM volumes to be encrypted" -msgstr "暗号化する LVM ボリューム" - -msgid "Select which LVM volumes to encrypt" -msgstr "暗号化する LVM ボリュームを選択" - -msgid "Default layout" -msgstr "デフォルトのレイアウト" - -msgid "No Encryption" -msgstr "暗号化なし" - -msgid "LUKS" -msgstr "LUKS" - -msgid "LVM on LUKS" -msgstr "LUKS 上の LVM" - -msgid "LUKS on LVM" -msgstr "LVM 上の LUKS" - -msgid "Yes" -msgstr "Yes" - -msgid "No" -msgstr "No" - -msgid "Archinstall help" -msgstr "Archinstall ヘルプ" - -msgid " (default)" -msgstr " (デフォルト)" - -msgid "Press Ctrl+h for help" -msgstr "Ctrl+H でヘルプを表示" - -msgid "Choose an option to give Sway access to your hardware" -msgstr "Sway にハードウェアへのアクセスを許可するオプションを選択" - -msgid "Seat access" -msgstr "Seat アクセス" - -msgid "Mountpoint" -msgstr "マウントポイント" - -msgid "HSM" -msgstr "HSM" - -msgid "Enter disk encryption password (leave blank for no encryption)" -msgstr "ディスクの暗号化パスワードを入力(暗号化しない場合は無記入)" - -msgid "Disk encryption password" -msgstr "ディスク暗号化パスワード" - -msgid "Partition - New" -msgstr "パーティション - 新規" - -msgid "Filesystem" -msgstr "ファイルシステム" - -msgid "Invalid size" -msgstr "無効なサイズ" - -msgid "Start (default: sector {}): " -msgstr "開始値(デフォルト: セクター {}): " - -msgid "End (default: {}): " -msgstr "終了値(デフォルト: {}): " - -msgid "Subvolume name" -msgstr "サブボリューム名" - -msgid "Disk configuration type" -msgstr "ディスク設定のタイプ" - -msgid "Root mount directory" -msgstr "ルートマウントディレクトリ" - -msgid "Select language" -msgstr "言語を選択" - -msgid "Write additional packages to install (space separated, leave blank to skip)" -msgstr "追加でインストールするパッケージを書く(スペースで区切る。無記入でスキップ)" - -msgid "Invalid download number" -msgstr "ダウンロード数が無効です" - -msgid "Number downloads" -msgstr "ダウンロード数" - -msgid "The username you entered is invalid" -msgstr "入力したユーザー名は無効です" - -msgid "Username" -msgstr "ユーザー名" - -#, python-brace-format -msgid "Should \"{}\" be a superuser (sudo)?\n" -msgstr "\"{}\" はスーパーユーザーに昇格しますか(sudo)?\n" - -msgid "Interfaces" -msgstr "インターフェイス" - -msgid "You need to enter a valid IP in IP-config mode" -msgstr "IP設定モードで有効なIPを入力する必要があります" - -msgid "Modes" -msgstr "モード" - -msgid "IP address" -msgstr "IPアドレス" - -msgid "Enter your gateway (router) IP address (leave blank for none)" -msgstr "ゲートウェイ(ルーター)の IP アドレスを入力。無い場合は無記入" - -msgid "Gateway address" -msgstr "ゲートウェイアドレス" - -msgid "Enter your DNS servers with space separated (leave blank for none)" -msgstr "DNS サーバーをスペースで区切って入力(無い場合は無記入)" - -msgid "DNS servers" -msgstr "DNSサーバー" - -msgid "Configure interfaces" -msgstr "インターフェースを設定" - -msgid "Kernel" -msgstr "カーネル" - -msgid "UEFI is not detected and some options are disabled" -msgstr "UEFIが検出されず、一部のオプションが無効になります" - -msgid "Info" -msgstr "情報" - -msgid "The proprietary Nvidia driver is not supported by Sway." -msgstr "プロプライエタリの Nvidia ドライバーは Sway ではサポートされていません。" - -msgid "It is likely that you will run into issues, are you okay with that?" -msgstr "問題が発生する可能性が高いですが、よろしいですか?" - -msgid "Main profile" -msgstr "メインプロファイル" - -msgid "Confirm password" -msgstr "パスワードを確認" - -msgid "The confirmation password did not match, please try again" -msgstr "確認のパスワードが一致しませんでした。もう一度試してください" - -msgid "Not a valid directory" -msgstr "有効なディレクトリではありません" - -msgid "Would you like to continue?" -msgstr "続行しますか?" - -msgid "Directory" -msgstr "ディレクトリ" - -msgid "Enter a directory for the configuration(s) to be saved (tab completion enabled)" -msgstr "設定を保存するディレクトリを入力(Tab で補完可能)" - -#, python-brace-format -msgid "Do you want to save the configuration file(s) to {}?" -msgstr "設定ファイルを次の場所に保存しますか? {}" - -msgid "Enabled" -msgstr "有効" - -msgid "Disabled" -msgstr "無効" - -msgid "Please submit this issue (and file) to https://github.com/archlinux/archinstall/issues" -msgstr "この問題(およびファイル)を https://github.com/archlinux/archinstall/issues に送信してください" - -msgid "Mirror name" -msgstr "ミラーの名前" - -msgid "Url" -msgstr "URL" - -msgid "Select signature check" -msgstr "署名チェックを選択" - -msgid "Select execution mode" -msgstr "実行モードを選択" - -msgid "Press ? for help" -msgstr "? を押すとヘルプを表示" - -msgid "Choose an option to give Hyprland access to your hardware" -msgstr "Hyprland にハードウェアへのアクセスを許可するオプションを選択" - -msgid "Additional repositories" -msgstr "追加リポジトリ" - -msgid "NTP" -msgstr "NTP" - -msgid "Swap on zram" -msgstr "Zram 上でスワップ" - -msgid "Name" -msgstr "名前" - -msgid "Signature check" -msgstr "署名チェック" - -#, python-brace-format -msgid "Selected free space segment on device {}:" -msgstr "デバイス {} の選択された空きスペースセグメント:" - -#, python-brace-format -msgid "Size: {} / {}" -msgstr "サイズ: {} / {}" - -#, python-brace-format -msgid "Size (default: {}): " -msgstr "サイズ (デフォルト: {}): " - -msgid "HSM device" -msgstr "HSM デバイス" - -msgid "Some packages could not be found in the repository" -msgstr "リポジトリ内にいくつかのパッケージが見つかりませんでした" - -msgid "User" -msgstr "ユーザー" - -msgid "The specified configuration will be applied" -msgstr "指定された設定が適用されます" - -msgid "Wipe" -msgstr "消去" - -msgid "Mark/Unmark as XBOOTLDR" -msgstr "XBOOTLDR としてマーク/マーク解除" - -msgid "Loading packages..." -msgstr "パッケージを読み込んでいます..." - -msgid "Select any packages from the below list that should be installed additionally" -msgstr "追加でインストールする必要があるパッケージをリストから選択してください" - -msgid "Add a custom repository" -msgstr "カスタムリポジトリを追加" - -msgid "Change custom repository" -msgstr "カスタムリポジトリを変更" - -msgid "Delete custom repository" -msgstr "カスタムリポジトリを削除" - -msgid "Repository name" -msgstr "リポジトリ名" - -msgid "Add a custom server" -msgstr "カスタムサーバーを追加" - -msgid "Change custom server" -msgstr "カスタムサーバーを変更" - -msgid "Delete custom server" -msgstr "カスタムサーバーを削除" - -msgid "Server url" -msgstr "サーバー URL" - -msgid "Select regions" -msgstr "地域を選択" - -msgid "Add custom servers" -msgstr "カスタムサーバーを追加" - -msgid "Add custom repository" -msgstr "カスタムリポジトリを追加" - -msgid "Loading mirror regions..." -msgstr "ミラーの地域を読み込んでいます..." - -msgid "Mirrors and repositories" -msgstr "ミラーとリポジトリ" - -msgid "Selected mirror regions" -msgstr "選択されたミラーの地域" - -msgid "Custom servers" -msgstr "カスタムサーバー" - -msgid "Custom repositories" -msgstr "カスタムリポジトリ" - -msgid "Only ASCII characters are supported" -msgstr "ASCII 文字のみサポートされます" - -msgid "Show help" -msgstr "ヘルプを表示" - -msgid "Exit help" -msgstr "ヘルプを終了" - -msgid "Preview scroll up" -msgstr "プレビューを上にスクロール" - -msgid "Preview scroll down" -msgstr "プレビューを下にスクロール" - -msgid "Move up" -msgstr "上に移動" - -msgid "Move down" -msgstr "下に移動" - -msgid "Move right" -msgstr "右に移動" - -msgid "Move left" -msgstr "左に移動" - -msgid "Jump to entry" -msgstr "エントリーへジャンプ" - -msgid "Skip selection (if available)" -msgstr "選択をスキップ(利用可能な場合)" - -msgid "Reset selection (if available)" -msgstr "選択をリセット(利用可能な場合)" - -msgid "Select on single select" -msgstr "単一で選択" - -msgid "Select on multi select" -msgstr "複数で選択" - -msgid "Reset" -msgstr "リセット" - -msgid "Skip selection menu" -msgstr "選択メニューをスキップ" - -msgid "Start search mode" -msgstr "検索モードを開始" - -msgid "Exit search mode" -msgstr "検索モードを終了" - -msgid "General" -msgstr "一般" - -msgid "Navigation" -msgstr "ナビゲーション" - -msgid "Selection" -msgstr "選択" - -msgid "Search" -msgstr "検索" - -msgid "Down" -msgstr "下へ" - -msgid "Up" -msgstr "上へ" - -msgid "Confirm" -msgstr "確認" - -msgid "Focus right" -msgstr "右にフォーカス" - -msgid "Focus left" -msgstr "左にフォーカス" - -msgid "Toggle" -msgstr "切り替え" - -msgid "Show/Hide help" -msgstr "ヘルプを表示/隠す" - -msgid "Quit" -msgstr "終了" - -msgid "First" -msgstr "最初" - -msgid "Last" -msgstr "最後" - -msgid "Select" -msgstr "選択" - -msgid "Page Up" -msgstr "上のページへ" - -msgid "Page Down" -msgstr "下のページへ" - -msgid "Page up" -msgstr "上のページへ" - -msgid "Page down" -msgstr "下のページへ" - -msgid "Page Left" -msgstr "左のページへ" - -msgid "Page Right" -msgstr "右のページへ" - -msgid "Cursor up" -msgstr "カーソルを上へ" - -msgid "Cursor down" -msgstr "カースルを下へ" - -msgid "Cursor right" -msgstr "カーソルを右へ" - -msgid "Cursor left" -msgstr "カーソルを左へ" - -msgid "Top" -msgstr "一番上へ" - -msgid "Bottom" -msgstr "一番下へ" - -msgid "Home" -msgstr "ホーム" - -msgid "End" -msgstr "最後へ" - -msgid "Toggle option" -msgstr "オプションを切り替え" - -msgid "Scroll Up" -msgstr "上にスクロール" - -msgid "Scroll Down" -msgstr "下にスクロール" - -msgid "Scroll Left" -msgstr "左にスクロール" - -msgid "Scroll Right" -msgstr "右にスクロール" - -msgid "Scroll Home" -msgstr "ホームにスクロール" - -msgid "Scroll End" -msgstr "一番下にスクロール" - -msgid "Focus Next" -msgstr "次にフォーカス" - -msgid "Focus Previous" -msgstr "前にフォーカス" - -msgid "Copy selected text" -msgstr "選択したテキストをコピー" - -msgid "labwc needs access to your seat (collection of hardware devices i.e. keyboard, mouse, etc)" -msgstr "labwc はお使いの Seat(キーボード、マウスなどのハードウェアデバイス群)にアクセスする必要があります" - -msgid "Choose an option to give labwc access to your hardware" -msgstr "labwc にハードウェアへのアクセスを許可するオプションを選択" - -msgid "niri needs access to your seat (collection of hardware devices i.e. keyboard, mouse, etc)" -msgstr "niri はお使いの Seat(キーボード、マウスなどのハードウェアデバイス群)にアクセスする必要があります" - -msgid "Choose an option to give niri access to your hardware" -msgstr "niri にハードウェアへのアクセスを許可するオプションを選択" - -msgid "Mark/Unmark as ESP" -msgstr "ESP としてマーク/マーク解除" - -msgid "Package group:" -msgstr "パッケージグループ:" - -msgid "Exit archinstall" -msgstr "archinstall を終了" - -msgid "Reboot system" -msgstr "システムを再起動" - -msgid "chroot into installation for post-installation configurations" -msgstr "インストール後の設定を行うためインストールディレクトリに chroot する" - -msgid "Installation completed" -msgstr "インストール完了" - -msgid "What would you like to do next?" -msgstr "次は何をしますか?" - -#, python-brace-format -msgid "Select which mode to configure for \"{}\"" -msgstr "「{}」に設定するモードを選択" - -msgid "Incorrect credentials file decryption password" -msgstr "認証情報ファイルの復号化パスワードが正しくありません" - -msgid "Incorrect password" -msgstr "パスワードが間違っています" - -msgid "Credentials file decryption password" -msgstr "認証情報ファイルの復号化パスワード" - -msgid "Do you want to encrypt the user_credentials.json file?" -msgstr "user_credentials.json ファイルを暗号化しますか?" - -msgid "Credentials file encryption password" -msgstr "認証情報ファイルの暗号化パスワード" - -#, python-brace-format -msgid "Repositories: {}" -msgstr "リポジトリ: {}" - -msgid "New version available" -msgstr "新しいバージョンが利用可能" - -msgid "Passwordless login" -msgstr "パスワードなしのログイン" - -msgid "Second factor login" -msgstr "2要素ログイン" - -msgid "Bluetooth" -msgstr "Bluetooth" - -msgid "Would you like to configure Bluetooth?" -msgstr "Bluetooth を設定しますか?" - -msgid "Print service" -msgstr "印刷サービス" - -msgid "Would you like to configure the print service?" -msgstr "印刷サービスを設定しますか?" - -msgid "Power management" -msgstr "電力管理" - -msgid "Authentication" -msgstr "認証" - -msgid "Applications" -msgstr "アプリケーション" - -msgid "U2F login method: " -msgstr "U2F ログインメソッド: " - -msgid "Passwordless sudo: " -msgstr "パスワードなしの sudo: " - -#, python-brace-format -msgid "Btrfs snapshot type: {}" -msgstr "Btrfs スナップショットのタイプ: {}" - -msgid "Syncing the system..." -msgstr "システムを同期..." - -msgid "Value cannot be empty" -msgstr "値は空にできません" - -msgid "Snapshot type" -msgstr "スナップショットのタイプ" - -#, python-brace-format -msgid "Snapshot type: {}" -msgstr "スナップショットのタイプ: {}" - -msgid "U2F login setup" -msgstr "U2F ログインの設定" - -msgid "No U2F devices found" -msgstr "U2F デバイスが見つかりません" - -msgid "U2F Login Method" -msgstr "U2F ログインメソッド" - -msgid "Enable passwordless sudo?" -msgstr "パスワードなしの sudo を有効にしますか?" - -#, python-brace-format -msgid "Setting up U2F device for user: {}" -msgstr "ユーザー用 U2F デバイスの設定: {}" - -msgid "You may need to enter the PIN and then touch your U2F device to register it" -msgstr "PIN を入力したのち U2F デバイスをタッチして登録しなければならない場合があります" - -msgid "Starting device modifications in " -msgstr "次の場所でデバイスの変更を開始しています " - -msgid "No network connection found" -msgstr "ネットワーク接続が見つかりません" - -msgid "Would you like to connect to a Wifi?" -msgstr "Wi-Fi に接続しますか?" - -msgid "No wifi interface found" -msgstr "Wi-Fi インターフェースが見つかりません" - -msgid "Select wifi network to connect to" -msgstr "接続する Wi-Fi ネットワークを選択" - -msgid "Scanning wifi networks..." -msgstr "Wi-Fi ネットワークをスキャンしています..." - -msgid "No wifi networks found" -msgstr "Wi-Fi ネットワークが見つかりません" - -msgid "Failed setting up wifi" -msgstr "Wi-Fi の設定に失敗しました" - -msgid "Enter wifi password" -msgstr "Wi-Fi パスワードを入力" - -msgid "Ok" -msgstr "OK" - -msgid "Removable" -msgstr "リムーバブル" - -msgid "Install to removable location" -msgstr "リムーバブルな場所にインストールする" - -msgid "Will install to /EFI/BOOT/ (removable location)" -msgstr "/EFI/BOOT/ (リムーバブルな場所) にインストールする" - -msgid "Will install to standard location with NVRAM entry" -msgstr "NVRAM エントリーのある標準の場所にインストールする" - -msgid "Would you like to install the bootloader to the default removable media search location?" -msgstr "ブートローダーをデフォルトのリムーバブルメディアの検索場所にインストールしますか?" - -msgid "This installs the bootloader to /EFI/BOOT/BOOTX64.EFI (or similar) which is useful for:" -msgstr "ブートローダーが /EFI/BOOT/BOOTX64.EFI (または類似の場所) にインストールされ、次の場合に役立ちます:" - -msgid "USB drives or other portable external media." -msgstr "USB ドライブまたは他のポータブル外部メディア。" - -msgid "Systems where you want the disk to be bootable on any computer." -msgstr "ディスクをどのコンピューターでも起動できるようにするシステム。" - -msgid "Firmware that does not properly support NVRAM boot entries." -msgstr "NVRAM ブートエントリーを適切にサポートしていないファームウェア。" - -msgid "Will install to /EFI/BOOT/ (removable location, safe default)" -msgstr "/EFI/BOOT/(リムーバブルな場所、安全なデフォルト)にインストール" - -msgid "Will install to custom location with NVRAM entry" -msgstr "NVRAM エントリーを使用してカスタムした場所にインストール" - -msgid "Firmware that does not properly support NVRAM boot entries like most MSI motherboards," -msgstr "ほとんどの MSI マザーボードのように NVRAM ブートエントリーを適切にサポートしていないファームウェア、" - -msgid "most Apple Macs, many laptops..." -msgstr "ほとんどの Apple Mac、多くのラップトップ..." - -msgid "Language" -msgstr "言語" - -msgid "Compression algorithm" -msgstr "圧縮アルゴリズム" - -msgid "Only packages such as base, sudo, linux, linux-firmware, efibootmgr and optional profile packages are installed." -msgstr "base、sudo、linux、linux-firmware、efibootmgr などのパッケージとオプションのプロファイルパッケージのみがインストールされます。" - -msgid "Select zram compression algorithm:" -msgstr "zram 圧縮アルゴリズムを選択:" - -msgid "Use Network Manager (default backend)" -msgstr "Network Manager を使用(デフォルトのバックエンド)" - -msgid "Use Network Manager (iwd backend)" -msgstr "Network Manager を使用(iwd バックエンド)" - -msgid "Firewall" -msgstr "ファイアウォール" - -msgid "Additional fonts" -msgstr "追加フォント" - -msgid "Select font packages to install" -msgstr "インストールするフォントパッケージを選択" - -msgid "Unicode font coverage for most languages" -msgstr "ほとんどの言語をカバーする Unicode フォント" - -msgid "color emoji for browsers and apps" -msgstr "ブラウザやアプリ用のカラー絵文字" - -msgid "Chinese, Japanese, Korean characters" -msgstr "日本語、中国語、韓国語の文字" - -msgid "Select audio configuration" -msgstr "オーディオ設定を選択" - -msgid "Enter credentials file decryption password" -msgstr "認証情報ファイルの復号パスワードを入力" - -msgid "Enter root password" -msgstr "root パスワードを入力" - -msgid "Select bootloader to install" -msgstr "インストールするブートローダーを選択" - -msgid "Configuration preview" -msgstr "設定プレビュー" - -msgid "Enter a directory for the configuration(s) to be saved" -msgstr "設定を保存するディレクトリを入力" - -msgid "Select encryption type" -msgstr "暗号化のタイプを選択" - -msgid "Select disks for the installation" -msgstr "インストール用のディスクを選択" - -msgid "Enter a mountpoint" -msgstr "マウントポイントを入力" - -#, python-brace-format -msgid "Enter a size (default: {}): " -msgstr "サイズを入力(デフォルト: {}): " - -msgid "Enter subvolume name" -msgstr "サブボリューム名を入力" - -msgid "Enter subvolume mountpoint" -msgstr "サブボリュームのマウントポイントを入力" - -msgid "Select a disk configuration" -msgstr "ディスクの設定を選択" - -msgid "Enter root mount directory" -msgstr "ルートマウントディレクトリを入力" - -msgid "You will use whatever drive-setup is mounted at the specified directory" -msgstr "指定されたディレクトリにマウントされているドライブの設定を使用します" - -msgid "WARNING: Archinstall won't check the suitability of this setup" -msgstr "警告: Archinstall はこのセットアップの適合性をチェックしません" - -msgid "Select main filesystem" -msgstr "メインファイルシステムを選択" - -msgid "Enter a hostname" -msgstr "ホスト名を入力" - -msgid "Select timezone" -msgstr "タイムゾーンを選択" - -msgid "Enter the number of parallel downloads to be enabled" -msgstr "有効にする並列ダウンロード数を入力" - -#, python-brace-format -msgid "Value must be between 1 and {}" -msgstr "値は 1 から {} までの範囲にしてください" - -msgid "Select which kernel(s) to install" -msgstr "インストールするカーネルを選択" - -msgid "Enter a respository name" -msgstr "リポジトリ名を入力" - -msgid "Enter the repository url" -msgstr "リポジトリの URL を入力" - -msgid "Enter server url" -msgstr "サーバーの URL を入力" - -msgid "Select mirror regions to be enabled" -msgstr "有効にするミラー地域を選択" - -msgid "Select optional repositories to be enabled" -msgstr "有効にするオプションリポジトリを選択" - -msgid "Select an interface" -msgstr "インターフェースを選択" - -msgid "Choose network configuration" -msgstr "ネットワーク設定を選択" - -msgid "Recommended: Network Manager for desktop, Manual for server" -msgstr "推奨: デスクトップのネットワークマネージャー、サーバーのマニュアル" - -msgid "Warning: no network configuration selected. Network will need to be set up manually on the installed system." -msgstr "警告: ネットワーク設定が選択されていません。インストール後のシステムで、ネットワークを手動で設定する必要があります。" - -msgid "No packages found" -msgstr "パッケージが見つかりません" - -msgid "Select which greeter to install" -msgstr "インストールするグリーターを選択" - -msgid "Select a profile type" -msgstr "プロファイルのタイプを選択" - -msgid "Enter new password" -msgstr "新しいパスワードを入力" - -msgid "Enter a username" -msgstr "ユーザー名を入力" - -msgid "Enter a password" -msgstr "パスワードを入力" - -msgid "The password did not match, please try again" -msgstr "パスワードが一致しません。もう一度試してください" - -msgid "Password strength: Weak" -msgstr "パスワードの強度: 弱い" - -msgid "Password strength: Moderate" -msgstr "パスワードの強度: 中程度" - -msgid "Password strength: Strong" -msgstr "パスワードの強度: 強い" - -msgid "The selected desktop profile requires a regular user to log in via the greeter" -msgstr "選択されたデスクトッププロファイルでは、一般ユーザーがグリーター経由でログインする必要があります" - -#, python-brace-format -msgid "Environment type: {} {}" -msgstr "環境タイプ: {} {}" - -msgid "Input cannot be empty" -msgstr "入力は空にできません" - msgid "Recommended" msgstr "推奨" @@ -2203,34 +35,15 @@ msgstr "グループ内のパッケージ" msgid "Minimal KDE Plasma installation" msgstr "最小限の KDE Plasma インストール" +msgid "Type" +msgstr "タイプ" + msgid "Description" msgstr "説明" msgid "Select a flavor of KDE Plasma to install" msgstr "インストールする KDE Plasma のバージョンを選択" -msgid "Arial/Times/Courier replacement, Cyrillic support for Steam/games" -msgstr "Arial/Times/Courier フォントの代替、Steam/ゲームにおけるキリル文字のサポート" - -msgid "wide Unicode coverage, good fallback font" -msgstr "Unicode を広くカバーする良い代替フォント" - -#, python-brace-format -msgid "Setting up U2F login: {u2f_config.u2f_login_method.value}" -msgstr "U2F ログインの設定: {u2f_config.u2f_login_method.value}" - -#, python-brace-format -msgid "Default: {DEFAULT_ITER_TIME}ms, Recommended range: 1000-60000" -msgstr "デフォルト: {DEFAULT_ITER_TIME}ms, 推奨範囲: 1000-60000" - -#, python-brace-format -msgid "Setting up U2F login: {}" -msgstr "U2F ログインの設定: {}" - -#, python-brace-format -msgid "Default: {}ms, Recommended range: 1000-60000" -msgstr "デフォルト: {}ms, 推奨範囲: 1000-60000" - #, python-brace-format msgid "{} needs access to your seat" msgstr "{} はお使いの Seat へのアクセスを必要とします" @@ -2242,6 +55,754 @@ msgstr "ハードウェアデバイス群。キーボード、マウスなど" msgid "Choose an option how to give {} access to your hardware" msgstr "{} にハードウェアへのアクセスを許可する方法を選択" +#, python-brace-format +msgid "Environment type: {} {}" +msgstr "環境タイプ: {} {}" + +#, python-brace-format +msgid "Environment type: {}" +msgstr "環境タイプ: {}" + +msgid "Installed packages" +msgstr "インストールするパッケージ" + +msgid "Bluetooth" +msgstr "Bluetooth" + +msgid "Audio" +msgstr "オーディオ" + +msgid "Print service" +msgstr "印刷サービス" + +msgid "Power management" +msgstr "電力管理" + +msgid "Firewall" +msgstr "ファイアウォール" + +msgid "Additional fonts" +msgstr "追加フォント" + +msgid "Enabled" +msgstr "有効" + +msgid "Disabled" +msgstr "無効" + +msgid "Would you like to configure Bluetooth?" +msgstr "Bluetooth を設定しますか?" + +msgid "Would you like to configure the print service?" +msgstr "印刷サービスを設定しますか?" + +msgid "Select audio configuration" +msgstr "オーディオ設定を選択" + +msgid "Select font packages to install" +msgstr "インストールするフォントパッケージを選択" + +msgid "ArchInstall Language" +msgstr "ArchInstall の言語" + +msgid "Version" +msgstr "バージョン" + +msgid "Installation Script" +msgstr "インストールスクリプト" + +msgid "Locales" +msgstr "ロケール" + +msgid "Disk configuration" +msgstr "ディスクの設定" + +msgid "Profile" +msgstr "プロファイル" + +msgid "Mirrors and repositories" +msgstr "ミラーとリポジトリ" + +msgid "Network" +msgstr "ネットワーク" + +msgid "Bootloader" +msgstr "ブートローダー" + +msgid "Application" +msgstr "アプリケーション" + +msgid "Authentication" +msgstr "認証" + +msgid "Swap" +msgstr "スワップ" + +msgid "Hostname" +msgstr "ホスト名" + +msgid "Kernels" +msgstr "カーネル" + +msgid "Automatic time sync (NTP)" +msgstr "時刻の自動同期(NTP)" + +msgid "Timezone" +msgstr "タイムゾーン" + +msgid "Services" +msgstr "サービス" + +msgid "Additional packages" +msgstr "追加パッケージ" + +msgid "Pacman" +msgstr "Pacman" + +msgid "Custom commands" +msgstr "カスタムコマンド" + +msgid "Users" +msgstr "ユーザー" + +msgid "Root encrypted password" +msgstr "Root 暗号化パスワード" + +msgid "Disk encryption password" +msgstr "ディスク暗号化パスワード" + +msgid "Incorrect credentials file decryption password" +msgstr "認証情報ファイルの復号化パスワードが正しくありません" + +msgid "Enter credentials file decryption password" +msgstr "認証情報ファイルの復号パスワードを入力" + +msgid "Incorrect password" +msgstr "パスワードが間違っています" + +#, python-brace-format +msgid "Setting up U2F login: {}" +msgstr "U2F ログインの設定: {}" + +#, python-brace-format +msgid "Setting up U2F device for user: {}" +msgstr "ユーザー用 U2F デバイスの設定: {}" + +msgid "You may need to enter the PIN and then touch your U2F device to register it" +msgstr "PIN を入力したのち U2F デバイスをタッチして登録しなければならない場合があります" + +msgid "Root password" +msgstr "root パスワード" + +msgid "User account" +msgstr "ユーザーアカウント" + +msgid "U2F login setup" +msgstr "U2F ログインの設定" + +msgid "U2F login method: " +msgstr "U2F ログインメソッド: " + +msgid "Passwordless sudo: " +msgstr "パスワードなしの sudo: " + +msgid "No U2F devices found" +msgstr "U2F デバイスが見つかりません" + +msgid "Enter root password" +msgstr "root パスワードを入力" + +msgid "Enable passwordless sudo?" +msgstr "パスワードなしの sudo を有効にしますか?" + +msgid "Unified kernel images" +msgstr "Unified カーネルイメージ" + +msgid "Install to removable location" +msgstr "リムーバブルな場所にインストールする" + +msgid "Will install to /EFI/BOOT/ (removable location, safe default)" +msgstr "/EFI/BOOT/(リムーバブルな場所、安全なデフォルト)にインストール" + +msgid "Will install to custom location with NVRAM entry" +msgstr "NVRAM エントリーを使用してカスタムした場所にインストール" + +msgid "Would you like to use unified kernel images?" +msgstr "Unified カーネルイメージを使用しますか?" + +msgid "Would you like to install the bootloader to the default removable media search location?" +msgstr "ブートローダーをデフォルトのリムーバブルメディアの検索場所にインストールしますか?" + +msgid "This installs the bootloader to /EFI/BOOT/BOOTX64.EFI (or similar) which is useful for:" +msgstr "ブートローダーが /EFI/BOOT/BOOTX64.EFI (または類似の場所) にインストールされ、次の場合に役立ちます:" + +msgid "Firmware that does not properly support NVRAM boot entries like most MSI motherboards," +msgstr "ほとんどの MSI マザーボードのように NVRAM ブートエントリーを適切にサポートしていないファームウェア、" + +msgid "most Apple Macs, many laptops..." +msgstr "ほとんどの Apple Mac、多くのラップトップ..." + +msgid "USB drives or other portable external media." +msgstr "USB ドライブまたは他のポータブル外部メディア。" + +msgid "Systems where you want the disk to be bootable on any computer." +msgstr "ディスクをどのコンピューターでも起動できるようにするシステム。" + +msgid "Select bootloader to install" +msgstr "インストールするブートローダーを選択" + +msgid "UEFI is not detected and some options are disabled" +msgstr "UEFIが検出されず、一部のオプションが無効になります" + +msgid "The specified configuration will be applied" +msgstr "指定された設定が適用されます" + +msgid "Would you like to continue?" +msgstr "続行しますか?" + +msgid "Configuration preview" +msgstr "設定プレビュー" + +msgid "Warning: no network configuration selected. Network will need to be set up manually on the installed system." +msgstr "警告: ネットワーク設定が選択されていません。インストール後のシステムで、ネットワークを手動で設定する必要があります。" + +msgid "No configuration" +msgstr "設定なし" + +msgid "Save user configuration (including disk layout)" +msgstr "ユーザー設定を保存(ディスクレイアウトを含む)" + +msgid "Save user credentials" +msgstr "ユーザーの認証情報を保存" + +msgid "Save all" +msgstr "すべて保存" + +msgid "Enter a directory for the configuration(s) to be saved" +msgstr "設定を保存するディレクトリを入力" + +#, python-brace-format +msgid "Do you want to save the configuration file(s) to {}?" +msgstr "設定ファイルを次の場所に保存しますか? {}" + +msgid "Do you want to encrypt the user_credentials.json file?" +msgstr "user_credentials.json ファイルを暗号化しますか?" + +msgid "Credentials file encryption password" +msgstr "認証情報ファイルの暗号化パスワード" + +msgid "Partitioning" +msgstr "パーティションを作成" + +msgid "Disk encryption" +msgstr "ディスクの暗号化" + +#, python-brace-format +msgid "Configuration type: {}" +msgstr "設定タイプ: {}" + +msgid "Mountpoint" +msgstr "マウントポイント" + +msgid "Configuration" +msgstr "設定" + +msgid "Wipe" +msgstr "消去" + +msgid "Physical volumes" +msgstr "物理ボリューム" + +msgid "Volumes" +msgstr "ボリューム" + +#, python-brace-format +msgid "Snapshot type: {}" +msgstr "スナップショットのタイプ: {}" + +msgid "LVM disk encryption with more than 2 partitions is currently not supported" +msgstr "パーティションが2個を超える場合の LVM ディスク暗号化は、現在サポートしていません" + +msgid "Encryption type" +msgstr "暗号化のタイプ" + +msgid "Password" +msgstr "パスワード" + +msgid "Iteration time" +msgstr "イテレーション時間" + +msgid "Select disks for the installation" +msgstr "インストール用のディスクを選択" + +msgid "Partitions" +msgstr "パーティション" + +msgid "Select a disk configuration" +msgstr "ディスクの設定を選択" + +msgid "Enter root mount directory" +msgstr "ルートマウントディレクトリを入力" + +msgid "You will use whatever drive-setup is mounted at the specified directory" +msgstr "指定されたディレクトリにマウントされているドライブの設定を使用します" + +msgid "WARNING: Archinstall won't check the suitability of this setup" +msgstr "警告: Archinstall はこのセットアップの適合性をチェックしません" + +msgid "Select main filesystem" +msgstr "メインファイルシステムを選択" + +msgid "Would you like to use compression or disable CoW?" +msgstr "圧縮を使用しますか、それとも CoW を無効にしますか?" + +msgid "Use compression" +msgstr "圧縮する" + +msgid "Disable Copy-on-Write" +msgstr "コピーオンライトを無効にする" + +msgid "Would you like to use BTRFS subvolumes with a default structure?" +msgstr "デフォルトの設定で Btrfs サブボリュームを使用しますか?" + +msgid "Would you like to create a separate partition for /home?" +msgstr "/home 用に別のパーティションを作成しますか?" + +msgid "The selected drives do not have the minimum capacity required for an automatic suggestion\n" +msgstr "選択したドライブには、自動提案に必要な最小容量がありません\n" + +#, python-brace-format +msgid "Minimum capacity for /home partition: {}GiB\n" +msgstr "/home パーティションの最小容量: {}GiB\n" + +#, python-brace-format +msgid "Minimum capacity for Arch Linux partition: {}GiB" +msgstr "Arch Linux パーティションの最小容量: {}GiB" + +msgid "Encryption password" +msgstr "暗号化パスワード" + +msgid "LVM volumes" +msgstr "LVM ボリューム" + +msgid "HSM" +msgstr "HSM" + +msgid "Partitions to be encrypted" +msgstr "暗号化するパーティション" + +msgid "LVM volumes to be encrypted" +msgstr "暗号化する LVM ボリューム" + +msgid "HSM device" +msgstr "HSM デバイス" + +msgid "Select encryption type" +msgstr "暗号化のタイプを選択" + +msgid "Enter disk encryption password (leave blank for no encryption)" +msgstr "ディスクの暗号化パスワードを入力(暗号化しない場合は無記入)" + +msgid "Select a FIDO2 device to use for HSM" +msgstr "HSM に使用する FIDO2 デバイスを選択" + +msgid "Enter iteration time for LUKS encryption (in milliseconds)" +msgstr "LUKS 暗号化のイテレーション時間(ミリ秒単位)を入力" + +msgid "Higher values increase security but slow down boot time" +msgstr "値を大きくするとセキュリティは向上しますが、起動時間が遅くなります" + +#, python-brace-format +msgid "Default: {}ms, Recommended range: 1000-60000" +msgstr "デフォルト: {}ms, 推奨範囲: 1000-60000" + +msgid "Iteration time must be at least 100ms" +msgstr "イテレーション時間は最低 100ms にしてください" + +msgid "Iteration time must be at most 120000ms" +msgstr "イテレーション時間は最大 120000ms にしてください" + +msgid "Please enter a valid number" +msgstr "有効な数字を入力してください" + +msgid "Suggest partition layout" +msgstr "パーティションレイアウトを提案" + +msgid "Remove all newly added partitions" +msgstr "新しく追加されたパーティションをすべて削除" + +msgid "Assign mountpoint" +msgstr "マウントポイントを割り当てる" + +msgid "Mark/Unmark to be formatted (wipes data)" +msgstr "フォーマット対象としてマーク/マーク解除(データを消去)" + +msgid "Mark/Unmark as bootable" +msgstr "ブータブルとしてマーク/マーク解除" + +msgid "Mark/Unmark as ESP" +msgstr "ESP としてマーク/マーク解除" + +msgid "Mark/Unmark as XBOOTLDR" +msgstr "XBOOTLDR としてマーク/マーク解除" + +msgid "Change filesystem" +msgstr "ファイルシステムを変更" + +msgid "Mark/Unmark as compressed" +msgstr "圧縮対象としてマーク/マーク解除" + +msgid "Mark/Unmark as nodatacow" +msgstr "nodatacow としてマーク/マーク解除" + +msgid "Set subvolumes" +msgstr "サブボリュームを設定" + +msgid "Delete partition" +msgstr "パーティションを削除" + +#, python-brace-format +msgid "Partition management: {}" +msgstr "パーティションを管理: {}" + +#, python-brace-format +msgid "Total length: {}" +msgstr "全体のサイズ: {}" + +msgid "Partition - New" +msgstr "パーティション - 新規" + +msgid "Partition" +msgstr "パーティション" + +msgid "This partition is currently encrypted, to format it a filesystem has to be specified" +msgstr "このパーティションは現在暗号化されています。フォーマットするにはファイルシステムを指定してください" + +msgid "Partition mount-points are relative to inside the installation, the boot would be /boot as an example." +msgstr "パーティションのマウントポイントはインストールにおける相対的なもので、例として boot は /boot になります。" + +msgid "Enter a mountpoint" +msgstr "マウントポイントを入力" + +msgid "Invalid size" +msgstr "無効なサイズ" + +#, python-brace-format +msgid "Selected free space segment on device {}:" +msgstr "デバイス {} の選択された空きスペースセグメント:" + +#, python-brace-format +msgid "Size: {} / {}" +msgstr "サイズ: {} / {}" + +msgid "All entered values can be suffixed with a unit: %, B, KB, KiB, MB, MiB..." +msgstr "入力したすべての値に、%、B、KB、KiB、MB、MiB などの単位を付けることができます。" + +msgid "If no unit is provided, the value is interpreted as sectors" +msgstr "単位が指定されていない場合、値はセクターとして解釈されます" + +#, python-brace-format +msgid "Enter a size (default: {}): " +msgstr "サイズを入力(デフォルト: {}): " + +msgid "This will remove all newly added partitions, continue?" +msgstr "これは新しく追加されたパーティションをすべて削除します。続けますか?" + +msgid "Add subvolume" +msgstr "サブボリュームを追加" + +msgid "Edit subvolume" +msgstr "サブボリュームを編集" + +msgid "Delete subvolume" +msgstr "サブボリュームを削除" + +msgid "Value cannot be empty" +msgstr "値は空にできません" + +msgid "Enter subvolume name" +msgstr "サブボリューム名を入力" + +msgid "Subvolume name" +msgstr "サブボリューム名" + +msgid "Enter subvolume mountpoint" +msgstr "サブボリュームのマウントポイントを入力" + +msgid "Exit archinstall" +msgstr "archinstall を終了" + +msgid "Reboot system" +msgstr "システムを再起動" + +msgid "chroot into installation for post-installation configurations" +msgstr "インストール後の設定を行うためインストールディレクトリに chroot する" + +msgid "Would you like to use automatic time synchronization (NTP) with the default time servers?\n" +msgstr "デフォルトのタイムサーバーで時刻の自動同期(NTP)を使用しますか?\n" + +msgid "" +"Hardware time and other post-configuration steps might be required in order for NTP to work.\n" +"For more information, please check the Arch wiki" +msgstr "" +"NTP が機能するには、ハードウェアクロックおよびその他の設定後のステップが必要になる場合があります。\n" +"詳細については Arch wiki を確認してください" + +msgid "Enter a hostname" +msgstr "ホスト名を入力" + +msgid "Select timezone" +msgstr "タイムゾーンを選択" + +msgid "What would you like to do next?" +msgstr "次は何をしますか?" + +msgid "Select which kernel(s) to install" +msgstr "インストールするカーネルを選択" + +msgid "For the best compatibility with your AMD hardware, you may want to use either the all open-source or AMD / ATI options." +msgstr "AMD ハードウェアとの互換性を最大限に高めるには、すべてのオープンソースまたは AMD/ATI オプションのいずれかを使用することをお勧めします。" + +msgid "For the best compatibility with your Intel hardware, you may want to use either the all open-source or Intel options.\n" +msgstr "Intel ハードウェアとの互換性を最大限に高めるには、すべてのオープンソースまたは Intel オプションのいずれかを使用することをお勧めします。\n" + +msgid "For the best compatibility with your Nvidia hardware, you may want to use the Nvidia proprietary driver.\n" +msgstr "Nvidia ハードウェアとの互換性を最大限に高めるには、Nvidia 独自のドライバーを使用することをお勧めします。\n" + +msgid "Would you like to use swap on zram?" +msgstr "zram でスワップを使用しますか?" + +msgid "Select zram compression algorithm:" +msgstr "zram 圧縮アルゴリズムを選択:" + +msgid "Archinstall language" +msgstr "Archinstall の言語" + +msgid "Applications" +msgstr "アプリケーション" + +msgid "Network configuration" +msgstr "ネットワーク設定" + +msgid "Save configuration" +msgstr "設定を保存" + +msgid "Install" +msgstr "インストール" + +msgid "Abort" +msgstr "中止" + +msgid "Either root-password or at least 1 user with sudo privileges must be specified" +msgstr "root パスワードか、1人以上の sudo 権限を持つユーザーを指定する必要があります" + +msgid "The selected desktop profile requires a regular user to log in via the greeter" +msgstr "選択されたデスクトッププロファイルでは、一般ユーザーがグリーター経由でログインする必要があります" + +msgid "Language" +msgstr "言語" + +msgid "NTP" +msgstr "NTP" + +msgid "LVM configuration type" +msgstr "LVM の設定タイプ" + +#, python-brace-format +msgid "Btrfs snapshot type: {}" +msgstr "Btrfs スナップショットのタイプ: {}" + +msgid "Swap on zram" +msgstr "Zram 上でスワップ" + +msgid "Compression algorithm" +msgstr "圧縮アルゴリズム" + +msgid "Parallel Downloads" +msgstr "並行ダウンロード" + +msgid "Color" +msgstr "カラー出力" + +msgid "Kernel" +msgstr "カーネル" + +msgid "Missing configurations:\n" +msgstr "設定が存在しません:\n" + +#, python-brace-format +msgid "Invalid configuration: {}" +msgstr "無効な設定: {}" + +msgid "Ready to install" +msgstr "インストールの準備が完了" + +msgid "Profiles" +msgstr "プロファイル" + +msgid "Graphics driver" +msgstr "グラフィックドライバー" + +msgid "Greeter" +msgstr "グリーター" + +msgid "Selected mirror regions" +msgstr "選択されたミラーの地域" + +msgid "Custom servers" +msgstr "カスタムサーバー" + +msgid "Optional repositories" +msgstr "オプションリポジトリ" + +msgid "Custom repositories" +msgstr "カスタムリポジトリ" + +#, python-brace-format +msgid "[!] A log file has been created here: {}" +msgstr "[!] ここにログファイルが作成されました: {}" + +msgid "Please submit this issue (and file) to https://github.com/archlinux/archinstall/issues" +msgstr "この問題(およびファイル)を https://github.com/archlinux/archinstall/issues に送信してください" + +msgid "Syncing the system..." +msgstr "システムを同期..." + +msgid "Waiting for time sync (timedatectl show) to complete." +msgstr "時刻の同期(timedatectl show)が完了するのを待機しています。" + +msgid "Time synchronization not completing, while you wait - check the docs for workarounds: https://archinstall.readthedocs.io/" +msgstr "待機中に時刻同期が完了しません - 回避策についてはドキュメントを確認してください: https://archinstall.readthedocs.io/" + +msgid "Skipping waiting for automatic time sync (this can cause issues if time is out of sync during installation)" +msgstr "自動での時刻同期の待機をスキップします(インストール中に時刻が同期していない場合は、問題が発生する可能性があります)" + +msgid "Waiting for Arch Linux keyring sync (archlinux-keyring-wkd-sync) to complete." +msgstr "Arch Linux キーリングの同期(archlinux-keyring-wkd-sync)が完了するのを待っています。" + +msgid "Keyboard layout" +msgstr "キーボードレイアウト" + +msgid "Locale language" +msgstr "ロケール言語" + +msgid "Locale encoding" +msgstr "ロケールエンコーディング" + +msgid "Console font" +msgstr "コンソールフォント" + +msgid "Back" +msgstr "戻る" + +msgid "Are you sure you want to reset this setting?" +msgstr "この設定をリセットしてもよろしいですか?" + +msgid "Confirm and exit" +msgstr "確認して終了" + +msgid "Cancel" +msgstr "キャンセル" + +msgid "Password strength: Weak" +msgstr "パスワードの強度: 弱い" + +msgid "Password strength: Moderate" +msgstr "パスワードの強度: 中程度" + +msgid "Password strength: Strong" +msgstr "パスワードの強度: 強い" + +msgid "Confirm password" +msgstr "パスワードを確認" + +msgid "The password did not match, please try again" +msgstr "パスワードが一致しません。もう一度試してください" + +msgid "Not a valid directory" +msgstr "有効なディレクトリではありません" + +msgid "Do you really want to abort?" +msgstr "本当に中止しますか?" + +msgid "Add a custom repository" +msgstr "カスタムリポジトリを追加" + +msgid "Change custom repository" +msgstr "カスタムリポジトリを変更" + +msgid "Delete custom repository" +msgstr "カスタムリポジトリを削除" + +msgid "Enter a repository name" +msgstr "リポジトリ名を入力" + +msgid "Name" +msgstr "名前" + +msgid "Enter the repository url" +msgstr "リポジトリの URL を入力" + +msgid "Url" +msgstr "URL" + +msgid "Select signature check" +msgstr "署名チェックを選択" + +msgid "Signature check" +msgstr "署名チェック" + +msgid "Select signature option" +msgstr "署名オプションを選択" + +msgid "Add a custom server" +msgstr "カスタムサーバーを追加" + +msgid "Change custom server" +msgstr "カスタムサーバーを変更" + +msgid "Delete custom server" +msgstr "カスタムサーバーを削除" + +msgid "Enter server url" +msgstr "サーバーの URL を入力" + +msgid "Select regions" +msgstr "地域を選択" + +msgid "Add custom servers" +msgstr "カスタムサーバーを追加" + +msgid "Add custom repository" +msgstr "カスタムリポジトリを追加" + +msgid "Additional repositories" +msgstr "追加リポジトリ" + +msgid "Loading mirror regions..." +msgstr "ミラーの地域を読み込んでいます..." + +msgid "Select mirror regions to be enabled" +msgstr "有効にするミラー地域を選択" + +msgid "Select optional repositories to be enabled" +msgstr "有効にするオプションリポジトリを選択" + +msgid "Unicode font coverage for most languages" +msgstr "ほとんどの言語をカバーする Unicode フォント" + +msgid "color emoji for browsers and apps" +msgstr "ブラウザやアプリ用のカラー絵文字" + +msgid "Chinese, Japanese, Korean characters" +msgstr "日本語、中国語、韓国語の文字" + +msgid "Arial/Times/Courier replacement, Cyrillic support for Steam/games" +msgstr "Arial/Times/Courier フォントの代替、Steam/ゲームにおけるキリル文字のサポート" + +msgid "wide Unicode coverage, good fallback font" +msgstr "Unicode を広くカバーする良い代替フォント" + msgid "Zram enabled" msgstr "Zram を有効にする" @@ -2271,6 +832,12 @@ msgstr "ファイアウォール \"{}\"" msgid "Extra fonts \"{}\"" msgstr "追加フォント \"{}\"" +msgid "Passwordless login" +msgstr "パスワードなしのログイン" + +msgid "Second factor login" +msgstr "2要素ログイン" + msgid "Root password set" msgstr "Root パスワードを設定" @@ -2282,20 +849,23 @@ msgid "U2F set up" msgstr "U2F を設定" #, python-brace-format -msgid "Keyboard layout \"{}\"" -msgstr "キーボードレイアウト \"{}\"" +msgid "Bootloader \"{}\"" +msgstr "ブートローダー \"{}\"" -#, python-brace-format -msgid "Locale language \"{}\"" -msgstr "ロケール言語 \"{}\"" +msgid "UKI enabled" +msgstr "UKI を有効にする" -#, python-brace-format -msgid "Locale encoding \"{}\"" -msgstr "ロケールエンコーディング \"{}\"" +msgid "Removable" +msgstr "リムーバブル" -#, python-brace-format -msgid "Console font \"{}\"" -msgstr "コンソールフォント \"{}\"" +msgid "Use a best-effort default partition layout" +msgstr "ベストエフォートのデフォルトパーティションレイアウトを使用" + +msgid "Manual Partitioning" +msgstr "手動でパーティションを作成" + +msgid "Pre-mounted configuration" +msgstr "現在のマウント設定" msgid "Default" msgstr "デフォルト" @@ -2325,8 +895,39 @@ msgstr "{} 暗号化" msgid "Btrfs snapshot \"{}\"" msgstr "Btrfs スナップショット \"{}\"" -msgid "Use standalone iwd" -msgstr "スタンドアロンの iwd を使用" +msgid "Unknown" +msgstr "不明" + +msgid "Default layout" +msgstr "デフォルトのレイアウト" + +msgid "No Encryption" +msgstr "暗号化なし" + +msgid "LUKS" +msgstr "LUKS" + +msgid "LVM on LUKS" +msgstr "LUKS 上の LVM" + +msgid "LUKS on LVM" +msgstr "LVM 上の LUKS" + +#, python-brace-format +msgid "Keyboard layout \"{}\"" +msgstr "キーボードレイアウト \"{}\"" + +#, python-brace-format +msgid "Locale language \"{}\"" +msgstr "ロケール言語 \"{}\"" + +#, python-brace-format +msgid "Locale encoding \"{}\"" +msgstr "ロケールエンコーディング \"{}\"" + +#, python-brace-format +msgid "Console font \"{}\"" +msgstr "コンソールフォント \"{}\"" #, python-brace-format msgid "Mirror regions \"{}\"" @@ -2342,12 +943,23 @@ msgstr "カスタムサーバーを設定" msgid "Custom repositories set up" msgstr "カスタムリポジトリを設定" -#, python-brace-format -msgid "Bootloader \"{}\"" -msgstr "ブートローダー \"{}\"" +msgid "Copy ISO network configuration to installation" +msgstr "ISO のネットワーク設定をインストール環境にコピー" -msgid "UKI enabled" -msgstr "UKI を有効にする" +msgid "Use Network Manager (default backend)" +msgstr "Network Manager を使用(デフォルトのバックエンド)" + +msgid "Use Network Manager (iwd backend)" +msgstr "Network Manager を使用(iwd バックエンド)" + +msgid "Use standalone iwd" +msgstr "スタンドアロンの iwd を使用" + +msgid "Manual configuration" +msgstr "手動設定" + +msgid "Package group:" +msgstr "パッケージグループ:" msgid "Color enabled" msgstr "カラー出力を有効にする" @@ -2360,29 +972,172 @@ msgstr "{} グラフィックドライバー" msgid "{} greeter" msgstr "{} グリーター" -msgid "ArchInstall Language" -msgstr "ArchInstall の言語" +msgid "very weak" +msgstr "非常に弱い" -msgid "Version" -msgstr "バージョン" +msgid "weak" +msgstr "弱い" -msgid "Installation Script" -msgstr "インストールスクリプト" +msgid "moderate" +msgstr "中程度" -msgid "Application" -msgstr "アプリケーション" +msgid "strong" +msgstr "強い" -msgid "Services" -msgstr "サービス" +msgid "Add interface" +msgstr "インターフェイスを追加" -msgid "Custom commands" -msgstr "カスタムコマンド" +msgid "Edit interface" +msgstr "インターフェイスを編集" -msgid "Users" +msgid "Delete interface" +msgstr "インターフェイスを削除" + +msgid "Select an interface" +msgstr "インターフェースを選択" + +msgid "You need to enter a valid IP in IP-config mode" +msgstr "IP設定モードで有効なIPを入力する必要があります" + +#, python-brace-format +msgid "Select which mode to configure for \"{}\"" +msgstr "「{}」に設定するモードを選択" + +#, python-brace-format +msgid "Enter the IP and subnet for {} (example: 192.168.0.5/24): " +msgstr "{} の IP とサブネットを入力(例: 192.168.0.5/24): " + +msgid "Enter your gateway (router) IP address (leave blank for none)" +msgstr "ゲートウェイ(ルーター)の IP アドレスを入力。無い場合は無記入" + +msgid "Enter your DNS servers with space separated (leave blank for none)" +msgstr "DNS サーバーをスペースで区切って入力(無い場合は無記入)" + +msgid "Choose network configuration" +msgstr "ネットワーク設定を選択" + +msgid "Recommended: Network Manager for desktop, Manual for server" +msgstr "推奨: デスクトップのネットワークマネージャー、サーバーのマニュアル" + +msgid "Configure interfaces" +msgstr "インターフェースを設定" + +msgid "No network connection found" +msgstr "ネットワーク接続が見つかりません" + +msgid "Would you like to connect to a Wifi?" +msgstr "Wi-Fi に接続しますか?" + +msgid "No wifi interface found" +msgstr "Wi-Fi インターフェースが見つかりません" + +msgid "Select wifi network to connect to" +msgstr "接続する Wi-Fi ネットワークを選択" + +msgid "Scanning wifi networks..." +msgstr "Wi-Fi ネットワークをスキャンしています..." + +msgid "No wifi networks found" +msgstr "Wi-Fi ネットワークが見つかりません" + +msgid "Failed setting up wifi" +msgstr "Wi-Fi の設定に失敗しました" + +msgid "Enter wifi password" +msgstr "Wi-Fi パスワードを入力" + +#, python-brace-format +msgid "Repositories: {}" +msgstr "リポジトリ: {}" + +msgid "Loading packages..." +msgstr "パッケージを読み込んでいます..." + +msgid "No packages found" +msgstr "パッケージが見つかりません" + +msgid "Only packages such as base, sudo, linux, linux-firmware, efibootmgr and optional profile packages are installed." +msgstr "base、sudo、linux、linux-firmware、efibootmgr などのパッケージとオプションのプロファイルパッケージのみがインストールされます。" + +msgid "Note: base-devel is no longer installed by default. Add it here if you need build tools." +msgstr "注意: base-devel はデフォルトではインストールされなくなりました。ビルドツールが必要な場合は、ここで追加してください。" + +msgid "Select any packages from the below list that should be installed additionally" +msgstr "追加でインストールする必要があるパッケージをリストから選択してください" + +msgid "Pacman is already running, waiting maximum 10 minutes for it to terminate." +msgstr "Pacman はすでに実行されており、終了するまで最大 10 分間待機します。" + +msgid "Pre-existing pacman lock never exited. Please clean up any existing pacman sessions before using archinstall." +msgstr "既存の pacman のロックが終了しませんでした。archinstall を使用する前に、既存の pacman セッションをすべてクリーンアップしてください。" + +#, python-brace-format +msgid "Enter the number of parallel downloads (1-{})" +msgstr "並列ダウンロード数を入力 (1-{})" + +#, python-brace-format +msgid "Value must be between 1 and {}" +msgstr "値は 1 から {} までの範囲にしてください" + +msgid "Enable colored output for pacman" +msgstr "pacman でカラー出力を有効にする" + +msgid "The proprietary Nvidia driver is not supported by Sway." +msgstr "プロプライエタリの Nvidia ドライバーは Sway ではサポートされていません。" + +msgid "It is likely that you will run into issues, are you okay with that?" +msgstr "問題が発生する可能性が高いですが、よろしいですか?" + +msgid "Selected profiles: " +msgstr "選択したプロファイル: " + +msgid "Select which greeter to install" +msgstr "インストールするグリーターを選択" + +msgid "Select a profile type" +msgstr "プロファイルのタイプを選択" + +#, python-brace-format +msgid "Unable to fetch profile from specified url: {}" +msgstr "指定された URL からプロファイルを取得できません: {}" + +#, python-brace-format +msgid "Profiles must have unique name, but profile definitions with duplicate name found: {}" +msgstr "プロファイルには一意の名前が必要ですが、重複した名前のプロファイル定義が見つかりました: {}" + +msgid "Add a user" +msgstr "ユーザーを追加" + +msgid "Change password" +msgstr "パスワードを変更" + +msgid "Promote/Demote user" +msgstr "ユーザーを昇格/降格" + +msgid "Delete User" +msgstr "ユーザーを削除" + +msgid "User" msgstr "ユーザー" -msgid "Root encrypted password" -msgstr "Root 暗号化パスワード" +msgid "Enter new password" +msgstr "新しいパスワードを入力" + +msgid "The username you entered is invalid" +msgstr "入力したユーザー名は無効です" + +msgid "Enter a username" +msgstr "ユーザー名を入力" + +msgid "Username" +msgstr "ユーザー名" + +msgid "Enter a password" +msgstr "パスワードを入力" + +#, python-brace-format +msgid "Should \"{}\" be a superuser (sudo)?\n" +msgstr "\"{}\" はスーパーユーザーに昇格しますか(sudo)?\n" #, python-brace-format msgid "About to upload \"{}\" to the publicly accessible {}" @@ -2397,3 +1152,1248 @@ msgstr "ログのアップロードに成功しました。URL: {}" msgid "Failed to upload log." msgstr "ログのアップロードに失敗しました。" + +msgid "Archinstall requires root privileges to run. See --help for more." +msgstr "Archinstall を実行するには root 権限が必要です。詳細については --help を参照してください。" + +msgid "New version available" +msgstr "新しいバージョンが利用可能" + +msgid "Starting device modifications in " +msgstr "次の場所でデバイスの変更を開始しています " + +msgid "Ok" +msgstr "OK" + +msgid "Input cannot be empty" +msgstr "入力は空にできません" + +msgid "Yes" +msgstr "Yes" + +msgid "No" +msgstr "No" + +msgid " (default)" +msgstr " (デフォルト)" + +#~ msgid "[!] A log file has been created here: {} {}" +#~ msgstr "[!] ここにログファイルが作成されました: {} {}" + +#~ msgid " Please submit this issue (and file) to https://github.com/archlinux/archinstall/issues" +#~ msgstr " この問題(およびファイル)を https://github.com/archlinux/archinstall/issues に送信してください" + +#~ msgid "And one more time for verification: " +#~ msgstr "確認のためにもう1度: " + +#~ msgid "Desired hostname for the installation: " +#~ msgstr "インストール時のホスト名: " + +#~ msgid "Username for required superuser with sudo privileges: " +#~ msgstr "sudo 権限を持つスーパーユーザーのユーザー名: " + +#~ msgid "Any additional users to install (leave blank for no users): " +#~ msgstr "インストールする追加のユーザー(ユーザーがない場合は無記入): " + +#~ msgid "Should this user be a superuser (sudoer)?" +#~ msgstr "このユーザーはスーパーユーザーに昇格しますか(sudoer)?" + +#~ msgid "Select a timezone" +#~ msgstr "タイムゾーンを選択" + +#~ msgid "Would you like to use GRUB as a bootloader instead of systemd-boot?" +#~ msgstr "systemd-boot の代わりに GRUB をブートローダーとして使用しますか?" + +#~ msgid "Choose a bootloader" +#~ msgstr "ブートローダーを選択" + +#~ msgid "Choose an audio server" +#~ msgstr "オーディオサーバーを選択" + +#~ msgid "Only packages such as base, base-devel, linux, linux-firmware, efibootmgr and optional profile packages are installed." +#~ msgstr "base, base-devel, linux, linux-firmware, efibootmgr などのパッケージとオプションのプロファイルパッケージのみがインストールされます。" + +#~ msgid "If you desire a web browser, such as firefox or chromium, you may specify it in the following prompt." +#~ msgstr "firefox や chromium などのウェブブラウザーをインストールする場合は、次のプロンプトで指定できます。" + +#~ msgid "Write additional packages to install (space separated, leave blank to skip): " +#~ msgstr "追加でインストールするパッケージを書く(スペースで区切る。無記入でスキップ): " + +#~ msgid "Use NetworkManager (necessary for configuring internet graphically in GNOME and KDE)" +#~ msgstr "ネットワークマネージャーを使用(GNOME と KDE でインターネットをグラフィカルに設定するのに必要)" + +#~ msgid "Select one network interface to configure" +#~ msgstr "設定するネットワークインターフェイスを 1 つ選択" + +#~ msgid "Select which mode to configure for \"{}\" or skip to use default mode \"{}\"" +#~ msgstr "どのモードを \"{}\" に設定するかを選択。スキップでデフォルトモード \"{}\" を使用" + +#~ msgid "Enter your gateway (router) IP address or leave blank for none: " +#~ msgstr "ゲートウェイ(ルーター)の IP アドレスを入力。無い場合は無記入: " + +#~ msgid "Enter your DNS servers (space separated, blank for none): " +#~ msgstr "DNS サーバーを入力(スペースで区切る。無い場合は無記入): " + +#~ msgid "Select which filesystem your main partition should use" +#~ msgstr "メインパーティションで使用するファイルシステムを選択" + +#~ msgid "Current partition layout" +#~ msgstr "現在のパーティションレイアウト" + +#~ msgid "" +#~ "Select what to do with\n" +#~ "{}" +#~ msgstr "" +#~ "何をするか選択\n" +#~ "{}" + +#~ msgid "Enter a desired filesystem type for the partition" +#~ msgstr "パーティションのファイルシステムを入力" + +#~ msgid "Enter the start location (in parted units: s, GB, %, etc. ; default: {}): " +#~ msgstr "開始場所を入力(単位: s, GB, % など。デフォルト: {}): " + +#~ msgid "Enter the end location (in parted units: s, GB, %, etc. ; ex: {}): " +#~ msgstr "終了場所を入力(単位: s, GB, % など。例: {}): " + +#~ msgid "{} contains queued partitions, this will remove those, are you sure?" +#~ msgstr "{} にはキューに入っているパーティションが含まれます。それらが削除されますがよろしいですか?" + +#~ msgid "" +#~ "{}\n" +#~ "\n" +#~ "Select by index which partitions to delete" +#~ msgstr "" +#~ "{}\n" +#~ "\n" +#~ "削除するパーティションをインデックスで選択" + +#~ msgid "" +#~ "{}\n" +#~ "\n" +#~ "Select by index which partition to mount where" +#~ msgstr "" +#~ "{}\n" +#~ "\n" +#~ "どのパーティションをどこにマウントするかをインデックスで選択" + +#~ msgid " * Partition mount-points are relative to inside the installation, the boot would be /boot as an example." +#~ msgstr "* パーティションのマウントポイントはインストールにおける相対的なものであり、例として boot は /boot になります。" + +#~ msgid "Select where to mount partition (leave blank to remove mountpoint): " +#~ msgstr "パーティションをマウントする場所を選択(無記入でマウントポイントを削除): " + +#~ msgid "" +#~ "{}\n" +#~ "\n" +#~ "Select which partition to mask for formatting" +#~ msgstr "" +#~ "{}\n" +#~ "\n" +#~ "フォーマット対象としてマークするパーティションを選択" + +#~ msgid "" +#~ "{}\n" +#~ "\n" +#~ "Select which partition to mark as encrypted" +#~ msgstr "" +#~ "{}\n" +#~ "\n" +#~ "暗号化対象としてマークするパーティションを選択" + +#~ msgid "" +#~ "{}\n" +#~ "\n" +#~ "Select which partition to mark as bootable" +#~ msgstr "" +#~ "{}\n" +#~ "\n" +#~ "ブータブルとしてマークするパーティションを選択" + +#~ msgid "" +#~ "{}\n" +#~ "\n" +#~ "Select which partition to set a filesystem on" +#~ msgstr "" +#~ "{}\n" +#~ "\n" +#~ "ファイルシステムを設定するパーティションを選択" + +#~ msgid "Enter a desired filesystem type for the partition: " +#~ msgstr "パーティションのファイルシステムを入力: " + +#~ msgid "Wipe all selected drives and use a best-effort default partition layout" +#~ msgstr "選択したすべてのドライブを消去し、ベストエフォートのデフォルトパーティションレイアウトを使用する" + +#~ msgid "Select what to do with each individual drive (followed by partition usage)" +#~ msgstr "個々のドライブをどうするかを選択(次にパーティションの使用方法が続く)" + +#~ msgid "Select what you wish to do with the selected block devices" +#~ msgstr "選択したブロックデバイスで何をするかを選択" + +#~ msgid "This is a list of pre-programmed profiles, they might make it easier to install things like desktop environments" +#~ msgstr "これは事前にプログラムされたプロファイルのリストです。デスクトップ環境などのインストールが簡単になる可能性があります" + +#~ msgid "Select keyboard layout" +#~ msgstr "キーボードレイアウトを選択" + +#~ msgid "Select one of the regions to download packages from" +#~ msgstr "パッケージをダウンロードする地域を 1 つ選択" + +#~ msgid "Select one or more hard drives to use and configure" +#~ msgstr "使用・設定する 1 つ以上のハードドライブを選択" + +#~ msgid "" +#~ "\n" +#~ "\n" +#~ "Select a graphics driver or leave blank to install all open-source drivers" +#~ msgstr "" +#~ "\n" +#~ "\n" +#~ "グラフィックドライバーを選択するか、無記入ですべてのオープンソースドライバーをインストール" + +#~ msgid "All open-source (default)" +#~ msgstr "すべてのオープンソース(デフォルト)" + +#~ msgid "Choose which kernels to use or leave blank for default \"{}\"" +#~ msgstr "使用するカーネルを選択。無記入でデフォルトの \"{}\"" + +#~ msgid "Choose which locale language to use" +#~ msgstr "使用するロケール言語を選択" + +#~ msgid "Choose which locale encoding to use" +#~ msgstr "使用するロケールエンコーディングを選択" + +#~ msgid "Select one of the values shown below: " +#~ msgstr "以下の値のいずれかを選択: " + +#~ msgid "Select one or more of the options below: " +#~ msgstr "以下のオプションを 1 つ以上選択: " + +#~ msgid "Adding partition...." +#~ msgstr "パーティションを追加...." + +#~ msgid "You need to enter a valid fs-type in order to continue. See `man parted` for valid fs-type's." +#~ msgstr "続行するには、有効な fs-type を入力する必要があります。有効な fs-type については、 `man parted` を参照してください。" + +#~ msgid "Error: Listing profiles on URL \"{}\" resulted in:" +#~ msgstr "エラー: URL \"{}\" のプロファイルをリストすると、次の結果が発生:" + +#~ msgid "Error: Could not decode \"{}\" result as JSON:" +#~ msgstr "エラー: \"{}\" の結果を JSON としてデコードできませんでした:" + +#~ msgid "Mirror region" +#~ msgstr "ミラーの地域" + +#~ msgid "Drive(s)" +#~ msgstr "ドライブ" + +#~ msgid "Disk layout" +#~ msgstr "ディスクレイアウト" + +#~ msgid "Superuser account" +#~ msgstr "スーパーユーザーアカウント" + +#~ msgid "Install ({} config(s) missing)" +#~ msgstr "インストール({} 個の設定がありません)" + +#~ msgid "" +#~ "You decided to skip harddrive selection\n" +#~ "and will use whatever drive-setup is mounted at {} (experimental)\n" +#~ "WARNING: Archinstall won't check the suitability of this setup\n" +#~ "Do you wish to continue?" +#~ msgstr "" +#~ "ハードドライブの選択をスキップし、\n" +#~ "{} にマウントしているドライブのセットアップを使用します(実験的)\n" +#~ "警告: Archinstall はこのセットアップの適合性をチェックしません\n" +#~ "続行しますか?" + +#~ msgid "Re-using partition instance: {}" +#~ msgstr "パーティションインスタンスの再利用: {}" + +#~ msgid "Create a new partition" +#~ msgstr "新しいパーティションを作成" + +#~ msgid "Delete a partition" +#~ msgstr "パーティションを削除" + +#~ msgid "Clear/Delete all partitions" +#~ msgstr "すべてのパーティションをクリア/削除" + +#~ msgid "Assign mount-point for a partition" +#~ msgstr "パーティションにマウントポイントを割り当てる" + +#~ msgid "Mark/Unmark a partition to be formatted (wipes data)" +#~ msgstr "フォーマットするパーティションとしてマーク/マーク解除(データを消去)" + +#~ msgid "Mark/Unmark a partition as encrypted" +#~ msgstr "暗号化するパーティションとしてマーク/マーク解除" + +#~ msgid "Mark/Unmark a partition as bootable (automatic for /boot)" +#~ msgstr "ブータブルパーティションとしてマーク/マーク解除(/boot の場合は自動)" + +#~ msgid "Set desired filesystem for a partition" +#~ msgstr "パーティションのファイルシステムを設定" + +#~ msgid "Not configured, unavailable unless setup manually" +#~ msgstr "未設定。手動で設定しない限り使用できません" + +#~ msgid "Set/Modify the below options" +#~ msgstr "以下のオプションを設定/変更" + +#~ msgid "" +#~ "Use ESC to skip\n" +#~ "\n" +#~ msgstr "" +#~ "Esc でスキップ\n" +#~ "\n" + +#~ msgid "Enter a password: " +#~ msgstr "パスワードを入力: " + +#~ msgid "Enter a encryption password for {}" +#~ msgstr "{} の暗号化パスワードを入力" + +#~ msgid "Enter disk encryption password (leave blank for no encryption): " +#~ msgstr "ディスクの暗号化パスワードを入力(暗号化しない場合は無記入): " + +#~ msgid "Create a required super-user with sudo privileges: " +#~ msgstr "sudo 権限を持つスーパーユーザーを作成: " + +#~ msgid "Enter root password (leave blank to disable root): " +#~ msgstr "root パスワードを入力(root を無効にする場合は無記入): " + +#~ msgid "Password for user \"{}\": " +#~ msgstr "ユーザー \"{}\" のパスワード: " + +#~ msgid "Verifying that additional packages exist (this might take a few seconds)" +#~ msgstr "追加のパッケージが存在することを確認(これには数秒かかる場合があります)" + +#~ msgid "Enter a username to create an additional user (leave blank to skip): " +#~ msgstr "ユーザー名を入力して追加ユーザーを作成(無記入でスキップ): " + +#~ msgid "Use ESC to skip\n" +#~ msgstr "Esc でスキップ\n" + +#~ msgid "" +#~ "\n" +#~ " Choose an object from the list, and select one of the available actions for it to execute" +#~ msgstr "" +#~ "\n" +#~ "リストからオブジェクトを選択し、そのオブジェクトで実行できるアクションの 1 つを選択" + +#~ msgid "Add" +#~ msgstr "追加" + +#~ msgid "Copy" +#~ msgstr "コピー" + +#~ msgid "Edit" +#~ msgstr "編集" + +#~ msgid "Delete" +#~ msgstr "削除" + +#~ msgid "Select an action for '{}'" +#~ msgstr "'{}' へのアクションを選択" + +#~ msgid "Copy to new key:" +#~ msgstr "新しいキーにコピー:" + +#~ msgid "Unknown nic type: {}. Possible values are {}" +#~ msgstr "不明な NIC タイプ: {}。可能な値は {}" + +#~ msgid "" +#~ "\n" +#~ "This is your chosen configuration:" +#~ msgstr "" +#~ "\n" +#~ "これが選択した設定です:" + +#~ msgid "Choose which optional additional repositories to enable" +#~ msgstr "オプションで有効にする追加リポジトリを選択" + +#~ msgid "" +#~ "\n" +#~ "Define a new user\n" +#~ msgstr "" +#~ "\n" +#~ "新しいユーザーを定義\n" + +#~ msgid "User Name : " +#~ msgstr "ユーザー名: " + +#~ msgid "Should {} be a superuser (sudoer)?" +#~ msgstr "{} はスーパーユーザーに昇格しますか(sudoer)?" + +#~ msgid "Define users with sudo privilege: " +#~ msgstr "sudo 権限を持つユーザーを定義: " + +#~ msgid "No network configuration" +#~ msgstr "ネットワーク設定なし" + +#~ msgid "Set desired subvolumes on a btrfs partition" +#~ msgstr "Btrfs パーティションに必要なサブボリュームを設定" + +#~ msgid "" +#~ "{}\n" +#~ "\n" +#~ "Select which partition to set subvolumes on" +#~ msgstr "" +#~ "{}\n" +#~ "\n" +#~ "サブボリュームを設定するパーティションを選択" + +#~ msgid "Manage btrfs subvolumes for current partition" +#~ msgstr "現在のパーティションの Btrfs サブボリュームを管理" + +#~ msgid "Save user configuration" +#~ msgstr "ユーザー設定を保存" + +#~ msgid "Save disk layout" +#~ msgstr "ディスクレイアウトを保存" + +#~ msgid "Choose which configuration to save" +#~ msgstr "保存する設定を選択" + +#~ msgid "Enter a directory for the configuration(s) to be saved: " +#~ msgstr "設定を保存するディレクトリを入力: " + +#~ msgid "Not a valid directory: {}" +#~ msgstr "有効なディレクトリではありません: {}" + +#~ msgid "The password you are using seems to be weak," +#~ msgstr "使用しているパスワードは弱いようです、" + +#~ msgid "are you sure you want to use it?" +#~ msgstr "本当に使用してもよろしいですか?" + +#~ msgid "Either root-password or at least 1 superuser must be specified" +#~ msgstr "root パスワードか、1人以上のスーパーユーザーを指定してください" + +#~ msgid "Manage superuser accounts: " +#~ msgstr "スーパーユーザーアカウントを管理: " + +#~ msgid "Manage ordinary user accounts: " +#~ msgstr "一般ユーザーのアカウントを管理: " + +#~ msgid " Subvolume :{:16}" +#~ msgstr " サブボリューム :{:16}" + +#~ msgid " mounted at {:16}" +#~ msgstr " マウント場所 {:16}" + +#~ msgid " with option {}" +#~ msgstr " 次のオプションで {}" + +#~ msgid "" +#~ "\n" +#~ " Fill the desired values for a new subvolume \n" +#~ msgstr "" +#~ "\n" +#~ " 新しいサブボリュームに必要な値を入力 \n" + +#~ msgid "Subvolume name " +#~ msgstr "サブボリューム名 " + +#~ msgid "Subvolume mountpoint" +#~ msgstr "サブボリュームのマウントポイント" + +#~ msgid "Subvolume options" +#~ msgstr "サブボリュームのオプション" + +#~ msgid "Save" +#~ msgstr "保存" + +#~ msgid "Subvolume name :" +#~ msgstr "サブボリューム名:" + +#~ msgid "Select a mount point :" +#~ msgstr "マウントポイントを選択:" + +#~ msgid "Select the desired subvolume options " +#~ msgstr "サブボリュームのオプションを選択" + +#~ msgid "Define users with sudo privilege, by username: " +#~ msgstr "sudo 権限を持つユーザーを、ユーザー名で定義: " + +#~ msgid "Would you like to use BTRFS compression?" +#~ msgstr "Btrfs の圧縮を使用しますか?" + +#~ msgid "Minimum capacity for /home partition: {}GB\n" +#~ msgstr "/home パーティションの最小容量: {}GB\n" + +#~ msgid "Minimum capacity for Arch Linux partition: {}GB" +#~ msgstr "Arch Linux パーティションの最小容量: {}GB" + +#~ msgid "Continue" +#~ msgstr "続行" + +#~ msgid "yes" +#~ msgstr "yes" + +#~ msgid "no" +#~ msgstr "no" + +#~ msgid "set: {}" +#~ msgstr "セット: {}" + +#~ msgid "Manual configuration setting must be a list" +#~ msgstr "手動設定はリストである必要があります" + +#~ msgid "No iface specified for manual configuration" +#~ msgstr "手動設定に iface が指定されていません" + +#~ msgid "Manual nic configuration with no auto DHCP requires an IP address" +#~ msgstr "自動 DHCP を使用しない手動 NIC 設定には、IP アドレスが必要です" + +#~ msgid "Select interface to add" +#~ msgstr "追加するインターフェースを選択" + +#~ msgid "Mark/Unmark a partition as compressed (btrfs only)" +#~ msgstr "圧縮するパーティションとしてマーク/マーク解除(Btrfs のみ)" + +#~ msgid "The password you are using seems to be weak, are you sure you want to use it?" +#~ msgstr "使用しているパスワードは弱いようですが、本当に使用してもよろしいですか?" + +#~ msgid "Provides a selection of desktop environments and tiling window managers, e.g. gnome, kde, sway" +#~ msgstr "デスクトップ環境とタイルウィンドウマネージャーの選択を提供します。e.g. gnome, kde, sway" + +#~ msgid "Select your desired desktop environment" +#~ msgstr "デスクトップ環境を選択" + +#~ msgid "A very basic installation that allows you to customize Arch Linux as you see fit." +#~ msgstr "Arch Linux を必要に応じてカスタマイズできる非常に基本的なインストールです。" + +#~ msgid "Provides a selection of various server packages to install and enable, e.g. httpd, nginx, mariadb" +#~ msgstr "さまざまなサーバー パッケージの選択肢を提供します。e.g. httpd, nginx, mariadb" + +#~ msgid "Choose which servers to install, if none then a minimal installation will be done" +#~ msgstr "インストールするサーバーを選択。存在しない場合は最小限のインストールが実行されます" + +#~ msgid "Installs a minimal system as well as xorg and graphics drivers." +#~ msgstr "最小限のシステムと xorg およびグラフィックドライバーをインストール。" + +#~ msgid "Press Enter to continue." +#~ msgstr "Enter キーで続行します。" + +#~ msgid "Would you like to chroot into the newly created installation and perform post-installation configuration?" +#~ msgstr "新しく作成したインストールに chroot して、インストール後の設定を実行しますか?" + +#~ msgid "Select one or more hard drives to use and configure\n" +#~ msgstr "使用・設定する 1 つ以上のハードドライブを選択\n" + +#~ msgid "Any modifications to the existing setting will reset the disk layout!" +#~ msgstr "既存の設定を変更すると、ディスクレイアウトがリセットされます!" + +#~ msgid "If you reset the harddrive selection this will also reset the current disk layout. Are you sure?" +#~ msgstr "ハードドライブの選択をリセットすると、現在のディスクレイアウトもリセットされます。よろしいですか?" + +#~ msgid "Save and exit" +#~ msgstr "保存して終了" + +#~ msgid "" +#~ "{}\n" +#~ "contains queued partitions, this will remove those, are you sure?" +#~ msgstr "" +#~ "{}\n" +#~ "キューに入れられたパーティションが含まれています。削除しますがよろしいですか?" + +#~ msgid "No audio server" +#~ msgstr "オーディオサーバーなし" + +#~ msgid "(default)" +#~ msgstr "(デフォルト)" + +#~ msgid "Use ESC to skip" +#~ msgstr "Esc でスキップ" + +#~ msgid "" +#~ "Use CTRL+C to reset current selection\n" +#~ "\n" +#~ msgstr "" +#~ "Ctrl+C で現在の選択をリセット\n" +#~ "\n" + +#~ msgid "Copy to: " +#~ msgstr "コピー先: " + +#~ msgid "Edit: " +#~ msgstr "編集: " + +#~ msgid "Key: " +#~ msgstr "キー: " + +#~ msgid "Edit {}: " +#~ msgstr "編集 {}: " + +#~ msgid "Add: " +#~ msgstr "追加: " + +#~ msgid "Value: " +#~ msgstr "値: " + +#~ msgid "You can skip selecting a drive and partitioning and use whatever drive-setup is mounted at /mnt (experimental)" +#~ msgstr "ドライブ選択とパーティション作成をスキップして、/mnt にマウントしているドライブのセットアップを使用できます(実験的)" + +#~ msgid "Select one of the disks or skip and use /mnt as default" +#~ msgstr "いずれかのディスクを選択するか、スキップして /mnt をデフォルトとして使用" + +#~ msgid "Select which partitions to mark for formatting:" +#~ msgstr "フォーマットするパーティションを選択:" + +#~ msgid "Use HSM to unlock encrypted drive" +#~ msgstr "HSM を使用して暗号化されたドライブのロックを解除" + +#~ msgid "Device" +#~ msgstr "デバイス" + +#~ msgid "Size" +#~ msgstr "サイズ" + +#~ msgid "Free space" +#~ msgstr "空き容量" + +#~ msgid "Bus-type" +#~ msgstr "Bus タイプ" + +#~ msgid "Enter username (leave blank to skip): " +#~ msgstr "ユーザー名を入力(無記入でスキップ): " + +#~ msgid "The username you entered is invalid. Try again" +#~ msgstr "入力したユーザー名は無効です。もう1度やり直してください" + +#~ msgid "Should \"{}\" be a superuser (sudo)?" +#~ msgstr "\"{}\" はスーパーユーザーに昇格しますか(sudo)?" + +#~ msgid "Select which partitions to encrypt" +#~ msgstr "暗号化するパーティションを選択" + +#~ msgid "Configured {} interfaces" +#~ msgstr "設定した {} インターフェース" + +#~ msgid "This option enables the number of parallel downloads that can occur during installation" +#~ msgstr "このオプションは、インストール中に実行できる並列ダウンロードの数を有効にします" + +#~ msgid "" +#~ "Enter the number of parallel downloads to be enabled.\n" +#~ " (Enter a value between 1 to {})\n" +#~ "Note:" +#~ msgstr "" +#~ "有効にする並列ダウンロードの数を入力してください。\n" +#~ " (1 から {} の値を入力)\n" +#~ "注意:" + +#~ msgid " - Maximum value : {} ( Allows {} parallel downloads, allows {} downloads at a time )" +#~ msgstr " - 最大値 : {} ({} 個の並列ダウンロードを許可して、1度に {} 個のダウンロードを許可する)" + +#~ msgid " - Minimum value : 1 ( Allows 1 parallel download, allows 2 downloads at a time )" +#~ msgstr " - 最小値 : 1 (1 個の並列ダウンロードを許可して、1度に 2 個のダウンロードを許可する)" + +#~ msgid " - Disable/Default : 0 ( Disables parallel downloading, allows only 1 download at a time )" +#~ msgstr " - 無効/デフォルト : 0 (並列ダウンロードを無効にして、1度に 1 個のみダウンロードを許可する)" + +#, python-brace-format +#~ msgid "Invalid input! Try again with a valid input [1 to {max_downloads}, or 0 to disable]" +#~ msgstr "無効な入力です!有効な入力を使用して再試行してください [1 - {max_downloads}、0 で無効]" + +#~ msgid "ESC to skip" +#~ msgstr "Esc でスキップ" + +#~ msgid "CTRL+C to reset" +#~ msgstr "Ctrl+C でリセット" + +#~ msgid "TAB to select" +#~ msgstr "Tab で選択" + +#~ msgid "[Default value: 0] > " +#~ msgstr "[デフォルト値: 0] > " + +#~ msgid "To be able to use this translation, please install a font manually that supports the language." +#~ msgstr "この翻訳を使用できるようにするには、その言語をサポートするフォントを手動でインストールしてください。" + +#~ msgid "The font should be stored as {}" +#~ msgstr "フォントは {} として保存する必要があります" + +#~ msgid "Select an execution mode" +#~ msgstr "実行モードを選択" + +#~ msgid "Select one or more devices to use and configure" +#~ msgstr "使用・設定する 1 つ以上のデバイスを選択" + +#~ msgid "If you reset the device selection this will also reset the current disk layout. Are you sure?" +#~ msgstr "デバイスの選択をリセットすると、現在のディスクレイアウトもリセットされます。よろしいですか?" + +#~ msgid "Existing Partitions" +#~ msgstr "既存のパーティション" + +#~ msgid "Select a partitioning option" +#~ msgstr "パーティション作成のオプションを選択" + +#~ msgid "Enter the root directory of the mounted devices: " +#~ msgstr "マウントしているデバイスのルートディレクトリを入力: " + +#~ msgid "This is a list of pre-programmed profiles_bck, they might make it easier to install things like desktop environments" +#~ msgstr "これは事前にプログラムされたプロファイルのリストです。デスクトップ環境などのインストールが簡単になる可能性があります" + +#~ msgid "Current profile selection" +#~ msgstr "現在のプロファイルの選択" + +#~ msgid "If mountpoint /boot is set, then the partition will also be marked as bootable." +#~ msgstr "マウントポイントに /boot が設定された場合、パーティションはブータブルとしてマークされます。" + +#~ msgid "Mountpoint: " +#~ msgstr "マウントポイント: " + +#~ msgid "Current free sectors on device {}:" +#~ msgstr "デバイス {} の現在の空きセクター:" + +#~ msgid "Total sectors: {}" +#~ msgstr "総セクター数: {}" + +#~ msgid "Enter the start sector (default: {}): " +#~ msgstr "開始セクターを入力(デフォルト: {}): " + +#~ msgid "Enter the end sector of the partition (percentage or block number, default: {}): " +#~ msgstr "パーティションの終了セクターを入力(パーセンテージかブロック番号。デフォルト: {}): " + +#~ msgid "Default: 10000ms, Recommended range: 1000-60000" +#~ msgstr "デフォルト: 10000ms, 推奨範囲: 1000-60000" + +#~ msgid "Iteration time cannot be empty" +#~ msgstr "イテレーション時間は空にできません" + +#~ msgid "No HSM devices available" +#~ msgstr "利用可能な HSM デバイスがありません" + +#~ msgid "Select disk encryption option" +#~ msgstr "ディスクの暗号化オプションを選択" + +#~ msgid "Partition encryption" +#~ msgstr "パーティションの暗号化" + +#, python-brace-format +#~ msgid " ! Formatting {} in " +#~ msgstr " ! {} のフォーマットまで " + +#~ msgid "← Back" +#~ msgstr "← 戻る" + +#~ msgid "All settings will be reset, are you sure?" +#~ msgstr "すべての設定がリセットされます。よろしいですか?" + +#~ msgid "Please chose which greeter to install for the chosen profiles: {}" +#~ msgstr "選択したプロファイルにインストールするグリーターを選択: {}" + +#~ msgid "The proprietary Nvidia driver is not supported by Sway. It is likely that you will run into issues, are you okay with that?" +#~ msgstr "プロプライエタリの Nvidia ドライバーは Sway ではサポートされていません。問題が発生する可能性がありますが、よろしいですか?" + +#~ msgid "Add profile" +#~ msgstr "プロファイルを追加" + +#~ msgid "Edit profile" +#~ msgstr "プロファイルを編集" + +#~ msgid "Delete profile" +#~ msgstr "プロファイルを削除" + +#~ msgid "Profile name: " +#~ msgstr "プロファイル名: " + +#~ msgid "The profile name you entered is already in use. Try again" +#~ msgstr "入力したプロファイル名はすでに使用されています。もう1度やり直してください" + +#~ msgid "Packages to be install with this profile (space separated, leave blank to skip): " +#~ msgstr "このプロファイルでインストールするパッケージ(スペースで区切る。無記入でスキップ): " + +#~ msgid "Services to be enabled with this profile (space separated, leave blank to skip): " +#~ msgstr "このプロファイルで有効にするサービス(スペースで区切る。未記入でスキップ): " + +#~ msgid "Should this profile be enabled for installation?" +#~ msgstr "このプロファイルのインストールを有効にしますか?" + +#~ msgid "Create your own" +#~ msgstr "自分で作成" + +#~ msgid "" +#~ "\n" +#~ "Select a graphics driver or leave blank to install all open-source drivers" +#~ msgstr "" +#~ "\n" +#~ "グラフィックドライバーを選択。無記入ですべてのオープンソースドライバーをインストール" + +#~ msgid "Sway needs access to your seat (collection of hardware devices i.e. keyboard, mouse, etc)" +#~ msgstr "Sway はお使いの Seat(キーボード、マウスなどのハードウェアデバイス群)にアクセスする必要があります" + +#~ msgid "" +#~ "\n" +#~ "\n" +#~ "Choose an option to give Sway access to your hardware" +#~ msgstr "" +#~ "\n" +#~ "\n" +#~ "Sway にハードウェアへのアクセスを許可するオプションを選択" + +#~ msgid "Please chose which greeter to install" +#~ msgstr "インストールするグリーターを選択" + +#~ msgid "This is a list of pre-programmed default_profiles" +#~ msgstr "これは、事前にプログラムされた default_profile のリストです" + +#~ msgid "Finding possible directories to save configuration files ..." +#~ msgstr "設定ファイルを保存できるディレクトリを検索しています..." + +#~ msgid "Select directory (or directories) for saving configuration files" +#~ msgstr "設定ファイルを保存するディレクトリを選択" + +#~ msgid "Add a custom mirror" +#~ msgstr "カスタムミラーを追加" + +#~ msgid "Change custom mirror" +#~ msgstr "カスタムミラーを変更" + +#~ msgid "Delete custom mirror" +#~ msgstr "カスタムミラーを削除" + +#~ msgid "Enter name (leave blank to skip): " +#~ msgstr "名前を入力(無記入でスキップ): " + +#~ msgid "Enter url (leave blank to skip): " +#~ msgstr "URL を入力(未記入でスキップ): " + +#~ msgid "Select signature check option" +#~ msgstr "署名チェックのオプションを選択" + +#~ msgid "Custom mirrors" +#~ msgstr "カスタムミラー" + +#~ msgid "Defined" +#~ msgstr "定義済み" + +#~ msgid "" +#~ "Enter a directory for the configuration(s) to be saved (tab completion enabled)\n" +#~ "Save directory: " +#~ msgstr "" +#~ "設定を保存するディレクトリを入力(Tab で補完可能)\n" +#~ "保存ディレクトリ: " + +#~ msgid "" +#~ "Do you want to save {} configuration file(s) in the following location?\n" +#~ "\n" +#~ "{}" +#~ msgstr "" +#~ "{} 設定ファイルを次の場所に保存しますか?\n" +#~ "\n" +#~ "{}" + +#~ msgid "Saving {} configuration files to {}" +#~ msgstr "{} 設定ファイルを {} に保存" + +#~ msgid "Mirrors" +#~ msgstr "ミラー" + +#~ msgid "Mirror regions" +#~ msgstr "ミラーの地域" + +#~ msgid " - Maximum value : {} ( Allows {} parallel downloads, allows {max_downloads+1} downloads at a time )" +#~ msgstr " - 最大値 : {}({} 個の並列ダウンロードを許可し、1度に {max_downloads+1} 個のダウンロードを許可する)" + +#~ msgid "Invalid input! Try again with a valid input [1 to {}, or 0 to disable]" +#~ msgstr "無効な入力です!有効な入力でやり直してください [1 から {}、または 0 で無効]" + +#~ msgid "Use NetworkManager (necessary to configure internet graphically in GNOME and KDE)" +#~ msgstr "ネットワークマネージャーを使用(GNOME と KDE でインターネットをグラフィカルに設定するのに必要)" + +#~ msgid "Total: {} / {}" +#~ msgstr "全体: {} / {}" + +#~ msgid "All entered values can be suffixed with a unit: B, KB, KiB, MB, MiB..." +#~ msgstr "入力したすべての値に、B、KB、KiB、MB、MiB などの単位を付けることができます。" + +#~ msgid "Enter start (default: sector {}): " +#~ msgstr "開始値を入力(デフォルト: セクター {}): " + +#~ msgid "Enter end (default: {}): " +#~ msgstr "終了値を入力(デフォルト: {}): " + +#~ msgid "Unable to determine fido2 devices. Is libfido2 installed?" +#~ msgstr "fido2 デバイスを特定できません。lifido2 はインストールされていますか?" + +#~ msgid "Path" +#~ msgstr "パス" + +#~ msgid "Manufacturer" +#~ msgstr "メーカー" + +#~ msgid "Product" +#~ msgstr "製品" + +#~ msgid "Disks" +#~ msgstr "ディスク" + +#~ msgid "Packages" +#~ msgstr "パッケージ" + +#~ msgid "Locale" +#~ msgstr "ロケール" + +#~ msgid "This option enables the number of parallel downloads that can occur during package downloads" +#~ msgstr "このオプションは、パッケージのダウンロード中に実行できる並列ダウンロードの数を設定します" + +#~ msgid "" +#~ "Enter the number of parallel downloads to be enabled.\n" +#~ "\n" +#~ "Note:\n" +#~ msgstr "" +#~ "並列ダウンロードの数を入力してください。\n" +#~ "\n" +#~ "注意:\n" + +#, python-brace-format +#~ msgid " - Maximum recommended value : {} ( Allows {} parallel downloads at a time )" +#~ msgstr " - 最大推奨値 : {} (1度に {} 個の並列ダウンロードを許可する)" + +#~ msgid " - Disable/Default : 0 ( Disables parallel downloading, allows only 1 download at a time )\n" +#~ msgstr " - 無効/デフォルト : 0 (並列ダウンロードを無効にして、1度に 1 個のダウンロードのみ許可する)\n" + +#~ msgid "Invalid input! Try again with a valid input [or 0 to disable]" +#~ msgstr "無効な入力です!有効な入力でやり直してください(無効にする場合は 0)" + +#~ msgid "Hyprland needs access to your seat (collection of hardware devices i.e. keyboard, mouse, etc)" +#~ msgstr "Hyprland はお使いの Seat(キーボード、マウスなどのハードウェアデバイス群)にアクセスする必要があります" + +#~ msgid "" +#~ "\n" +#~ "\n" +#~ "Choose an option to give Hyprland access to your hardware" +#~ msgstr "" +#~ "\n" +#~ "\n" +#~ "Hyprland にハードウェアへのアクセスを許可するオプションを選択" + +#~ msgid "Time syncronization not completing, while you wait - check the docs for workarounds: https://archinstall.readthedocs.io/" +#~ msgstr "待機中に時刻同期が完了しません - 回避策についてはドキュメントを確認してください: https://archinstall.readthedocs.io/" + +#~ msgid "Provides a selection of desktop environments and tiling window managers, e.g. GNOME, KDE Plasma, Sway" +#~ msgstr "デスクトップ環境とタイルウィンドウマネージャーの選択を提供します。例: GNOME, KDE Plasma, Sway" + +#~ msgid "Use NetworkManager (necessary to configure internet graphically in GNOME and KDE Plasma)" +#~ msgstr "ネットワークマネージャーを使用(GNOME と KDE Plasma でインターネットをグラフィカルに設定するのに必要)" + +#~ msgid "Select a LVM option" +#~ msgstr "LVM のオプションを選択" + +#~ msgid "Logical Volume Management (LVM)" +#~ msgstr "論理ボリューム管理(LVM)" + +#~ msgid "Select which LVM volumes to encrypt" +#~ msgstr "暗号化する LVM ボリュームを選択" + +#~ msgid "Archinstall help" +#~ msgstr "Archinstall ヘルプ" + +#~ msgid "Press Ctrl+h for help" +#~ msgstr "Ctrl+H でヘルプを表示" + +#~ msgid "Choose an option to give Sway access to your hardware" +#~ msgstr "Sway にハードウェアへのアクセスを許可するオプションを選択" + +#~ msgid "Seat access" +#~ msgstr "Seat アクセス" + +#~ msgid "Filesystem" +#~ msgstr "ファイルシステム" + +#~ msgid "Start (default: sector {}): " +#~ msgstr "開始値(デフォルト: セクター {}): " + +#~ msgid "End (default: {}): " +#~ msgstr "終了値(デフォルト: {}): " + +#~ msgid "Disk configuration type" +#~ msgstr "ディスク設定のタイプ" + +#~ msgid "Root mount directory" +#~ msgstr "ルートマウントディレクトリ" + +#~ msgid "Select language" +#~ msgstr "言語を選択" + +#~ msgid "Write additional packages to install (space separated, leave blank to skip)" +#~ msgstr "追加でインストールするパッケージを書く(スペースで区切る。無記入でスキップ)" + +#~ msgid "Invalid download number" +#~ msgstr "ダウンロード数が無効です" + +#~ msgid "Number downloads" +#~ msgstr "ダウンロード数" + +#~ msgid "Interfaces" +#~ msgstr "インターフェイス" + +#~ msgid "Modes" +#~ msgstr "モード" + +#~ msgid "IP address" +#~ msgstr "IPアドレス" + +#~ msgid "Gateway address" +#~ msgstr "ゲートウェイアドレス" + +#~ msgid "DNS servers" +#~ msgstr "DNSサーバー" + +#~ msgid "Info" +#~ msgstr "情報" + +#~ msgid "Main profile" +#~ msgstr "メインプロファイル" + +#~ msgid "The confirmation password did not match, please try again" +#~ msgstr "確認のパスワードが一致しませんでした。もう一度試してください" + +#~ msgid "Directory" +#~ msgstr "ディレクトリ" + +#~ msgid "Enter a directory for the configuration(s) to be saved (tab completion enabled)" +#~ msgstr "設定を保存するディレクトリを入力(Tab で補完可能)" + +#~ msgid "Mirror name" +#~ msgstr "ミラーの名前" + +#~ msgid "Select execution mode" +#~ msgstr "実行モードを選択" + +#~ msgid "Press ? for help" +#~ msgstr "? を押すとヘルプを表示" + +#~ msgid "Choose an option to give Hyprland access to your hardware" +#~ msgstr "Hyprland にハードウェアへのアクセスを許可するオプションを選択" + +#, python-brace-format +#~ msgid "Size (default: {}): " +#~ msgstr "サイズ (デフォルト: {}): " + +#~ msgid "Some packages could not be found in the repository" +#~ msgstr "リポジトリ内にいくつかのパッケージが見つかりませんでした" + +#~ msgid "Repository name" +#~ msgstr "リポジトリ名" + +#~ msgid "Server url" +#~ msgstr "サーバー URL" + +#~ msgid "Only ASCII characters are supported" +#~ msgstr "ASCII 文字のみサポートされます" + +#~ msgid "Show help" +#~ msgstr "ヘルプを表示" + +#~ msgid "Exit help" +#~ msgstr "ヘルプを終了" + +#~ msgid "Preview scroll up" +#~ msgstr "プレビューを上にスクロール" + +#~ msgid "Preview scroll down" +#~ msgstr "プレビューを下にスクロール" + +#~ msgid "Move up" +#~ msgstr "上に移動" + +#~ msgid "Move down" +#~ msgstr "下に移動" + +#~ msgid "Move right" +#~ msgstr "右に移動" + +#~ msgid "Move left" +#~ msgstr "左に移動" + +#~ msgid "Jump to entry" +#~ msgstr "エントリーへジャンプ" + +#~ msgid "Skip selection (if available)" +#~ msgstr "選択をスキップ(利用可能な場合)" + +#~ msgid "Reset selection (if available)" +#~ msgstr "選択をリセット(利用可能な場合)" + +#~ msgid "Select on single select" +#~ msgstr "単一で選択" + +#~ msgid "Select on multi select" +#~ msgstr "複数で選択" + +#~ msgid "Reset" +#~ msgstr "リセット" + +#~ msgid "Skip selection menu" +#~ msgstr "選択メニューをスキップ" + +#~ msgid "Start search mode" +#~ msgstr "検索モードを開始" + +#~ msgid "Exit search mode" +#~ msgstr "検索モードを終了" + +#~ msgid "General" +#~ msgstr "一般" + +#~ msgid "Navigation" +#~ msgstr "ナビゲーション" + +#~ msgid "Selection" +#~ msgstr "選択" + +#~ msgid "Search" +#~ msgstr "検索" + +#~ msgid "Down" +#~ msgstr "下へ" + +#~ msgid "Up" +#~ msgstr "上へ" + +#~ msgid "Confirm" +#~ msgstr "確認" + +#~ msgid "Focus right" +#~ msgstr "右にフォーカス" + +#~ msgid "Focus left" +#~ msgstr "左にフォーカス" + +#~ msgid "Toggle" +#~ msgstr "切り替え" + +#~ msgid "Show/Hide help" +#~ msgstr "ヘルプを表示/隠す" + +#~ msgid "Quit" +#~ msgstr "終了" + +#~ msgid "First" +#~ msgstr "最初" + +#~ msgid "Last" +#~ msgstr "最後" + +#~ msgid "Select" +#~ msgstr "選択" + +#~ msgid "Page Up" +#~ msgstr "上のページへ" + +#~ msgid "Page Down" +#~ msgstr "下のページへ" + +#~ msgid "Page up" +#~ msgstr "上のページへ" + +#~ msgid "Page down" +#~ msgstr "下のページへ" + +#~ msgid "Page Left" +#~ msgstr "左のページへ" + +#~ msgid "Page Right" +#~ msgstr "右のページへ" + +#~ msgid "Cursor up" +#~ msgstr "カーソルを上へ" + +#~ msgid "Cursor down" +#~ msgstr "カースルを下へ" + +#~ msgid "Cursor right" +#~ msgstr "カーソルを右へ" + +#~ msgid "Cursor left" +#~ msgstr "カーソルを左へ" + +#~ msgid "Top" +#~ msgstr "一番上へ" + +#~ msgid "Bottom" +#~ msgstr "一番下へ" + +#~ msgid "Home" +#~ msgstr "ホーム" + +#~ msgid "End" +#~ msgstr "最後へ" + +#~ msgid "Toggle option" +#~ msgstr "オプションを切り替え" + +#~ msgid "Scroll Up" +#~ msgstr "上にスクロール" + +#~ msgid "Scroll Down" +#~ msgstr "下にスクロール" + +#~ msgid "Scroll Left" +#~ msgstr "左にスクロール" + +#~ msgid "Scroll Right" +#~ msgstr "右にスクロール" + +#~ msgid "Scroll Home" +#~ msgstr "ホームにスクロール" + +#~ msgid "Scroll End" +#~ msgstr "一番下にスクロール" + +#~ msgid "Focus Next" +#~ msgstr "次にフォーカス" + +#~ msgid "Focus Previous" +#~ msgstr "前にフォーカス" + +#~ msgid "Copy selected text" +#~ msgstr "選択したテキストをコピー" + +#~ msgid "labwc needs access to your seat (collection of hardware devices i.e. keyboard, mouse, etc)" +#~ msgstr "labwc はお使いの Seat(キーボード、マウスなどのハードウェアデバイス群)にアクセスする必要があります" + +#~ msgid "Choose an option to give labwc access to your hardware" +#~ msgstr "labwc にハードウェアへのアクセスを許可するオプションを選択" + +#~ msgid "niri needs access to your seat (collection of hardware devices i.e. keyboard, mouse, etc)" +#~ msgstr "niri はお使いの Seat(キーボード、マウスなどのハードウェアデバイス群)にアクセスする必要があります" + +#~ msgid "Choose an option to give niri access to your hardware" +#~ msgstr "niri にハードウェアへのアクセスを許可するオプションを選択" + +#~ msgid "Installation completed" +#~ msgstr "インストール完了" + +#~ msgid "Credentials file decryption password" +#~ msgstr "認証情報ファイルの復号化パスワード" + +#~ msgid "Snapshot type" +#~ msgstr "スナップショットのタイプ" + +#~ msgid "U2F Login Method" +#~ msgstr "U2F ログインメソッド" + +#~ msgid "Will install to /EFI/BOOT/ (removable location)" +#~ msgstr "/EFI/BOOT/ (リムーバブルな場所) にインストールする" + +#~ msgid "Will install to standard location with NVRAM entry" +#~ msgstr "NVRAM エントリーのある標準の場所にインストールする" + +#~ msgid "Firmware that does not properly support NVRAM boot entries." +#~ msgstr "NVRAM ブートエントリーを適切にサポートしていないファームウェア。" + +#~ msgid "Enter the number of parallel downloads to be enabled" +#~ msgstr "有効にする並列ダウンロード数を入力" + +#, python-brace-format +#~ msgid "Setting up U2F login: {u2f_config.u2f_login_method.value}" +#~ msgstr "U2F ログインの設定: {u2f_config.u2f_login_method.value}" + +#, python-brace-format +#~ msgid "Default: {DEFAULT_ITER_TIME}ms, Recommended range: 1000-60000" +#~ msgstr "デフォルト: {DEFAULT_ITER_TIME}ms, 推奨範囲: 1000-60000" From af58671e882e2ed4ac92c512bc27d8ba53b11ef3 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 22 May 2026 12:39:11 +1000 Subject: [PATCH 57/61] Update dependency ruff to v0.15.14 (#4551) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 0f455c30..b7545121 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,7 +37,7 @@ dev = [ "mypy==2.1.0", "flake8==7.3.0", "pre-commit==4.6.0", - "ruff==0.15.13", + "ruff==0.15.14", "pylint==4.0.5", "pytest==9.0.3", "hypothesis>=6.152.4", From d92a98d3bfabcbb8ce7752776f5f8de184b5b898 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 22 May 2026 12:39:28 +1000 Subject: [PATCH 58/61] Update pre-commit hook astral-sh/ruff-pre-commit to v0.15.14 (#4552) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index de2bcef4..6ba223de 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,7 +1,7 @@ default_stages: ['pre-commit'] repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.15.13 + rev: v0.15.14 hooks: # fix unused imports and sort them - id: ruff From af2120c0e94ada1bb7e9eea44283d19323ce2f27 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Sat, 23 May 2026 13:49:54 +0200 Subject: [PATCH 59/61] mkosi for test image builds (#4539) Added a `mkosi` profile as well as organized test tooling a bit. --------- Co-authored-by: 0xdeadd --- .github/workflows/iso-build.yaml | 2 +- .github/workflows/uki-build.yaml | 40 ++ .gitignore | 4 + pyproject.toml | 5 +- .../mkarchiso/build_iso.sh | 0 test_tooling/mkosi/README.md | 12 + test_tooling/mkosi/mkosi.cache/.gitkeep | 0 test_tooling/mkosi/mkosi.conf | 50 ++ .../etc/systemd/network/20-ethernet.network | 24 + .../getty@tty1.service.d/autologin.conf | 3 + test_tooling/mkosi/mkosi.extra/root/.profile | 5 + .../system-preset/5-archinstall.preset | 1 + test_tooling/mkosi/mkosi.output/.gitkeep | 0 test_tooling/mkosi/mkosi.postinst.chroot | 10 + test_tooling/mkosi/mkosi.profiles/archinstall | 56 ++ test_tooling/mkosi/mkosi.version | 1 + test_tooling/qemu/README.md | 18 + test_tooling/qemu/qemu.py | 483 ++++++++++++++++++ 18 files changed, 712 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/uki-build.yaml rename build_iso.sh => test_tooling/mkarchiso/build_iso.sh (100%) create mode 100644 test_tooling/mkosi/README.md create mode 100644 test_tooling/mkosi/mkosi.cache/.gitkeep create mode 100644 test_tooling/mkosi/mkosi.conf create mode 100644 test_tooling/mkosi/mkosi.extra/etc/systemd/network/20-ethernet.network create mode 100644 test_tooling/mkosi/mkosi.extra/etc/systemd/system/getty@tty1.service.d/autologin.conf create mode 100644 test_tooling/mkosi/mkosi.extra/root/.profile create mode 100644 test_tooling/mkosi/mkosi.extra/usr/lib/systemd/system-preset/5-archinstall.preset create mode 100644 test_tooling/mkosi/mkosi.output/.gitkeep create mode 100755 test_tooling/mkosi/mkosi.postinst.chroot create mode 100644 test_tooling/mkosi/mkosi.profiles/archinstall create mode 100644 test_tooling/mkosi/mkosi.version create mode 100644 test_tooling/qemu/README.md create mode 100644 test_tooling/qemu/qemu.py diff --git a/.github/workflows/iso-build.yaml b/.github/workflows/iso-build.yaml index dfaed9b8..83d58fd6 100644 --- a/.github/workflows/iso-build.yaml +++ b/.github/workflows/iso-build.yaml @@ -32,7 +32,7 @@ jobs: - run: cat /etc/os-release - run: pacman-key --init - run: pacman --noconfirm -Sy archlinux-keyring - - run: ./build_iso.sh + - run: ./test_tooling/mkarchiso/build_iso.sh - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: name: Arch Live ISO diff --git a/.github/workflows/uki-build.yaml b/.github/workflows/uki-build.yaml new file mode 100644 index 00000000..df41bc55 --- /dev/null +++ b/.github/workflows/uki-build.yaml @@ -0,0 +1,40 @@ +# This workflow will build an Arch Linux UKI file with the commit on it + +name: Build Arch UKI with ArchInstall Commit + +on: + push: + branches: + - master + - main # In case we adopt this convention in the future + pull_request: + paths-ignore: + - 'docs/**' + - '**.editorconfig' + - '**.gitignore' + - '**.md' + - 'LICENSE' + - 'PKGBUILD' + release: + types: + - created + +jobs: + build: + runs-on: ubuntu-latest + container: + image: archlinux/archlinux:latest + options: --privileged + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + - run: pwd + - run: find . + - run: cat /etc/os-release + - run: pacman-key --init + - run: pacman --noconfirm -Sy archlinux-keyring + - run: pacman --noconfirm -Sy mkosi + - run: (cd test_tooling/mkosi/ && mkosi build -B) + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 + with: + name: Arch Live UKI + path: test_tooling/mkosi/mkosi.output/*.efi diff --git a/.gitignore b/.gitignore index 376bdbd5..54c865e0 100644 --- a/.gitignore +++ b/.gitignore @@ -41,3 +41,7 @@ requirements.txt /cmd_output.txt node_modules/ uv.lock +test_tooling/mkosi/mkosi.output/*image* +test_tooling/mkosi/mkosi.cache/** +test_tooling/mkosi/mkosi.tools/** +test_tooling/mkosi/mkosi.tools.manifest diff --git a/pyproject.toml b/pyproject.toml index b7545121..8d0c5a64 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -69,7 +69,10 @@ archinstall = "archinstall" [tool.mypy] python_version = "3.14" files = "." -exclude = "^build/" +exclude = [ + "^build/", + "^test_tooling/", +] disallow_any_explicit = false disallow_any_expr = false disallow_any_unimported = true diff --git a/build_iso.sh b/test_tooling/mkarchiso/build_iso.sh similarity index 100% rename from build_iso.sh rename to test_tooling/mkarchiso/build_iso.sh diff --git a/test_tooling/mkosi/README.md b/test_tooling/mkosi/README.md new file mode 100644 index 00000000..d26d0d9f --- /dev/null +++ b/test_tooling/mkosi/README.md @@ -0,0 +1,12 @@ +# To build + + mkosi build -B + +# To run + + mkosi qemu \ + --drive=archinstall_small:25G \ + -- \ + -device nvme,serial=archinstall_small,drive=archinstall_small + +*note: in order to boot the installation, we need to disable UKI being added to -kernel. I don't know of a way to do this yet, unless we tinker with mkosi/qemu.py - https://github.com/Torxed/mkosi/commit/6f3c20802bd73f88b672cc96ef0db1e542084316 - in which case we could do: `mkosi qemu --drive=archinstall_small:25G -- -device nvme,serial=archinstall_small,drive=archinstall_small,bootindex=0 -kernel none` but it still won't boot properly.* \ No newline at end of file diff --git a/test_tooling/mkosi/mkosi.cache/.gitkeep b/test_tooling/mkosi/mkosi.cache/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/test_tooling/mkosi/mkosi.conf b/test_tooling/mkosi/mkosi.conf new file mode 100644 index 00000000..fdc36613 --- /dev/null +++ b/test_tooling/mkosi/mkosi.conf @@ -0,0 +1,50 @@ +[Distribution] +Distribution=arch +# LocalMirror=file:///var/lib/localmirror + +[Output] +Format=uki +# Format=disk + +[Include] +Include=mkosi-vm + +[Validation] +SecureBoot=false +SecureBootAutoEnroll=false +Sign=false +# Signing artifacts (hashsums etc) using a GPG key: +# Key=D4B58E897A929F2E +# Signing secure boot using PIV on yubikey: +# SecureBoot=true +# SecureBootKey=pkcs11: +# SecureBootCertificate=secureboot.crt + +[Content] +Packages= + pacman + archlinux-keyring + amd-ucode + intel-ucode +# tpm2-tss +# libfido2 +# libp11-kit +WithDocs=false +RootPassword=toor +Timezone=Europe/Stockholm +Keymap=sv-latin1 +InitrdProfiles=network + +[Config] +Profiles=archinstall + +[Build] +Incremental=true +ToolsTree=default +ToolsTreeProfiles=devel,misc,package-manager,runtime,gui +WithNetwork=yes + +[Runtime] +Console=gui +CPUs=4 +RAM=8G diff --git a/test_tooling/mkosi/mkosi.extra/etc/systemd/network/20-ethernet.network b/test_tooling/mkosi/mkosi.extra/etc/systemd/network/20-ethernet.network new file mode 100644 index 00000000..478a55da --- /dev/null +++ b/test_tooling/mkosi/mkosi.extra/etc/systemd/network/20-ethernet.network @@ -0,0 +1,24 @@ +[Match] +# Matching with "Type=ether" causes issues with containers because it also matches virtual Ethernet interfaces (veth*). +# See https://bugs.archlinux.org/task/70892 +# Instead match by globbing the network interface name. +Name=en* +Name=eth* + +[Link] +RequiredForOnline=routable + +[Network] +DHCP=yes +MulticastDNS=yes + +# systemd-networkd does not set per-interface-type default route metrics +# https://github.com/systemd/systemd/issues/17698 +# Explicitly set route metric, so that Ethernet is preferred over Wi-Fi and Wi-Fi is preferred over mobile broadband. +# Use values from NetworkManager. From nm_device_get_route_metric_default in +# https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/blob/main/src/core/devices/nm-device.c +[DHCPv4] +RouteMetric=100 + +[IPv6AcceptRA] +RouteMetric=100 \ No newline at end of file diff --git a/test_tooling/mkosi/mkosi.extra/etc/systemd/system/getty@tty1.service.d/autologin.conf b/test_tooling/mkosi/mkosi.extra/etc/systemd/system/getty@tty1.service.d/autologin.conf new file mode 100644 index 00000000..161d4952 --- /dev/null +++ b/test_tooling/mkosi/mkosi.extra/etc/systemd/system/getty@tty1.service.d/autologin.conf @@ -0,0 +1,3 @@ +[Service] +ExecStart= +ExecStart=-/usr/bin/agetty --noreset --noclear --autologin root - ${TERM} \ No newline at end of file diff --git a/test_tooling/mkosi/mkosi.extra/root/.profile b/test_tooling/mkosi/mkosi.extra/root/.profile new file mode 100644 index 00000000..c8d1791b --- /dev/null +++ b/test_tooling/mkosi/mkosi.extra/root/.profile @@ -0,0 +1,5 @@ +cd archinstall-git +rm -rf dist + +uv build --no-build-isolation --wheel +uv pip install dist/*.whl --break-system-packages --system --no-build --no-deps \ No newline at end of file diff --git a/test_tooling/mkosi/mkosi.extra/usr/lib/systemd/system-preset/5-archinstall.preset b/test_tooling/mkosi/mkosi.extra/usr/lib/systemd/system-preset/5-archinstall.preset new file mode 100644 index 00000000..8423c627 --- /dev/null +++ b/test_tooling/mkosi/mkosi.extra/usr/lib/systemd/system-preset/5-archinstall.preset @@ -0,0 +1 @@ +enable systemd-networkd.service \ No newline at end of file diff --git a/test_tooling/mkosi/mkosi.output/.gitkeep b/test_tooling/mkosi/mkosi.output/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/test_tooling/mkosi/mkosi.postinst.chroot b/test_tooling/mkosi/mkosi.postinst.chroot new file mode 100755 index 00000000..096857e8 --- /dev/null +++ b/test_tooling/mkosi/mkosi.postinst.chroot @@ -0,0 +1,10 @@ +#!/usr/bin/env bash + +git clone https://github.com/archlinux/archinstall.git /root/archinstall-git +(cd /root/archinstall-git && git checkout master) + +# TODO: Set geo-mirrors statically instead +curl -s "https://archlinux.org/mirrorlist/?country=SE&protocol=https&use_mirror_status=on" | sed -e 's/^#Server/Server/' -e '/^#/d' | rankmirrors -n 5 - > /etc/pacman.d/mirrorlist + +pacman-key --init +pacman-key --populate archlinux \ No newline at end of file diff --git a/test_tooling/mkosi/mkosi.profiles/archinstall b/test_tooling/mkosi/mkosi.profiles/archinstall new file mode 100644 index 00000000..75628b61 --- /dev/null +++ b/test_tooling/mkosi/mkosi.profiles/archinstall @@ -0,0 +1,56 @@ +[Content] +Packages= + acpid + intel-media-driver + linux-firmware + linux-firmware-intel + ca-certificates-mozilla + ca-certificates-utils + nano + gvfs + noto-fonts + cantarell-fonts + ttf-dejavu + polkit + bash + bzip2 + coreutils + file + filesystem + findutils + gawk + gcc-libs + gettext + glibc + grep + gzip + iproute2 + iputils + licenses + pciutils + procps-ng + psmisc + sed + tar + linux + diffutils + less + strace + util-linux + xz + pacman-contrib + gcc + git + pkgconfig + python + python-pip + python-uv + python-setuptools + python-pyparted + python-pydantic + python-textual + dosfstools + btrfs-progs + arch-install-scripts +WithDocs=false +KernelCommandLine=quiet splash \ No newline at end of file diff --git a/test_tooling/mkosi/mkosi.version b/test_tooling/mkosi/mkosi.version new file mode 100644 index 00000000..ca7bf83a --- /dev/null +++ b/test_tooling/mkosi/mkosi.version @@ -0,0 +1 @@ +13 \ No newline at end of file diff --git a/test_tooling/qemu/README.md b/test_tooling/qemu/README.md new file mode 100644 index 00000000..e36ccfa1 --- /dev/null +++ b/test_tooling/qemu/README.md @@ -0,0 +1,18 @@ +# Qemu helper + +Can be used with the `mkosi` test tooling + +After `mkosi -B build` has been executed, run the following: + + python test_tooling/qemu/qemu.py \ + --uki ./test_tooling/mkosi/mkosi.output/image_13.efi \ + --harddrive ~/test.qcow2:15G \ + --harddrive ~/test_large.qcow2:25G + +And install using `archinstall`, after the machine has been shutdown, run: + + python test_tooling/qemu/qemu.py \ + --harddrive ~/test.qcow2:15G \ + --harddrive ~/test_large.qcow2:25G + +As this will boot EFI mode with just the harddrives to verify the installation. \ No newline at end of file diff --git a/test_tooling/qemu/qemu.py b/test_tooling/qemu/qemu.py new file mode 100644 index 00000000..c8307771 --- /dev/null +++ b/test_tooling/qemu/qemu.py @@ -0,0 +1,483 @@ +import getpass +import grp +import hashlib +import os +import pathlib +import re +import shlex +import shutil +import subprocess +import sys +import time +from argparse import ArgumentParser +from collections.abc import Iterator +from select import EPOLLHUP, EPOLLIN, epoll +from shutil import which +from types import TracebackType +from typing import Any, Self, override + + +class RequirementError(Exception): + pass + + +class ArgumentError(Exception): + pass + + +def get_master(interface): + master_path = pathlib.Path(f'/sys/class/net/{interface}/master') + return master_path.readlink().name if master_path.exists() else None + + +def gray(text): + return f'\033[38;5;246m{text}\033[0m' + + +def orange(text): + return f'\033[38;5;208m{text}\033[0m' + + +def red(text): + return f'\033[31m{text}\033[0m' + + +sudo_password = None # Gets populated later +harddrives = {} +username = getpass.getuser() +groupname = grp.getgrgid(os.getgid()).gr_name + +# https://stackoverflow.com/a/43627833/929999 +_VT100_ESCAPE_REGEX = r'\x1B\[[?0-9;]*[a-zA-Z]' +_VT100_ESCAPE_REGEX_BYTES = _VT100_ESCAPE_REGEX.encode() + +parser = ArgumentParser(description='A set of common parameters for the tooling', add_help=True) + +# Defaults to the order of which the harddrives are defined. +boot_option = parser.add_mutually_exclusive_group() +boot_option.add_argument('--uki', help='Boot a UKI (EFI) image') +boot_option.add_argument('--kernel', help='Boot a Linux kernel') +boot_option.add_argument('--iso', help='Boot a ISO 9660') + +networking = parser.add_argument_group('Networking', "Disables the default '-net nic -net user' network behavior of Qemu.") +networking.add_argument('--tap', nargs='?', help='Configures a TAP interface and passes it in as a virtio-net-pci.', default=None, type=str) +networking.add_argument('--tap-mac', nargs='?', help='MAC for the --tap interface', default='52:54:00:00:00:02') +networking.add_argument('--bridge', nargs='?', help='Configures a bridge, to which the --tap is added.', default=None, type=str) +networking.add_argument('--bridge-mac', nargs='?', help='MAC for the interface', default=None) +networking.add_argument('--bridge-master', nargs='?', help="Which interface to set as 'master' on the bridge.", default=None, type=str) + +hardware = parser.add_argument_group('Hardware', 'General hardware specs for the virtual machine') +# To override the use of EFI boot (will not work with --uki for obvious reasons) +hardware.add_argument('--bios', action='store_true', help='Disables EFI (edk2/ovmf) and uses BIOS support instead', default=False) +hardware.add_argument('--memory', nargs='?', help='Ammount of memory to supply the machine', default=8192) +hardware.add_argument('--harddrive', action='append', help='Sets up one or more virtio-scsi-pci, size is defined by --harddrive test.qcow2:15G', type=str) +hardware.add_argument('--cpu', help='Sets the number of cores to allocate (default nproc -1)', type=str, default=os.cpu_count() - 1 if os.cpu_count() else 1) +hardware.add_argument('--resolution', help="Sets Qemu's VGA resolution", type=str, default='1920x1107') + +kernel = parser.add_argument_group('Kernel', '--kernel specific arguments') +kernel.add_argument('--initrd', nargs='?', help='Defines which ISO to run (skips build all together)', default=None, type=pathlib.Path) + +args, unknowns = parser.parse_known_args() # pylint: disable=redefined-outer-name + +if args.bios and args.uki: + raise ArgumentError('Cannot boot a --uki image with --bios mode (at least not that I know of).') + +if args.uki is None and args.kernel is None and args.iso is None and args.harddrive is None: + raise ArgumentError('Cannot boot this machine, define at least one of: --uki, --kernel, --iso, --harddrive') + +if args.bridge is None and args.bridge_master: + raise ArgumentError('Cannot use --bridge-master without defining --bridge') + +if args.bridge is None and args.bridge_mac: + raise ArgumentError('Cannot use --bridge-mac without defining --bridge') +elif args.bridge and args.bridge_mac is None: + args.bridge_mac = '52:54:00:00:00:1' + +if args.tap and not args.bridge and get_master(args.tap) is None: + # We'll allow it, because maybe we're tesing what happens without networking, but the NIC exists. + # Or the user has some creative iptables/nftables forwarding. + print(orange('--tap does not have a master, consider adding --bridge or manual set a master using ip-link(8).')) + +if args.tap is None and args.bridge: + print(orange("--bridge* arguments will be ignored since there's no --tap defined")) +elif args.tap and args.tap_mac is None: + args.tap_mac = '52:54:00:00:00:2' + + +class SysCallError(Exception): + def __init__(self, message: str, exit_code: int | None = None, worker_log: bytes = b'') -> None: + super().__init__(message) + self.message = message + self.exit_code = exit_code + self.worker_log = worker_log + + +def clear_vt100_escape_codes(data: bytes) -> bytes: + return re.sub(_VT100_ESCAPE_REGEX_BYTES, b'', data) + + +def locate_binary(name: str) -> str: + if path := which(name): + return path + raise RequirementError(f'Binary {name} does not exist.') + + +def _pid_exists(pid: int) -> bool: + try: + return any(subprocess.check_output(['ps', '--no-headers', '-o', 'pid', '-p', str(pid)]).strip()) + except subprocess.CalledProcessError: + return False + + +class SysCommandWorker: + def __init__( + self, + cmd: str | list[str], + peek_output: bool | None = False, + environment_vars: dict[str, str] | None = None, + working_directory: str = './', + remove_vt100_escape_codes_from_lines: bool = True, + ): + if isinstance(cmd, str): + cmd = shlex.split(cmd) + + if cmd and not cmd[0].startswith(('/', './')): # Path() does not work well + cmd[0] = locate_binary(cmd[0]) + + self.cmd = cmd + self.peek_output = peek_output + # define the standard locale for command outputs. For now the C ascii one. Can be overridden + self.environment_vars = {'LC_ALL': 'C'} + if environment_vars: + self.environment_vars.update(environment_vars) + + self.working_directory = working_directory + + self.exit_code: int | None = None + self._trace_log = b'' + self._trace_log_pos = 0 + self.poll_object = epoll() + self.child_fd: int | None = None + self.started = False + self.ended = False + self.remove_vt100_escape_codes_from_lines: bool = remove_vt100_escape_codes_from_lines + + def __contains__(self, key: bytes) -> bool: + """ + Contains will also move the current buffert position forward. + This is to avoid re-checking the same data when looking for output. + """ + assert isinstance(key, bytes) + + index = self._trace_log.find(key, self._trace_log_pos) + if index >= 0: + self._trace_log_pos += index + len(key) + return True + + return False + + def __iter__(self, *args: str, **kwargs: dict[str, Any]) -> Iterator[bytes]: # pylint: disable=redefined-outer-name + last_line = self._trace_log.rfind(b'\n') + lines = filter(None, self._trace_log[self._trace_log_pos : last_line].splitlines()) + for line in lines: + if self.remove_vt100_escape_codes_from_lines: + line = clear_vt100_escape_codes(line) + + yield line + b'\n' + + self._trace_log_pos = last_line + + @override + def __repr__(self) -> str: + self.make_sure_we_are_executing() + return str(self._trace_log) + + @override + def __str__(self) -> str: + try: + return self._trace_log.decode('utf-8') + except UnicodeDecodeError: + return str(self._trace_log) + + def __enter__(self) -> Self: + return self + + def __exit__(self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None) -> None: + # b''.join(sys_command('sync')) # No need to, since the underlying fs() object will call sync. + # TODO: https://stackoverflow.com/questions/28157929/how-to-safely-handle-an-exception-inside-a-context-manager + + if self.child_fd: + try: + os.close(self.child_fd) + except Exception: + pass + + if self.peek_output: + # To make sure any peaked output didn't leave us hanging + # on the same line we were on. + sys.stdout.write('\n') + sys.stdout.flush() + + if exc_type is not None: + print(gray(str(exc_value))) + + if self.exit_code != 0: + raise SysCallError( + f'{self.cmd} exited with abnormal exit code [{self.exit_code}]: {str(self)[-500:]}', + self.exit_code, + worker_log=self._trace_log, + ) + + def is_alive(self) -> bool: + self.poll() + + if self.started and not self.ended: + return True + + return False + + def write(self, data: bytes, line_ending: bool = True) -> int: + assert isinstance(data, bytes) # TODO: Maybe we can support str as well and encode it + + self.make_sure_we_are_executing() + + if self.child_fd: + return os.write(self.child_fd, data + (b'\n' if line_ending else b'')) + + return 0 + + def make_sure_we_are_executing(self) -> bool: + if not self.started: + return self.execute() + return True + + def tell(self) -> int: + self.make_sure_we_are_executing() + return self._trace_log_pos + + def seek(self, pos: int) -> None: + self.make_sure_we_are_executing() + # Safety check to ensure 0 < pos < len(tracelog) + self._trace_log_pos = min(max(0, pos), len(self._trace_log)) + + def peak(self, output: str | bytes) -> bool: + if self.peek_output: + if isinstance(output, bytes): + try: + output = output.decode('UTF-8') + except UnicodeDecodeError: + return False + + sys.stdout.write(output) + sys.stdout.flush() + + return True + + def poll(self) -> None: + self.make_sure_we_are_executing() + + if self.child_fd: + got_output = False + for _fileno, _event in self.poll_object.poll(0.1): + try: + output = os.read(self.child_fd, 8192) + got_output = True + self.peak(output) + self._trace_log += output + except OSError: + self.ended = True + break + + if self.ended or (not got_output and not _pid_exists(self.pid)): + self.ended = True + try: + wait_status = os.waitpid(self.pid, 0)[1] + self.exit_code = os.waitstatus_to_exitcode(wait_status) + except ChildProcessError: + try: + wait_status = os.waitpid(self.child_fd, 0)[1] + self.exit_code = os.waitstatus_to_exitcode(wait_status) + except ChildProcessError: + self.exit_code = 1 + + def execute(self) -> bool: + import pty + + if (old_dir := os.getcwd()) != self.working_directory: + os.chdir(str(self.working_directory)) + + # Note: If for any reason, we get a Python exception between here + # and until os.close(), the traceback will get locked inside + # stdout of the child_fd object. `os.read(self.child_fd, 8192)` is the + # only way to get the traceback without losing it. + + self.pid, self.child_fd = pty.fork() + + # https://stackoverflow.com/questions/4022600/python-pty-fork-how-does-it-work + if not self.pid: + try: + os.execve(self.cmd[0], list(self.cmd), {**os.environ, **self.environment_vars}) + except FileNotFoundError: + print(red(f'{self.cmd[0]} does not exist.')) + self.exit_code = 1 + return False + else: + # Only parent process moves back to the original working directory + os.chdir(old_dir) + + self.started = True + self.poll_object.register(self.child_fd, EPOLLIN | EPOLLHUP) + + return True + + def decode(self, encoding: str = 'UTF-8') -> str: + return self._trace_log.decode(encoding) + + +def ensure_sudo(): + global sudo_password # pylint: disable=global-statement + + if sudo_password is None: + if (sudo_password := getpass.getpass(f'[sudo] password for {username}: ')) == '': + raise ValueError('Certain commands need sudo to work and no sudo password was given.') + + +def setup_networking(): + if args.tap: + if pathlib.Path(f'/sys/class/net/{args.tap}').exists() is False: + print(gray(f'Creating {args.tap} for user {username} and group {groupname}')) + handle, pw_prompted = SysCommandWorker(f'sudo ip tuntap add dev {args.tap} mode tap user {username} group {groupname}'), False + while handle.is_alive(): + if b'password for' in handle and pw_prompted is False: + ensure_sudo() + handle.write(bytes(sudo_password, 'UTF-8')) + pw_prompted = True + + if args.bridge: + if pathlib.Path(f'/sys/class/net/{args.bridge}').exists() is False: + print(gray(f'Creating {args.bridge}')) + handle, pw_prompted = SysCommandWorker(f'sudo ip link add name {args.bridge} type bridge'), False + while handle.is_alive(): + if b'password for' in handle and pw_prompted is False: + ensure_sudo() + handle.write(bytes(sudo_password, 'UTF-8')) + pw_prompted = True + + if args.bridge_mac: + handle, pw_prompted = SysCommandWorker(f'sudo ip link set dev {args.bridge} address {args.bridge_mac}'), False + print(gray(f'Setting bridge {args.bridge} MAC address to {args.bridge_mac}')) + while handle.is_alive(): + if b'password for' in handle and pw_prompted is False: + ensure_sudo() + handle.write(bytes(sudo_password, 'UTF-8')) + pw_prompted = True + + if args.bridge_master and get_master(args.bridge) != args.bridge_master: + handle, pw_prompted = SysCommandWorker(f'sudo ip link set dev {args.bridge_master} master {args.bridge}'), False + print(gray(f'Setting interface {args.bridge_master} master to {args.bridge}')) + while handle.is_alive(): + if b'password for' in handle and pw_prompted is False: + ensure_sudo() + handle.write(bytes(sudo_password, 'UTF-8')) + pw_prompted = True + + print(gray(f'Setting interface {args.tap} master to {args.bridge}')) + handle, pw_prompted = SysCommandWorker(f'sudo ip link set dev {args.tap} master {args.bridge}'), False + while handle.is_alive(): + if b'password for' in handle and pw_prompted is False: + ensure_sudo() + handle.write(bytes(sudo_password, 'UTF-8')) + pw_prompted = True + + print(gray(f'Bringing up bridge {args.bridge}')) + handle, pw_prompted = SysCommandWorker(f'sudo ip link set dev {args.bridge} up'), False + while handle.is_alive(): + if b'password for' in handle and pw_prompted is False: + ensure_sudo() + handle.write(bytes(sudo_password, 'UTF-8')) + pw_prompted = True + + print(gray(f'Bringing interface {args.tap} up')) + handle, pw_prompted = SysCommandWorker(f'sudo ip link set dev {args.tap} up'), False + while handle.is_alive(): + if b'password for' in handle and pw_prompted is False: + ensure_sudo() + handle.write(bytes(sudo_password, 'UTF-8')) + pw_prompted = True + + +def setup_disks(): + if args.harddrive: + for harddrive_arg in args.harddrive: + path, size = harddrive_arg.split(':') + path = pathlib.Path(path.strip()).expanduser().resolve().absolute() + harddrives[path] = size.strip() + + if path.exists() is False: + handle = SysCommandWorker(f'qemu-img create -f qcow2 {hdd} {size}') + while handle.is_alive(): + time.sleep(0.01) + + if handle.exit_code != 0: + raise ValueError(f'Could not create harddrive {hdd}: {handle}') + + +setup_networking() +setup_disks() + +if args.uki or args.bios is False: + disk_paths_hash = hashlib.sha1((''.join(sorted([str(x) for x in harddrives.keys()]))).encode()).hexdigest() + + shutil.copy2('/usr/share/ovmf/x64/OVMF_CODE.secboot.4m.fd', f'./OVMF_CODE.secboot.4m.fd.{disk_paths_hash}') + shutil.copy2('/usr/share/ovmf/x64/OVMF_VARS.4m.fd', f'./OVMF_VARS.4m.fd.{disk_paths_hash}') + +boot_index = 0 +qemu = 'qemu-system-x86_64' +qemu += ' -cpu host' +qemu += ' -enable-kvm' +qemu += ' -machine q35,accel=kvm' +qemu += ' -object rng-random,filename=/dev/urandom,id=rng0' +qemu += ' -device virtio-rng-pci,rng=rng0' +qemu += ' -global driver=cfi.pflash01,property=secure,value=on' +qemu += f' -smp {args.cpu},sockets=1,dies=1,cores={args.cpu},threads=1' +# qemu += f' -vga vga' +qemu += f' -device VGA,edid=on,xres={args.resolution.split("x")[0]},yres={args.resolution.split("x")[1]}' +qemu += ' -device intel-iommu,device-iotlb=on,caching-mode=on' +qemu += f' -m {args.memory}' +if args.bios is False: + qemu += f' -drive if=pflash,format=raw,readonly=on,file=./OVMF_CODE.secboot.4m.fd.{disk_paths_hash}' + qemu += f' -drive if=pflash,format=raw,file=./OVMF_VARS.4m.fd.{disk_paths_hash}' +if args.uki: + qemu += f' -kernel {args.uki}' + boot_index += 1 +scsi_index = 0 +for scsi_index, hdd in enumerate(harddrives.keys()): + # qemu += f' -device virtio-scsi-pci,bus=pcie.0,id=scsi{index}' + # qemu += f' -device scsi-hd,drive=hdd{index},bus=scsi{index}.0,id=scsi{index}.0,bootindex={hdd_boot_priority+index}' + # qemu += f' -drive file={hdd},if=none,format=qcow2,discard=unmap,aio=native,cache=none,id=hdd{index}' + qemu += f' -device virtio-scsi-pci,bus=pcie.0,id=scsi{scsi_index},addr=0x{scsi_index + 8}' + qemu += f' -device scsi-hd,drive=libvirt-{scsi_index}-format,bus=scsi{scsi_index}.0,id=scsi{scsi_index}-0-0-0,channel=0,scsi-id=0,lun=0,device_id=drive-scsi0-0-0-0,bootindex={boot_index},write-cache=on' # noqa: E501 + qemu += f' -blockdev \'{{"driver":"file","filename":"{hdd}","aio":"threads","node-name":"libvirt-{scsi_index}-storage","cache":{{"direct":false,"no-flush":false}},"auto-read-only":true,"discard":"unmap"}}\'' # noqa: E501 + qemu += f' -blockdev \'{{"node-name":"libvirt-{scsi_index}-format","read-only":false,"discard":"unmap","cache":{{"direct":true,"no-flush":false}},"driver":"qcow2","file":"libvirt-{scsi_index}-storage","backing":null}}\'' # noqa: E501 + boot_index += 1 +if args.iso: + qemu += f' -device virtio-scsi-pci,bus=pcie.0,id=scsi{scsi_index + 1}' + qemu += f' -device scsi-cd,drive=cdrom0,bus=scsi{scsi_index + 1}.0,bootindex={boot_index}' + qemu += f' -drive file={args.iso},media=cdrom,if=none,format=raw,cache=none,id=cdrom0' + boot_index += 1 + +# if args.vfio: +# qemu += f' -drive file={args.vfio},index=2,media=cdrom' + +if args.tap: + qemu += f' -device virtio-net-pci,mac={args.tap_mac},id=network0,netdev=network0.0,status=on,bus=pcie.0' + qemu += f' -netdev tap,ifname={args.tap},id=network0.0,script=no,downscript=no' + +print(gray(qemu)) + +qemu_session = subprocess.run(shlex.split(qemu), check=True, capture_output=True) + +if qemu_session.stdout: + print(qemu_session.stdout.decode()) +if qemu_session.returncode != 0: + print(red(qemu_session.stderr.decode())) From 3bc248b47f654c48acde1c27463c0c6441a9d74a Mon Sep 17 00:00:00 2001 From: BB Date: Mon, 25 May 2026 07:13:56 -0400 Subject: [PATCH 60/61] add niri DankMaterialShell profile (#4554) --- archinstall/default_profiles/desktops/niri.py | 2 +- .../default_profiles/desktops/niri_dms.py | 66 +++++ .../desktops/niri_dms_assets/dms/alttab.kdl | 10 + .../desktops/niri_dms_assets/dms/binds.kdl | 221 ++++++++++++++ .../desktops/niri_dms_assets/dms/colors.kdl | 39 +++ .../desktops/niri_dms_assets/dms/cursor.kdl | 6 + .../desktops/niri_dms_assets/dms/layout.kdl | 22 ++ .../desktops/niri_dms_assets/dms/outputs.kdl | 7 + .../desktops/niri_dms_assets/niri.kdl | 279 ++++++++++++++++++ archinstall/default_profiles/profile.py | 1 + archinstall/lib/profile/profiles_handler.py | 23 ++ pyproject.toml | 1 + 12 files changed, 676 insertions(+), 1 deletion(-) create mode 100644 archinstall/default_profiles/desktops/niri_dms.py create mode 100644 archinstall/default_profiles/desktops/niri_dms_assets/dms/alttab.kdl create mode 100644 archinstall/default_profiles/desktops/niri_dms_assets/dms/binds.kdl create mode 100644 archinstall/default_profiles/desktops/niri_dms_assets/dms/colors.kdl create mode 100644 archinstall/default_profiles/desktops/niri_dms_assets/dms/cursor.kdl create mode 100644 archinstall/default_profiles/desktops/niri_dms_assets/dms/layout.kdl create mode 100644 archinstall/default_profiles/desktops/niri_dms_assets/dms/outputs.kdl create mode 100644 archinstall/default_profiles/desktops/niri_dms_assets/niri.kdl diff --git a/archinstall/default_profiles/desktops/niri.py b/archinstall/default_profiles/desktops/niri.py index 4d955fbf..d8db75f5 100644 --- a/archinstall/default_profiles/desktops/niri.py +++ b/archinstall/default_profiles/desktops/niri.py @@ -7,7 +7,7 @@ from archinstall.default_profiles.profile import CustomSetting, DisplayServerTyp class NiriProfile(Profile): def __init__(self) -> None: super().__init__( - 'Niri', + 'niri', ProfileType.WindowMgr, support_gfx_driver=True, display_server=DisplayServerType.Wayland, diff --git a/archinstall/default_profiles/desktops/niri_dms.py b/archinstall/default_profiles/desktops/niri_dms.py new file mode 100644 index 00000000..11f32e34 --- /dev/null +++ b/archinstall/default_profiles/desktops/niri_dms.py @@ -0,0 +1,66 @@ +import shutil +from pathlib import Path +from typing import TYPE_CHECKING, override + +from archinstall.default_profiles.profile import DisplayServerType, GreeterType, Profile, ProfileType + +if TYPE_CHECKING: + from archinstall.lib.installer import Installer + from archinstall.lib.models.users import User + + +_TERMINAL = 'alacritty' +_ASSETS_DIR = Path(__file__).parent / 'niri_dms_assets' + + +class NiriDmsProfile(Profile): + def __init__(self) -> None: + super().__init__( + 'niri - DankMaterialShell', + ProfileType.WindowMgr, + support_gfx_driver=True, + display_server=DisplayServerType.Wayland, + ) + + @property + @override + def packages(self) -> list[str]: + return [ + 'niri', + 'dms-shell-niri', + 'polkit', + 'xdg-desktop-portal-gnome', + 'xorg-xwayland', + 'matugen', + 'cava', + 'kimageformats', + 'cups-pk-helper', + 'tuned-ppd', + _TERMINAL, + ] + + @property + @override + def default_greeter_type(self) -> GreeterType: + return GreeterType.GreetdDms + + @override + def provision(self, install_session: Installer, users: list[User]) -> None: + binds = (_ASSETS_DIR / 'dms/binds.kdl').read_text().replace('{{TERMINAL_COMMAND}}', _TERMINAL) + + for user in users: + home = install_session.target / 'home' / user.username + niri_dir = home / '.config/niri' + dms_dir = niri_dir / 'dms' + dms_dir.mkdir(parents=True, exist_ok=True) + + shutil.copy(_ASSETS_DIR / 'niri.kdl', niri_dir / 'config.kdl') + for name in ('colors.kdl', 'layout.kdl', 'alttab.kdl', 'outputs.kdl', 'cursor.kdl'): + shutil.copy(_ASSETS_DIR / 'dms' / name, dms_dir / name) + (dms_dir / 'binds.kdl').write_text(binds) + + niri_unit_dropin = home / '.config/systemd/user/niri.service.d' + niri_unit_dropin.mkdir(parents=True, exist_ok=True) + (niri_unit_dropin / 'dms.conf').write_text('[Unit]\nWants=dms.service\n') + + install_session.arch_chroot(f'chown -R {user.username}:{user.username} /home/{user.username}/.config') diff --git a/archinstall/default_profiles/desktops/niri_dms_assets/dms/alttab.kdl b/archinstall/default_profiles/desktops/niri_dms_assets/dms/alttab.kdl new file mode 100644 index 00000000..5f9bdcd0 --- /dev/null +++ b/archinstall/default_profiles/desktops/niri_dms_assets/dms/alttab.kdl @@ -0,0 +1,10 @@ +// ! DO NOT EDIT ! +// ! AUTO-GENERATED BY DMS ! +// ! CHANGES WILL BE OVERWRITTEN ! +// ! PLACE YOUR CUSTOM CONFIGURATION ELSEWHERE ! + +recent-windows { + highlight { + corner-radius 12 + } +} diff --git a/archinstall/default_profiles/desktops/niri_dms_assets/dms/binds.kdl b/archinstall/default_profiles/desktops/niri_dms_assets/dms/binds.kdl new file mode 100644 index 00000000..27cb860e --- /dev/null +++ b/archinstall/default_profiles/desktops/niri_dms_assets/dms/binds.kdl @@ -0,0 +1,221 @@ +binds { + // === System & Overview === + Mod+D repeat=false { toggle-overview; } + Mod+Tab repeat=false { toggle-overview; } + Mod+Shift+Slash { show-hotkey-overlay; } + + // === Application Launchers === + Mod+T hotkey-overlay-title="Open Terminal" { spawn "{{TERMINAL_COMMAND}}"; } + Mod+Space hotkey-overlay-title="Application Launcher" { + spawn "dms" "ipc" "call" "spotlight" "toggle"; + } + Mod+V hotkey-overlay-title="Clipboard Manager" { + spawn "dms" "ipc" "call" "clipboard" "toggle"; + } + Mod+M hotkey-overlay-title="Task Manager" { + spawn "dms" "ipc" "call" "processlist" "focusOrToggle"; + } + + Super+X hotkey-overlay-title="Power Menu: Toggle" { spawn "dms" "ipc" "call" "powermenu" "toggle"; } + Mod+Comma hotkey-overlay-title="Settings" { + spawn "dms" "ipc" "call" "settings" "focusOrToggle"; + } + Mod+Y hotkey-overlay-title="Browse Wallpapers" { + spawn "dms" "ipc" "call" "dankdash" "wallpaper"; + } + Mod+N hotkey-overlay-title="Notification Center" { spawn "dms" "ipc" "call" "notifications" "toggle"; } + Mod+Shift+N hotkey-overlay-title="Notepad" { spawn "dms" "ipc" "call" "notepad" "toggle"; } + + // === Security === + Mod+Alt+L hotkey-overlay-title="Lock Screen" { + spawn "dms" "ipc" "call" "lock" "lock"; + } + Mod+Shift+E { quit; } + Ctrl+Alt+Delete hotkey-overlay-title="Task Manager" { + spawn "dms" "ipc" "call" "processlist" "focusOrToggle"; + } + + // === Audio Controls === + XF86AudioRaiseVolume allow-when-locked=true { + spawn "dms" "ipc" "call" "audio" "increment" "3"; + } + XF86AudioLowerVolume allow-when-locked=true { + spawn "dms" "ipc" "call" "audio" "decrement" "3"; + } + XF86AudioMute allow-when-locked=true { + spawn "dms" "ipc" "call" "audio" "mute"; + } + XF86AudioMicMute allow-when-locked=true { + spawn "dms" "ipc" "call" "audio" "micmute"; + } + XF86AudioPause allow-when-locked=true { + spawn "dms" "ipc" "call" "mpris" "playPause"; + } + XF86AudioPlay allow-when-locked=true { + spawn "dms" "ipc" "call" "mpris" "playPause"; + } + XF86AudioPrev allow-when-locked=true { + spawn "dms" "ipc" "call" "mpris" "previous"; + } + XF86AudioNext allow-when-locked=true { + spawn "dms" "ipc" "call" "mpris" "next"; + } + Ctrl+XF86AudioRaiseVolume allow-when-locked=true { + spawn "dms" "ipc" "call" "mpris" "increment" "3"; + } + Ctrl+XF86AudioLowerVolume allow-when-locked=true { + spawn "dms" "ipc" "call" "mpris" "decrement" "3"; + } + + // === Brightness Controls === + XF86MonBrightnessUp allow-when-locked=true { + spawn "dms" "ipc" "call" "brightness" "increment" "5" ""; + } + XF86MonBrightnessDown allow-when-locked=true { + spawn "dms" "ipc" "call" "brightness" "decrement" "5" ""; + } + + // === Window Management === + Mod+Q repeat=false { close-window; } + Mod+F { maximize-column; } + Mod+Shift+F { fullscreen-window; } + Mod+Shift+T { toggle-window-floating; } + Mod+Shift+V { switch-focus-between-floating-and-tiling; } + Mod+W { toggle-column-tabbed-display; } + Mod+Shift+W hotkey-overlay-title="Create window rule" { spawn "dms" "ipc" "call" "window-rules" "toggle"; } + + // === Focus Navigation === + Mod+Left { focus-column-left; } + Mod+Down { focus-window-down; } + Mod+Up { focus-window-up; } + Mod+Right { focus-column-right; } + Mod+H { focus-column-left; } + Mod+J { focus-window-down; } + Mod+K { focus-window-up; } + Mod+L { focus-column-right; } + + // === Window Movement === + Mod+Shift+Left { move-column-left; } + Mod+Shift+Down { move-window-down; } + Mod+Shift+Up { move-window-up; } + Mod+Shift+Right { move-column-right; } + Mod+Shift+H { move-column-left; } + Mod+Shift+J { move-window-down; } + Mod+Shift+K { move-window-up; } + Mod+Shift+L { move-column-right; } + + // === Column Navigation === + Mod+Home { focus-column-first; } + Mod+End { focus-column-last; } + Mod+Ctrl+Home { move-column-to-first; } + Mod+Ctrl+End { move-column-to-last; } + + // === Monitor Navigation === + Mod+Ctrl+Left { focus-monitor-left; } + //Mod+Ctrl+Down { focus-monitor-down; } + //Mod+Ctrl+Up { focus-monitor-up; } + Mod+Ctrl+Right { focus-monitor-right; } + Mod+Ctrl+H { focus-monitor-left; } + Mod+Ctrl+J { focus-monitor-down; } + Mod+Ctrl+K { focus-monitor-up; } + Mod+Ctrl+L { focus-monitor-right; } + + // === Move to Monitor === + Mod+Shift+Ctrl+Left { move-column-to-monitor-left; } + Mod+Shift+Ctrl+Down { move-column-to-monitor-down; } + Mod+Shift+Ctrl+Up { move-column-to-monitor-up; } + Mod+Shift+Ctrl+Right { move-column-to-monitor-right; } + Mod+Shift+Ctrl+H { move-column-to-monitor-left; } + Mod+Shift+Ctrl+J { move-column-to-monitor-down; } + Mod+Shift+Ctrl+K { move-column-to-monitor-up; } + Mod+Shift+Ctrl+L { move-column-to-monitor-right; } + + // === Workspace Navigation === + Mod+Page_Down { focus-workspace-down; } + Mod+Page_Up { focus-workspace-up; } + Mod+U { focus-workspace-down; } + Mod+I { focus-workspace-up; } + Mod+Ctrl+Down { move-column-to-workspace-down; } + Mod+Ctrl+Up { move-column-to-workspace-up; } + Mod+Ctrl+U { move-column-to-workspace-down; } + Mod+Ctrl+I { move-column-to-workspace-up; } + + // === Workspace Management === + Ctrl+Shift+R hotkey-overlay-title="Rename Workspace" { + spawn "dms" "ipc" "call" "workspace-rename" "open"; + } + + // === Move Workspaces === + Mod+Shift+Page_Down { move-workspace-down; } + Mod+Shift+Page_Up { move-workspace-up; } + Mod+Shift+U { move-workspace-down; } + Mod+Shift+I { move-workspace-up; } + + // === Mouse Wheel Navigation === + Mod+WheelScrollDown cooldown-ms=150 { focus-workspace-down; } + Mod+WheelScrollUp cooldown-ms=150 { focus-workspace-up; } + Mod+Ctrl+WheelScrollDown cooldown-ms=150 { move-column-to-workspace-down; } + Mod+Ctrl+WheelScrollUp cooldown-ms=150 { move-column-to-workspace-up; } + + Mod+WheelScrollRight { focus-column-right; } + Mod+WheelScrollLeft { focus-column-left; } + Mod+Ctrl+WheelScrollRight { move-column-right; } + Mod+Ctrl+WheelScrollLeft { move-column-left; } + + Mod+Shift+WheelScrollDown { focus-column-right; } + Mod+Shift+WheelScrollUp { focus-column-left; } + Mod+Ctrl+Shift+WheelScrollDown { move-column-right; } + Mod+Ctrl+Shift+WheelScrollUp { move-column-left; } + + // === Numbered Workspaces === + Mod+1 { focus-workspace 1; } + Mod+2 { focus-workspace 2; } + Mod+3 { focus-workspace 3; } + Mod+4 { focus-workspace 4; } + Mod+5 { focus-workspace 5; } + Mod+6 { focus-workspace 6; } + Mod+7 { focus-workspace 7; } + Mod+8 { focus-workspace 8; } + Mod+9 { focus-workspace 9; } + + // === Move to Numbered Workspaces === + Mod+Shift+1 { move-column-to-workspace 1; } + Mod+Shift+2 { move-column-to-workspace 2; } + Mod+Shift+3 { move-column-to-workspace 3; } + Mod+Shift+4 { move-column-to-workspace 4; } + Mod+Shift+5 { move-column-to-workspace 5; } + Mod+Shift+6 { move-column-to-workspace 6; } + Mod+Shift+7 { move-column-to-workspace 7; } + Mod+Shift+8 { move-column-to-workspace 8; } + Mod+Shift+9 { move-column-to-workspace 9; } + + // === Column Management === + Mod+BracketLeft { consume-or-expel-window-left; } + Mod+BracketRight { consume-or-expel-window-right; } + Mod+Period { expel-window-from-column; } + + // === Sizing & Layout === + Mod+R { switch-preset-column-width; } + Mod+Shift+R { switch-preset-window-height; } + Mod+Ctrl+R { reset-window-height; } + Mod+Ctrl+F { expand-column-to-available-width; } + Mod+C { center-column; } + Mod+Ctrl+C { center-visible-columns; } + + // === Manual Sizing === + Mod+Minus { set-column-width "-10%"; } + Mod+Equal { set-column-width "+10%"; } + Mod+Shift+Minus { set-window-height "-10%"; } + Mod+Shift+Equal { set-window-height "+10%"; } + + // === Screenshots === + XF86Launch1 { screenshot; } + Ctrl+XF86Launch1 { screenshot-screen; } + Alt+XF86Launch1 { screenshot-window; } + Print { screenshot; } + Ctrl+Print { screenshot-screen; } + Alt+Print { screenshot-window; } + // === System Controls === + Mod+Escape allow-inhibiting=false { toggle-keyboard-shortcuts-inhibit; } + Mod+Shift+P { power-off-monitors; } +} diff --git a/archinstall/default_profiles/desktops/niri_dms_assets/dms/colors.kdl b/archinstall/default_profiles/desktops/niri_dms_assets/dms/colors.kdl new file mode 100644 index 00000000..145a1798 --- /dev/null +++ b/archinstall/default_profiles/desktops/niri_dms_assets/dms/colors.kdl @@ -0,0 +1,39 @@ +// ! Auto-generated file. Do not edit directly. +// Remove `include "dms/colors.kdl"` from your config to override. + +layout { + background-color "transparent" + + focus-ring { + active-color "#d0bcff" + inactive-color "#948f99" + urgent-color "#f2b8b5" + } + + border { + active-color "#d0bcff" + inactive-color "#948f99" + urgent-color "#f2b8b5" + } + + shadow { + color "#00000070" + } + + tab-indicator { + active-color "#d0bcff" + inactive-color "#948f99" + urgent-color "#f2b8b5" + } + + insert-hint { + color "#d0bcff80" + } +} + +recent-windows { + highlight { + active-color "#4f378b" + urgent-color "#f2b8b5" + } +} diff --git a/archinstall/default_profiles/desktops/niri_dms_assets/dms/cursor.kdl b/archinstall/default_profiles/desktops/niri_dms_assets/dms/cursor.kdl new file mode 100644 index 00000000..db3d385a --- /dev/null +++ b/archinstall/default_profiles/desktops/niri_dms_assets/dms/cursor.kdl @@ -0,0 +1,6 @@ +// Place cursor configuration here. +// Example: +// cursor { +// xcursor-theme "Adwaita" +// xcursor-size 24 +// } diff --git a/archinstall/default_profiles/desktops/niri_dms_assets/dms/layout.kdl b/archinstall/default_profiles/desktops/niri_dms_assets/dms/layout.kdl new file mode 100644 index 00000000..1951500b --- /dev/null +++ b/archinstall/default_profiles/desktops/niri_dms_assets/dms/layout.kdl @@ -0,0 +1,22 @@ +// ! DO NOT EDIT ! +// ! AUTO-GENERATED BY DMS ! +// ! CHANGES WILL BE OVERWRITTEN ! +// ! PLACE YOUR CUSTOM CONFIGURATION ELSEWHERE ! + +layout { + gaps 4 + + border { + width 2 + } + + focus-ring { + width 2 + } +} +window-rule { + geometry-corner-radius 12 + clip-to-geometry true + tiled-state true + draw-border-with-background false +} diff --git a/archinstall/default_profiles/desktops/niri_dms_assets/dms/outputs.kdl b/archinstall/default_profiles/desktops/niri_dms_assets/dms/outputs.kdl new file mode 100644 index 00000000..3fe37358 --- /dev/null +++ b/archinstall/default_profiles/desktops/niri_dms_assets/dms/outputs.kdl @@ -0,0 +1,7 @@ +// Place per-output configuration here. +// Example: +// output "DP-1" { +// mode "2560x1440@165" +// position x=0 y=0 +// scale 1 +// } diff --git a/archinstall/default_profiles/desktops/niri_dms_assets/niri.kdl b/archinstall/default_profiles/desktops/niri_dms_assets/niri.kdl new file mode 100644 index 00000000..0a759e46 --- /dev/null +++ b/archinstall/default_profiles/desktops/niri_dms_assets/niri.kdl @@ -0,0 +1,279 @@ +// This config is in the KDL format: https://kdl.dev +// "/-" comments out the following node. +// Check the wiki for a full description of the configuration: +// https://github.com/YaLTeR/niri/wiki/Configuration:-Introduction +config-notification { + disable-failed +} + +gestures { + hot-corners { + off + } +} + +// Input device configuration. +// Find the full list of options on the wiki: +// https://github.com/YaLTeR/niri/wiki/Configuration:-Input +input { + keyboard { + xkb { + // You can set rules, model, layout, variant and options. + // For more information, see xkeyboard-config(7). + + // For example: + // layout "us,ru" + // options "grp:win_space_toggle,compose:ralt,ctrl:nocaps" + + // If this section is empty, niri will fetch xkb settings + // from org.freedesktop.locale1. You can control these using + // localectl set-x11-keymap. + } + + // Enable numlock on startup, omitting this setting disables it. + numlock + } + + // Next sections include libinput settings. + // Omitting settings disables them, or leaves them at their default values. + // All commented-out settings here are examples, not defaults. + touchpad { + // off + tap + // dwt + // dwtp + // drag false + // drag-lock + natural-scroll + // accel-speed 0.2 + // accel-profile "flat" + // scroll-method "two-finger" + // disabled-on-external-mouse + } + + mouse { + // off + // natural-scroll + // accel-speed 0.2 + // accel-profile "flat" + // scroll-method "no-scroll" + } + + trackpoint { + // off + // natural-scroll + // accel-speed 0.2 + // accel-profile "flat" + // scroll-method "on-button-down" + // scroll-button 273 + // scroll-button-lock + // middle-emulation + } + + // Uncomment this to make the mouse warp to the center of newly focused windows. + // warp-mouse-to-focus + + // Focus windows and outputs automatically when moving the mouse into them. + // Setting max-scroll-amount="0%" makes it work only on windows already fully on screen. + // focus-follows-mouse max-scroll-amount="0%" +} +// You can configure outputs by their name, which you can find +// by running `niri msg outputs` while inside a niri instance. +// The built-in laptop monitor is usually called "eDP-1". +// Find more information on the wiki: +// https://github.com/YaLTeR/niri/wiki/Configuration:-Outputs +// Remember to uncomment the node by removing "/-"! +/-output "eDP-2" { + mode "2560x1600@239.998993" + position x=2560 y=0 + variable-refresh-rate +} +// Settings that influence how windows are positioned and sized. +// Find more information on the wiki: +// https://github.com/YaLTeR/niri/wiki/Configuration:-Layout +layout { + // Set gaps around windows in logical pixels. + background-color "transparent" + // When to center a column when changing focus, options are: + // - "never", default behavior, focusing an off-screen column will keep at the left + // or right edge of the screen. + // - "always", the focused column will always be centered. + // - "on-overflow", focusing a column will center it if it doesn't fit + // together with the previously focused column. + center-focused-column "never" + // You can customize the widths that "switch-preset-column-width" (Mod+R) toggles between. + preset-column-widths { + // Proportion sets the width as a fraction of the output width, taking gaps into account. + // For example, you can perfectly fit four windows sized "proportion 0.25" on an output. + // The default preset widths are 1/3, 1/2 and 2/3 of the output. + proportion 0.33333 + proportion 0.5 + proportion 0.66667 + // Fixed sets the width in logical pixels exactly. + // fixed 1920 + } + // You can also customize the heights that "switch-preset-window-height" (Mod+Shift+R) toggles between. + // preset-window-heights { } + // You can change the default width of the new windows. + default-column-width { proportion 0.5; } + // If you leave the brackets empty, the windows themselves will decide their initial width. + // default-column-width {} + // By default focus ring and border are rendered as a solid background rectangle + // behind windows. That is, they will show up through semitransparent windows. + // This is because windows using client-side decorations can have an arbitrary shape. + // + // If you don't like that, you should uncomment `prefer-no-csd` below. + // Niri will draw focus ring and border *around* windows that agree to omit their + // client-side decorations. + // + // Alternatively, you can override it with a window rule called + // `draw-border-with-background`. + border { + off + width 4 + active-color "#707070" // Neutral gray + inactive-color "#d0d0d0" // Light gray + urgent-color "#cc4444" // Softer red + } + shadow { + softness 30 + spread 5 + offset x=0 y=5 + color "#0007" + } + struts { + } +} +layer-rule { + match namespace="^quickshell$" + place-within-backdrop true +} +overview { + workspace-shadow { + off + } +} +// Add lines like this to spawn processes at startup. +// Note that running niri as a session supports xdg-desktop-autostart, +// which may be more convenient to use. +// See the binds section below for more spawn examples. +// This line starts waybar, a commonly used bar for Wayland compositors. +environment { + XDG_CURRENT_DESKTOP "niri" +} +hotkey-overlay { + skip-at-startup +} +prefer-no-csd +screenshot-path "~/Pictures/Screenshots/Screenshot from %Y-%m-%d %H-%M-%S.png" +animations { + workspace-switch { + spring damping-ratio=0.80 stiffness=523 epsilon=0.0001 + } + window-open { + duration-ms 150 + curve "ease-out-expo" + } + window-close { + duration-ms 150 + curve "ease-out-quad" + } + horizontal-view-movement { + spring damping-ratio=0.85 stiffness=423 epsilon=0.0001 + } + window-movement { + spring damping-ratio=0.75 stiffness=323 epsilon=0.0001 + } + window-resize { + spring damping-ratio=0.85 stiffness=423 epsilon=0.0001 + } + config-notification-open-close { + spring damping-ratio=0.65 stiffness=923 epsilon=0.001 + } + screenshot-ui-open { + duration-ms 200 + curve "ease-out-quad" + } + overview-open-close { + spring damping-ratio=0.85 stiffness=800 epsilon=0.0001 + } +} +// Window rules let you adjust behavior for individual windows. +// Find more information on the wiki: +// https://github.com/YaLTeR/niri/wiki/Configuration:-Window-Rules +// Work around WezTerm's initial configure bug +// by setting an empty default-column-width. +window-rule { + // This regular expression is intentionally made as specific as possible, + // since this is the default config, and we want no false positives. + // You can get away with just app-id="wezterm" if you want. + match app-id=r#"^org\.wezfurlong\.wezterm$"# + default-column-width {} +} +window-rule { + match app-id=r#"^org\.gnome\."# + draw-border-with-background false + geometry-corner-radius 12 + clip-to-geometry true +} +window-rule { + match app-id=r#"^gnome-control-center$"# + match app-id=r#"^pavucontrol$"# + match app-id=r#"^nm-connection-editor$"# + default-column-width { proportion 0.5; } + open-floating false +} +window-rule { + match app-id=r#"^org\.gnome\.Calculator$"# + match app-id=r#"^gnome-calculator$"# + match app-id=r#"^galculator$"# + match app-id=r#"^blueman-manager$"# + match app-id=r#"^org\.gnome\.Nautilus$"# + match app-id=r#"^xdg-desktop-portal$"# + open-floating true +} +window-rule { + match app-id=r#"^steam$"# title=r#"^notificationtoasts_\d+_desktop$"# + default-floating-position x=10 y=10 relative-to="bottom-right" + open-focused false +} +window-rule { + match app-id=r#"^org\.wezfurlong\.wezterm$"# + match app-id="Alacritty" + match app-id="zen" + match app-id="com.mitchellh.ghostty" + match app-id="kitty" + draw-border-with-background false +} +window-rule { + match app-id=r#"firefox$"# title="^Picture-in-Picture$" + match app-id="zoom" + open-floating true +} +// Open dms windows as floating by default +window-rule { + match app-id=r#"org.quickshell$"# + match app-id=r#"com.danklinux.dms$"# + open-floating true +} +debug { + honor-xdg-activation-with-invalid-serial +} + +// Override to disable super+tab +recent-windows { + binds { + Alt+Tab { next-window scope="output"; } + Alt+Shift+Tab { previous-window scope="output"; } + Alt+grave { next-window filter="app-id"; } + Alt+Shift+grave { previous-window filter="app-id"; } + } +} + +// Include dms files +include "dms/colors.kdl" +include "dms/layout.kdl" +include "dms/alttab.kdl" +include "dms/binds.kdl" +include "dms/outputs.kdl" +include "dms/cursor.kdl" diff --git a/archinstall/default_profiles/profile.py b/archinstall/default_profiles/profile.py index cc56186f..6c328a46 100644 --- a/archinstall/default_profiles/profile.py +++ b/archinstall/default_profiles/profile.py @@ -37,6 +37,7 @@ class GreeterType(Enum): Ly = 'ly' CosmicSession = 'cosmic-greeter' PlasmaLoginManager = 'plasma-login-manager' + GreetdDms = 'dms-greeter' class SelectResult(Enum): diff --git a/archinstall/lib/profile/profiles_handler.py b/archinstall/lib/profile/profiles_handler.py index d937a520..1e770de5 100644 --- a/archinstall/lib/profile/profiles_handler.py +++ b/archinstall/lib/profile/profiles_handler.py @@ -175,6 +175,9 @@ class ProfileHandler: case GreeterType.PlasmaLoginManager: packages = ['plasma-login-manager'] service = ['plasmalogin'] + case GreeterType.GreetdDms: + packages = ['greetd'] + service = ['greetd'] if packages: install_session.add_additional_packages(packages) @@ -194,6 +197,26 @@ class ProfileHandler: with open(path, 'w') as file: file.write(filedata) + if greeter == GreeterType.GreetdDms: + greetd_config = install_session.target / 'etc/greetd/config.toml' + greetd_config.parent.mkdir(parents=True, exist_ok=True) + greetd_config.write_text( + '[terminal]\n' + 'vt = 1\n' + '\n' + '[default_session]\n' + 'user = "greeter"\n' + 'command = "/usr/share/quickshell/dms/Modules/Greetd/assets/dms-greeter --command niri -p /usr/share/quickshell/dms"\n', + ) + + tmpfiles = install_session.target / 'etc/tmpfiles.d/dms-greeter.conf' + tmpfiles.parent.mkdir(parents=True, exist_ok=True) + tmpfiles.write_text( + '# Path Mode User Group Age Argument\n' + 'd /var/cache/dms-greeter 0750 greeter greeter -\n' + 'd /var/lib/greeter 0755 greeter greeter -\n', + ) + def install_gfx_driver(self, install_session: Installer, driver: GfxDriver) -> None: debug(f'Installing GFX driver: {driver.value}') diff --git a/pyproject.toml b/pyproject.toml index 8d0c5a64..7942fb6e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -61,6 +61,7 @@ include-package-data = true "**/*.po", "**/*.pot", "**/*.json", + "**/*.kdl", ] [tool.setuptools.package-dir] From b96afcc579c9a23b5779fa86b2c6775a556bf61b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 25 May 2026 21:58:49 +1000 Subject: [PATCH 61/61] Update dependency arch/python-textual to v8.2.7 (#4556) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 7942fb6e..ccbee27b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,7 +21,7 @@ dependencies = [ "pyparted==3.13.0", "pydantic==2.13.4", "cryptography==48.0.0", - "textual==8.2.6", + "textual==8.2.7", "markdown-it-py==4.0.0", "linkify-it-py==2.1.0", ]