diff --git a/.gitignore b/.gitignore
index 7c800d8dfd..1051ddacee 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
.log/
.yardoc/
+.vscode/tasks.json
diff --git a/.yupdate.post b/.yupdate.post
index 2d91b28549..c3560a7012 100755
--- a/.yupdate.post
+++ b/.yupdate.post
@@ -25,8 +25,8 @@ function restart_service() {
if [ -n "$SERVICE_START" ]; then
SERVICE_START_UNIX_TIME=$(date -d "$SERVICE_START" +"%s")
- # find the date of the latest file in the gem
- NEWEST_FILE_TIME=$(find /usr/lib*/ruby/gems/*/gems/$SERVICE_NAME-* -exec stat --format %Y "{}" \; | sort -nr | head -n 1)
+ # find the date of the latest file in the product configuration or in the gem
+ NEWEST_FILE_TIME=$(find /usr/share/agama/products.d /usr/lib*/ruby/gems/*/gems/$SERVICE_NAME-* -exec stat --format %Y "{}" \; | sort -nr | head -n 1)
# when a file is newer than the start time then restart the service
if [ -n "$NEWEST_FILE_TIME" ] && [ "$SERVICE_START_UNIX_TIME" -lt "$NEWEST_FILE_TIME" ]; then
diff --git a/Rakefile b/Rakefile
index 37ec973652..095acb3b1c 100644
--- a/Rakefile
+++ b/Rakefile
@@ -170,5 +170,21 @@ if ENV["YUPDATE_FORCE"] == "1" || File.exist?("/.packages.initrd") || live_iso?
FileUtils.mkdir_p(File.join(destdir, "/usr/share"))
FileUtils.cp_r("playwright/.", File.join(destdir, "/usr/share/agama-playwright"))
end
+
+ if ENV["YUPDATE_SKIP_PRODUCTS"] != "1"
+ files = Dir.glob("products.d/*.y{a}ml")
+ files.each do |f|
+ # the sources contain several products, update only the existing files
+ oldfile = File.join("/usr/share/agama/", f)
+ if File.exist?(oldfile)
+ target = File.join(destdir, "/usr/share/agama/", f)
+ FileUtils.mkdir_p(File.dirname(target))
+ FileUtils.cp(f, target)
+ else
+ # if there is a new product file it needs to be copied manually
+ puts "Skipping product file: #{f}"
+ end
+ end
+ end
end
end
diff --git a/doc/dbus/bus/org.opensuse.Agama1.Locale.bus.xml b/doc/dbus/bus/org.opensuse.Agama1.Locale.bus.xml
index 38416bd6bc..8f4dec9054 100644
--- a/doc/dbus/bus/org.opensuse.Agama1.Locale.bus.xml
+++ b/doc/dbus/bus/org.opensuse.Agama1.Locale.bus.xml
@@ -38,39 +38,47 @@
-
-
+
+
-
-
-
-
-
+
+
+
-
-
+
+
-
-
diff --git a/doc/dbus/org.opensuse.Agama1.Locale.doc.xml b/doc/dbus/org.opensuse.Agama1.Locale.doc.xml
index 8844528be1..3f0256a75e 100644
--- a/doc/dbus/org.opensuse.Agama1.Locale.doc.xml
+++ b/doc/dbus/org.opensuse.Agama1.Locale.doc.xml
@@ -1,47 +1,48 @@
-
-
+
-
-
-
+
+
-
-
-
-
-
+
+
+
-
-
+
+
-
-
-
-
diff --git a/doc/yaml_config.md b/doc/yaml_config.md
index a98d48c929..c2a8e0de9a 100644
--- a/doc/yaml_config.md
+++ b/doc/yaml_config.md
@@ -38,6 +38,15 @@ Array of patterns that have to be selected.
Array of patterns that should be selected but can be deselected or skipped if not available.
+#### user\_patterns
+
+Array of patterns that are displayed in the pattern selector UI and user can
+select them to install.
+
+If the list is empty then the pattern selector is not displayed. If the key is
+not defined or the value is missing or is `null` then all available user visible
+patterns are displayed.
+
### security
Options related to security
diff --git a/products.d/ALP-Dolomite.yaml b/products.d/ALP-Dolomite.yaml
index c2d4e7d039..d1cbea6f61 100644
--- a/products.d/ALP-Dolomite.yaml
+++ b/products.d/ALP-Dolomite.yaml
@@ -14,12 +14,17 @@ translations:
cs: SUSE ALP Dolomite je minimální neměnitelný základní OS, zaměřený na
bezpečnost pro poskytování úplného minima ke spuštění úloh a služeb v
kontejnerech nebo virtuálních strojích.
+ sv: SUSE ALP Dolomite är en minimal oföränderlig OS-kärna, fokuserad på säkerhet
+ för att tillhandahålla det absoluta minimum för att köra
+ arbetsbelastningar och tjänster som behållare eller virtuella maskiner.
software:
mandatory_patterns:
- alp_base_zypper
- alp_cockpit
- alp_hardware
optional_patterns: null # no optional pattern shared
+ # no user selectable patterns, do not display the pattern selector
+ user_patterns: []
mandatory_packages:
- package: ppc64-diag # Needed for hardware-based installations
archs: ppc64
diff --git a/products.d/agama-products-opensuse.changes b/products.d/agama-products-opensuse.changes
index f84460af9e..eb6cd2d50f 100644
--- a/products.d/agama-products-opensuse.changes
+++ b/products.d/agama-products-opensuse.changes
@@ -1,3 +1,9 @@
+-------------------------------------------------------------------
+Mon Dec 4 14:11:51 UTC 2023 - Ancor Gonzalez Sosa
+
+- Preliminary definitions of openSUSE MicroOS products
+- Remove Leap 16.0 for now
+
-------------------------------------------------------------------
Mon Oct 30 14:38:51 UTC 2023 - Josef Reidinger
diff --git a/products.d/agama-products-opensuse.spec b/products.d/agama-products-opensuse.spec
index d2c23829f9..815663bc4a 100644
--- a/products.d/agama-products-opensuse.spec
+++ b/products.d/agama-products-opensuse.spec
@@ -52,8 +52,9 @@ install -m 0644 *.yaml %{buildroot}%{_datadir}/agama/products.d
%files
%dir %{_datadir}/agama
%dir %{_datadir}/agama/products.d
+%{_datadir}/agama/products.d/microos.yaml
+%{_datadir}/agama/products.d/microos-desktop.yaml
%{_datadir}/agama/products.d/tumbleweed.yaml
-%{_datadir}/agama/products.d/leap16.yaml
%files -n agama-products-ALP-Dolomite
%dir %{_datadir}/agama
diff --git a/products.d/leap16.yaml b/products.d/leap16.yaml
deleted file mode 100644
index b7913cfb05..0000000000
--- a/products.d/leap16.yaml
+++ /dev/null
@@ -1,98 +0,0 @@
-id: Leap16
-name: openSUSE Leap 16.0
-archs: x86_64,aarch64
-# ------------------------------------------------------------------------------
-# WARNING: When changing the product description delete the translations located
-# at the at translations/description key below to avoid using obsolete
-# translations!!
-# ------------------------------------------------------------------------------
-description: '[Experimental project] openSUSE Leap 16 is built on top of the
- next generation Adaptable Linux Platform (ALP) from SUSE.'
-# Do not manually change any translations! See README.md for more details.
-translations:
- description:
- cs: "[Experimentální projekt] openSUSE Leap 16 je postaven na budoucí generaci
- Adaptable Linux Platform (ALP) od SUSE."
-software:
- installation_repositories:
- - url: https://download.opensuse.org/repositories/openSUSE:/Leap:/16.0/images/repo/Leap-16.0-x86_64-Media1/
- archs: x86_64
- - url: https://download.opensuse.org/repositories/openSUSE:/Leap:/16.0/images/repo/Leap-16.0-aarch64-Media1/
- archs: aarch64
- mandatory_patterns:
- - alp_base
- - alp_base_zypper
- - alp_cockpit
- - alp-container_runtime
- - alp_defaults
- optional_patterns: null # no optional pattern shared
- mandatory_packages: null
- optional_packages: null
- base_product: Leap16
-
-security:
- lsm: selinux
- available_lsms:
- # apparmor:
- # patterns:
- # - apparmor
- selinux:
- patterns:
- - alp_selinux
- policy: enforcing
- none:
- patterns: null
-
-storage:
- space_policy: delete
- encryption:
- method: luks2
- pbkd_function: pbkdf2
- tpm_luks_open: true
- volumes:
- - "/"
- volume_templates:
- - mount_path: "/"
- filesystem: btrfs
- btrfs:
- snapshots: true
- read_only: true
- default_subvolume: "@"
- subvolumes:
- - path: root
- - path: home
- - path: opt
- - path: srv
- - path: boot/writable
- - path: usr/local
- - path: boot/grub2/arm64-efi
- archs: aarch64
- - path: boot/grub2/i386-pc
- archs: x86_64
- - path: boot/grub2/powerpc-ieee1275
- archs: ppc,!board_powernv
- - path: boot/grub2/s390x-emu
- archs: s390
- - path: boot/grub2/x86_64-efi
- archs: x86_64
- - path: var
- copy_on_write: false
- size:
- auto: false
- min: 5 GiB
- outline:
- required: true
- filesystems:
- - btrfs
- snapshots_configurable: false
- - filesystem: xfs
- size:
- auto: false
- outline:
- required: false
- filesystems:
- - btrfs
- - ext2
- - ext3
- - ext4
- - xfs
diff --git a/products.d/microos-desktop.yaml b/products.d/microos-desktop.yaml
new file mode 100644
index 0000000000..8a850ec72d
--- /dev/null
+++ b/products.d/microos-desktop.yaml
@@ -0,0 +1,110 @@
+id: MicroOS-Desktop
+name: openSUSE MicroOS Desktop
+# ------------------------------------------------------------------------------
+# WARNING: When changing the product description delete the translations located
+# at the at translations/description key below to avoid using obsolete
+# translations!!
+# ------------------------------------------------------------------------------
+description: 'A distribution for the desktop offering automatic updates and
+ rollback on top of the foundations of openSUSE MicroOS. Includes Podman
+ Container Runtime and allows to manage software using Gnome Software or
+ KDE Discover.'
+# Do not manually change any translations! See README.md for more details.
+translations:
+software:
+ installation_repositories:
+ - url: https://download.opensuse.org/tumbleweed/repo/oss/
+ archs: x86_64
+ - url: https://download.opensuse.org/ports/aarch64/tumbleweed/repo/oss/
+ archs: aarch64
+ - url: https://download.opensuse.org/ports/zsystems/tumbleweed/repo/oss/
+ archs: s390
+ - url: https://download.opensuse.org/ports/ppc/tumbleweed/repo/oss/
+ archs: ppc
+ - url: https://download.opensuse.org/tumbleweed/repo/non-oss/
+ archs: x86_64
+ # aarch64 does not have non-oss ports. Keep eye if it change
+ - url: https://download.opensuse.org/ports/zsystems/tumbleweed/repo/non-oss/
+ archs: s390
+ - url: https://download.opensuse.org/ports/ppc/tumbleweed/repo/non-oss/
+ archs: ppc
+ - url: https://download.opensuse.org/update/tumbleweed/
+ archs: x86_64
+ - url: https://download.opensuse.org/ports/aarch64/update/tumbleweed/
+ archs: aarch64
+ - url: https://download.opensuse.org/ports/zsystems/update/tumbleweed/
+ archs: s390
+ - url: https://download.opensuse.org/ports/ppc/tumbleweed/repo/oss/
+ archs: ppc
+ mandatory_patterns:
+ - microos_base
+ - microos_base_zypper
+ - microos_defaults
+ - microos_hardware
+ - container_runtime
+ - pattern: 32bit
+ archs: x86_64
+ optional_patterns:
+ - microos_gnome_desktop
+ user_patterns:
+ - microos_gnome_desktop
+ - microos_kde_desktop
+ mandatory_packages:
+ - NetworkManager
+ optional_packages: null
+ base_product: MicroOS
+
+security:
+ lsm: selinux
+ available_lsms:
+ selinux:
+ patterns:
+ - microos_selinux
+ policy: enforcing
+ none:
+ patterns: null
+
+storage:
+ space_policy: delete
+ volumes:
+ - "/"
+ volume_templates:
+ - mount_path: "/"
+ filesystem: btrfs
+ btrfs:
+ snapshots: true
+ read_only: true
+ default_subvolume: "@"
+ subvolumes:
+ - path: home
+ - path: opt
+ - path: root
+ - path: srv
+ - path: usr/local
+ - path: boot/writable
+ # Unified var subvolume - https://lists.opensuse.org/opensuse-packaging/2017-11/msg00017.html
+ - path: var
+ copy_on_write: false
+ # Architecture specific subvolumes
+ - path: boot/grub2/arm64-efi
+ archs: aarch64
+ - path: boot/grub2/arm-efi
+ archs: arm
+ - path: boot/grub2/i386-pc
+ archs: x86_64
+ - path: boot/grub2/powerpc-ieee1275
+ archs: ppc,!board_powernv
+ - path: boot/grub2/s390x-emu
+ archs: s390
+ - path: boot/grub2/x86_64-efi
+ archs: x86_64
+ - path: boot/grub2/riscv64-efi
+ archs: riscv64
+ size:
+ auto: false
+ min: 5 GiB
+ outline:
+ required: true
+ snapshots_configurable: false
+ filesystems:
+ - btrfs
diff --git a/products.d/microos.yaml b/products.d/microos.yaml
new file mode 100644
index 0000000000..ec998f2eec
--- /dev/null
+++ b/products.d/microos.yaml
@@ -0,0 +1,126 @@
+id: MicroOS
+name: openSUSE MicroOS
+# ------------------------------------------------------------------------------
+# WARNING: When changing the product description delete the translations located
+# at the at translations/description key below to avoid using obsolete
+# translations!!
+# ------------------------------------------------------------------------------
+description: 'A quick, small distribution designed to host container workloads
+ with automated administration & patching. openSUSE MicroOS provides
+ transactional (atomic) updates upon a read-only btrfs root file system.
+ As rolling release distribution the software is always up-to-date.'
+# Do not manually change any translations! See README.md for more details.
+translations:
+software:
+ installation_repositories:
+ - url: https://download.opensuse.org/tumbleweed/repo/oss/
+ archs: x86_64
+ - url: https://download.opensuse.org/ports/aarch64/tumbleweed/repo/oss/
+ archs: aarch64
+ - url: https://download.opensuse.org/ports/zsystems/tumbleweed/repo/oss/
+ archs: s390
+ - url: https://download.opensuse.org/ports/ppc/tumbleweed/repo/oss/
+ archs: ppc
+ - url: https://download.opensuse.org/tumbleweed/repo/non-oss/
+ archs: x86_64
+ # aarch64 does not have non-oss ports. Keep eye if it change
+ - url: https://download.opensuse.org/ports/zsystems/tumbleweed/repo/non-oss/
+ archs: s390
+ - url: https://download.opensuse.org/ports/ppc/tumbleweed/repo/non-oss/
+ archs: ppc
+ - url: https://download.opensuse.org/update/tumbleweed/
+ archs: x86_64
+ - url: https://download.opensuse.org/ports/aarch64/update/tumbleweed/
+ archs: aarch64
+ - url: https://download.opensuse.org/ports/zsystems/update/tumbleweed/
+ archs: s390
+ - url: https://download.opensuse.org/ports/ppc/tumbleweed/repo/oss/
+ archs: ppc
+ mandatory_patterns:
+ - microos_base
+ - microos_base_zypper
+ - microos_defaults
+ - microos_hardware
+ - pattern: 32bit
+ archs: x86_64
+ optional_patterns: null
+ user_patterns:
+ - container_runtime
+ - microos_ra_agent
+ - microos_ra_verifier
+ mandatory_packages:
+ - NetworkManager
+ optional_packages: null
+ base_product: MicroOS
+
+security:
+ lsm: selinux
+ available_lsms:
+ selinux:
+ patterns:
+ - microos_selinux
+ policy: enforcing
+ none:
+ patterns: null
+
+storage:
+ space_policy: delete
+ volumes:
+ - "/"
+ - "/var"
+ volume_templates:
+ - mount_path: "/"
+ filesystem: btrfs
+ btrfs:
+ snapshots: true
+ read_only: true
+ default_subvolume: "@"
+ subvolumes:
+ - path: home
+ - path: opt
+ - path: root
+ - path: srv
+ - path: usr/local
+ - path: boot/writable
+ # Unified var subvolume - https://lists.opensuse.org/opensuse-packaging/2017-11/msg00017.html
+ - path: var
+ copy_on_write: false
+ # Architecture specific subvolumes
+ - path: boot/grub2/arm64-efi
+ archs: aarch64
+ - path: boot/grub2/arm-efi
+ archs: arm
+ - path: boot/grub2/i386-pc
+ archs: x86_64
+ - path: boot/grub2/powerpc-ieee1275
+ archs: ppc,!board_powernv
+ - path: boot/grub2/s390x-emu
+ archs: s390
+ - path: boot/grub2/x86_64-efi
+ archs: x86_64
+ - path: boot/grub2/riscv64-efi
+ archs: riscv64
+ size:
+ auto: true
+ outline:
+ required: true
+ snapshots_configurable: false
+ filesystems:
+ - btrfs
+ auto_size:
+ base_min: 5 GiB
+ base_max: 25 GiB
+ max_fallback_for:
+ - "/var"
+ - mount_path: "/var"
+ filesystem: btrfs
+ mount_options:
+ - "x-initrd.mount"
+ - "nodatacow"
+ size:
+ auto: false
+ min: 5 GiB
+ outline:
+ required: false
+ filesystems:
+ - btrfs
diff --git a/products.d/tumbleweed.yaml b/products.d/tumbleweed.yaml
index e5c72cd2cb..fc8e934e5a 100644
--- a/products.d/tumbleweed.yaml
+++ b/products.d/tumbleweed.yaml
@@ -15,6 +15,10 @@ translations:
cs: Tumbleweed je rolující verze distribuce openSUSE obsahující poslední
"stabilní" verze veškerého software namísto pevných pravidelných vydání.
Projekt je určen pro uživatele, kteří chtějí nejnovější stabilní software.
+ sv: Tumbleweed-distributionen är en ren rullande version av openSUSE som
+ innehåller de senaste "stabila" versionerna av all programvara istället
+ för att förlita sig på stela periodiska utgivningscykler. Projektet gör
+ detta för användare som vill ha den senaste stabila programvaran.
software:
installation_repositories:
- url: https://download.opensuse.org/tumbleweed/repo/oss/
@@ -43,6 +47,16 @@ software:
mandatory_patterns:
- enhanced_base # only pattern that is shared among all roles on TW
optional_patterns: null # no optional pattern shared
+ user_patterns:
+ - basic_desktop
+ - xfce
+ - kde
+ - gnome
+ - yast2_basis
+ - yast2_desktop
+ - yast2_server
+ - multimedia
+ - office
mandatory_packages:
- NetworkManager
optional_packages: null
diff --git a/rust/Cargo.lock b/rust/Cargo.lock
index bb72b0de76..cb533f2e9b 100644
--- a/rust/Cargo.lock
+++ b/rust/Cargo.lock
@@ -48,7 +48,11 @@ dependencies = [
"agama-locale-data",
"anyhow",
"cidr",
+ "gettext-rs",
"log",
+ "macaddr",
+ "once_cell",
+ "regex",
"serde",
"serde_yaml",
"simplelog",
@@ -100,6 +104,7 @@ dependencies = [
"quick-xml",
"regex",
"serde",
+ "thiserror",
]
[[package]]
@@ -393,6 +398,12 @@ version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
+[[package]]
+name = "block"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a"
+
[[package]]
name = "block-buffer"
version = "0.10.4"
@@ -893,6 +904,26 @@ dependencies = [
"wasi",
]
+[[package]]
+name = "gettext-rs"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e49ea8a8fad198aaa1f9655a2524b64b70eb06b2f3ff37da407566c93054f364"
+dependencies = [
+ "gettext-sys",
+ "locale_config",
+]
+
+[[package]]
+name = "gettext-sys"
+version = "0.21.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c63ce2e00f56a206778276704bbe38564c8695249fdc8f354b4ef71c57c3839d"
+dependencies = [
+ "cc",
+ "temp-dir",
+]
+
[[package]]
name = "gimli"
version = "0.28.0"
@@ -1081,6 +1112,19 @@ version = "0.4.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829"
+[[package]]
+name = "locale_config"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08d2c35b16f4483f6c26f0e4e9550717a2f6575bcd6f12a53ff0c490a94a6934"
+dependencies = [
+ "lazy_static",
+ "objc",
+ "objc-foundation",
+ "regex",
+ "winapi",
+]
+
[[package]]
name = "lock_api"
version = "0.4.11"
@@ -1100,6 +1144,21 @@ dependencies = [
"value-bag",
]
+[[package]]
+name = "macaddr"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baee0bbc17ce759db233beb01648088061bf678383130602a298e6998eedb2d8"
+
+[[package]]
+name = "malloc_buf"
+version = "0.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb"
+dependencies = [
+ "libc",
+]
+
[[package]]
name = "memchr"
version = "2.6.4"
@@ -1281,6 +1340,35 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3"
+[[package]]
+name = "objc"
+version = "0.2.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1"
+dependencies = [
+ "malloc_buf",
+]
+
+[[package]]
+name = "objc-foundation"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9"
+dependencies = [
+ "block",
+ "objc",
+ "objc_id",
+]
+
+[[package]]
+name = "objc_id"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b"
+dependencies = [
+ "objc",
+]
+
[[package]]
name = "object"
version = "0.32.1"
@@ -1831,6 +1919,12 @@ dependencies = [
"log",
]
+[[package]]
+name = "temp-dir"
+version = "0.1.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af547b166dd1ea4b472165569fc456cfb6818116f854690b0ff205e636523dab"
+
[[package]]
name = "tempfile"
version = "3.8.1"
diff --git a/rust/agama-cli/src/logs.rs b/rust/agama-cli/src/logs.rs
index 027da2c9a2..f6b6fcf404 100644
--- a/rust/agama-cli/src/logs.rs
+++ b/rust/agama-cli/src/logs.rs
@@ -112,7 +112,7 @@ const DEFAULT_PATHS: [&str; 14] = [
"/linuxrc.config",
];
-const DEFAULT_RESULT: &str = "/tmp/agama_logs";
+const DEFAULT_RESULT: &str = "/tmp/agama-logs";
// what compression is used by default:
// (, )
const DEFAULT_COMPRESSION: (&str, &str) = ("bzip2", "tar.bz2");
@@ -398,7 +398,11 @@ fn store(options: LogOptions) -> Result<(), io::Error> {
showln(verbose, "\t- proceeding output of commands");
// store it
- showln(true, format!("Storing result in: \"{}\"", result).as_str());
+ if verbose {
+ showln(true, format!("Storing result in: \"{}\"", result).as_str());
+ } else {
+ showln(true, result.as_str());
+ }
for log in log_sources.iter() {
show(
diff --git a/rust/agama-dbus-server/Cargo.toml b/rust/agama-dbus-server/Cargo.toml
index 1b28677bcf..e6e95c67a0 100644
--- a/rust/agama-dbus-server/Cargo.toml
+++ b/rust/agama-dbus-server/Cargo.toml
@@ -21,3 +21,7 @@ serde_yaml = "0.9.24"
cidr = { version = "0.2.2", features = ["serde"] }
tokio = { version = "1.33.0", features = ["macros", "rt-multi-thread"] }
tokio-stream = "0.1.14"
+gettext-rs = { version = "0.7.0", features = ["gettext-system"] }
+regex = "1.10.2"
+once_cell = "1.18.0"
+macaddr = "1.0"
diff --git a/rust/agama-dbus-server/src/error.rs b/rust/agama-dbus-server/src/error.rs
index 81202acd14..926feab063 100644
--- a/rust/agama-dbus-server/src/error.rs
+++ b/rust/agama-dbus-server/src/error.rs
@@ -19,3 +19,9 @@ impl From for Error {
Self::Anyhow(format!("{:#}", e))
}
}
+
+impl From for zbus::fdo::Error {
+ fn from(value: Error) -> zbus::fdo::Error {
+ zbus::fdo::Error::Failed(format!("Localization error: {value}"))
+ }
+}
diff --git a/rust/agama-dbus-server/src/locale.rs b/rust/agama-dbus-server/src/locale.rs
index 54e5bcb2cc..aabdfdc260 100644
--- a/rust/agama-dbus-server/src/locale.rs
+++ b/rust/agama-dbus-server/src/locale.rs
@@ -1,62 +1,52 @@
+pub mod helpers;
+mod keyboard;
+mod locale;
+mod timezone;
+
use crate::error::Error;
+use agama_locale_data::{KeymapId, LocaleCode};
use anyhow::Context;
-use std::{fs::read_dir, process::Command};
+use keyboard::KeymapsDatabase;
+use locale::LocalesDatabase;
+use std::process::Command;
+use timezone::TimezonesDatabase;
use zbus::{dbus_interface, Connection};
pub struct Locale {
+ timezone: String,
+ timezones_db: TimezonesDatabase,
locales: Vec,
- keymap: String,
- timezone_id: String,
- supported_locales: Vec,
- ui_locale: String,
+ locales_db: LocalesDatabase,
+ keymap: KeymapId,
+ keymaps_db: KeymapsDatabase,
+ ui_locale: LocaleCode,
}
#[dbus_interface(name = "org.opensuse.Agama1.Locale")]
impl Locale {
- /// Get labels for locales. The first pair is english language and territory
- /// and second one is localized one to target language from locale.
+ /// Gets the supported locales information.
///
- // Can be `async` as well.
- // NOTE: check how often it is used and if often, it can be easily cached
- fn labels_for_locales(&self) -> Result, Error> {
- const DEFAULT_LANG: &str = "en";
- let mut res = Vec::with_capacity(self.supported_locales.len());
- let languages = agama_locale_data::get_languages()?;
- let territories = agama_locale_data::get_territories()?;
- for locale in self.supported_locales.as_slice() {
- let (loc_language, loc_territory) = agama_locale_data::parse_locale(locale.as_str())?;
-
- let language = languages
- .find_by_id(loc_language)
- .context("language for passed locale not found")?;
- let territory = territories
- .find_by_id(loc_territory)
- .context("territory for passed locale not found")?;
-
- let default_ret = (
- language
- .names
- .name_for(DEFAULT_LANG)
- .context("missing default translation for language")?,
- territory
- .names
- .name_for(DEFAULT_LANG)
- .context("missing default translation for territory")?,
- );
- let localized_ret = (
- language
- .names
- .name_for(language.id.as_str())
- .context("missing native label for language")?,
- territory
- .names
- .name_for(language.id.as_str())
- .context("missing native label for territory")?,
- );
- res.push((default_ret, localized_ret));
- }
-
- Ok(res)
+ /// Each element of the list has these parts:
+ ///
+ /// * The locale code (e.g., "es_ES.UTF-8").
+ /// * The name of the language according to the language defined by the
+ /// UILocale property.
+ /// * The name of the territory according to the language defined by the
+ /// UILocale property.
+ fn list_locales(&self) -> Result, Error> {
+ let locales = self
+ .locales_db
+ .entries()
+ .iter()
+ .map(|l| {
+ (
+ l.code.to_string(),
+ l.language.to_string(),
+ l.territory.to_string(),
+ )
+ })
+ .collect::>();
+ Ok(locales)
}
#[dbus_interface(property)]
@@ -67,7 +57,7 @@ impl Locale {
#[dbus_interface(property)]
fn set_locales(&mut self, locales: Vec) -> zbus::fdo::Result<()> {
for loc in &locales {
- if !self.supported_locales.contains(loc) {
+ if !self.locales_db.exists(loc.as_str()) {
return Err(zbus::fdo::Error::Failed(format!(
"Unsupported locale value '{loc}'"
)));
@@ -77,119 +67,85 @@ impl Locale {
Ok(())
}
- #[dbus_interface(property)]
- fn supported_locales(&self) -> Vec {
- self.supported_locales.to_owned()
- }
-
- #[dbus_interface(property)]
- fn set_supported_locales(&mut self, locales: Vec) -> Result<(), zbus::fdo::Error> {
- self.supported_locales = locales;
- // TODO: handle if current selected locale contain something that is no longer supported
- Ok(())
- }
-
#[dbus_interface(property, name = "UILocale")]
- fn ui_locale(&self) -> &str {
- &self.ui_locale
+ fn ui_locale(&self) -> String {
+ self.ui_locale.to_string()
}
#[dbus_interface(property, name = "UILocale")]
- fn set_ui_locale(&mut self, locale: &str) {
- self.ui_locale = locale.to_string();
+ fn set_ui_locale(&mut self, locale: &str) -> zbus::fdo::Result<()> {
+ let locale: LocaleCode = locale
+ .try_into()
+ .map_err(|_e| zbus::fdo::Error::Failed(format!("Invalid locale value '{locale}'")))?;
+ helpers::set_service_locale(&locale);
+ Ok(self.translate(&locale)?)
}
- /// Gets list of locales available on system.
+ /// Returns a list of the supported keymaps.
///
- /// # Examples
+ /// Each element of the list contains:
///
- /// ```
- /// use agama_dbus_server::locale::Locale;
- /// let locale = Locale::new();
- /// assert!(locale.list_ui_locales().unwrap().len() > 0);
- /// ```
- #[dbus_interface(name = "ListUILocales")]
- pub fn list_ui_locales(&self) -> Result, Error> {
- // english is always available ui localization
- let mut result = vec!["en".to_string()];
- const DIR: &str = "/usr/share/YaST2/locale/";
- let entries = read_dir(DIR);
- if entries.is_err() {
- // if dir is not there act like if it is empty
- return Ok(result);
- }
-
- for entry in entries.unwrap() {
- let entry = entry.context("Failed to read entry in YaST2 locale dir")?;
- let name = entry
- .file_name()
- .to_str()
- .context("Non valid UTF entry found in YaST2 locale dir")?
- .to_string();
- result.push(name)
- }
-
- Ok(result)
+ /// * The keymap identifier (e.g., "es" or "es(ast)").
+ /// * The name of the keyboard in language set by the UILocale property.
+ fn list_keymaps(&self) -> Result, Error> {
+ let keymaps = self
+ .keymaps_db
+ .entries()
+ .iter()
+ .map(|k| (k.id.to_string(), k.localized_description()))
+ .collect();
+ Ok(keymaps)
}
- /* support only keymaps for console for now
- fn list_x11_keyboards(&self) -> Result, Error> {
- let keyboards = agama_locale_data::get_xkeyboards()?;
- let ret = keyboards
- .keyboard.iter()
- .map(|k| (k.id.clone(), k.description.clone()))
- .collect();
- Ok(ret)
- }
-
- fn set_x11_keyboard(&mut self, keyboard: &str) {
- self.keyboard_id = keyboard.to_string();
- }
- */
-
- #[dbus_interface(name = "ListVConsoleKeyboards")]
- fn list_keyboards(&self) -> Result, Error> {
- let res = agama_locale_data::get_key_maps()?;
- Ok(res)
+ #[dbus_interface(property)]
+ fn keymap(&self) -> String {
+ self.keymap.to_string()
}
- #[dbus_interface(property, name = "VConsoleKeyboard")]
- fn keymap(&self) -> &str {
- self.keymap.as_str()
- }
+ #[dbus_interface(property)]
+ fn set_keymap(&mut self, keymap_id: &str) -> Result<(), zbus::fdo::Error> {
+ let keymap_id: KeymapId = keymap_id
+ .parse()
+ .map_err(|_e| zbus::fdo::Error::InvalidArgs("Invalid keymap".to_string()))?;
- #[dbus_interface(property, name = "VConsoleKeyboard")]
- fn set_keymap(&mut self, keyboard: &str) -> Result<(), zbus::fdo::Error> {
- let exist = agama_locale_data::get_key_maps()
- .unwrap()
- .iter()
- .any(|k| k == keyboard);
- if !exist {
- return Err(zbus::fdo::Error::Failed(
- "Invalid keyboard value".to_string(),
- ));
+ if !self.keymaps_db.exists(&keymap_id) {
+ return Err(zbus::fdo::Error::Failed("Invalid keymap value".to_string()));
}
- self.keymap = keyboard.to_string();
+ self.keymap = keymap_id;
Ok(())
}
- fn list_timezones(&self, locale: &str) -> Result, Error> {
- let timezones = agama_locale_data::get_timezones();
- let localized =
- agama_locale_data::get_timezone_parts()?.localize_timezones(locale, &timezones);
- let ret = timezones.into_iter().zip(localized.into_iter()).collect();
- Ok(ret)
+ /// Returns a list of the supported timezones.
+ ///
+ /// Each element of the list contains:
+ ///
+ /// * The timezone identifier (e.g., "Europe/Berlin").
+ /// * A list containing each part of the name in the language set by the
+ /// UILocale property.
+ fn list_timezones(&self) -> Result)>, Error> {
+ let timezones: Vec<_> = self
+ .timezones_db
+ .entries()
+ .iter()
+ .map(|tz| (tz.code.to_string(), tz.parts.clone()))
+ .collect();
+ Ok(timezones)
}
#[dbus_interface(property)]
fn timezone(&self) -> &str {
- self.timezone_id.as_str()
+ self.timezone.as_str()
}
#[dbus_interface(property)]
fn set_timezone(&mut self, timezone: &str) -> Result<(), zbus::fdo::Error> {
- // NOTE: cannot use crate::Error as property expect this one
- self.timezone_id = timezone.to_string();
+ let timezone = timezone.to_string();
+ if !self.timezones_db.exists(&timezone) {
+ return Err(zbus::fdo::Error::Failed(format!(
+ "Unsupported timezone value '{timezone}'"
+ )));
+ }
+ self.timezone = timezone;
Ok(())
}
@@ -198,52 +154,78 @@ impl Locale {
const ROOT: &str = "/mnt";
Command::new("/usr/bin/systemd-firstboot")
.args([
- "root",
+ "--root",
ROOT,
+ "--force",
"--locale",
self.locales.first().context("missing locale")?.as_str(),
+ "--keymap",
+ &self.keymap.to_string(),
+ "--timezone",
+ &self.timezone,
])
.status()
.context("Failed to execute systemd-firstboot")?;
- Command::new("/usr/bin/systemd-firstboot")
- .args(["root", ROOT, "--keymap", self.keymap.as_str()])
- .status()
- .context("Failed to execute systemd-firstboot")?;
- Command::new("/usr/bin/systemd-firstboot")
- .args(["root", ROOT, "--timezone", self.timezone_id.as_str()])
- .status()
- .context("Failed to execute systemd-firstboot")?;
Ok(())
}
}
impl Locale {
- pub fn new() -> Self {
- Self {
- locales: vec!["en_US.UTF-8".to_string()],
- keymap: "us".to_string(),
- timezone_id: "America/Los_Angeles".to_string(),
- supported_locales: vec!["en_US.UTF-8".to_string()],
- ui_locale: "en".to_string(),
- }
- }
-}
-
-impl Default for Locale {
- fn default() -> Self {
- Self::new()
+ pub fn new_with_locale(ui_locale: &LocaleCode) -> Result {
+ const DEFAULT_TIMEZONE: &str = "Europe/Berlin";
+
+ let locale = ui_locale.to_string();
+ let mut locales_db = LocalesDatabase::new();
+ locales_db.read(&locale)?;
+
+ let default_locale = if locales_db.exists(locale.as_str()) {
+ ui_locale.to_string()
+ } else {
+ // TODO: handle the case where the database is empty (not expected!)
+ locales_db.entries().get(0).unwrap().code.to_string()
+ };
+
+ let mut timezones_db = TimezonesDatabase::new();
+ timezones_db.read(&locale)?;
+ let mut default_timezone = DEFAULT_TIMEZONE.to_string();
+ if !timezones_db.exists(&default_timezone) {
+ default_timezone = timezones_db.entries().get(0).unwrap().code.to_string();
+ };
+
+ let mut keymaps_db = KeymapsDatabase::new();
+ keymaps_db.read()?;
+
+ let locale = Self {
+ keymap: "us".parse().unwrap(),
+ timezone: default_timezone,
+ locales: vec![default_locale],
+ locales_db,
+ timezones_db,
+ keymaps_db,
+ ui_locale: ui_locale.clone(),
+ };
+
+ Ok(locale)
+ }
+
+ pub fn translate(&mut self, locale: &LocaleCode) -> Result<(), Error> {
+ self.timezones_db.read(&locale.language)?;
+ self.locales_db.read(&locale.language)?;
+ self.ui_locale = locale.clone();
+ Ok(())
}
}
pub async fn export_dbus_objects(
connection: &Connection,
+ locale: &LocaleCode,
) -> Result<(), Box> {
const PATH: &str = "/org/opensuse/Agama1/Locale";
// When serving, request the service name _after_ exposing the main object
- let locale = Locale::new();
- connection.object_server().at(PATH, locale).await?;
+ let locale_iface = Locale::new_with_locale(locale)?;
+ connection.object_server().at(PATH, locale_iface).await?;
Ok(())
}
diff --git a/rust/agama-dbus-server/src/locale/helpers.rs b/rust/agama-dbus-server/src/locale/helpers.rs
new file mode 100644
index 0000000000..d9e65b6de7
--- /dev/null
+++ b/rust/agama-dbus-server/src/locale/helpers.rs
@@ -0,0 +1,30 @@
+//! Helpers functions
+//!
+//! FIXME: find a better place for the localization function
+
+use agama_locale_data::LocaleCode;
+use gettextrs::{bind_textdomain_codeset, setlocale, textdomain, LocaleCategory};
+use std::env;
+
+/// Initializes the service locale.
+///
+/// It returns the used locale. Defaults to `en_US.UTF-8`.
+pub fn init_locale() -> Result> {
+ let lang = env::var("LANG").unwrap_or("en_US.UTF-8".to_string());
+ let locale: LocaleCode = lang.as_str().try_into().unwrap_or_default();
+
+ set_service_locale(&locale);
+ textdomain("xkeyboard-config")?;
+ bind_textdomain_codeset("xkeyboard-config", "UTF-8")?;
+ Ok(locale)
+}
+
+/// Sets the service locale.
+///
+pub fn set_service_locale(locale: &LocaleCode) {
+ // Let's force the encoding to be 'UTF-8'.
+ let locale = format!("{}.UTF-8", locale.to_string());
+ if setlocale(LocaleCategory::LcAll, locale).is_none() {
+ log::warn!("Could not set the locale");
+ }
+}
diff --git a/rust/agama-dbus-server/src/locale/keyboard.rs b/rust/agama-dbus-server/src/locale/keyboard.rs
new file mode 100644
index 0000000000..8a286bf4f6
--- /dev/null
+++ b/rust/agama-dbus-server/src/locale/keyboard.rs
@@ -0,0 +1,92 @@
+use agama_locale_data::{get_localectl_keymaps, keyboard::XkbConfigRegistry, KeymapId};
+use gettextrs::*;
+use std::collections::HashMap;
+
+// Minimal representation of a keymap
+pub struct Keymap {
+ pub id: KeymapId,
+ description: String,
+}
+
+impl Keymap {
+ pub fn new(id: KeymapId, description: &str) -> Self {
+ Self {
+ id,
+ description: description.to_string(),
+ }
+ }
+
+ pub fn localized_description(&self) -> String {
+ gettext(&self.description)
+ }
+}
+
+/// Represents the keymaps database.
+///
+/// The list of supported keymaps is read from `systemd-localed` and the
+/// descriptions from the X Keyboard Configuraiton Database (see
+/// `agama_locale_data::XkbConfigRegistry`).
+#[derive(Default)]
+pub struct KeymapsDatabase {
+ keymaps: Vec,
+}
+
+impl KeymapsDatabase {
+ pub fn new() -> Self {
+ Self::default()
+ }
+
+ /// Reads the list of keymaps.
+ pub fn read(&mut self) -> anyhow::Result<()> {
+ self.keymaps = get_keymaps()?;
+ Ok(())
+ }
+
+ pub fn exists(&self, id: &KeymapId) -> bool {
+ self.keymaps.iter().any(|k| &k.id == id)
+ }
+
+ /// Returns the list of keymaps.
+ pub fn entries(&self) -> &Vec {
+ &self.keymaps
+ }
+}
+
+/// Returns the list of keymaps to offer.
+///
+/// It only includes the keyboards supported by `localectl` but getting
+/// the description from the X Keyboard Configuration Database.
+fn get_keymaps() -> anyhow::Result> {
+ let mut keymaps: Vec = vec![];
+ let xkb_descriptions = get_keymap_descriptions();
+ let keymap_ids = get_localectl_keymaps()?;
+ for keymap_id in keymap_ids {
+ let keymap_id_str = keymap_id.to_string();
+ if let Some(description) = xkb_descriptions.get(&keymap_id_str) {
+ keymaps.push(Keymap::new(keymap_id, description));
+ } else {
+ log::debug!("Keyboard '{}' not found in xkb database", keymap_id_str);
+ }
+ }
+
+ Ok(keymaps)
+}
+
+/// Returns a map of keymaps ids and its descriptions from the X Keyboard
+/// Configuration Database.
+fn get_keymap_descriptions() -> HashMap {
+ let layouts = XkbConfigRegistry::from_system().unwrap();
+ let mut keymaps = HashMap::new();
+
+ for layout in layouts.layout_list.layouts {
+ let name = layout.config_item.name;
+ keymaps.insert(name.to_string(), layout.config_item.description.to_string());
+
+ for variant in layout.variants_list.variants {
+ let id = format!("{}({})", &name, &variant.config_item.name);
+ keymaps.insert(id, variant.config_item.description);
+ }
+ }
+
+ keymaps
+}
diff --git a/rust/agama-dbus-server/src/locale/locale.rs b/rust/agama-dbus-server/src/locale/locale.rs
new file mode 100644
index 0000000000..88a6b4c95e
--- /dev/null
+++ b/rust/agama-dbus-server/src/locale/locale.rs
@@ -0,0 +1,139 @@
+//! This module provides support for reading the locales database.
+
+use crate::error::Error;
+use agama_locale_data::{InvalidLocaleCode, LocaleCode};
+use anyhow::Context;
+use std::process::Command;
+
+/// Represents a locale, including the localized language and territory.
+#[derive(Debug)]
+pub struct LocaleEntry {
+ /// The locale code (e.g., "es_ES.UTF-8").
+ pub code: LocaleCode,
+ /// Localized language name (e.g., "Spanish", "Español", etc.)
+ pub language: String,
+ /// Localized territory name (e.g., "Spain", "España", etc.)
+ pub territory: String,
+}
+
+/// Represents the locales database.
+///
+/// The list of supported locales is read from `systemd-localed`. However, the
+/// translations are obtained from the `agama_locale_data` crate.
+#[derive(Default)]
+pub struct LocalesDatabase {
+ known_locales: Vec,
+ locales: Vec,
+}
+
+impl LocalesDatabase {
+ pub fn new() -> Self {
+ Self::default()
+ }
+
+ /// Loads the list of locales.
+ ///
+ /// * `ui_language`: language to translate the descriptions (e.g., "en").
+ pub fn read(&mut self, ui_language: &str) -> Result<(), Error> {
+ let result = Command::new("/usr/bin/localectl")
+ .args(["list-locales"])
+ .output()
+ .context("Failed to get the list of locales")?;
+ let output =
+ String::from_utf8(result.stdout).context("Invalid UTF-8 sequence from list-locales")?;
+ self.known_locales = output
+ .lines()
+ .filter_map(|line| TryInto::::try_into(line).ok())
+ .collect();
+ self.locales = self.get_locales(&ui_language)?;
+ Ok(())
+ }
+
+ /// Determines whether a locale exists in the database.
+ pub fn exists(&self, locale: T) -> bool
+ where
+ T: TryInto,
+ T::Error: Into,
+ {
+ if let Ok(locale) = TryInto::::try_into(locale) {
+ return self.known_locales.contains(&locale);
+ }
+
+ false
+ }
+
+ /// Returns the list of locales.
+ pub fn entries(&self) -> &Vec {
+ &self.locales
+ }
+
+ /// Gets the supported locales information.
+ ///
+ /// * `ui_language`: language to use in the translations.
+ fn get_locales(&self, ui_language: &str) -> Result, Error> {
+ const DEFAULT_LANG: &str = "en";
+ let mut result = Vec::with_capacity(self.known_locales.len());
+ let languages = agama_locale_data::get_languages()?;
+ let territories = agama_locale_data::get_territories()?;
+ for code in self.known_locales.as_slice() {
+ let language = languages
+ .find_by_id(&code.language)
+ .context("language not found")?;
+
+ let names = &language.names;
+ let language_label = names
+ .name_for(&ui_language)
+ .or_else(|| names.name_for(DEFAULT_LANG))
+ .unwrap_or(language.id.to_string());
+
+ let territory = territories
+ .find_by_id(&code.territory)
+ .context("territory not found")?;
+
+ let names = &territory.names;
+ let territory_label = names
+ .name_for(&ui_language)
+ .or_else(|| names.name_for(DEFAULT_LANG))
+ .unwrap_or(territory.id.to_string());
+
+ let entry = LocaleEntry {
+ code: code.clone(),
+ language: language_label,
+ territory: territory_label,
+ };
+ result.push(entry)
+ }
+
+ Ok(result)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::LocalesDatabase;
+ use agama_locale_data::LocaleCode;
+
+ #[ignore]
+ #[test]
+ fn test_read_locales() {
+ let mut db = LocalesDatabase::new();
+ db.read("de").unwrap();
+ let found_locales = db.entries();
+ let spanish: LocaleCode = "es_ES".try_into().unwrap();
+ let found = found_locales
+ .into_iter()
+ .find(|l| l.code == spanish)
+ .unwrap();
+ assert_eq!(&found.language, "Spanisch");
+ assert_eq!(&found.territory, "Spanien");
+ }
+
+ #[ignore]
+ #[test]
+ fn test_locale_exists() {
+ let mut db = LocalesDatabase::new();
+ db.read("en").unwrap();
+ assert!(db.exists("en_US"));
+ assert!(!db.exists("unknown_UNKNOWN"));
+ }
+}
diff --git a/rust/agama-dbus-server/src/locale/timezone.rs b/rust/agama-dbus-server/src/locale/timezone.rs
new file mode 100644
index 0000000000..8b60197a45
--- /dev/null
+++ b/rust/agama-dbus-server/src/locale/timezone.rs
@@ -0,0 +1,102 @@
+//! This module provides support for reading the timezones database.
+
+use crate::error::Error;
+use agama_locale_data::timezone_part::TimezoneIdParts;
+
+/// Represents a timezone, including each part as localized.
+#[derive(Debug)]
+pub struct TimezoneEntry {
+ /// Timezone identifier (e.g. "Atlantic/Canary").
+ pub code: String,
+ /// Localized parts (e.g., "Atlántico", "Canarias").
+ pub parts: Vec,
+}
+
+#[derive(Default)]
+pub struct TimezonesDatabase {
+ timezones: Vec,
+}
+
+impl TimezonesDatabase {
+ pub fn new() -> Self {
+ Self::default()
+ }
+
+ /// Initializes the list of known timezones.
+ ///
+ /// * `ui_language`: language to translate the descriptions (e.g., "en").
+ pub fn read(&mut self, ui_language: &str) -> Result<(), Error> {
+ self.timezones = self.get_timezones(ui_language)?;
+ Ok(())
+ }
+
+ /// Determines whether a timezone exists in the database.
+ pub fn exists(&self, timezone: &String) -> bool {
+ self.timezones.iter().any(|t| &t.code == timezone)
+ }
+
+ /// Returns the list of timezones.
+ pub fn entries(&self) -> &Vec {
+ &self.timezones
+ }
+
+ /// Returns a list of the supported timezones.
+ ///
+ /// Each element of the list contains a timezone identifier and a vector
+ /// containing the translation of each part of the language.
+ ///
+ /// * `ui_language`: language to translate the descriptions (e.g., "en").
+ fn get_timezones(&self, ui_language: &str) -> Result, Error> {
+ let timezones = agama_locale_data::get_timezones();
+ let tz_parts = agama_locale_data::get_timezone_parts()?;
+ let ret = timezones
+ .into_iter()
+ .map(|tz| {
+ let parts = translate_parts(&tz, &ui_language, &tz_parts);
+ TimezoneEntry { code: tz, parts }
+ })
+ .collect();
+ Ok(ret)
+ }
+}
+
+fn translate_parts(timezone: &str, ui_language: &str, tz_parts: &TimezoneIdParts) -> Vec {
+ timezone
+ .split("/")
+ .map(|part| {
+ tz_parts
+ .localize_part(part, &ui_language)
+ .unwrap_or(part.to_owned())
+ })
+ .collect()
+}
+
+#[cfg(test)]
+mod tests {
+ use super::TimezonesDatabase;
+
+ #[test]
+ fn test_read_timezones() {
+ let mut db = TimezonesDatabase::new();
+ db.read("es").unwrap();
+ let found_timezones = db.entries();
+ dbg!(&found_timezones);
+ let found = found_timezones
+ .into_iter()
+ .find(|tz| tz.code == "Europe/Berlin")
+ .unwrap();
+ assert_eq!(&found.code, "Europe/Berlin");
+ assert_eq!(
+ found.parts,
+ vec!["Europa".to_string(), "Berlín".to_string()]
+ )
+ }
+
+ #[test]
+ fn test_timezone_exists() {
+ let mut db = TimezonesDatabase::new();
+ db.read("es").unwrap();
+ assert!(db.exists(&"Atlantic/Canary".to_string()));
+ assert!(!db.exists(&"Unknown/Unknown".to_string()));
+ }
+}
diff --git a/rust/agama-dbus-server/src/main.rs b/rust/agama-dbus-server/src/main.rs
index cc849d0c1b..33c0c41db8 100644
--- a/rust/agama-dbus-server/src/main.rs
+++ b/rust/agama-dbus-server/src/main.rs
@@ -1,8 +1,8 @@
-use agama_dbus_server::{locale, network, questions};
+use agama_dbus_server::{locale, locale::helpers, network, questions};
use agama_lib::connection_to;
use anyhow::Context;
-use log::LevelFilter;
+use log::{self, LevelFilter};
use std::future::pending;
use tokio;
@@ -11,6 +11,8 @@ const SERVICE_NAME: &str = "org.opensuse.Agama1";
#[tokio::main]
async fn main() -> Result<(), Box> {
+ let locale = helpers::init_locale()?;
+
// be smart with logging and log directly to journal if connected to it
if systemd_journal_logger::connected_to_journal() {
// unwrap here is intentional as we are sure no other logger is active yet
@@ -35,7 +37,7 @@ async fn main() -> Result<(), Box> {
// When adding more services here, the order might be important.
questions::export_dbus_objects(&connection).await?;
log::info!("Started questions interface");
- locale::export_dbus_objects(&connection).await?;
+ locale::export_dbus_objects(&connection, &locale).await?;
log::info!("Started locale interface");
network::export_dbus_objects(&connection).await?;
log::info!("Started network interface");
diff --git a/rust/agama-dbus-server/src/network/dbus/interfaces.rs b/rust/agama-dbus-server/src/network/dbus/interfaces.rs
index 682dbbfe10..23ebdbb784 100644
--- a/rust/agama-dbus-server/src/network/dbus/interfaces.rs
+++ b/rust/agama-dbus-server/src/network/dbus/interfaces.rs
@@ -6,11 +6,13 @@ use super::ObjectsRegistry;
use crate::network::{
action::Action,
error::NetworkStateError,
- model::{Connection as NetworkConnection, Device as NetworkDevice, WirelessConnection},
+ model::{
+ Connection as NetworkConnection, Device as NetworkDevice, MacAddress, WirelessConnection,
+ },
};
use agama_lib::network::types::SSID;
-use std::sync::Arc;
+use std::{str::FromStr, sync::Arc};
use tokio::sync::mpsc::UnboundedSender;
use tokio::sync::{MappedMutexGuard, Mutex, MutexGuard};
use zbus::{
@@ -238,6 +240,19 @@ impl Connection {
connection.set_interface(name);
self.update_connection(connection).await
}
+
+ /// Custom mac-address
+ #[dbus_interface(property)]
+ pub async fn mac_address(&self) -> String {
+ self.get_connection().await.mac_address()
+ }
+
+ #[dbus_interface(property)]
+ pub async fn set_mac_address(&mut self, mac_address: &str) -> zbus::fdo::Result<()> {
+ let mut connection = self.get_connection().await;
+ connection.set_mac_address(MacAddress::from_str(mac_address)?);
+ self.update_connection(connection).await
+ }
}
/// D-Bus interface for Match settings
diff --git a/rust/agama-dbus-server/src/network/model.rs b/rust/agama-dbus-server/src/network/model.rs
index 88a5416180..b413284999 100644
--- a/rust/agama-dbus-server/src/network/model.rs
+++ b/rust/agama-dbus-server/src/network/model.rs
@@ -221,6 +221,7 @@ pub enum Connection {
Ethernet(EthernetConnection),
Wireless(WirelessConnection),
Loopback(LoopbackConnection),
+ Dummy(DummyConnection),
}
impl Connection {
@@ -236,6 +237,7 @@ impl Connection {
}),
DeviceType::Loopback => Connection::Loopback(LoopbackConnection { base }),
DeviceType::Ethernet => Connection::Ethernet(EthernetConnection { base }),
+ DeviceType::Dummy => Connection::Dummy(DummyConnection { base }),
}
}
@@ -246,6 +248,7 @@ impl Connection {
Connection::Ethernet(conn) => &conn.base,
Connection::Wireless(conn) => &conn.base,
Connection::Loopback(conn) => &conn.base,
+ Connection::Dummy(conn) => &conn.base,
}
}
@@ -254,6 +257,7 @@ impl Connection {
Connection::Ethernet(conn) => &mut conn.base,
Connection::Wireless(conn) => &mut conn.base,
Connection::Loopback(conn) => &mut conn.base,
+ Connection::Dummy(conn) => &mut conn.base,
}
}
@@ -306,12 +310,25 @@ impl Connection {
pub fn is_loopback(&self) -> bool {
matches!(self, Connection::Loopback(_))
}
+
+ pub fn is_ethernet(&self) -> bool {
+ matches!(self, Connection::Loopback(_)) || matches!(self, Connection::Ethernet(_))
+ }
+
+ pub fn mac_address(&self) -> String {
+ self.base().mac_address.to_string()
+ }
+
+ pub fn set_mac_address(&mut self, mac_address: MacAddress) {
+ self.base_mut().mac_address = mac_address;
+ }
}
#[derive(Debug, Default, Clone)]
pub struct BaseConnection {
pub id: String,
pub uuid: Uuid,
+ pub mac_address: MacAddress,
pub ip_config: IpConfig,
pub status: Status,
pub interface: String,
@@ -324,6 +341,59 @@ impl PartialEq for BaseConnection {
}
}
+#[derive(Debug, Error)]
+#[error("Invalid MAC address: {0}")]
+pub struct InvalidMacAddress(String);
+
+#[derive(Debug, Default, Clone)]
+pub enum MacAddress {
+ MacAddress(macaddr::MacAddr6),
+ Preserve,
+ Permanent,
+ Random,
+ Stable,
+ #[default]
+ Unset,
+}
+
+impl FromStr for MacAddress {
+ type Err = InvalidMacAddress;
+
+ fn from_str(s: &str) -> Result {
+ match s {
+ "preserve" => Ok(Self::Preserve),
+ "permanent" => Ok(Self::Permanent),
+ "random" => Ok(Self::Random),
+ "stable" => Ok(Self::Stable),
+ "" => Ok(Self::Unset),
+ _ => Ok(Self::MacAddress(match macaddr::MacAddr6::from_str(s) {
+ Ok(mac) => mac,
+ Err(e) => return Err(InvalidMacAddress(e.to_string())),
+ })),
+ }
+ }
+}
+
+impl fmt::Display for MacAddress {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ let output = match &self {
+ Self::MacAddress(mac) => mac.to_string(),
+ Self::Preserve => "preserve".to_string(),
+ Self::Permanent => "permanent".to_string(),
+ Self::Random => "random".to_string(),
+ Self::Stable => "stable".to_string(),
+ Self::Unset => "".to_string(),
+ };
+ write!(f, "{}", output)
+ }
+}
+
+impl From for zbus::fdo::Error {
+ fn from(value: InvalidMacAddress) -> Self {
+ zbus::fdo::Error::Failed(value.to_string())
+ }
+}
+
#[derive(Debug, Default, Clone, Copy, PartialEq)]
pub enum Status {
#[default]
@@ -479,6 +549,11 @@ pub struct LoopbackConnection {
pub base: BaseConnection,
}
+#[derive(Debug, Default, PartialEq, Clone)]
+pub struct DummyConnection {
+ pub base: BaseConnection,
+}
+
#[derive(Debug, Default, PartialEq, Clone)]
pub struct WirelessConfig {
pub mode: WirelessMode,
diff --git a/rust/agama-dbus-server/src/network/nm/dbus.rs b/rust/agama-dbus-server/src/network/nm/dbus.rs
index d601291e9f..11cc97c19a 100644
--- a/rust/agama-dbus-server/src/network/nm/dbus.rs
+++ b/rust/agama-dbus-server/src/network/nm/dbus.rs
@@ -17,6 +17,7 @@ const ETHERNET_KEY: &str = "802-3-ethernet";
const WIRELESS_KEY: &str = "802-11-wireless";
const WIRELESS_SECURITY_KEY: &str = "802-11-wireless-security";
const LOOPBACK_KEY: &str = "loopback";
+const DUMMY_KEY: &str = "dummy";
/// Converts a connection struct into a HashMap that can be sent over D-Bus.
///
@@ -32,14 +33,22 @@ pub fn connection_to_dbus(conn: &Connection) -> NestedHash {
result.insert("ipv6", ip_config_to_ipv6_dbus(conn.ip_config()));
result.insert("match", match_config_to_dbus(conn.match_config()));
- if let Connection::Wireless(wireless) = conn {
- connection_dbus.insert("type", "802-11-wireless".into());
+ if conn.is_ethernet() {
+ let ethernet_config =
+ HashMap::from([("assigned-mac-address", Value::new(conn.mac_address()))]);
+ result.insert(ETHERNET_KEY, ethernet_config);
+ } else if let Connection::Wireless(wireless) = conn {
+ connection_dbus.insert("type", WIRELESS_KEY.into());
let wireless_dbus = wireless_config_to_dbus(wireless);
for (k, v) in wireless_dbus {
result.insert(k, v);
}
}
+ if let Connection::Dummy(_) = conn {
+ connection_dbus.insert("type", DUMMY_KEY.into());
+ }
+
result.insert("connection", connection_dbus);
result
}
@@ -57,6 +66,10 @@ pub fn connection_from_dbus(conn: OwnedNestedHash) -> Option {
}));
}
+ if conn.get(DUMMY_KEY).is_some() {
+ return Some(Connection::Dummy(DummyConnection { base }));
+ };
+
if conn.get(LOOPBACK_KEY).is_some() {
return Some(Connection::Loopback(LoopbackConnection { base }));
};
@@ -218,6 +231,10 @@ fn wireless_config_to_dbus(conn: &WirelessConnection) -> NestedHash {
let wireless: HashMap<&str, zvariant::Value> = HashMap::from([
("mode", Value::new(config.mode.to_string())),
("ssid", Value::new(config.ssid.to_vec())),
+ (
+ "assigned-mac-address",
+ Value::new(conn.base.mac_address.to_string()),
+ ),
]);
let mut security: HashMap<&str, zvariant::Value> =
@@ -291,11 +308,31 @@ fn base_connection_from_dbus(conn: &OwnedNestedHash) -> Option {
base_connection.match_config = match_config_from_dbus(match_config)?;
}
+ if let Some(ethernet_config) = conn.get(ETHERNET_KEY) {
+ base_connection.mac_address = mac_address_from_dbus(ethernet_config)?;
+ } else if let Some(wireless_config) = conn.get(WIRELESS_KEY) {
+ base_connection.mac_address = mac_address_from_dbus(wireless_config)?;
+ }
+
base_connection.ip_config = ip_config_from_dbus(&conn)?;
Some(base_connection)
}
+fn mac_address_from_dbus(config: &HashMap) -> Option {
+ if let Some(mac_address) = config.get("assigned-mac-address") {
+ match MacAddress::from_str(mac_address.downcast_ref::()?) {
+ Ok(mac) => Some(mac),
+ Err(e) => {
+ log::warn!("Couldn't parse MAC: {}", e);
+ None
+ }
+ }
+ } else {
+ Some(MacAddress::Unset)
+ }
+}
+
fn match_config_from_dbus(
match_config: &HashMap,
) -> Option {
@@ -585,6 +622,8 @@ mod test {
let match_config = connection.match_config();
assert_eq!(match_config.kernel, vec!["pci-0000:00:19.0"]);
+ assert_eq!(connection.mac_address(), "12:34:56:78:9A:BC");
+
assert_eq!(
ip_config.addresses,
vec![
@@ -640,6 +679,10 @@ mod test {
"ssid".to_string(),
Value::new("agama".as_bytes()).to_owned(),
),
+ (
+ "assigned-mac-address".to_string(),
+ Value::new("13:45:67:89:AB:CD").to_owned(),
+ ),
]);
let security_section =
@@ -652,6 +695,7 @@ mod test {
]);
let connection = connection_from_dbus(dbus_conn).unwrap();
+ assert_eq!(connection.mac_address(), "13:45:67:89:AB:CD".to_string());
assert!(matches!(connection, Connection::Wireless(_)));
if let Connection::Wireless(connection) = connection {
assert_eq!(connection.wireless.ssid, SSID(vec![97, 103, 97, 109, 97]));
@@ -679,6 +723,12 @@ mod test {
let wireless = wireless_dbus.get("802-11-wireless").unwrap();
let mode: &str = wireless.get("mode").unwrap().downcast_ref().unwrap();
assert_eq!(mode, "infrastructure");
+ let mac_address: &str = wireless
+ .get("assigned-mac-address")
+ .unwrap()
+ .downcast_ref()
+ .unwrap();
+ assert_eq!(mac_address, "FD:CB:A9:87:65:43");
let ssid: &zvariant::Array = wireless.get("ssid").unwrap().downcast_ref().unwrap();
let ssid: Vec = ssid
@@ -804,19 +854,33 @@ mod test {
Value::new("eth0".to_string()).to_owned(),
),
]);
+ let ethernet = HashMap::from([(
+ "assigned-mac-address".to_string(),
+ Value::new("12:34:56:78:9A:BC".to_string()).to_owned(),
+ )]);
original.insert("connection".to_string(), connection);
+ original.insert(ETHERNET_KEY.to_string(), ethernet);
let mut updated = Connection::Ethernet(EthernetConnection::default());
updated.set_interface("");
+ updated.set_mac_address(MacAddress::Unset);
let updated = connection_to_dbus(&updated);
let merged = merge_dbus_connections(&original, &updated);
let connection = merged.get("connection").unwrap();
assert_eq!(connection.get("interface-name"), None);
+ let ethernet = merged.get(ETHERNET_KEY).unwrap();
+ assert_eq!(ethernet.get("assigned-mac-address"), Some(&Value::from("")));
}
fn build_ethernet_section_from_dbus() -> HashMap {
- HashMap::from([("auto-negotiate".to_string(), true.into())])
+ HashMap::from([
+ ("auto-negotiate".to_string(), true.into()),
+ (
+ "assigned-mac-address".to_string(),
+ Value::new("12:34:56:78:9A:BC").to_owned(),
+ ),
+ ])
}
fn build_base_connection() -> BaseConnection {
@@ -840,9 +904,11 @@ mod test {
}]),
..Default::default()
};
+ let mac_address = MacAddress::from_str("FD:CB:A9:87:65:43").unwrap();
BaseConnection {
id: "agama".to_string(),
ip_config,
+ mac_address,
..Default::default()
}
}
@@ -859,6 +925,14 @@ mod test {
let id: &str = connection_dbus.get("id").unwrap().downcast_ref().unwrap();
assert_eq!(id, "agama");
+ let ethernet_connection = conn_dbus.get(ETHERNET_KEY).unwrap();
+ let mac_address: &str = ethernet_connection
+ .get("assigned-mac-address")
+ .unwrap()
+ .downcast_ref()
+ .unwrap();
+ assert_eq!(mac_address, "FD:CB:A9:87:65:43");
+
let ipv4_dbus = conn_dbus.get("ipv4").unwrap();
let gateway4: &str = ipv4_dbus.get("gateway").unwrap().downcast_ref().unwrap();
assert_eq!(gateway4, "192.168.0.1");
diff --git a/rust/agama-dbus-server/src/network/nm/model.rs b/rust/agama-dbus-server/src/network/nm/model.rs
index 93ce9f69c8..9175b8b342 100644
--- a/rust/agama-dbus-server/src/network/nm/model.rs
+++ b/rust/agama-dbus-server/src/network/nm/model.rs
@@ -83,6 +83,7 @@ impl TryFrom for DeviceType {
NmDeviceType(0) => Ok(DeviceType::Loopback),
NmDeviceType(1) => Ok(DeviceType::Ethernet),
NmDeviceType(2) => Ok(DeviceType::Wireless),
+ NmDeviceType(3) => Ok(DeviceType::Dummy),
NmDeviceType(_) => Err(NmError::UnsupportedDeviceType(value.into())),
}
}
diff --git a/rust/agama-dbus-server/tests/network.rs b/rust/agama-dbus-server/tests/network.rs
index 37d404210a..e4f8d4862e 100644
--- a/rust/agama-dbus-server/tests/network.rs
+++ b/rust/agama-dbus-server/tests/network.rs
@@ -62,6 +62,7 @@ async fn test_add_connection() -> Result<(), Box> {
let addresses: Vec = vec!["192.168.0.2/24".parse()?, "::ffff:c0a8:7ac7/64".parse()?];
let wlan0 = settings::NetworkConnection {
id: "wlan0".to_string(),
+ mac_address: Some("FD:CB:A9:87:65:43".to_string()),
method4: Some("auto".to_string()),
method6: Some("disabled".to_string()),
addresses: addresses.clone(),
@@ -80,6 +81,7 @@ async fn test_add_connection() -> Result<(), Box> {
let conn = conns.first().unwrap();
assert_eq!(conn.id, "wlan0");
+ assert_eq!(conn.mac_address, Some("FD:CB:A9:87:65:43".to_string()));
assert_eq!(conn.device_type(), DeviceType::Wireless);
assert_eq!(&conn.addresses, &addresses);
let method4 = conn.method4.as_ref().unwrap();
diff --git a/rust/agama-lib/share/profile.schema.json b/rust/agama-lib/share/profile.schema.json
index 2d9cb33b0a..97e2a29293 100644
--- a/rust/agama-lib/share/profile.schema.json
+++ b/rust/agama-lib/share/profile.schema.json
@@ -36,6 +36,10 @@
"description": "The name of the network interface bound to this connection",
"type": "string"
},
+ "mac-address": {
+ "description": "Custom mac-address (can also be 'preserve', 'permanent', 'random' or 'stable')",
+ "type": "string"
+ },
"method4": {
"description": "IPv4 configuration method (e.g., 'auto')",
"type": "string",
diff --git a/rust/agama-lib/src/network/client.rs b/rust/agama-lib/src/network/client.rs
index 485baee516..d8e3077216 100644
--- a/rust/agama-lib/src/network/client.rs
+++ b/rust/agama-lib/src/network/client.rs
@@ -67,6 +67,10 @@ impl<'a> NetworkClient<'a> {
"" => None,
value => Some(value.to_string()),
};
+ let mac_address = match connection_proxy.mac_address().await?.as_str() {
+ "" => None,
+ value => Some(value.to_string()),
+ };
let ip_proxy = IPProxy::builder(&self.connection)
.path(path)?
@@ -91,6 +95,7 @@ impl<'a> NetworkClient<'a> {
addresses,
nameservers,
interface,
+ mac_address,
..Default::default()
})
}
@@ -189,6 +194,9 @@ impl<'a> NetworkClient<'a> {
let interface = conn.interface.as_deref().unwrap_or("");
proxy.set_interface(interface).await?;
+ let mac_address = conn.mac_address.as_deref().unwrap_or("");
+ proxy.set_mac_address(mac_address).await?;
+
self.update_ip_settings(path, conn).await?;
if let Some(ref wireless) = conn.wireless {
diff --git a/rust/agama-lib/src/network/proxies.rs b/rust/agama-lib/src/network/proxies.rs
index 2b43124722..314e0c5b23 100644
--- a/rust/agama-lib/src/network/proxies.rs
+++ b/rust/agama-lib/src/network/proxies.rs
@@ -83,6 +83,10 @@ trait Connection {
fn interface(&self) -> zbus::Result;
#[dbus_proxy(property)]
fn set_interface(&self, interface: &str) -> zbus::Result<()>;
+ #[dbus_proxy(property)]
+ fn mac_address(&self) -> zbus::Result;
+ #[dbus_proxy(property)]
+ fn set_mac_address(&self, mac_address: &str) -> zbus::Result<()>;
}
#[dbus_proxy(
diff --git a/rust/agama-lib/src/network/settings.rs b/rust/agama-lib/src/network/settings.rs
index cd344ef323..34be2f4090 100644
--- a/rust/agama-lib/src/network/settings.rs
+++ b/rust/agama-lib/src/network/settings.rs
@@ -69,6 +69,8 @@ pub struct NetworkConnection {
pub interface: Option,
#[serde(skip_serializing_if = "Option::is_none")]
pub match_settings: Option,
+ #[serde(rename = "mac-address", skip_serializing_if = "Option::is_none")]
+ pub mac_address: Option,
}
impl NetworkConnection {
diff --git a/rust/agama-lib/src/network/types.rs b/rust/agama-lib/src/network/types.rs
index 5aa56c5145..10daa400c1 100644
--- a/rust/agama-lib/src/network/types.rs
+++ b/rust/agama-lib/src/network/types.rs
@@ -29,6 +29,7 @@ pub enum DeviceType {
Loopback = 0,
Ethernet = 1,
Wireless = 2,
+ Dummy = 3,
}
#[derive(Debug, Error, PartialEq)]
@@ -43,6 +44,7 @@ impl TryFrom for DeviceType {
0 => Ok(DeviceType::Loopback),
1 => Ok(DeviceType::Ethernet),
2 => Ok(DeviceType::Wireless),
+ 3 => Ok(DeviceType::Dummy),
_ => Err(InvalidDeviceType(value)),
}
}
diff --git a/rust/agama-lib/src/proxies.rs b/rust/agama-lib/src/proxies.rs
index cf4b175e93..e72cdf7401 100644
--- a/rust/agama-lib/src/proxies.rs
+++ b/rust/agama-lib/src/proxies.rs
@@ -75,40 +75,43 @@ trait Manager {
) -> zbus::Result>>;
}
-#[dbus_proxy(interface = "org.opensuse.Agama1.Locale", assume_defaults = true)]
+#[dbus_proxy(
+ interface = "org.opensuse.Agama1.Locale",
+ default_service = "org.opensuse.Agama1",
+ default_path = "/org/opensuse/Agama1/Locale"
+)]
trait Locale {
/// Commit method
fn commit(&self) -> zbus::Result<()>;
- /// LabelsForLocales method
- fn labels_for_locales(&self) -> zbus::Result>;
+ /// ListKeymaps method
+ fn list_keymaps(&self) -> zbus::Result>;
+
+ /// ListLocales method
+ fn list_locales(&self) -> zbus::Result>;
/// ListTimezones method
- fn list_timezones(&self, locale: &str) -> zbus::Result>;
+ fn list_timezones(&self) -> zbus::Result)>>;
- /// ListVConsoleKeyboards method
- #[dbus_proxy(name = "ListVConsoleKeyboards")]
- fn list_vconsole_keyboards(&self) -> zbus::Result>;
+ /// Keymap property
+ #[dbus_proxy(property)]
+ fn keymap(&self) -> zbus::Result;
+ fn set_keymap(&self, value: &str) -> zbus::Result<()>;
/// Locales property
#[dbus_proxy(property)]
fn locales(&self) -> zbus::Result>;
fn set_locales(&self, value: &[&str]) -> zbus::Result<()>;
- /// SupportedLocales property
- #[dbus_proxy(property)]
- fn supported_locales(&self) -> zbus::Result>;
- fn set_supported_locales(&self, value: &[&str]) -> zbus::Result<()>;
-
/// Timezone property
#[dbus_proxy(property)]
fn timezone(&self) -> zbus::Result;
fn set_timezone(&self, value: &str) -> zbus::Result<()>;
- /// VConsoleKeyboard property
- #[dbus_proxy(property, name = "VConsoleKeyboard")]
- fn vconsole_keyboard(&self) -> zbus::Result;
- fn set_vconsole_keyboard(&self, value: &str) -> zbus::Result<()>;
+ /// UILocale property
+ #[dbus_proxy(property, name = "UILocale")]
+ fn uilocale(&self) -> zbus::Result;
+ fn set_uilocale(&self, value: &str) -> zbus::Result<()>;
}
#[dbus_proxy(
diff --git a/rust/agama-locale-data/Cargo.toml b/rust/agama-locale-data/Cargo.toml
index 46380041c5..ca9eda6ba3 100644
--- a/rust/agama-locale-data/Cargo.toml
+++ b/rust/agama-locale-data/Cargo.toml
@@ -12,3 +12,4 @@ quick-xml = { version = "0.28.2", features = ["serialize"] }
flate2 = "1.0.25"
chrono-tz = "0.8.2"
regex = "1"
+thiserror = "1.0.50"
diff --git a/rust/agama-locale-data/src/keyboard.rs b/rust/agama-locale-data/src/keyboard.rs
new file mode 100644
index 0000000000..879217dd7d
--- /dev/null
+++ b/rust/agama-locale-data/src/keyboard.rs
@@ -0,0 +1,5 @@
+pub mod xkb_config_registry;
+pub mod xkeyboard;
+
+pub use xkb_config_registry::XkbConfigRegistry;
+pub use xkeyboard::XKeyboards;
diff --git a/rust/agama-locale-data/src/keyboard/xkb_config_registry.rs b/rust/agama-locale-data/src/keyboard/xkb_config_registry.rs
new file mode 100644
index 0000000000..a1bdd07cb6
--- /dev/null
+++ b/rust/agama-locale-data/src/keyboard/xkb_config_registry.rs
@@ -0,0 +1,70 @@
+//! This module aims to read the information in the X Keyboard Configuration Database.
+//!
+//! https://freedesktop.org/Software/XKeyboardConfig
+
+use quick_xml::de::from_str;
+use serde::Deserialize;
+use std::{error::Error, fs};
+
+const DB_PATH: &'static str = "/usr/share/X11/xkb/rules/base.xml";
+
+/// X Keyboard Configuration Database
+#[derive(Deserialize, Debug)]
+pub struct XkbConfigRegistry {
+ #[serde(rename = "layoutList")]
+ pub layout_list: LayoutList,
+}
+
+impl XkbConfigRegistry {
+ /// Reads the database from the given file
+ ///
+ /// - `path`: database path.
+ pub fn from(path: &str) -> Result> {
+ let contents = fs::read_to_string(&path)?;
+ Ok(from_str(&contents)?)
+ }
+
+ /// Reads the database from the default path.
+ pub fn from_system() -> Result> {
+ Self::from(DB_PATH)
+ }
+}
+
+#[derive(Deserialize, Debug)]
+pub struct LayoutList {
+ #[serde(rename = "layout")]
+ pub layouts: Vec,
+}
+
+#[derive(Deserialize, Debug)]
+pub struct Layout {
+ #[serde(rename = "configItem")]
+ pub config_item: ConfigItem,
+ #[serde(rename = "variantList", default)]
+ pub variants_list: VariantList,
+}
+
+#[derive(Deserialize, Debug)]
+pub struct ConfigItem {
+ pub name: String,
+ #[serde(rename = "description")]
+ pub description: String,
+}
+
+#[derive(Deserialize, Debug, Default)]
+pub struct VariantList {
+ #[serde(rename = "variant", default)]
+ pub variants: Vec,
+}
+
+#[derive(Deserialize, Debug)]
+pub struct Variant {
+ #[serde(rename = "configItem")]
+ pub config_item: VariantConfigItem,
+}
+
+#[derive(Deserialize, Debug)]
+pub struct VariantConfigItem {
+ pub name: String,
+ pub description: String,
+}
diff --git a/rust/agama-locale-data/src/xkeyboard.rs b/rust/agama-locale-data/src/keyboard/xkeyboard.rs
similarity index 100%
rename from rust/agama-locale-data/src/xkeyboard.rs
rename to rust/agama-locale-data/src/keyboard/xkeyboard.rs
diff --git a/rust/agama-locale-data/src/lib.rs b/rust/agama-locale-data/src/lib.rs
index 0fe0803915..e1a48914d0 100644
--- a/rust/agama-locale-data/src/lib.rs
+++ b/rust/agama-locale-data/src/lib.rs
@@ -1,7 +1,6 @@
use anyhow::Context;
use flate2::bufread::GzDecoder;
use quick_xml::de::Deserializer;
-use regex::Regex;
use serde::Deserialize;
use std::fs::File;
use std::io::BufRead;
@@ -9,12 +8,17 @@ use std::io::BufReader;
use std::process::Command;
pub mod deprecated_timezones;
+pub mod keyboard;
pub mod language;
+mod locale;
pub mod localization;
pub mod ranked;
pub mod territory;
pub mod timezone_part;
-pub mod xkeyboard;
+
+use keyboard::xkeyboard;
+
+pub use locale::{InvalidLocaleCode, KeymapId, LocaleCode};
fn file_reader(file_path: &str) -> anyhow::Result {
let file = File::open(file_path)
@@ -39,10 +43,13 @@ pub fn get_xkeyboards() -> anyhow::Result {
/// Requires working localectl.
///
/// ```no_run
-/// let key_maps = agama_locale_data::get_key_maps().unwrap();
-/// assert!(key_maps.contains(&"us".to_string()))
+/// use agama_locale_data::KeymapId;
+///
+/// let key_maps = agama_locale_data::get_localectl_keymaps().unwrap();
+/// let us: KeymapId = "us".parse().unwrap();
+/// assert!(key_maps.contains(&us));
/// ```
-pub fn get_key_maps() -> anyhow::Result> {
+pub fn get_localectl_keymaps() -> anyhow::Result> {
const BINARY: &str = "/usr/bin/localectl";
let output = Command::new(BINARY)
.arg("list-keymaps")
@@ -50,31 +57,11 @@ pub fn get_key_maps() -> anyhow::Result> {
.context("failed to execute localectl list-maps")?
.stdout;
let output = String::from_utf8(output).context("Strange localectl output formatting")?;
- let ret = output.split('\n').map(|l| l.trim().to_string()).collect();
+ let ret: Vec<_> = output.lines().flat_map(|l| l.parse().ok()).collect();
Ok(ret)
}
-/// Parses given locale to language and territory part
-///
-/// /// ## Examples
-///
-/// ```
-/// let result = agama_locale_data::parse_locale("en_US.UTF-8").unwrap();
-/// assert_eq!(result.0, "en");
-/// assert_eq!(result.1, "US")
-/// ```
-pub fn parse_locale(locale: &str) -> anyhow::Result<(&str, &str)> {
- let locale_regexp: Regex = Regex::new(r"^([[:alpha:]]+)_([[:alpha:]]+)").unwrap();
- let captures = locale_regexp
- .captures(locale)
- .context("Failed to parse locale")?;
- Ok((
- captures.get(1).unwrap().as_str(),
- captures.get(2).unwrap().as_str(),
- ))
-}
-
/// Returns struct which contain list of known languages
pub fn get_languages() -> anyhow::Result {
const FILE_PATH: &str = "/usr/share/langtable/data/languages.xml.gz";
diff --git a/rust/agama-locale-data/src/locale.rs b/rust/agama-locale-data/src/locale.rs
new file mode 100644
index 0000000000..90551ce233
--- /dev/null
+++ b/rust/agama-locale-data/src/locale.rs
@@ -0,0 +1,157 @@
+//! Defines useful types to deal with localization values
+
+use regex::Regex;
+use std::sync::OnceLock;
+use std::{fmt::Display, str::FromStr};
+use thiserror::Error;
+
+#[derive(Clone, Debug, PartialEq)]
+pub struct LocaleCode {
+ // ISO-639
+ pub language: String,
+ // ISO-3166
+ pub territory: String,
+ // encoding: String,
+}
+
+impl Display for LocaleCode {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(f, "{}_{}", &self.language, &self.territory)
+ }
+}
+
+impl Default for LocaleCode {
+ fn default() -> Self {
+ Self {
+ language: "en".to_string(),
+ territory: "US".to_string(),
+ }
+ }
+}
+
+#[derive(Error, Debug)]
+#[error("Not a valid locale string: {0}")]
+pub struct InvalidLocaleCode(String);
+
+impl TryFrom<&str> for LocaleCode {
+ type Error = InvalidLocaleCode;
+
+ fn try_from(value: &str) -> Result {
+ let locale_regexp: Regex = Regex::new(r"^([[:alpha:]]+)_([[:alpha:]]+)").unwrap();
+ let captures = locale_regexp
+ .captures(value)
+ .ok_or_else(|| InvalidLocaleCode(value.to_string()))?;
+
+ Ok(Self {
+ language: captures.get(1).unwrap().as_str().to_string(),
+ territory: captures.get(2).unwrap().as_str().to_string(),
+ })
+ }
+}
+
+static KEYMAP_ID_REGEX: OnceLock = OnceLock::new();
+
+/// Keymap layout identifier
+///
+/// ```
+/// use agama_locale_data::KeymapId;
+/// use std::str::FromStr;
+///
+/// let id: KeymapId = "es(ast)".parse().unwrap();
+/// assert_eq!(&id.layout, "es");
+/// assert_eq!(id.variant.clone(), Some("ast".to_string()));
+/// assert_eq!(id.dashed(), "es-ast".to_string());
+///
+/// let id_with_dashes: KeymapId = "es-ast".parse().unwrap();
+/// assert_eq!(id, id_with_dashes);
+/// ```
+#[derive(Clone, Debug, PartialEq)]
+pub struct KeymapId {
+ pub layout: String,
+ pub variant: Option,
+}
+
+#[derive(Error, Debug)]
+#[error("Invalid keymap ID: {0}")]
+pub struct InvalidKeymap(String);
+
+impl KeymapId {
+ pub fn dashed(&self) -> String {
+ if let Some(variant) = &self.variant {
+ format!("{}-{}", &self.layout, variant)
+ } else {
+ self.layout.to_owned()
+ }
+ }
+}
+
+impl Display for KeymapId {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ if let Some(variant) = &self.variant {
+ write!(f, "{}({})", &self.layout, variant)
+ } else {
+ write!(f, "{}", &self.layout)
+ }
+ }
+}
+
+impl FromStr for KeymapId {
+ type Err = InvalidKeymap;
+
+ fn from_str(s: &str) -> Result {
+ let re = KEYMAP_ID_REGEX
+ .get_or_init(|| Regex::new(r"(\w+)((\((?\w+)\)|-(?\w+)))?").unwrap());
+
+ if let Some(parts) = re.captures(s) {
+ let mut variant = None;
+ if let Some(var1) = parts.name("var1") {
+ variant = Some(var1.as_str().to_string());
+ }
+ if let Some(var2) = parts.name("var2") {
+ variant = Some(var2.as_str().to_string());
+ }
+ Ok(KeymapId {
+ layout: parts[1].to_string(),
+ variant,
+ })
+ } else {
+ Err(InvalidKeymap(s.to_string()))
+ }
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::KeymapId;
+ use std::str::FromStr;
+
+ #[test]
+ fn test_parse_keymap_id() {
+ let keymap_id0 = KeymapId::from_str("es").unwrap();
+ assert_eq!(
+ KeymapId {
+ layout: "es".to_string(),
+ variant: None
+ },
+ keymap_id0
+ );
+
+ let keymap_id1 = KeymapId::from_str("es(ast)").unwrap();
+ assert_eq!(
+ KeymapId {
+ layout: "es".to_string(),
+ variant: Some("ast".to_string())
+ },
+ keymap_id1
+ );
+
+ let keymap_id2 = KeymapId::from_str("es-ast").unwrap();
+ assert_eq!(
+ KeymapId {
+ layout: "es".to_string(),
+ variant: Some("ast".to_string())
+ },
+ keymap_id2
+ );
+ }
+}
diff --git a/rust/agama-locale-data/src/timezone_part.rs b/rust/agama-locale-data/src/timezone_part.rs
index c1390c3815..1f1d1223e6 100644
--- a/rust/agama-locale-data/src/timezone_part.rs
+++ b/rust/agama-locale-data/src/timezone_part.rs
@@ -20,6 +20,14 @@ pub struct TimezoneIdParts {
}
impl TimezoneIdParts {
+ // TODO: Implement a caching mechanism
+ pub fn localize_part(&self, part_id: &str, language: &str) -> Option {
+ self.timezone_part
+ .iter()
+ .find(|p| p.id == part_id)
+ .and_then(|p| p.names.name_for(language))
+ }
+
/// Localized given list of timezones to given language
/// # Examples
///
diff --git a/rust/package/agama-cli.changes b/rust/package/agama-cli.changes
index 0e95ea5b8c..0a6e8d573f 100644
--- a/rust/package/agama-cli.changes
+++ b/rust/package/agama-cli.changes
@@ -1,3 +1,42 @@
+-------------------------------------------------------------------
+Tue Dec 5 11:18:41 UTC 2023 - Jorik Cronenberg
+
+- Add ability to assign a custom MAC address for network
+ connections (gh#openSUSE/agama#893)
+
+-------------------------------------------------------------------
+Tue Dec 5 09:46:48 UTC 2023 - José Iván López González
+
+- Explicitly add dependencies instead of relying on the live ISO
+ to provide the required packages (gh#openSUSE/agama/911).
+
+-------------------------------------------------------------------
+Sun Dec 3 15:53:34 UTC 2023 - Imobach Gonzalez Sosa
+
+- Use a single call to systemd-firstboot to write the localization
+ settings (gh#openSUSE/agama#903).
+
+-------------------------------------------------------------------
+Sat Dec 2 18:05:54 UTC 2023 - Imobach Gonzalez Sosa
+
+- Version 6
+
+-------------------------------------------------------------------
+Wed Nov 29 11:19:51 UTC 2023 - Imobach Gonzalez Sosa
+
+- Rework the org.opensuse.Agama1.Locale interface
+ (gh#openSUSE/agama#881):
+ * Replace LabelsForLocales function with ListLocales.
+ * Add a ListKeymaps function.
+ * Extend the ListTimezone function to include the translation of
+ each part.
+ * Drop ListUILocales and ListVConsoleKeyboards functions.
+ * Remove the SupportedLocales and VConsoleKeyboard properties.
+ * Do not read the lists of locales, keymaps and timezones on
+ each request.
+ * Peform some validation when trying to change the Locales,
+ Keymap and Timezone properties.
+
-------------------------------------------------------------------
Thu Nov 16 11:06:30 UTC 2023 - Imobach Gonzalez Sosa
diff --git a/rust/package/agama-cli.spec b/rust/package/agama-cli.spec
index 9518cb35b3..1e200fa2b3 100644
--- a/rust/package/agama-cli.spec
+++ b/rust/package/agama-cli.spec
@@ -40,6 +40,8 @@ Requires: lshw
# required by "agama logs store"
Requires: bzip2
Requires: tar
+# required for translating the keyboards descriptions
+Requires: xkeyboard-config-lang
%description
Command line program to interact with the agama service.
diff --git a/service/.rubocop.yml b/service/.rubocop.yml
index ed2faf80db..137ea30537 100644
--- a/service/.rubocop.yml
+++ b/service/.rubocop.yml
@@ -24,3 +24,7 @@ Metrics/BlockLength:
# assignment in method calls is used to document some params
Lint/UselessAssignment:
Enabled: false
+
+# be less strict
+Metrics/AbcSize:
+ Max: 32
diff --git a/service/Gemfile.lock b/service/Gemfile.lock
old mode 100644
new mode 100755
index d6f25b0609..9f6cb9c0d0
--- a/service/Gemfile.lock
+++ b/service/Gemfile.lock
@@ -1,7 +1,7 @@
PATH
remote: .
specs:
- agama (5)
+ agama (6)
cfa (~> 1.0.2)
cfa_grub2 (~> 2.0.0)
cheetah (~> 1.0.0)
@@ -73,4 +73,4 @@ DEPENDENCIES
yard (~> 0.9.0)
BUNDLED WITH
- 2.3.26
+ 2.4.22
diff --git a/service/lib/agama/config.rb b/service/lib/agama/config.rb
index 0e524d648c..3b4c2ba811 100644
--- a/service/lib/agama/config.rb
+++ b/service/lib/agama/config.rb
@@ -168,12 +168,14 @@ def merge(config)
#
# @param keys [Array] Config data keys of the collection.
# @param property [Symbol|String|nil] Property to retrieve of the elements.
+ # @param default [Object] The default value returned when the value is not
+ # found or is not an array
#
# @return [Array]
- def arch_elements_from(*keys, property: nil)
+ def arch_elements_from(*keys, property: nil, default: [])
keys.map!(&:to_s)
elements = products.dig(*keys)
- return [] unless elements.is_a?(Array)
+ return default unless elements.is_a?(Array)
elements.map do |element|
if !element.is_a?(Hash)
diff --git a/service/lib/agama/dbus/clients/base.rb b/service/lib/agama/dbus/clients/base.rb
index 9aa6b633e2..bb26770c42 100644
--- a/service/lib/agama/dbus/clients/base.rb
+++ b/service/lib/agama/dbus/clients/base.rb
@@ -27,6 +27,22 @@ module Agama
module DBus
module Clients
# Base class for D-Bus clients
+ #
+ # The clients should be singleton because the #on_properties_change method
+ # will not work properly with several instances. Given a
+ # ::DBus::BusConnection object, ruby-dbus does not allow to register more
+ # than one callback for the same object.
+ #
+ # It causes the last instance to overwrite the callbacks from previous ones.
+ #
+ # @example Creating a new client
+ # require "singleton"
+ #
+ # class Locale < Base
+ # include Singleton
+ #
+ # # client methods
+ # end
class Base
# @!method service_name
# Name of the D-Bus service
diff --git a/service/lib/agama/dbus/clients/locale.rb b/service/lib/agama/dbus/clients/locale.rb
index 7a3a37b359..1cc0cd797a 100644
--- a/service/lib/agama/dbus/clients/locale.rb
+++ b/service/lib/agama/dbus/clients/locale.rb
@@ -20,12 +20,14 @@
# find current contact information at www.suse.com.
require "agama/dbus/clients/base"
+require "singleton"
module Agama
module DBus
module Clients
# D-Bus client for locale configuration
class Locale < Base
+ include Singleton
INTERFACE_NAME = "org.opensuse.Agama1.Locale"
def initialize
@@ -39,13 +41,6 @@ def service_name
@service_name ||= "org.opensuse.Agama1"
end
- # Sets the supported locales. It can differs per product.
- #
- # @param locales [Array]
- def supported_locales=(locales)
- dbus_object.supported_locales = locales
- end
-
def ui_locale
dbus_object[INTERFACE_NAME]["UILocale"]
end
@@ -54,10 +49,6 @@ def ui_locale=(locale)
dbus_object[INTERFACE_NAME]["UILocale"] = locale
end
- def available_ui_locales
- dbus_object.ListUILocales
- end
-
# Finishes the language installation
def finish
dbus_object.Commit
diff --git a/service/lib/agama/dbus/manager.rb b/service/lib/agama/dbus/manager.rb
index 1a759ce672..d1f129d18e 100644
--- a/service/lib/agama/dbus/manager.rb
+++ b/service/lib/agama/dbus/manager.rb
@@ -60,7 +60,7 @@ def initialize(backend, logger)
dbus_method(:Probe, "") { config_phase }
dbus_method(:Commit, "") { install_phase }
dbus_method(:CanInstall, "out result:b") { can_install? }
- dbus_method(:CollectLogs, "out tarball_filesystem_path:s, in user:s") { |u| collect_logs(u) }
+ dbus_method(:CollectLogs, "out tarball_filesystem_path:s") { collect_logs }
dbus_method(:Finish, "") { finish_phase }
dbus_reader :installation_phases, "aa{sv}"
dbus_reader :current_installation_phase, "u"
@@ -92,8 +92,8 @@ def can_install?
end
# Collects the YaST logs
- def collect_logs(user)
- backend.collect_logs(user)
+ def collect_logs
+ backend.collect_logs
end
# Last action for the installer
diff --git a/service/lib/agama/dbus/manager_service.rb b/service/lib/agama/dbus/manager_service.rb
index 4c73938a0f..6351ef4031 100644
--- a/service/lib/agama/dbus/manager_service.rb
+++ b/service/lib/agama/dbus/manager_service.rb
@@ -73,7 +73,7 @@ def start
setup_cockpit
export
# We need locale for data from users
- locale_client = Clients::Locale.new
+ locale_client = Clients::Locale.instance
# TODO: test if we need to pass block with additional actions
@ui_locale = UILocale.new(locale_client)
manager.on_progress_change { dispatch } # make single thread more responsive
diff --git a/service/lib/agama/dbus/software/manager.rb b/service/lib/agama/dbus/software/manager.rb
index aad196e3cb..a22729c3eb 100644
--- a/service/lib/agama/dbus/software/manager.rb
+++ b/service/lib/agama/dbus/software/manager.rb
@@ -132,9 +132,9 @@ def finish
# Registers callback to be called
def register_callbacks
- lang_client = Agama::DBus::Clients::Locale.new
- lang_client.on_language_selected do |language_ids|
+ Agama::DBus::Clients::Locale.instance.on_language_selected do |language_ids|
backend.languages = language_ids
+ probe
end
nm_client = Agama::DBus::Clients::Network.new
diff --git a/service/lib/agama/dbus/software_service.rb b/service/lib/agama/dbus/software_service.rb
index 3f357bb000..245f2f6a97 100644
--- a/service/lib/agama/dbus/software_service.rb
+++ b/service/lib/agama/dbus/software_service.rb
@@ -58,8 +58,7 @@ def start
# for some reason the the "export" method must be called before
# registering the language change callback to work properly
export
- locale_client = Clients::Locale.new
- @ui_locale = UILocale.new(locale_client) do |locale|
+ @ui_locale = UILocale.new(Clients::Locale.instance) do |locale|
# set the locale in the Language module, when changing the repository
# (product) it calls Pkg.SetTextLocale(Language.language) internally
Yast::Language.Set(locale)
diff --git a/service/lib/agama/dbus/storage_service.rb b/service/lib/agama/dbus/storage_service.rb
index 773e37a3e9..37da268335 100644
--- a/service/lib/agama/dbus/storage_service.rb
+++ b/service/lib/agama/dbus/storage_service.rb
@@ -53,9 +53,8 @@ def bus
# Starts storage service. It does more then just #export method.
def start
export
- locale_client = Clients::Locale.new
# TODO: test if we need to pass block with additional actions
- @ui_locale = UILocale.new(locale_client)
+ @ui_locale = UILocale.new(Clients::Locale.instance)
end
# Exports the storage proposal object through the D-Bus service
diff --git a/service/lib/agama/dbus/y2dir/modules/InstFunctions.rb b/service/lib/agama/dbus/y2dir/modules/InstFunctions.rb
new file mode 100644
index 0000000000..a1d5a21cc4
--- /dev/null
+++ b/service/lib/agama/dbus/y2dir/modules/InstFunctions.rb
@@ -0,0 +1,58 @@
+# Copyright (c) [2022-2023] SUSE LLC
+#
+# All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of version 2 of the GNU General Public License as published
+# by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+# more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, contact SUSE LLC.
+#
+# To contact SUSE LLC about this file by physical or electronic mail, you may
+# find current contact information at www.suse.com.
+
+require "yast"
+
+# :nodoc:
+module Yast
+ # Replacement for the Yast::Package module
+ #
+ # @see https://github.com/yast/yast-installation/blob/279b7d108eab24082237cf5e3f02a31f58fef8da/src/modules/InstFunctions.rb
+ class InstFunctionsClass < Module
+ def main
+ puts "Loading mocked module #{__FILE__}"
+ end
+
+ # @see https://github.com/yast/yast-installation/blob/279b7d108eab24082237cf5e3f02a31f58fef8da/src/modules/InstFunctions.rb#L56
+ def ignored_features
+ []
+ end
+
+ # @see https://github.com/yast/yast-installation/blob/279b7d108eab24082237cf5e3f02a31f58fef8da/src/modules/InstFunctions.rb#L83
+ def reset_ignored_features; end
+
+ # @see https://github.com/yast/yast-installation/blob/279b7d108eab24082237cf5e3f02a31f58fef8da/src/modules/InstFunctions.rb#L91
+ def feature_ignored?(_feature_name)
+ false
+ end
+
+ # @see https://github.com/yast/yast-installation/blob/279b7d108eab24082237cf5e3f02a31f58fef8da/src/modules/InstFunctions.rb#L107
+ def second_stage_required?
+ false
+ end
+
+ # @see https://github.com/yast/yast-installation/blob/279b7d108eab24082237cf5e3f02a31f58fef8da/src/modules/InstFunctions.rb#L137
+ def self_update_explicitly_enabled?
+ false
+ end
+ end
+
+ InstFunctions = InstFunctionsClass.new
+ InstFunctions.main
+end
diff --git a/service/lib/agama/dbus/y2dir/storage/modules/InstFunctions.rb b/service/lib/agama/dbus/y2dir/storage/modules/InstFunctions.rb
new file mode 120000
index 0000000000..42c5d80092
--- /dev/null
+++ b/service/lib/agama/dbus/y2dir/storage/modules/InstFunctions.rb
@@ -0,0 +1 @@
+../../modules/InstFunctions.rb
\ No newline at end of file
diff --git a/service/lib/agama/manager.rb b/service/lib/agama/manager.rb
index b11b5960cb..48579520c6 100644
--- a/service/lib/agama/manager.rb
+++ b/service/lib/agama/manager.rb
@@ -152,7 +152,7 @@ def proxy
#
# @return [DBus::Clients::Locale]
def language
- @language ||= DBus::Clients::Locale.new
+ DBus::Clients::Locale.instance
end
# Users client
@@ -205,18 +205,21 @@ def valid?
# Collects the logs and stores them into an archive
#
- # @param user [String] local username who will own archive
+ # @param path [String] directory where to store logs
+ #
# @return [String] path to created archive
- def collect_logs(user)
- output = Yast::Execute.locally!("save_y2logs", stderr: :capture)
- path = output[/^.* (\/tmp\/y2log-\S*)/, 1]
- Yast::Execute.locally!("chown", "#{user}:", path)
+ def collect_logs(path: nil)
+ opt = "-d #{path}" unless path.nil? || path.empty?
- path
+ `agama logs store #{opt}`.strip
end
# Whatever has to be done at the end of installation
def finish_installation
+ logs = collect_logs(path: "/tmp/var/logs/")
+
+ logger.info("Installation logs stored in #{logs}")
+
cmd = if iguana?
"/usr/bin/agamactl -k"
else
diff --git a/service/lib/agama/software/manager.rb b/service/lib/agama/software/manager.rb
index be47087609..9c2aeb8cf9 100644
--- a/service/lib/agama/software/manager.rb
+++ b/service/lib/agama/software/manager.rb
@@ -214,6 +214,11 @@ def patterns(filtered)
patterns = Y2Packager::Resolvable.find({ kind: :pattern }, preload)
patterns = patterns.select(&:user_visible) if filtered
+ # only display the configured patterns
+ if product.user_patterns && filtered
+ patterns.select! { |p| product.user_patterns.include?(p.name) }
+ end
+
patterns
end
diff --git a/service/lib/agama/software/product.rb b/service/lib/agama/software/product.rb
index 24bb8c0b2b..9e1179ce3a 100644
--- a/service/lib/agama/software/product.rb
+++ b/service/lib/agama/software/product.rb
@@ -73,6 +73,11 @@ class Product
# @return [Array]
attr_accessor :optional_patterns
+ # Optional user selectable patterns
+ #
+ # @return [Array]
+ attr_accessor :user_patterns
+
# Product translations.
#
# @example
@@ -94,6 +99,8 @@ def initialize(id)
@optional_packages = []
@mandatory_patterns = []
@optional_patterns = []
+ # nil = display all visible patterns, [] = display no patterns
+ @user_patterns = nil
@translations = {}
end
diff --git a/service/lib/agama/software/product_builder.rb b/service/lib/agama/software/product_builder.rb
index b4ecfeddf8..c048be304e 100644
--- a/service/lib/agama/software/product_builder.rb
+++ b/service/lib/agama/software/product_builder.rb
@@ -47,6 +47,7 @@ def build
product.optional_packages = data[:optional_packages]
product.mandatory_patterns = data[:mandatory_patterns]
product.optional_patterns = data[:optional_patterns]
+ product.user_patterns = data[:user_patterns]
product.translations = attrs["translations"] || {}
end
end
@@ -79,6 +80,9 @@ def product_data_from_config(id)
),
optional_patterns: config.arch_elements_from(
id, "software", "optional_patterns", property: :pattern
+ ),
+ user_patterns: config.arch_elements_from(
+ id, "software", "user_patterns", property: :pattern, default: nil
)
}
end
diff --git a/service/lib/agama/software/proposal.rb b/service/lib/agama/software/proposal.rb
index 383e1509d5..c899a1e82f 100644
--- a/service/lib/agama/software/proposal.rb
+++ b/service/lib/agama/software/proposal.rb
@@ -56,7 +56,7 @@ class Proposal
attr_accessor :base_product
# @return [Array] List of languages to install
- attr_accessor :languages
+ attr_reader :languages
# Constructor
#
@@ -114,6 +114,13 @@ def valid?
!(proposal.nil? || errors?)
end
+ # Sets the languages to install
+ #
+ # @param [Array] value Languages in xx_XX format (e.g., "en_US").
+ def languages=(value)
+ @languages = value.map { |l| l.split(".").first }.compact
+ end
+
private
# @return [Logger]
@@ -129,8 +136,12 @@ def initialize_target
Yast::Pkg.TargetFinish # ensure that previous target is closed
Yast::Pkg.TargetInitialize(Yast::Installation.destdir)
Yast::Pkg.TargetLoad
- Yast::Pkg.SetAdditionalLocales(languages)
- Yast::Pkg.SetSolverFlags("ignoreAlreadyRecommended" => false, "onlyRequires" => true)
+
+ preferred, *additional = languages
+ Yast::Pkg.SetPackageLocale(preferred) if preferred
+ Yast::Pkg.SetAdditionalLocales(additional)
+
+ Yast::Pkg.SetSolverFlags("ignoreAlreadyRecommended" => false, "onlyRequires" => false)
end
# Selects the base product
diff --git a/service/package/gem2rpm.yml b/service/package/gem2rpm.yml
index f708e08f4d..51654f6372 100644
--- a/service/package/gem2rpm.yml
+++ b/service/package/gem2rpm.yml
@@ -18,25 +18,52 @@
%global rb_build_versions %{rb_default_ruby}
BuildRequires: dbus-1-common
Requires: dbus-1-common
- Requires: snapper
+ Requires: suseconnect-ruby-bindings
+ # YaST dependencies
+ Requires: autoyast2-installation
+ # ArchFilter
+ Requires: yast2 >= 4.5.20
Requires: yast2-bootloader
Requires: yast2-country
Requires: yast2-hardware-detection
Requires: yast2-installation
+ Requires: yast2-iscsi-client >= 4.5.7
Requires: yast2-network
Requires: yast2-proxy
# ProposalSettings#swap_reuse
Requires: yast2-storage-ng >= 5.0.3
- Requires: open-iscsi
- Requires: yast2-iscsi-client >= 4.5.7
Requires: yast2-users
- # required for registration
- Requires: suseconnect-ruby-bindings
- # yast2 with ArchFilter
- Requires: yast2 >= 4.5.20
%ifarch s390 s390x
Requires: yast2-s390 >= 4.6.4
+ Requires: yast2-reipl
+ Requires: yast2-cio
%endif
+ # Storage dependencies
+ Requires: bcache-tools
+ Requires: btrfsprogs
+ Requires: cryptsetup
+ Requires: dmraid
+ Requires: dosfstools
+ Requires: e2fsprogs
+ Requires: exfat-utils
+ Requires: f2fs-tools
+ Requires: fcoe-utils
+ Requires: fde-tools
+ Requires: jfsutils
+ Requires: libstorage-ng-lang
+ Requires: lvm2
+ Requires: mdadm
+ Requires: multipath-tools
+ Requires: nilfs-utils
+ Requires: nfs-client
+ Requires: ntfs-3g
+ Requires: ntfsprogs
+ Requires: nvme-cli
+ Requires: open-iscsi
+ Requires: quota
+ Requires: snapper
+ Requires: udftools
+ Requires: xfsprogs
:filelist: "%{_datadir}/dbus-1/agama.conf\n
%dir %{_datadir}/dbus-1/agama-services\n
%{_datadir}/dbus-1/agama-services/org.opensuse.Agama*.service\n
diff --git a/service/package/rubygem-agama.changes b/service/package/rubygem-agama.changes
index 2c9d28c3a7..2f9e627bd5 100644
--- a/service/package/rubygem-agama.changes
+++ b/service/package/rubygem-agama.changes
@@ -1,3 +1,32 @@
+-------------------------------------------------------------------
+Tue Dec 5 09:49:10 UTC 2023 - José Iván López González
+
+- Explicitly add dependencies instead of relying on the live ISO
+ to provide the required packages (gh#openSUSE/agama/911).
+
+-------------------------------------------------------------------
+Sun Dec 3 15:45:22 UTC 2023 - Imobach Gonzalez Sosa
+
+- Redefine the InstFunctions module to avoid calling code that
+ causes unwanted side effects, like resetting the timezone
+ (gh#openSUSE/agama#903).
+
+-------------------------------------------------------------------
+Sat Dec 2 18:05:37 UTC 2023 - Imobach Gonzalez Sosa
+
+- Version 6
+
+-------------------------------------------------------------------
+Wed Nov 29 11:26:39 UTC 2023 - Imobach Gonzalez Sosa
+
+- Update the software proposal when the locale changes
+ (gh#openSUSE/agama#881).
+
+-------------------------------------------------------------------
+Fri Nov 24 14:50:22 UTC 2023 - Imobach Gonzalez Sosa
+
+- Install recommended packages (gh#openSUSE/agama#889).
+
-------------------------------------------------------------------
Thu Nov 16 16:27:37 UTC 2023 - Ladislav Slezák
diff --git a/service/test/agama/dbus/clients/locale_test.rb b/service/test/agama/dbus/clients/locale_test.rb
index 431c08515f..c454bc0f68 100644
--- a/service/test/agama/dbus/clients/locale_test.rb
+++ b/service/test/agama/dbus/clients/locale_test.rb
@@ -39,17 +39,7 @@
let(:dbus_object) { instance_double(DBus::ProxyObject) }
let(:lang_iface) { instance_double(DBus::ProxyObjectInterface) }
- subject { described_class.new }
-
- describe "#supported_locales=" do
- # Using partial double because methods are dynamically added to the proxy object
- let(:dbus_object) { double(DBus::ProxyObject) }
-
- it "calls the D-Bus object" do
- expect(dbus_object).to receive(:supported_locales=).with(["no", "se"])
- subject.supported_locales = ["no", "se"]
- end
- end
+ subject { described_class.instance }
describe "#finish" do
let(:dbus_object) { double(DBus::ProxyObject) }
diff --git a/service/test/agama/dbus/manager_service_test.rb b/service/test/agama/dbus/manager_service_test.rb
index 8de02632b6..6149a198cf 100644
--- a/service/test/agama/dbus/manager_service_test.rb
+++ b/service/test/agama/dbus/manager_service_test.rb
@@ -48,7 +48,7 @@
.and_return(object_server)
allow(Agama::Manager).to receive(:new).with(config, logger).and_return(manager)
allow(Agama::CockpitManager).to receive(:new).and_return(cockpit)
- allow(Agama::DBus::Clients::Locale).to receive(:new).and_return(locale_client)
+ allow(Agama::DBus::Clients::Locale).to receive(:instance).and_return(locale_client)
allow(Agama::DBus::Manager).to receive(:new).with(manager, logger).and_return(manager_obj)
allow(Agama::DBus::Users).to receive(:new).and_return(users_obj)
end
diff --git a/service/test/agama/dbus/software/manager_test.rb b/service/test/agama/dbus/software/manager_test.rb
index 925218b9d5..ead7fcdc49 100644
--- a/service/test/agama/dbus/software/manager_test.rb
+++ b/service/test/agama/dbus/software/manager_test.rb
@@ -52,7 +52,7 @@
let(:issues_interface) { Agama::DBus::Interfaces::Issues::ISSUES_INTERFACE }
before do
- allow(Agama::DBus::Clients::Locale).to receive(:new).and_return(locale_client)
+ allow(Agama::DBus::Clients::Locale).to receive(:instance).and_return(locale_client)
allow(Agama::DBus::Clients::Network).to receive(:new).and_return(network_client)
allow(backend).to receive(:probe)
allow(backend).to receive(:propose)
diff --git a/service/test/agama/manager_test.rb b/service/test/agama/manager_test.rb
index 9e465df938..b9e70a30fd 100644
--- a/service/test/agama/manager_test.rb
+++ b/service/test/agama/manager_test.rb
@@ -65,7 +65,7 @@
before do
allow(Agama::Network).to receive(:new).and_return(network)
allow(Agama::ProxySetup).to receive(:instance).and_return(proxy)
- allow(Agama::DBus::Clients::Locale).to receive(:new).and_return(locale)
+ allow(Agama::DBus::Clients::Locale).to receive(:instance).and_return(locale)
allow(Agama::DBus::Clients::Software).to receive(:new).and_return(software)
allow(Agama::DBus::Clients::Storage).to receive(:new).and_return(storage)
allow(Agama::Users).to receive(:new).and_return(users)
@@ -214,13 +214,12 @@
describe "#collect_logs" do
it "collects the logs and returns the path to the archive" do
- expect(Yast::Execute).to receive(:locally!)
- .with("save_y2logs", stderr: :capture)
- .and_return("Saving YaST logs to /tmp/y2log-hWBn95.tar.xz")
- expect(Yast::Execute).to receive(:locally!)
- .with("chown", "ytm:", /y2log-hWBn95/)
+ # %x returns the command output including trailing \n
+ expect(subject).to receive(:`)
+ .with("agama logs store ")
+ .and_return("/tmp/y2log-hWBn95.tar.xz\n")
- path = subject.collect_logs("ytm")
+ path = subject.collect_logs
expect(path).to eq("/tmp/y2log-hWBn95.tar.xz")
end
end
diff --git a/service/test/agama/software/manager_test.rb b/service/test/agama/software/manager_test.rb
index f899ab64b3..1603656c4f 100644
--- a/service/test/agama/software/manager_test.rb
+++ b/service/test/agama/software/manager_test.rb
@@ -229,12 +229,70 @@
describe "#products" do
it "returns the list of known products" do
products = subject.products
- expect(products.size).to eq(3)
+ expect(products.size).to eq(4)
expect(products).to all(be_a(Agama::Software::Product))
expect(products).to contain_exactly(
an_object_having_attributes(id: "ALP-Dolomite"),
an_object_having_attributes(id: "Tumbleweed"),
- an_object_having_attributes(id: "Leap16")
+ an_object_having_attributes(id: "MicroOS"),
+ an_object_having_attributes(id: "MicroOS-Desktop")
+ )
+ end
+ end
+
+ describe "#patterns" do
+ it "returns only the specified patterns" do
+ expect(Y2Packager::Resolvable).to receive(:find).and_return(
+ [
+ double(
+ arch: "x86_64",
+ category: "Base Technologies",
+ description: "YaST tools for installing your system.",
+ icon: "./yast",
+ kind: :pattern,
+ name: "yast2_install_wf",
+ order: "1240",
+ source: 0,
+ summary: "YaST Installation Packages",
+ user_visible: false,
+ version: "20220411-1.4"
+ ),
+ double(
+ arch: "x86_64",
+ category: "Base Technologies",
+ description: "YaST tools for basic system administration.",
+ icon: "./yast",
+ kind: :pattern,
+ name: "yast2_basis",
+ order: "1220",
+ source: 0,
+ summary: "YaST Base Utilities",
+ user_visible: true,
+ version: "20220411-1.4"
+ ),
+ double(
+ arch: "noarch",
+ category: "Graphical Environments",
+ description:
+ "Packages providing the Plasma desktop environment and " \
+ "applications from KDE.",
+ icon: "./pattern-kde",
+ kind: :pattern,
+ name: "kde",
+ order: "1110",
+ source: 0,
+ summary: "KDE Applications and Plasma 5 Desktop",
+ user_visible: true,
+ version: "20230801-1.1"
+ )
+ ]
+ )
+
+ allow(subject.product).to receive(:user_patterns).and_return(["kde"])
+ patterns = subject.patterns(true)
+
+ expect(patterns).to contain_exactly(
+ an_object_having_attributes(name: "kde")
)
end
end
diff --git a/service/test/agama/software/proposal_test.rb b/service/test/agama/software/proposal_test.rb
index 8dfdf1cb94..1c3c176589 100644
--- a/service/test/agama/software/proposal_test.rb
+++ b/service/test/agama/software/proposal_test.rb
@@ -59,8 +59,9 @@
end
it "selects the language packages" do
+ expect(Yast::Pkg).to receive(:SetPackageLocale).with("cs_CZ")
expect(Yast::Pkg).to receive(:SetAdditionalLocales).with(["de_DE"])
- subject.languages = ["de_DE"]
+ subject.languages = ["cs_CZ", "de_DE"]
subject.calculate
end
@@ -162,4 +163,11 @@
end
end
end
+
+ describe "#languages" do
+ it "sets the languages to install removing the encoding" do
+ subject.languages = ["es_ES.UTF-8", "en_US"]
+ expect(subject.languages).to eq(["es_ES", "en_US"])
+ end
+ end
end
diff --git a/setup-service.sh b/setup-services.sh
similarity index 52%
rename from setup-service.sh
rename to setup-services.sh
index 97c3dcd8af..cc32797f70 100755
--- a/setup-service.sh
+++ b/setup-services.sh
@@ -1,13 +1,11 @@
#!/bin/sh -x
-# Using a git checkout in the current directory,
-# set up the service (backend) part of agama
-# so that it can be used by
-# - the web frontend (as set up by setup.sh)
+# Using a git checkout in the current directory and set up the services, so that it can be used by:
+# - the web frontend (as set up by setup-web.sh)
# - the CLI
# or both
-# Exit on error; unset variables are an error
+# Exit on error; unset variables are an error.
set -eu
MYDIR=$(realpath $(dirname $0))
@@ -32,26 +30,89 @@ sudosed() {
sed -e "$1" "$2" | $SUDO tee "$3" > /dev/null
}
-# - Install RPM dependencies
-
-# this repo can be removed once python-language-data reaches Factory
-test -f /etc/zypp/repos.d/d_l_python.repo || \
- $SUDO zypper --non-interactive \
- addrepo https://download.opensuse.org/repositories/devel:/languages:/python/openSUSE_Tumbleweed/ d_l_python
-$SUDO zypper --non-interactive --gpg-auto-import-keys install gcc gcc-c++ make openssl-devel ruby-devel \
- python-langtable-data git augeas-devel jemalloc-devel awk suseconnect-ruby-bindings || exit 1
-
-# only install cargo if it is not available (avoid conflicts with rustup)
-which cargo || $SUDO zypper --non-interactive install cargo
-
-# - Install service rubygem dependencies
+# if agama is already running -> stop it
+$SUDO systemctl list-unit-files agama.service &>/dev/null && $SUDO systemctl stop agama.service
+
+# Ruby services
+
+# Packages required for Ruby development (i.e., bundle install).
+$SUDO zypper --non-interactive --gpg-auto-import-keys install \
+ gcc \
+ gcc-c++ \
+ make \
+ openssl-devel \
+ ruby-devel \
+ augeas-devel || exit 1
+
+# Packages required by Agama Ruby services (see ./service/package/gem2rpm.yml).
+# TODO extract list from gem2rpm.yml
+$SUDO zypper --non-interactive --gpg-auto-import-keys install \
+ dbus-1-common \
+ suseconnect-ruby-bindings \
+ autoyast2-installation \
+ yast2 \
+ yast2-bootloader \
+ yast2-country \
+ yast2-hardware-detection \
+ yast2-installation \
+ yast2-iscsi-client \
+ yast2-network \
+ yast2-proxy \
+ yast2-storage-ng \
+ yast2-users \
+ bcache-tools \
+ btrfsprogs \
+ cryptsetup \
+ dmraid \
+ dosfstools \
+ e2fsprogs \
+ exfat-utils \
+ f2fs-tools \
+ fcoe-utils \
+ fde-tools \
+ jfsutils \
+ libstorage-ng-lang \
+ lvm2 \
+ mdadm \
+ multipath-tools \
+ nilfs-utils \
+ nfs-client \
+ ntfs-3g \
+ ntfsprogs \
+ nvme-cli \
+ open-iscsi \
+ quota \
+ snapper \
+ udftools \
+ xfsprogs || exit 1
+
+# Install s390 packages (do not exit on failure).
+$SUDO zypper --non-interactive --gpg-auto-import-keys install \
+ yast2-s390 \
+ yast2-reipl \
+ yast2-cio
+
+# Rubygem dependencies
(
cd $MYDIR/service
bundle config set --local path 'vendor/bundle'
bundle install
)
-# - build also rust service
+# Rust service, CLI and auto-installation.
+
+# Only install cargo if it is not available (avoid conflicts with rustup)
+which cargo || $SUDO zypper --non-interactive install cargo
+
+# Packages required by Rust code (see ./rust/package/agama-cli.spec)
+$SUDO zypper --non-interactive install \
+ bzip2 \
+ jsonnet \
+ lshw \
+ python-langtable-data \
+ tar \
+ xkeyboard-config-lang || exit 1
+
(
cd $MYDIR/rust
cargo build
@@ -70,7 +131,7 @@ $SUDO cp -v $MYDIR/service/share/dbus.conf /usr/share/dbus-1/agama.conf
# cleanup previous installation
[[ -d $DBUSDIR ]] && $SUDO rm -r $DBUSDIR
- # create services
+ # create services
$SUDO mkdir -p $DBUSDIR
for SVC in org.opensuse.Agama*.service; do
sudosed "s@\(Exec\)=/usr/bin/@\1=$MYDIR/service/bin/@" $SVC $DBUSDIR/$SVC
diff --git a/setup-web.sh b/setup-web.sh
new file mode 100755
index 0000000000..7e07af57a5
--- /dev/null
+++ b/setup-web.sh
@@ -0,0 +1,29 @@
+#!/bin/sh -x
+
+# Exit on error; unset variables are an error.
+set -eu
+
+MYDIR=$(realpath $(dirname $0))
+
+# Helper:
+# Ensure root privileges for the installation.
+# In a testing container, we are root but there is no sudo.
+if [ $(id --user) != 0 ]; then
+ SUDO=sudo
+ if [ $($SUDO id --user) != 0 ]; then
+ echo "We are not root and cannot sudo, cannot continue."
+ exit 1
+ fi
+else
+ SUDO=""
+fi
+
+$SUDO zypper --non-interactive --gpg-auto-import-keys install \
+ make \
+ 'npm>=18' \
+ cockpit || exit 1
+
+$SUDO systemctl start cockpit
+
+cd web; make devel-install; cd -
+$SUDO ln -snf `pwd`/web/dist /usr/share/cockpit/agama
diff --git a/setup.sh b/setup.sh
index 2f4bcaeb24..16d446444b 100755
--- a/setup.sh
+++ b/setup.sh
@@ -1,9 +1,9 @@
#!/bin/sh
-# This script sets up the development environment without installing any
-# package. This script is supposed to run within a repository clone.
+# This script sets up the development environment without installing Agama packages. This script is
+# supposed to run within a repository clone.
-# Exit on error; unset variables are an error
+# Exit on error; unset variables are an error.
set -eu
MYDIR=$(realpath $(dirname $0))
@@ -21,24 +21,23 @@ else
SUDO=""
fi
-# Backend setup
+# Services setup
+if ! $MYDIR/setup-services.sh; then
+ echo "Services setup failed."
+ echo "Agama services are NOT running."
-$MYDIR/setup-service.sh
+ exit 2
+fi;
-# Install Frontend dependencies
+# Web setup
+if ! $MYDIR/setup-web.sh; then
+ echo "Web client setup failed."
+ echo "Agama web client is NOT running."
-$SUDO zypper --non-interactive --gpg-auto-import-keys install \
- make git 'npm>=18' cockpit
+ exit 3
+fi;
-# Web Frontend
-
-$SUDO systemctl start cockpit
-
-# set up the web UI
-cd web; make devel-install; cd -
-$SUDO ln -snf `pwd`/web/dist /usr/share/cockpit/agama
-
-# Start the installer
+# Start the installer.
echo
echo "D-Bus will start the services, see journalctl for their logs."
echo "To start the services manually, logging to the terminal:"
diff --git a/web/cspell.json b/web/cspell.json
index fa98f5ff09..428bf4ef41 100644
--- a/web/cspell.json
+++ b/web/cspell.json
@@ -43,6 +43,8 @@
"ipaddr",
"iscsi",
"jdoe",
+ "keymap",
+ "keymaps",
"libyui",
"lldp",
"localdomain",
diff --git a/web/package-lock.json b/web/package-lock.json
index fed95a9d64..a587f2ad6c 100644
--- a/web/package-lock.json
+++ b/web/package-lock.json
@@ -104,9 +104,9 @@
}
},
"node_modules/@adobe/css-tools": {
- "version": "4.3.1",
- "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.3.1.tgz",
- "integrity": "sha512-/62yikz7NLScCGAAST5SHdnjaDJQBDq0M2muyRTpf2VQhw6StBg2ALiu73zSJQ4fMVLA+0uBhBHAle7Wg+2kSg==",
+ "version": "4.3.2",
+ "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.3.2.tgz",
+ "integrity": "sha512-DA5a1C0gD/pLOvhv33YMrbf2FK3oUzwNl9oOJqE4XVjuEtt6XIakRcsd7eLiOSPkp1kTRQGICTA8cKra/vFbjw==",
"dev": true
},
"node_modules/@ampproject/remapping": {
@@ -123,12 +123,12 @@
}
},
"node_modules/@babel/code-frame": {
- "version": "7.22.13",
- "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz",
- "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==",
+ "version": "7.23.5",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz",
+ "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==",
"dev": true,
"dependencies": {
- "@babel/highlight": "^7.22.13",
+ "@babel/highlight": "^7.23.4",
"chalk": "^2.4.2"
},
"engines": {
@@ -136,30 +136,30 @@
}
},
"node_modules/@babel/compat-data": {
- "version": "7.23.3",
- "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.3.tgz",
- "integrity": "sha512-BmR4bWbDIoFJmJ9z2cZ8Gmm2MXgEDgjdWgpKmKWUt54UGFJdlj31ECtbaDvCG/qVdG3AQ1SfpZEs01lUFbzLOQ==",
+ "version": "7.23.5",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.5.tgz",
+ "integrity": "sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==",
"dev": true,
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/core": {
- "version": "7.23.3",
- "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.3.tgz",
- "integrity": "sha512-Jg+msLuNuCJDyBvFv5+OKOUjWMZgd85bKjbICd3zWrKAo+bJ49HJufi7CQE0q0uR8NGyO6xkCACScNqyjHSZew==",
+ "version": "7.23.5",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.5.tgz",
+ "integrity": "sha512-Cwc2XjUrG4ilcfOw4wBAK+enbdgwAcAJCfGUItPBKR7Mjw4aEfAFYrLxeRp4jWgtNIKn3n2AlBOfwwafl+42/g==",
"dev": true,
"dependencies": {
"@ampproject/remapping": "^2.2.0",
- "@babel/code-frame": "^7.22.13",
- "@babel/generator": "^7.23.3",
+ "@babel/code-frame": "^7.23.5",
+ "@babel/generator": "^7.23.5",
"@babel/helper-compilation-targets": "^7.22.15",
"@babel/helper-module-transforms": "^7.23.3",
- "@babel/helpers": "^7.23.2",
- "@babel/parser": "^7.23.3",
+ "@babel/helpers": "^7.23.5",
+ "@babel/parser": "^7.23.5",
"@babel/template": "^7.22.15",
- "@babel/traverse": "^7.23.3",
- "@babel/types": "^7.23.3",
+ "@babel/traverse": "^7.23.5",
+ "@babel/types": "^7.23.5",
"convert-source-map": "^2.0.0",
"debug": "^4.1.0",
"gensync": "^1.0.0-beta.2",
@@ -193,12 +193,12 @@
}
},
"node_modules/@babel/generator": {
- "version": "7.23.3",
- "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.3.tgz",
- "integrity": "sha512-keeZWAV4LU3tW0qRi19HRpabC/ilM0HRBBzf9/k8FFiG4KVpiv0FIy4hHfLfFQZNhziCTPTmd59zoyv6DNISzg==",
+ "version": "7.23.5",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.5.tgz",
+ "integrity": "sha512-BPssCHrBD+0YrxviOa3QzpqwhNIXKEtOa2jQrm4FlmkC2apYgRnQcmPWiGZDlGxiNtltnUFolMe8497Esry+jA==",
"dev": true,
"dependencies": {
- "@babel/types": "^7.23.3",
+ "@babel/types": "^7.23.5",
"@jridgewell/gen-mapping": "^0.3.2",
"@jridgewell/trace-mapping": "^0.3.17",
"jsesc": "^2.5.1"
@@ -248,17 +248,17 @@
}
},
"node_modules/@babel/helper-create-class-features-plugin": {
- "version": "7.22.15",
- "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.15.tgz",
- "integrity": "sha512-jKkwA59IXcvSaiK2UN45kKwSC9o+KuoXsBDvHvU/7BecYIp8GQ2UwrVvFgJASUT+hBnwJx6MhvMCuMzwZZ7jlg==",
+ "version": "7.23.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.23.5.tgz",
+ "integrity": "sha512-QELlRWxSpgdwdJzSJn4WAhKC+hvw/AtHbbrIoncKHkhKKR/luAlKkgBDcri1EzWAo8f8VvYVryEHN4tax/V67A==",
"dev": true,
"dependencies": {
"@babel/helper-annotate-as-pure": "^7.22.5",
- "@babel/helper-environment-visitor": "^7.22.5",
- "@babel/helper-function-name": "^7.22.5",
- "@babel/helper-member-expression-to-functions": "^7.22.15",
+ "@babel/helper-environment-visitor": "^7.22.20",
+ "@babel/helper-function-name": "^7.23.0",
+ "@babel/helper-member-expression-to-functions": "^7.23.0",
"@babel/helper-optimise-call-expression": "^7.22.5",
- "@babel/helper-replace-supers": "^7.22.9",
+ "@babel/helper-replace-supers": "^7.22.20",
"@babel/helper-skip-transparent-expression-wrappers": "^7.22.5",
"@babel/helper-split-export-declaration": "^7.22.6",
"semver": "^6.3.1"
@@ -472,9 +472,9 @@
}
},
"node_modules/@babel/helper-string-parser": {
- "version": "7.22.5",
- "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz",
- "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==",
+ "version": "7.23.4",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz",
+ "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==",
"dev": true,
"engines": {
"node": ">=6.9.0"
@@ -490,9 +490,9 @@
}
},
"node_modules/@babel/helper-validator-option": {
- "version": "7.22.15",
- "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.15.tgz",
- "integrity": "sha512-bMn7RmyFjY/mdECUbgn9eoSY4vqvacUnS9i9vGAGttgFWesO6B4CYWA7XlpbWgBt71iv/hfbPlynohStqnu5hA==",
+ "version": "7.23.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz",
+ "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==",
"dev": true,
"engines": {
"node": ">=6.9.0"
@@ -513,23 +513,23 @@
}
},
"node_modules/@babel/helpers": {
- "version": "7.23.2",
- "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.2.tgz",
- "integrity": "sha512-lzchcp8SjTSVe/fPmLwtWVBFC7+Tbn8LGHDVfDp9JGxpAY5opSaEFgt8UQvrnECWOTdji2mOWMz1rOhkHscmGQ==",
+ "version": "7.23.5",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.5.tgz",
+ "integrity": "sha512-oO7us8FzTEsG3U6ag9MfdF1iA/7Z6dz+MtFhifZk8C8o453rGJFFWUP1t+ULM9TUIAzC9uxXEiXjOiVMyd7QPg==",
"dev": true,
"dependencies": {
"@babel/template": "^7.22.15",
- "@babel/traverse": "^7.23.2",
- "@babel/types": "^7.23.0"
+ "@babel/traverse": "^7.23.5",
+ "@babel/types": "^7.23.5"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/highlight": {
- "version": "7.22.20",
- "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz",
- "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==",
+ "version": "7.23.4",
+ "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz",
+ "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==",
"dev": true,
"dependencies": {
"@babel/helper-validator-identifier": "^7.22.20",
@@ -541,9 +541,9 @@
}
},
"node_modules/@babel/parser": {
- "version": "7.23.3",
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.3.tgz",
- "integrity": "sha512-uVsWNvlVsIninV2prNz/3lHCb+5CJ+e+IUBfbjToAHODtfGYLfCFuY4AU7TskI+dAKk+njsPiBjq1gKTvZOBaw==",
+ "version": "7.23.5",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.5.tgz",
+ "integrity": "sha512-hOOqoiNXrmGdFbhgCzu6GiURxUgM27Xwd/aPuu8RfHEZPBzL1Z54okAHAQjXfcQNwvrlkAmAp4SlRTZ45vlthQ==",
"dev": true,
"bin": {
"parser": "bin/babel-parser.js"
@@ -921,9 +921,9 @@
}
},
"node_modules/@babel/plugin-transform-async-generator-functions": {
- "version": "7.23.3",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.23.3.tgz",
- "integrity": "sha512-59GsVNavGxAXCDDbakWSMJhajASb4kBCqDjqJsv+p5nKdbz7istmZ3HrX3L2LuiI80+zsOADCvooqQH3qGCucQ==",
+ "version": "7.23.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.23.4.tgz",
+ "integrity": "sha512-efdkfPhHYTtn0G6n2ddrESE91fgXxjlqLsnUtPWnJs4a4mZIbUaK7ffqKIIUKXSHwcDvaCVX6GXkaJJFqtX7jw==",
"dev": true,
"dependencies": {
"@babel/helper-environment-visitor": "^7.22.20",
@@ -971,9 +971,9 @@
}
},
"node_modules/@babel/plugin-transform-block-scoping": {
- "version": "7.23.3",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.23.3.tgz",
- "integrity": "sha512-QPZxHrThbQia7UdvfpaRRlq/J9ciz1J4go0k+lPBXbgaNeY7IQrBj/9ceWjvMMI07/ZBzHl/F0R/2K0qH7jCVw==",
+ "version": "7.23.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.23.4.tgz",
+ "integrity": "sha512-0QqbP6B6HOh7/8iNR4CQU2Th/bbRtBp4KS9vcaZd1fZ0wSh5Fyssg0UCIHwxh+ka+pNDREbVLQnHCMHKZfPwfw==",
"dev": true,
"dependencies": {
"@babel/helper-plugin-utils": "^7.22.5"
@@ -1002,9 +1002,9 @@
}
},
"node_modules/@babel/plugin-transform-class-static-block": {
- "version": "7.23.3",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.23.3.tgz",
- "integrity": "sha512-PENDVxdr7ZxKPyi5Ffc0LjXdnJyrJxyqF5T5YjlVg4a0VFfQHW0r8iAtRiDXkfHlu1wwcvdtnndGYIeJLSuRMQ==",
+ "version": "7.23.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.23.4.tgz",
+ "integrity": "sha512-nsWu/1M+ggti1SOALj3hfx5FXzAY06fwPJsUZD4/A5e1bWi46VUIWtD+kOX6/IdhXGsXBWllLFDSnqSCdUNydQ==",
"dev": true,
"dependencies": {
"@babel/helper-create-class-features-plugin": "^7.22.15",
@@ -1019,9 +1019,9 @@
}
},
"node_modules/@babel/plugin-transform-classes": {
- "version": "7.23.3",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.23.3.tgz",
- "integrity": "sha512-FGEQmugvAEu2QtgtU0uTASXevfLMFfBeVCIIdcQhn/uBQsMTjBajdnAtanQlOcuihWh10PZ7+HWvc7NtBwP74w==",
+ "version": "7.23.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.23.5.tgz",
+ "integrity": "sha512-jvOTR4nicqYC9yzOHIhXG5emiFEOpappSJAl73SDSEDcybD+Puuze8Tnpb9p9qEyYup24tq891gkaygIFvWDqg==",
"dev": true,
"dependencies": {
"@babel/helper-annotate-as-pure": "^7.22.5",
@@ -1104,9 +1104,9 @@
}
},
"node_modules/@babel/plugin-transform-dynamic-import": {
- "version": "7.23.3",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.23.3.tgz",
- "integrity": "sha512-vTG+cTGxPFou12Rj7ll+eD5yWeNl5/8xvQvF08y5Gv3v4mZQoyFf8/n9zg4q5vvCWt5jmgymfzMAldO7orBn7A==",
+ "version": "7.23.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.23.4.tgz",
+ "integrity": "sha512-V6jIbLhdJK86MaLh4Jpghi8ho5fGzt3imHOBu/x0jlBaPYqDoWz4RDXjmMOfnh+JWNaQleEAByZLV0QzBT4YQQ==",
"dev": true,
"dependencies": {
"@babel/helper-plugin-utils": "^7.22.5",
@@ -1136,9 +1136,9 @@
}
},
"node_modules/@babel/plugin-transform-export-namespace-from": {
- "version": "7.23.3",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.23.3.tgz",
- "integrity": "sha512-yCLhW34wpJWRdTxxWtFZASJisihrfyMOTOQexhVzA78jlU+dH7Dw+zQgcPepQ5F3C6bAIiblZZ+qBggJdHiBAg==",
+ "version": "7.23.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.23.4.tgz",
+ "integrity": "sha512-GzuSBcKkx62dGzZI1WVgTWvkkz84FZO5TC5T8dl/Tht/rAla6Dg/Mz9Yhypg+ezVACf/rgDuQt3kbWEv7LdUDQ==",
"dev": true,
"dependencies": {
"@babel/helper-plugin-utils": "^7.22.5",
@@ -1184,9 +1184,9 @@
}
},
"node_modules/@babel/plugin-transform-json-strings": {
- "version": "7.23.3",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.23.3.tgz",
- "integrity": "sha512-H9Ej2OiISIZowZHaBwF0tsJOih1PftXJtE8EWqlEIwpc7LMTGq0rPOrywKLQ4nefzx8/HMR0D3JGXoMHYvhi0A==",
+ "version": "7.23.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.23.4.tgz",
+ "integrity": "sha512-81nTOqM1dMwZ/aRXQ59zVubN9wHGqk6UtqRK+/q+ciXmRy8fSolhGVvG09HHRGo4l6fr/c4ZhXUQH0uFW7PZbg==",
"dev": true,
"dependencies": {
"@babel/helper-plugin-utils": "^7.22.5",
@@ -1215,9 +1215,9 @@
}
},
"node_modules/@babel/plugin-transform-logical-assignment-operators": {
- "version": "7.23.3",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.23.3.tgz",
- "integrity": "sha512-+pD5ZbxofyOygEp+zZAfujY2ShNCXRpDRIPOiBmTO693hhyOEteZgl876Xs9SAHPQpcV0vz8LvA/T+w8AzyX8A==",
+ "version": "7.23.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.23.4.tgz",
+ "integrity": "sha512-Mc/ALf1rmZTP4JKKEhUwiORU+vcfarFVLfcFiolKUo6sewoxSEgl36ak5t+4WamRsNr6nzjZXQjM35WsU+9vbg==",
"dev": true,
"dependencies": {
"@babel/helper-plugin-utils": "^7.22.5",
@@ -1344,9 +1344,9 @@
}
},
"node_modules/@babel/plugin-transform-nullish-coalescing-operator": {
- "version": "7.23.3",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.23.3.tgz",
- "integrity": "sha512-xzg24Lnld4DYIdysyf07zJ1P+iIfJpxtVFOzX4g+bsJ3Ng5Le7rXx9KwqKzuyaUeRnt+I1EICwQITqc0E2PmpA==",
+ "version": "7.23.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.23.4.tgz",
+ "integrity": "sha512-jHE9EVVqHKAQx+VePv5LLGHjmHSJR76vawFPTdlxR/LVJPfOEGxREQwQfjuZEOPTwG92X3LINSh3M40Rv4zpVA==",
"dev": true,
"dependencies": {
"@babel/helper-plugin-utils": "^7.22.5",
@@ -1360,9 +1360,9 @@
}
},
"node_modules/@babel/plugin-transform-numeric-separator": {
- "version": "7.23.3",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.23.3.tgz",
- "integrity": "sha512-s9GO7fIBi/BLsZ0v3Rftr6Oe4t0ctJ8h4CCXfPoEJwmvAPMyNrfkOOJzm6b9PX9YXcCJWWQd/sBF/N26eBiMVw==",
+ "version": "7.23.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.23.4.tgz",
+ "integrity": "sha512-mps6auzgwjRrwKEZA05cOwuDc9FAzoyFS4ZsG/8F43bTLf/TgkJg7QXOrPO1JO599iA3qgK9MXdMGOEC8O1h6Q==",
"dev": true,
"dependencies": {
"@babel/helper-plugin-utils": "^7.22.5",
@@ -1376,9 +1376,9 @@
}
},
"node_modules/@babel/plugin-transform-object-rest-spread": {
- "version": "7.23.3",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.23.3.tgz",
- "integrity": "sha512-VxHt0ANkDmu8TANdE9Kc0rndo/ccsmfe2Cx2y5sI4hu3AukHQ5wAu4cM7j3ba8B9548ijVyclBU+nuDQftZsog==",
+ "version": "7.23.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.23.4.tgz",
+ "integrity": "sha512-9x9K1YyeQVw0iOXJlIzwm8ltobIIv7j2iLyP2jIhEbqPRQ7ScNgwQufU2I0Gq11VjyG4gI4yMXt2VFags+1N3g==",
"dev": true,
"dependencies": {
"@babel/compat-data": "^7.23.3",
@@ -1411,9 +1411,9 @@
}
},
"node_modules/@babel/plugin-transform-optional-catch-binding": {
- "version": "7.23.3",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.23.3.tgz",
- "integrity": "sha512-LxYSb0iLjUamfm7f1D7GpiS4j0UAC8AOiehnsGAP8BEsIX8EOi3qV6bbctw8M7ZvLtcoZfZX5Z7rN9PlWk0m5A==",
+ "version": "7.23.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.23.4.tgz",
+ "integrity": "sha512-XIq8t0rJPHf6Wvmbn9nFxU6ao4c7WhghTR5WyV8SrJfUFzyxhCm4nhC+iAp3HFhbAKLfYpgzhJ6t4XCtVwqO5A==",
"dev": true,
"dependencies": {
"@babel/helper-plugin-utils": "^7.22.5",
@@ -1427,9 +1427,9 @@
}
},
"node_modules/@babel/plugin-transform-optional-chaining": {
- "version": "7.23.3",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.23.3.tgz",
- "integrity": "sha512-zvL8vIfIUgMccIAK1lxjvNv572JHFJIKb4MWBz5OGdBQA0fB0Xluix5rmOby48exiJc987neOmP/m9Fnpkz3Tg==",
+ "version": "7.23.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.23.4.tgz",
+ "integrity": "sha512-ZU8y5zWOfjM5vZ+asjgAPwDaBjJzgufjES89Rs4Lpq63O300R/kOz30WCLo6BxxX6QVEilwSlpClnG5cZaikTA==",
"dev": true,
"dependencies": {
"@babel/helper-plugin-utils": "^7.22.5",
@@ -1475,9 +1475,9 @@
}
},
"node_modules/@babel/plugin-transform-private-property-in-object": {
- "version": "7.23.3",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.23.3.tgz",
- "integrity": "sha512-a5m2oLNFyje2e/rGKjVfAELTVI5mbA0FeZpBnkOWWV7eSmKQ+T/XW0Vf+29ScLzSxX+rnsarvU0oie/4m6hkxA==",
+ "version": "7.23.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.23.4.tgz",
+ "integrity": "sha512-9G3K1YqTq3F4Vt88Djx1UZ79PDyj+yKRnUy7cZGSMe+a7jkwD259uKKuUzQlPkGam7R+8RJwh5z4xO27fA1o2A==",
"dev": true,
"dependencies": {
"@babel/helper-annotate-as-pure": "^7.22.5",
@@ -1538,16 +1538,16 @@
}
},
"node_modules/@babel/plugin-transform-react-jsx": {
- "version": "7.22.15",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.22.15.tgz",
- "integrity": "sha512-oKckg2eZFa8771O/5vi7XeTvmM6+O9cxZu+kanTU7tD4sin5nO/G8jGJhq8Hvt2Z0kUoEDRayuZLaUlYl8QuGA==",
+ "version": "7.23.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.23.4.tgz",
+ "integrity": "sha512-5xOpoPguCZCRbo/JeHlloSkTA8Bld1J/E1/kLfD1nsuiW1m8tduTA1ERCgIZokDflX/IBzKcqR3l7VlRgiIfHA==",
"dev": true,
"dependencies": {
"@babel/helper-annotate-as-pure": "^7.22.5",
"@babel/helper-module-imports": "^7.22.15",
"@babel/helper-plugin-utils": "^7.22.5",
- "@babel/plugin-syntax-jsx": "^7.22.5",
- "@babel/types": "^7.22.15"
+ "@babel/plugin-syntax-jsx": "^7.23.3",
+ "@babel/types": "^7.23.4"
},
"engines": {
"node": ">=6.9.0"
@@ -1695,13 +1695,13 @@
}
},
"node_modules/@babel/plugin-transform-typescript": {
- "version": "7.23.3",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.23.3.tgz",
- "integrity": "sha512-ogV0yWnq38CFwH20l2Afz0dfKuZBx9o/Y2Rmh5vuSS0YD1hswgEgTfyTzuSrT2q9btmHRSqYoSfwFUVaC1M1Jw==",
+ "version": "7.23.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.23.5.tgz",
+ "integrity": "sha512-2fMkXEJkrmwgu2Bsv1Saxgj30IXZdJ+84lQcKKI7sm719oXs0BBw2ZENKdJdR1PjWndgLCEBNXJOri0fk7RYQA==",
"dev": true,
"dependencies": {
"@babel/helper-annotate-as-pure": "^7.22.5",
- "@babel/helper-create-class-features-plugin": "^7.22.15",
+ "@babel/helper-create-class-features-plugin": "^7.23.5",
"@babel/helper-plugin-utils": "^7.22.5",
"@babel/plugin-syntax-typescript": "^7.23.3"
},
@@ -1776,15 +1776,15 @@
}
},
"node_modules/@babel/preset-env": {
- "version": "7.23.3",
- "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.23.3.tgz",
- "integrity": "sha512-ovzGc2uuyNfNAs/jyjIGxS8arOHS5FENZaNn4rtE7UdKMMkqHCvboHfcuhWLZNX5cB44QfcGNWjaevxMzzMf+Q==",
+ "version": "7.23.5",
+ "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.23.5.tgz",
+ "integrity": "sha512-0d/uxVD6tFGWXGDSfyMD1p2otoaKmu6+GD+NfAx0tMaH+dxORnp7T9TaVQ6mKyya7iBtCIVxHjWT7MuzzM9z+A==",
"dev": true,
"dependencies": {
- "@babel/compat-data": "^7.23.3",
+ "@babel/compat-data": "^7.23.5",
"@babel/helper-compilation-targets": "^7.22.15",
"@babel/helper-plugin-utils": "^7.22.5",
- "@babel/helper-validator-option": "^7.22.15",
+ "@babel/helper-validator-option": "^7.23.5",
"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.23.3",
"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.23.3",
"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.23.3",
@@ -1808,25 +1808,25 @@
"@babel/plugin-syntax-top-level-await": "^7.14.5",
"@babel/plugin-syntax-unicode-sets-regex": "^7.18.6",
"@babel/plugin-transform-arrow-functions": "^7.23.3",
- "@babel/plugin-transform-async-generator-functions": "^7.23.3",
+ "@babel/plugin-transform-async-generator-functions": "^7.23.4",
"@babel/plugin-transform-async-to-generator": "^7.23.3",
"@babel/plugin-transform-block-scoped-functions": "^7.23.3",
- "@babel/plugin-transform-block-scoping": "^7.23.3",
+ "@babel/plugin-transform-block-scoping": "^7.23.4",
"@babel/plugin-transform-class-properties": "^7.23.3",
- "@babel/plugin-transform-class-static-block": "^7.23.3",
- "@babel/plugin-transform-classes": "^7.23.3",
+ "@babel/plugin-transform-class-static-block": "^7.23.4",
+ "@babel/plugin-transform-classes": "^7.23.5",
"@babel/plugin-transform-computed-properties": "^7.23.3",
"@babel/plugin-transform-destructuring": "^7.23.3",
"@babel/plugin-transform-dotall-regex": "^7.23.3",
"@babel/plugin-transform-duplicate-keys": "^7.23.3",
- "@babel/plugin-transform-dynamic-import": "^7.23.3",
+ "@babel/plugin-transform-dynamic-import": "^7.23.4",
"@babel/plugin-transform-exponentiation-operator": "^7.23.3",
- "@babel/plugin-transform-export-namespace-from": "^7.23.3",
+ "@babel/plugin-transform-export-namespace-from": "^7.23.4",
"@babel/plugin-transform-for-of": "^7.23.3",
"@babel/plugin-transform-function-name": "^7.23.3",
- "@babel/plugin-transform-json-strings": "^7.23.3",
+ "@babel/plugin-transform-json-strings": "^7.23.4",
"@babel/plugin-transform-literals": "^7.23.3",
- "@babel/plugin-transform-logical-assignment-operators": "^7.23.3",
+ "@babel/plugin-transform-logical-assignment-operators": "^7.23.4",
"@babel/plugin-transform-member-expression-literals": "^7.23.3",
"@babel/plugin-transform-modules-amd": "^7.23.3",
"@babel/plugin-transform-modules-commonjs": "^7.23.3",
@@ -1834,15 +1834,15 @@
"@babel/plugin-transform-modules-umd": "^7.23.3",
"@babel/plugin-transform-named-capturing-groups-regex": "^7.22.5",
"@babel/plugin-transform-new-target": "^7.23.3",
- "@babel/plugin-transform-nullish-coalescing-operator": "^7.23.3",
- "@babel/plugin-transform-numeric-separator": "^7.23.3",
- "@babel/plugin-transform-object-rest-spread": "^7.23.3",
+ "@babel/plugin-transform-nullish-coalescing-operator": "^7.23.4",
+ "@babel/plugin-transform-numeric-separator": "^7.23.4",
+ "@babel/plugin-transform-object-rest-spread": "^7.23.4",
"@babel/plugin-transform-object-super": "^7.23.3",
- "@babel/plugin-transform-optional-catch-binding": "^7.23.3",
- "@babel/plugin-transform-optional-chaining": "^7.23.3",
+ "@babel/plugin-transform-optional-catch-binding": "^7.23.4",
+ "@babel/plugin-transform-optional-chaining": "^7.23.4",
"@babel/plugin-transform-parameters": "^7.23.3",
"@babel/plugin-transform-private-methods": "^7.23.3",
- "@babel/plugin-transform-private-property-in-object": "^7.23.3",
+ "@babel/plugin-transform-private-property-in-object": "^7.23.4",
"@babel/plugin-transform-property-literals": "^7.23.3",
"@babel/plugin-transform-regenerator": "^7.23.3",
"@babel/plugin-transform-reserved-words": "^7.23.3",
@@ -1929,9 +1929,9 @@
"dev": true
},
"node_modules/@babel/runtime": {
- "version": "7.23.2",
- "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.2.tgz",
- "integrity": "sha512-mM8eg4yl5D6i3lu2QKPuPH4FArvJ8KhTofbE7jwMUv9KX5mBvwPAqnV3MlyBNqdp9RyRKP6Yck8TrfYrPvX3bg==",
+ "version": "7.23.5",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.5.tgz",
+ "integrity": "sha512-NdUTHcPe4C99WxPub+K9l9tK5/lV4UXIoaHSYgzco9BCyjKAAwzdBI+wWtYqHt7LJdbo74ZjRPJgzVweq1sz0w==",
"dev": true,
"dependencies": {
"regenerator-runtime": "^0.14.0"
@@ -1955,19 +1955,19 @@
}
},
"node_modules/@babel/traverse": {
- "version": "7.23.3",
- "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.3.tgz",
- "integrity": "sha512-+K0yF1/9yR0oHdE0StHuEj3uTPzwwbrLGfNOndVJVV2TqA5+j3oljJUb4nmB954FLGjNem976+B+eDuLIjesiQ==",
+ "version": "7.23.5",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.5.tgz",
+ "integrity": "sha512-czx7Xy5a6sapWWRx61m1Ke1Ra4vczu1mCTtJam5zRTBOonfdJ+S/B6HYmGYu3fJtr8GGET3si6IhgWVBhJ/m8w==",
"dev": true,
"dependencies": {
- "@babel/code-frame": "^7.22.13",
- "@babel/generator": "^7.23.3",
+ "@babel/code-frame": "^7.23.5",
+ "@babel/generator": "^7.23.5",
"@babel/helper-environment-visitor": "^7.22.20",
"@babel/helper-function-name": "^7.23.0",
"@babel/helper-hoist-variables": "^7.22.5",
"@babel/helper-split-export-declaration": "^7.22.6",
- "@babel/parser": "^7.23.3",
- "@babel/types": "^7.23.3",
+ "@babel/parser": "^7.23.5",
+ "@babel/types": "^7.23.5",
"debug": "^4.1.0",
"globals": "^11.1.0"
},
@@ -1976,12 +1976,12 @@
}
},
"node_modules/@babel/types": {
- "version": "7.23.3",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.3.tgz",
- "integrity": "sha512-OZnvoH2l8PK5eUvEcUyCt/sXgr/h+UWpVuBbOljwcrAgUl6lpchoQ++PHGyQy1AtYnVA6CEq3y5xeEI10brpXw==",
+ "version": "7.23.5",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.5.tgz",
+ "integrity": "sha512-ON5kSOJwVO6xXVRTvOI0eOnWe7VdUcIpsovGo9U/Br4Ie4UVFQTboO2cYnDhAGU6Fp+UxSiT+pMft0SMHfuq6w==",
"dev": true,
"dependencies": {
- "@babel/helper-string-parser": "^7.22.5",
+ "@babel/helper-string-parser": "^7.23.4",
"@babel/helper-validator-identifier": "^7.22.20",
"to-fast-properties": "^2.0.0"
},
@@ -1996,16 +1996,16 @@
"dev": true
},
"node_modules/@cspell/cspell-bundled-dicts": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/@cspell/cspell-bundled-dicts/-/cspell-bundled-dicts-8.0.0.tgz",
- "integrity": "sha512-Phbb1ij1TQQuqxuuvxf5P6fvV9U+EVoATNLmDqFHvRZfUyuhgbJuCMzIPeBx4GfTTDWlPs51FYRvZ/Q8xBHsyA==",
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/@cspell/cspell-bundled-dicts/-/cspell-bundled-dicts-8.1.0.tgz",
+ "integrity": "sha512-o/R/kR1QO9SQV2hUroaguTlHD6MDDtrVY6Xj5eG0loM7T0Pm3TEdlGYQ0LP6O9/CfUiHTntIFUM+PJ999+LuHQ==",
"dev": true,
"dependencies": {
"@cspell/dict-ada": "^4.0.2",
"@cspell/dict-aws": "^4.0.0",
"@cspell/dict-bash": "^4.1.2",
- "@cspell/dict-companies": "^3.0.27",
- "@cspell/dict-cpp": "^5.0.9",
+ "@cspell/dict-companies": "^3.0.28",
+ "@cspell/dict-cpp": "^5.0.10",
"@cspell/dict-cryptocurrencies": "^4.0.0",
"@cspell/dict-csharp": "^4.0.2",
"@cspell/dict-css": "^4.0.12",
@@ -2014,16 +2014,16 @@
"@cspell/dict-docker": "^1.1.7",
"@cspell/dict-dotnet": "^5.0.0",
"@cspell/dict-elixir": "^4.0.3",
- "@cspell/dict-en_us": "^4.3.11",
+ "@cspell/dict-en_us": "^4.3.12",
"@cspell/dict-en-common-misspellings": "^1.0.2",
"@cspell/dict-en-gb": "1.1.33",
- "@cspell/dict-filetypes": "^3.0.2",
+ "@cspell/dict-filetypes": "^3.0.3",
"@cspell/dict-fonts": "^4.0.0",
"@cspell/dict-fsharp": "^1.0.1",
"@cspell/dict-fullstack": "^3.1.5",
"@cspell/dict-gaming-terms": "^1.0.4",
"@cspell/dict-git": "^2.0.0",
- "@cspell/dict-golang": "^6.0.4",
+ "@cspell/dict-golang": "^6.0.5",
"@cspell/dict-haskell": "^4.0.1",
"@cspell/dict-html": "^4.0.5",
"@cspell/dict-html-symbol-entities": "^4.0.0",
@@ -2034,7 +2034,7 @@
"@cspell/dict-lua": "^4.0.2",
"@cspell/dict-makefile": "^1.0.0",
"@cspell/dict-node": "^4.0.3",
- "@cspell/dict-npm": "^5.0.12",
+ "@cspell/dict-npm": "^5.0.13",
"@cspell/dict-php": "^4.0.4",
"@cspell/dict-powershell": "^5.0.2",
"@cspell/dict-public-licenses": "^2.0.5",
@@ -2043,7 +2043,7 @@
"@cspell/dict-ruby": "^5.0.1",
"@cspell/dict-rust": "^4.0.1",
"@cspell/dict-scala": "^5.0.0",
- "@cspell/dict-software-terms": "^3.3.9",
+ "@cspell/dict-software-terms": "^3.3.11",
"@cspell/dict-sql": "^2.1.2",
"@cspell/dict-svelte": "^1.0.2",
"@cspell/dict-swift": "^2.0.1",
@@ -2055,51 +2055,51 @@
}
},
"node_modules/@cspell/cspell-json-reporter": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/@cspell/cspell-json-reporter/-/cspell-json-reporter-8.0.0.tgz",
- "integrity": "sha512-1ltK5N4xMGWjDSIkU+GJd3rXV8buXgO/lAgnpM1RhKWqAmG+u0k6pnhk2vIo/4qZQpgfK0l3J3h/Ky2FcE95vA==",
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/@cspell/cspell-json-reporter/-/cspell-json-reporter-8.1.0.tgz",
+ "integrity": "sha512-Iss9dq5XBc5wYADv/Z59W4DgRQYs8BSHNVD6+LbQctuqmeJAte426/oi4x0Y76AJtEe0N6BZouj8HXykovwP5w==",
"dev": true,
"dependencies": {
- "@cspell/cspell-types": "8.0.0"
+ "@cspell/cspell-types": "8.1.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@cspell/cspell-pipe": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/@cspell/cspell-pipe/-/cspell-pipe-8.0.0.tgz",
- "integrity": "sha512-1MH+9q3AmbzwK1BYhSGla8e4MAAYzzPApGvv8eyv0rWDmgmDTkGqJPTTvYj1wFvll5ximQ5OolpPQGv3JoWvtQ==",
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/@cspell/cspell-pipe/-/cspell-pipe-8.1.0.tgz",
+ "integrity": "sha512-HDNX7MFAPAJ9acyYBa1bG+P4WiHHMFNYeywYBf3h6ScVhHobAqnhqS6b8R7MVhVRivwnKIQPG3zK7UpcwfyRcw==",
"dev": true,
"engines": {
"node": ">=18"
}
},
"node_modules/@cspell/cspell-resolver": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/@cspell/cspell-resolver/-/cspell-resolver-8.0.0.tgz",
- "integrity": "sha512-gtALHFLT2vSZ7BZlIg26AY3W9gkiqxPGE75iypWz06JHJs05ngnAR+h6VOu0+rmgx98hNfzPPEh4g+Tjm8Ma0A==",
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/@cspell/cspell-resolver/-/cspell-resolver-8.1.0.tgz",
+ "integrity": "sha512-nlppKh2o6g0zz+oIQ/dZB+oFQFf8lvn3mJKBpDwoeQY7/o9ZORPibXjtqXM83OhhdpoUVuk+3RFMsnFBBffa2Q==",
"dev": true,
"dependencies": {
- "global-dirs": "^3.0.1"
+ "global-directory": "^4.0.1"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@cspell/cspell-service-bus": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/@cspell/cspell-service-bus/-/cspell-service-bus-8.0.0.tgz",
- "integrity": "sha512-1EYhIHoZnhxpfEp6Bno6yVWYBuYfaQrwIfeDMntnezUcSmi7RyroQEcp5U7sLv69vhRD2c81o7r8iUaAbPSmIg==",
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/@cspell/cspell-service-bus/-/cspell-service-bus-8.1.0.tgz",
+ "integrity": "sha512-9Enayhkef732f15kHgiUe4QKyJgKk1dcZ4EFq4eyzUUDFF/eBv6qTQo5k2juUhPIjaKosqqMBHg4ffXcpkhr+Q==",
"dev": true,
"engines": {
"node": ">=18"
}
},
"node_modules/@cspell/cspell-types": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/@cspell/cspell-types/-/cspell-types-8.0.0.tgz",
- "integrity": "sha512-dPdxQI8dLJoJEjylaPYfCJNnm2XNMYPuowHE2FMcsnFR9hEchQAhnKVc/aD63IUYnUtUrPxPlUJdoAoj569e+g==",
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/@cspell/cspell-types/-/cspell-types-8.1.0.tgz",
+ "integrity": "sha512-1SxBjQdZtVjrTs3Ftw5I3nNpuDjdpsFMvfbbt6EnxqMpmZiUwkqxLCKla0pEy5R9CZcFFlntlOTMTmNsIkgmWg==",
"dev": true,
"engines": {
"node": ">=18"
@@ -2124,15 +2124,15 @@
"dev": true
},
"node_modules/@cspell/dict-companies": {
- "version": "3.0.27",
- "resolved": "https://registry.npmjs.org/@cspell/dict-companies/-/dict-companies-3.0.27.tgz",
- "integrity": "sha512-gaPR/luf+4oKGyxvW4GbxGGPdHiC5kj/QefnmQqrLFrLiCSXMZg5/NL+Lr4E5lcHsd35meX61svITQAvsT7lyQ==",
+ "version": "3.0.28",
+ "resolved": "https://registry.npmjs.org/@cspell/dict-companies/-/dict-companies-3.0.28.tgz",
+ "integrity": "sha512-UinHkMYB/1pUkLKm1PGIm9PBFYxeAa6YvbB1Rq/RAAlrs0WDwiDBr3BAYdxydukG1IqqwT5z9WtU+8D/yV/5lw==",
"dev": true
},
"node_modules/@cspell/dict-cpp": {
- "version": "5.0.9",
- "resolved": "https://registry.npmjs.org/@cspell/dict-cpp/-/dict-cpp-5.0.9.tgz",
- "integrity": "sha512-ql9WPNp8c+fhdpVpjpZEUWmxBHJXs9CJuiVVfW/iwv5AX7VuMHyEwid+9/6nA8qnCxkUQ5pW83Ums1lLjn8ScA==",
+ "version": "5.0.10",
+ "resolved": "https://registry.npmjs.org/@cspell/dict-cpp/-/dict-cpp-5.0.10.tgz",
+ "integrity": "sha512-WCRuDrkFdpmeIR6uXQYKU9loMQKNFS4bUhtHdv5fu4qVyJSh3k/kgmtTm1h1BDTj8EwPRc/RGxS+9Z3b2mnabA==",
"dev": true
},
"node_modules/@cspell/dict-cryptocurrencies": {
@@ -2190,9 +2190,9 @@
"dev": true
},
"node_modules/@cspell/dict-en_us": {
- "version": "4.3.11",
- "resolved": "https://registry.npmjs.org/@cspell/dict-en_us/-/dict-en_us-4.3.11.tgz",
- "integrity": "sha512-GhdavZFlS2YbUNcRtPbgJ9j6aUyq116LmDQ2/Q5SpQxJ5/6vVs8Yj5WxV1JD+Zh/Zim1NJDcneTOuLsUGi+Czw==",
+ "version": "4.3.12",
+ "resolved": "https://registry.npmjs.org/@cspell/dict-en_us/-/dict-en_us-4.3.12.tgz",
+ "integrity": "sha512-1bsUxFjgxF30FTzcU5uvmCvH3lyqVKR9dbwsJhomBlUM97f0edrd6590SiYBXDm7ruE68m3lJd4vs0Ev2D6FtQ==",
"dev": true
},
"node_modules/@cspell/dict-en-common-misspellings": {
@@ -2208,9 +2208,9 @@
"dev": true
},
"node_modules/@cspell/dict-filetypes": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/@cspell/dict-filetypes/-/dict-filetypes-3.0.2.tgz",
- "integrity": "sha512-StoC0wPmFNav6F6P8/FYFN1BpZfPgOmktb8gQ9wTauelWofPeBW+A0t5ncZt9hXHtnbGDA98v4ukacV+ucbnUg==",
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/@cspell/dict-filetypes/-/dict-filetypes-3.0.3.tgz",
+ "integrity": "sha512-J9UP+qwwBLfOQ8Qg9tAsKtSY/WWmjj21uj6zXTI9hRLD1eG1uUOLcfVovAmtmVqUWziPSKMr87F6SXI3xmJXgw==",
"dev": true
},
"node_modules/@cspell/dict-fonts": {
@@ -2244,9 +2244,9 @@
"dev": true
},
"node_modules/@cspell/dict-golang": {
- "version": "6.0.4",
- "resolved": "https://registry.npmjs.org/@cspell/dict-golang/-/dict-golang-6.0.4.tgz",
- "integrity": "sha512-jOfewPEyN6U9Q80okE3b1PTYBfqZgHh7w4o271GSuAX+VKJ1lUDhdR4bPKRxSDdO5jHArw2u5C8nH2CWGuygbQ==",
+ "version": "6.0.5",
+ "resolved": "https://registry.npmjs.org/@cspell/dict-golang/-/dict-golang-6.0.5.tgz",
+ "integrity": "sha512-w4mEqGz4/wV+BBljLxduFNkMrd3rstBNDXmoX5kD4UTzIb4Sy0QybWCtg2iVT+R0KWiRRA56QKOvBsgXiddksA==",
"dev": true
},
"node_modules/@cspell/dict-haskell": {
@@ -2310,9 +2310,9 @@
"dev": true
},
"node_modules/@cspell/dict-npm": {
- "version": "5.0.12",
- "resolved": "https://registry.npmjs.org/@cspell/dict-npm/-/dict-npm-5.0.12.tgz",
- "integrity": "sha512-T/+WeQmtbxo7ad6hrdI8URptYstKJP+kXyWJZfuVJJGWJQ7yubxrI5Z5AfM+Dh/ff4xHmdzapxD9adaEQ727uw==",
+ "version": "5.0.13",
+ "resolved": "https://registry.npmjs.org/@cspell/dict-npm/-/dict-npm-5.0.13.tgz",
+ "integrity": "sha512-uPb3DlQA/FvlmzT5RjZoy7fy91mxMRZW1B+K3atVM5A/cmP1QlDaSW/iCtde5kHET1MOV7uxz+vy0Yha2OI5pQ==",
"dev": true
},
"node_modules/@cspell/dict-php": {
@@ -2367,9 +2367,9 @@
"dev": true
},
"node_modules/@cspell/dict-software-terms": {
- "version": "3.3.9",
- "resolved": "https://registry.npmjs.org/@cspell/dict-software-terms/-/dict-software-terms-3.3.9.tgz",
- "integrity": "sha512-/O3EWe0SIznx18S7J3GAXPDe7sexn3uTsf4IlnGYK9WY6ZRuEywkXCB+5/USLTGf4+QC05pkHofphdvVSifDyA==",
+ "version": "3.3.11",
+ "resolved": "https://registry.npmjs.org/@cspell/dict-software-terms/-/dict-software-terms-3.3.11.tgz",
+ "integrity": "sha512-a2Zml4G47dbQ6GDdN7+YlIWs3nFnIcJkZOLT88m/LzxjApiF7AOZLqQiKwow03hyvGSuZy8itgQZmQHoPlw2vQ==",
"dev": true
},
"node_modules/@cspell/dict-sql": {
@@ -2403,21 +2403,21 @@
"dev": true
},
"node_modules/@cspell/dynamic-import": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/@cspell/dynamic-import/-/dynamic-import-8.0.0.tgz",
- "integrity": "sha512-HNkCepopgiEGuI1QGA6ob4+ayvoSMxvAqetLxP0u1sZzc50LH2DEWwotcNrpVdzZOtERHvIBcGaQKIBEx8pPRQ==",
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/@cspell/dynamic-import/-/dynamic-import-8.1.0.tgz",
+ "integrity": "sha512-TJ1OnP0ubdVr5YTMU15rVs8R6ROuPvP/Z5lY2gtHscEsf9tZxvIt3924uMc9fTJXgNsITNWSoCzgwJYcDvGM6A==",
"dev": true,
"dependencies": {
- "import-meta-resolve": "^3.1.1"
+ "import-meta-resolve": "^4.0.0"
},
"engines": {
"node": ">=18.0"
}
},
"node_modules/@cspell/strong-weak-map": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/@cspell/strong-weak-map/-/strong-weak-map-8.0.0.tgz",
- "integrity": "sha512-fRlqPSdpdub52vFtulDgLPzGPGe75I04ScId1zOO9ABP7/ro8VmaG//m1k7hsPkm6h7FG4jWympoA3aXDAcXaA==",
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/@cspell/strong-weak-map/-/strong-weak-map-8.1.0.tgz",
+ "integrity": "sha512-yBc3ejGpx3QLbfS+Sec8ycS+lKuou5rnnpfz3aVBCnNHUPozosFuNYPFB6Iah2CBY6v6rkDCkIp5vnp1IwQzdA==",
"dev": true,
"engines": {
"node": ">=18"
@@ -2555,9 +2555,9 @@
}
},
"node_modules/@eslint/eslintrc": {
- "version": "2.1.3",
- "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.3.tgz",
- "integrity": "sha512-yZzuIG+jnVu6hNSzFEN07e8BxF3uAzYtQb6uDkaYZLo6oYZDCq454c5kB8zxnzfCYyP4MIuyBn10L0DqwujTmA==",
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz",
+ "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==",
"dev": true,
"dependencies": {
"ajv": "^6.12.4",
@@ -2645,9 +2645,9 @@
}
},
"node_modules/@eslint/js": {
- "version": "8.53.0",
- "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.53.0.tgz",
- "integrity": "sha512-Kn7K8dx/5U6+cT1yEhpX1w4PCSg0M+XyRILPgvwcEBjerFWCwQj5sbr3/VmxqV0JGHCBCzyd6LxypEuehypY1w==",
+ "version": "8.55.0",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.55.0.tgz",
+ "integrity": "sha512-qQfo2mxH5yVom1kacMtZZJFVdW+E70mqHMJvVg6WTLo+VBuQJ4TojZlfWBjK0ve5BdEeNAVxOsl/nvNMpJOaJA==",
"dev": true,
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
@@ -3572,9 +3572,9 @@
}
},
"node_modules/@jsdoc/salty": {
- "version": "0.2.6",
- "resolved": "https://registry.npmjs.org/@jsdoc/salty/-/salty-0.2.6.tgz",
- "integrity": "sha512-aA+awb5yoml8TQ3CzI5Ue7sM3VMRC4l1zJJW4fgZ8OCL1wshJZhNzaf0PL85DSnOUw6QuFgeHGD/eq/xwwAF2g==",
+ "version": "0.2.7",
+ "resolved": "https://registry.npmjs.org/@jsdoc/salty/-/salty-0.2.7.tgz",
+ "integrity": "sha512-mh8LbS9d4Jq84KLw8pzho7XC2q2/IJGiJss3xwRoLD1A+EE16SjN4PfaG4jRCzKegTFLlN0Zd8SdUPE6XdoPFg==",
"dev": true,
"dependencies": {
"lodash": "^4.17.21"
@@ -3590,9 +3590,9 @@
"dev": true
},
"node_modules/@material-symbols/svg-400": {
- "version": "0.14.0",
- "resolved": "https://registry.npmjs.org/@material-symbols/svg-400/-/svg-400-0.14.0.tgz",
- "integrity": "sha512-sbK2DZAIcLderTE1uSLvRFjoDzdiPrtmQg/NJAaKzKqxWjvhqMZjbrd5mAdzHPlTkuzM548foNXs0gcaREONSg=="
+ "version": "0.14.1",
+ "resolved": "https://registry.npmjs.org/@material-symbols/svg-400/-/svg-400-0.14.1.tgz",
+ "integrity": "sha512-ZI3fbSZ0PCT0XEsSTICY5qfvoxEQmgYrN+C49m116f1e7u7XEMz9VV6TwWmfR0blTcByRVMoYFO3kLl1McVQDQ=="
},
"node_modules/@nicolo-ribaudo/eslint-scope-5-internals": {
"version": "5.1.1-v1",
@@ -3747,9 +3747,9 @@
}
},
"node_modules/@remix-run/router": {
- "version": "1.11.0",
- "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.11.0.tgz",
- "integrity": "sha512-BHdhcWgeiudl91HvVa2wxqZjSHbheSgIiDvxrF1VjFzBzpTtuDPkOdOi3Iqvc08kXtFkLjhbS+ML9aM8mJS+wQ==",
+ "version": "1.13.1",
+ "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.13.1.tgz",
+ "integrity": "sha512-so+DHzZKsoOcoXrILB4rqDkMDy7NLMErRdOxvzvOKb507YINKUP4Di+shbTZDhSE/pBZ+vr7XGIpcOO0VLSA+Q==",
"engines": {
"node": ">=14.0.0"
}
@@ -4125,9 +4125,9 @@
}
},
"node_modules/@testing-library/jest-dom": {
- "version": "6.1.4",
- "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.1.4.tgz",
- "integrity": "sha512-wpoYrCYwSZ5/AxcrjLxJmCU6I5QAJXslEeSiMQqaWmP2Kzpd1LvF/qxmAIW2qposULGWq2gw30GgVNFLSc2Jnw==",
+ "version": "6.1.5",
+ "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.1.5.tgz",
+ "integrity": "sha512-3y04JLW+EceVPy2Em3VwNr95dOKqA8DhR0RJHhHKDZNYXcVXnEK7WIrpj4eYU8SVt/qYZ2aRWt/WgQ+grNES8g==",
"dev": true,
"dependencies": {
"@adobe/css-tools": "^4.3.1",
@@ -4233,9 +4233,9 @@
}
},
"node_modules/@testing-library/react": {
- "version": "14.1.0",
- "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-14.1.0.tgz",
- "integrity": "sha512-hcvfZEEyO0xQoZeHmUbuMs7APJCGELpilL7bY+BaJaMP57aWc6q1etFwScnoZDheYjk4ESdlzPdQ33IbsKAK/A==",
+ "version": "14.1.2",
+ "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-14.1.2.tgz",
+ "integrity": "sha512-z4p7DVBTPjKM5qDZ0t5ZjzkpSNb+fZy1u6bzO7kk8oeGagpPCAtgh4cx1syrfp7a+QWkM021jGqjJaxJJnXAZg==",
"dev": true,
"dependencies": {
"@babel/runtime": "^7.12.5",
@@ -4288,9 +4288,9 @@
"dev": true
},
"node_modules/@types/babel__core": {
- "version": "7.20.4",
- "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.4.tgz",
- "integrity": "sha512-mLnSC22IC4vcWiuObSRjrLd9XcBTGf59vUSoq2jkQDJ/QQ8PMI9rSuzE+aEV8karUMbskw07bKYoUJCKTUaygg==",
+ "version": "7.20.5",
+ "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
+ "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
"dev": true,
"dependencies": {
"@babel/parser": "^7.20.7",
@@ -4357,9 +4357,9 @@
}
},
"node_modules/@types/connect-history-api-fallback": {
- "version": "1.5.3",
- "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.3.tgz",
- "integrity": "sha512-6mfQ6iNvhSKCZJoY6sIG3m0pKkdUcweVNOLuBBKvoWGzl2yRxOJcYOTRyLKt3nxXvBLJWa6QkW//tgbIwJehmA==",
+ "version": "1.5.4",
+ "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.4.tgz",
+ "integrity": "sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw==",
"dev": true,
"dependencies": {
"@types/express-serve-static-core": "*",
@@ -4367,9 +4367,9 @@
}
},
"node_modules/@types/eslint": {
- "version": "8.44.7",
- "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.44.7.tgz",
- "integrity": "sha512-f5ORu2hcBbKei97U73mf+l9t4zTGl74IqZ0GQk4oVea/VS8tQZYkUveSYojk+frraAVYId0V2WC9O4PTNru2FQ==",
+ "version": "8.44.8",
+ "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.44.8.tgz",
+ "integrity": "sha512-4K8GavROwhrYl2QXDXm0Rv9epkA8GBFu0EI+XrrnnuCl7u8CWBRusX7fXJfanhZTDWSAL24gDI/UqXyUM0Injw==",
"dev": true,
"dependencies": {
"@types/estree": "*",
@@ -4471,9 +4471,9 @@
}
},
"node_modules/@types/jest": {
- "version": "29.5.8",
- "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.8.tgz",
- "integrity": "sha512-fXEFTxMV2Co8ZF5aYFJv+YeA08RTYJfhtN5c9JSv/mFEMe+xxjufCb+PHL+bJcMs/ebPUsBu+UNTEz+ydXrR6g==",
+ "version": "29.5.10",
+ "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.10.tgz",
+ "integrity": "sha512-tE4yxKEphEyxj9s4inideLHktW/x6DwesIwWZ9NN1FKf9zbJYsnhBoA9vrHA/IuIOKwPa5PcFBNV4lpMIOEzyQ==",
"dev": true,
"dependencies": {
"expect": "^29.0.0",
@@ -4570,18 +4570,18 @@
"dev": true
},
"node_modules/@types/node": {
- "version": "20.9.0",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-20.9.0.tgz",
- "integrity": "sha512-nekiGu2NDb1BcVofVcEKMIwzlx4NjHlcjhoxxKBNLtz15Y1z7MYf549DFvkHSId02Ax6kGwWntIBPC3l/JZcmw==",
+ "version": "20.10.3",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.3.tgz",
+ "integrity": "sha512-XJavIpZqiXID5Yxnxv3RUDKTN5b81ddNC3ecsA0SoFXz/QU8OGBwZGMomiq0zw+uuqbL/krztv/DINAQ/EV4gg==",
"dev": true,
"dependencies": {
"undici-types": "~5.26.4"
}
},
"node_modules/@types/node-forge": {
- "version": "1.3.9",
- "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.9.tgz",
- "integrity": "sha512-meK88cx/sTalPSLSoCzkiUB4VPIFHmxtXm5FaaqRDqBX2i/Sy8bJ4odsan0b20RBjPh06dAQ+OTTdnyQyhJZyQ==",
+ "version": "1.3.10",
+ "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.10.tgz",
+ "integrity": "sha512-y6PJDYN4xYBxwd22l+OVH35N+1fCYWiuC3aiP2SlXVE6Lo7SS+rSx9r89hLxrP4pn6n1lBGhHJ12pj3F3Mpttw==",
"dev": true,
"dependencies": {
"@types/node": "*"
@@ -4594,9 +4594,9 @@
"dev": true
},
"node_modules/@types/prop-types": {
- "version": "15.7.10",
- "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.10.tgz",
- "integrity": "sha512-mxSnDQxPqsZxmeShFH+uwQ4kO4gcJcGahjjMFeLbKE95IAZiiZyiEepGZjtXJ7hN/yfu0bu9xN2ajcU0JcxX6A==",
+ "version": "15.7.11",
+ "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.11.tgz",
+ "integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==",
"dev": true
},
"node_modules/@types/qs": {
@@ -4612,9 +4612,9 @@
"dev": true
},
"node_modules/@types/react": {
- "version": "18.2.37",
- "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.37.tgz",
- "integrity": "sha512-RGAYMi2bhRgEXT3f4B92WTohopH6bIXw05FuGlmJEnv/omEn190+QYEIYxIAuIBdKgboYYdVved2p1AxZVQnaw==",
+ "version": "18.2.41",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.41.tgz",
+ "integrity": "sha512-CwOGr/PiLiNBxEBqpJ7fO3kocP/2SSuC9fpH5K7tusrg4xPSRT/193rzolYwQnTN02We/ATXKnb6GqA5w4fRxw==",
"dev": true,
"dependencies": {
"@types/prop-types": "*",
@@ -4623,9 +4623,9 @@
}
},
"node_modules/@types/react-dom": {
- "version": "18.2.15",
- "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.15.tgz",
- "integrity": "sha512-HWMdW+7r7MR5+PZqJF6YFNSCtjz1T0dsvo/f1BV6HkV+6erD/nA7wd9NM00KVG83zf2nJ7uATPO9ttdIPvi3gg==",
+ "version": "18.2.17",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.17.tgz",
+ "integrity": "sha512-rvrT/M7Df5eykWFxn6MYt5Pem/Dbyc1N8Y0S9Mrkw2WFCRiqUgw9P7ul2NpwsXCSM1DVdENzdG9J5SreqfAIWg==",
"dev": true,
"dependencies": {
"@types/react": "*"
@@ -4638,15 +4638,15 @@
"dev": true
},
"node_modules/@types/scheduler": {
- "version": "0.16.6",
- "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.6.tgz",
- "integrity": "sha512-Vlktnchmkylvc9SnwwwozTv04L/e1NykF5vgoQ0XTmI8DD+wxfjQuHuvHS3p0r2jz2x2ghPs2h1FVeDirIteWA==",
+ "version": "0.16.8",
+ "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz",
+ "integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==",
"dev": true
},
"node_modules/@types/semver": {
- "version": "7.5.5",
- "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.5.tgz",
- "integrity": "sha512-+d+WYC1BxJ6yVOgUgzK8gWvp5qF8ssV5r4nsDcZWKRWcDQLQ619tvWAxJQYGgBrO1MnLJC7a5GtiYsAoQ47dJg==",
+ "version": "7.5.6",
+ "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.6.tgz",
+ "integrity": "sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==",
"dev": true
},
"node_modules/@types/send": {
@@ -4701,18 +4701,18 @@
"dev": true
},
"node_modules/@types/ws": {
- "version": "8.5.9",
- "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.9.tgz",
- "integrity": "sha512-jbdrY0a8lxfdTp/+r7Z4CkycbOFN8WX+IOchLJr3juT/xzbJ8URyTVSJ/hvNdadTgM1mnedb47n+Y31GsFnQlg==",
+ "version": "8.5.10",
+ "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz",
+ "integrity": "sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==",
"dev": true,
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/yargs": {
- "version": "17.0.31",
- "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.31.tgz",
- "integrity": "sha512-bocYSx4DI8TmdlvxqGpVNXOgCNR1Jj0gNPhhAY+iz1rgKDAaYrAYdFYnhDV1IFuiuVc9HkOwyDcFxaTElF3/wg==",
+ "version": "17.0.32",
+ "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz",
+ "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==",
"dev": true,
"dependencies": {
"@types/yargs-parser": "*"
@@ -4725,16 +4725,16 @@
"dev": true
},
"node_modules/@typescript-eslint/eslint-plugin": {
- "version": "6.11.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.11.0.tgz",
- "integrity": "sha512-uXnpZDc4VRjY4iuypDBKzW1rz9T5YBBK0snMn8MaTSNd2kMlj50LnLBABELjJiOL5YHk7ZD8hbSpI9ubzqYI0w==",
+ "version": "6.13.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.13.1.tgz",
+ "integrity": "sha512-5bQDGkXaxD46bPvQt08BUz9YSaO4S0fB1LB5JHQuXTfkGPI3+UUeS387C/e9jRie5GqT8u5kFTrMvAjtX4O5kA==",
"dev": true,
"dependencies": {
"@eslint-community/regexpp": "^4.5.1",
- "@typescript-eslint/scope-manager": "6.11.0",
- "@typescript-eslint/type-utils": "6.11.0",
- "@typescript-eslint/utils": "6.11.0",
- "@typescript-eslint/visitor-keys": "6.11.0",
+ "@typescript-eslint/scope-manager": "6.13.1",
+ "@typescript-eslint/type-utils": "6.13.1",
+ "@typescript-eslint/utils": "6.13.1",
+ "@typescript-eslint/visitor-keys": "6.13.1",
"debug": "^4.3.4",
"graphemer": "^1.4.0",
"ignore": "^5.2.4",
@@ -4793,15 +4793,15 @@
"dev": true
},
"node_modules/@typescript-eslint/parser": {
- "version": "6.11.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.11.0.tgz",
- "integrity": "sha512-+whEdjk+d5do5nxfxx73oanLL9ghKO3EwM9kBCkUtWMRwWuPaFv9ScuqlYfQ6pAD6ZiJhky7TZ2ZYhrMsfMxVQ==",
+ "version": "6.13.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.13.1.tgz",
+ "integrity": "sha512-fs2XOhWCzRhqMmQf0eicLa/CWSaYss2feXsy7xBD/pLyWke/jCIVc2s1ikEAtSW7ina1HNhv7kONoEfVNEcdDQ==",
"dev": true,
"dependencies": {
- "@typescript-eslint/scope-manager": "6.11.0",
- "@typescript-eslint/types": "6.11.0",
- "@typescript-eslint/typescript-estree": "6.11.0",
- "@typescript-eslint/visitor-keys": "6.11.0",
+ "@typescript-eslint/scope-manager": "6.13.1",
+ "@typescript-eslint/types": "6.13.1",
+ "@typescript-eslint/typescript-estree": "6.13.1",
+ "@typescript-eslint/visitor-keys": "6.13.1",
"debug": "^4.3.4"
},
"engines": {
@@ -4821,13 +4821,13 @@
}
},
"node_modules/@typescript-eslint/scope-manager": {
- "version": "6.11.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.11.0.tgz",
- "integrity": "sha512-0A8KoVvIURG4uhxAdjSaxy8RdRE//HztaZdG8KiHLP8WOXSk0vlF7Pvogv+vlJA5Rnjj/wDcFENvDaHb+gKd1A==",
+ "version": "6.13.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.13.1.tgz",
+ "integrity": "sha512-BW0kJ7ceiKi56GbT2KKzZzN+nDxzQK2DS6x0PiSMPjciPgd/JRQGMibyaN2cPt2cAvuoH0oNvn2fwonHI+4QUQ==",
"dev": true,
"dependencies": {
- "@typescript-eslint/types": "6.11.0",
- "@typescript-eslint/visitor-keys": "6.11.0"
+ "@typescript-eslint/types": "6.13.1",
+ "@typescript-eslint/visitor-keys": "6.13.1"
},
"engines": {
"node": "^16.0.0 || >=18.0.0"
@@ -4838,13 +4838,13 @@
}
},
"node_modules/@typescript-eslint/type-utils": {
- "version": "6.11.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.11.0.tgz",
- "integrity": "sha512-nA4IOXwZtqBjIoYrJcYxLRO+F9ri+leVGoJcMW1uqr4r1Hq7vW5cyWrA43lFbpRvQ9XgNrnfLpIkO3i1emDBIA==",
+ "version": "6.13.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.13.1.tgz",
+ "integrity": "sha512-A2qPlgpxx2v//3meMqQyB1qqTg1h1dJvzca7TugM3Yc2USDY+fsRBiojAEo92HO7f5hW5mjAUF6qobOPzlBCBQ==",
"dev": true,
"dependencies": {
- "@typescript-eslint/typescript-estree": "6.11.0",
- "@typescript-eslint/utils": "6.11.0",
+ "@typescript-eslint/typescript-estree": "6.13.1",
+ "@typescript-eslint/utils": "6.13.1",
"debug": "^4.3.4",
"ts-api-utils": "^1.0.1"
},
@@ -4865,9 +4865,9 @@
}
},
"node_modules/@typescript-eslint/types": {
- "version": "6.11.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.11.0.tgz",
- "integrity": "sha512-ZbEzuD4DwEJxwPqhv3QULlRj8KYTAnNsXxmfuUXFCxZmO6CF2gM/y+ugBSAQhrqaJL3M+oe4owdWunaHM6beqA==",
+ "version": "6.13.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.13.1.tgz",
+ "integrity": "sha512-gjeEskSmiEKKFIbnhDXUyiqVma1gRCQNbVZ1C8q7Zjcxh3WZMbzWVfGE9rHfWd1msQtPS0BVD9Jz9jded44eKg==",
"dev": true,
"engines": {
"node": "^16.0.0 || >=18.0.0"
@@ -4878,13 +4878,13 @@
}
},
"node_modules/@typescript-eslint/typescript-estree": {
- "version": "6.11.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.11.0.tgz",
- "integrity": "sha512-Aezzv1o2tWJwvZhedzvD5Yv7+Lpu1by/U1LZ5gLc4tCx8jUmuSCMioPFRjliN/6SJIvY6HpTtJIWubKuYYYesQ==",
+ "version": "6.13.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.13.1.tgz",
+ "integrity": "sha512-sBLQsvOC0Q7LGcUHO5qpG1HxRgePbT6wwqOiGLpR8uOJvPJbfs0mW3jPA3ujsDvfiVwVlWUDESNXv44KtINkUQ==",
"dev": true,
"dependencies": {
- "@typescript-eslint/types": "6.11.0",
- "@typescript-eslint/visitor-keys": "6.11.0",
+ "@typescript-eslint/types": "6.13.1",
+ "@typescript-eslint/visitor-keys": "6.13.1",
"debug": "^4.3.4",
"globby": "^11.1.0",
"is-glob": "^4.0.3",
@@ -4938,17 +4938,17 @@
"dev": true
},
"node_modules/@typescript-eslint/utils": {
- "version": "6.11.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.11.0.tgz",
- "integrity": "sha512-p23ibf68fxoZy605dc0dQAEoUsoiNoP3MD9WQGiHLDuTSOuqoTsa4oAy+h3KDkTcxbbfOtUjb9h3Ta0gT4ug2g==",
+ "version": "6.13.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.13.1.tgz",
+ "integrity": "sha512-ouPn/zVoan92JgAegesTXDB/oUp6BP1v8WpfYcqh649ejNc9Qv+B4FF2Ff626kO1xg0wWwwG48lAJ4JuesgdOw==",
"dev": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.4.0",
"@types/json-schema": "^7.0.12",
"@types/semver": "^7.5.0",
- "@typescript-eslint/scope-manager": "6.11.0",
- "@typescript-eslint/types": "6.11.0",
- "@typescript-eslint/typescript-estree": "6.11.0",
+ "@typescript-eslint/scope-manager": "6.13.1",
+ "@typescript-eslint/types": "6.13.1",
+ "@typescript-eslint/typescript-estree": "6.13.1",
"semver": "^7.5.4"
},
"engines": {
@@ -4996,12 +4996,12 @@
"dev": true
},
"node_modules/@typescript-eslint/visitor-keys": {
- "version": "6.11.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.11.0.tgz",
- "integrity": "sha512-+SUN/W7WjBr05uRxPggJPSzyB8zUpaYo2hByKasWbqr3PM8AXfZt8UHdNpBS1v9SA62qnSSMF3380SwDqqprgQ==",
+ "version": "6.13.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.13.1.tgz",
+ "integrity": "sha512-NDhQUy2tg6XGNBGDRm1XybOHSia8mcXmlbKWoQP+nm1BIIMxa55shyJfZkHpEBN62KNPLrocSM2PdPcaLgDKMQ==",
"dev": true,
"dependencies": {
- "@typescript-eslint/types": "6.11.0",
+ "@typescript-eslint/types": "6.13.1",
"eslint-visitor-keys": "^3.4.1"
},
"engines": {
@@ -5236,6 +5236,7 @@
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz",
"integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==",
+ "deprecated": "Use your platform's native atob() and btoa() methods instead",
"dev": true
},
"node_modules/accepts": {
@@ -5997,9 +5998,9 @@
}
},
"node_modules/browserslist": {
- "version": "4.22.1",
- "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.1.tgz",
- "integrity": "sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ==",
+ "version": "4.22.2",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.2.tgz",
+ "integrity": "sha512-0UgcrvQmBDvZHFGdYUehrCNIazki7/lUP3kkoi/r3YB2amZbFM9J43ZRkJTXBUZK4gmx56+Sqk9+Vs9mwZx9+A==",
"dev": true,
"funding": [
{
@@ -6016,9 +6017,9 @@
}
],
"dependencies": {
- "caniuse-lite": "^1.0.30001541",
- "electron-to-chromium": "^1.4.535",
- "node-releases": "^2.0.13",
+ "caniuse-lite": "^1.0.30001565",
+ "electron-to-chromium": "^1.4.601",
+ "node-releases": "^2.0.14",
"update-browserslist-db": "^1.0.13"
},
"bin": {
@@ -6206,9 +6207,9 @@
}
},
"node_modules/caniuse-lite": {
- "version": "1.0.30001562",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001562.tgz",
- "integrity": "sha512-kfte3Hym//51EdX4239i+Rmp20EsLIYGdPkERegTgU19hQWCRhsRFGKHTliUlsry53tv17K7n077Kqa0WJU4ng==",
+ "version": "1.0.30001566",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001566.tgz",
+ "integrity": "sha512-ggIhCsTxmITBAMmK8yZjEhCO5/47jKXPu6Dha/wuCS4JePVL+3uiDEBuhu2aIoT+bqTOR8L76Ip1ARL9xYsEJA==",
"dev": true,
"funding": [
{
@@ -6357,9 +6358,9 @@
"dev": true
},
"node_modules/clean-css": {
- "version": "5.3.2",
- "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.2.tgz",
- "integrity": "sha512-JVJbM+f3d3Q704rF4bqQ5UUyTtuJ0JRKNbTKVEeujCCBoMdkEi+V+e8oktO9qGQNSvHrFTM6JZRXrUvGR1czww==",
+ "version": "5.3.3",
+ "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.3.tgz",
+ "integrity": "sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==",
"dev": true,
"dependencies": {
"source-map": "~0.6.0"
@@ -6784,9 +6785,9 @@
}
},
"node_modules/core-js-compat": {
- "version": "3.33.2",
- "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.33.2.tgz",
- "integrity": "sha512-axfo+wxFVxnqf8RvxTzoAlzW4gRoacrHeoFlc9n0x50+7BEyZL/Rt3hicaED1/CEd7I6tPCPVUYcJwCMO5XUYw==",
+ "version": "3.33.3",
+ "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.33.3.tgz",
+ "integrity": "sha512-cNzGqFsh3Ot+529GIXacjTJ7kegdt5fPXxCBVS1G0iaZpuo/tBz399ymceLJveQhFFZ8qThHiP3fzuoQjKN2ow==",
"dev": true,
"dependencies": {
"browserslist": "^4.22.1"
@@ -6797,9 +6798,9 @@
}
},
"node_modules/core-js-pure": {
- "version": "3.33.2",
- "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.33.2.tgz",
- "integrity": "sha512-a8zeCdyVk7uF2elKIGz67AjcXOxjRbwOLz8SbklEso1V+2DoW4OkAMZN9S9GBgvZIaqQi/OemFX4OiSoQEmg1Q==",
+ "version": "3.33.3",
+ "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.33.3.tgz",
+ "integrity": "sha512-taJ00IDOP+XYQEA2dAe4ESkmHt1fL8wzYDo3mRWQey8uO9UojlBFMneA65kMyxfYP7106c6LzWaq7/haDT6BCQ==",
"dev": true,
"hasInstallScript": true,
"funding": {
@@ -6990,25 +6991,25 @@
}
},
"node_modules/cspell": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/cspell/-/cspell-8.0.0.tgz",
- "integrity": "sha512-Nayy25Dh+GAlDFDpVZaQhmidP947rpj1Pn9lmZ3nUFjD9W/yj0h0vrjMLMN4dbonddkmKh4t51C+7NuMP405hg==",
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/cspell/-/cspell-8.1.0.tgz",
+ "integrity": "sha512-oxQLyhW3yIAfvDdtoobvriWqfWVqOBo1o+WWRxlDyJdKDBH6my++p6KU3ZjxcJb7VG+CRLGfU7zASWwTPxMXRA==",
"dev": true,
"dependencies": {
- "@cspell/cspell-json-reporter": "8.0.0",
- "@cspell/cspell-pipe": "8.0.0",
- "@cspell/cspell-types": "8.0.0",
- "@cspell/dynamic-import": "8.0.0",
+ "@cspell/cspell-json-reporter": "8.1.0",
+ "@cspell/cspell-pipe": "8.1.0",
+ "@cspell/cspell-types": "8.1.0",
+ "@cspell/dynamic-import": "8.1.0",
"chalk": "^5.3.0",
"chalk-template": "^1.1.0",
"commander": "^11.1.0",
- "cspell-gitignore": "8.0.0",
- "cspell-glob": "8.0.0",
- "cspell-io": "8.0.0",
- "cspell-lib": "8.0.0",
+ "cspell-gitignore": "8.1.0",
+ "cspell-glob": "8.1.0",
+ "cspell-io": "8.1.0",
+ "cspell-lib": "8.1.0",
"fast-glob": "^3.3.2",
"fast-json-stable-stringify": "^2.1.0",
- "file-entry-cache": "^7.0.1",
+ "file-entry-cache": "^7.0.2",
"get-stdin": "^9.0.0",
"semver": "^7.5.4",
"strip-ansi": "^7.1.0",
@@ -7025,36 +7026,44 @@
"url": "https://github.com/streetsidesoftware/cspell?sponsor=1"
}
},
+ "node_modules/cspell-config-lib": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/cspell-config-lib/-/cspell-config-lib-8.1.0.tgz",
+ "integrity": "sha512-mIv8etMAp05OapdxJQt0nkfzclMti8AfACPryWnVePrwB89A2KjErHYBa7hX6gn20B4K+KgD7ckPcOi6L8vLYA==",
+ "dev": true,
+ "dependencies": {
+ "@cspell/cspell-types": "8.1.0",
+ "comment-json": "^4.2.3",
+ "yaml": "^2.3.4"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/cspell-dictionary": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/cspell-dictionary/-/cspell-dictionary-8.0.0.tgz",
- "integrity": "sha512-R/AzUj7W7F4O4fAOL8jvIiUqPYGy6jIBlDkxO9SZe/A6D2kOICZZzGSXMZ0M7OKYqxc6cioQUMKOJsLkDXfDXw==",
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/cspell-dictionary/-/cspell-dictionary-8.1.0.tgz",
+ "integrity": "sha512-nwvlPiM7jsZThZ2bUS2CYzqwAbxWC4OL5GozQfbGEwW/8unNhifBpJzlOZuzLyX4Vu94ETExeIc625wBqPWjVA==",
"dev": true,
"dependencies": {
- "@cspell/cspell-pipe": "8.0.0",
- "@cspell/cspell-types": "8.0.0",
- "cspell-trie-lib": "8.0.0",
- "fast-equals": "^4.0.3",
+ "@cspell/cspell-pipe": "8.1.0",
+ "@cspell/cspell-types": "8.1.0",
+ "cspell-trie-lib": "8.1.0",
+ "fast-equals": "^5.0.1",
"gensequence": "^6.0.0"
},
"engines": {
"node": ">=18"
}
},
- "node_modules/cspell-dictionary/node_modules/fast-equals": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-4.0.3.tgz",
- "integrity": "sha512-G3BSX9cfKttjr+2o1O22tYMLq0DPluZnYtq1rXumE1SpL/F/SLIfHx08WYQoWSIpeMYf8sRbJ8++71+v6Pnxfg==",
- "dev": true
- },
"node_modules/cspell-gitignore": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/cspell-gitignore/-/cspell-gitignore-8.0.0.tgz",
- "integrity": "sha512-Uv+ENdUm+EXwQuG9187lKmE1t8b2KW+6VaQHP7r01WiuhkwhfzmWA7C30iXVcwRcsMw07wKiWvMEtG6Zlzi6lQ==",
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/cspell-gitignore/-/cspell-gitignore-8.1.0.tgz",
+ "integrity": "sha512-upMIEjbBz1g92Vt80h2hMMRZ9057iAmCWxi05l0WrwGrtc3CGsA8gQQIFIbVZ0x86Sbmv1cBZms1Y/hKWPWuvg==",
"dev": true,
"dependencies": {
- "cspell-glob": "8.0.0",
- "find-up": "^5.0.0"
+ "cspell-glob": "8.1.0",
+ "find-up-simple": "^1.0.0"
},
"bin": {
"cspell-gitignore": "bin.mjs"
@@ -7064,9 +7073,9 @@
}
},
"node_modules/cspell-glob": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/cspell-glob/-/cspell-glob-8.0.0.tgz",
- "integrity": "sha512-wOkRA1OTIPhyN7a+k9Qq45yFXM+tBFi9DS5ObiLv6t6VTBIeMQpwRK0KLViHmjTgiA6eWx53Dnr+DZfxcAkcZA==",
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/cspell-glob/-/cspell-glob-8.1.0.tgz",
+ "integrity": "sha512-onPRqJqPZaaUQ1CKeuh2fJJ9UjIBicRq6Ffd6bqWCu7IdwfEBPtjWa/nlEjCVp1CMRwhS3Y0zG3jHkKLydsR4Q==",
"dev": true,
"dependencies": {
"micromatch": "^4.0.5"
@@ -7076,13 +7085,13 @@
}
},
"node_modules/cspell-grammar": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/cspell-grammar/-/cspell-grammar-8.0.0.tgz",
- "integrity": "sha512-uxpRvbBxOih6SjFQvKTBPTA+YyqYM5UFTNTFuRnA6g6WZeg+NJaTkbQrTgXja4B2r8MJ6XU22YrKTtHNNcP7bQ==",
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/cspell-grammar/-/cspell-grammar-8.1.0.tgz",
+ "integrity": "sha512-E28SDJYOOuHk8eBtMSIGyCu8qiKb/H4LX1J/kw8+eV0RLvnllmq2FAYFBk8jtu4uW49TW5n/eLg7J2TvPONYAA==",
"dev": true,
"dependencies": {
- "@cspell/cspell-pipe": "8.0.0",
- "@cspell/cspell-types": "8.0.0"
+ "@cspell/cspell-pipe": "8.1.0",
+ "@cspell/cspell-types": "8.1.0"
},
"bin": {
"cspell-grammar": "bin.mjs"
@@ -7092,40 +7101,39 @@
}
},
"node_modules/cspell-io": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/cspell-io/-/cspell-io-8.0.0.tgz",
- "integrity": "sha512-NVdVmQd7SU/nxYwWtO/6gzux/kp1Dt36zKds0+QHZhQ18JJjXduF5e+WUttqKi2oj/vvmjiG4HGFKQVDBcBz3w==",
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/cspell-io/-/cspell-io-8.1.0.tgz",
+ "integrity": "sha512-oPRMS/XUWcdZXMj6Zhs65mgOVyRZajAhHLm18o6cPLOGUD0770oMqi8ZNKj7LuvubkyP/NL0m4AEcWwvmz/Cbw==",
"dev": true,
"dependencies": {
- "@cspell/cspell-service-bus": "8.0.0"
+ "@cspell/cspell-service-bus": "8.1.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/cspell-lib": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/cspell-lib/-/cspell-lib-8.0.0.tgz",
- "integrity": "sha512-X/BzUjrzHOx7YlhvSph/OlMu1RmCTnybeZvIE67d1Pd7wT1TmZhFTnmvruUhoHxWEudOEe4HjzuNL9ph6Aw+aA==",
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/cspell-lib/-/cspell-lib-8.1.0.tgz",
+ "integrity": "sha512-tatdY9teElqqPtKHAY1osOhV68h/f3x+4Niw7rV12OXmJ9El1lPka59bVTV401fODWRoF3WWJXUpTg012zhdrQ==",
"dev": true,
"dependencies": {
- "@cspell/cspell-bundled-dicts": "8.0.0",
- "@cspell/cspell-pipe": "8.0.0",
- "@cspell/cspell-resolver": "8.0.0",
- "@cspell/cspell-types": "8.0.0",
- "@cspell/dynamic-import": "8.0.0",
- "@cspell/strong-weak-map": "8.0.0",
+ "@cspell/cspell-bundled-dicts": "8.1.0",
+ "@cspell/cspell-pipe": "8.1.0",
+ "@cspell/cspell-resolver": "8.1.0",
+ "@cspell/cspell-types": "8.1.0",
+ "@cspell/dynamic-import": "8.1.0",
+ "@cspell/strong-weak-map": "8.1.0",
"clear-module": "^4.1.2",
"comment-json": "^4.2.3",
"configstore": "^6.0.0",
- "cosmiconfig": "8.0.0",
- "cspell-dictionary": "8.0.0",
- "cspell-glob": "8.0.0",
- "cspell-grammar": "8.0.0",
- "cspell-io": "8.0.0",
- "cspell-trie-lib": "8.0.0",
+ "cspell-config-lib": "8.1.0",
+ "cspell-dictionary": "8.1.0",
+ "cspell-glob": "8.1.0",
+ "cspell-grammar": "8.1.0",
+ "cspell-io": "8.1.0",
+ "cspell-trie-lib": "8.1.0",
"fast-equals": "^5.0.1",
- "find-up": "^6.3.0",
"gensequence": "^6.0.0",
"import-fresh": "^3.3.0",
"resolve-from": "^5.0.0",
@@ -7136,129 +7144,14 @@
"node": ">=18"
}
},
- "node_modules/cspell-lib/node_modules/argparse": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
- "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
- "dev": true
- },
- "node_modules/cspell-lib/node_modules/cosmiconfig": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.0.0.tgz",
- "integrity": "sha512-da1EafcpH6b/TD8vDRaWV7xFINlHlF6zKsGwS1TsuVJTZRkquaS5HTMq7uq6h31619QjbsYl21gVDOm32KM1vQ==",
- "dev": true,
- "dependencies": {
- "import-fresh": "^3.2.1",
- "js-yaml": "^4.1.0",
- "parse-json": "^5.0.0",
- "path-type": "^4.0.0"
- },
- "engines": {
- "node": ">=14"
- }
- },
- "node_modules/cspell-lib/node_modules/find-up": {
- "version": "6.3.0",
- "resolved": "https://registry.npmjs.org/find-up/-/find-up-6.3.0.tgz",
- "integrity": "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==",
- "dev": true,
- "dependencies": {
- "locate-path": "^7.1.0",
- "path-exists": "^5.0.0"
- },
- "engines": {
- "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/cspell-lib/node_modules/js-yaml": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
- "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
- "dev": true,
- "dependencies": {
- "argparse": "^2.0.1"
- },
- "bin": {
- "js-yaml": "bin/js-yaml.js"
- }
- },
- "node_modules/cspell-lib/node_modules/locate-path": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz",
- "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==",
- "dev": true,
- "dependencies": {
- "p-locate": "^6.0.0"
- },
- "engines": {
- "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/cspell-lib/node_modules/p-limit": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz",
- "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==",
- "dev": true,
- "dependencies": {
- "yocto-queue": "^1.0.0"
- },
- "engines": {
- "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/cspell-lib/node_modules/p-locate": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz",
- "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==",
- "dev": true,
- "dependencies": {
- "p-limit": "^4.0.0"
- },
- "engines": {
- "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/cspell-lib/node_modules/path-exists": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz",
- "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==",
- "dev": true,
- "engines": {
- "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
- }
- },
- "node_modules/cspell-lib/node_modules/yocto-queue": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz",
- "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==",
- "dev": true,
- "engines": {
- "node": ">=12.20"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/cspell-trie-lib": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/cspell-trie-lib/-/cspell-trie-lib-8.0.0.tgz",
- "integrity": "sha512-0rC5e1C0uM78uuS+lC1T18EojWZyNvq4bPOPCisnwuhuWrAfCqrFrX/qDNslWk3VTOPbsEMlFj6OnIGQnfwSKg==",
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/cspell-trie-lib/-/cspell-trie-lib-8.1.0.tgz",
+ "integrity": "sha512-OF5ZNuGPIGg2CCMdMeAgd1I2iVDjoelpMjVDyqpuNu+RVpAkmNRqMFDBlsnJPWCCeOLn7blWPMBZW2KXctsm3Q==",
"dev": true,
"dependencies": {
- "@cspell/cspell-pipe": "8.0.0",
- "@cspell/cspell-types": "8.0.0",
+ "@cspell/cspell-pipe": "8.1.0",
+ "@cspell/cspell-types": "8.1.0",
"gensequence": "^6.0.0"
},
"engines": {
@@ -7964,6 +7857,7 @@
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz",
"integrity": "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==",
+ "deprecated": "Use your platform's native DOMException instead",
"dev": true,
"dependencies": {
"webidl-conversions": "^7.0.0"
@@ -8033,9 +7927,9 @@
"dev": true
},
"node_modules/electron-to-chromium": {
- "version": "1.4.582",
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.582.tgz",
- "integrity": "sha512-89o0MGoocwYbzqUUjc+VNpeOFSOK9nIdC5wY4N+PVUarUK0MtjyTjks75AZS2bW4Kl8MdewdFsWaH0jLy+JNoA==",
+ "version": "1.4.601",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.601.tgz",
+ "integrity": "sha512-SpwUMDWe9tQu8JX5QCO1+p/hChAi9AE9UpoC3rcHVc+gdCGlbT3SGb5I1klgb952HRIyvt9wZhSz9bNBYz9swA==",
"dev": true
},
"node_modules/emittery": {
@@ -8304,15 +8198,15 @@
}
},
"node_modules/eslint": {
- "version": "8.53.0",
- "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.53.0.tgz",
- "integrity": "sha512-N4VuiPjXDUa4xVeV/GC/RV3hQW9Nw+Y463lkWaKKXKYMvmRiRDAtfpuPFLN+E1/6ZhyR8J2ig+eVREnYgUsiag==",
+ "version": "8.55.0",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.55.0.tgz",
+ "integrity": "sha512-iyUUAM0PCKj5QpwGfmCAG9XXbZCWsqP/eWAWrG/W0umvjuLRBECwSFdt+rCntju0xEH7teIABPwXpahftIaTdA==",
"dev": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.6.1",
- "@eslint/eslintrc": "^2.1.3",
- "@eslint/js": "8.53.0",
+ "@eslint/eslintrc": "^2.1.4",
+ "@eslint/js": "8.55.0",
"@humanwhocodes/config-array": "^0.11.13",
"@humanwhocodes/module-importer": "^1.0.1",
"@nodelib/fs.walk": "^1.2.8",
@@ -8358,6 +8252,18 @@
"url": "https://opencollective.com/eslint"
}
},
+ "node_modules/eslint-compat-utils": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/eslint-compat-utils/-/eslint-compat-utils-0.1.2.tgz",
+ "integrity": "sha512-Jia4JDldWnFNIru1Ehx1H5s9/yxiRHY/TimCuUc0jNexew3cF1gI6CYZil1ociakfWO3rRqFjl1mskBblB3RYg==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "peerDependencies": {
+ "eslint": ">=6.0.0"
+ }
+ },
"node_modules/eslint-config-standard": {
"version": "17.1.0",
"resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-17.1.0.tgz",
@@ -8508,13 +8414,14 @@
}
},
"node_modules/eslint-plugin-es-x": {
- "version": "7.3.0",
- "resolved": "https://registry.npmjs.org/eslint-plugin-es-x/-/eslint-plugin-es-x-7.3.0.tgz",
- "integrity": "sha512-W9zIs+k00I/I13+Bdkl/zG1MEO07G97XjUSQuH117w620SJ6bHtLUmoMvkGA2oYnI/gNdr+G7BONLyYnFaLLEQ==",
+ "version": "7.5.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-es-x/-/eslint-plugin-es-x-7.5.0.tgz",
+ "integrity": "sha512-ODswlDSO0HJDzXU0XvgZ3lF3lS3XAZEossh15Q2UHjwrJggWeBoKqqEsLTZLXl+dh5eOAozG0zRcYtuE35oTuQ==",
"dev": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.1.2",
- "@eslint-community/regexpp": "^4.6.0"
+ "@eslint-community/regexpp": "^4.6.0",
+ "eslint-compat-utils": "^0.1.2"
},
"engines": {
"node": "^14.18.0 || >=16.0.0"
@@ -9390,12 +9297,12 @@
}
},
"node_modules/file-entry-cache": {
- "version": "7.0.1",
- "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-7.0.1.tgz",
- "integrity": "sha512-uLfFktPmRetVCbHe5UPuekWrQ6hENufnA46qEGbfACkK5drjTTdQYUragRgMjHldcbYG+nslUerqMPjbBSHXjQ==",
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-7.0.2.tgz",
+ "integrity": "sha512-TfW7/1iI4Cy7Y8L6iqNdZQVvdXn0f8B4QcIXmkIbtTIe/Okm/nSlHb4IwGzRVOd3WfSieCgvf5cMzEfySAIl0g==",
"dev": true,
"dependencies": {
- "flat-cache": "^3.1.1"
+ "flat-cache": "^3.2.0"
},
"engines": {
"node": ">=12.0.0"
@@ -9489,6 +9396,18 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/find-up-simple": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/find-up-simple/-/find-up-simple-1.0.0.tgz",
+ "integrity": "sha512-q7Us7kcjj2VMePAa02hDAF6d+MzsdsAWEwYyOpwUtlerRBkOEPBCRZrAV4XfcSN8fHAgaD0hP7miwoay6DCprw==",
+ "dev": true,
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/flat": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz",
@@ -9822,16 +9741,16 @@
"integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==",
"dev": true
},
- "node_modules/global-dirs": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.1.tgz",
- "integrity": "sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==",
+ "node_modules/global-directory": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/global-directory/-/global-directory-4.0.1.tgz",
+ "integrity": "sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q==",
"dev": true,
"dependencies": {
- "ini": "2.0.0"
+ "ini": "4.1.1"
},
"engines": {
- "node": ">=10"
+ "node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
@@ -10439,9 +10358,9 @@
}
},
"node_modules/ignore": {
- "version": "5.2.4",
- "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz",
- "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==",
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz",
+ "integrity": "sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==",
"dev": true,
"engines": {
"node": ">= 4"
@@ -10583,9 +10502,9 @@
}
},
"node_modules/import-meta-resolve": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-3.1.1.tgz",
- "integrity": "sha512-qeywsE/KC3w9Fd2ORrRDUw6nS/nLwZpXgfrOc2IILvZYnCaEMd+D56Vfg9k4G29gIeVi3XKql1RQatME8iYsiw==",
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.0.0.tgz",
+ "integrity": "sha512-okYUR7ZQPH+efeuMJGlq4f8ubUgO50kByRPyt/Cy1Io4PSRsPjxME+YlVaCOx+NIToW7hCsZNFJyTPFFKepRSA==",
"dev": true,
"funding": {
"type": "github",
@@ -10627,12 +10546,12 @@
"dev": true
},
"node_modules/ini": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz",
- "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==",
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.1.tgz",
+ "integrity": "sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==",
"dev": true,
"engines": {
- "node": ">=10"
+ "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
}
},
"node_modules/internal-slot": {
@@ -14059,9 +13978,9 @@
"dev": true
},
"node_modules/node-releases": {
- "version": "2.0.13",
- "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz",
- "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==",
+ "version": "2.0.14",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz",
+ "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==",
"dev": true
},
"node_modules/node-watch": {
@@ -14203,13 +14122,13 @@
}
},
"node_modules/object.assign": {
- "version": "4.1.4",
- "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz",
- "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==",
+ "version": "4.1.5",
+ "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz",
+ "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==",
"dev": true,
"dependencies": {
- "call-bind": "^1.0.2",
- "define-properties": "^1.1.4",
+ "call-bind": "^1.0.5",
+ "define-properties": "^1.2.1",
"has-symbols": "^1.0.3",
"object-keys": "^1.1.1"
},
@@ -14705,9 +14624,9 @@
}
},
"node_modules/postcss": {
- "version": "8.4.31",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
- "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==",
+ "version": "8.4.32",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.32.tgz",
+ "integrity": "sha512-D/kj5JNu6oo2EIy+XL/26JEDTlIbB8hw85G8StOE6L74RQAVVP5rej6wxCNqyMbR4RkPfqvezVbPw81Ngd6Kcw==",
"dev": true,
"funding": [
{
@@ -14724,7 +14643,7 @@
}
],
"dependencies": {
- "nanoid": "^3.3.6",
+ "nanoid": "^3.3.7",
"picocolors": "^1.0.0",
"source-map-js": "^1.0.2"
},
@@ -15581,11 +15500,11 @@
}
},
"node_modules/react-router": {
- "version": "6.18.0",
- "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.18.0.tgz",
- "integrity": "sha512-vk2y7Dsy8wI02eRRaRmOs9g2o+aE72YCx5q9VasT1N9v+lrdB79tIqrjMfByHiY5+6aYkH2rUa5X839nwWGPDg==",
+ "version": "6.20.1",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.20.1.tgz",
+ "integrity": "sha512-ccvLrB4QeT5DlaxSFFYi/KR8UMQ4fcD8zBcR71Zp1kaYTC5oJKYAp1cbavzGrogwxca+ubjkd7XjFZKBW8CxPA==",
"dependencies": {
- "@remix-run/router": "1.11.0"
+ "@remix-run/router": "1.13.1"
},
"engines": {
"node": ">=14.0.0"
@@ -15595,12 +15514,12 @@
}
},
"node_modules/react-router-dom": {
- "version": "6.18.0",
- "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.18.0.tgz",
- "integrity": "sha512-Ubrue4+Ercc/BoDkFQfc6og5zRQ4A8YxSO3Knsne+eRbZ+IepAsK249XBH/XaFuOYOYr3L3r13CXTLvYt5JDjw==",
+ "version": "6.20.1",
+ "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.20.1.tgz",
+ "integrity": "sha512-npzfPWcxfQN35psS7rJgi/EW0Gx6EsNjfdJSAk73U/HqMEJZ2k/8puxfwHFgDQhBGmS3+sjnGbMdMSV45axPQw==",
"dependencies": {
- "@remix-run/router": "1.11.0",
- "react-router": "6.18.0"
+ "@remix-run/router": "1.13.1",
+ "react-router": "6.20.1"
},
"engines": {
"node": ">=14.0.0"
@@ -17267,15 +17186,16 @@
"dev": true
},
"node_modules/svgo": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.0.3.tgz",
- "integrity": "sha512-X4UZvLhOglD5Xrp834HzGHf8RKUW0Ahigg/08yRO1no9t2NxffOkMiQ0WmaMIbaGlVTlSst2zWANsdhz5ybXgA==",
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.0.5.tgz",
+ "integrity": "sha512-HQKHEo73pMNOlDlBcLgZRcHW2+1wo7bFYayAXkGN0l/2+h68KjlfZyMRhdhaGvoHV2eApOovl12zoFz42sT6rQ==",
"dev": true,
"dependencies": {
"@trysound/sax": "0.2.0",
"commander": "^7.2.0",
"css-select": "^5.1.0",
"css-tree": "^2.2.1",
+ "css-what": "^6.1.0",
"csso": "5.0.5",
"picocolors": "^1.0.0"
},
@@ -17936,9 +17856,9 @@
}
},
"node_modules/typedoc": {
- "version": "0.25.3",
- "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.25.3.tgz",
- "integrity": "sha512-Ow8Bo7uY1Lwy7GTmphRIMEo6IOZ+yYUyrc8n5KXIZg1svpqhZSWgni2ZrDhe+wLosFS8yswowUzljTAV/3jmWw==",
+ "version": "0.25.4",
+ "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.25.4.tgz",
+ "integrity": "sha512-Du9ImmpBCw54bX275yJrxPVnjdIyJO/84co0/L9mwe0R3G4FSR6rQ09AlXVRvZEGMUg09+z/usc8mgygQ1aidA==",
"dev": true,
"dependencies": {
"lunr": "^2.3.9",
@@ -17953,7 +17873,7 @@
"node": ">= 16"
},
"peerDependencies": {
- "typescript": "4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x || 5.2.x"
+ "typescript": "4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x || 5.2.x || 5.3.x"
}
},
"node_modules/typedoc-plugin-missing-exports": {
@@ -17990,9 +17910,9 @@
}
},
"node_modules/typescript": {
- "version": "5.2.2",
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz",
- "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==",
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.2.tgz",
+ "integrity": "sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ==",
"dev": true,
"bin": {
"tsc": "bin/tsc",
@@ -18182,9 +18102,9 @@
}
},
"node_modules/v8-to-istanbul": {
- "version": "9.1.3",
- "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.1.3.tgz",
- "integrity": "sha512-9lDD+EVI2fjFsMWXc6dy5JJzBsVTcQ2fVkfBvncZ6xJWG9wtBhOldG+mHkSL0+V1K/xgZz0JDO5UT5hFwHUghg==",
+ "version": "9.2.0",
+ "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.2.0.tgz",
+ "integrity": "sha512-/EH/sDgxU2eGxajKdwLCDmQ4FWq+kpi3uCmBGpw1xJtnAxEjlD8j8PEiGWpCIMIs3ciNAgH0d3TTJiUkYzyZjA==",
"dev": true,
"dependencies": {
"@jridgewell/trace-mapping": "^0.3.12",
@@ -18846,6 +18766,15 @@
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
"dev": true
},
+ "node_modules/yaml": {
+ "version": "2.3.4",
+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.4.tgz",
+ "integrity": "sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==",
+ "dev": true,
+ "engines": {
+ "node": ">= 14"
+ }
+ },
"node_modules/yargs": {
"version": "17.7.2",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
diff --git a/web/package/cockpit-agama.changes b/web/package/cockpit-agama.changes
index 37e83816a0..972aa9b9b6 100644
--- a/web/package/cockpit-agama.changes
+++ b/web/package/cockpit-agama.changes
@@ -1,3 +1,38 @@
+-------------------------------------------------------------------
+Sat Dec 2 18:06:02 UTC 2023 - Imobach Gonzalez Sosa
+
+- Version 6
+
+-------------------------------------------------------------------
+Thu Nov 30 22:39:57 UTC 2023 - David Diaz
+
+- UI: make selectors more compact (gh#openSUSE/agama#898).
+
+-------------------------------------------------------------------
+Thu Nov 30 15:19:38 UTC 2023 - José Iván López González
+
+- Allow selecting the storage policy to make free space for the
+ installation (gh#openSUSE/agama#883).
+
+-------------------------------------------------------------------
+Wed Nov 29 14:15:16 UTC 2023 - José Iván López González
+
+- Allow selecting language, keymap and timezone for the target
+ system (gh#openSUSE/agama#881).
+
+-------------------------------------------------------------------
+Wed Nov 29 13:01:04 UTC 2023 - David Diaz
+
+- UI: improve the look and feel by fine tunning the sections spacing,
+ alignment, and icon sizes (gh#openSUSE/agama#892).
+
+-------------------------------------------------------------------
+Tue Nov 21 15:21:06 UTC 2023 - David Diaz
+
+- UI: Do not crash when clicking the install button. It started
+ failing after removing core-js dependency (gh#openSUSE/agama#880
+ and related to gh#openSUSE/agama#866).
+
-------------------------------------------------------------------
Fri Nov 17 13:27:22 UTC 2023 - David Diaz
diff --git a/web/po/cs.po b/web/po/cs.po
index 18fa976e5f..b1241fde2f 100644
--- a/web/po/cs.po
+++ b/web/po/cs.po
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2023-11-05 02:14+0000\n"
+"POT-Creation-Date: 2023-12-03 02:17+0000\n"
"PO-Revision-Date: 2023-07-26 15:03+0000\n"
"Last-Translator: Ladislav Slezák \n"
"Language-Team: Czech =2 && n<=4) ? 1 : 2;\n"
"X-Generator: Weblate 4.9.1\n"
-#: src/App.jsx:112
+#: src/App.jsx:110
msgid "Diagnostic tools"
msgstr ""
@@ -64,6 +64,7 @@ msgstr ""
#: src/components/core/About.jsx:71 src/components/core/FileViewer.jsx:80
#: src/components/core/Sidebar.jsx:177 src/components/core/Terminal.jsx:48
#: src/components/network/WifiSelector.jsx:151
+#: src/components/product/ProductPage.jsx:239
msgid "Close"
msgstr "Zavřít"
@@ -132,18 +133,23 @@ msgid ""
msgstr ""
#. TRANSLATORS: button label
-#: src/components/core/InstallButton.jsx:97
-#: src/components/storage/ProposalSettingsSection.jsx:166
-#: src/components/storage/ProposalSettingsSection.jsx:359
-#: src/components/storage/ProposalSettingsSection.jsx:504
+#: src/components/core/InstallButton.jsx:97 src/components/l10n/L10nPage.jsx:75
+#: src/components/l10n/L10nPage.jsx:191 src/components/l10n/L10nPage.jsx:304
+#: src/components/product/ProductPage.jsx:71
+#: src/components/product/ProductPage.jsx:140
+#: src/components/product/ProductPage.jsx:207
+#: src/components/storage/ProposalSettingsSection.jsx:170
+#: src/components/storage/ProposalSettingsSection.jsx:363
+#: src/components/storage/ProposalSettingsSection.jsx:508
+#: src/components/storage/ProposalSettingsSection.jsx:604
#: src/components/storage/ProposalVolumes.jsx:148
-#: src/components/storage/ProposalVolumes.jsx:282
+#: src/components/storage/ProposalVolumes.jsx:283
#: src/components/storage/ZFCPPage.jsx:513
msgid "Accept"
msgstr ""
#. TRANSLATORS: button label
-#: src/components/core/InstallButton.jsx:145
+#: src/components/core/InstallButton.jsx:148
msgid "Install"
msgstr ""
@@ -193,10 +199,40 @@ msgstr ""
msgid "There are new issues"
msgstr ""
-#: src/components/core/IssuesPage.jsx:115
+#. TRANSLATORS: page section
+#: src/components/core/IssuesPage.jsx:92
+#: src/components/overview/ProductSection.jsx:71
+#: src/components/product/ProductPage.jsx:434
+msgid "Product"
+msgstr ""
+
+#. TRANSLATORS: page title
+#. TRANSLATORS: page section title
+#: src/components/core/IssuesPage.jsx:100
+#: src/components/overview/StorageSection.jsx:208
+#: src/components/storage/ProposalPage.jsx:218
+msgid "Storage"
+msgstr ""
+
+#. TRANSLATORS: page title
+#. TRANSLATORS: page section
+#: src/components/core/IssuesPage.jsx:108
+#: src/components/overview/SoftwareSection.jsx:141
+#: src/components/software/SoftwarePage.jsx:81
+msgid "Software"
+msgstr ""
+
+#: src/components/core/IssuesPage.jsx:129
msgid "No issues found. Everything looks ok."
msgstr ""
+#. TRANSLATORS: search field placeholder text
+#: src/components/core/ListSearch.jsx:50
+#: src/components/software/PatternSelector.jsx:220
+#: src/components/software/PatternSelector.jsx:221
+msgid "Search"
+msgstr ""
+
#: src/components/core/LogsButton.jsx:98
msgid "Collecting logs..."
msgstr ""
@@ -256,12 +292,11 @@ msgstr ""
#. TRANSLATORS: dropdown label
#: src/components/core/RowActions.jsx:66
#: src/components/storage/ProposalVolumes.jsx:119
-#: src/components/storage/ProposalVolumes.jsx:309
+#: src/components/storage/ProposalVolumes.jsx:310
msgid "Actions"
msgstr ""
#: src/components/core/SectionSkeleton.jsx:29
-#: src/components/core/SectionSkeleton.jsx:34
msgid "Waiting"
msgstr ""
@@ -318,22 +353,121 @@ msgstr[2] ""
msgid "Basic popover"
msgstr ""
-#: src/components/l10n/L10nPage.jsx:72
-#: src/components/l10n/LanguageSwitcher.jsx:50
-msgid "language"
+#. TRANSLATORS: placeholder text for search input in the keyboard selector.
+#: src/components/l10n/KeymapSelector.jsx:82
+msgid "Filter by description or keymap code"
+msgstr ""
+
+#: src/components/l10n/KeymapSelector.jsx:89
+msgid "Available keymaps"
+msgstr ""
+
+#: src/components/l10n/L10nPage.jsx:66 src/components/l10n/L10nPage.jsx:140
+msgid "Select time zone"
+msgstr ""
+
+#: src/components/l10n/L10nPage.jsx:67
+#, c-format
+msgid "%s will use the selected time zone."
+msgstr ""
+
+#: src/components/l10n/L10nPage.jsx:128
+msgid "Time zone"
+msgstr ""
+
+#: src/components/l10n/L10nPage.jsx:134
+msgid "Change time zone"
+msgstr ""
+
+#: src/components/l10n/L10nPage.jsx:139
+msgid "Time zone not selected yet"
+msgstr ""
+
+#: src/components/l10n/L10nPage.jsx:182 src/components/l10n/L10nPage.jsx:258
+msgid "Select language"
+msgstr ""
+
+#: src/components/l10n/L10nPage.jsx:183
+#, c-format
+msgid "%s will use the selected language."
+msgstr ""
+
+#: src/components/l10n/L10nPage.jsx:246
+msgid "Language"
+msgstr ""
+
+#: src/components/l10n/L10nPage.jsx:252
+msgid "Change language"
+msgstr ""
+
+#: src/components/l10n/L10nPage.jsx:257
+msgid "Language not selected yet"
+msgstr ""
+
+#: src/components/l10n/L10nPage.jsx:295 src/components/l10n/L10nPage.jsx:369
+msgid "Select keyboard"
+msgstr ""
+
+#: src/components/l10n/L10nPage.jsx:296
+#, c-format
+msgid "%s will use the selected keyboard."
+msgstr ""
+
+#: src/components/l10n/L10nPage.jsx:357
+msgid "Keyboard"
+msgstr ""
+
+#: src/components/l10n/L10nPage.jsx:363
+msgid "Change keyboard"
+msgstr ""
+
+#: src/components/l10n/L10nPage.jsx:368
+msgid "Keyboard not selected yet"
msgstr ""
-#. TRANSLATORS: page header
#. TRANSLATORS: page section
-#: src/components/l10n/L10nPage.jsx:84
-#: src/components/overview/L10nSection.jsx:82
+#. TRANSLATORS: page title
+#: src/components/l10n/L10nPage.jsx:385
+#: src/components/overview/L10nSection.jsx:52
msgid "Localization"
msgstr ""
+#: src/components/l10n/L10nPage.jsx:387
+#: src/components/product/ProductPage.jsx:434
+#: src/components/software/SoftwarePage.jsx:81
+#: src/components/storage/DASDPage.jsx:187
+#: src/components/storage/ISCSIPage.jsx:39
+#: src/components/storage/ProposalPage.jsx:218
+#: src/components/storage/ZFCPPage.jsx:736
+#: src/components/users/UsersPage.jsx:30
+msgid "Back"
+msgstr ""
+
#: src/components/l10n/LanguageSwitcher.jsx:46
msgid "Display Language"
msgstr ""
+#: src/components/l10n/LanguageSwitcher.jsx:50
+msgid "language"
+msgstr ""
+
+#: src/components/l10n/LocaleSelector.jsx:82
+msgid "Filter by language, territory or locale code"
+msgstr ""
+
+#: src/components/l10n/LocaleSelector.jsx:89
+msgid "Available locales"
+msgstr ""
+
+#. TRANSLATORS: placeholder text for search input in the timezone selector.
+#: src/components/l10n/TimezoneSelector.jsx:102
+msgid "Filter by territory, time zone code or UTC offset"
+msgstr ""
+
+#: src/components/l10n/TimezoneSelector.jsx:109
+msgid "Available time zones"
+msgstr ""
+
#: src/components/layout/Loading.jsx:30
msgid "Loading installation environment, please wait."
msgstr ""
@@ -394,7 +528,7 @@ msgid "IP addresses"
msgstr ""
#: src/components/network/ConnectionsTable.jsx:67
-#: src/components/storage/ProposalVolumes.jsx:233
+#: src/components/storage/ProposalVolumes.jsx:234
#: src/components/storage/iscsi/InitiatorPresenter.jsx:49
#: src/components/storage/iscsi/NodesPresenter.jsx:73
#: src/components/users/FirstUser.jsx:170
@@ -535,6 +669,8 @@ msgid "WPA & WPA2 Personal"
msgstr ""
#: src/components/network/WifiConnectionForm.jsx:91
+#: src/components/product/ProductPage.jsx:128
+#: src/components/product/ProductPage.jsx:194
#: src/components/storage/ZFCPDiskForm.jsx:112
#: src/components/storage/iscsi/DiscoverForm.jsx:108
#: src/components/storage/iscsi/LoginForm.jsx:72
@@ -607,9 +743,9 @@ msgstr ""
msgid "Forget network"
msgstr ""
-#. TRANSLATORS: %s will be replaced by a language name and code,
-#. example: "English (en_US.UTF-8)"
-#: src/components/overview/L10nSection.jsx:70
+#. TRANSLATORS: %s will be replaced by a language name and territory, example:
+#. "English (United States)".
+#: src/components/overview/L10nSection.jsx:34
#, c-format
msgid "The system will use %s as its default language."
msgstr ""
@@ -628,43 +764,61 @@ msgstr[0] ""
msgstr[1] ""
msgstr[2] ""
+#. TRANSLATORS: page title
+#: src/components/overview/Overview.jsx:47
+msgid "Installation Summary"
+msgstr ""
+
+#. TRANSLATORS: %s is replaced by a product name (e.g., SUSE ALP-Dolomite)
+#: src/components/overview/ProductSection.jsx:48
+#, c-format
+msgid "%s (registered)"
+msgstr ""
+
#: src/components/overview/SoftwareSection.jsx:37
msgid "Reading software repositories"
msgstr ""
#. TRANSLATORS: clickable link label
-#: src/components/overview/SoftwareSection.jsx:135
+#: src/components/overview/SoftwareSection.jsx:131
msgid "Refresh the repositories"
msgstr ""
-#. TRANSLATORS: page section
-#. TRANSLATORS: page title
-#: src/components/overview/SoftwareSection.jsx:145
-#: src/components/software/SoftwarePage.jsx:81
-msgid "Software"
+#: src/components/overview/StorageSection.jsx:42
+#: src/components/storage/ProposalSettingsSection.jsx:126
+msgid "No device selected yet"
msgstr ""
-#: src/components/overview/StorageSection.jsx:36
-#: src/components/storage/ProposalSettingsSection.jsx:122
-msgid "No device selected yet"
+#. TRANSLATORS: %s will be replaced by the device name and its size,
+#. example: "/dev/sda, 20 GiB"
+#: src/components/overview/StorageSection.jsx:52
+#, c-format
+msgid "Install using device %s shrinking existing partitions as needed"
msgstr ""
#. TRANSLATORS: %s will be replaced by the device name and its size,
#. example: "/dev/sda, 20 GiB"
-#: src/components/overview/StorageSection.jsx:44
+#: src/components/overview/StorageSection.jsx:56
+#, c-format
+msgid "Install using device %s without modifying existing partitions"
+msgstr ""
+
+#. TRANSLATORS: %s will be replaced by the device name and its size,
+#. example: "/dev/sda, 20 GiB"
+#: src/components/overview/StorageSection.jsx:60
#, c-format
msgid "Install using device %s and deleting all its content"
msgstr ""
-#: src/components/overview/StorageSection.jsx:57
-msgid "Probing storage devices"
+#. TRANSLATORS: %s will be replaced by the device name and its size,
+#. example: "/dev/sda, 20 GiB"
+#: src/components/overview/StorageSection.jsx:66
+#, c-format
+msgid "Install using device %s"
msgstr ""
-#. TRANSLATORS: page title
-#. TRANSLATORS: page section title
-#: src/components/overview/StorageSection.jsx:182
-#: src/components/storage/ProposalPage.jsx:218
-msgid "Storage"
+#: src/components/overview/StorageSection.jsx:83
+msgid "Probing storage devices"
msgstr ""
#. TRANSLATORS: %s will be replaced by the user name
@@ -700,6 +854,96 @@ msgstr ""
msgid "Users"
msgstr ""
+#: src/components/product/ProductPage.jsx:63
+#: src/components/product/ProductSelectionPage.jsx:73
+msgid "Choose a product"
+msgstr ""
+
+#: src/components/product/ProductPage.jsx:122
+#, c-format
+msgid "Register %s"
+msgstr ""
+
+#: src/components/product/ProductPage.jsx:188
+#, c-format
+msgid "Deregister %s"
+msgstr ""
+
+#. TRANSLATORS: %s is replaced by a product name (e.g., SUSE ALP-Dolomite)
+#: src/components/product/ProductPage.jsx:202
+#, c-format
+msgid "Do you want to deregister %s?"
+msgstr ""
+
+#: src/components/product/ProductPage.jsx:227
+msgid "Registered warning"
+msgstr ""
+
+#. TRANSLATORS: %s is replaced by a product name (e.g., SUSE ALP-Dolomite)
+#: src/components/product/ProductPage.jsx:232
+#, c-format
+msgid "The product %s must be deregistered before selecting a new product."
+msgstr ""
+
+#: src/components/product/ProductPage.jsx:263
+msgid "Change product"
+msgstr ""
+
+#: src/components/product/ProductPage.jsx:305
+msgid "Register"
+msgstr ""
+
+#: src/components/product/ProductPage.jsx:337
+msgid "Deregister product"
+msgstr ""
+
+#: src/components/product/ProductPage.jsx:370
+msgid "Code:"
+msgstr ""
+
+#: src/components/product/ProductPage.jsx:374
+msgid "Email:"
+msgstr ""
+
+#. TRANSLATORS: section title.
+#: src/components/product/ProductPage.jsx:390
+msgid "Registration"
+msgstr ""
+
+#: src/components/product/ProductPage.jsx:399
+msgid "This product requires registration."
+msgstr ""
+
+#: src/components/product/ProductPage.jsx:405
+msgid "This product does not require registration."
+msgstr ""
+
+#: src/components/product/ProductRegistrationForm.jsx:63
+msgid "Registration code"
+msgstr ""
+
+#: src/components/product/ProductRegistrationForm.jsx:66
+msgid "Email"
+msgstr ""
+
+#: src/components/product/ProductSelectionPage.jsx:58
+msgid "Loading available products, please wait..."
+msgstr ""
+
+#. TRANSLATORS: page header
+#: src/components/product/ProductSelectionPage.jsx:64
+msgid "Product selection"
+msgstr ""
+
+#. TRANSLATORS: button label
+#: src/components/product/ProductSelectionPage.jsx:69
+msgid "Select"
+msgstr ""
+
+#: src/components/product/ProductSelector.jsx:29
+msgid "No products available for selection"
+msgstr ""
+
#: src/components/questions/GenericQuestion.jsx:35
#: src/components/questions/LuksActivationQuestion.jsx:60
msgid "Question"
@@ -719,10 +963,6 @@ msgstr ""
msgid "Encryption Password"
msgstr ""
-#: src/components/software/ChangeProductLink.jsx:36
-msgid "Change product"
-msgstr ""
-
#. TRANSLATORS: pattern status, selected to install (by user)
#: src/components/software/PatternItem.jsx:63
msgid "selected"
@@ -740,46 +980,13 @@ msgstr ""
#. TRANSLATORS: error summary, always plural, %d is replaced by number of errors (2 or more)
#. if there is just a single error then the error is displayed directly instead of this summary
-#: src/components/software/PatternSelector.jsx:206
+#: src/components/software/PatternSelector.jsx:207
#, c-format
msgid "%d errors"
msgstr ""
-#: src/components/software/PatternSelector.jsx:210
-msgid "Software summary and filter options"
-msgstr ""
-
-#. TRANSLATORS: search field placeholder text
#: src/components/software/PatternSelector.jsx:215
-#: src/components/software/PatternSelector.jsx:216
-msgid "Search"
-msgstr ""
-
-#: src/components/software/ProductSelectionPage.jsx:69
-msgid "Loading available products, please wait..."
-msgstr ""
-
-#. TRANSLATORS: page header
-#: src/components/software/ProductSelectionPage.jsx:94
-msgid "Product selection"
-msgstr ""
-
-#. TRANSLATORS: button label
-#: src/components/software/ProductSelectionPage.jsx:99
-msgid "Select"
-msgstr ""
-
-#: src/components/software/ProductSelectionPage.jsx:104
-msgid "Choose a product"
-msgstr ""
-
-#: src/components/software/SoftwarePage.jsx:81
-#: src/components/storage/DASDPage.jsx:187
-#: src/components/storage/ISCSIPage.jsx:39
-#: src/components/storage/ProposalPage.jsx:218
-#: src/components/storage/ZFCPPage.jsx:736
-#: src/components/users/UsersPage.jsx:30
-msgid "Back"
+msgid "Software summary and filter options"
msgstr ""
#. TRANSLATORS: %s will be replaced by the estimated installation size,
@@ -959,65 +1166,80 @@ msgstr ""
msgid "iSCSI"
msgstr ""
-#: src/components/storage/ProposalSettingsSection.jsx:135
+#: src/components/storage/ProposalSettingsSection.jsx:139
msgid "Select the device for installing the system."
msgstr ""
-#: src/components/storage/ProposalSettingsSection.jsx:140
#: src/components/storage/ProposalSettingsSection.jsx:144
-#: src/components/storage/ProposalSettingsSection.jsx:240
+#: src/components/storage/ProposalSettingsSection.jsx:148
+#: src/components/storage/ProposalSettingsSection.jsx:244
msgid "Installation device"
msgstr ""
-#: src/components/storage/ProposalSettingsSection.jsx:150
+#: src/components/storage/ProposalSettingsSection.jsx:154
msgid "No devices found"
msgstr ""
-#: src/components/storage/ProposalSettingsSection.jsx:237
+#: src/components/storage/ProposalSettingsSection.jsx:241
msgid "Devices for creating the volume group"
msgstr ""
-#: src/components/storage/ProposalSettingsSection.jsx:246
+#: src/components/storage/ProposalSettingsSection.jsx:250
msgid "Custom devices"
msgstr ""
-#: src/components/storage/ProposalSettingsSection.jsx:310
+#: src/components/storage/ProposalSettingsSection.jsx:314
msgid ""
"Configuration of the system volume group. All the file systems will be "
"created in a logical volume of the system volume group."
msgstr ""
-#: src/components/storage/ProposalSettingsSection.jsx:316
+#: src/components/storage/ProposalSettingsSection.jsx:320
msgid "Configure the LVM settings"
msgstr ""
-#: src/components/storage/ProposalSettingsSection.jsx:321
-#: src/components/storage/ProposalSettingsSection.jsx:341
+#: src/components/storage/ProposalSettingsSection.jsx:325
+#: src/components/storage/ProposalSettingsSection.jsx:345
msgid "LVM settings"
msgstr ""
-#: src/components/storage/ProposalSettingsSection.jsx:334
+#: src/components/storage/ProposalSettingsSection.jsx:338
msgid "Use logical volume management (LVM)"
msgstr ""
-#: src/components/storage/ProposalSettingsSection.jsx:342
+#: src/components/storage/ProposalSettingsSection.jsx:346
msgid "System Volume Group"
msgstr ""
-#: src/components/storage/ProposalSettingsSection.jsx:470
+#: src/components/storage/ProposalSettingsSection.jsx:474
msgid "Change encryption password"
msgstr ""
-#: src/components/storage/ProposalSettingsSection.jsx:475
-#: src/components/storage/ProposalSettingsSection.jsx:496
+#: src/components/storage/ProposalSettingsSection.jsx:479
+#: src/components/storage/ProposalSettingsSection.jsx:500
msgid "Encryption settings"
msgstr ""
-#: src/components/storage/ProposalSettingsSection.jsx:489
+#: src/components/storage/ProposalSettingsSection.jsx:493
msgid "Use encryption"
msgstr ""
-#: src/components/storage/ProposalSettingsSection.jsx:557
+#: src/components/storage/ProposalSettingsSection.jsx:578
+msgid ""
+"Select how to make free space in the disks selected for allocating the "
+"file systems."
+msgstr ""
+
+#. TRANSLATORS: To be completed with the rest of a sentence like "deleting all content"
+#: src/components/storage/ProposalSettingsSection.jsx:584
+msgid "Find space"
+msgstr ""
+
+#: src/components/storage/ProposalSettingsSection.jsx:588
+msgid "Space Policy"
+msgstr ""
+
+#: src/components/storage/ProposalSettingsSection.jsx:662
msgid "Settings"
msgstr ""
@@ -1070,48 +1292,48 @@ msgid "partition"
msgstr ""
#. TRANSLATORS: filesystem flag, it uses an encryption
-#: src/components/storage/ProposalVolumes.jsx:215
+#: src/components/storage/ProposalVolumes.jsx:216
msgid "encrypted"
msgstr ""
#. TRANSLATORS: filesystem flag, it allows creating snapshots
-#: src/components/storage/ProposalVolumes.jsx:217
+#: src/components/storage/ProposalVolumes.jsx:218
msgid "with snapshots"
msgstr ""
#. TRANSLATORS: flag for transactional file system
-#: src/components/storage/ProposalVolumes.jsx:219
+#: src/components/storage/ProposalVolumes.jsx:220
msgid "transactional"
msgstr ""
-#: src/components/storage/ProposalVolumes.jsx:228
+#: src/components/storage/ProposalVolumes.jsx:229
#: src/components/storage/iscsi/NodesPresenter.jsx:77
msgid "Delete"
msgstr ""
-#: src/components/storage/ProposalVolumes.jsx:274
+#: src/components/storage/ProposalVolumes.jsx:275
msgid "Edit file system"
msgstr ""
-#: src/components/storage/ProposalVolumes.jsx:306
+#: src/components/storage/ProposalVolumes.jsx:307
#: src/components/storage/VolumeForm.jsx:500
msgid "Mount point"
msgstr ""
-#: src/components/storage/ProposalVolumes.jsx:307
+#: src/components/storage/ProposalVolumes.jsx:308
msgid "Details"
msgstr ""
-#: src/components/storage/ProposalVolumes.jsx:308
+#: src/components/storage/ProposalVolumes.jsx:309
#: src/components/storage/VolumeForm.jsx:517
msgid "Size"
msgstr ""
-#: src/components/storage/ProposalVolumes.jsx:344
+#: src/components/storage/ProposalVolumes.jsx:345
msgid "Table with mount points"
msgstr ""
-#: src/components/storage/ProposalVolumes.jsx:407
+#: src/components/storage/ProposalVolumes.jsx:408
msgid "File systems to create in your system"
msgstr ""
@@ -1320,64 +1542,64 @@ msgid "Storage zFCP"
msgstr ""
#. TRANSLATORS: multipath device type
-#: src/components/storage/device-utils.jsx:98
+#: src/components/storage/device-utils.jsx:97
msgid "Multipath"
msgstr ""
#. TRANSLATORS: %s is replaced by the device bus ID
-#: src/components/storage/device-utils.jsx:103
+#: src/components/storage/device-utils.jsx:102
#, c-format
msgid "DASD %s"
msgstr ""
#. TRANSLATORS: software RAID device, %s is replaced by the RAID level, e.g. RAID-1
-#: src/components/storage/device-utils.jsx:108
+#: src/components/storage/device-utils.jsx:107
#, c-format
msgid "Software %s"
msgstr ""
-#: src/components/storage/device-utils.jsx:113
+#: src/components/storage/device-utils.jsx:112
msgid "SD Card"
msgstr ""
#. TRANSLATORS: %s is replaced by the device transport name, e.g. USB, SATA, SCSI...
-#: src/components/storage/device-utils.jsx:115
+#: src/components/storage/device-utils.jsx:114
#, c-format
msgid "Transport %s"
msgstr ""
#. TRANSLATORS: RAID details, %s is replaced by list of devices used by the array
-#: src/components/storage/device-utils.jsx:134
+#: src/components/storage/device-utils.jsx:133
#, c-format
msgid "Members: %s"
msgstr ""
#. TRANSLATORS: RAID details, %s is replaced by list of devices used by the array
-#: src/components/storage/device-utils.jsx:143
+#: src/components/storage/device-utils.jsx:142
#, c-format
msgid "Devices: %s"
msgstr ""
#. TRANSLATORS: multipath details, %s is replaced by list of connections used by the device
-#: src/components/storage/device-utils.jsx:152
+#: src/components/storage/device-utils.jsx:151
#, c-format
msgid "Wires: %s"
msgstr ""
#. TRANSLATORS: disk partition info, %s is replaced by partition table
#. type (MS-DOS or GPT), %d is the number of the partitions
-#: src/components/storage/device-utils.jsx:176
+#: src/components/storage/device-utils.jsx:175
#, c-format
msgid "%s with %d partitions"
msgstr ""
#. TRANSLATORS: status message, no existing content was found on the disk,
#. i.e. the disk is completely empty
-#: src/components/storage/device-utils.jsx:200
+#: src/components/storage/device-utils.jsx:199
msgid "No content found"
msgstr ""
-#: src/components/storage/device-utils.jsx:271
+#: src/components/storage/device-utils.jsx:270
msgid "Available devices"
msgstr ""
@@ -1553,6 +1775,71 @@ msgstr ""
msgid "Targets"
msgstr ""
+#. TRANSLATORS: automatic actions to find space for installation in the target disk(s)
+#: src/components/storage/space-policy-utils.jsx:59
+msgid "Delete current content"
+msgstr ""
+
+#. TRANSLATORS: automatic actions to find space for installation in the target disk(s)
+#: src/components/storage/space-policy-utils.jsx:63
+msgid "Shrink existing partitions"
+msgstr ""
+
+#. TRANSLATORS: automatic actions to find space for installation in the target disk(s)
+#: src/components/storage/space-policy-utils.jsx:67
+msgid "Use available space"
+msgstr ""
+
+#: src/components/storage/space-policy-utils.jsx:79
+msgid "All partitions will be removed and any data in the disks will be lost."
+msgstr ""
+
+#: src/components/storage/space-policy-utils.jsx:82
+msgid ""
+"The data is kept, but the current partitions will be resized as needed to "
+"make enough space."
+msgstr ""
+
+#: src/components/storage/space-policy-utils.jsx:85
+msgid ""
+"The data is kept and existing partitions will not be modified. Only the "
+"space that is not assigned to any partition will be used."
+msgstr ""
+
+#: src/components/storage/space-policy-utils.jsx:112
+msgid "Select a mechanism to make space"
+msgstr ""
+
+#: src/components/storage/space-policy-utils.jsx:137
+#, c-format
+msgid "deleting all content of the installation device"
+msgid_plural "deleting all content of the %d selected disks"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
+#: src/components/storage/space-policy-utils.jsx:147
+#, c-format
+msgid "shrinking partitions of the installation device"
+msgid_plural "shrinking partitions of the %d selected disks"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
+#. TRANSLATORS: This is presented next to the label "Find space", so the whole sentence
+#. would read as "Find space without modifying any partition".
+#: src/components/storage/space-policy-utils.jsx:155
+msgid "without modifying any partition"
+msgstr ""
+
+#: src/components/storage/space-policy-utils.jsx:170
+#, c-format
+msgid "This will only affect the installation device"
+msgid_plural "This will affect the %d disks selected for installation"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
#: src/components/storage/utils.js:44
msgid "KiB"
msgstr ""
diff --git a/web/po/de.po b/web/po/de.po
index ca1e44c6e6..2f9ad13487 100644
--- a/web/po/de.po
+++ b/web/po/de.po
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2023-11-05 02:14+0000\n"
+"POT-Creation-Date: 2023-12-03 02:17+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Automatically generated\n"
"Language-Team: none\n"
@@ -17,7 +17,7 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
-#: src/App.jsx:112
+#: src/App.jsx:110
msgid "Diagnostic tools"
msgstr ""
@@ -61,6 +61,7 @@ msgstr ""
#: src/components/core/About.jsx:71 src/components/core/FileViewer.jsx:80
#: src/components/core/Sidebar.jsx:177 src/components/core/Terminal.jsx:48
#: src/components/network/WifiSelector.jsx:151
+#: src/components/product/ProductPage.jsx:239
msgid "Close"
msgstr ""
@@ -129,18 +130,23 @@ msgid ""
msgstr ""
#. TRANSLATORS: button label
-#: src/components/core/InstallButton.jsx:97
-#: src/components/storage/ProposalSettingsSection.jsx:166
-#: src/components/storage/ProposalSettingsSection.jsx:359
-#: src/components/storage/ProposalSettingsSection.jsx:504
+#: src/components/core/InstallButton.jsx:97 src/components/l10n/L10nPage.jsx:75
+#: src/components/l10n/L10nPage.jsx:191 src/components/l10n/L10nPage.jsx:304
+#: src/components/product/ProductPage.jsx:71
+#: src/components/product/ProductPage.jsx:140
+#: src/components/product/ProductPage.jsx:207
+#: src/components/storage/ProposalSettingsSection.jsx:170
+#: src/components/storage/ProposalSettingsSection.jsx:363
+#: src/components/storage/ProposalSettingsSection.jsx:508
+#: src/components/storage/ProposalSettingsSection.jsx:604
#: src/components/storage/ProposalVolumes.jsx:148
-#: src/components/storage/ProposalVolumes.jsx:282
+#: src/components/storage/ProposalVolumes.jsx:283
#: src/components/storage/ZFCPPage.jsx:513
msgid "Accept"
msgstr ""
#. TRANSLATORS: button label
-#: src/components/core/InstallButton.jsx:145
+#: src/components/core/InstallButton.jsx:148
msgid "Install"
msgstr ""
@@ -190,10 +196,40 @@ msgstr ""
msgid "There are new issues"
msgstr ""
-#: src/components/core/IssuesPage.jsx:115
+#. TRANSLATORS: page section
+#: src/components/core/IssuesPage.jsx:92
+#: src/components/overview/ProductSection.jsx:71
+#: src/components/product/ProductPage.jsx:434
+msgid "Product"
+msgstr ""
+
+#. TRANSLATORS: page title
+#. TRANSLATORS: page section title
+#: src/components/core/IssuesPage.jsx:100
+#: src/components/overview/StorageSection.jsx:208
+#: src/components/storage/ProposalPage.jsx:218
+msgid "Storage"
+msgstr ""
+
+#. TRANSLATORS: page title
+#. TRANSLATORS: page section
+#: src/components/core/IssuesPage.jsx:108
+#: src/components/overview/SoftwareSection.jsx:141
+#: src/components/software/SoftwarePage.jsx:81
+msgid "Software"
+msgstr ""
+
+#: src/components/core/IssuesPage.jsx:129
msgid "No issues found. Everything looks ok."
msgstr ""
+#. TRANSLATORS: search field placeholder text
+#: src/components/core/ListSearch.jsx:50
+#: src/components/software/PatternSelector.jsx:220
+#: src/components/software/PatternSelector.jsx:221
+msgid "Search"
+msgstr ""
+
#: src/components/core/LogsButton.jsx:98
msgid "Collecting logs..."
msgstr ""
@@ -253,12 +289,11 @@ msgstr ""
#. TRANSLATORS: dropdown label
#: src/components/core/RowActions.jsx:66
#: src/components/storage/ProposalVolumes.jsx:119
-#: src/components/storage/ProposalVolumes.jsx:309
+#: src/components/storage/ProposalVolumes.jsx:310
msgid "Actions"
msgstr ""
#: src/components/core/SectionSkeleton.jsx:29
-#: src/components/core/SectionSkeleton.jsx:34
msgid "Waiting"
msgstr ""
@@ -314,22 +349,121 @@ msgstr[1] ""
msgid "Basic popover"
msgstr ""
-#: src/components/l10n/L10nPage.jsx:72
-#: src/components/l10n/LanguageSwitcher.jsx:50
-msgid "language"
+#. TRANSLATORS: placeholder text for search input in the keyboard selector.
+#: src/components/l10n/KeymapSelector.jsx:82
+msgid "Filter by description or keymap code"
+msgstr ""
+
+#: src/components/l10n/KeymapSelector.jsx:89
+msgid "Available keymaps"
+msgstr ""
+
+#: src/components/l10n/L10nPage.jsx:66 src/components/l10n/L10nPage.jsx:140
+msgid "Select time zone"
+msgstr ""
+
+#: src/components/l10n/L10nPage.jsx:67
+#, c-format
+msgid "%s will use the selected time zone."
+msgstr ""
+
+#: src/components/l10n/L10nPage.jsx:128
+msgid "Time zone"
+msgstr ""
+
+#: src/components/l10n/L10nPage.jsx:134
+msgid "Change time zone"
+msgstr ""
+
+#: src/components/l10n/L10nPage.jsx:139
+msgid "Time zone not selected yet"
+msgstr ""
+
+#: src/components/l10n/L10nPage.jsx:182 src/components/l10n/L10nPage.jsx:258
+msgid "Select language"
+msgstr ""
+
+#: src/components/l10n/L10nPage.jsx:183
+#, c-format
+msgid "%s will use the selected language."
+msgstr ""
+
+#: src/components/l10n/L10nPage.jsx:246
+msgid "Language"
+msgstr ""
+
+#: src/components/l10n/L10nPage.jsx:252
+msgid "Change language"
+msgstr ""
+
+#: src/components/l10n/L10nPage.jsx:257
+msgid "Language not selected yet"
+msgstr ""
+
+#: src/components/l10n/L10nPage.jsx:295 src/components/l10n/L10nPage.jsx:369
+msgid "Select keyboard"
+msgstr ""
+
+#: src/components/l10n/L10nPage.jsx:296
+#, c-format
+msgid "%s will use the selected keyboard."
+msgstr ""
+
+#: src/components/l10n/L10nPage.jsx:357
+msgid "Keyboard"
+msgstr ""
+
+#: src/components/l10n/L10nPage.jsx:363
+msgid "Change keyboard"
+msgstr ""
+
+#: src/components/l10n/L10nPage.jsx:368
+msgid "Keyboard not selected yet"
msgstr ""
-#. TRANSLATORS: page header
#. TRANSLATORS: page section
-#: src/components/l10n/L10nPage.jsx:84
-#: src/components/overview/L10nSection.jsx:82
+#. TRANSLATORS: page title
+#: src/components/l10n/L10nPage.jsx:385
+#: src/components/overview/L10nSection.jsx:52
msgid "Localization"
msgstr ""
+#: src/components/l10n/L10nPage.jsx:387
+#: src/components/product/ProductPage.jsx:434
+#: src/components/software/SoftwarePage.jsx:81
+#: src/components/storage/DASDPage.jsx:187
+#: src/components/storage/ISCSIPage.jsx:39
+#: src/components/storage/ProposalPage.jsx:218
+#: src/components/storage/ZFCPPage.jsx:736
+#: src/components/users/UsersPage.jsx:30
+msgid "Back"
+msgstr ""
+
#: src/components/l10n/LanguageSwitcher.jsx:46
msgid "Display Language"
msgstr ""
+#: src/components/l10n/LanguageSwitcher.jsx:50
+msgid "language"
+msgstr ""
+
+#: src/components/l10n/LocaleSelector.jsx:82
+msgid "Filter by language, territory or locale code"
+msgstr ""
+
+#: src/components/l10n/LocaleSelector.jsx:89
+msgid "Available locales"
+msgstr ""
+
+#. TRANSLATORS: placeholder text for search input in the timezone selector.
+#: src/components/l10n/TimezoneSelector.jsx:102
+msgid "Filter by territory, time zone code or UTC offset"
+msgstr ""
+
+#: src/components/l10n/TimezoneSelector.jsx:109
+msgid "Available time zones"
+msgstr ""
+
#: src/components/layout/Loading.jsx:30
msgid "Loading installation environment, please wait."
msgstr ""
@@ -390,7 +524,7 @@ msgid "IP addresses"
msgstr ""
#: src/components/network/ConnectionsTable.jsx:67
-#: src/components/storage/ProposalVolumes.jsx:233
+#: src/components/storage/ProposalVolumes.jsx:234
#: src/components/storage/iscsi/InitiatorPresenter.jsx:49
#: src/components/storage/iscsi/NodesPresenter.jsx:73
#: src/components/users/FirstUser.jsx:170
@@ -531,6 +665,8 @@ msgid "WPA & WPA2 Personal"
msgstr ""
#: src/components/network/WifiConnectionForm.jsx:91
+#: src/components/product/ProductPage.jsx:128
+#: src/components/product/ProductPage.jsx:194
#: src/components/storage/ZFCPDiskForm.jsx:112
#: src/components/storage/iscsi/DiscoverForm.jsx:108
#: src/components/storage/iscsi/LoginForm.jsx:72
@@ -603,9 +739,9 @@ msgstr ""
msgid "Forget network"
msgstr ""
-#. TRANSLATORS: %s will be replaced by a language name and code,
-#. example: "English (en_US.UTF-8)"
-#: src/components/overview/L10nSection.jsx:70
+#. TRANSLATORS: %s will be replaced by a language name and territory, example:
+#. "English (United States)".
+#: src/components/overview/L10nSection.jsx:34
#, c-format
msgid "The system will use %s as its default language."
msgstr ""
@@ -623,43 +759,61 @@ msgid_plural "%d connections set:"
msgstr[0] ""
msgstr[1] ""
+#. TRANSLATORS: page title
+#: src/components/overview/Overview.jsx:47
+msgid "Installation Summary"
+msgstr ""
+
+#. TRANSLATORS: %s is replaced by a product name (e.g., SUSE ALP-Dolomite)
+#: src/components/overview/ProductSection.jsx:48
+#, c-format
+msgid "%s (registered)"
+msgstr ""
+
#: src/components/overview/SoftwareSection.jsx:37
msgid "Reading software repositories"
msgstr ""
#. TRANSLATORS: clickable link label
-#: src/components/overview/SoftwareSection.jsx:135
+#: src/components/overview/SoftwareSection.jsx:131
msgid "Refresh the repositories"
msgstr ""
-#. TRANSLATORS: page section
-#. TRANSLATORS: page title
-#: src/components/overview/SoftwareSection.jsx:145
-#: src/components/software/SoftwarePage.jsx:81
-msgid "Software"
+#: src/components/overview/StorageSection.jsx:42
+#: src/components/storage/ProposalSettingsSection.jsx:126
+msgid "No device selected yet"
msgstr ""
-#: src/components/overview/StorageSection.jsx:36
-#: src/components/storage/ProposalSettingsSection.jsx:122
-msgid "No device selected yet"
+#. TRANSLATORS: %s will be replaced by the device name and its size,
+#. example: "/dev/sda, 20 GiB"
+#: src/components/overview/StorageSection.jsx:52
+#, c-format
+msgid "Install using device %s shrinking existing partitions as needed"
msgstr ""
#. TRANSLATORS: %s will be replaced by the device name and its size,
#. example: "/dev/sda, 20 GiB"
-#: src/components/overview/StorageSection.jsx:44
+#: src/components/overview/StorageSection.jsx:56
+#, c-format
+msgid "Install using device %s without modifying existing partitions"
+msgstr ""
+
+#. TRANSLATORS: %s will be replaced by the device name and its size,
+#. example: "/dev/sda, 20 GiB"
+#: src/components/overview/StorageSection.jsx:60
#, c-format
msgid "Install using device %s and deleting all its content"
msgstr ""
-#: src/components/overview/StorageSection.jsx:57
-msgid "Probing storage devices"
+#. TRANSLATORS: %s will be replaced by the device name and its size,
+#. example: "/dev/sda, 20 GiB"
+#: src/components/overview/StorageSection.jsx:66
+#, c-format
+msgid "Install using device %s"
msgstr ""
-#. TRANSLATORS: page title
-#. TRANSLATORS: page section title
-#: src/components/overview/StorageSection.jsx:182
-#: src/components/storage/ProposalPage.jsx:218
-msgid "Storage"
+#: src/components/overview/StorageSection.jsx:83
+msgid "Probing storage devices"
msgstr ""
#. TRANSLATORS: %s will be replaced by the user name
@@ -695,6 +849,96 @@ msgstr ""
msgid "Users"
msgstr ""
+#: src/components/product/ProductPage.jsx:63
+#: src/components/product/ProductSelectionPage.jsx:73
+msgid "Choose a product"
+msgstr ""
+
+#: src/components/product/ProductPage.jsx:122
+#, c-format
+msgid "Register %s"
+msgstr ""
+
+#: src/components/product/ProductPage.jsx:188
+#, c-format
+msgid "Deregister %s"
+msgstr ""
+
+#. TRANSLATORS: %s is replaced by a product name (e.g., SUSE ALP-Dolomite)
+#: src/components/product/ProductPage.jsx:202
+#, c-format
+msgid "Do you want to deregister %s?"
+msgstr ""
+
+#: src/components/product/ProductPage.jsx:227
+msgid "Registered warning"
+msgstr ""
+
+#. TRANSLATORS: %s is replaced by a product name (e.g., SUSE ALP-Dolomite)
+#: src/components/product/ProductPage.jsx:232
+#, c-format
+msgid "The product %s must be deregistered before selecting a new product."
+msgstr ""
+
+#: src/components/product/ProductPage.jsx:263
+msgid "Change product"
+msgstr ""
+
+#: src/components/product/ProductPage.jsx:305
+msgid "Register"
+msgstr ""
+
+#: src/components/product/ProductPage.jsx:337
+msgid "Deregister product"
+msgstr ""
+
+#: src/components/product/ProductPage.jsx:370
+msgid "Code:"
+msgstr ""
+
+#: src/components/product/ProductPage.jsx:374
+msgid "Email:"
+msgstr ""
+
+#. TRANSLATORS: section title.
+#: src/components/product/ProductPage.jsx:390
+msgid "Registration"
+msgstr ""
+
+#: src/components/product/ProductPage.jsx:399
+msgid "This product requires registration."
+msgstr ""
+
+#: src/components/product/ProductPage.jsx:405
+msgid "This product does not require registration."
+msgstr ""
+
+#: src/components/product/ProductRegistrationForm.jsx:63
+msgid "Registration code"
+msgstr ""
+
+#: src/components/product/ProductRegistrationForm.jsx:66
+msgid "Email"
+msgstr ""
+
+#: src/components/product/ProductSelectionPage.jsx:58
+msgid "Loading available products, please wait..."
+msgstr ""
+
+#. TRANSLATORS: page header
+#: src/components/product/ProductSelectionPage.jsx:64
+msgid "Product selection"
+msgstr ""
+
+#. TRANSLATORS: button label
+#: src/components/product/ProductSelectionPage.jsx:69
+msgid "Select"
+msgstr ""
+
+#: src/components/product/ProductSelector.jsx:29
+msgid "No products available for selection"
+msgstr ""
+
#: src/components/questions/GenericQuestion.jsx:35
#: src/components/questions/LuksActivationQuestion.jsx:60
msgid "Question"
@@ -714,10 +958,6 @@ msgstr ""
msgid "Encryption Password"
msgstr ""
-#: src/components/software/ChangeProductLink.jsx:36
-msgid "Change product"
-msgstr ""
-
#. TRANSLATORS: pattern status, selected to install (by user)
#: src/components/software/PatternItem.jsx:63
msgid "selected"
@@ -735,46 +975,13 @@ msgstr ""
#. TRANSLATORS: error summary, always plural, %d is replaced by number of errors (2 or more)
#. if there is just a single error then the error is displayed directly instead of this summary
-#: src/components/software/PatternSelector.jsx:206
+#: src/components/software/PatternSelector.jsx:207
#, c-format
msgid "%d errors"
msgstr ""
-#: src/components/software/PatternSelector.jsx:210
-msgid "Software summary and filter options"
-msgstr ""
-
-#. TRANSLATORS: search field placeholder text
#: src/components/software/PatternSelector.jsx:215
-#: src/components/software/PatternSelector.jsx:216
-msgid "Search"
-msgstr ""
-
-#: src/components/software/ProductSelectionPage.jsx:69
-msgid "Loading available products, please wait..."
-msgstr ""
-
-#. TRANSLATORS: page header
-#: src/components/software/ProductSelectionPage.jsx:94
-msgid "Product selection"
-msgstr ""
-
-#. TRANSLATORS: button label
-#: src/components/software/ProductSelectionPage.jsx:99
-msgid "Select"
-msgstr ""
-
-#: src/components/software/ProductSelectionPage.jsx:104
-msgid "Choose a product"
-msgstr ""
-
-#: src/components/software/SoftwarePage.jsx:81
-#: src/components/storage/DASDPage.jsx:187
-#: src/components/storage/ISCSIPage.jsx:39
-#: src/components/storage/ProposalPage.jsx:218
-#: src/components/storage/ZFCPPage.jsx:736
-#: src/components/users/UsersPage.jsx:30
-msgid "Back"
+msgid "Software summary and filter options"
msgstr ""
#. TRANSLATORS: %s will be replaced by the estimated installation size,
@@ -952,65 +1159,80 @@ msgstr ""
msgid "iSCSI"
msgstr ""
-#: src/components/storage/ProposalSettingsSection.jsx:135
+#: src/components/storage/ProposalSettingsSection.jsx:139
msgid "Select the device for installing the system."
msgstr ""
-#: src/components/storage/ProposalSettingsSection.jsx:140
#: src/components/storage/ProposalSettingsSection.jsx:144
-#: src/components/storage/ProposalSettingsSection.jsx:240
+#: src/components/storage/ProposalSettingsSection.jsx:148
+#: src/components/storage/ProposalSettingsSection.jsx:244
msgid "Installation device"
msgstr ""
-#: src/components/storage/ProposalSettingsSection.jsx:150
+#: src/components/storage/ProposalSettingsSection.jsx:154
msgid "No devices found"
msgstr ""
-#: src/components/storage/ProposalSettingsSection.jsx:237
+#: src/components/storage/ProposalSettingsSection.jsx:241
msgid "Devices for creating the volume group"
msgstr ""
-#: src/components/storage/ProposalSettingsSection.jsx:246
+#: src/components/storage/ProposalSettingsSection.jsx:250
msgid "Custom devices"
msgstr ""
-#: src/components/storage/ProposalSettingsSection.jsx:310
+#: src/components/storage/ProposalSettingsSection.jsx:314
msgid ""
"Configuration of the system volume group. All the file systems will be "
"created in a logical volume of the system volume group."
msgstr ""
-#: src/components/storage/ProposalSettingsSection.jsx:316
+#: src/components/storage/ProposalSettingsSection.jsx:320
msgid "Configure the LVM settings"
msgstr ""
-#: src/components/storage/ProposalSettingsSection.jsx:321
-#: src/components/storage/ProposalSettingsSection.jsx:341
+#: src/components/storage/ProposalSettingsSection.jsx:325
+#: src/components/storage/ProposalSettingsSection.jsx:345
msgid "LVM settings"
msgstr ""
-#: src/components/storage/ProposalSettingsSection.jsx:334
+#: src/components/storage/ProposalSettingsSection.jsx:338
msgid "Use logical volume management (LVM)"
msgstr ""
-#: src/components/storage/ProposalSettingsSection.jsx:342
+#: src/components/storage/ProposalSettingsSection.jsx:346
msgid "System Volume Group"
msgstr ""
-#: src/components/storage/ProposalSettingsSection.jsx:470
+#: src/components/storage/ProposalSettingsSection.jsx:474
msgid "Change encryption password"
msgstr ""
-#: src/components/storage/ProposalSettingsSection.jsx:475
-#: src/components/storage/ProposalSettingsSection.jsx:496
+#: src/components/storage/ProposalSettingsSection.jsx:479
+#: src/components/storage/ProposalSettingsSection.jsx:500
msgid "Encryption settings"
msgstr ""
-#: src/components/storage/ProposalSettingsSection.jsx:489
+#: src/components/storage/ProposalSettingsSection.jsx:493
msgid "Use encryption"
msgstr ""
-#: src/components/storage/ProposalSettingsSection.jsx:557
+#: src/components/storage/ProposalSettingsSection.jsx:578
+msgid ""
+"Select how to make free space in the disks selected for allocating the "
+"file systems."
+msgstr ""
+
+#. TRANSLATORS: To be completed with the rest of a sentence like "deleting all content"
+#: src/components/storage/ProposalSettingsSection.jsx:584
+msgid "Find space"
+msgstr ""
+
+#: src/components/storage/ProposalSettingsSection.jsx:588
+msgid "Space Policy"
+msgstr ""
+
+#: src/components/storage/ProposalSettingsSection.jsx:662
msgid "Settings"
msgstr ""
@@ -1063,48 +1285,48 @@ msgid "partition"
msgstr ""
#. TRANSLATORS: filesystem flag, it uses an encryption
-#: src/components/storage/ProposalVolumes.jsx:215
+#: src/components/storage/ProposalVolumes.jsx:216
msgid "encrypted"
msgstr ""
#. TRANSLATORS: filesystem flag, it allows creating snapshots
-#: src/components/storage/ProposalVolumes.jsx:217
+#: src/components/storage/ProposalVolumes.jsx:218
msgid "with snapshots"
msgstr ""
#. TRANSLATORS: flag for transactional file system
-#: src/components/storage/ProposalVolumes.jsx:219
+#: src/components/storage/ProposalVolumes.jsx:220
msgid "transactional"
msgstr ""
-#: src/components/storage/ProposalVolumes.jsx:228
+#: src/components/storage/ProposalVolumes.jsx:229
#: src/components/storage/iscsi/NodesPresenter.jsx:77
msgid "Delete"
msgstr ""
-#: src/components/storage/ProposalVolumes.jsx:274
+#: src/components/storage/ProposalVolumes.jsx:275
msgid "Edit file system"
msgstr ""
-#: src/components/storage/ProposalVolumes.jsx:306
+#: src/components/storage/ProposalVolumes.jsx:307
#: src/components/storage/VolumeForm.jsx:500
msgid "Mount point"
msgstr ""
-#: src/components/storage/ProposalVolumes.jsx:307
+#: src/components/storage/ProposalVolumes.jsx:308
msgid "Details"
msgstr ""
-#: src/components/storage/ProposalVolumes.jsx:308
+#: src/components/storage/ProposalVolumes.jsx:309
#: src/components/storage/VolumeForm.jsx:517
msgid "Size"
msgstr ""
-#: src/components/storage/ProposalVolumes.jsx:344
+#: src/components/storage/ProposalVolumes.jsx:345
msgid "Table with mount points"
msgstr ""
-#: src/components/storage/ProposalVolumes.jsx:407
+#: src/components/storage/ProposalVolumes.jsx:408
msgid "File systems to create in your system"
msgstr ""
@@ -1313,64 +1535,64 @@ msgid "Storage zFCP"
msgstr ""
#. TRANSLATORS: multipath device type
-#: src/components/storage/device-utils.jsx:98
+#: src/components/storage/device-utils.jsx:97
msgid "Multipath"
msgstr ""
#. TRANSLATORS: %s is replaced by the device bus ID
-#: src/components/storage/device-utils.jsx:103
+#: src/components/storage/device-utils.jsx:102
#, c-format
msgid "DASD %s"
msgstr ""
#. TRANSLATORS: software RAID device, %s is replaced by the RAID level, e.g. RAID-1
-#: src/components/storage/device-utils.jsx:108
+#: src/components/storage/device-utils.jsx:107
#, c-format
msgid "Software %s"
msgstr ""
-#: src/components/storage/device-utils.jsx:113
+#: src/components/storage/device-utils.jsx:112
msgid "SD Card"
msgstr ""
#. TRANSLATORS: %s is replaced by the device transport name, e.g. USB, SATA, SCSI...
-#: src/components/storage/device-utils.jsx:115
+#: src/components/storage/device-utils.jsx:114
#, c-format
msgid "Transport %s"
msgstr ""
#. TRANSLATORS: RAID details, %s is replaced by list of devices used by the array
-#: src/components/storage/device-utils.jsx:134
+#: src/components/storage/device-utils.jsx:133
#, c-format
msgid "Members: %s"
msgstr ""
#. TRANSLATORS: RAID details, %s is replaced by list of devices used by the array
-#: src/components/storage/device-utils.jsx:143
+#: src/components/storage/device-utils.jsx:142
#, c-format
msgid "Devices: %s"
msgstr ""
#. TRANSLATORS: multipath details, %s is replaced by list of connections used by the device
-#: src/components/storage/device-utils.jsx:152
+#: src/components/storage/device-utils.jsx:151
#, c-format
msgid "Wires: %s"
msgstr ""
#. TRANSLATORS: disk partition info, %s is replaced by partition table
#. type (MS-DOS or GPT), %d is the number of the partitions
-#: src/components/storage/device-utils.jsx:176
+#: src/components/storage/device-utils.jsx:175
#, c-format
msgid "%s with %d partitions"
msgstr ""
#. TRANSLATORS: status message, no existing content was found on the disk,
#. i.e. the disk is completely empty
-#: src/components/storage/device-utils.jsx:200
+#: src/components/storage/device-utils.jsx:199
msgid "No content found"
msgstr ""
-#: src/components/storage/device-utils.jsx:271
+#: src/components/storage/device-utils.jsx:270
msgid "Available devices"
msgstr ""
@@ -1546,6 +1768,68 @@ msgstr ""
msgid "Targets"
msgstr ""
+#. TRANSLATORS: automatic actions to find space for installation in the target disk(s)
+#: src/components/storage/space-policy-utils.jsx:59
+msgid "Delete current content"
+msgstr ""
+
+#. TRANSLATORS: automatic actions to find space for installation in the target disk(s)
+#: src/components/storage/space-policy-utils.jsx:63
+msgid "Shrink existing partitions"
+msgstr ""
+
+#. TRANSLATORS: automatic actions to find space for installation in the target disk(s)
+#: src/components/storage/space-policy-utils.jsx:67
+msgid "Use available space"
+msgstr ""
+
+#: src/components/storage/space-policy-utils.jsx:79
+msgid "All partitions will be removed and any data in the disks will be lost."
+msgstr ""
+
+#: src/components/storage/space-policy-utils.jsx:82
+msgid ""
+"The data is kept, but the current partitions will be resized as needed to "
+"make enough space."
+msgstr ""
+
+#: src/components/storage/space-policy-utils.jsx:85
+msgid ""
+"The data is kept and existing partitions will not be modified. Only the "
+"space that is not assigned to any partition will be used."
+msgstr ""
+
+#: src/components/storage/space-policy-utils.jsx:112
+msgid "Select a mechanism to make space"
+msgstr ""
+
+#: src/components/storage/space-policy-utils.jsx:137
+#, c-format
+msgid "deleting all content of the installation device"
+msgid_plural "deleting all content of the %d selected disks"
+msgstr[0] ""
+msgstr[1] ""
+
+#: src/components/storage/space-policy-utils.jsx:147
+#, c-format
+msgid "shrinking partitions of the installation device"
+msgid_plural "shrinking partitions of the %d selected disks"
+msgstr[0] ""
+msgstr[1] ""
+
+#. TRANSLATORS: This is presented next to the label "Find space", so the whole sentence
+#. would read as "Find space without modifying any partition".
+#: src/components/storage/space-policy-utils.jsx:155
+msgid "without modifying any partition"
+msgstr ""
+
+#: src/components/storage/space-policy-utils.jsx:170
+#, c-format
+msgid "This will only affect the installation device"
+msgid_plural "This will affect the %d disks selected for installation"
+msgstr[0] ""
+msgstr[1] ""
+
#: src/components/storage/utils.js:44
msgid "KiB"
msgstr ""
diff --git a/web/po/es.po b/web/po/es.po
index 63dcfe787e..d66fbecc57 100644
--- a/web/po/es.po
+++ b/web/po/es.po
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2023-11-05 02:14+0000\n"
+"POT-Creation-Date: 2023-12-03 02:17+0000\n"
"PO-Revision-Date: 2023-10-27 13:15+0000\n"
"Last-Translator: Victor hck \n"
"Language-Team: Spanish \n"
"Language-Team: French 1;\n"
"X-Generator: Weblate 4.9.1\n"
-#: src/App.jsx:112
+#: src/App.jsx:110
msgid "Diagnostic tools"
msgstr "Outils de diagnostic"
@@ -68,6 +68,7 @@ msgstr "Pour plus d'informations, veuillez consulter le dépôt du projet à %s.
#: src/components/core/About.jsx:71 src/components/core/FileViewer.jsx:80
#: src/components/core/Sidebar.jsx:177 src/components/core/Terminal.jsx:48
#: src/components/network/WifiSelector.jsx:151
+#: src/components/product/ProductPage.jsx:239
msgid "Close"
msgstr "Fermer"
@@ -144,18 +145,23 @@ msgstr ""
"Veuillez consulter les erreurs signalées et réessayer."
#. TRANSLATORS: button label
-#: src/components/core/InstallButton.jsx:97
-#: src/components/storage/ProposalSettingsSection.jsx:166
-#: src/components/storage/ProposalSettingsSection.jsx:359
-#: src/components/storage/ProposalSettingsSection.jsx:504
+#: src/components/core/InstallButton.jsx:97 src/components/l10n/L10nPage.jsx:75
+#: src/components/l10n/L10nPage.jsx:191 src/components/l10n/L10nPage.jsx:304
+#: src/components/product/ProductPage.jsx:71
+#: src/components/product/ProductPage.jsx:140
+#: src/components/product/ProductPage.jsx:207
+#: src/components/storage/ProposalSettingsSection.jsx:170
+#: src/components/storage/ProposalSettingsSection.jsx:363
+#: src/components/storage/ProposalSettingsSection.jsx:508
+#: src/components/storage/ProposalSettingsSection.jsx:604
#: src/components/storage/ProposalVolumes.jsx:148
-#: src/components/storage/ProposalVolumes.jsx:282
+#: src/components/storage/ProposalVolumes.jsx:283
#: src/components/storage/ZFCPPage.jsx:513
msgid "Accept"
msgstr "Accepter"
#. TRANSLATORS: button label
-#: src/components/core/InstallButton.jsx:145
+#: src/components/core/InstallButton.jsx:148
msgid "Install"
msgstr "Installer"
@@ -207,10 +213,40 @@ msgstr "Montrer les problèmes"
msgid "There are new issues"
msgstr "Il y a de nouveaux problèmes"
-#: src/components/core/IssuesPage.jsx:115
+#. TRANSLATORS: page section
+#: src/components/core/IssuesPage.jsx:92
+#: src/components/overview/ProductSection.jsx:71
+#: src/components/product/ProductPage.jsx:434
+msgid "Product"
+msgstr ""
+
+#. TRANSLATORS: page title
+#. TRANSLATORS: page section title
+#: src/components/core/IssuesPage.jsx:100
+#: src/components/overview/StorageSection.jsx:208
+#: src/components/storage/ProposalPage.jsx:218
+msgid "Storage"
+msgstr "Stockage"
+
+#. TRANSLATORS: page title
+#. TRANSLATORS: page section
+#: src/components/core/IssuesPage.jsx:108
+#: src/components/overview/SoftwareSection.jsx:141
+#: src/components/software/SoftwarePage.jsx:81
+msgid "Software"
+msgstr "Logiciel"
+
+#: src/components/core/IssuesPage.jsx:129
msgid "No issues found. Everything looks ok."
msgstr "Aucun problème n'a été détecté. Tout semble correct."
+#. TRANSLATORS: search field placeholder text
+#: src/components/core/ListSearch.jsx:50
+#: src/components/software/PatternSelector.jsx:220
+#: src/components/software/PatternSelector.jsx:221
+msgid "Search"
+msgstr "Rechercher"
+
#: src/components/core/LogsButton.jsx:98
msgid "Collecting logs..."
msgstr "Collecte des journaux..."
@@ -274,12 +310,11 @@ msgstr "Annuler"
#. TRANSLATORS: dropdown label
#: src/components/core/RowActions.jsx:66
#: src/components/storage/ProposalVolumes.jsx:119
-#: src/components/storage/ProposalVolumes.jsx:309
+#: src/components/storage/ProposalVolumes.jsx:310
msgid "Actions"
msgstr "Actions"
#: src/components/core/SectionSkeleton.jsx:29
-#: src/components/core/SectionSkeleton.jsx:34
msgid "Waiting"
msgstr "En attente"
@@ -335,22 +370,132 @@ msgstr[1] "erreurs %d trouvées"
msgid "Basic popover"
msgstr "Popover basique"
-#: src/components/l10n/L10nPage.jsx:72
-#: src/components/l10n/LanguageSwitcher.jsx:50
-msgid "language"
+#. TRANSLATORS: placeholder text for search input in the keyboard selector.
+#: src/components/l10n/KeymapSelector.jsx:82
+msgid "Filter by description or keymap code"
+msgstr ""
+
+#: src/components/l10n/KeymapSelector.jsx:89
+#, fuzzy
+msgid "Available keymaps"
+msgstr "Périphériques disponibles"
+
+#: src/components/l10n/L10nPage.jsx:66 src/components/l10n/L10nPage.jsx:140
+msgid "Select time zone"
+msgstr ""
+
+#: src/components/l10n/L10nPage.jsx:67
+#, c-format
+msgid "%s will use the selected time zone."
+msgstr ""
+
+#: src/components/l10n/L10nPage.jsx:128
+msgid "Time zone"
+msgstr ""
+
+#: src/components/l10n/L10nPage.jsx:134
+msgid "Change time zone"
+msgstr ""
+
+#: src/components/l10n/L10nPage.jsx:139
+#, fuzzy
+msgid "Time zone not selected yet"
+msgstr "non sélectionné"
+
+#: src/components/l10n/L10nPage.jsx:182 src/components/l10n/L10nPage.jsx:258
+#, fuzzy
+msgid "Select language"
msgstr "langue"
-#. TRANSLATORS: page header
+#: src/components/l10n/L10nPage.jsx:183
+#, fuzzy, c-format
+msgid "%s will use the selected language."
+msgstr "Le système utilisera %s comme langue par défaut."
+
+#: src/components/l10n/L10nPage.jsx:246
+#, fuzzy
+msgid "Language"
+msgstr "langue"
+
+#: src/components/l10n/L10nPage.jsx:252
+#, fuzzy
+msgid "Change language"
+msgstr "langue"
+
+#: src/components/l10n/L10nPage.jsx:257
+#, fuzzy
+msgid "Language not selected yet"
+msgstr "non sélectionné"
+
+#: src/components/l10n/L10nPage.jsx:295 src/components/l10n/L10nPage.jsx:369
+#, fuzzy
+msgid "Select keyboard"
+msgstr "sélectionné"
+
+#: src/components/l10n/L10nPage.jsx:296
+#, c-format
+msgid "%s will use the selected keyboard."
+msgstr ""
+
+#: src/components/l10n/L10nPage.jsx:357
+msgid "Keyboard"
+msgstr ""
+
+#: src/components/l10n/L10nPage.jsx:363
+#, fuzzy
+msgid "Change keyboard"
+msgstr "Modifier le mot de passe de chiffrement"
+
+#: src/components/l10n/L10nPage.jsx:368
+#, fuzzy
+msgid "Keyboard not selected yet"
+msgstr "non sélectionné"
+
#. TRANSLATORS: page section
-#: src/components/l10n/L10nPage.jsx:84
-#: src/components/overview/L10nSection.jsx:82
+#. TRANSLATORS: page title
+#: src/components/l10n/L10nPage.jsx:385
+#: src/components/overview/L10nSection.jsx:52
msgid "Localization"
msgstr "Localisation"
+#: src/components/l10n/L10nPage.jsx:387
+#: src/components/product/ProductPage.jsx:434
+#: src/components/software/SoftwarePage.jsx:81
+#: src/components/storage/DASDPage.jsx:187
+#: src/components/storage/ISCSIPage.jsx:39
+#: src/components/storage/ProposalPage.jsx:218
+#: src/components/storage/ZFCPPage.jsx:736
+#: src/components/users/UsersPage.jsx:30
+msgid "Back"
+msgstr "Retour"
+
#: src/components/l10n/LanguageSwitcher.jsx:46
msgid "Display Language"
msgstr "Langue d'affichage"
+#: src/components/l10n/LanguageSwitcher.jsx:50
+msgid "language"
+msgstr "langue"
+
+#: src/components/l10n/LocaleSelector.jsx:82
+msgid "Filter by language, territory or locale code"
+msgstr ""
+
+#: src/components/l10n/LocaleSelector.jsx:89
+#, fuzzy
+msgid "Available locales"
+msgstr "Périphériques disponibles"
+
+#. TRANSLATORS: placeholder text for search input in the timezone selector.
+#: src/components/l10n/TimezoneSelector.jsx:102
+msgid "Filter by territory, time zone code or UTC offset"
+msgstr ""
+
+#: src/components/l10n/TimezoneSelector.jsx:109
+#, fuzzy
+msgid "Available time zones"
+msgstr "Périphériques disponibles"
+
#: src/components/layout/Loading.jsx:30
msgid "Loading installation environment, please wait."
msgstr "Chargement de l'environnement d'installation, veuillez patienter."
@@ -411,7 +556,7 @@ msgid "IP addresses"
msgstr "Adresses IP"
#: src/components/network/ConnectionsTable.jsx:67
-#: src/components/storage/ProposalVolumes.jsx:233
+#: src/components/storage/ProposalVolumes.jsx:234
#: src/components/storage/iscsi/InitiatorPresenter.jsx:49
#: src/components/storage/iscsi/NodesPresenter.jsx:73
#: src/components/users/FirstUser.jsx:170
@@ -555,6 +700,8 @@ msgid "WPA & WPA2 Personal"
msgstr "WPA & WPA2 Personal"
#: src/components/network/WifiConnectionForm.jsx:91
+#: src/components/product/ProductPage.jsx:128
+#: src/components/product/ProductPage.jsx:194
#: src/components/storage/ZFCPDiskForm.jsx:112
#: src/components/storage/iscsi/DiscoverForm.jsx:108
#: src/components/storage/iscsi/LoginForm.jsx:72
@@ -627,9 +774,9 @@ msgstr "La connexion %s attend un changement d'état"
msgid "Forget network"
msgstr "Oublier le réseau"
-#. TRANSLATORS: %s will be replaced by a language name and code,
-#. example: "English (en_US.UTF-8)"
-#: src/components/overview/L10nSection.jsx:70
+#. TRANSLATORS: %s will be replaced by a language name and territory, example:
+#. "English (United States)".
+#: src/components/overview/L10nSection.jsx:34
#, c-format
msgid "The system will use %s as its default language."
msgstr "Le système utilisera %s comme langue par défaut."
@@ -647,46 +794,67 @@ msgid_plural "%d connections set:"
msgstr[0] "Connexion %d établie:"
msgstr[1] "Connexions %d établies:"
+#. TRANSLATORS: page title
+#: src/components/overview/Overview.jsx:47
+#, fuzzy
+msgid "Installation Summary"
+msgstr "Périphérique d'installation"
+
+#. TRANSLATORS: %s is replaced by a product name (e.g., SUSE ALP-Dolomite)
+#: src/components/overview/ProductSection.jsx:48
+#, c-format
+msgid "%s (registered)"
+msgstr ""
+
#: src/components/overview/SoftwareSection.jsx:37
msgid "Reading software repositories"
msgstr "Lecture des dépôts de logiciels"
#. TRANSLATORS: clickable link label
-#: src/components/overview/SoftwareSection.jsx:135
+#: src/components/overview/SoftwareSection.jsx:131
msgid "Refresh the repositories"
msgstr "Rafraîchir les dépôts"
-#. TRANSLATORS: page section
-#. TRANSLATORS: page title
-#: src/components/overview/SoftwareSection.jsx:145
-#: src/components/software/SoftwarePage.jsx:81
-msgid "Software"
-msgstr "Logiciel"
-
-#: src/components/overview/StorageSection.jsx:36
-#: src/components/storage/ProposalSettingsSection.jsx:122
+#: src/components/overview/StorageSection.jsx:42
+#: src/components/storage/ProposalSettingsSection.jsx:126
msgid "No device selected yet"
msgstr "Aucun périphérique n'a encore été sélectionné"
#. TRANSLATORS: %s will be replaced by the device name and its size,
#. example: "/dev/sda, 20 GiB"
-#: src/components/overview/StorageSection.jsx:44
+#: src/components/overview/StorageSection.jsx:52
+#, fuzzy, c-format
+msgid "Install using device %s shrinking existing partitions as needed"
+msgstr ""
+"Installer en utilisant le périphérique %s et en supprimant tout son contenu"
+
+#. TRANSLATORS: %s will be replaced by the device name and its size,
+#. example: "/dev/sda, 20 GiB"
+#: src/components/overview/StorageSection.jsx:56
+#, fuzzy, c-format
+msgid "Install using device %s without modifying existing partitions"
+msgstr ""
+"Installer en utilisant le périphérique %s et en supprimant tout son contenu"
+
+#. TRANSLATORS: %s will be replaced by the device name and its size,
+#. example: "/dev/sda, 20 GiB"
+#: src/components/overview/StorageSection.jsx:60
#, c-format
msgid "Install using device %s and deleting all its content"
msgstr ""
"Installer en utilisant le périphérique %s et en supprimant tout son contenu"
-#: src/components/overview/StorageSection.jsx:57
+#. TRANSLATORS: %s will be replaced by the device name and its size,
+#. example: "/dev/sda, 20 GiB"
+#: src/components/overview/StorageSection.jsx:66
+#, fuzzy, c-format
+msgid "Install using device %s"
+msgstr "Périphérique d'installation"
+
+#: src/components/overview/StorageSection.jsx:83
msgid "Probing storage devices"
msgstr "Sonder les périphériques de stockage"
-#. TRANSLATORS: page title
-#. TRANSLATORS: page section title
-#: src/components/overview/StorageSection.jsx:182
-#: src/components/storage/ProposalPage.jsx:218
-msgid "Storage"
-msgstr "Stockage"
-
#. TRANSLATORS: %s will be replaced by the user name
#: src/components/overview/UsersSection.jsx:80
#, c-format
@@ -722,6 +890,99 @@ msgstr "Authentification root pour l'utilisation d'une clé SSH publique"
msgid "Users"
msgstr "Utilisateurs"
+#: src/components/product/ProductPage.jsx:63
+#: src/components/product/ProductSelectionPage.jsx:73
+msgid "Choose a product"
+msgstr "Choisir un produit"
+
+#: src/components/product/ProductPage.jsx:122
+#, c-format
+msgid "Register %s"
+msgstr ""
+
+#: src/components/product/ProductPage.jsx:188
+#, c-format
+msgid "Deregister %s"
+msgstr ""
+
+#. TRANSLATORS: %s is replaced by a product name (e.g., SUSE ALP-Dolomite)
+#: src/components/product/ProductPage.jsx:202
+#, c-format
+msgid "Do you want to deregister %s?"
+msgstr ""
+
+#: src/components/product/ProductPage.jsx:227
+msgid "Registered warning"
+msgstr ""
+
+#. TRANSLATORS: %s is replaced by a product name (e.g., SUSE ALP-Dolomite)
+#: src/components/product/ProductPage.jsx:232
+#, c-format
+msgid "The product %s must be deregistered before selecting a new product."
+msgstr ""
+
+#: src/components/product/ProductPage.jsx:263
+msgid "Change product"
+msgstr "Changer de produit"
+
+#: src/components/product/ProductPage.jsx:305
+msgid "Register"
+msgstr ""
+
+#: src/components/product/ProductPage.jsx:337
+#, fuzzy
+msgid "Deregister product"
+msgstr "Changer de produit"
+
+#: src/components/product/ProductPage.jsx:370
+msgid "Code:"
+msgstr ""
+
+#: src/components/product/ProductPage.jsx:374
+msgid "Email:"
+msgstr ""
+
+#. TRANSLATORS: section title.
+#: src/components/product/ProductPage.jsx:390
+#, fuzzy
+msgid "Registration"
+msgstr "Question"
+
+#: src/components/product/ProductPage.jsx:399
+msgid "This product requires registration."
+msgstr ""
+
+#: src/components/product/ProductPage.jsx:405
+msgid "This product does not require registration."
+msgstr ""
+
+#: src/components/product/ProductRegistrationForm.jsx:63
+msgid "Registration code"
+msgstr ""
+
+#: src/components/product/ProductRegistrationForm.jsx:66
+msgid "Email"
+msgstr ""
+
+#: src/components/product/ProductSelectionPage.jsx:58
+msgid "Loading available products, please wait..."
+msgstr "Chargement des produits disponibles, veuillez patienter..."
+
+#. TRANSLATORS: page header
+#: src/components/product/ProductSelectionPage.jsx:64
+msgid "Product selection"
+msgstr "Sélection des produits"
+
+#. TRANSLATORS: button label
+#: src/components/product/ProductSelectionPage.jsx:69
+msgid "Select"
+msgstr "Sélectionner"
+
+#: src/components/product/ProductSelector.jsx:29
+#, fuzzy
+msgid "No products available for selection"
+msgstr "Sélection des produits"
+
#: src/components/questions/GenericQuestion.jsx:35
#: src/components/questions/LuksActivationQuestion.jsx:60
msgid "Question"
@@ -741,10 +1002,6 @@ msgstr "Appareil chiffré"
msgid "Encryption Password"
msgstr "Mot de passe de chiffrement"
-#: src/components/software/ChangeProductLink.jsx:36
-msgid "Change product"
-msgstr "Changer de produit"
-
#. TRANSLATORS: pattern status, selected to install (by user)
#: src/components/software/PatternItem.jsx:63
msgid "selected"
@@ -762,48 +1019,15 @@ msgstr "non sélectionné"
#. TRANSLATORS: error summary, always plural, %d is replaced by number of errors (2 or more)
#. if there is just a single error then the error is displayed directly instead of this summary
-#: src/components/software/PatternSelector.jsx:206
+#: src/components/software/PatternSelector.jsx:207
#, c-format
msgid "%d errors"
msgstr "%d erreurs"
-#: src/components/software/PatternSelector.jsx:210
+#: src/components/software/PatternSelector.jsx:215
msgid "Software summary and filter options"
msgstr "Synthèse logiciel et options de filtrage"
-#. TRANSLATORS: search field placeholder text
-#: src/components/software/PatternSelector.jsx:215
-#: src/components/software/PatternSelector.jsx:216
-msgid "Search"
-msgstr "Rechercher"
-
-#: src/components/software/ProductSelectionPage.jsx:69
-msgid "Loading available products, please wait..."
-msgstr "Chargement des produits disponibles, veuillez patienter..."
-
-#. TRANSLATORS: page header
-#: src/components/software/ProductSelectionPage.jsx:94
-msgid "Product selection"
-msgstr "Sélection des produits"
-
-#. TRANSLATORS: button label
-#: src/components/software/ProductSelectionPage.jsx:99
-msgid "Select"
-msgstr "Sélectionner"
-
-#: src/components/software/ProductSelectionPage.jsx:104
-msgid "Choose a product"
-msgstr "Choisir un produit"
-
-#: src/components/software/SoftwarePage.jsx:81
-#: src/components/storage/DASDPage.jsx:187
-#: src/components/storage/ISCSIPage.jsx:39
-#: src/components/storage/ProposalPage.jsx:218
-#: src/components/storage/ZFCPPage.jsx:736
-#: src/components/users/UsersPage.jsx:30
-msgid "Back"
-msgstr "Retour"
-
#. TRANSLATORS: %s will be replaced by the estimated installation size,
#. example: "728.8 MiB"
#: src/components/software/UsedSize.jsx:32
@@ -983,29 +1207,29 @@ msgstr "Se connecter aux cibles iSCSI"
msgid "iSCSI"
msgstr "iSCSI"
-#: src/components/storage/ProposalSettingsSection.jsx:135
+#: src/components/storage/ProposalSettingsSection.jsx:139
msgid "Select the device for installing the system."
msgstr "Sélectionner le périphérique pour l'installation du système."
-#: src/components/storage/ProposalSettingsSection.jsx:140
#: src/components/storage/ProposalSettingsSection.jsx:144
-#: src/components/storage/ProposalSettingsSection.jsx:240
+#: src/components/storage/ProposalSettingsSection.jsx:148
+#: src/components/storage/ProposalSettingsSection.jsx:244
msgid "Installation device"
msgstr "Périphérique d'installation"
-#: src/components/storage/ProposalSettingsSection.jsx:150
+#: src/components/storage/ProposalSettingsSection.jsx:154
msgid "No devices found"
msgstr "Aucun périphérique n'a été trouvé"
-#: src/components/storage/ProposalSettingsSection.jsx:237
+#: src/components/storage/ProposalSettingsSection.jsx:241
msgid "Devices for creating the volume group"
msgstr "Périphériques pour la création du groupe de volumes"
-#: src/components/storage/ProposalSettingsSection.jsx:246
+#: src/components/storage/ProposalSettingsSection.jsx:250
msgid "Custom devices"
msgstr "Périphériques personnalisés"
-#: src/components/storage/ProposalSettingsSection.jsx:310
+#: src/components/storage/ProposalSettingsSection.jsx:314
msgid ""
"Configuration of the system volume group. All the file systems will be "
"created in a logical volume of the system volume group."
@@ -1013,37 +1237,52 @@ msgstr ""
"Configuration du groupe de volumes système. Tous les systèmes de fichiers "
"seront créés dans un volume logique du groupe de volume système."
-#: src/components/storage/ProposalSettingsSection.jsx:316
+#: src/components/storage/ProposalSettingsSection.jsx:320
msgid "Configure the LVM settings"
msgstr "Configurer les paramètres LVM"
-#: src/components/storage/ProposalSettingsSection.jsx:321
-#: src/components/storage/ProposalSettingsSection.jsx:341
+#: src/components/storage/ProposalSettingsSection.jsx:325
+#: src/components/storage/ProposalSettingsSection.jsx:345
msgid "LVM settings"
msgstr "Paramètres LVM"
-#: src/components/storage/ProposalSettingsSection.jsx:334
+#: src/components/storage/ProposalSettingsSection.jsx:338
msgid "Use logical volume management (LVM)"
msgstr "Utiliser la gestion des volumes logiques (LVM)"
-#: src/components/storage/ProposalSettingsSection.jsx:342
+#: src/components/storage/ProposalSettingsSection.jsx:346
msgid "System Volume Group"
msgstr "Groupe de volume système"
-#: src/components/storage/ProposalSettingsSection.jsx:470
+#: src/components/storage/ProposalSettingsSection.jsx:474
msgid "Change encryption password"
msgstr "Modifier le mot de passe de chiffrement"
-#: src/components/storage/ProposalSettingsSection.jsx:475
-#: src/components/storage/ProposalSettingsSection.jsx:496
+#: src/components/storage/ProposalSettingsSection.jsx:479
+#: src/components/storage/ProposalSettingsSection.jsx:500
msgid "Encryption settings"
msgstr "Paramètres de chiffrement"
-#: src/components/storage/ProposalSettingsSection.jsx:489
+#: src/components/storage/ProposalSettingsSection.jsx:493
msgid "Use encryption"
msgstr "Utiliser le chiffrement"
-#: src/components/storage/ProposalSettingsSection.jsx:557
+#: src/components/storage/ProposalSettingsSection.jsx:578
+msgid ""
+"Select how to make free space in the disks selected for allocating the "
+"file systems."
+msgstr ""
+
+#. TRANSLATORS: To be completed with the rest of a sentence like "deleting all content"
+#: src/components/storage/ProposalSettingsSection.jsx:584
+msgid "Find space"
+msgstr ""
+
+#: src/components/storage/ProposalSettingsSection.jsx:588
+msgid "Space Policy"
+msgstr ""
+
+#: src/components/storage/ProposalSettingsSection.jsx:662
msgid "Settings"
msgstr "Paramètres"
@@ -1096,48 +1335,48 @@ msgid "partition"
msgstr "partition"
#. TRANSLATORS: filesystem flag, it uses an encryption
-#: src/components/storage/ProposalVolumes.jsx:215
+#: src/components/storage/ProposalVolumes.jsx:216
msgid "encrypted"
msgstr "chiffré"
#. TRANSLATORS: filesystem flag, it allows creating snapshots
-#: src/components/storage/ProposalVolumes.jsx:217
+#: src/components/storage/ProposalVolumes.jsx:218
msgid "with snapshots"
msgstr "avec des clichés"
#. TRANSLATORS: flag for transactional file system
-#: src/components/storage/ProposalVolumes.jsx:219
+#: src/components/storage/ProposalVolumes.jsx:220
msgid "transactional"
msgstr "transactionnel"
-#: src/components/storage/ProposalVolumes.jsx:228
+#: src/components/storage/ProposalVolumes.jsx:229
#: src/components/storage/iscsi/NodesPresenter.jsx:77
msgid "Delete"
msgstr "Supprimer"
-#: src/components/storage/ProposalVolumes.jsx:274
+#: src/components/storage/ProposalVolumes.jsx:275
msgid "Edit file system"
msgstr "Modifier le système de fichiers"
-#: src/components/storage/ProposalVolumes.jsx:306
+#: src/components/storage/ProposalVolumes.jsx:307
#: src/components/storage/VolumeForm.jsx:500
msgid "Mount point"
msgstr "Point de montage"
-#: src/components/storage/ProposalVolumes.jsx:307
+#: src/components/storage/ProposalVolumes.jsx:308
msgid "Details"
msgstr "Détails"
-#: src/components/storage/ProposalVolumes.jsx:308
+#: src/components/storage/ProposalVolumes.jsx:309
#: src/components/storage/VolumeForm.jsx:517
msgid "Size"
msgstr "Taille"
-#: src/components/storage/ProposalVolumes.jsx:344
+#: src/components/storage/ProposalVolumes.jsx:345
msgid "Table with mount points"
msgstr "Table avec points de montage"
-#: src/components/storage/ProposalVolumes.jsx:407
+#: src/components/storage/ProposalVolumes.jsx:408
msgid "File systems to create in your system"
msgstr "Systèmes de fichiers à créer dans votre système"
@@ -1354,64 +1593,64 @@ msgid "Storage zFCP"
msgstr "Stockage zFCP"
#. TRANSLATORS: multipath device type
-#: src/components/storage/device-utils.jsx:98
+#: src/components/storage/device-utils.jsx:97
msgid "Multipath"
msgstr "Chemins multiples"
#. TRANSLATORS: %s is replaced by the device bus ID
-#: src/components/storage/device-utils.jsx:103
+#: src/components/storage/device-utils.jsx:102
#, c-format
msgid "DASD %s"
msgstr "DASD %s"
#. TRANSLATORS: software RAID device, %s is replaced by the RAID level, e.g. RAID-1
-#: src/components/storage/device-utils.jsx:108
+#: src/components/storage/device-utils.jsx:107
#, c-format
msgid "Software %s"
msgstr "Logiciel %s"
-#: src/components/storage/device-utils.jsx:113
+#: src/components/storage/device-utils.jsx:112
msgid "SD Card"
msgstr "Carte SD"
#. TRANSLATORS: %s is replaced by the device transport name, e.g. USB, SATA, SCSI...
-#: src/components/storage/device-utils.jsx:115
+#: src/components/storage/device-utils.jsx:114
#, c-format
msgid "Transport %s"
msgstr "Transport %s"
#. TRANSLATORS: RAID details, %s is replaced by list of devices used by the array
-#: src/components/storage/device-utils.jsx:134
+#: src/components/storage/device-utils.jsx:133
#, c-format
msgid "Members: %s"
msgstr "Membres : %s"
#. TRANSLATORS: RAID details, %s is replaced by list of devices used by the array
-#: src/components/storage/device-utils.jsx:143
+#: src/components/storage/device-utils.jsx:142
#, c-format
msgid "Devices: %s"
msgstr "Périphériques : %s"
#. TRANSLATORS: multipath details, %s is replaced by list of connections used by the device
-#: src/components/storage/device-utils.jsx:152
+#: src/components/storage/device-utils.jsx:151
#, c-format
msgid "Wires: %s"
msgstr "Chemins: %s"
#. TRANSLATORS: disk partition info, %s is replaced by partition table
#. type (MS-DOS or GPT), %d is the number of the partitions
-#: src/components/storage/device-utils.jsx:176
+#: src/components/storage/device-utils.jsx:175
#, c-format
msgid "%s with %d partitions"
msgstr "%s avec %d partitions"
#. TRANSLATORS: status message, no existing content was found on the disk,
#. i.e. the disk is completely empty
-#: src/components/storage/device-utils.jsx:200
+#: src/components/storage/device-utils.jsx:199
msgid "No content found"
msgstr "Aucun contenu n'a été trouvé"
-#: src/components/storage/device-utils.jsx:271
+#: src/components/storage/device-utils.jsx:270
msgid "Available devices"
msgstr "Périphériques disponibles"
@@ -1589,6 +1828,70 @@ msgstr "Découvrir"
msgid "Targets"
msgstr "Cibles"
+#. TRANSLATORS: automatic actions to find space for installation in the target disk(s)
+#: src/components/storage/space-policy-utils.jsx:59
+msgid "Delete current content"
+msgstr ""
+
+#. TRANSLATORS: automatic actions to find space for installation in the target disk(s)
+#: src/components/storage/space-policy-utils.jsx:63
+msgid "Shrink existing partitions"
+msgstr ""
+
+#. TRANSLATORS: automatic actions to find space for installation in the target disk(s)
+#: src/components/storage/space-policy-utils.jsx:67
+#, fuzzy
+msgid "Use available space"
+msgstr "Périphériques disponibles"
+
+#: src/components/storage/space-policy-utils.jsx:79
+msgid "All partitions will be removed and any data in the disks will be lost."
+msgstr ""
+
+#: src/components/storage/space-policy-utils.jsx:82
+msgid ""
+"The data is kept, but the current partitions will be resized as needed to "
+"make enough space."
+msgstr ""
+
+#: src/components/storage/space-policy-utils.jsx:85
+msgid ""
+"The data is kept and existing partitions will not be modified. Only the "
+"space that is not assigned to any partition will be used."
+msgstr ""
+
+#: src/components/storage/space-policy-utils.jsx:112
+msgid "Select a mechanism to make space"
+msgstr ""
+
+#: src/components/storage/space-policy-utils.jsx:137
+#, c-format
+msgid "deleting all content of the installation device"
+msgid_plural "deleting all content of the %d selected disks"
+msgstr[0] ""
+msgstr[1] ""
+
+#: src/components/storage/space-policy-utils.jsx:147
+#, c-format
+msgid "shrinking partitions of the installation device"
+msgid_plural "shrinking partitions of the %d selected disks"
+msgstr[0] ""
+msgstr[1] ""
+
+#. TRANSLATORS: This is presented next to the label "Find space", so the whole sentence
+#. would read as "Find space without modifying any partition".
+#: src/components/storage/space-policy-utils.jsx:155
+#, fuzzy
+msgid "without modifying any partition"
+msgstr "%s avec %d partitions"
+
+#: src/components/storage/space-policy-utils.jsx:170
+#, c-format
+msgid "This will only affect the installation device"
+msgid_plural "This will affect the %d disks selected for installation"
+msgstr[0] ""
+msgstr[1] ""
+
#: src/components/storage/utils.js:44
msgid "KiB"
msgstr "KiB"
diff --git a/web/po/ja.po b/web/po/ja.po
index 26db6fb39b..2c34aeb395 100644
--- a/web/po/ja.po
+++ b/web/po/ja.po
@@ -7,8 +7,8 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2023-11-05 02:14+0000\n"
-"PO-Revision-Date: 2023-11-01 05:15+0000\n"
+"POT-Creation-Date: 2023-12-03 02:17+0000\n"
+"PO-Revision-Date: 2023-11-29 01:01+0000\n"
"Last-Translator: Yasuhiko Kamata \n"
"Language-Team: Japanese \n"
@@ -19,7 +19,7 @@ msgstr ""
"Plural-Forms: nplurals=1; plural=0;\n"
"X-Generator: Weblate 4.9.1\n"
-#: src/App.jsx:112
+#: src/App.jsx:110
msgid "Diagnostic tools"
msgstr "診断ツール"
@@ -66,6 +66,7 @@ msgstr "詳しくは %s にあるプロジェクトのリポジトリをご覧
#: src/components/core/About.jsx:71 src/components/core/FileViewer.jsx:80
#: src/components/core/Sidebar.jsx:177 src/components/core/Terminal.jsx:48
#: src/components/network/WifiSelector.jsx:151
+#: src/components/product/ProductPage.jsx:239
msgid "Close"
msgstr "閉じる"
@@ -143,18 +144,23 @@ msgstr ""
"をご確認のうえ、やり直してください。"
#. TRANSLATORS: button label
-#: src/components/core/InstallButton.jsx:97
-#: src/components/storage/ProposalSettingsSection.jsx:166
-#: src/components/storage/ProposalSettingsSection.jsx:359
-#: src/components/storage/ProposalSettingsSection.jsx:504
+#: src/components/core/InstallButton.jsx:97 src/components/l10n/L10nPage.jsx:75
+#: src/components/l10n/L10nPage.jsx:191 src/components/l10n/L10nPage.jsx:304
+#: src/components/product/ProductPage.jsx:71
+#: src/components/product/ProductPage.jsx:140
+#: src/components/product/ProductPage.jsx:207
+#: src/components/storage/ProposalSettingsSection.jsx:170
+#: src/components/storage/ProposalSettingsSection.jsx:363
+#: src/components/storage/ProposalSettingsSection.jsx:508
+#: src/components/storage/ProposalSettingsSection.jsx:604
#: src/components/storage/ProposalVolumes.jsx:148
-#: src/components/storage/ProposalVolumes.jsx:282
+#: src/components/storage/ProposalVolumes.jsx:283
#: src/components/storage/ZFCPPage.jsx:513
msgid "Accept"
msgstr "受け入れる"
#. TRANSLATORS: button label
-#: src/components/core/InstallButton.jsx:145
+#: src/components/core/InstallButton.jsx:148
msgid "Install"
msgstr "インストール"
@@ -204,11 +210,41 @@ msgstr "問題点の表示"
msgid "There are new issues"
msgstr "新しい問題点があります"
-#: src/components/core/IssuesPage.jsx:115
+#. TRANSLATORS: page section
+#: src/components/core/IssuesPage.jsx:92
+#: src/components/overview/ProductSection.jsx:71
+#: src/components/product/ProductPage.jsx:434
+msgid "Product"
+msgstr "製品"
+
+#. TRANSLATORS: page title
+#. TRANSLATORS: page section title
+#: src/components/core/IssuesPage.jsx:100
+#: src/components/overview/StorageSection.jsx:208
+#: src/components/storage/ProposalPage.jsx:218
+msgid "Storage"
+msgstr "ストレージ"
+
+#. TRANSLATORS: page title
+#. TRANSLATORS: page section
+#: src/components/core/IssuesPage.jsx:108
+#: src/components/overview/SoftwareSection.jsx:141
+#: src/components/software/SoftwarePage.jsx:81
+msgid "Software"
+msgstr "ソフトウエア"
+
+#: src/components/core/IssuesPage.jsx:129
msgid "No issues found. Everything looks ok."
msgstr ""
"問題点は見つかりませんでした。インストールに支障はないものと思われます。"
+#. TRANSLATORS: search field placeholder text
+#: src/components/core/ListSearch.jsx:50
+#: src/components/software/PatternSelector.jsx:220
+#: src/components/software/PatternSelector.jsx:221
+msgid "Search"
+msgstr "検索"
+
#: src/components/core/LogsButton.jsx:98
msgid "Collecting logs..."
msgstr "ログを収集しています..."
@@ -270,12 +306,11 @@ msgstr "キャンセル"
#. TRANSLATORS: dropdown label
#: src/components/core/RowActions.jsx:66
#: src/components/storage/ProposalVolumes.jsx:119
-#: src/components/storage/ProposalVolumes.jsx:309
+#: src/components/storage/ProposalVolumes.jsx:310
msgid "Actions"
msgstr "処理"
#: src/components/core/SectionSkeleton.jsx:29
-#: src/components/core/SectionSkeleton.jsx:34
msgid "Waiting"
msgstr "お待ちください"
@@ -330,22 +365,132 @@ msgstr[0] "%d 個のエラーが見つかりました"
msgid "Basic popover"
msgstr "基本ポップオーバー"
-#: src/components/l10n/L10nPage.jsx:72
-#: src/components/l10n/LanguageSwitcher.jsx:50
-msgid "language"
+#. TRANSLATORS: placeholder text for search input in the keyboard selector.
+#: src/components/l10n/KeymapSelector.jsx:82
+msgid "Filter by description or keymap code"
+msgstr ""
+
+#: src/components/l10n/KeymapSelector.jsx:89
+#, fuzzy
+msgid "Available keymaps"
+msgstr "利用可能なデバイス"
+
+#: src/components/l10n/L10nPage.jsx:66 src/components/l10n/L10nPage.jsx:140
+msgid "Select time zone"
+msgstr ""
+
+#: src/components/l10n/L10nPage.jsx:67
+#, c-format
+msgid "%s will use the selected time zone."
+msgstr ""
+
+#: src/components/l10n/L10nPage.jsx:128
+msgid "Time zone"
+msgstr ""
+
+#: src/components/l10n/L10nPage.jsx:134
+msgid "Change time zone"
+msgstr ""
+
+#: src/components/l10n/L10nPage.jsx:139
+#, fuzzy
+msgid "Time zone not selected yet"
+msgstr "未選択"
+
+#: src/components/l10n/L10nPage.jsx:182 src/components/l10n/L10nPage.jsx:258
+#, fuzzy
+msgid "Select language"
msgstr "言語"
-#. TRANSLATORS: page header
+#: src/components/l10n/L10nPage.jsx:183
+#, fuzzy, c-format
+msgid "%s will use the selected language."
+msgstr "システムは %s を既定の言語として使用します。"
+
+#: src/components/l10n/L10nPage.jsx:246
+#, fuzzy
+msgid "Language"
+msgstr "言語"
+
+#: src/components/l10n/L10nPage.jsx:252
+#, fuzzy
+msgid "Change language"
+msgstr "言語"
+
+#: src/components/l10n/L10nPage.jsx:257
+#, fuzzy
+msgid "Language not selected yet"
+msgstr "未選択"
+
+#: src/components/l10n/L10nPage.jsx:295 src/components/l10n/L10nPage.jsx:369
+#, fuzzy
+msgid "Select keyboard"
+msgstr "選択済み"
+
+#: src/components/l10n/L10nPage.jsx:296
+#, c-format
+msgid "%s will use the selected keyboard."
+msgstr ""
+
+#: src/components/l10n/L10nPage.jsx:357
+msgid "Keyboard"
+msgstr ""
+
+#: src/components/l10n/L10nPage.jsx:363
+#, fuzzy
+msgid "Change keyboard"
+msgstr "暗号化パスワードを変更する"
+
+#: src/components/l10n/L10nPage.jsx:368
+#, fuzzy
+msgid "Keyboard not selected yet"
+msgstr "未選択"
+
#. TRANSLATORS: page section
-#: src/components/l10n/L10nPage.jsx:84
-#: src/components/overview/L10nSection.jsx:82
+#. TRANSLATORS: page title
+#: src/components/l10n/L10nPage.jsx:385
+#: src/components/overview/L10nSection.jsx:52
msgid "Localization"
msgstr "ローカライゼーション"
+#: src/components/l10n/L10nPage.jsx:387
+#: src/components/product/ProductPage.jsx:434
+#: src/components/software/SoftwarePage.jsx:81
+#: src/components/storage/DASDPage.jsx:187
+#: src/components/storage/ISCSIPage.jsx:39
+#: src/components/storage/ProposalPage.jsx:218
+#: src/components/storage/ZFCPPage.jsx:736
+#: src/components/users/UsersPage.jsx:30
+msgid "Back"
+msgstr "戻る"
+
#: src/components/l10n/LanguageSwitcher.jsx:46
msgid "Display Language"
msgstr "表示言語"
+#: src/components/l10n/LanguageSwitcher.jsx:50
+msgid "language"
+msgstr "言語"
+
+#: src/components/l10n/LocaleSelector.jsx:82
+msgid "Filter by language, territory or locale code"
+msgstr ""
+
+#: src/components/l10n/LocaleSelector.jsx:89
+#, fuzzy
+msgid "Available locales"
+msgstr "利用可能なデバイス"
+
+#. TRANSLATORS: placeholder text for search input in the timezone selector.
+#: src/components/l10n/TimezoneSelector.jsx:102
+msgid "Filter by territory, time zone code or UTC offset"
+msgstr ""
+
+#: src/components/l10n/TimezoneSelector.jsx:109
+#, fuzzy
+msgid "Available time zones"
+msgstr "利用可能なデバイス"
+
#: src/components/layout/Loading.jsx:30
msgid "Loading installation environment, please wait."
msgstr "インストール環境を読み込んでいます。しばらくお待ちください。"
@@ -406,7 +551,7 @@ msgid "IP addresses"
msgstr "IP アドレス"
#: src/components/network/ConnectionsTable.jsx:67
-#: src/components/storage/ProposalVolumes.jsx:233
+#: src/components/storage/ProposalVolumes.jsx:234
#: src/components/storage/iscsi/InitiatorPresenter.jsx:49
#: src/components/storage/iscsi/NodesPresenter.jsx:73
#: src/components/users/FirstUser.jsx:170
@@ -551,6 +696,8 @@ msgid "WPA & WPA2 Personal"
msgstr "WPA および WPA2 Personal"
#: src/components/network/WifiConnectionForm.jsx:91
+#: src/components/product/ProductPage.jsx:128
+#: src/components/product/ProductPage.jsx:194
#: src/components/storage/ZFCPDiskForm.jsx:112
#: src/components/storage/iscsi/DiscoverForm.jsx:108
#: src/components/storage/iscsi/LoginForm.jsx:72
@@ -623,9 +770,9 @@ msgstr "接続 %s は状態の変化を待っています"
msgid "Forget network"
msgstr "ネットワークの削除"
-#. TRANSLATORS: %s will be replaced by a language name and code,
-#. example: "English (en_US.UTF-8)"
-#: src/components/overview/L10nSection.jsx:70
+#. TRANSLATORS: %s will be replaced by a language name and territory, example:
+#. "English (United States)".
+#: src/components/overview/L10nSection.jsx:34
#, c-format
msgid "The system will use %s as its default language."
msgstr "システムは %s を既定の言語として使用します。"
@@ -642,45 +789,63 @@ msgid "%d connection set:"
msgid_plural "%d connections set:"
msgstr[0] "%d 個の接続が設定されています:"
+#. TRANSLATORS: page title
+#: src/components/overview/Overview.jsx:47
+msgid "Installation Summary"
+msgstr "インストールの概要"
+
+#. TRANSLATORS: %s is replaced by a product name (e.g., SUSE ALP-Dolomite)
+#: src/components/overview/ProductSection.jsx:48
+#, c-format
+msgid "%s (registered)"
+msgstr "%s (登録済み)"
+
#: src/components/overview/SoftwareSection.jsx:37
msgid "Reading software repositories"
msgstr "ソフトウエアリポジトリを読み込んでいます"
#. TRANSLATORS: clickable link label
-#: src/components/overview/SoftwareSection.jsx:135
+#: src/components/overview/SoftwareSection.jsx:131
msgid "Refresh the repositories"
msgstr "リポジトリの更新"
-#. TRANSLATORS: page section
-#. TRANSLATORS: page title
-#: src/components/overview/SoftwareSection.jsx:145
-#: src/components/software/SoftwarePage.jsx:81
-msgid "Software"
-msgstr "ソフトウエア"
-
-#: src/components/overview/StorageSection.jsx:36
-#: src/components/storage/ProposalSettingsSection.jsx:122
+#: src/components/overview/StorageSection.jsx:42
+#: src/components/storage/ProposalSettingsSection.jsx:126
msgid "No device selected yet"
msgstr "まだ何もデバイスを選択していません"
#. TRANSLATORS: %s will be replaced by the device name and its size,
#. example: "/dev/sda, 20 GiB"
-#: src/components/overview/StorageSection.jsx:44
+#: src/components/overview/StorageSection.jsx:52
+#, fuzzy, c-format
+msgid "Install using device %s shrinking existing partitions as needed"
+msgstr "デバイス %s の内容を全て削除してインストールします"
+
+#. TRANSLATORS: %s will be replaced by the device name and its size,
+#. example: "/dev/sda, 20 GiB"
+#: src/components/overview/StorageSection.jsx:56
+#, fuzzy, c-format
+msgid "Install using device %s without modifying existing partitions"
+msgstr "デバイス %s の内容を全て削除してインストールします"
+
+#. TRANSLATORS: %s will be replaced by the device name and its size,
+#. example: "/dev/sda, 20 GiB"
+#: src/components/overview/StorageSection.jsx:60
#, c-format
msgid "Install using device %s and deleting all its content"
msgstr "デバイス %s の内容を全て削除してインストールします"
-#: src/components/overview/StorageSection.jsx:57
+#. TRANSLATORS: %s will be replaced by the device name and its size,
+#. example: "/dev/sda, 20 GiB"
+#: src/components/overview/StorageSection.jsx:66
+#, fuzzy, c-format
+msgid "Install using device %s"
+msgstr "インストール先のデバイス"
+
+#: src/components/overview/StorageSection.jsx:83
msgid "Probing storage devices"
msgstr "ストレージデバイスを検出しています"
-#. TRANSLATORS: page title
-#. TRANSLATORS: page section title
-#: src/components/overview/StorageSection.jsx:182
-#: src/components/storage/ProposalPage.jsx:218
-msgid "Storage"
-msgstr "ストレージ"
-
#. TRANSLATORS: %s will be replaced by the user name
#: src/components/overview/UsersSection.jsx:80
#, c-format
@@ -714,6 +879,96 @@ msgstr "公開 SSH 鍵による root 認証を設定済み"
msgid "Users"
msgstr "ユーザ"
+#: src/components/product/ProductPage.jsx:63
+#: src/components/product/ProductSelectionPage.jsx:73
+msgid "Choose a product"
+msgstr "製品を選択してください"
+
+#: src/components/product/ProductPage.jsx:122
+#, c-format
+msgid "Register %s"
+msgstr "%s の登録"
+
+#: src/components/product/ProductPage.jsx:188
+#, c-format
+msgid "Deregister %s"
+msgstr "%s の登録解除"
+
+#. TRANSLATORS: %s is replaced by a product name (e.g., SUSE ALP-Dolomite)
+#: src/components/product/ProductPage.jsx:202
+#, c-format
+msgid "Do you want to deregister %s?"
+msgstr "%s を登録解除してよろしいですか?"
+
+#: src/components/product/ProductPage.jsx:227
+msgid "Registered warning"
+msgstr "登録済みによる警告"
+
+#. TRANSLATORS: %s is replaced by a product name (e.g., SUSE ALP-Dolomite)
+#: src/components/product/ProductPage.jsx:232
+#, c-format
+msgid "The product %s must be deregistered before selecting a new product."
+msgstr "新しい製品を選択する前に、製品 %s の登録を解除しなければなりません。"
+
+#: src/components/product/ProductPage.jsx:263
+msgid "Change product"
+msgstr "製品の変更"
+
+#: src/components/product/ProductPage.jsx:305
+msgid "Register"
+msgstr "登録"
+
+#: src/components/product/ProductPage.jsx:337
+msgid "Deregister product"
+msgstr "製品の登録解除"
+
+#: src/components/product/ProductPage.jsx:370
+msgid "Code:"
+msgstr "コード:"
+
+#: src/components/product/ProductPage.jsx:374
+msgid "Email:"
+msgstr "電子メール:"
+
+#. TRANSLATORS: section title.
+#: src/components/product/ProductPage.jsx:390
+msgid "Registration"
+msgstr "登録"
+
+#: src/components/product/ProductPage.jsx:399
+msgid "This product requires registration."
+msgstr "この製品には登録が必要です。"
+
+#: src/components/product/ProductPage.jsx:405
+msgid "This product does not require registration."
+msgstr "この製品には登録は不要です。"
+
+#: src/components/product/ProductRegistrationForm.jsx:63
+msgid "Registration code"
+msgstr "登録コード"
+
+#: src/components/product/ProductRegistrationForm.jsx:66
+msgid "Email"
+msgstr "電子メール"
+
+#: src/components/product/ProductSelectionPage.jsx:58
+msgid "Loading available products, please wait..."
+msgstr "利用可能な製品を読み込んでいます。しばらくお待ちください..."
+
+#. TRANSLATORS: page header
+#: src/components/product/ProductSelectionPage.jsx:64
+msgid "Product selection"
+msgstr "製品の選択"
+
+#. TRANSLATORS: button label
+#: src/components/product/ProductSelectionPage.jsx:69
+msgid "Select"
+msgstr "選択"
+
+#: src/components/product/ProductSelector.jsx:29
+msgid "No products available for selection"
+msgstr "選択できる製品がありません"
+
#: src/components/questions/GenericQuestion.jsx:35
#: src/components/questions/LuksActivationQuestion.jsx:60
msgid "Question"
@@ -733,10 +988,6 @@ msgstr "暗号化されたデバイス"
msgid "Encryption Password"
msgstr "暗号化パスワード"
-#: src/components/software/ChangeProductLink.jsx:36
-msgid "Change product"
-msgstr "製品の変更"
-
#. TRANSLATORS: pattern status, selected to install (by user)
#: src/components/software/PatternItem.jsx:63
msgid "selected"
@@ -754,48 +1005,15 @@ msgstr "未選択"
#. TRANSLATORS: error summary, always plural, %d is replaced by number of errors (2 or more)
#. if there is just a single error then the error is displayed directly instead of this summary
-#: src/components/software/PatternSelector.jsx:206
+#: src/components/software/PatternSelector.jsx:207
#, c-format
msgid "%d errors"
msgstr "%d 個のエラー"
-#: src/components/software/PatternSelector.jsx:210
+#: src/components/software/PatternSelector.jsx:215
msgid "Software summary and filter options"
msgstr "ソフトウエアの概要とフィルタのオプション"
-#. TRANSLATORS: search field placeholder text
-#: src/components/software/PatternSelector.jsx:215
-#: src/components/software/PatternSelector.jsx:216
-msgid "Search"
-msgstr "検索"
-
-#: src/components/software/ProductSelectionPage.jsx:69
-msgid "Loading available products, please wait..."
-msgstr "利用可能な製品を読み込んでいます。しばらくお待ちください..."
-
-#. TRANSLATORS: page header
-#: src/components/software/ProductSelectionPage.jsx:94
-msgid "Product selection"
-msgstr "製品の選択"
-
-#. TRANSLATORS: button label
-#: src/components/software/ProductSelectionPage.jsx:99
-msgid "Select"
-msgstr "選択"
-
-#: src/components/software/ProductSelectionPage.jsx:104
-msgid "Choose a product"
-msgstr "製品を選択してください"
-
-#: src/components/software/SoftwarePage.jsx:81
-#: src/components/storage/DASDPage.jsx:187
-#: src/components/storage/ISCSIPage.jsx:39
-#: src/components/storage/ProposalPage.jsx:218
-#: src/components/storage/ZFCPPage.jsx:736
-#: src/components/users/UsersPage.jsx:30
-msgid "Back"
-msgstr "戻る"
-
#. TRANSLATORS: %s will be replaced by the estimated installation size,
#. example: "728.8 MiB"
#: src/components/software/UsedSize.jsx:32
@@ -971,29 +1189,29 @@ msgstr "iSCSI ターゲットへの接続"
msgid "iSCSI"
msgstr "iSCSI"
-#: src/components/storage/ProposalSettingsSection.jsx:135
+#: src/components/storage/ProposalSettingsSection.jsx:139
msgid "Select the device for installing the system."
msgstr "システムのインストール先デバイスを選択してください。"
-#: src/components/storage/ProposalSettingsSection.jsx:140
#: src/components/storage/ProposalSettingsSection.jsx:144
-#: src/components/storage/ProposalSettingsSection.jsx:240
+#: src/components/storage/ProposalSettingsSection.jsx:148
+#: src/components/storage/ProposalSettingsSection.jsx:244
msgid "Installation device"
msgstr "インストール先のデバイス"
-#: src/components/storage/ProposalSettingsSection.jsx:150
+#: src/components/storage/ProposalSettingsSection.jsx:154
msgid "No devices found"
msgstr "デバイスが見つかりませんでした"
-#: src/components/storage/ProposalSettingsSection.jsx:237
+#: src/components/storage/ProposalSettingsSection.jsx:241
msgid "Devices for creating the volume group"
msgstr "ボリュームグループを作成するデバイス"
-#: src/components/storage/ProposalSettingsSection.jsx:246
+#: src/components/storage/ProposalSettingsSection.jsx:250
msgid "Custom devices"
msgstr "独自のデバイス"
-#: src/components/storage/ProposalSettingsSection.jsx:310
+#: src/components/storage/ProposalSettingsSection.jsx:314
msgid ""
"Configuration of the system volume group. All the file systems will be "
"created in a logical volume of the system volume group."
@@ -1001,37 +1219,52 @@ msgstr ""
"システムのボリュームグループ向けの設定です。全てのファイルシステムは、システ"
"ムのボリュームグループにある論理ボリューム内に作成されます。"
-#: src/components/storage/ProposalSettingsSection.jsx:316
+#: src/components/storage/ProposalSettingsSection.jsx:320
msgid "Configure the LVM settings"
msgstr "LVM 設定"
-#: src/components/storage/ProposalSettingsSection.jsx:321
-#: src/components/storage/ProposalSettingsSection.jsx:341
+#: src/components/storage/ProposalSettingsSection.jsx:325
+#: src/components/storage/ProposalSettingsSection.jsx:345
msgid "LVM settings"
msgstr "LVM 設定"
-#: src/components/storage/ProposalSettingsSection.jsx:334
+#: src/components/storage/ProposalSettingsSection.jsx:338
msgid "Use logical volume management (LVM)"
msgstr "論理ボリュームマネージャ (LVM) を使用する"
-#: src/components/storage/ProposalSettingsSection.jsx:342
+#: src/components/storage/ProposalSettingsSection.jsx:346
msgid "System Volume Group"
msgstr "システムのボリュームグループ"
-#: src/components/storage/ProposalSettingsSection.jsx:470
+#: src/components/storage/ProposalSettingsSection.jsx:474
msgid "Change encryption password"
msgstr "暗号化パスワードを変更する"
-#: src/components/storage/ProposalSettingsSection.jsx:475
-#: src/components/storage/ProposalSettingsSection.jsx:496
+#: src/components/storage/ProposalSettingsSection.jsx:479
+#: src/components/storage/ProposalSettingsSection.jsx:500
msgid "Encryption settings"
msgstr "暗号化の設定"
-#: src/components/storage/ProposalSettingsSection.jsx:489
+#: src/components/storage/ProposalSettingsSection.jsx:493
msgid "Use encryption"
msgstr "暗号化を使用する"
-#: src/components/storage/ProposalSettingsSection.jsx:557
+#: src/components/storage/ProposalSettingsSection.jsx:578
+msgid ""
+"Select how to make free space in the disks selected for allocating the "
+"file systems."
+msgstr ""
+
+#. TRANSLATORS: To be completed with the rest of a sentence like "deleting all content"
+#: src/components/storage/ProposalSettingsSection.jsx:584
+msgid "Find space"
+msgstr ""
+
+#: src/components/storage/ProposalSettingsSection.jsx:588
+msgid "Space Policy"
+msgstr ""
+
+#: src/components/storage/ProposalSettingsSection.jsx:662
msgid "Settings"
msgstr "設定"
@@ -1084,48 +1317,48 @@ msgid "partition"
msgstr "パーティション"
#. TRANSLATORS: filesystem flag, it uses an encryption
-#: src/components/storage/ProposalVolumes.jsx:215
+#: src/components/storage/ProposalVolumes.jsx:216
msgid "encrypted"
msgstr "暗号化"
#. TRANSLATORS: filesystem flag, it allows creating snapshots
-#: src/components/storage/ProposalVolumes.jsx:217
+#: src/components/storage/ProposalVolumes.jsx:218
msgid "with snapshots"
msgstr "スナップショット有り"
#. TRANSLATORS: flag for transactional file system
-#: src/components/storage/ProposalVolumes.jsx:219
+#: src/components/storage/ProposalVolumes.jsx:220
msgid "transactional"
msgstr "トランザクション型"
-#: src/components/storage/ProposalVolumes.jsx:228
+#: src/components/storage/ProposalVolumes.jsx:229
#: src/components/storage/iscsi/NodesPresenter.jsx:77
msgid "Delete"
msgstr "削除"
-#: src/components/storage/ProposalVolumes.jsx:274
+#: src/components/storage/ProposalVolumes.jsx:275
msgid "Edit file system"
msgstr "ファイルシステムの編集"
-#: src/components/storage/ProposalVolumes.jsx:306
+#: src/components/storage/ProposalVolumes.jsx:307
#: src/components/storage/VolumeForm.jsx:500
msgid "Mount point"
msgstr "マウントポイント"
-#: src/components/storage/ProposalVolumes.jsx:307
+#: src/components/storage/ProposalVolumes.jsx:308
msgid "Details"
msgstr "詳細"
-#: src/components/storage/ProposalVolumes.jsx:308
+#: src/components/storage/ProposalVolumes.jsx:309
#: src/components/storage/VolumeForm.jsx:517
msgid "Size"
msgstr "サイズ"
-#: src/components/storage/ProposalVolumes.jsx:344
+#: src/components/storage/ProposalVolumes.jsx:345
msgid "Table with mount points"
msgstr "マウントポイントの一覧"
-#: src/components/storage/ProposalVolumes.jsx:407
+#: src/components/storage/ProposalVolumes.jsx:408
msgid "File systems to create in your system"
msgstr "お使いのシステム内で作成するファイルシステム"
@@ -1341,64 +1574,64 @@ msgid "Storage zFCP"
msgstr "ストレージ zFCP"
#. TRANSLATORS: multipath device type
-#: src/components/storage/device-utils.jsx:98
+#: src/components/storage/device-utils.jsx:97
msgid "Multipath"
msgstr "マルチパス"
#. TRANSLATORS: %s is replaced by the device bus ID
-#: src/components/storage/device-utils.jsx:103
+#: src/components/storage/device-utils.jsx:102
#, c-format
msgid "DASD %s"
msgstr "DASD %s"
#. TRANSLATORS: software RAID device, %s is replaced by the RAID level, e.g. RAID-1
-#: src/components/storage/device-utils.jsx:108
+#: src/components/storage/device-utils.jsx:107
#, c-format
msgid "Software %s"
msgstr "ソフトウエア %s"
-#: src/components/storage/device-utils.jsx:113
+#: src/components/storage/device-utils.jsx:112
msgid "SD Card"
msgstr "SD カード"
#. TRANSLATORS: %s is replaced by the device transport name, e.g. USB, SATA, SCSI...
-#: src/components/storage/device-utils.jsx:115
+#: src/components/storage/device-utils.jsx:114
#, c-format
msgid "Transport %s"
msgstr "トランスポート %s"
#. TRANSLATORS: RAID details, %s is replaced by list of devices used by the array
-#: src/components/storage/device-utils.jsx:134
+#: src/components/storage/device-utils.jsx:133
#, c-format
msgid "Members: %s"
msgstr "メンバー: %s"
#. TRANSLATORS: RAID details, %s is replaced by list of devices used by the array
-#: src/components/storage/device-utils.jsx:143
+#: src/components/storage/device-utils.jsx:142
#, c-format
msgid "Devices: %s"
msgstr "デバイス: %s"
#. TRANSLATORS: multipath details, %s is replaced by list of connections used by the device
-#: src/components/storage/device-utils.jsx:152
+#: src/components/storage/device-utils.jsx:151
#, c-format
msgid "Wires: %s"
msgstr "接続: %s"
#. TRANSLATORS: disk partition info, %s is replaced by partition table
#. type (MS-DOS or GPT), %d is the number of the partitions
-#: src/components/storage/device-utils.jsx:176
+#: src/components/storage/device-utils.jsx:175
#, c-format
msgid "%s with %d partitions"
msgstr "%s (%d 個のパーティション)"
#. TRANSLATORS: status message, no existing content was found on the disk,
#. i.e. the disk is completely empty
-#: src/components/storage/device-utils.jsx:200
+#: src/components/storage/device-utils.jsx:199
msgid "No content found"
msgstr "内容が見つかりませんでした"
-#: src/components/storage/device-utils.jsx:271
+#: src/components/storage/device-utils.jsx:270
msgid "Available devices"
msgstr "利用可能なデバイス"
@@ -1575,6 +1808,67 @@ msgstr "検索"
msgid "Targets"
msgstr "ターゲット"
+#. TRANSLATORS: automatic actions to find space for installation in the target disk(s)
+#: src/components/storage/space-policy-utils.jsx:59
+msgid "Delete current content"
+msgstr ""
+
+#. TRANSLATORS: automatic actions to find space for installation in the target disk(s)
+#: src/components/storage/space-policy-utils.jsx:63
+msgid "Shrink existing partitions"
+msgstr ""
+
+#. TRANSLATORS: automatic actions to find space for installation in the target disk(s)
+#: src/components/storage/space-policy-utils.jsx:67
+#, fuzzy
+msgid "Use available space"
+msgstr "利用可能なデバイス"
+
+#: src/components/storage/space-policy-utils.jsx:79
+msgid "All partitions will be removed and any data in the disks will be lost."
+msgstr ""
+
+#: src/components/storage/space-policy-utils.jsx:82
+msgid ""
+"The data is kept, but the current partitions will be resized as needed to "
+"make enough space."
+msgstr ""
+
+#: src/components/storage/space-policy-utils.jsx:85
+msgid ""
+"The data is kept and existing partitions will not be modified. Only the "
+"space that is not assigned to any partition will be used."
+msgstr ""
+
+#: src/components/storage/space-policy-utils.jsx:112
+msgid "Select a mechanism to make space"
+msgstr ""
+
+#: src/components/storage/space-policy-utils.jsx:137
+#, c-format
+msgid "deleting all content of the installation device"
+msgid_plural "deleting all content of the %d selected disks"
+msgstr[0] ""
+
+#: src/components/storage/space-policy-utils.jsx:147
+#, c-format
+msgid "shrinking partitions of the installation device"
+msgid_plural "shrinking partitions of the %d selected disks"
+msgstr[0] ""
+
+#. TRANSLATORS: This is presented next to the label "Find space", so the whole sentence
+#. would read as "Find space without modifying any partition".
+#: src/components/storage/space-policy-utils.jsx:155
+#, fuzzy
+msgid "without modifying any partition"
+msgstr "%s (%d 個のパーティション)"
+
+#: src/components/storage/space-policy-utils.jsx:170
+#, c-format
+msgid "This will only affect the installation device"
+msgid_plural "This will affect the %d disks selected for installation"
+msgstr[0] ""
+
#: src/components/storage/utils.js:44
msgid "KiB"
msgstr "KiB"
diff --git a/web/po/mk.po b/web/po/mk.po
index 80184f36df..71e8565b1f 100644
--- a/web/po/mk.po
+++ b/web/po/mk.po
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2023-11-05 02:14+0000\n"
+"POT-Creation-Date: 2023-12-03 02:17+0000\n"
"PO-Revision-Date: 2023-10-21 23:15+0000\n"
"Last-Translator: Kristijan Fremen Velkovski \n"
"Language-Team: Macedonian \n"
"Language-Team: Dutch \n"
"Language-Team: Portuguese (Brazil) 1;\n"
"X-Generator: Weblate 4.9.1\n"
-#: src/App.jsx:112
+#: src/App.jsx:110
msgid "Diagnostic tools"
msgstr "Ferramentas de diagnóstico"
@@ -63,6 +63,7 @@ msgstr ""
#: src/components/core/About.jsx:71 src/components/core/FileViewer.jsx:80
#: src/components/core/Sidebar.jsx:177 src/components/core/Terminal.jsx:48
#: src/components/network/WifiSelector.jsx:151
+#: src/components/product/ProductPage.jsx:239
msgid "Close"
msgstr ""
@@ -131,18 +132,23 @@ msgid ""
msgstr ""
#. TRANSLATORS: button label
-#: src/components/core/InstallButton.jsx:97
-#: src/components/storage/ProposalSettingsSection.jsx:166
-#: src/components/storage/ProposalSettingsSection.jsx:359
-#: src/components/storage/ProposalSettingsSection.jsx:504
+#: src/components/core/InstallButton.jsx:97 src/components/l10n/L10nPage.jsx:75
+#: src/components/l10n/L10nPage.jsx:191 src/components/l10n/L10nPage.jsx:304
+#: src/components/product/ProductPage.jsx:71
+#: src/components/product/ProductPage.jsx:140
+#: src/components/product/ProductPage.jsx:207
+#: src/components/storage/ProposalSettingsSection.jsx:170
+#: src/components/storage/ProposalSettingsSection.jsx:363
+#: src/components/storage/ProposalSettingsSection.jsx:508
+#: src/components/storage/ProposalSettingsSection.jsx:604
#: src/components/storage/ProposalVolumes.jsx:148
-#: src/components/storage/ProposalVolumes.jsx:282
+#: src/components/storage/ProposalVolumes.jsx:283
#: src/components/storage/ZFCPPage.jsx:513
msgid "Accept"
msgstr ""
#. TRANSLATORS: button label
-#: src/components/core/InstallButton.jsx:145
+#: src/components/core/InstallButton.jsx:148
msgid "Install"
msgstr ""
@@ -192,10 +198,40 @@ msgstr ""
msgid "There are new issues"
msgstr ""
-#: src/components/core/IssuesPage.jsx:115
+#. TRANSLATORS: page section
+#: src/components/core/IssuesPage.jsx:92
+#: src/components/overview/ProductSection.jsx:71
+#: src/components/product/ProductPage.jsx:434
+msgid "Product"
+msgstr ""
+
+#. TRANSLATORS: page title
+#. TRANSLATORS: page section title
+#: src/components/core/IssuesPage.jsx:100
+#: src/components/overview/StorageSection.jsx:208
+#: src/components/storage/ProposalPage.jsx:218
+msgid "Storage"
+msgstr ""
+
+#. TRANSLATORS: page title
+#. TRANSLATORS: page section
+#: src/components/core/IssuesPage.jsx:108
+#: src/components/overview/SoftwareSection.jsx:141
+#: src/components/software/SoftwarePage.jsx:81
+msgid "Software"
+msgstr ""
+
+#: src/components/core/IssuesPage.jsx:129
msgid "No issues found. Everything looks ok."
msgstr ""
+#. TRANSLATORS: search field placeholder text
+#: src/components/core/ListSearch.jsx:50
+#: src/components/software/PatternSelector.jsx:220
+#: src/components/software/PatternSelector.jsx:221
+msgid "Search"
+msgstr ""
+
#: src/components/core/LogsButton.jsx:98
msgid "Collecting logs..."
msgstr ""
@@ -255,12 +291,11 @@ msgstr ""
#. TRANSLATORS: dropdown label
#: src/components/core/RowActions.jsx:66
#: src/components/storage/ProposalVolumes.jsx:119
-#: src/components/storage/ProposalVolumes.jsx:309
+#: src/components/storage/ProposalVolumes.jsx:310
msgid "Actions"
msgstr ""
#: src/components/core/SectionSkeleton.jsx:29
-#: src/components/core/SectionSkeleton.jsx:34
msgid "Waiting"
msgstr ""
@@ -316,22 +351,121 @@ msgstr[1] ""
msgid "Basic popover"
msgstr ""
-#: src/components/l10n/L10nPage.jsx:72
-#: src/components/l10n/LanguageSwitcher.jsx:50
-msgid "language"
+#. TRANSLATORS: placeholder text for search input in the keyboard selector.
+#: src/components/l10n/KeymapSelector.jsx:82
+msgid "Filter by description or keymap code"
+msgstr ""
+
+#: src/components/l10n/KeymapSelector.jsx:89
+msgid "Available keymaps"
+msgstr ""
+
+#: src/components/l10n/L10nPage.jsx:66 src/components/l10n/L10nPage.jsx:140
+msgid "Select time zone"
+msgstr ""
+
+#: src/components/l10n/L10nPage.jsx:67
+#, c-format
+msgid "%s will use the selected time zone."
+msgstr ""
+
+#: src/components/l10n/L10nPage.jsx:128
+msgid "Time zone"
+msgstr ""
+
+#: src/components/l10n/L10nPage.jsx:134
+msgid "Change time zone"
+msgstr ""
+
+#: src/components/l10n/L10nPage.jsx:139
+msgid "Time zone not selected yet"
+msgstr ""
+
+#: src/components/l10n/L10nPage.jsx:182 src/components/l10n/L10nPage.jsx:258
+msgid "Select language"
+msgstr ""
+
+#: src/components/l10n/L10nPage.jsx:183
+#, c-format
+msgid "%s will use the selected language."
+msgstr ""
+
+#: src/components/l10n/L10nPage.jsx:246
+msgid "Language"
+msgstr ""
+
+#: src/components/l10n/L10nPage.jsx:252
+msgid "Change language"
+msgstr ""
+
+#: src/components/l10n/L10nPage.jsx:257
+msgid "Language not selected yet"
+msgstr ""
+
+#: src/components/l10n/L10nPage.jsx:295 src/components/l10n/L10nPage.jsx:369
+msgid "Select keyboard"
+msgstr ""
+
+#: src/components/l10n/L10nPage.jsx:296
+#, c-format
+msgid "%s will use the selected keyboard."
+msgstr ""
+
+#: src/components/l10n/L10nPage.jsx:357
+msgid "Keyboard"
+msgstr ""
+
+#: src/components/l10n/L10nPage.jsx:363
+msgid "Change keyboard"
+msgstr ""
+
+#: src/components/l10n/L10nPage.jsx:368
+msgid "Keyboard not selected yet"
msgstr ""
-#. TRANSLATORS: page header
#. TRANSLATORS: page section
-#: src/components/l10n/L10nPage.jsx:84
-#: src/components/overview/L10nSection.jsx:82
+#. TRANSLATORS: page title
+#: src/components/l10n/L10nPage.jsx:385
+#: src/components/overview/L10nSection.jsx:52
msgid "Localization"
msgstr ""
+#: src/components/l10n/L10nPage.jsx:387
+#: src/components/product/ProductPage.jsx:434
+#: src/components/software/SoftwarePage.jsx:81
+#: src/components/storage/DASDPage.jsx:187
+#: src/components/storage/ISCSIPage.jsx:39
+#: src/components/storage/ProposalPage.jsx:218
+#: src/components/storage/ZFCPPage.jsx:736
+#: src/components/users/UsersPage.jsx:30
+msgid "Back"
+msgstr ""
+
#: src/components/l10n/LanguageSwitcher.jsx:46
msgid "Display Language"
msgstr ""
+#: src/components/l10n/LanguageSwitcher.jsx:50
+msgid "language"
+msgstr ""
+
+#: src/components/l10n/LocaleSelector.jsx:82
+msgid "Filter by language, territory or locale code"
+msgstr ""
+
+#: src/components/l10n/LocaleSelector.jsx:89
+msgid "Available locales"
+msgstr ""
+
+#. TRANSLATORS: placeholder text for search input in the timezone selector.
+#: src/components/l10n/TimezoneSelector.jsx:102
+msgid "Filter by territory, time zone code or UTC offset"
+msgstr ""
+
+#: src/components/l10n/TimezoneSelector.jsx:109
+msgid "Available time zones"
+msgstr ""
+
#: src/components/layout/Loading.jsx:30
msgid "Loading installation environment, please wait."
msgstr ""
@@ -392,7 +526,7 @@ msgid "IP addresses"
msgstr ""
#: src/components/network/ConnectionsTable.jsx:67
-#: src/components/storage/ProposalVolumes.jsx:233
+#: src/components/storage/ProposalVolumes.jsx:234
#: src/components/storage/iscsi/InitiatorPresenter.jsx:49
#: src/components/storage/iscsi/NodesPresenter.jsx:73
#: src/components/users/FirstUser.jsx:170
@@ -533,6 +667,8 @@ msgid "WPA & WPA2 Personal"
msgstr ""
#: src/components/network/WifiConnectionForm.jsx:91
+#: src/components/product/ProductPage.jsx:128
+#: src/components/product/ProductPage.jsx:194
#: src/components/storage/ZFCPDiskForm.jsx:112
#: src/components/storage/iscsi/DiscoverForm.jsx:108
#: src/components/storage/iscsi/LoginForm.jsx:72
@@ -605,9 +741,9 @@ msgstr ""
msgid "Forget network"
msgstr ""
-#. TRANSLATORS: %s will be replaced by a language name and code,
-#. example: "English (en_US.UTF-8)"
-#: src/components/overview/L10nSection.jsx:70
+#. TRANSLATORS: %s will be replaced by a language name and territory, example:
+#. "English (United States)".
+#: src/components/overview/L10nSection.jsx:34
#, c-format
msgid "The system will use %s as its default language."
msgstr ""
@@ -625,43 +761,61 @@ msgid_plural "%d connections set:"
msgstr[0] ""
msgstr[1] ""
+#. TRANSLATORS: page title
+#: src/components/overview/Overview.jsx:47
+msgid "Installation Summary"
+msgstr ""
+
+#. TRANSLATORS: %s is replaced by a product name (e.g., SUSE ALP-Dolomite)
+#: src/components/overview/ProductSection.jsx:48
+#, c-format
+msgid "%s (registered)"
+msgstr ""
+
#: src/components/overview/SoftwareSection.jsx:37
msgid "Reading software repositories"
msgstr ""
#. TRANSLATORS: clickable link label
-#: src/components/overview/SoftwareSection.jsx:135
+#: src/components/overview/SoftwareSection.jsx:131
msgid "Refresh the repositories"
msgstr ""
-#. TRANSLATORS: page section
-#. TRANSLATORS: page title
-#: src/components/overview/SoftwareSection.jsx:145
-#: src/components/software/SoftwarePage.jsx:81
-msgid "Software"
+#: src/components/overview/StorageSection.jsx:42
+#: src/components/storage/ProposalSettingsSection.jsx:126
+msgid "No device selected yet"
msgstr ""
-#: src/components/overview/StorageSection.jsx:36
-#: src/components/storage/ProposalSettingsSection.jsx:122
-msgid "No device selected yet"
+#. TRANSLATORS: %s will be replaced by the device name and its size,
+#. example: "/dev/sda, 20 GiB"
+#: src/components/overview/StorageSection.jsx:52
+#, c-format
+msgid "Install using device %s shrinking existing partitions as needed"
msgstr ""
#. TRANSLATORS: %s will be replaced by the device name and its size,
#. example: "/dev/sda, 20 GiB"
-#: src/components/overview/StorageSection.jsx:44
+#: src/components/overview/StorageSection.jsx:56
+#, c-format
+msgid "Install using device %s without modifying existing partitions"
+msgstr ""
+
+#. TRANSLATORS: %s will be replaced by the device name and its size,
+#. example: "/dev/sda, 20 GiB"
+#: src/components/overview/StorageSection.jsx:60
#, c-format
msgid "Install using device %s and deleting all its content"
msgstr ""
-#: src/components/overview/StorageSection.jsx:57
-msgid "Probing storage devices"
+#. TRANSLATORS: %s will be replaced by the device name and its size,
+#. example: "/dev/sda, 20 GiB"
+#: src/components/overview/StorageSection.jsx:66
+#, c-format
+msgid "Install using device %s"
msgstr ""
-#. TRANSLATORS: page title
-#. TRANSLATORS: page section title
-#: src/components/overview/StorageSection.jsx:182
-#: src/components/storage/ProposalPage.jsx:218
-msgid "Storage"
+#: src/components/overview/StorageSection.jsx:83
+msgid "Probing storage devices"
msgstr ""
#. TRANSLATORS: %s will be replaced by the user name
@@ -697,6 +851,96 @@ msgstr ""
msgid "Users"
msgstr ""
+#: src/components/product/ProductPage.jsx:63
+#: src/components/product/ProductSelectionPage.jsx:73
+msgid "Choose a product"
+msgstr ""
+
+#: src/components/product/ProductPage.jsx:122
+#, c-format
+msgid "Register %s"
+msgstr ""
+
+#: src/components/product/ProductPage.jsx:188
+#, c-format
+msgid "Deregister %s"
+msgstr ""
+
+#. TRANSLATORS: %s is replaced by a product name (e.g., SUSE ALP-Dolomite)
+#: src/components/product/ProductPage.jsx:202
+#, c-format
+msgid "Do you want to deregister %s?"
+msgstr ""
+
+#: src/components/product/ProductPage.jsx:227
+msgid "Registered warning"
+msgstr ""
+
+#. TRANSLATORS: %s is replaced by a product name (e.g., SUSE ALP-Dolomite)
+#: src/components/product/ProductPage.jsx:232
+#, c-format
+msgid "The product %s must be deregistered before selecting a new product."
+msgstr ""
+
+#: src/components/product/ProductPage.jsx:263
+msgid "Change product"
+msgstr ""
+
+#: src/components/product/ProductPage.jsx:305
+msgid "Register"
+msgstr ""
+
+#: src/components/product/ProductPage.jsx:337
+msgid "Deregister product"
+msgstr ""
+
+#: src/components/product/ProductPage.jsx:370
+msgid "Code:"
+msgstr ""
+
+#: src/components/product/ProductPage.jsx:374
+msgid "Email:"
+msgstr ""
+
+#. TRANSLATORS: section title.
+#: src/components/product/ProductPage.jsx:390
+msgid "Registration"
+msgstr ""
+
+#: src/components/product/ProductPage.jsx:399
+msgid "This product requires registration."
+msgstr ""
+
+#: src/components/product/ProductPage.jsx:405
+msgid "This product does not require registration."
+msgstr ""
+
+#: src/components/product/ProductRegistrationForm.jsx:63
+msgid "Registration code"
+msgstr ""
+
+#: src/components/product/ProductRegistrationForm.jsx:66
+msgid "Email"
+msgstr ""
+
+#: src/components/product/ProductSelectionPage.jsx:58
+msgid "Loading available products, please wait..."
+msgstr ""
+
+#. TRANSLATORS: page header
+#: src/components/product/ProductSelectionPage.jsx:64
+msgid "Product selection"
+msgstr ""
+
+#. TRANSLATORS: button label
+#: src/components/product/ProductSelectionPage.jsx:69
+msgid "Select"
+msgstr ""
+
+#: src/components/product/ProductSelector.jsx:29
+msgid "No products available for selection"
+msgstr ""
+
#: src/components/questions/GenericQuestion.jsx:35
#: src/components/questions/LuksActivationQuestion.jsx:60
msgid "Question"
@@ -716,10 +960,6 @@ msgstr ""
msgid "Encryption Password"
msgstr ""
-#: src/components/software/ChangeProductLink.jsx:36
-msgid "Change product"
-msgstr ""
-
#. TRANSLATORS: pattern status, selected to install (by user)
#: src/components/software/PatternItem.jsx:63
msgid "selected"
@@ -737,46 +977,13 @@ msgstr ""
#. TRANSLATORS: error summary, always plural, %d is replaced by number of errors (2 or more)
#. if there is just a single error then the error is displayed directly instead of this summary
-#: src/components/software/PatternSelector.jsx:206
+#: src/components/software/PatternSelector.jsx:207
#, c-format
msgid "%d errors"
msgstr ""
-#: src/components/software/PatternSelector.jsx:210
-msgid "Software summary and filter options"
-msgstr ""
-
-#. TRANSLATORS: search field placeholder text
#: src/components/software/PatternSelector.jsx:215
-#: src/components/software/PatternSelector.jsx:216
-msgid "Search"
-msgstr ""
-
-#: src/components/software/ProductSelectionPage.jsx:69
-msgid "Loading available products, please wait..."
-msgstr ""
-
-#. TRANSLATORS: page header
-#: src/components/software/ProductSelectionPage.jsx:94
-msgid "Product selection"
-msgstr ""
-
-#. TRANSLATORS: button label
-#: src/components/software/ProductSelectionPage.jsx:99
-msgid "Select"
-msgstr ""
-
-#: src/components/software/ProductSelectionPage.jsx:104
-msgid "Choose a product"
-msgstr ""
-
-#: src/components/software/SoftwarePage.jsx:81
-#: src/components/storage/DASDPage.jsx:187
-#: src/components/storage/ISCSIPage.jsx:39
-#: src/components/storage/ProposalPage.jsx:218
-#: src/components/storage/ZFCPPage.jsx:736
-#: src/components/users/UsersPage.jsx:30
-msgid "Back"
+msgid "Software summary and filter options"
msgstr ""
#. TRANSLATORS: %s will be replaced by the estimated installation size,
@@ -954,65 +1161,80 @@ msgstr ""
msgid "iSCSI"
msgstr ""
-#: src/components/storage/ProposalSettingsSection.jsx:135
+#: src/components/storage/ProposalSettingsSection.jsx:139
msgid "Select the device for installing the system."
msgstr ""
-#: src/components/storage/ProposalSettingsSection.jsx:140
#: src/components/storage/ProposalSettingsSection.jsx:144
-#: src/components/storage/ProposalSettingsSection.jsx:240
+#: src/components/storage/ProposalSettingsSection.jsx:148
+#: src/components/storage/ProposalSettingsSection.jsx:244
msgid "Installation device"
msgstr ""
-#: src/components/storage/ProposalSettingsSection.jsx:150
+#: src/components/storage/ProposalSettingsSection.jsx:154
msgid "No devices found"
msgstr ""
-#: src/components/storage/ProposalSettingsSection.jsx:237
+#: src/components/storage/ProposalSettingsSection.jsx:241
msgid "Devices for creating the volume group"
msgstr ""
-#: src/components/storage/ProposalSettingsSection.jsx:246
+#: src/components/storage/ProposalSettingsSection.jsx:250
msgid "Custom devices"
msgstr ""
-#: src/components/storage/ProposalSettingsSection.jsx:310
+#: src/components/storage/ProposalSettingsSection.jsx:314
msgid ""
"Configuration of the system volume group. All the file systems will be "
"created in a logical volume of the system volume group."
msgstr ""
-#: src/components/storage/ProposalSettingsSection.jsx:316
+#: src/components/storage/ProposalSettingsSection.jsx:320
msgid "Configure the LVM settings"
msgstr ""
-#: src/components/storage/ProposalSettingsSection.jsx:321
-#: src/components/storage/ProposalSettingsSection.jsx:341
+#: src/components/storage/ProposalSettingsSection.jsx:325
+#: src/components/storage/ProposalSettingsSection.jsx:345
msgid "LVM settings"
msgstr ""
-#: src/components/storage/ProposalSettingsSection.jsx:334
+#: src/components/storage/ProposalSettingsSection.jsx:338
msgid "Use logical volume management (LVM)"
msgstr ""
-#: src/components/storage/ProposalSettingsSection.jsx:342
+#: src/components/storage/ProposalSettingsSection.jsx:346
msgid "System Volume Group"
msgstr ""
-#: src/components/storage/ProposalSettingsSection.jsx:470
+#: src/components/storage/ProposalSettingsSection.jsx:474
msgid "Change encryption password"
msgstr ""
-#: src/components/storage/ProposalSettingsSection.jsx:475
-#: src/components/storage/ProposalSettingsSection.jsx:496
+#: src/components/storage/ProposalSettingsSection.jsx:479
+#: src/components/storage/ProposalSettingsSection.jsx:500
msgid "Encryption settings"
msgstr ""
-#: src/components/storage/ProposalSettingsSection.jsx:489
+#: src/components/storage/ProposalSettingsSection.jsx:493
msgid "Use encryption"
msgstr ""
-#: src/components/storage/ProposalSettingsSection.jsx:557
+#: src/components/storage/ProposalSettingsSection.jsx:578
+msgid ""
+"Select how to make free space in the disks selected for allocating the "
+"file systems."
+msgstr ""
+
+#. TRANSLATORS: To be completed with the rest of a sentence like "deleting all content"
+#: src/components/storage/ProposalSettingsSection.jsx:584
+msgid "Find space"
+msgstr ""
+
+#: src/components/storage/ProposalSettingsSection.jsx:588
+msgid "Space Policy"
+msgstr ""
+
+#: src/components/storage/ProposalSettingsSection.jsx:662
msgid "Settings"
msgstr ""
@@ -1065,48 +1287,48 @@ msgid "partition"
msgstr ""
#. TRANSLATORS: filesystem flag, it uses an encryption
-#: src/components/storage/ProposalVolumes.jsx:215
+#: src/components/storage/ProposalVolumes.jsx:216
msgid "encrypted"
msgstr ""
#. TRANSLATORS: filesystem flag, it allows creating snapshots
-#: src/components/storage/ProposalVolumes.jsx:217
+#: src/components/storage/ProposalVolumes.jsx:218
msgid "with snapshots"
msgstr ""
#. TRANSLATORS: flag for transactional file system
-#: src/components/storage/ProposalVolumes.jsx:219
+#: src/components/storage/ProposalVolumes.jsx:220
msgid "transactional"
msgstr ""
-#: src/components/storage/ProposalVolumes.jsx:228
+#: src/components/storage/ProposalVolumes.jsx:229
#: src/components/storage/iscsi/NodesPresenter.jsx:77
msgid "Delete"
msgstr ""
-#: src/components/storage/ProposalVolumes.jsx:274
+#: src/components/storage/ProposalVolumes.jsx:275
msgid "Edit file system"
msgstr ""
-#: src/components/storage/ProposalVolumes.jsx:306
+#: src/components/storage/ProposalVolumes.jsx:307
#: src/components/storage/VolumeForm.jsx:500
msgid "Mount point"
msgstr ""
-#: src/components/storage/ProposalVolumes.jsx:307
+#: src/components/storage/ProposalVolumes.jsx:308
msgid "Details"
msgstr ""
-#: src/components/storage/ProposalVolumes.jsx:308
+#: src/components/storage/ProposalVolumes.jsx:309
#: src/components/storage/VolumeForm.jsx:517
msgid "Size"
msgstr ""
-#: src/components/storage/ProposalVolumes.jsx:344
+#: src/components/storage/ProposalVolumes.jsx:345
msgid "Table with mount points"
msgstr ""
-#: src/components/storage/ProposalVolumes.jsx:407
+#: src/components/storage/ProposalVolumes.jsx:408
msgid "File systems to create in your system"
msgstr ""
@@ -1315,64 +1537,64 @@ msgid "Storage zFCP"
msgstr ""
#. TRANSLATORS: multipath device type
-#: src/components/storage/device-utils.jsx:98
+#: src/components/storage/device-utils.jsx:97
msgid "Multipath"
msgstr ""
#. TRANSLATORS: %s is replaced by the device bus ID
-#: src/components/storage/device-utils.jsx:103
+#: src/components/storage/device-utils.jsx:102
#, c-format
msgid "DASD %s"
msgstr ""
#. TRANSLATORS: software RAID device, %s is replaced by the RAID level, e.g. RAID-1
-#: src/components/storage/device-utils.jsx:108
+#: src/components/storage/device-utils.jsx:107
#, c-format
msgid "Software %s"
msgstr ""
-#: src/components/storage/device-utils.jsx:113
+#: src/components/storage/device-utils.jsx:112
msgid "SD Card"
msgstr ""
#. TRANSLATORS: %s is replaced by the device transport name, e.g. USB, SATA, SCSI...
-#: src/components/storage/device-utils.jsx:115
+#: src/components/storage/device-utils.jsx:114
#, c-format
msgid "Transport %s"
msgstr ""
#. TRANSLATORS: RAID details, %s is replaced by list of devices used by the array
-#: src/components/storage/device-utils.jsx:134
+#: src/components/storage/device-utils.jsx:133
#, c-format
msgid "Members: %s"
msgstr ""
#. TRANSLATORS: RAID details, %s is replaced by list of devices used by the array
-#: src/components/storage/device-utils.jsx:143
+#: src/components/storage/device-utils.jsx:142
#, c-format
msgid "Devices: %s"
msgstr ""
#. TRANSLATORS: multipath details, %s is replaced by list of connections used by the device
-#: src/components/storage/device-utils.jsx:152
+#: src/components/storage/device-utils.jsx:151
#, c-format
msgid "Wires: %s"
msgstr ""
#. TRANSLATORS: disk partition info, %s is replaced by partition table
#. type (MS-DOS or GPT), %d is the number of the partitions
-#: src/components/storage/device-utils.jsx:176
+#: src/components/storage/device-utils.jsx:175
#, c-format
msgid "%s with %d partitions"
msgstr ""
#. TRANSLATORS: status message, no existing content was found on the disk,
#. i.e. the disk is completely empty
-#: src/components/storage/device-utils.jsx:200
+#: src/components/storage/device-utils.jsx:199
msgid "No content found"
msgstr ""
-#: src/components/storage/device-utils.jsx:271
+#: src/components/storage/device-utils.jsx:270
msgid "Available devices"
msgstr ""
@@ -1548,6 +1770,68 @@ msgstr ""
msgid "Targets"
msgstr ""
+#. TRANSLATORS: automatic actions to find space for installation in the target disk(s)
+#: src/components/storage/space-policy-utils.jsx:59
+msgid "Delete current content"
+msgstr ""
+
+#. TRANSLATORS: automatic actions to find space for installation in the target disk(s)
+#: src/components/storage/space-policy-utils.jsx:63
+msgid "Shrink existing partitions"
+msgstr ""
+
+#. TRANSLATORS: automatic actions to find space for installation in the target disk(s)
+#: src/components/storage/space-policy-utils.jsx:67
+msgid "Use available space"
+msgstr ""
+
+#: src/components/storage/space-policy-utils.jsx:79
+msgid "All partitions will be removed and any data in the disks will be lost."
+msgstr ""
+
+#: src/components/storage/space-policy-utils.jsx:82
+msgid ""
+"The data is kept, but the current partitions will be resized as needed to "
+"make enough space."
+msgstr ""
+
+#: src/components/storage/space-policy-utils.jsx:85
+msgid ""
+"The data is kept and existing partitions will not be modified. Only the "
+"space that is not assigned to any partition will be used."
+msgstr ""
+
+#: src/components/storage/space-policy-utils.jsx:112
+msgid "Select a mechanism to make space"
+msgstr ""
+
+#: src/components/storage/space-policy-utils.jsx:137
+#, c-format
+msgid "deleting all content of the installation device"
+msgid_plural "deleting all content of the %d selected disks"
+msgstr[0] ""
+msgstr[1] ""
+
+#: src/components/storage/space-policy-utils.jsx:147
+#, c-format
+msgid "shrinking partitions of the installation device"
+msgid_plural "shrinking partitions of the %d selected disks"
+msgstr[0] ""
+msgstr[1] ""
+
+#. TRANSLATORS: This is presented next to the label "Find space", so the whole sentence
+#. would read as "Find space without modifying any partition".
+#: src/components/storage/space-policy-utils.jsx:155
+msgid "without modifying any partition"
+msgstr ""
+
+#: src/components/storage/space-policy-utils.jsx:170
+#, c-format
+msgid "This will only affect the installation device"
+msgid_plural "This will affect the %d disks selected for installation"
+msgstr[0] ""
+msgstr[1] ""
+
#: src/components/storage/utils.js:44
msgid "KiB"
msgstr ""
diff --git a/web/po/ru.po b/web/po/ru.po
index 2aa9dd8489..179651a49a 100644
--- a/web/po/ru.po
+++ b/web/po/ru.po
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2023-11-05 02:14+0000\n"
+"POT-Creation-Date: 2023-12-03 02:17+0000\n"
"PO-Revision-Date: 2023-09-11 13:15+0000\n"
"Last-Translator: Alex Minton \n"
"Language-Team: Russian =2 && n"
-"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"
+"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
+"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"
"X-Generator: Weblate 4.9.1\n"
-#: src/App.jsx:112
+#: src/App.jsx:110
msgid "Diagnostic tools"
msgstr "Утилиты диагностики"
@@ -33,7 +33,6 @@ msgstr ""
#. TRANSLATORS: error message
#: src/DevServerWrapper.jsx:88
#, fuzzy
-#| msgid "Cannot connect to D-Bus"
msgid "Cannot connect to the Cockpit server"
msgstr "Не удалось подключиться к службе D-Bus"
@@ -72,6 +71,7 @@ msgstr ""
#: src/components/core/About.jsx:71 src/components/core/FileViewer.jsx:80
#: src/components/core/Sidebar.jsx:177 src/components/core/Terminal.jsx:48
#: src/components/network/WifiSelector.jsx:151
+#: src/components/product/ProductPage.jsx:239
msgid "Close"
msgstr "Закрыть"
@@ -144,18 +144,23 @@ msgid ""
msgstr ""
#. TRANSLATORS: button label
-#: src/components/core/InstallButton.jsx:97
-#: src/components/storage/ProposalSettingsSection.jsx:166
-#: src/components/storage/ProposalSettingsSection.jsx:359
-#: src/components/storage/ProposalSettingsSection.jsx:504
+#: src/components/core/InstallButton.jsx:97 src/components/l10n/L10nPage.jsx:75
+#: src/components/l10n/L10nPage.jsx:191 src/components/l10n/L10nPage.jsx:304
+#: src/components/product/ProductPage.jsx:71
+#: src/components/product/ProductPage.jsx:140
+#: src/components/product/ProductPage.jsx:207
+#: src/components/storage/ProposalSettingsSection.jsx:170
+#: src/components/storage/ProposalSettingsSection.jsx:363
+#: src/components/storage/ProposalSettingsSection.jsx:508
+#: src/components/storage/ProposalSettingsSection.jsx:604
#: src/components/storage/ProposalVolumes.jsx:148
-#: src/components/storage/ProposalVolumes.jsx:282
+#: src/components/storage/ProposalVolumes.jsx:283
#: src/components/storage/ZFCPPage.jsx:513
msgid "Accept"
msgstr "Подтвердить"
#. TRANSLATORS: button label
-#: src/components/core/InstallButton.jsx:145
+#: src/components/core/InstallButton.jsx:148
msgid "Install"
msgstr "Установить"
@@ -207,10 +212,40 @@ msgstr "Показать ошибки"
msgid "There are new issues"
msgstr "Есть новые ошибки"
-#: src/components/core/IssuesPage.jsx:115
+#. TRANSLATORS: page section
+#: src/components/core/IssuesPage.jsx:92
+#: src/components/overview/ProductSection.jsx:71
+#: src/components/product/ProductPage.jsx:434
+msgid "Product"
+msgstr ""
+
+#. TRANSLATORS: page title
+#. TRANSLATORS: page section title
+#: src/components/core/IssuesPage.jsx:100
+#: src/components/overview/StorageSection.jsx:208
+#: src/components/storage/ProposalPage.jsx:218
+msgid "Storage"
+msgstr ""
+
+#. TRANSLATORS: page title
+#. TRANSLATORS: page section
+#: src/components/core/IssuesPage.jsx:108
+#: src/components/overview/SoftwareSection.jsx:141
+#: src/components/software/SoftwarePage.jsx:81
+msgid "Software"
+msgstr "Программы"
+
+#: src/components/core/IssuesPage.jsx:129
msgid "No issues found. Everything looks ok."
msgstr "Проблем не обнаружено. Все выглядит нормально."
+#. TRANSLATORS: search field placeholder text
+#: src/components/core/ListSearch.jsx:50
+#: src/components/software/PatternSelector.jsx:220
+#: src/components/software/PatternSelector.jsx:221
+msgid "Search"
+msgstr ""
+
#: src/components/core/LogsButton.jsx:98
msgid "Collecting logs..."
msgstr "Сбор журналов..."
@@ -273,12 +308,11 @@ msgstr "Отмена"
#. TRANSLATORS: dropdown label
#: src/components/core/RowActions.jsx:66
#: src/components/storage/ProposalVolumes.jsx:119
-#: src/components/storage/ProposalVolumes.jsx:309
+#: src/components/storage/ProposalVolumes.jsx:310
msgid "Actions"
msgstr "Действия"
#: src/components/core/SectionSkeleton.jsx:29
-#: src/components/core/SectionSkeleton.jsx:34
msgid "Waiting"
msgstr "Ожидание"
@@ -335,24 +369,128 @@ msgstr[2] "Найдено %d ошибок"
msgid "Basic popover"
msgstr "Базовое всплывающее окно"
-#: src/components/l10n/L10nPage.jsx:72
-#: src/components/l10n/LanguageSwitcher.jsx:50
-msgid "language"
+#. TRANSLATORS: placeholder text for search input in the keyboard selector.
+#: src/components/l10n/KeymapSelector.jsx:82
+msgid "Filter by description or keymap code"
+msgstr ""
+
+#: src/components/l10n/KeymapSelector.jsx:89
+msgid "Available keymaps"
+msgstr ""
+
+#: src/components/l10n/L10nPage.jsx:66 src/components/l10n/L10nPage.jsx:140
+msgid "Select time zone"
+msgstr ""
+
+#: src/components/l10n/L10nPage.jsx:67
+#, c-format
+msgid "%s will use the selected time zone."
+msgstr ""
+
+#: src/components/l10n/L10nPage.jsx:128
+msgid "Time zone"
+msgstr ""
+
+#: src/components/l10n/L10nPage.jsx:134
+msgid "Change time zone"
+msgstr ""
+
+#: src/components/l10n/L10nPage.jsx:139
+#, fuzzy
+msgid "Time zone not selected yet"
+msgstr "Устройство ещё не выбрано"
+
+#: src/components/l10n/L10nPage.jsx:182 src/components/l10n/L10nPage.jsx:258
+#, fuzzy
+msgid "Select language"
msgstr "Язык"
-#. TRANSLATORS: page header
+#: src/components/l10n/L10nPage.jsx:183
+#, fuzzy, c-format
+msgid "%s will use the selected language."
+msgstr "Система будет использовать %s в качестве языка по умолчанию."
+
+#: src/components/l10n/L10nPage.jsx:246
+#, fuzzy
+msgid "Language"
+msgstr "Язык"
+
+#: src/components/l10n/L10nPage.jsx:252
+#, fuzzy
+msgid "Change language"
+msgstr "Язык"
+
+#: src/components/l10n/L10nPage.jsx:257
+msgid "Language not selected yet"
+msgstr ""
+
+#: src/components/l10n/L10nPage.jsx:295 src/components/l10n/L10nPage.jsx:369
+#, fuzzy
+msgid "Select keyboard"
+msgstr "Отключено"
+
+#: src/components/l10n/L10nPage.jsx:296
+#, c-format
+msgid "%s will use the selected keyboard."
+msgstr ""
+
+#: src/components/l10n/L10nPage.jsx:357
+msgid "Keyboard"
+msgstr ""
+
+#: src/components/l10n/L10nPage.jsx:363
+msgid "Change keyboard"
+msgstr ""
+
+#: src/components/l10n/L10nPage.jsx:368
+#, fuzzy
+msgid "Keyboard not selected yet"
+msgstr "Устройство ещё не выбрано"
+
#. TRANSLATORS: page section
-#: src/components/l10n/L10nPage.jsx:84
-#: src/components/overview/L10nSection.jsx:82
+#. TRANSLATORS: page title
+#: src/components/l10n/L10nPage.jsx:385
+#: src/components/overview/L10nSection.jsx:52
msgid "Localization"
msgstr "Локализация"
+#: src/components/l10n/L10nPage.jsx:387
+#: src/components/product/ProductPage.jsx:434
+#: src/components/software/SoftwarePage.jsx:81
+#: src/components/storage/DASDPage.jsx:187
+#: src/components/storage/ISCSIPage.jsx:39
+#: src/components/storage/ProposalPage.jsx:218
+#: src/components/storage/ZFCPPage.jsx:736
+#: src/components/users/UsersPage.jsx:30
+msgid "Back"
+msgstr ""
+
#: src/components/l10n/LanguageSwitcher.jsx:46
#, fuzzy
-#| msgid "language"
msgid "Display Language"
msgstr "Язык"
+#: src/components/l10n/LanguageSwitcher.jsx:50
+msgid "language"
+msgstr "Язык"
+
+#: src/components/l10n/LocaleSelector.jsx:82
+msgid "Filter by language, territory or locale code"
+msgstr ""
+
+#: src/components/l10n/LocaleSelector.jsx:89
+msgid "Available locales"
+msgstr ""
+
+#. TRANSLATORS: placeholder text for search input in the timezone selector.
+#: src/components/l10n/TimezoneSelector.jsx:102
+msgid "Filter by territory, time zone code or UTC offset"
+msgstr ""
+
+#: src/components/l10n/TimezoneSelector.jsx:109
+msgid "Available time zones"
+msgstr ""
+
#: src/components/layout/Loading.jsx:30
msgid "Loading installation environment, please wait."
msgstr "Загрузка установочной среды, пожалуйста, подождите."
@@ -413,7 +551,7 @@ msgid "IP addresses"
msgstr "IP-адреса"
#: src/components/network/ConnectionsTable.jsx:67
-#: src/components/storage/ProposalVolumes.jsx:233
+#: src/components/storage/ProposalVolumes.jsx:234
#: src/components/storage/iscsi/InitiatorPresenter.jsx:49
#: src/components/storage/iscsi/NodesPresenter.jsx:73
#: src/components/users/FirstUser.jsx:170
@@ -556,6 +694,8 @@ msgid "WPA & WPA2 Personal"
msgstr "WPA и WPA2 Personal"
#: src/components/network/WifiConnectionForm.jsx:91
+#: src/components/product/ProductPage.jsx:128
+#: src/components/product/ProductPage.jsx:194
#: src/components/storage/ZFCPDiskForm.jsx:112
#: src/components/storage/iscsi/DiscoverForm.jsx:108
#: src/components/storage/iscsi/LoginForm.jsx:72
@@ -628,9 +768,9 @@ msgstr "Соединение %s ожидает изменения состоян
msgid "Forget network"
msgstr "Забыть сеть"
-#. TRANSLATORS: %s will be replaced by a language name and code,
-#. example: "English (en_US.UTF-8)"
-#: src/components/overview/L10nSection.jsx:70
+#. TRANSLATORS: %s will be replaced by a language name and territory, example:
+#. "English (United States)".
+#: src/components/overview/L10nSection.jsx:34
#, c-format
msgid "The system will use %s as its default language."
msgstr "Система будет использовать %s в качестве языка по умолчанию."
@@ -649,43 +789,62 @@ msgstr[0] "Активно %d соединение:"
msgstr[1] "Активно %d соединения:"
msgstr[2] "Активно %d соединений:"
+#. TRANSLATORS: page title
+#: src/components/overview/Overview.jsx:47
+#, fuzzy
+msgid "Installation Summary"
+msgstr "Установка завершена"
+
+#. TRANSLATORS: %s is replaced by a product name (e.g., SUSE ALP-Dolomite)
+#: src/components/overview/ProductSection.jsx:48
+#, c-format
+msgid "%s (registered)"
+msgstr ""
+
#: src/components/overview/SoftwareSection.jsx:37
msgid "Reading software repositories"
msgstr "Чтение репозиториев"
#. TRANSLATORS: clickable link label
-#: src/components/overview/SoftwareSection.jsx:135
+#: src/components/overview/SoftwareSection.jsx:131
msgid "Refresh the repositories"
msgstr "Обновить репозитории"
-#. TRANSLATORS: page section
-#. TRANSLATORS: page title
-#: src/components/overview/SoftwareSection.jsx:145
-#: src/components/software/SoftwarePage.jsx:81
-msgid "Software"
-msgstr "Программы"
-
-#: src/components/overview/StorageSection.jsx:36
-#: src/components/storage/ProposalSettingsSection.jsx:122
+#: src/components/overview/StorageSection.jsx:42
+#: src/components/storage/ProposalSettingsSection.jsx:126
msgid "No device selected yet"
msgstr "Устройство ещё не выбрано"
#. TRANSLATORS: %s will be replaced by the device name and its size,
#. example: "/dev/sda, 20 GiB"
-#: src/components/overview/StorageSection.jsx:44
+#: src/components/overview/StorageSection.jsx:52
#, c-format
-msgid "Install using device %s and deleting all its content"
+msgid "Install using device %s shrinking existing partitions as needed"
msgstr ""
-#: src/components/overview/StorageSection.jsx:57
-msgid "Probing storage devices"
+#. TRANSLATORS: %s will be replaced by the device name and its size,
+#. example: "/dev/sda, 20 GiB"
+#: src/components/overview/StorageSection.jsx:56
+#, c-format
+msgid "Install using device %s without modifying existing partitions"
msgstr ""
-#. TRANSLATORS: page title
-#. TRANSLATORS: page section title
-#: src/components/overview/StorageSection.jsx:182
-#: src/components/storage/ProposalPage.jsx:218
-msgid "Storage"
+#. TRANSLATORS: %s will be replaced by the device name and its size,
+#. example: "/dev/sda, 20 GiB"
+#: src/components/overview/StorageSection.jsx:60
+#, c-format
+msgid "Install using device %s and deleting all its content"
+msgstr ""
+
+#. TRANSLATORS: %s will be replaced by the device name and its size,
+#. example: "/dev/sda, 20 GiB"
+#: src/components/overview/StorageSection.jsx:66
+#, fuzzy, c-format
+msgid "Install using device %s"
+msgstr "Установка займёт %s"
+
+#: src/components/overview/StorageSection.jsx:83
+msgid "Probing storage devices"
msgstr ""
#. TRANSLATORS: %s will be replaced by the user name
@@ -721,6 +880,96 @@ msgstr ""
msgid "Users"
msgstr ""
+#: src/components/product/ProductPage.jsx:63
+#: src/components/product/ProductSelectionPage.jsx:73
+msgid "Choose a product"
+msgstr ""
+
+#: src/components/product/ProductPage.jsx:122
+#, c-format
+msgid "Register %s"
+msgstr ""
+
+#: src/components/product/ProductPage.jsx:188
+#, c-format
+msgid "Deregister %s"
+msgstr ""
+
+#. TRANSLATORS: %s is replaced by a product name (e.g., SUSE ALP-Dolomite)
+#: src/components/product/ProductPage.jsx:202
+#, c-format
+msgid "Do you want to deregister %s?"
+msgstr ""
+
+#: src/components/product/ProductPage.jsx:227
+msgid "Registered warning"
+msgstr ""
+
+#. TRANSLATORS: %s is replaced by a product name (e.g., SUSE ALP-Dolomite)
+#: src/components/product/ProductPage.jsx:232
+#, c-format
+msgid "The product %s must be deregistered before selecting a new product."
+msgstr ""
+
+#: src/components/product/ProductPage.jsx:263
+msgid "Change product"
+msgstr ""
+
+#: src/components/product/ProductPage.jsx:305
+msgid "Register"
+msgstr ""
+
+#: src/components/product/ProductPage.jsx:337
+msgid "Deregister product"
+msgstr ""
+
+#: src/components/product/ProductPage.jsx:370
+msgid "Code:"
+msgstr ""
+
+#: src/components/product/ProductPage.jsx:374
+msgid "Email:"
+msgstr ""
+
+#. TRANSLATORS: section title.
+#: src/components/product/ProductPage.jsx:390
+msgid "Registration"
+msgstr ""
+
+#: src/components/product/ProductPage.jsx:399
+msgid "This product requires registration."
+msgstr ""
+
+#: src/components/product/ProductPage.jsx:405
+msgid "This product does not require registration."
+msgstr ""
+
+#: src/components/product/ProductRegistrationForm.jsx:63
+msgid "Registration code"
+msgstr ""
+
+#: src/components/product/ProductRegistrationForm.jsx:66
+msgid "Email"
+msgstr ""
+
+#: src/components/product/ProductSelectionPage.jsx:58
+msgid "Loading available products, please wait..."
+msgstr ""
+
+#. TRANSLATORS: page header
+#: src/components/product/ProductSelectionPage.jsx:64
+msgid "Product selection"
+msgstr ""
+
+#. TRANSLATORS: button label
+#: src/components/product/ProductSelectionPage.jsx:69
+msgid "Select"
+msgstr ""
+
+#: src/components/product/ProductSelector.jsx:29
+msgid "No products available for selection"
+msgstr ""
+
#: src/components/questions/GenericQuestion.jsx:35
#: src/components/questions/LuksActivationQuestion.jsx:60
msgid "Question"
@@ -740,14 +989,9 @@ msgstr ""
msgid "Encryption Password"
msgstr ""
-#: src/components/software/ChangeProductLink.jsx:36
-msgid "Change product"
-msgstr ""
-
#. TRANSLATORS: pattern status, selected to install (by user)
#: src/components/software/PatternItem.jsx:63
#, fuzzy
-#| msgid "Disconnected"
msgid "selected"
msgstr "Отключено"
@@ -763,48 +1007,13 @@ msgstr ""
#. TRANSLATORS: error summary, always plural, %d is replaced by number of errors (2 or more)
#. if there is just a single error then the error is displayed directly instead of this summary
-#: src/components/software/PatternSelector.jsx:206
+#: src/components/software/PatternSelector.jsx:207
#, fuzzy, c-format
-#| msgid "%d error found"
-#| msgid_plural "%d errors found"
msgid "%d errors"
msgstr "Найдена %d ошибка"
-#: src/components/software/PatternSelector.jsx:210
-msgid "Software summary and filter options"
-msgstr ""
-
-#. TRANSLATORS: search field placeholder text
#: src/components/software/PatternSelector.jsx:215
-#: src/components/software/PatternSelector.jsx:216
-msgid "Search"
-msgstr ""
-
-#: src/components/software/ProductSelectionPage.jsx:69
-msgid "Loading available products, please wait..."
-msgstr ""
-
-#. TRANSLATORS: page header
-#: src/components/software/ProductSelectionPage.jsx:94
-msgid "Product selection"
-msgstr ""
-
-#. TRANSLATORS: button label
-#: src/components/software/ProductSelectionPage.jsx:99
-msgid "Select"
-msgstr ""
-
-#: src/components/software/ProductSelectionPage.jsx:104
-msgid "Choose a product"
-msgstr ""
-
-#: src/components/software/SoftwarePage.jsx:81
-#: src/components/storage/DASDPage.jsx:187
-#: src/components/storage/ISCSIPage.jsx:39
-#: src/components/storage/ProposalPage.jsx:218
-#: src/components/storage/ZFCPPage.jsx:736
-#: src/components/users/UsersPage.jsx:30
-msgid "Back"
+msgid "Software summary and filter options"
msgstr ""
#. TRANSLATORS: %s will be replaced by the estimated installation size,
@@ -984,65 +1193,80 @@ msgstr ""
msgid "iSCSI"
msgstr ""
-#: src/components/storage/ProposalSettingsSection.jsx:135
+#: src/components/storage/ProposalSettingsSection.jsx:139
msgid "Select the device for installing the system."
msgstr ""
-#: src/components/storage/ProposalSettingsSection.jsx:140
#: src/components/storage/ProposalSettingsSection.jsx:144
-#: src/components/storage/ProposalSettingsSection.jsx:240
+#: src/components/storage/ProposalSettingsSection.jsx:148
+#: src/components/storage/ProposalSettingsSection.jsx:244
msgid "Installation device"
msgstr ""
-#: src/components/storage/ProposalSettingsSection.jsx:150
+#: src/components/storage/ProposalSettingsSection.jsx:154
msgid "No devices found"
msgstr ""
-#: src/components/storage/ProposalSettingsSection.jsx:237
+#: src/components/storage/ProposalSettingsSection.jsx:241
msgid "Devices for creating the volume group"
msgstr ""
-#: src/components/storage/ProposalSettingsSection.jsx:246
+#: src/components/storage/ProposalSettingsSection.jsx:250
msgid "Custom devices"
msgstr ""
-#: src/components/storage/ProposalSettingsSection.jsx:310
+#: src/components/storage/ProposalSettingsSection.jsx:314
msgid ""
"Configuration of the system volume group. All the file systems will be "
"created in a logical volume of the system volume group."
msgstr ""
-#: src/components/storage/ProposalSettingsSection.jsx:316
+#: src/components/storage/ProposalSettingsSection.jsx:320
msgid "Configure the LVM settings"
msgstr ""
-#: src/components/storage/ProposalSettingsSection.jsx:321
-#: src/components/storage/ProposalSettingsSection.jsx:341
+#: src/components/storage/ProposalSettingsSection.jsx:325
+#: src/components/storage/ProposalSettingsSection.jsx:345
msgid "LVM settings"
msgstr ""
-#: src/components/storage/ProposalSettingsSection.jsx:334
+#: src/components/storage/ProposalSettingsSection.jsx:338
msgid "Use logical volume management (LVM)"
msgstr ""
-#: src/components/storage/ProposalSettingsSection.jsx:342
+#: src/components/storage/ProposalSettingsSection.jsx:346
msgid "System Volume Group"
msgstr ""
-#: src/components/storage/ProposalSettingsSection.jsx:470
+#: src/components/storage/ProposalSettingsSection.jsx:474
msgid "Change encryption password"
msgstr ""
-#: src/components/storage/ProposalSettingsSection.jsx:475
-#: src/components/storage/ProposalSettingsSection.jsx:496
+#: src/components/storage/ProposalSettingsSection.jsx:479
+#: src/components/storage/ProposalSettingsSection.jsx:500
msgid "Encryption settings"
msgstr ""
-#: src/components/storage/ProposalSettingsSection.jsx:489
+#: src/components/storage/ProposalSettingsSection.jsx:493
msgid "Use encryption"
msgstr ""
-#: src/components/storage/ProposalSettingsSection.jsx:557
+#: src/components/storage/ProposalSettingsSection.jsx:578
+msgid ""
+"Select how to make free space in the disks selected for allocating the "
+"file systems."
+msgstr ""
+
+#. TRANSLATORS: To be completed with the rest of a sentence like "deleting all content"
+#: src/components/storage/ProposalSettingsSection.jsx:584
+msgid "Find space"
+msgstr ""
+
+#: src/components/storage/ProposalSettingsSection.jsx:588
+msgid "Space Policy"
+msgstr ""
+
+#: src/components/storage/ProposalSettingsSection.jsx:662
msgid "Settings"
msgstr ""
@@ -1095,48 +1319,48 @@ msgid "partition"
msgstr ""
#. TRANSLATORS: filesystem flag, it uses an encryption
-#: src/components/storage/ProposalVolumes.jsx:215
+#: src/components/storage/ProposalVolumes.jsx:216
msgid "encrypted"
msgstr ""
#. TRANSLATORS: filesystem flag, it allows creating snapshots
-#: src/components/storage/ProposalVolumes.jsx:217
+#: src/components/storage/ProposalVolumes.jsx:218
msgid "with snapshots"
msgstr ""
#. TRANSLATORS: flag for transactional file system
-#: src/components/storage/ProposalVolumes.jsx:219
+#: src/components/storage/ProposalVolumes.jsx:220
msgid "transactional"
msgstr ""
-#: src/components/storage/ProposalVolumes.jsx:228
+#: src/components/storage/ProposalVolumes.jsx:229
#: src/components/storage/iscsi/NodesPresenter.jsx:77
msgid "Delete"
msgstr ""
-#: src/components/storage/ProposalVolumes.jsx:274
+#: src/components/storage/ProposalVolumes.jsx:275
msgid "Edit file system"
msgstr ""
-#: src/components/storage/ProposalVolumes.jsx:306
+#: src/components/storage/ProposalVolumes.jsx:307
#: src/components/storage/VolumeForm.jsx:500
msgid "Mount point"
msgstr ""
-#: src/components/storage/ProposalVolumes.jsx:307
+#: src/components/storage/ProposalVolumes.jsx:308
msgid "Details"
msgstr ""
-#: src/components/storage/ProposalVolumes.jsx:308
+#: src/components/storage/ProposalVolumes.jsx:309
#: src/components/storage/VolumeForm.jsx:517
msgid "Size"
msgstr ""
-#: src/components/storage/ProposalVolumes.jsx:344
+#: src/components/storage/ProposalVolumes.jsx:345
msgid "Table with mount points"
msgstr ""
-#: src/components/storage/ProposalVolumes.jsx:407
+#: src/components/storage/ProposalVolumes.jsx:408
msgid "File systems to create in your system"
msgstr ""
@@ -1345,64 +1569,64 @@ msgid "Storage zFCP"
msgstr ""
#. TRANSLATORS: multipath device type
-#: src/components/storage/device-utils.jsx:98
+#: src/components/storage/device-utils.jsx:97
msgid "Multipath"
msgstr ""
#. TRANSLATORS: %s is replaced by the device bus ID
-#: src/components/storage/device-utils.jsx:103
+#: src/components/storage/device-utils.jsx:102
#, c-format
msgid "DASD %s"
msgstr ""
#. TRANSLATORS: software RAID device, %s is replaced by the RAID level, e.g. RAID-1
-#: src/components/storage/device-utils.jsx:108
+#: src/components/storage/device-utils.jsx:107
#, c-format
msgid "Software %s"
msgstr ""
-#: src/components/storage/device-utils.jsx:113
+#: src/components/storage/device-utils.jsx:112
msgid "SD Card"
msgstr ""
#. TRANSLATORS: %s is replaced by the device transport name, e.g. USB, SATA, SCSI...
-#: src/components/storage/device-utils.jsx:115
+#: src/components/storage/device-utils.jsx:114
#, c-format
msgid "Transport %s"
msgstr ""
#. TRANSLATORS: RAID details, %s is replaced by list of devices used by the array
-#: src/components/storage/device-utils.jsx:134
+#: src/components/storage/device-utils.jsx:133
#, c-format
msgid "Members: %s"
msgstr ""
#. TRANSLATORS: RAID details, %s is replaced by list of devices used by the array
-#: src/components/storage/device-utils.jsx:143
+#: src/components/storage/device-utils.jsx:142
#, c-format
msgid "Devices: %s"
msgstr ""
#. TRANSLATORS: multipath details, %s is replaced by list of connections used by the device
-#: src/components/storage/device-utils.jsx:152
+#: src/components/storage/device-utils.jsx:151
#, c-format
msgid "Wires: %s"
msgstr ""
#. TRANSLATORS: disk partition info, %s is replaced by partition table
#. type (MS-DOS or GPT), %d is the number of the partitions
-#: src/components/storage/device-utils.jsx:176
+#: src/components/storage/device-utils.jsx:175
#, c-format
msgid "%s with %d partitions"
msgstr ""
#. TRANSLATORS: status message, no existing content was found on the disk,
#. i.e. the disk is completely empty
-#: src/components/storage/device-utils.jsx:200
+#: src/components/storage/device-utils.jsx:199
msgid "No content found"
msgstr ""
-#: src/components/storage/device-utils.jsx:271
+#: src/components/storage/device-utils.jsx:270
msgid "Available devices"
msgstr ""
@@ -1578,6 +1802,71 @@ msgstr ""
msgid "Targets"
msgstr ""
+#. TRANSLATORS: automatic actions to find space for installation in the target disk(s)
+#: src/components/storage/space-policy-utils.jsx:59
+msgid "Delete current content"
+msgstr ""
+
+#. TRANSLATORS: automatic actions to find space for installation in the target disk(s)
+#: src/components/storage/space-policy-utils.jsx:63
+msgid "Shrink existing partitions"
+msgstr ""
+
+#. TRANSLATORS: automatic actions to find space for installation in the target disk(s)
+#: src/components/storage/space-policy-utils.jsx:67
+msgid "Use available space"
+msgstr ""
+
+#: src/components/storage/space-policy-utils.jsx:79
+msgid "All partitions will be removed and any data in the disks will be lost."
+msgstr ""
+
+#: src/components/storage/space-policy-utils.jsx:82
+msgid ""
+"The data is kept, but the current partitions will be resized as needed to "
+"make enough space."
+msgstr ""
+
+#: src/components/storage/space-policy-utils.jsx:85
+msgid ""
+"The data is kept and existing partitions will not be modified. Only the "
+"space that is not assigned to any partition will be used."
+msgstr ""
+
+#: src/components/storage/space-policy-utils.jsx:112
+msgid "Select a mechanism to make space"
+msgstr ""
+
+#: src/components/storage/space-policy-utils.jsx:137
+#, c-format
+msgid "deleting all content of the installation device"
+msgid_plural "deleting all content of the %d selected disks"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
+#: src/components/storage/space-policy-utils.jsx:147
+#, c-format
+msgid "shrinking partitions of the installation device"
+msgid_plural "shrinking partitions of the %d selected disks"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
+#. TRANSLATORS: This is presented next to the label "Find space", so the whole sentence
+#. would read as "Find space without modifying any partition".
+#: src/components/storage/space-policy-utils.jsx:155
+msgid "without modifying any partition"
+msgstr ""
+
+#: src/components/storage/space-policy-utils.jsx:170
+#, c-format
+msgid "This will only affect the installation device"
+msgid_plural "This will affect the %d disks selected for installation"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
#: src/components/storage/utils.js:44
msgid "KiB"
msgstr ""
diff --git a/web/po/sv.po b/web/po/sv.po
index db664c6e06..5307ea2b9a 100644
--- a/web/po/sv.po
+++ b/web/po/sv.po
@@ -5,10 +5,10 @@
#
msgid ""
msgstr ""
-"Project-Id-Version: PACKAGE VERSION\n"
+"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2023-11-06 12:00+0000\n"
-"PO-Revision-Date: 2023-11-06 12:01+0000\n"
+"POT-Creation-Date: 2023-12-03 02:17+0000\n"
+"PO-Revision-Date: 2023-11-23 09:02+0100\n"
"Last-Translator: Luna Jernberg \n"
"Language-Team: Swedish \n"
@@ -17,9 +17,9 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
-"X-Generator: Weblate 4.9.1\n"
+"X-Generator: Poedit 3.4.1\n"
-#: src/App.jsx:112
+#: src/App.jsx:110
msgid "Diagnostic tools"
msgstr "Diagnostik verktyg"
@@ -67,6 +67,7 @@ msgstr "För mer information, var vänlig och besök projektets arkiv på %s."
#: src/components/core/About.jsx:71 src/components/core/FileViewer.jsx:80
#: src/components/core/Sidebar.jsx:177 src/components/core/Terminal.jsx:48
#: src/components/network/WifiSelector.jsx:151
+#: src/components/product/ProductPage.jsx:239
msgid "Close"
msgstr "Stäng"
@@ -142,18 +143,23 @@ msgstr ""
"de rapporterade felen och försök igen."
#. TRANSLATORS: button label
-#: src/components/core/InstallButton.jsx:97
-#: src/components/storage/ProposalSettingsSection.jsx:166
-#: src/components/storage/ProposalSettingsSection.jsx:359
-#: src/components/storage/ProposalSettingsSection.jsx:504
+#: src/components/core/InstallButton.jsx:97 src/components/l10n/L10nPage.jsx:75
+#: src/components/l10n/L10nPage.jsx:191 src/components/l10n/L10nPage.jsx:304
+#: src/components/product/ProductPage.jsx:71
+#: src/components/product/ProductPage.jsx:140
+#: src/components/product/ProductPage.jsx:207
+#: src/components/storage/ProposalSettingsSection.jsx:170
+#: src/components/storage/ProposalSettingsSection.jsx:363
+#: src/components/storage/ProposalSettingsSection.jsx:508
+#: src/components/storage/ProposalSettingsSection.jsx:604
#: src/components/storage/ProposalVolumes.jsx:148
-#: src/components/storage/ProposalVolumes.jsx:282
+#: src/components/storage/ProposalVolumes.jsx:283
#: src/components/storage/ZFCPPage.jsx:513
msgid "Accept"
msgstr "Acceptera"
#. TRANSLATORS: button label
-#: src/components/core/InstallButton.jsx:145
+#: src/components/core/InstallButton.jsx:148
msgid "Install"
msgstr "Installera"
@@ -205,10 +211,40 @@ msgstr "Visa problem"
msgid "There are new issues"
msgstr "Det finns nya problem"
-#: src/components/core/IssuesPage.jsx:115
+#. TRANSLATORS: page section
+#: src/components/core/IssuesPage.jsx:92
+#: src/components/overview/ProductSection.jsx:71
+#: src/components/product/ProductPage.jsx:434
+msgid "Product"
+msgstr "Produkt"
+
+#. TRANSLATORS: page title
+#. TRANSLATORS: page section title
+#: src/components/core/IssuesPage.jsx:100
+#: src/components/overview/StorageSection.jsx:208
+#: src/components/storage/ProposalPage.jsx:218
+msgid "Storage"
+msgstr "Lagring"
+
+#. TRANSLATORS: page title
+#. TRANSLATORS: page section
+#: src/components/core/IssuesPage.jsx:108
+#: src/components/overview/SoftwareSection.jsx:141
+#: src/components/software/SoftwarePage.jsx:81
+msgid "Software"
+msgstr "Programvara"
+
+#: src/components/core/IssuesPage.jsx:129
msgid "No issues found. Everything looks ok."
msgstr "Inga problem hittades. Allt ser ok ut."
+#. TRANSLATORS: search field placeholder text
+#: src/components/core/ListSearch.jsx:50
+#: src/components/software/PatternSelector.jsx:220
+#: src/components/software/PatternSelector.jsx:221
+msgid "Search"
+msgstr "Sök"
+
#: src/components/core/LogsButton.jsx:98
msgid "Collecting logs..."
msgstr "Samlar loggar..."
@@ -270,12 +306,11 @@ msgstr "Avbryt"
#. TRANSLATORS: dropdown label
#: src/components/core/RowActions.jsx:66
#: src/components/storage/ProposalVolumes.jsx:119
-#: src/components/storage/ProposalVolumes.jsx:309
+#: src/components/storage/ProposalVolumes.jsx:310
msgid "Actions"
msgstr "Åtgärder"
#: src/components/core/SectionSkeleton.jsx:29
-#: src/components/core/SectionSkeleton.jsx:34
msgid "Waiting"
msgstr "Väntande"
@@ -331,23 +366,132 @@ msgstr[1] "%d fel upptäcktes"
msgid "Basic popover"
msgstr "Enkel popover"
-#: src/components/l10n/L10nPage.jsx:72
-#: src/components/l10n/LanguageSwitcher.jsx:50
-msgid "language"
+#. TRANSLATORS: placeholder text for search input in the keyboard selector.
+#: src/components/l10n/KeymapSelector.jsx:82
+msgid "Filter by description or keymap code"
+msgstr ""
+
+#: src/components/l10n/KeymapSelector.jsx:89
+#, fuzzy
+msgid "Available keymaps"
+msgstr "Tillgängliga enheter"
+
+#: src/components/l10n/L10nPage.jsx:66 src/components/l10n/L10nPage.jsx:140
+msgid "Select time zone"
+msgstr ""
+
+#: src/components/l10n/L10nPage.jsx:67
+#, c-format
+msgid "%s will use the selected time zone."
+msgstr ""
+
+#: src/components/l10n/L10nPage.jsx:128
+msgid "Time zone"
+msgstr ""
+
+#: src/components/l10n/L10nPage.jsx:134
+msgid "Change time zone"
+msgstr ""
+
+#: src/components/l10n/L10nPage.jsx:139
+#, fuzzy
+msgid "Time zone not selected yet"
+msgstr "inte vald"
+
+#: src/components/l10n/L10nPage.jsx:182 src/components/l10n/L10nPage.jsx:258
+#, fuzzy
+msgid "Select language"
msgstr "språk"
-#. TRANSLATORS: page header
+#: src/components/l10n/L10nPage.jsx:183
+#, fuzzy, c-format
+msgid "%s will use the selected language."
+msgstr "Systemet kommer att använda %s som dess standardspråk."
+
+#: src/components/l10n/L10nPage.jsx:246
+#, fuzzy
+msgid "Language"
+msgstr "språk"
+
+#: src/components/l10n/L10nPage.jsx:252
+#, fuzzy
+msgid "Change language"
+msgstr "språk"
+
+#: src/components/l10n/L10nPage.jsx:257
+#, fuzzy
+msgid "Language not selected yet"
+msgstr "inte vald"
+
+#: src/components/l10n/L10nPage.jsx:295 src/components/l10n/L10nPage.jsx:369
+#, fuzzy
+msgid "Select keyboard"
+msgstr "vald"
+
+#: src/components/l10n/L10nPage.jsx:296
+#, c-format
+msgid "%s will use the selected keyboard."
+msgstr ""
+
+#: src/components/l10n/L10nPage.jsx:357
+msgid "Keyboard"
+msgstr ""
+
+#: src/components/l10n/L10nPage.jsx:363
+#, fuzzy
+msgid "Change keyboard"
+msgstr "Ändra krypteringslösenord"
+
+#: src/components/l10n/L10nPage.jsx:368
+#, fuzzy
+msgid "Keyboard not selected yet"
+msgstr "inte vald"
+
#. TRANSLATORS: page section
-#: src/components/l10n/L10nPage.jsx:84
-#: src/components/overview/L10nSection.jsx:82
+#. TRANSLATORS: page title
+#: src/components/l10n/L10nPage.jsx:385
+#: src/components/overview/L10nSection.jsx:52
msgid "Localization"
msgstr "Lokalisering"
+#: src/components/l10n/L10nPage.jsx:387
+#: src/components/product/ProductPage.jsx:434
+#: src/components/software/SoftwarePage.jsx:81
+#: src/components/storage/DASDPage.jsx:187
+#: src/components/storage/ISCSIPage.jsx:39
+#: src/components/storage/ProposalPage.jsx:218
+#: src/components/storage/ZFCPPage.jsx:736
+#: src/components/users/UsersPage.jsx:30
+msgid "Back"
+msgstr "Bakåt"
+
#: src/components/l10n/LanguageSwitcher.jsx:46
-#| msgid "language"
msgid "Display Language"
msgstr "Visningsspråk"
+#: src/components/l10n/LanguageSwitcher.jsx:50
+msgid "language"
+msgstr "språk"
+
+#: src/components/l10n/LocaleSelector.jsx:82
+msgid "Filter by language, territory or locale code"
+msgstr ""
+
+#: src/components/l10n/LocaleSelector.jsx:89
+#, fuzzy
+msgid "Available locales"
+msgstr "Tillgängliga enheter"
+
+#. TRANSLATORS: placeholder text for search input in the timezone selector.
+#: src/components/l10n/TimezoneSelector.jsx:102
+msgid "Filter by territory, time zone code or UTC offset"
+msgstr ""
+
+#: src/components/l10n/TimezoneSelector.jsx:109
+#, fuzzy
+msgid "Available time zones"
+msgstr "Tillgängliga enheter"
+
#: src/components/layout/Loading.jsx:30
msgid "Loading installation environment, please wait."
msgstr "Laddar installationsmiljö, vänligen vänta."
@@ -408,7 +552,7 @@ msgid "IP addresses"
msgstr "IP adresser"
#: src/components/network/ConnectionsTable.jsx:67
-#: src/components/storage/ProposalVolumes.jsx:233
+#: src/components/storage/ProposalVolumes.jsx:234
#: src/components/storage/iscsi/InitiatorPresenter.jsx:49
#: src/components/storage/iscsi/NodesPresenter.jsx:73
#: src/components/users/FirstUser.jsx:170
@@ -552,6 +696,8 @@ msgid "WPA & WPA2 Personal"
msgstr "WPA & WPA2 Personal"
#: src/components/network/WifiConnectionForm.jsx:91
+#: src/components/product/ProductPage.jsx:128
+#: src/components/product/ProductPage.jsx:194
#: src/components/storage/ZFCPDiskForm.jsx:112
#: src/components/storage/iscsi/DiscoverForm.jsx:108
#: src/components/storage/iscsi/LoginForm.jsx:72
@@ -624,9 +770,9 @@ msgstr "%s anslutningen väntar på en tillståndsändring"
msgid "Forget network"
msgstr "Glöm nätverk"
-#. TRANSLATORS: %s will be replaced by a language name and code,
-#. example: "English (en_US.UTF-8)"
-#: src/components/overview/L10nSection.jsx:70
+#. TRANSLATORS: %s will be replaced by a language name and territory, example:
+#. "English (United States)".
+#: src/components/overview/L10nSection.jsx:34
#, c-format
msgid "The system will use %s as its default language."
msgstr "Systemet kommer att använda %s som dess standardspråk."
@@ -644,45 +790,63 @@ msgid_plural "%d connections set:"
msgstr[0] "%d anslutning inställd:"
msgstr[1] "%d anslutningar inställda:"
+#. TRANSLATORS: page title
+#: src/components/overview/Overview.jsx:47
+msgid "Installation Summary"
+msgstr "Installationssammanfattning"
+
+#. TRANSLATORS: %s is replaced by a product name (e.g., SUSE ALP-Dolomite)
+#: src/components/overview/ProductSection.jsx:48
+#, c-format
+msgid "%s (registered)"
+msgstr "%s (registrerad)"
+
#: src/components/overview/SoftwareSection.jsx:37
msgid "Reading software repositories"
msgstr "Läser programvaruförråd"
#. TRANSLATORS: clickable link label
-#: src/components/overview/SoftwareSection.jsx:135
+#: src/components/overview/SoftwareSection.jsx:131
msgid "Refresh the repositories"
msgstr "Uppdatera förråden"
-#. TRANSLATORS: page section
-#. TRANSLATORS: page title
-#: src/components/overview/SoftwareSection.jsx:145
-#: src/components/software/SoftwarePage.jsx:81
-msgid "Software"
-msgstr "Programvara"
-
-#: src/components/overview/StorageSection.jsx:36
-#: src/components/storage/ProposalSettingsSection.jsx:122
+#: src/components/overview/StorageSection.jsx:42
+#: src/components/storage/ProposalSettingsSection.jsx:126
msgid "No device selected yet"
msgstr "Ingen enhet vald ännu"
#. TRANSLATORS: %s will be replaced by the device name and its size,
#. example: "/dev/sda, 20 GiB"
-#: src/components/overview/StorageSection.jsx:44
+#: src/components/overview/StorageSection.jsx:52
+#, fuzzy, c-format
+msgid "Install using device %s shrinking existing partitions as needed"
+msgstr "Installerar på enhet %s och raderar allt innehåll"
+
+#. TRANSLATORS: %s will be replaced by the device name and its size,
+#. example: "/dev/sda, 20 GiB"
+#: src/components/overview/StorageSection.jsx:56
+#, fuzzy, c-format
+msgid "Install using device %s without modifying existing partitions"
+msgstr "Installerar på enhet %s och raderar allt innehåll"
+
+#. TRANSLATORS: %s will be replaced by the device name and its size,
+#. example: "/dev/sda, 20 GiB"
+#: src/components/overview/StorageSection.jsx:60
#, c-format
msgid "Install using device %s and deleting all its content"
msgstr "Installerar på enhet %s och raderar allt innehåll"
-#: src/components/overview/StorageSection.jsx:57
+#. TRANSLATORS: %s will be replaced by the device name and its size,
+#. example: "/dev/sda, 20 GiB"
+#: src/components/overview/StorageSection.jsx:66
+#, fuzzy, c-format
+msgid "Install using device %s"
+msgstr "Installationsenhet"
+
+#: src/components/overview/StorageSection.jsx:83
msgid "Probing storage devices"
msgstr "Undersöker lagringsenheter"
-#. TRANSLATORS: page title
-#. TRANSLATORS: page section title
-#: src/components/overview/StorageSection.jsx:182
-#: src/components/storage/ProposalPage.jsx:218
-msgid "Storage"
-msgstr "Lagring"
-
#. TRANSLATORS: %s will be replaced by the user name
#: src/components/overview/UsersSection.jsx:80
#, c-format
@@ -718,6 +882,96 @@ msgstr "Rootautentiseringsuppsättning för användning av offentlig SSH nyckel"
msgid "Users"
msgstr "Användare"
+#: src/components/product/ProductPage.jsx:63
+#: src/components/product/ProductSelectionPage.jsx:73
+msgid "Choose a product"
+msgstr "Välj en produkt"
+
+#: src/components/product/ProductPage.jsx:122
+#, c-format
+msgid "Register %s"
+msgstr "Registrera %s"
+
+#: src/components/product/ProductPage.jsx:188
+#, c-format
+msgid "Deregister %s"
+msgstr "Avregistrera %s"
+
+#. TRANSLATORS: %s is replaced by a product name (e.g., SUSE ALP-Dolomite)
+#: src/components/product/ProductPage.jsx:202
+#, c-format
+msgid "Do you want to deregister %s?"
+msgstr "Vill du avregistrera %s?"
+
+#: src/components/product/ProductPage.jsx:227
+msgid "Registered warning"
+msgstr "Registrerad varning"
+
+#. TRANSLATORS: %s is replaced by a product name (e.g., SUSE ALP-Dolomite)
+#: src/components/product/ProductPage.jsx:232
+#, c-format
+msgid "The product %s must be deregistered before selecting a new product."
+msgstr "Produkten %s måste avregistreras innan du väljer en ny produkt."
+
+#: src/components/product/ProductPage.jsx:263
+msgid "Change product"
+msgstr "Ändra produkt"
+
+#: src/components/product/ProductPage.jsx:305
+msgid "Register"
+msgstr "Registrera"
+
+#: src/components/product/ProductPage.jsx:337
+msgid "Deregister product"
+msgstr "Avregistrera produkt"
+
+#: src/components/product/ProductPage.jsx:370
+msgid "Code:"
+msgstr "Kod:"
+
+#: src/components/product/ProductPage.jsx:374
+msgid "Email:"
+msgstr "E-post:"
+
+#. TRANSLATORS: section title.
+#: src/components/product/ProductPage.jsx:390
+msgid "Registration"
+msgstr "Registrering"
+
+#: src/components/product/ProductPage.jsx:399
+msgid "This product requires registration."
+msgstr "Denna produkt kräver registrering."
+
+#: src/components/product/ProductPage.jsx:405
+msgid "This product does not require registration."
+msgstr "Denna produkt kräver ingen registrering."
+
+#: src/components/product/ProductRegistrationForm.jsx:63
+msgid "Registration code"
+msgstr "Registreringskod"
+
+#: src/components/product/ProductRegistrationForm.jsx:66
+msgid "Email"
+msgstr "E-post"
+
+#: src/components/product/ProductSelectionPage.jsx:58
+msgid "Loading available products, please wait..."
+msgstr "Laddar tillgängliga produkter, vänligen vänta..."
+
+#. TRANSLATORS: page header
+#: src/components/product/ProductSelectionPage.jsx:64
+msgid "Product selection"
+msgstr "Produktval"
+
+#. TRANSLATORS: button label
+#: src/components/product/ProductSelectionPage.jsx:69
+msgid "Select"
+msgstr "Välj"
+
+#: src/components/product/ProductSelector.jsx:29
+msgid "No products available for selection"
+msgstr "Inga produkter tillgängliga för val"
+
#: src/components/questions/GenericQuestion.jsx:35
#: src/components/questions/LuksActivationQuestion.jsx:60
msgid "Question"
@@ -737,10 +991,6 @@ msgstr "Krypterad enhet"
msgid "Encryption Password"
msgstr "Krypteringslösenord"
-#: src/components/software/ChangeProductLink.jsx:36
-msgid "Change product"
-msgstr "Ändra produkt"
-
#. TRANSLATORS: pattern status, selected to install (by user)
#: src/components/software/PatternItem.jsx:63
msgid "selected"
@@ -758,47 +1008,14 @@ msgstr "inte vald"
#. TRANSLATORS: error summary, always plural, %d is replaced by number of errors (2 or more)
#. if there is just a single error then the error is displayed directly instead of this summary
-#: src/components/software/PatternSelector.jsx:206
+#: src/components/software/PatternSelector.jsx:207
#, c-format
msgid "%d errors"
msgstr "%d fel"
-#: src/components/software/PatternSelector.jsx:210
-msgid "Software summary and filter options"
-msgstr ""
-
-#. TRANSLATORS: search field placeholder text
#: src/components/software/PatternSelector.jsx:215
-#: src/components/software/PatternSelector.jsx:216
-msgid "Search"
-msgstr "Sök"
-
-#: src/components/software/ProductSelectionPage.jsx:69
-msgid "Loading available products, please wait..."
-msgstr "Laddar tillgängliga produkter, vänligen vänta..."
-
-#. TRANSLATORS: page header
-#: src/components/software/ProductSelectionPage.jsx:94
-msgid "Product selection"
-msgstr "Produktval"
-
-#. TRANSLATORS: button label
-#: src/components/software/ProductSelectionPage.jsx:99
-msgid "Select"
-msgstr "Välj"
-
-#: src/components/software/ProductSelectionPage.jsx:104
-msgid "Choose a product"
-msgstr "Välj en produkt"
-
-#: src/components/software/SoftwarePage.jsx:81
-#: src/components/storage/DASDPage.jsx:187
-#: src/components/storage/ISCSIPage.jsx:39
-#: src/components/storage/ProposalPage.jsx:218
-#: src/components/storage/ZFCPPage.jsx:736
-#: src/components/users/UsersPage.jsx:30
-msgid "Back"
-msgstr "Bakåt"
+msgid "Software summary and filter options"
+msgstr "Programsammanfattning och filteralternativ"
#. TRANSLATORS: %s will be replaced by the estimated installation size,
#. example: "728.8 MiB"
@@ -977,29 +1194,29 @@ msgstr "Anslut till iSCSI mål"
msgid "iSCSI"
msgstr "iSCSI"
-#: src/components/storage/ProposalSettingsSection.jsx:135
+#: src/components/storage/ProposalSettingsSection.jsx:139
msgid "Select the device for installing the system."
msgstr "Välj enhet för installation av systemet."
-#: src/components/storage/ProposalSettingsSection.jsx:140
#: src/components/storage/ProposalSettingsSection.jsx:144
-#: src/components/storage/ProposalSettingsSection.jsx:240
+#: src/components/storage/ProposalSettingsSection.jsx:148
+#: src/components/storage/ProposalSettingsSection.jsx:244
msgid "Installation device"
msgstr "Installationsenhet"
-#: src/components/storage/ProposalSettingsSection.jsx:150
+#: src/components/storage/ProposalSettingsSection.jsx:154
msgid "No devices found"
msgstr "Inga enheter hittades"
-#: src/components/storage/ProposalSettingsSection.jsx:237
+#: src/components/storage/ProposalSettingsSection.jsx:241
msgid "Devices for creating the volume group"
msgstr "Enheter för att skapa volymgrupp"
-#: src/components/storage/ProposalSettingsSection.jsx:246
+#: src/components/storage/ProposalSettingsSection.jsx:250
msgid "Custom devices"
msgstr "Anpassade enheter"
-#: src/components/storage/ProposalSettingsSection.jsx:310
+#: src/components/storage/ProposalSettingsSection.jsx:314
msgid ""
"Configuration of the system volume group. All the file systems will be "
"created in a logical volume of the system volume group."
@@ -1007,37 +1224,52 @@ msgstr ""
"Konfiguration av systemvolymgruppen. Alla filsystem kommer att skapas i en "
"logisk volym av systemvolymgruppen."
-#: src/components/storage/ProposalSettingsSection.jsx:316
+#: src/components/storage/ProposalSettingsSection.jsx:320
msgid "Configure the LVM settings"
msgstr "Konfigurera LVM-inställningar"
-#: src/components/storage/ProposalSettingsSection.jsx:321
-#: src/components/storage/ProposalSettingsSection.jsx:341
+#: src/components/storage/ProposalSettingsSection.jsx:325
+#: src/components/storage/ProposalSettingsSection.jsx:345
msgid "LVM settings"
msgstr "LVM-inställningar"
-#: src/components/storage/ProposalSettingsSection.jsx:334
+#: src/components/storage/ProposalSettingsSection.jsx:338
msgid "Use logical volume management (LVM)"
msgstr "Använd logisk volymhantering (LVM)"
-#: src/components/storage/ProposalSettingsSection.jsx:342
+#: src/components/storage/ProposalSettingsSection.jsx:346
msgid "System Volume Group"
msgstr "System volymgrupp"
-#: src/components/storage/ProposalSettingsSection.jsx:470
+#: src/components/storage/ProposalSettingsSection.jsx:474
msgid "Change encryption password"
msgstr "Ändra krypteringslösenord"
-#: src/components/storage/ProposalSettingsSection.jsx:475
-#: src/components/storage/ProposalSettingsSection.jsx:496
+#: src/components/storage/ProposalSettingsSection.jsx:479
+#: src/components/storage/ProposalSettingsSection.jsx:500
msgid "Encryption settings"
msgstr "Krypteringsinställningar"
-#: src/components/storage/ProposalSettingsSection.jsx:489
+#: src/components/storage/ProposalSettingsSection.jsx:493
msgid "Use encryption"
msgstr "Använd kryptering"
-#: src/components/storage/ProposalSettingsSection.jsx:557
+#: src/components/storage/ProposalSettingsSection.jsx:578
+msgid ""
+"Select how to make free space in the disks selected for allocating the "
+"file systems."
+msgstr ""
+
+#. TRANSLATORS: To be completed with the rest of a sentence like "deleting all content"
+#: src/components/storage/ProposalSettingsSection.jsx:584
+msgid "Find space"
+msgstr ""
+
+#: src/components/storage/ProposalSettingsSection.jsx:588
+msgid "Space Policy"
+msgstr ""
+
+#: src/components/storage/ProposalSettingsSection.jsx:662
msgid "Settings"
msgstr "Inställningar"
@@ -1090,48 +1322,48 @@ msgid "partition"
msgstr "partition"
#. TRANSLATORS: filesystem flag, it uses an encryption
-#: src/components/storage/ProposalVolumes.jsx:215
+#: src/components/storage/ProposalVolumes.jsx:216
msgid "encrypted"
msgstr "krypterad"
#. TRANSLATORS: filesystem flag, it allows creating snapshots
-#: src/components/storage/ProposalVolumes.jsx:217
+#: src/components/storage/ProposalVolumes.jsx:218
msgid "with snapshots"
msgstr "med ögonblicksavbilder"
#. TRANSLATORS: flag for transactional file system
-#: src/components/storage/ProposalVolumes.jsx:219
+#: src/components/storage/ProposalVolumes.jsx:220
msgid "transactional"
msgstr "transaktionell"
-#: src/components/storage/ProposalVolumes.jsx:228
+#: src/components/storage/ProposalVolumes.jsx:229
#: src/components/storage/iscsi/NodesPresenter.jsx:77
msgid "Delete"
msgstr "Ta bort"
-#: src/components/storage/ProposalVolumes.jsx:274
+#: src/components/storage/ProposalVolumes.jsx:275
msgid "Edit file system"
msgstr "Redigera filsystem"
-#: src/components/storage/ProposalVolumes.jsx:306
+#: src/components/storage/ProposalVolumes.jsx:307
#: src/components/storage/VolumeForm.jsx:500
msgid "Mount point"
msgstr "Monteringspunkt"
-#: src/components/storage/ProposalVolumes.jsx:307
+#: src/components/storage/ProposalVolumes.jsx:308
msgid "Details"
msgstr "Detaljer"
-#: src/components/storage/ProposalVolumes.jsx:308
+#: src/components/storage/ProposalVolumes.jsx:309
#: src/components/storage/VolumeForm.jsx:517
msgid "Size"
msgstr "Storlek"
-#: src/components/storage/ProposalVolumes.jsx:344
+#: src/components/storage/ProposalVolumes.jsx:345
msgid "Table with mount points"
msgstr "Tabell med monteringspunkter"
-#: src/components/storage/ProposalVolumes.jsx:407
+#: src/components/storage/ProposalVolumes.jsx:408
msgid "File systems to create in your system"
msgstr "Filsystem att skapa i ditt system"
@@ -1347,64 +1579,64 @@ msgid "Storage zFCP"
msgstr "Lagring zFCP"
#. TRANSLATORS: multipath device type
-#: src/components/storage/device-utils.jsx:98
+#: src/components/storage/device-utils.jsx:97
msgid "Multipath"
msgstr "Flervägs"
#. TRANSLATORS: %s is replaced by the device bus ID
-#: src/components/storage/device-utils.jsx:103
+#: src/components/storage/device-utils.jsx:102
#, c-format
msgid "DASD %s"
msgstr "DASD %s"
#. TRANSLATORS: software RAID device, %s is replaced by the RAID level, e.g. RAID-1
-#: src/components/storage/device-utils.jsx:108
+#: src/components/storage/device-utils.jsx:107
#, c-format
msgid "Software %s"
msgstr "Programvara %s"
-#: src/components/storage/device-utils.jsx:113
+#: src/components/storage/device-utils.jsx:112
msgid "SD Card"
msgstr "SD-kort"
#. TRANSLATORS: %s is replaced by the device transport name, e.g. USB, SATA, SCSI...
-#: src/components/storage/device-utils.jsx:115
+#: src/components/storage/device-utils.jsx:114
#, c-format
msgid "Transport %s"
msgstr "Transport %s"
#. TRANSLATORS: RAID details, %s is replaced by list of devices used by the array
-#: src/components/storage/device-utils.jsx:134
+#: src/components/storage/device-utils.jsx:133
#, c-format
msgid "Members: %s"
msgstr "Medlemmar: %s"
#. TRANSLATORS: RAID details, %s is replaced by list of devices used by the array
-#: src/components/storage/device-utils.jsx:143
+#: src/components/storage/device-utils.jsx:142
#, c-format
msgid "Devices: %s"
msgstr "Enheter: %s"
#. TRANSLATORS: multipath details, %s is replaced by list of connections used by the device
-#: src/components/storage/device-utils.jsx:152
+#: src/components/storage/device-utils.jsx:151
#, c-format
msgid "Wires: %s"
msgstr "Kablar: %s"
#. TRANSLATORS: disk partition info, %s is replaced by partition table
#. type (MS-DOS or GPT), %d is the number of the partitions
-#: src/components/storage/device-utils.jsx:176
+#: src/components/storage/device-utils.jsx:175
#, c-format
msgid "%s with %d partitions"
msgstr "%s med %d partitioner"
#. TRANSLATORS: status message, no existing content was found on the disk,
#. i.e. the disk is completely empty
-#: src/components/storage/device-utils.jsx:200
+#: src/components/storage/device-utils.jsx:199
msgid "No content found"
msgstr "Inget innehåll hittades"
-#: src/components/storage/device-utils.jsx:271
+#: src/components/storage/device-utils.jsx:270
msgid "Available devices"
msgstr "Tillgängliga enheter"
@@ -1491,7 +1723,7 @@ msgstr "Redigera iSCSI initiativtagare"
#. TRANSLATORS: iSCSI initiator name
#: src/components/storage/iscsi/InitiatorForm.jsx:49
msgid "Initiator name"
-msgstr "initiativtagarens namn"
+msgstr "Initiativtagarens namn"
#. TRANSLATORS: usually just keep the original text
#. iBFT = iSCSI Boot Firmware Table, HW support for booting from iSCSI
@@ -1580,6 +1812,70 @@ msgstr "Upptäck"
msgid "Targets"
msgstr "Mål"
+#. TRANSLATORS: automatic actions to find space for installation in the target disk(s)
+#: src/components/storage/space-policy-utils.jsx:59
+msgid "Delete current content"
+msgstr ""
+
+#. TRANSLATORS: automatic actions to find space for installation in the target disk(s)
+#: src/components/storage/space-policy-utils.jsx:63
+msgid "Shrink existing partitions"
+msgstr ""
+
+#. TRANSLATORS: automatic actions to find space for installation in the target disk(s)
+#: src/components/storage/space-policy-utils.jsx:67
+#, fuzzy
+msgid "Use available space"
+msgstr "Tillgängliga enheter"
+
+#: src/components/storage/space-policy-utils.jsx:79
+msgid "All partitions will be removed and any data in the disks will be lost."
+msgstr ""
+
+#: src/components/storage/space-policy-utils.jsx:82
+msgid ""
+"The data is kept, but the current partitions will be resized as needed to "
+"make enough space."
+msgstr ""
+
+#: src/components/storage/space-policy-utils.jsx:85
+msgid ""
+"The data is kept and existing partitions will not be modified. Only the "
+"space that is not assigned to any partition will be used."
+msgstr ""
+
+#: src/components/storage/space-policy-utils.jsx:112
+msgid "Select a mechanism to make space"
+msgstr ""
+
+#: src/components/storage/space-policy-utils.jsx:137
+#, c-format
+msgid "deleting all content of the installation device"
+msgid_plural "deleting all content of the %d selected disks"
+msgstr[0] ""
+msgstr[1] ""
+
+#: src/components/storage/space-policy-utils.jsx:147
+#, c-format
+msgid "shrinking partitions of the installation device"
+msgid_plural "shrinking partitions of the %d selected disks"
+msgstr[0] ""
+msgstr[1] ""
+
+#. TRANSLATORS: This is presented next to the label "Find space", so the whole sentence
+#. would read as "Find space without modifying any partition".
+#: src/components/storage/space-policy-utils.jsx:155
+#, fuzzy
+msgid "without modifying any partition"
+msgstr "%s med %d partitioner"
+
+#: src/components/storage/space-policy-utils.jsx:170
+#, c-format
+msgid "This will only affect the installation device"
+msgid_plural "This will affect the %d disks selected for installation"
+msgstr[0] ""
+msgstr[1] ""
+
#: src/components/storage/utils.js:44
msgid "KiB"
msgstr "KiB"
diff --git a/web/po/uk.po b/web/po/uk.po
index 6c0254a32e..cfe42f68fa 100644
--- a/web/po/uk.po
+++ b/web/po/uk.po
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2023-11-05 02:14+0000\n"
+"POT-Creation-Date: 2023-12-03 02:17+0000\n"
"PO-Revision-Date: 2023-08-06 21:15+0000\n"
"Last-Translator: Milachew \n"
"Language-Team: Ukrainian =2 && n"
-"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"
+"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
+"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"
"X-Generator: Weblate 4.9.1\n"
-#: src/App.jsx:112
+#: src/App.jsx:110
msgid "Diagnostic tools"
msgstr ""
@@ -64,6 +64,7 @@ msgstr ""
#: src/components/core/About.jsx:71 src/components/core/FileViewer.jsx:80
#: src/components/core/Sidebar.jsx:177 src/components/core/Terminal.jsx:48
#: src/components/network/WifiSelector.jsx:151
+#: src/components/product/ProductPage.jsx:239
msgid "Close"
msgstr "Закрити"
@@ -132,18 +133,23 @@ msgid ""
msgstr ""
#. TRANSLATORS: button label
-#: src/components/core/InstallButton.jsx:97
-#: src/components/storage/ProposalSettingsSection.jsx:166
-#: src/components/storage/ProposalSettingsSection.jsx:359
-#: src/components/storage/ProposalSettingsSection.jsx:504
+#: src/components/core/InstallButton.jsx:97 src/components/l10n/L10nPage.jsx:75
+#: src/components/l10n/L10nPage.jsx:191 src/components/l10n/L10nPage.jsx:304
+#: src/components/product/ProductPage.jsx:71
+#: src/components/product/ProductPage.jsx:140
+#: src/components/product/ProductPage.jsx:207
+#: src/components/storage/ProposalSettingsSection.jsx:170
+#: src/components/storage/ProposalSettingsSection.jsx:363
+#: src/components/storage/ProposalSettingsSection.jsx:508
+#: src/components/storage/ProposalSettingsSection.jsx:604
#: src/components/storage/ProposalVolumes.jsx:148
-#: src/components/storage/ProposalVolumes.jsx:282
+#: src/components/storage/ProposalVolumes.jsx:283
#: src/components/storage/ZFCPPage.jsx:513
msgid "Accept"
msgstr ""
#. TRANSLATORS: button label
-#: src/components/core/InstallButton.jsx:145
+#: src/components/core/InstallButton.jsx:148
msgid "Install"
msgstr ""
@@ -193,10 +199,40 @@ msgstr ""
msgid "There are new issues"
msgstr ""
-#: src/components/core/IssuesPage.jsx:115
+#. TRANSLATORS: page section
+#: src/components/core/IssuesPage.jsx:92
+#: src/components/overview/ProductSection.jsx:71
+#: src/components/product/ProductPage.jsx:434
+msgid "Product"
+msgstr ""
+
+#. TRANSLATORS: page title
+#. TRANSLATORS: page section title
+#: src/components/core/IssuesPage.jsx:100
+#: src/components/overview/StorageSection.jsx:208
+#: src/components/storage/ProposalPage.jsx:218
+msgid "Storage"
+msgstr ""
+
+#. TRANSLATORS: page title
+#. TRANSLATORS: page section
+#: src/components/core/IssuesPage.jsx:108
+#: src/components/overview/SoftwareSection.jsx:141
+#: src/components/software/SoftwarePage.jsx:81
+msgid "Software"
+msgstr ""
+
+#: src/components/core/IssuesPage.jsx:129
msgid "No issues found. Everything looks ok."
msgstr ""
+#. TRANSLATORS: search field placeholder text
+#: src/components/core/ListSearch.jsx:50
+#: src/components/software/PatternSelector.jsx:220
+#: src/components/software/PatternSelector.jsx:221
+msgid "Search"
+msgstr ""
+
#: src/components/core/LogsButton.jsx:98
msgid "Collecting logs..."
msgstr ""
@@ -256,12 +292,11 @@ msgstr ""
#. TRANSLATORS: dropdown label
#: src/components/core/RowActions.jsx:66
#: src/components/storage/ProposalVolumes.jsx:119
-#: src/components/storage/ProposalVolumes.jsx:309
+#: src/components/storage/ProposalVolumes.jsx:310
msgid "Actions"
msgstr ""
#: src/components/core/SectionSkeleton.jsx:29
-#: src/components/core/SectionSkeleton.jsx:34
msgid "Waiting"
msgstr ""
@@ -318,22 +353,121 @@ msgstr[2] ""
msgid "Basic popover"
msgstr ""
-#: src/components/l10n/L10nPage.jsx:72
-#: src/components/l10n/LanguageSwitcher.jsx:50
-msgid "language"
+#. TRANSLATORS: placeholder text for search input in the keyboard selector.
+#: src/components/l10n/KeymapSelector.jsx:82
+msgid "Filter by description or keymap code"
+msgstr ""
+
+#: src/components/l10n/KeymapSelector.jsx:89
+msgid "Available keymaps"
+msgstr ""
+
+#: src/components/l10n/L10nPage.jsx:66 src/components/l10n/L10nPage.jsx:140
+msgid "Select time zone"
+msgstr ""
+
+#: src/components/l10n/L10nPage.jsx:67
+#, c-format
+msgid "%s will use the selected time zone."
+msgstr ""
+
+#: src/components/l10n/L10nPage.jsx:128
+msgid "Time zone"
+msgstr ""
+
+#: src/components/l10n/L10nPage.jsx:134
+msgid "Change time zone"
+msgstr ""
+
+#: src/components/l10n/L10nPage.jsx:139
+msgid "Time zone not selected yet"
+msgstr ""
+
+#: src/components/l10n/L10nPage.jsx:182 src/components/l10n/L10nPage.jsx:258
+msgid "Select language"
+msgstr ""
+
+#: src/components/l10n/L10nPage.jsx:183
+#, c-format
+msgid "%s will use the selected language."
+msgstr ""
+
+#: src/components/l10n/L10nPage.jsx:246
+msgid "Language"
+msgstr ""
+
+#: src/components/l10n/L10nPage.jsx:252
+msgid "Change language"
+msgstr ""
+
+#: src/components/l10n/L10nPage.jsx:257
+msgid "Language not selected yet"
+msgstr ""
+
+#: src/components/l10n/L10nPage.jsx:295 src/components/l10n/L10nPage.jsx:369
+msgid "Select keyboard"
+msgstr ""
+
+#: src/components/l10n/L10nPage.jsx:296
+#, c-format
+msgid "%s will use the selected keyboard."
+msgstr ""
+
+#: src/components/l10n/L10nPage.jsx:357
+msgid "Keyboard"
+msgstr ""
+
+#: src/components/l10n/L10nPage.jsx:363
+msgid "Change keyboard"
+msgstr ""
+
+#: src/components/l10n/L10nPage.jsx:368
+msgid "Keyboard not selected yet"
msgstr ""
-#. TRANSLATORS: page header
#. TRANSLATORS: page section
-#: src/components/l10n/L10nPage.jsx:84
-#: src/components/overview/L10nSection.jsx:82
+#. TRANSLATORS: page title
+#: src/components/l10n/L10nPage.jsx:385
+#: src/components/overview/L10nSection.jsx:52
msgid "Localization"
msgstr ""
+#: src/components/l10n/L10nPage.jsx:387
+#: src/components/product/ProductPage.jsx:434
+#: src/components/software/SoftwarePage.jsx:81
+#: src/components/storage/DASDPage.jsx:187
+#: src/components/storage/ISCSIPage.jsx:39
+#: src/components/storage/ProposalPage.jsx:218
+#: src/components/storage/ZFCPPage.jsx:736
+#: src/components/users/UsersPage.jsx:30
+msgid "Back"
+msgstr ""
+
#: src/components/l10n/LanguageSwitcher.jsx:46
msgid "Display Language"
msgstr ""
+#: src/components/l10n/LanguageSwitcher.jsx:50
+msgid "language"
+msgstr ""
+
+#: src/components/l10n/LocaleSelector.jsx:82
+msgid "Filter by language, territory or locale code"
+msgstr ""
+
+#: src/components/l10n/LocaleSelector.jsx:89
+msgid "Available locales"
+msgstr ""
+
+#. TRANSLATORS: placeholder text for search input in the timezone selector.
+#: src/components/l10n/TimezoneSelector.jsx:102
+msgid "Filter by territory, time zone code or UTC offset"
+msgstr ""
+
+#: src/components/l10n/TimezoneSelector.jsx:109
+msgid "Available time zones"
+msgstr ""
+
#: src/components/layout/Loading.jsx:30
msgid "Loading installation environment, please wait."
msgstr ""
@@ -394,7 +528,7 @@ msgid "IP addresses"
msgstr ""
#: src/components/network/ConnectionsTable.jsx:67
-#: src/components/storage/ProposalVolumes.jsx:233
+#: src/components/storage/ProposalVolumes.jsx:234
#: src/components/storage/iscsi/InitiatorPresenter.jsx:49
#: src/components/storage/iscsi/NodesPresenter.jsx:73
#: src/components/users/FirstUser.jsx:170
@@ -535,6 +669,8 @@ msgid "WPA & WPA2 Personal"
msgstr ""
#: src/components/network/WifiConnectionForm.jsx:91
+#: src/components/product/ProductPage.jsx:128
+#: src/components/product/ProductPage.jsx:194
#: src/components/storage/ZFCPDiskForm.jsx:112
#: src/components/storage/iscsi/DiscoverForm.jsx:108
#: src/components/storage/iscsi/LoginForm.jsx:72
@@ -607,9 +743,9 @@ msgstr ""
msgid "Forget network"
msgstr ""
-#. TRANSLATORS: %s will be replaced by a language name and code,
-#. example: "English (en_US.UTF-8)"
-#: src/components/overview/L10nSection.jsx:70
+#. TRANSLATORS: %s will be replaced by a language name and territory, example:
+#. "English (United States)".
+#: src/components/overview/L10nSection.jsx:34
#, c-format
msgid "The system will use %s as its default language."
msgstr ""
@@ -628,43 +764,61 @@ msgstr[0] ""
msgstr[1] ""
msgstr[2] ""
+#. TRANSLATORS: page title
+#: src/components/overview/Overview.jsx:47
+msgid "Installation Summary"
+msgstr ""
+
+#. TRANSLATORS: %s is replaced by a product name (e.g., SUSE ALP-Dolomite)
+#: src/components/overview/ProductSection.jsx:48
+#, c-format
+msgid "%s (registered)"
+msgstr ""
+
#: src/components/overview/SoftwareSection.jsx:37
msgid "Reading software repositories"
msgstr ""
#. TRANSLATORS: clickable link label
-#: src/components/overview/SoftwareSection.jsx:135
+#: src/components/overview/SoftwareSection.jsx:131
msgid "Refresh the repositories"
msgstr ""
-#. TRANSLATORS: page section
-#. TRANSLATORS: page title
-#: src/components/overview/SoftwareSection.jsx:145
-#: src/components/software/SoftwarePage.jsx:81
-msgid "Software"
+#: src/components/overview/StorageSection.jsx:42
+#: src/components/storage/ProposalSettingsSection.jsx:126
+msgid "No device selected yet"
msgstr ""
-#: src/components/overview/StorageSection.jsx:36
-#: src/components/storage/ProposalSettingsSection.jsx:122
-msgid "No device selected yet"
+#. TRANSLATORS: %s will be replaced by the device name and its size,
+#. example: "/dev/sda, 20 GiB"
+#: src/components/overview/StorageSection.jsx:52
+#, c-format
+msgid "Install using device %s shrinking existing partitions as needed"
msgstr ""
#. TRANSLATORS: %s will be replaced by the device name and its size,
#. example: "/dev/sda, 20 GiB"
-#: src/components/overview/StorageSection.jsx:44
+#: src/components/overview/StorageSection.jsx:56
+#, c-format
+msgid "Install using device %s without modifying existing partitions"
+msgstr ""
+
+#. TRANSLATORS: %s will be replaced by the device name and its size,
+#. example: "/dev/sda, 20 GiB"
+#: src/components/overview/StorageSection.jsx:60
#, c-format
msgid "Install using device %s and deleting all its content"
msgstr ""
-#: src/components/overview/StorageSection.jsx:57
-msgid "Probing storage devices"
+#. TRANSLATORS: %s will be replaced by the device name and its size,
+#. example: "/dev/sda, 20 GiB"
+#: src/components/overview/StorageSection.jsx:66
+#, c-format
+msgid "Install using device %s"
msgstr ""
-#. TRANSLATORS: page title
-#. TRANSLATORS: page section title
-#: src/components/overview/StorageSection.jsx:182
-#: src/components/storage/ProposalPage.jsx:218
-msgid "Storage"
+#: src/components/overview/StorageSection.jsx:83
+msgid "Probing storage devices"
msgstr ""
#. TRANSLATORS: %s will be replaced by the user name
@@ -700,6 +854,96 @@ msgstr ""
msgid "Users"
msgstr ""
+#: src/components/product/ProductPage.jsx:63
+#: src/components/product/ProductSelectionPage.jsx:73
+msgid "Choose a product"
+msgstr ""
+
+#: src/components/product/ProductPage.jsx:122
+#, c-format
+msgid "Register %s"
+msgstr ""
+
+#: src/components/product/ProductPage.jsx:188
+#, c-format
+msgid "Deregister %s"
+msgstr ""
+
+#. TRANSLATORS: %s is replaced by a product name (e.g., SUSE ALP-Dolomite)
+#: src/components/product/ProductPage.jsx:202
+#, c-format
+msgid "Do you want to deregister %s?"
+msgstr ""
+
+#: src/components/product/ProductPage.jsx:227
+msgid "Registered warning"
+msgstr ""
+
+#. TRANSLATORS: %s is replaced by a product name (e.g., SUSE ALP-Dolomite)
+#: src/components/product/ProductPage.jsx:232
+#, c-format
+msgid "The product %s must be deregistered before selecting a new product."
+msgstr ""
+
+#: src/components/product/ProductPage.jsx:263
+msgid "Change product"
+msgstr ""
+
+#: src/components/product/ProductPage.jsx:305
+msgid "Register"
+msgstr ""
+
+#: src/components/product/ProductPage.jsx:337
+msgid "Deregister product"
+msgstr ""
+
+#: src/components/product/ProductPage.jsx:370
+msgid "Code:"
+msgstr ""
+
+#: src/components/product/ProductPage.jsx:374
+msgid "Email:"
+msgstr ""
+
+#. TRANSLATORS: section title.
+#: src/components/product/ProductPage.jsx:390
+msgid "Registration"
+msgstr ""
+
+#: src/components/product/ProductPage.jsx:399
+msgid "This product requires registration."
+msgstr ""
+
+#: src/components/product/ProductPage.jsx:405
+msgid "This product does not require registration."
+msgstr ""
+
+#: src/components/product/ProductRegistrationForm.jsx:63
+msgid "Registration code"
+msgstr ""
+
+#: src/components/product/ProductRegistrationForm.jsx:66
+msgid "Email"
+msgstr ""
+
+#: src/components/product/ProductSelectionPage.jsx:58
+msgid "Loading available products, please wait..."
+msgstr ""
+
+#. TRANSLATORS: page header
+#: src/components/product/ProductSelectionPage.jsx:64
+msgid "Product selection"
+msgstr ""
+
+#. TRANSLATORS: button label
+#: src/components/product/ProductSelectionPage.jsx:69
+msgid "Select"
+msgstr ""
+
+#: src/components/product/ProductSelector.jsx:29
+msgid "No products available for selection"
+msgstr ""
+
#: src/components/questions/GenericQuestion.jsx:35
#: src/components/questions/LuksActivationQuestion.jsx:60
msgid "Question"
@@ -719,10 +963,6 @@ msgstr ""
msgid "Encryption Password"
msgstr ""
-#: src/components/software/ChangeProductLink.jsx:36
-msgid "Change product"
-msgstr ""
-
#. TRANSLATORS: pattern status, selected to install (by user)
#: src/components/software/PatternItem.jsx:63
msgid "selected"
@@ -740,46 +980,13 @@ msgstr ""
#. TRANSLATORS: error summary, always plural, %d is replaced by number of errors (2 or more)
#. if there is just a single error then the error is displayed directly instead of this summary
-#: src/components/software/PatternSelector.jsx:206
+#: src/components/software/PatternSelector.jsx:207
#, c-format
msgid "%d errors"
msgstr ""
-#: src/components/software/PatternSelector.jsx:210
-msgid "Software summary and filter options"
-msgstr ""
-
-#. TRANSLATORS: search field placeholder text
#: src/components/software/PatternSelector.jsx:215
-#: src/components/software/PatternSelector.jsx:216
-msgid "Search"
-msgstr ""
-
-#: src/components/software/ProductSelectionPage.jsx:69
-msgid "Loading available products, please wait..."
-msgstr ""
-
-#. TRANSLATORS: page header
-#: src/components/software/ProductSelectionPage.jsx:94
-msgid "Product selection"
-msgstr ""
-
-#. TRANSLATORS: button label
-#: src/components/software/ProductSelectionPage.jsx:99
-msgid "Select"
-msgstr ""
-
-#: src/components/software/ProductSelectionPage.jsx:104
-msgid "Choose a product"
-msgstr ""
-
-#: src/components/software/SoftwarePage.jsx:81
-#: src/components/storage/DASDPage.jsx:187
-#: src/components/storage/ISCSIPage.jsx:39
-#: src/components/storage/ProposalPage.jsx:218
-#: src/components/storage/ZFCPPage.jsx:736
-#: src/components/users/UsersPage.jsx:30
-msgid "Back"
+msgid "Software summary and filter options"
msgstr ""
#. TRANSLATORS: %s will be replaced by the estimated installation size,
@@ -959,65 +1166,80 @@ msgstr ""
msgid "iSCSI"
msgstr ""
-#: src/components/storage/ProposalSettingsSection.jsx:135
+#: src/components/storage/ProposalSettingsSection.jsx:139
msgid "Select the device for installing the system."
msgstr ""
-#: src/components/storage/ProposalSettingsSection.jsx:140
#: src/components/storage/ProposalSettingsSection.jsx:144
-#: src/components/storage/ProposalSettingsSection.jsx:240
+#: src/components/storage/ProposalSettingsSection.jsx:148
+#: src/components/storage/ProposalSettingsSection.jsx:244
msgid "Installation device"
msgstr ""
-#: src/components/storage/ProposalSettingsSection.jsx:150
+#: src/components/storage/ProposalSettingsSection.jsx:154
msgid "No devices found"
msgstr ""
-#: src/components/storage/ProposalSettingsSection.jsx:237
+#: src/components/storage/ProposalSettingsSection.jsx:241
msgid "Devices for creating the volume group"
msgstr ""
-#: src/components/storage/ProposalSettingsSection.jsx:246
+#: src/components/storage/ProposalSettingsSection.jsx:250
msgid "Custom devices"
msgstr ""
-#: src/components/storage/ProposalSettingsSection.jsx:310
+#: src/components/storage/ProposalSettingsSection.jsx:314
msgid ""
"Configuration of the system volume group. All the file systems will be "
"created in a logical volume of the system volume group."
msgstr ""
-#: src/components/storage/ProposalSettingsSection.jsx:316
+#: src/components/storage/ProposalSettingsSection.jsx:320
msgid "Configure the LVM settings"
msgstr ""
-#: src/components/storage/ProposalSettingsSection.jsx:321
-#: src/components/storage/ProposalSettingsSection.jsx:341
+#: src/components/storage/ProposalSettingsSection.jsx:325
+#: src/components/storage/ProposalSettingsSection.jsx:345
msgid "LVM settings"
msgstr ""
-#: src/components/storage/ProposalSettingsSection.jsx:334
+#: src/components/storage/ProposalSettingsSection.jsx:338
msgid "Use logical volume management (LVM)"
msgstr ""
-#: src/components/storage/ProposalSettingsSection.jsx:342
+#: src/components/storage/ProposalSettingsSection.jsx:346
msgid "System Volume Group"
msgstr ""
-#: src/components/storage/ProposalSettingsSection.jsx:470
+#: src/components/storage/ProposalSettingsSection.jsx:474
msgid "Change encryption password"
msgstr ""
-#: src/components/storage/ProposalSettingsSection.jsx:475
-#: src/components/storage/ProposalSettingsSection.jsx:496
+#: src/components/storage/ProposalSettingsSection.jsx:479
+#: src/components/storage/ProposalSettingsSection.jsx:500
msgid "Encryption settings"
msgstr ""
-#: src/components/storage/ProposalSettingsSection.jsx:489
+#: src/components/storage/ProposalSettingsSection.jsx:493
msgid "Use encryption"
msgstr ""
-#: src/components/storage/ProposalSettingsSection.jsx:557
+#: src/components/storage/ProposalSettingsSection.jsx:578
+msgid ""
+"Select how to make free space in the disks selected for allocating the "
+"file systems."
+msgstr ""
+
+#. TRANSLATORS: To be completed with the rest of a sentence like "deleting all content"
+#: src/components/storage/ProposalSettingsSection.jsx:584
+msgid "Find space"
+msgstr ""
+
+#: src/components/storage/ProposalSettingsSection.jsx:588
+msgid "Space Policy"
+msgstr ""
+
+#: src/components/storage/ProposalSettingsSection.jsx:662
msgid "Settings"
msgstr ""
@@ -1070,48 +1292,48 @@ msgid "partition"
msgstr ""
#. TRANSLATORS: filesystem flag, it uses an encryption
-#: src/components/storage/ProposalVolumes.jsx:215
+#: src/components/storage/ProposalVolumes.jsx:216
msgid "encrypted"
msgstr ""
#. TRANSLATORS: filesystem flag, it allows creating snapshots
-#: src/components/storage/ProposalVolumes.jsx:217
+#: src/components/storage/ProposalVolumes.jsx:218
msgid "with snapshots"
msgstr ""
#. TRANSLATORS: flag for transactional file system
-#: src/components/storage/ProposalVolumes.jsx:219
+#: src/components/storage/ProposalVolumes.jsx:220
msgid "transactional"
msgstr ""
-#: src/components/storage/ProposalVolumes.jsx:228
+#: src/components/storage/ProposalVolumes.jsx:229
#: src/components/storage/iscsi/NodesPresenter.jsx:77
msgid "Delete"
msgstr ""
-#: src/components/storage/ProposalVolumes.jsx:274
+#: src/components/storage/ProposalVolumes.jsx:275
msgid "Edit file system"
msgstr ""
-#: src/components/storage/ProposalVolumes.jsx:306
+#: src/components/storage/ProposalVolumes.jsx:307
#: src/components/storage/VolumeForm.jsx:500
msgid "Mount point"
msgstr ""
-#: src/components/storage/ProposalVolumes.jsx:307
+#: src/components/storage/ProposalVolumes.jsx:308
msgid "Details"
msgstr ""
-#: src/components/storage/ProposalVolumes.jsx:308
+#: src/components/storage/ProposalVolumes.jsx:309
#: src/components/storage/VolumeForm.jsx:517
msgid "Size"
msgstr ""
-#: src/components/storage/ProposalVolumes.jsx:344
+#: src/components/storage/ProposalVolumes.jsx:345
msgid "Table with mount points"
msgstr ""
-#: src/components/storage/ProposalVolumes.jsx:407
+#: src/components/storage/ProposalVolumes.jsx:408
msgid "File systems to create in your system"
msgstr ""
@@ -1320,64 +1542,64 @@ msgid "Storage zFCP"
msgstr ""
#. TRANSLATORS: multipath device type
-#: src/components/storage/device-utils.jsx:98
+#: src/components/storage/device-utils.jsx:97
msgid "Multipath"
msgstr ""
#. TRANSLATORS: %s is replaced by the device bus ID
-#: src/components/storage/device-utils.jsx:103
+#: src/components/storage/device-utils.jsx:102
#, c-format
msgid "DASD %s"
msgstr ""
#. TRANSLATORS: software RAID device, %s is replaced by the RAID level, e.g. RAID-1
-#: src/components/storage/device-utils.jsx:108
+#: src/components/storage/device-utils.jsx:107
#, c-format
msgid "Software %s"
msgstr ""
-#: src/components/storage/device-utils.jsx:113
+#: src/components/storage/device-utils.jsx:112
msgid "SD Card"
msgstr ""
#. TRANSLATORS: %s is replaced by the device transport name, e.g. USB, SATA, SCSI...
-#: src/components/storage/device-utils.jsx:115
+#: src/components/storage/device-utils.jsx:114
#, c-format
msgid "Transport %s"
msgstr ""
#. TRANSLATORS: RAID details, %s is replaced by list of devices used by the array
-#: src/components/storage/device-utils.jsx:134
+#: src/components/storage/device-utils.jsx:133
#, c-format
msgid "Members: %s"
msgstr ""
#. TRANSLATORS: RAID details, %s is replaced by list of devices used by the array
-#: src/components/storage/device-utils.jsx:143
+#: src/components/storage/device-utils.jsx:142
#, c-format
msgid "Devices: %s"
msgstr ""
#. TRANSLATORS: multipath details, %s is replaced by list of connections used by the device
-#: src/components/storage/device-utils.jsx:152
+#: src/components/storage/device-utils.jsx:151
#, c-format
msgid "Wires: %s"
msgstr ""
#. TRANSLATORS: disk partition info, %s is replaced by partition table
#. type (MS-DOS or GPT), %d is the number of the partitions
-#: src/components/storage/device-utils.jsx:176
+#: src/components/storage/device-utils.jsx:175
#, c-format
msgid "%s with %d partitions"
msgstr ""
#. TRANSLATORS: status message, no existing content was found on the disk,
#. i.e. the disk is completely empty
-#: src/components/storage/device-utils.jsx:200
+#: src/components/storage/device-utils.jsx:199
msgid "No content found"
msgstr ""
-#: src/components/storage/device-utils.jsx:271
+#: src/components/storage/device-utils.jsx:270
msgid "Available devices"
msgstr ""
@@ -1553,6 +1775,71 @@ msgstr ""
msgid "Targets"
msgstr ""
+#. TRANSLATORS: automatic actions to find space for installation in the target disk(s)
+#: src/components/storage/space-policy-utils.jsx:59
+msgid "Delete current content"
+msgstr ""
+
+#. TRANSLATORS: automatic actions to find space for installation in the target disk(s)
+#: src/components/storage/space-policy-utils.jsx:63
+msgid "Shrink existing partitions"
+msgstr ""
+
+#. TRANSLATORS: automatic actions to find space for installation in the target disk(s)
+#: src/components/storage/space-policy-utils.jsx:67
+msgid "Use available space"
+msgstr ""
+
+#: src/components/storage/space-policy-utils.jsx:79
+msgid "All partitions will be removed and any data in the disks will be lost."
+msgstr ""
+
+#: src/components/storage/space-policy-utils.jsx:82
+msgid ""
+"The data is kept, but the current partitions will be resized as needed to "
+"make enough space."
+msgstr ""
+
+#: src/components/storage/space-policy-utils.jsx:85
+msgid ""
+"The data is kept and existing partitions will not be modified. Only the "
+"space that is not assigned to any partition will be used."
+msgstr ""
+
+#: src/components/storage/space-policy-utils.jsx:112
+msgid "Select a mechanism to make space"
+msgstr ""
+
+#: src/components/storage/space-policy-utils.jsx:137
+#, c-format
+msgid "deleting all content of the installation device"
+msgid_plural "deleting all content of the %d selected disks"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
+#: src/components/storage/space-policy-utils.jsx:147
+#, c-format
+msgid "shrinking partitions of the installation device"
+msgid_plural "shrinking partitions of the %d selected disks"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
+#. TRANSLATORS: This is presented next to the label "Find space", so the whole sentence
+#. would read as "Find space without modifying any partition".
+#: src/components/storage/space-policy-utils.jsx:155
+msgid "without modifying any partition"
+msgstr ""
+
+#: src/components/storage/space-policy-utils.jsx:170
+#, c-format
+msgid "This will only affect the installation device"
+msgid_plural "This will affect the %d disks selected for installation"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
#: src/components/storage/utils.js:44
msgid "KiB"
msgstr ""
diff --git a/web/src/App.jsx b/web/src/App.jsx
index 4099afcd10..c374c3c87b 100644
--- a/web/src/App.jsx
+++ b/web/src/App.jsx
@@ -41,7 +41,7 @@ import {
} from "~/components/core";
import { LanguageSwitcher } from "./components/l10n";
import { Layout, Loading, Title } from "./components/layout";
-import { useL10n } from "./context/l10n";
+import { useInstallerL10n } from "./context/installerL10n";
// D-Bus connection attempts before displaying an error.
const ATTEMPTS = 3;
@@ -57,7 +57,7 @@ function App() {
const client = useInstallerClient();
const { attempt } = useInstallerClientStatus();
const { products } = useProduct();
- const { language } = useL10n();
+ const { language } = useInstallerL10n();
const [status, setStatus] = useState(undefined);
const [phase, setPhase] = useState(undefined);
diff --git a/web/src/App.test.jsx b/web/src/App.test.jsx
index 59b0753521..0e6bab251d 100644
--- a/web/src/App.test.jsx
+++ b/web/src/App.test.jsx
@@ -70,9 +70,14 @@ describe("App", () => {
onPhaseChange: onPhaseChangeFn,
onStatusChange: onStatusChangeFn,
},
- language: {
- getUILanguage: jest.fn().mockResolvedValue("en-us"),
- setUILanguage: jest.fn().mockResolvedValue("en-us"),
+ l10n: {
+ locales: jest.fn().mockResolvedValue([["en_us", "English", "United States"]]),
+ getLocales: jest.fn().mockResolvedValue(["en_us"]),
+ getUILocale: jest.fn().mockResolvedValue("en_us"),
+ setUILocale: jest.fn().mockResolvedValue("en_us"),
+ onTimezoneChange: jest.fn(),
+ onLocalesChange: jest.fn(),
+ onKeymapChange: jest.fn()
}
};
});
diff --git a/web/src/assets/styles/blocks.scss b/web/src/assets/styles/blocks.scss
index 0498607f60..22113f29e1 100644
--- a/web/src/assets/styles/blocks.scss
+++ b/web/src/assets/styles/blocks.scss
@@ -3,11 +3,15 @@
// section layouts.
section:not([class^="pf-c"]) {
display: grid;
- grid-template-columns: min-content 1fr;
+ grid-template-columns: var(--section-icon-size) 1fr;
grid-template-areas:
"icon title"
".... content";
gap: var(--spacer-small);
+ padding-inline-start: calc(
+ var(--header-icon-size) - var(--section-icon-size)
+ );
+ padding-inline-end: var(--section-icon-size);
}
section:not(:last-child, [class^="pf-c"]) {
@@ -16,6 +20,8 @@ section:not(:last-child, [class^="pf-c"]) {
section > svg {
grid-area: icon;
+ block-size: var(--section-icon-size);
+ inline-size: var(--section-icon-size);
}
section > h2 {
@@ -84,6 +90,7 @@ section > .content {
right: 0;
z-index: 1000;
inline-size: 70%;
+ min-inline-size: min-content;
box-shadow: -10px 10px 20px 0 var(--color-primary);
}
@@ -140,6 +147,7 @@ section > .content {
.sidebar[data-state="hidden"] {
transition: all 0.04s ease-in-out;
inline-size: 0;
+ min-inline-size: 0;
box-shadow: none;
}
@@ -264,31 +272,32 @@ span.notification-mark[data-variant="sidebar"] {
}
}
-.device-list {
- [role="option"] {
- padding: var(--spacer-normal);
+ul[data-type="agama/list"] {
+ li {
border: 2px solid var(--color-gray-dark);
+ padding: var(--spacer-normal);
+ text-align: start;
background: var(--color-gray-light);
- border-radius: 5px;
- transition: all 0.2s ease-in-out;
- display: grid;
+ &:nth-child(n+2) {
+ border-top: 0;
+ }
- gap: var(--spacer-small);
- grid-template-columns: 1fr 2fr 2fr;
- grid-template-areas:
- "type-and-size drive-info drive-content"
- ;
+ > div {
+ margin-block-end: var(--spacer-smaller);
+ }
- > :first-child {
- align-self: center;
- text-align: center;
- justify-self: start;
+ // Done in two rules instead of div:not(:last-child) to avoid specificity
+ // problems later; see the storage-devices selector
+ > div:last-child {
+ margin-block-end: 0;
}
}
- [aria-selected] {
- border: 2px solid var(--color-primary);
+ // FIXME: see if it's semantically correct to mark an li as aria-selected when
+ // not belongs to a listbox or grid list ul.
+ li[aria-selected] {
+ border-color: var(--color-primary);
box-shadow: 0 2px 5px 0 var(--color-gray-dark);
background: var(--color-primary);
color: white;
@@ -300,6 +309,113 @@ span.notification-mark[data-variant="sidebar"] {
}
}
+// These attributes together means that UI is rendering a selector
+ul[data-type="agama/list"][role="listbox"] {
+ li[role="option"] {
+ cursor: pointer;
+
+ &:first-child {
+ border-radius: 5px 5px 0 0;
+ }
+
+ &:last-child {
+ border-radius: 0 0 5px 5px;
+ }
+
+ &:only-child {
+ border-radius: 5px;
+ }
+
+ &:hover {
+ &:not([aria-selected]) {
+ background: var(--color-gray-dark);
+ }
+ }
+ }
+}
+
+// Each kind of list/selector has its way of laying out their items
+ul[data-of="agama/storage-devices"] {
+ li {
+ display: grid;
+ gap: var(--spacer-smaller);
+ grid-template-columns: 1fr 2fr 2fr;
+ grid-template-areas: "type-and-size drive-info drive-content";
+
+ svg {
+ vertical-align: inherit;
+ }
+
+ > div {
+ margin-block-end: 0;
+ }
+
+ > :first-child {
+ align-self: center;
+ text-align: center;
+ justify-self: start;
+ }
+ }
+}
+
+ul[data-of="agama/space-policies"] {
+ // It works with the default styling
+}
+
+ul[data-of="agama/locales"] {
+ li {
+ display: grid;
+ grid-template-columns: 1fr 2fr;
+
+ > :last-child {
+ grid-column: 1 / -1;
+ font-size: var(--fs-small);
+ }
+ }
+}
+
+ul[data-of="agama/keymaps"] {
+ li {
+ > :last-child {
+ font-size: var(--fs-small);
+ }
+ }
+}
+
+ul[data-of="agama/timezones"] {
+ li {
+ display: grid;
+ grid-template-columns: 1fr 2fr 1fr;
+
+ > :last-child {
+ grid-column: 1 / -1;
+ font-size: 80%;
+ }
+
+ > :nth-child(3) {
+ color: var(--color-gray-dimmed);
+ text-align: end;
+ }
+ }
+}
+
+[role="dialog"] {
+ .sticky-top-0 {
+ position: sticky;
+ top: calc(-1 * var(--pf-v5-c-modal-box__body--PaddingTop));
+ margin-block-start: calc(-1 * var(--pf-v5-c-modal-box__body--PaddingTop));
+ padding-block-start: var(--pf-v5-c-modal-box__body--PaddingTop);
+ background-color: var(--pf-v5-c-modal-box--BackgroundColor);
+
+ [role="search"] {
+ width: 100%;
+ padding: var(--spacer-small);
+ border: 1px solid var(--color-primary);
+ border-radius: 5px;
+ }
+ }
+}
+
// compact lists in popover
.pf-v5-c-popover li + li {
margin: 0;
diff --git a/web/src/assets/styles/global.scss b/web/src/assets/styles/global.scss
index 9e2723e621..c9d0625683 100644
--- a/web/src/assets/styles/global.scss
+++ b/web/src/assets/styles/global.scss
@@ -56,6 +56,16 @@ th {
text-align: start;
}
+li {
+ svg {
+ vertical-align: middle;
+ }
+
+ span {
+ margin-inline-start: var(--spacer-small);
+ }
+}
+
// Style focus making use of :focus-visible
*:focus {
outline: none;
diff --git a/web/src/assets/styles/layout.scss b/web/src/assets/styles/layout.scss
index eca0fc4683..a0243fa6dc 100644
--- a/web/src/assets/styles/layout.scss
+++ b/web/src/assets/styles/layout.scss
@@ -33,6 +33,8 @@
svg {
color: white;
+ block-size: var(--header-icon-size);
+ inline-size: var(--header-icon-size);
}
}
@@ -40,7 +42,7 @@
grid-area: content;
overflow-y: auto; // Sadly, only Firefox supports overflow-block at this moment (Jan 2023)
overflow-block: auto;
- padding: var(--spacer-medium);
+ padding: var(--spacer-medium) var(--spacer-small);
}
.wrapper > footer {
diff --git a/web/src/assets/styles/patternfly-overrides.scss b/web/src/assets/styles/patternfly-overrides.scss
index fa620490e7..26f7377dce 100644
--- a/web/src/assets/styles/patternfly-overrides.scss
+++ b/web/src/assets/styles/patternfly-overrides.scss
@@ -190,3 +190,38 @@ table td > .pf-v5-c-empty-state {
--pf-v5-c-toggle-group__button--m-selected--BackgroundColor: var(--color-primary);
--pf-v5-c-toggle-group__button--Color: var(--color-gray-light);
}
+
+// Reduce padding of PF/Hint because it looks like an option of current Agama
+// select
+.pf-v5-c-hint {
+ --pf-v5-c-hint--PaddingTop: var(--spacer-small);
+ --pf-v5-c-hint--PaddingRight: var(--spacer-small);
+ --pf-v5-c-hint--PaddingBottom: var(--spacer-small);
+ --pf-v5-c-hint--PaddingLeft: var(--spacer-small);
+}
+
+// Do not reserve space for PF/Hint actions when there are none
+.pf-v5-c-hint__actions:empty {
+ display: none;
+}
+
+// Make PF/ExpandableSection looks a bit different when wrapped in a PF/Hint
+.pf-v5-c-hint {
+ .pf-v5-c-expandable-section {
+ --pf-v5-c-expandable-section__toggle--Color: var(--color-primary);
+ }
+
+ .pf-v5-c-expandable-section__toggle,
+ .pf-v5-c-expandable-section__toggle:hover {
+ // NOTE. would be nice to being able to use darker variant of primary color
+ // when hovering the link, but we aren't ready yet. We should switch to hsla
+ // colors or so.
+ --pf-v5-c-expandable-section__toggle--Color: var(--color-primary);
+ text-decoration: underline;
+ }
+
+ .pf-v5-c-expandable-section__content {
+ --pf-v5-c-expandable-section__content--PaddingRight: var(--spacer-normal);
+ --pf-v5-c-expandable-section__content--PaddingLeft: var(--spacer-normal);
+ }
+}
diff --git a/web/src/assets/styles/utilities.scss b/web/src/assets/styles/utilities.scss
index 1f84016877..39f71a946f 100644
--- a/web/src/assets/styles/utilities.scss
+++ b/web/src/assets/styles/utilities.scss
@@ -25,6 +25,11 @@
text-align: center;
}
+.title {
+ font-size: var(--fs-large);
+ font-weight: var(--fw-bold);
+}
+
.bold {
font-weight: bold;
}
@@ -59,6 +64,11 @@
height: 24px;
}
+.icon-size-28 {
+ width: 28px;
+ height: 28px;
+}
+
.icon-size-32 {
width: 32px;
height: 32px;
@@ -161,6 +171,6 @@
max-inline-size: calc(var(--ui-max-inline-size) + var(--spacer-large))
}
-.cursor-pointer {
- cursor: pointer;
+.height-75 {
+ height: 75dvh;
}
diff --git a/web/src/assets/styles/variables.scss b/web/src/assets/styles/variables.scss
index 670e87510c..0b01334550 100644
--- a/web/src/assets/styles/variables.scss
+++ b/web/src/assets/styles/variables.scss
@@ -8,7 +8,9 @@
--fw-medium: 500;
--fw-bold: 700;
+ --fs-small: 0.7rem;
--fs-base: 14px;
+ --fs-large: 1rem;
--fs-h1: 1.5rem;
--fs-h2: 1.2rem;
@@ -18,6 +20,8 @@
--ui-max-inline-size: 1024px;
+ // FIXME: this should be changed to --spacer-xs, --spacer-s, and so
+ --spacer-smaller: 0.3rem;
--spacer-small: 0.5rem;
--spacer-normal: 1rem;
--spacer-medium: 1.5rem;
@@ -26,7 +30,7 @@
--stack-gutter: var(--spacer-normal);
--split-gutter: var(--spacer-small);
- --wrapper-padding: var(--spacer-normal);
+ --wrapper-padding: var(--spacer-small);
--wrapper-background: white;
--color-primary: #0c322c;
@@ -35,6 +39,7 @@
--color-gray: #f2f2f2;
--color-gray-dark: #efefef; // Fog
--color-gray-darker: #999;
+ --color-gray-dimmed: #888;
--color-link: #0c322c;
--color-link-hover: #30ba78;
@@ -60,7 +65,8 @@
--gradient-border-start-color: var(--color-gray);
--gradient-border-end-color: transparent;
- --icon-size-m: 32px;
+ --header-icon-size: 32px;
+ --section-icon-size: 28px;
--header-block-size: auto;
--footer-block-size: auto;
diff --git a/web/src/client/index.js b/web/src/client/index.js
index 34a656972e..34942691c8 100644
--- a/web/src/client/index.js
+++ b/web/src/client/index.js
@@ -21,7 +21,7 @@
// @ts-check
-import { LanguageClient } from "./language";
+import { L10nClient } from "./l10n";
import { ManagerClient } from "./manager";
import { Monitor } from "./monitor";
import { SoftwareClient } from "./software";
@@ -37,7 +37,7 @@ const MANAGER_SERVICE = "org.opensuse.Agama.Manager1";
/**
* @typedef {object} InstallerClient
- * @property {LanguageClient} language - language client.
+ * @property {L10nClient} l10n - localization client.
* @property {ManagerClient} manager - manager client.
* @property {Monitor} monitor - service monitor.
* @property {NetworkClient} network - network client.
@@ -71,7 +71,7 @@ const MANAGER_SERVICE = "org.opensuse.Agama.Manager1";
* @return {InstallerClient}
*/
const createClient = (address = "unix:path=/run/agama/bus") => {
- const language = new LanguageClient(address);
+ const l10n = new L10nClient(address);
const manager = new ManagerClient(address);
const monitor = new Monitor(address, MANAGER_SERVICE);
const network = new NetworkClient();
@@ -122,7 +122,7 @@ const createClient = (address = "unix:path=/run/agama/bus") => {
};
return {
- language,
+ l10n,
manager,
monitor,
network,
diff --git a/web/src/client/l10n.js b/web/src/client/l10n.js
new file mode 100644
index 0000000000..cf9154a6dd
--- /dev/null
+++ b/web/src/client/l10n.js
@@ -0,0 +1,263 @@
+/*
+ * Copyright (c) [2022-2023] SUSE LLC
+ *
+ * All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2 of the GNU General Public License as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, contact SUSE LLC.
+ *
+ * To contact SUSE LLC about this file by physical or electronic mail, you may
+ * find current contact information at www.suse.com.
+ */
+
+// @ts-check
+import DBusClient from "./dbus";
+import { timezoneUTCOffset } from "~/utils";
+
+const LOCALE_SERVICE = "org.opensuse.Agama1";
+const LOCALE_IFACE = "org.opensuse.Agama1.Locale";
+const LOCALE_PATH = "/org/opensuse/Agama1/Locale";
+
+/**
+ * @typedef {object} Timezone
+ * @property {string} id - Timezone id (e.g., "Atlantic/Canary").
+ * @property {Array} parts - Name of the timezone parts (e.g., ["Atlantic", "Canary"]).
+ * @property {number} utcOffset - UTC offset.
+ */
+
+/**
+ * @typedef {object} Locale
+ * @property {string} id - Language id (e.g., "en_US").
+ * @property {string} name - Language name (e.g., "English").
+ * @property {string} territory - Territory name (e.g., "United States").
+ */
+
+/**
+ * @typedef {object} Keymap
+ * @property {string} id - Keyboard id (e.g., "us").
+ * @property {string} name - Keyboard name (e.g., "English (US)").
+ */
+
+/**
+ * Manages localization.
+ */
+class L10nClient {
+ /**
+ * @param {string|undefined} address - D-Bus address; if it is undefined, it uses the system bus.
+ */
+ constructor(address = undefined) {
+ this.client = new DBusClient(LOCALE_SERVICE, address);
+ }
+
+ /**
+ * Selected locale to translate the installer UI.
+ *
+ * @return {Promise} Locale id.
+ */
+ async getUILocale() {
+ const proxy = await this.client.proxy(LOCALE_IFACE);
+ return proxy.UILocale;
+ }
+
+ /**
+ * Sets the locale to translate the installer UI.
+ *
+ * @param {String} id - Locale id.
+ * @return {Promise}
+ */
+ async setUILocale(id) {
+ const proxy = await this.client.proxy(LOCALE_IFACE);
+ proxy.UILocale = id;
+ }
+
+ /**
+ * All possible timezones for the target system.
+ *
+ * @return {Promise>}
+ */
+ async timezones() {
+ const proxy = await this.client.proxy(LOCALE_IFACE);
+ const timezones = await proxy.ListTimezones();
+
+ return timezones.map(this.buildTimezone);
+ }
+
+ /**
+ * Timezone selected for the target system.
+ *
+ * @return {Promise} Id of the timezone.
+ */
+ async getTimezone() {
+ const proxy = await this.client.proxy(LOCALE_IFACE);
+ return proxy.Timezone;
+ }
+
+ /**
+ * Sets the timezone for the target system.
+ *
+ * @param {string} id - Id of the timezone.
+ * @return {Promise}
+ */
+ async setTimezone(id) {
+ const proxy = await this.client.proxy(LOCALE_IFACE);
+ proxy.Timezone = id;
+ }
+
+ /**
+ * Available locales to install in the target system.
+ *
+ * @return {Promise>}
+ */
+ async locales() {
+ const proxy = await this.client.proxy(LOCALE_IFACE);
+ const locales = await proxy.ListLocales();
+
+ return locales.map(this.buildLocale);
+ }
+
+ /**
+ * Locales selected to install in the target system.
+ *
+ * @return {Promise>} Ids of the locales.
+ */
+ async getLocales() {
+ const proxy = await this.client.proxy(LOCALE_IFACE);
+ return proxy.Locales;
+ }
+
+ /**
+ * Sets the locales to install in the target system.
+ *
+ * @param {Array} ids - Ids of the locales.
+ * @return {Promise}
+ */
+ async setLocales(ids) {
+ const proxy = await this.client.proxy(LOCALE_IFACE);
+ proxy.Locales = ids;
+ }
+
+ /**
+ * Available keymaps to install in the target system.
+ *
+ * Note that name is localized to the current selected UI language:
+ * { id: "es", name: "Spanish (ES)" }
+ *
+ * @return {Promise>}
+ */
+ async keymaps() {
+ const proxy = await this.client.proxy(LOCALE_IFACE);
+ const keymaps = await proxy.ListKeymaps();
+
+ return keymaps.map(this.buildKeymap);
+ }
+
+ /**
+ * Keymap selected to install in the target system.
+ *
+ * @return {Promise} Id of the keymap.
+ */
+ async getKeymap() {
+ const proxy = await this.client.proxy(LOCALE_IFACE);
+ return proxy.Keymap;
+ }
+
+ /**
+ * Sets the keymap to install in the target system.
+ *
+ * @param {string} id - Id of the keymap.
+ * @return {Promise}
+ */
+ async setKeymap(id) {
+ const proxy = await this.client.proxy(LOCALE_IFACE);
+
+ proxy.Keymap = id;
+ }
+
+ /**
+ * Register a callback to run when Timezone D-Bus property changes.
+ *
+ * @param {(timezone: string) => void} handler - Function to call when Timezone changes.
+ * @return {import ("./dbus").RemoveFn} Function to disable the callback.
+ */
+ onTimezoneChange(handler) {
+ return this.client.onObjectChanged(LOCALE_PATH, LOCALE_IFACE, changes => {
+ if ("Timezone" in changes) {
+ const id = changes.Timezone.v;
+ handler(id);
+ }
+ });
+ }
+
+ /**
+ * Register a callback to run when Locales D-Bus property changes.
+ *
+ * @param {(language: string) => void} handler - Function to call when Locales changes.
+ * @return {import ("./dbus").RemoveFn} Function to disable the callback.
+ */
+ onLocalesChange(handler) {
+ return this.client.onObjectChanged(LOCALE_PATH, LOCALE_IFACE, changes => {
+ if ("Locales" in changes) {
+ const selectedIds = changes.Locales.v;
+ handler(selectedIds);
+ }
+ });
+ }
+
+ /**
+ * Register a callback to run when Keymap D-Bus property changes.
+ *
+ * @param {(language: string) => void} handler - Function to call when Keymap changes.
+ * @return {import ("./dbus").RemoveFn} Function to disable the callback.
+ */
+ onKeymapChange(handler) {
+ return this.client.onObjectChanged(LOCALE_PATH, LOCALE_IFACE, changes => {
+ if ("Keymap" in changes) {
+ const id = changes.Keymap.v;
+ handler(id);
+ }
+ });
+ }
+
+ /**
+ * @private
+ *
+ * @param {[string, Array]} dbusTimezone
+ * @returns {Timezone}
+ */
+ buildTimezone([id, parts]) {
+ const utcOffset = timezoneUTCOffset(id);
+
+ return ({ id, parts, utcOffset });
+ }
+
+ /**
+ * @private
+ *
+ * @param {[string, string, string]} dbusLocale
+ * @returns {Locale}
+ */
+ buildLocale([id, name, territory]) {
+ return ({ id, name, territory });
+ }
+
+ /**
+ * @private
+ *
+ * @param {[string, string]} dbusKeymap
+ * @returns {Keymap}
+ */
+ buildKeymap([id, name]) {
+ return ({ id, name });
+ }
+}
+
+export { L10nClient };
diff --git a/web/src/client/language.test.js b/web/src/client/l10n.test.js
similarity index 56%
rename from web/src/client/language.test.js
rename to web/src/client/l10n.test.js
index ebe05fb712..590d177143 100644
--- a/web/src/client/language.test.js
+++ b/web/src/client/l10n.test.js
@@ -1,5 +1,5 @@
/*
- * Copyright (c) [2022] SUSE LLC
+ * Copyright (c) [2022-2023] SUSE LLC
*
* All Rights Reserved.
*
@@ -23,34 +23,40 @@
// cspell:ignore Cestina
import DBusClient from "./dbus";
-import { LanguageClient } from "./language";
+import { L10nClient } from "./l10n";
jest.mock("./dbus");
-const langProxy = {
- wait: jest.fn(),
- SupportedLocales: ["es_ES.UTF-8", "en_US.UTF-8"],
- LabelsForLocales: jest.fn().mockResolvedValue(
- [[["Spanish", "Spain"], ["Español", "España"]], [['English', 'United States'], ['English', 'United States']]]
+const L10N_IFACE = "org.opensuse.Agama1.Locale";
+
+const l10nProxy = {
+ ListLocales: jest.fn().mockResolvedValue(
+ [
+ ["es_ES.UTF-8", "Spanish", "Spain"],
+ ["en_US.UTF-8", "English", "United States"]
+ ]
),
};
-jest.mock("./dbus");
-
beforeEach(() => {
// @ts-ignore
DBusClient.mockImplementation(() => {
- return { proxy: () => langProxy };
+ return {
+ proxy: (iface) => {
+ if (iface === L10N_IFACE) return l10nProxy;
+ }
+ };
});
});
-describe("#getLanguages", () => {
- it("returns the list of available languages", async () => {
- const client = new LanguageClient();
- const availableLanguages = await client.getLanguages();
- expect(availableLanguages).toEqual([
- { id: "es_ES.UTF-8", name: "Spanish" },
- { id: "en_US.UTF-8", name: "English" }
+describe("#locales", () => {
+ it("returns the list of available locales", async () => {
+ const client = new L10nClient();
+ const locales = await client.locales();
+
+ expect(locales).toEqual([
+ { id: "es_ES.UTF-8", name: "Spanish", territory: "Spain" },
+ { id: "en_US.UTF-8", name: "English", territory: "United States" }
]);
});
});
diff --git a/web/src/client/language.js b/web/src/client/language.js
deleted file mode 100644
index e40ab1164c..0000000000
--- a/web/src/client/language.js
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * Copyright (c) [2022] SUSE LLC
- *
- * All Rights Reserved.
- *
- * This program is free software; you can redistribute it and/or modify it
- * under the terms of version 2 of the GNU General Public License as published
- * by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
- * more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, contact SUSE LLC.
- *
- * To contact SUSE LLC about this file by physical or electronic mail, you may
- * find current contact information at www.suse.com.
- */
-
-// @ts-check
-import DBusClient from "./dbus";
-
-const LANGUAGE_SERVICE = "org.opensuse.Agama1";
-const LANGUAGE_IFACE = "org.opensuse.Agama1.Locale";
-const LANGUAGE_PATH = "/org/opensuse/Agama1/Locale";
-
-/**
- * @typedef {object} Language
- * @property {string} id - Language ID (e.g., "en_US")
- * @property {string} name - Language name (e.g., "English (US)")
- */
-
-/**
- * Allows getting the list of available languages and selecting one for installation.
- */
-class LanguageClient {
- /**
- * @param {string|undefined} address - D-Bus address; if it is undefined, it uses the system bus.
- */
- constructor(address = undefined) {
- this.client = new DBusClient(LANGUAGE_SERVICE, address);
- }
-
- /**
- * Returns the list of available languages
- *
- * @return {Promise>}
- */
- async getLanguages() {
- const proxy = await this.client.proxy(LANGUAGE_IFACE);
- const locales = proxy.SupportedLocales;
- const labels = await proxy.LabelsForLocales();
- return locales.map((locale, index) => {
- // labels structure is [[en_lang, en_territory], [native_lang, native_territory]]
- const [[en_lang,], [,]] = labels[index];
- return { id: locale, name: en_lang };
- });
- }
-
- /**
- * Returns the languages selected for installation
- *
- * @return {Promise>} IDs of the selected languages
- */
- async getSelectedLanguages() {
- const proxy = await this.client.proxy(LANGUAGE_IFACE);
- return proxy.Locales;
- }
-
- /**
- * Set the languages to install
- *
- * @param {string} langIDs - Identifier of languages to install
- * @return {Promise}
- */
- async setLanguages(langIDs) {
- const proxy = await this.client.proxy(LANGUAGE_IFACE);
- proxy.Locales = langIDs;
- }
-
- /**
- * Returns the current backend locale
- *
- * @return {Promise} the locale string
- */
- async getUILanguage() {
- const proxy = await this.client.proxy(LANGUAGE_IFACE);
- return proxy.UILocale;
- }
-
- /**
- * Set the backend language
- *
- * @param {String} lang the locale string
- * @return {Promise}
- */
- async setUILanguage(lang) {
- const proxy = await this.client.proxy(LANGUAGE_IFACE);
- proxy.UILocale = lang;
- }
-
- /**
- * Register a callback to run when properties in the Language object change
- *
- * @param {(language: string) => void} handler - function to call when the language change
- * @return {import ("./dbus").RemoveFn} function to disable the callback
- */
- onLanguageChange(handler) {
- return this.client.onObjectChanged(LANGUAGE_PATH, LANGUAGE_IFACE, changes => {
- const selected = changes.Locales.v[0];
- handler(selected);
- });
- }
-}
-
-export { LanguageClient };
diff --git a/web/src/client/manager.js b/web/src/client/manager.js
index ee847521fc..fcd223c505 100644
--- a/web/src/client/manager.js
+++ b/web/src/client/manager.js
@@ -86,7 +86,7 @@ class ManagerBaseClient {
*/
async fetchLogs() {
const proxy = await this.client.proxy(MANAGER_IFACE);
- const path = await proxy.CollectLogs("root");
+ const path = await proxy.CollectLogs();
const file = cockpit.file(path, { binary: true });
return file.read();
}
diff --git a/web/src/client/storage.js b/web/src/client/storage.js
index 9f771b596c..7290b74bc3 100644
--- a/web/src/client/storage.js
+++ b/web/src/client/storage.js
@@ -228,8 +228,10 @@ class ProposalManager {
* @property {string} bootDevice
* @property {string} encryptionPassword
* @property {boolean} lvm
+ * @property {string} spacePolicy
* @property {string[]} systemVGDevices
* @property {Volume[]} volumes
+ * @property {StorageDevice[]} installationDevices
*
* @typedef {object} Volume
* @property {string} mountPath
@@ -311,6 +313,8 @@ class ProposalManager {
if (!proxy) return undefined;
+ const systemDevices = await this.system.getDevices();
+
const buildResult = (proxy) => {
const buildAction = dbusAction => {
return {
@@ -320,13 +324,33 @@ class ProposalManager {
};
};
+ const buildInstallationDevices = (proxy, devices) => {
+ const findDevice = (devices, name) => {
+ const device = devices.find(d => d.name === name);
+
+ if (device === undefined) console.log("D-Bus object not found: ", name);
+
+ return device;
+ };
+
+ const names = proxy.SystemVGDevices.filter(n => n !== proxy.BootDevice).concat([proxy.BootDevice]);
+ // #findDevice returns undefined if no device is found with the given name.
+ return names.map(dev => findDevice(devices, dev)).filter(dev => dev !== undefined);
+ };
+
return {
settings: {
bootDevice: proxy.BootDevice,
lvm: proxy.LVM,
+ spacePolicy: proxy.SpacePolicy,
systemVGDevices: proxy.SystemVGDevices,
encryptionPassword: proxy.EncryptionPassword,
volumes: proxy.Volumes.map(this.buildVolume),
+ // NOTE: strictly speaking, installation devices does not belong to the settings. It
+ // should be a separate method instead of an attribute in the settings object.
+ // Nevertheless, it was added here for simplicity and to avoid passing more props in some
+ // react components. Please, do not use settings as a jumble.
+ installationDevices: buildInstallationDevices(proxy, systemDevices)
},
actions: proxy.Actions.map(buildAction)
};
@@ -341,7 +365,7 @@ class ProposalManager {
* @param {ProposalSettings} settings
* @returns {Promise} 0 on success, 1 on failure
*/
- async calculate({ bootDevice, encryptionPassword, lvm, systemVGDevices, volumes }) {
+ async calculate({ bootDevice, encryptionPassword, lvm, spacePolicy, systemVGDevices, volumes }) {
const dbusVolume = (volume) => {
return removeUndefinedCockpitProperties({
MountPath: { t: "s", v: volume.mountPath },
@@ -358,6 +382,7 @@ class ProposalManager {
BootDevice: { t: "s", v: bootDevice },
EncryptionPassword: { t: "s", v: encryptionPassword },
LVM: { t: "b", v: lvm },
+ SpacePolicy: { t: "s", v: spacePolicy },
SystemVGDevices: { t: "as", v: systemVGDevices },
Volumes: { t: "aa{sv}", v: volumes?.map(dbusVolume) }
});
diff --git a/web/src/client/storage.test.js b/web/src/client/storage.test.js
index 739c5b52a2..8c3826221f 100644
--- a/web/src/client/storage.test.js
+++ b/web/src/client/storage.test.js
@@ -155,6 +155,7 @@ const contexts = {
LVM: true,
SystemVGDevices: ["/dev/sda", "/dev/sdb"],
EncryptionPassword: "00000",
+ SpacePolicy: "delete",
Volumes: [
{
MountPath: { t: "s", v: "/" },
@@ -811,6 +812,7 @@ describe("#proposal", () => {
describe("if there is a proposal", () => {
beforeEach(() => {
+ contexts.withSystemDevices();
contexts.withProposal();
client = new StorageClient();
});
@@ -818,11 +820,12 @@ describe("#proposal", () => {
it("returns the proposal settings and actions", async () => {
const { settings, actions } = await client.proposal.getResult();
- expect(settings).toStrictEqual({
+ expect(settings).toMatchObject({
bootDevice: "/dev/sda",
lvm: true,
systemVGDevices: ["/dev/sda", "/dev/sdb"],
encryptionPassword: "00000",
+ spacePolicy: "delete",
volumes: [
{
mountPath: "/",
@@ -861,6 +864,10 @@ describe("#proposal", () => {
]
});
+ expect(settings.installationDevices.map(d => d.name).sort()).toStrictEqual(
+ ["/dev/sda", "/dev/sdb"].sort()
+ );
+
expect(actions).toStrictEqual([
{ text: "Mount /dev/sdb1 as root", subvol: false, delete: false }
]);
diff --git a/web/src/components/core/InstallButton.jsx b/web/src/components/core/InstallButton.jsx
index fb5eb9af70..55e23fdd94 100644
--- a/web/src/components/core/InstallButton.jsx
+++ b/web/src/components/core/InstallButton.jsx
@@ -131,9 +131,12 @@ const InstallButton = ({ onClick }) => {
const open = async () => {
if (onClick) onClick();
const canInstall = await client.manager.canInstall();
- if (canInstall) setHasIssues(await client.issues.any());
setIsOpen(true);
setError(!canInstall);
+ if (canInstall) {
+ const issues = await client.issues();
+ setHasIssues(Object.values(issues).some(n => n.length > 0));
+ }
};
const close = () => setIsOpen(false);
const install = () => client.manager.startInstallation();
diff --git a/web/src/components/core/InstallButton.test.jsx b/web/src/components/core/InstallButton.test.jsx
index 08858affaf..9ee5bfc039 100644
--- a/web/src/components/core/InstallButton.test.jsx
+++ b/web/src/components/core/InstallButton.test.jsx
@@ -31,19 +31,18 @@ jest.mock("~/client", () => ({
createClient: jest.fn()
}));
-describe("when the button is clicked and there are not errors", () => {
- let hasIssues = false;
+let issues;
+describe("when the button is clicked and there are not errors", () => {
beforeEach(() => {
+ issues = {};
createClient.mockImplementation(() => {
return {
manager: {
startInstallation: startInstallationFn,
canInstall: () => Promise.resolve(true),
},
- issues: {
- any: () => Promise.resolve(hasIssues)
- }
+ issues: jest.fn().mockResolvedValue({ ...issues })
};
});
});
@@ -74,7 +73,16 @@ describe("when the button is clicked and there are not errors", () => {
describe("if there are issues", () => {
beforeEach(() => {
- hasIssues = true;
+ issues = {
+ product: [],
+ storage: [
+ { description: "storage issue 1", details: "Details 1", source: "system", severity: "warn" },
+ { description: "storage issue 2", details: "Details 2", source: "config", severity: "error" }
+ ],
+ software: [
+ { description: "software issue 1", details: "Details 1", source: "system", severity: "warn" }
+ ]
+ };
});
it("shows a link to go to the issues page", async () => {
@@ -87,10 +95,6 @@ describe("when the button is clicked and there are not errors", () => {
});
describe("if there are not issues", () => {
- beforeEach(() => {
- hasIssues = false;
- });
-
it("does not show a link to go to the issues page", async () => {
const { user } = installerRender( );
const button = await screen.findByRole("button", { name: "Install" });
diff --git a/web/src/components/core/ListSearch.jsx b/web/src/components/core/ListSearch.jsx
new file mode 100644
index 0000000000..1b53ce73e9
--- /dev/null
+++ b/web/src/components/core/ListSearch.jsx
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) [2023] SUSE LLC
+ *
+ * All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2 of the GNU General Public License as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, contact SUSE LLC.
+ *
+ * To contact SUSE LLC about this file by physical or electronic mail, you may
+ * find current contact information at www.suse.com.
+ */
+
+import React from "react";
+
+import { _ } from "~/i18n";
+import { noop, useDebounce } from "~/utils";
+
+const search = (elements, term) => {
+ const value = term.toLowerCase();
+
+ const match = (element) => {
+ return Object.values(element)
+ .join('')
+ .toLowerCase()
+ .includes(value);
+ };
+
+ return elements.filter(match);
+};
+
+/**
+ * Input field for searching in a given list of elements.
+ * @component
+ *
+ * @param {object} props
+ * @param {string} [props.placeholder]
+ * @param {object[]} [props.elements] - List of elements in which to search.
+ * @param {(elements: object[]) => void} - Callback to be called with the filtered list of elements.
+ */
+export default function ListSearch({
+ placeholder = _("Search"),
+ elements = [],
+ onChange: onChangeProp = noop
+}) {
+ const searchHandler = useDebounce(term => onChangeProp(search(elements, term)), 500);
+
+ const onChange = (e) => searchHandler(e.target.value);
+
+ return (
+
+ );
+}
diff --git a/web/src/components/core/ListSearch.test.jsx b/web/src/components/core/ListSearch.test.jsx
new file mode 100644
index 0000000000..6ea805cc59
--- /dev/null
+++ b/web/src/components/core/ListSearch.test.jsx
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) [2023] SUSE LLC
+ *
+ * All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2 of the GNU General Public License as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, contact SUSE LLC.
+ *
+ * To contact SUSE LLC about this file by physical or electronic mail, you may
+ * find current contact information at www.suse.com.
+ */
+
+import React, { useState } from "react";
+import { screen, waitFor } from "@testing-library/react";
+import { plainRender } from "~/test-utils";
+import { ListSearch } from "~/components/core";
+
+const fruits = [
+ { name: "Apple", color: "red", size: "medium" },
+ { name: "Banana", color: "yellow", size: "medium" },
+ { name: "Grape", color: "green", size: "small" },
+ { name: "Pear", color: "green", size: "medium" }
+];
+
+const FruitList = ({ fruits }) => {
+ const [filteredFruits, setFilteredFruits] = useState(fruits);
+
+ return (
+ <>
+
+
+ {filteredFruits.map((f, i) => {f.name} )}
+
+ >
+ );
+};
+
+it("searches for elements matching the given term (case-insensitive)", async () => {
+ const { user } = plainRender( );
+
+ const searchInput = screen.getByRole("search");
+
+ // Search for "medium" size fruit
+ await user.type(searchInput, "medium");
+ await waitFor(() => (
+ expect(screen.queryByRole("option", { name: /grape/ })).not.toBeInTheDocument())
+ );
+ screen.getByRole("option", { name: "Apple" });
+ screen.getByRole("option", { name: "Banana" });
+ screen.getByRole("option", { name: "Pear" });
+
+ // Search for "green" fruit
+ await user.clear(searchInput);
+ await user.type(searchInput, "Green");
+ await waitFor(() => (
+ expect(screen.queryByRole("option", { name: "Apple" })).not.toBeInTheDocument())
+ );
+ await waitFor(() => (
+ expect(screen.queryByRole("option", { name: "Banana" })).not.toBeInTheDocument())
+ );
+ screen.getByRole("option", { name: "Grape" });
+ screen.getByRole("option", { name: "Pear" });
+
+ // Search for known fruit
+ await user.clear(searchInput);
+ await user.type(searchInput, "ap");
+ await waitFor(() => (
+ expect(screen.queryByRole("option", { name: "Banana" })).not.toBeInTheDocument())
+ );
+ await waitFor(() => (
+ expect(screen.queryByRole("option", { name: "Pear" })).not.toBeInTheDocument())
+ );
+ screen.getByRole("option", { name: "Apple" });
+ screen.getByRole("option", { name: "Grape" });
+
+ // Search for unknown fruit
+ await user.clear(searchInput);
+ await user.type(searchInput, "tomato");
+ await waitFor(() => (
+ expect(screen.queryByRole("option", { name: "Apple" })).not.toBeInTheDocument())
+ );
+ await waitFor(() => (
+ expect(screen.queryByRole("option", { name: "Banana" })).not.toBeInTheDocument())
+ );
+ await waitFor(() => (
+ expect(screen.queryByRole("option", { name: "Grape" })).not.toBeInTheDocument())
+ );
+ await waitFor(() => (
+ expect(screen.queryByRole("option", { name: "Pear" })).not.toBeInTheDocument())
+ );
+});
diff --git a/web/src/components/core/LogsButton.jsx b/web/src/components/core/LogsButton.jsx
index 1c7987ce13..a4140931fb 100644
--- a/web/src/components/core/LogsButton.jsx
+++ b/web/src/components/core/LogsButton.jsx
@@ -27,7 +27,7 @@ import { Alert, Button } from "@patternfly/react-core";
import { Icon } from "~/components/layout";
import { _ } from "~/i18n";
-const FILENAME = "y2logs.tar.xz";
+const FILENAME = "agama-installation-logs.tar.bzip2";
const FILETYPE = "application/x-xz";
/**
diff --git a/web/src/components/core/LogsButton.test.jsx b/web/src/components/core/LogsButton.test.jsx
index 0b643d3be0..cfb9c3ccb3 100644
--- a/web/src/components/core/LogsButton.test.jsx
+++ b/web/src/components/core/LogsButton.test.jsx
@@ -119,7 +119,7 @@ describe("LogsButton", () => {
// And test what we're looking for
expect(document.createElement).toHaveBeenCalledWith('a');
expect(anchorMock).toHaveAttribute("href", "fake-blob-url");
- expect(anchorMock).toHaveAttribute("download", expect.stringMatching(/y2logs/));
+ expect(anchorMock).toHaveAttribute("download", expect.stringMatching(/agama-installation-logs/));
expect(anchorMock.click).toHaveBeenCalled();
// Be polite and restore document.createElement function,
diff --git a/web/src/components/core/Page.jsx b/web/src/components/core/Page.jsx
index 227182e70e..a9c0a2ece2 100644
--- a/web/src/components/core/Page.jsx
+++ b/web/src/components/core/Page.jsx
@@ -87,7 +87,7 @@ export default function Page({
return (
<>
{ title && {title} }
- { icon && }
+ { icon && }
{ action ||
diff --git a/web/src/components/core/Section.jsx b/web/src/components/core/Section.jsx
index 99a219eb6c..d5d94f5fee 100644
--- a/web/src/components/core/Section.jsx
+++ b/web/src/components/core/Section.jsx
@@ -77,12 +77,14 @@ export default function Section({
const Header = () => {
if (!title?.trim()) return;
- const header = !path?.trim() ? <>{title}> : {title};
+ const iconName = loading ? "loading" : icon;
+ const headerIcon = iconName ? : null;
+ const headerText = !path?.trim() ? title : {title};
return (
<>
-
-
+ {headerIcon}
+
>
);
};
diff --git a/web/src/components/core/Section.test.jsx b/web/src/components/core/Section.test.jsx
index 2a0ca85dc5..da9c53bfd8 100644
--- a/web/src/components/core/Section.test.jsx
+++ b/web/src/components/core/Section.test.jsx
@@ -49,6 +49,8 @@ describe("Section", () => {
const { container } = plainRender();
const icon = container.querySelector("svg");
expect(icon).toBeNull();
+ // Check that component was not mounted with 'undefined'
+ expect(console.error).not.toHaveBeenCalled();
});
it("does not render an icon if not valid icon name is given", () => {
@@ -70,12 +72,6 @@ describe("Section", () => {
const icon = container.querySelector("svg");
expect(icon).toBeNull();
});
-
- it("does not render the loading icon", () => {
- const { container } = plainRender();
- const icon = container.querySelector("svg");
- expect(icon).toBeNull();
- });
});
describe("when aria-label is given", () => {
@@ -166,6 +162,7 @@ describe("Section", () => {
expect(icon).toBeNull();
});
});
+
describe("when path is given", () => {
it("renders a link for navigating to it", async () => {
installerRender();
diff --git a/web/src/components/core/index.js b/web/src/components/core/index.js
index 73593f96ed..5a3d808cf0 100644
--- a/web/src/components/core/index.js
+++ b/web/src/components/core/index.js
@@ -39,6 +39,7 @@ export { default as InstallButton } from "./InstallButton";
export { default as IssuesLink } from "./IssuesLink";
export { default as IssuesPage } from "./IssuesPage";
export { default as SectionSkeleton } from "./SectionSkeleton";
+export { default as ListSearch } from "./ListSearch";
export { default as LogsButton } from "./LogsButton";
export { default as FileViewer } from "./FileViewer";
export { default as RowActions } from "./RowActions";
diff --git a/web/src/components/l10n/KeymapSelector.jsx b/web/src/components/l10n/KeymapSelector.jsx
new file mode 100644
index 0000000000..af4eeb057b
--- /dev/null
+++ b/web/src/components/l10n/KeymapSelector.jsx
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) [2023] SUSE LLC
+ *
+ * All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2 of the GNU General Public License as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, contact SUSE LLC.
+ *
+ * To contact SUSE LLC about this file by physical or electronic mail, you may
+ * find current contact information at www.suse.com.
+ */
+
+import React, { useState } from "react";
+
+import { _ } from "~/i18n";
+import { ListSearch } from "~/components/core";
+import { noop } from "~/utils";
+
+/**
+ * @typedef {import ("~/clients/l10n").Keymap} Keymap
+ */
+
+const ListBox = ({ children, ...props }) => {
+ return (
+
+ );
+};
+
+const ListBoxItem = ({ isSelected, children, onClick, ...props }) => {
+ if (isSelected) props['aria-selected'] = true;
+
+ return (
+
+ {children}
+
+ );
+};
+
+/**
+ * Content for a keymap item
+ * @component
+ *
+ * @param {Object} props
+ * @param {Keymap} props.keymap
+ */
+const KeymapItem = ({ keymap }) => {
+ return (
+ <>
+ {keymap.name}
+ {keymap.id}
+ >
+ );
+};
+
+/**
+ * Component for selecting a keymap.
+ * @component
+ *
+ * @param {Object} props
+ * @param {string} [props.value] - Id of the currently selected keymap.
+ * @param {Keymap[]} [props.keymap] - Keymaps for selection.
+ * @param {(id: string) => void} [props.onChange] - Callback to be called when the selected keymap
+ * changes.
+ */
+export default function KeymapSelector({ value, keymaps = [], onChange = noop }) {
+ const [filteredKeymaps, setFilteredKeymaps] = useState(keymaps);
+
+ // TRANSLATORS: placeholder text for search input in the keyboard selector.
+ const helpSearch = _("Filter by description or keymap code");
+
+ return (
+ <>
+
+
+
+
+ { filteredKeymaps.map((keymap, index) => (
+ onChange(keymap.id)}
+ isSelected={keymap.id === value}
+ >
+
+
+ ))}
+
+ >
+ );
+}
diff --git a/web/src/components/l10n/L10nPage.jsx b/web/src/components/l10n/L10nPage.jsx
index 660c5bf90a..4121631782 100644
--- a/web/src/components/l10n/L10nPage.jsx
+++ b/web/src/components/l10n/L10nPage.jsx
@@ -1,5 +1,5 @@
/*
- * Copyright (c) [2022] SUSE LLC
+ * Copyright (c) [2022-2023] SUSE LLC
*
* All Rights Reserved.
*
@@ -19,72 +19,377 @@
* find language contact information at www.suse.com.
*/
-import React, { useState, useEffect } from "react";
-import { useCancellablePromise } from "~/utils";
+import React, { useState } from "react";
+import { Button, Form } from "@patternfly/react-core";
+import { sprintf } from "sprintf-js";
+
import { useInstallerClient } from "~/context/installer";
import { _ } from "~/i18n";
+import { If, Page, Popup, Section } from "~/components/core";
+import { KeymapSelector, LocaleSelector, TimezoneSelector } from "~/components/l10n";
+import { noop } from "~/utils";
+import { useL10n } from "~/context/l10n";
+import { useProduct } from "~/context/product";
+
+/**
+ * Popup for selecting a timezone.
+ * @component
+ *
+ * @param {object} props
+ * @param {function} props.onFinish - Callback to be called when the timezone is correctly selected.
+ * @param {function} props.onCancel - Callback to be called when the timezone selection is canceled.
+ */
+const TimezonePopup = ({ onFinish = noop, onCancel = noop }) => {
+ const { l10n } = useInstallerClient();
+ const { timezones, selectedTimezone } = useL10n();
+
+ const [timezoneId, setTimezoneId] = useState(selectedTimezone?.id);
+ const { selectedProduct } = useProduct();
+ const sortedTimezones = timezones.sort((timezone1, timezone2) => {
+ const timezoneText = t => t.parts.join('').toLowerCase();
+ return timezoneText(timezone1) > timezoneText(timezone2) ? 1 : -1;
+ });
+
+ const onSubmit = async (e) => {
+ e.preventDefault();
+
+ if (timezoneId !== selectedTimezone?.id) {
+ await l10n.setTimezone(timezoneId);
+ }
+
+ onFinish();
+ };
+
+ return (
+
+
+
+
+ {_("Accept")}
+
+
+
+
+ );
+};
+
+/**
+ * Button for opening the selection of timezone.
+ * @component
+ *
+ * @param {object} props
+ * @param {React.ReactNode} props.children - Button children.
+ */
+const TimezoneButton = ({ children }) => {
+ const [isPopupOpen, setIsPopupOpen] = useState(false);
-import {
- Form,
- FormGroup,
- FormSelect,
- FormSelectOption
-} from "@patternfly/react-core";
+ const openPopup = () => setIsPopupOpen(true);
+ const closePopup = () => setIsPopupOpen(false);
-import { Page } from "~/components/core";
+ return (
+ <>
+
+ {children}
+
-const initialState = {
- languages: [],
- language: ""
+
+ }
+ />
+ >
+ );
};
-export default function LanguageSelector() {
- const { language: client } = useInstallerClient();
- const { cancellablePromise } = useCancellablePromise();
- const [state, setState] = useState(initialState);
- const { languages, language } = state;
+/**
+ * Section for configuring timezone.
+ * @component
+ */
+const TimezoneSection = () => {
+ const { selectedTimezone } = useL10n();
- const updateState = ({ ...payload }) => {
- setState(previousState => ({ ...previousState, ...payload }));
+ return (
+
+
+ {(selectedTimezone?.parts || []).join(' - ')}
+ {_("Change time zone")}
+ >
+ }
+ else={
+ <>
+ {_("Time zone not selected yet")}
+ {_("Select time zone")}
+ >
+ }
+ />
+
+ );
+};
+
+/**
+ * Popup for selecting a locale.
+ * @component
+ *
+ * @param {object} props
+ * @param {function} props.onFinish - Callback to be called when the locale is correctly selected.
+ * @param {function} props.onCancel - Callback to be called when the locale selection is canceled.
+ */
+const LocalePopup = ({ onFinish = noop, onCancel = noop }) => {
+ const { l10n } = useInstallerClient();
+ const { locales, selectedLocales } = useL10n();
+ const { selectedProduct } = useProduct();
+ const [localeId, setLocaleId] = useState(selectedLocales[0]?.id);
+
+ const sortedLocales = locales.sort((locale1, locale2) => {
+ const localeText = l => [l.name, l.territory].join('').toLowerCase();
+ return localeText(locale1) > localeText(locale2) ? 1 : -1;
+ });
+
+ const onSubmit = async (e) => {
+ e.preventDefault();
+
+ const [locale] = selectedLocales;
+
+ if (localeId !== locale?.id) {
+ await l10n.setLocales([localeId]);
+ }
+
+ onFinish();
};
- useEffect(() => {
- const loadLanguages = async () => {
- const languages = await cancellablePromise(client.getLanguages());
- const [language] = await cancellablePromise(client.getSelectedLanguages());
- updateState({ languages, language });
- };
-
- loadLanguages().catch(console.error);
- }, [client, cancellablePromise]);
-
- const accept = () => client.setLanguages([language]);
-
- const LanguageField = ({ selected }) => {
- const selectorOptions = languages.map(lang => (
-
- ));
-
- return (
-
- updateState({ language: v })}
- >
- {selectorOptions}
-
-
- );
+ return (
+
+
+
+
+ {_("Accept")}
+
+
+
+
+ );
+};
+
+/**
+ * Button for opening the selection of locales.
+ * @component
+ *
+ * @param {object} props
+ * @param {React.ReactNode} props.children - Button children.
+ */
+const LocaleButton = ({ children }) => {
+ const [isPopupOpen, setIsPopupOpen] = useState(false);
+
+ const openPopup = () => setIsPopupOpen(true);
+ const closePopup = () => setIsPopupOpen(false);
+
+ return (
+ <>
+
+ {children}
+
+
+
+ }
+ />
+ >
+ );
+};
+
+/**
+ * Section for configuring locales.
+ * @component
+ */
+const LocaleSection = () => {
+ const { selectedLocales } = useL10n();
+
+ const [locale] = selectedLocales;
+
+ return (
+
+
+ {locale?.name} - {locale?.territory}
+ {_("Change language")}
+ >
+ }
+ else={
+ <>
+ {_("Language not selected yet")}
+ {_("Select language")}
+ >
+ }
+ />
+
+ );
+};
+
+/**
+ * Popup for selecting a keymap.
+ * @component
+ *
+ * @param {object} props
+ * @param {function} props.onFinish - Callback to be called when the keymap is correctly selected.
+ * @param {function} props.onCancel - Callback to be called when the keymap selection is canceled.
+ */
+const KeymapPopup = ({ onFinish = noop, onCancel = noop }) => {
+ const { l10n } = useInstallerClient();
+ const { keymaps, selectedKeymap } = useL10n();
+ const { selectedProduct } = useProduct();
+ const [keymapId, setKeymapId] = useState(selectedKeymap?.id);
+
+ const sortedKeymaps = keymaps.sort((k1, k2) => k1.name > k2.name ? 1 : -1);
+
+ const onSubmit = async (e) => {
+ e.preventDefault();
+
+ if (keymapId !== selectedKeymap?.id) {
+ await l10n.setKeymap(keymapId);
+ }
+
+ onFinish();
};
return (
- // TRANSLATORS: page header
-
-
- {_("Accept")}
+ {_("Close")}
diff --git a/web/src/components/product/ProductPage.test.jsx b/web/src/components/product/ProductPage.test.jsx
index 11a89fba2c..618d3e8994 100644
--- a/web/src/components/product/ProductPage.test.jsx
+++ b/web/src/components/product/ProductPage.test.jsx
@@ -247,7 +247,7 @@ describe("when the button for changing the product is clicked", () => {
const popup = await screen.findByRole("dialog");
within(popup).getByText(/must be deregistered/);
- const accept = within(popup).getByRole("button", { name: "Accept" });
+ const accept = within(popup).getByRole("button", { name: "Close" });
await user.click(accept);
expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
diff --git a/web/src/components/storage/ProposalSettingsSection.jsx b/web/src/components/storage/ProposalSettingsSection.jsx
index 3142d9c515..0321ab042c 100644
--- a/web/src/components/storage/ProposalSettingsSection.jsx
+++ b/web/src/components/storage/ProposalSettingsSection.jsx
@@ -29,7 +29,11 @@ import {
import { _ } from "~/i18n";
import { If, PasswordAndConfirmationInput, Section, Popup } from "~/components/core";
-import { DeviceList, DeviceSelector, ProposalVolumes } from "~/components/storage";
+import {
+ DeviceList, DeviceSelector,
+ ProposalVolumes,
+ SpacePolicyButton, SpacePolicySelector, SpacePolicyDisksHint
+} from "~/components/storage";
import { deviceLabel } from '~/components/storage/utils';
import { Icon } from "~/components/layout";
import { noop } from "~/utils";
@@ -228,7 +232,7 @@ const LVMSettingsForm = ({
const BootDevice = () => {
const bootDevice = devices.find(d => d.name === settings.bootDevice);
- return ;
+ return ;
};
return (
@@ -509,6 +513,103 @@ const EncryptionPasswordField = ({
);
};
+/**
+ * Form for configuring the space policy.
+ * @component
+ *
+ * @param {object} props
+ * @param {string} props.id - Form ID.
+ * @param {ProposalSettings} props.settings - Settings used for calculating a proposal.
+ * @param {onSubmitFn} [props.onSubmit=noop] - On submit callback.
+ *
+ * @callback onSubmitFn
+ * @param {string} policy - Name of the selected policy.
+ */
+const SpacePolicyForm = ({
+ id,
+ policy,
+ onSubmit: onSubmitProp = noop
+}) => {
+ const [spacePolicy, setSpacePolicy] = useState(policy);
+
+ const onSubmit = (e) => {
+ e.preventDefault();
+ onSubmitProp(spacePolicy);
+ };
+
+ return (
+
+
+
+ );
+};
+
+/**
+ * Allows to select SpacePolicy.
+ * @component
+ *
+ * @param {object} props
+ * @param {ProposalSettings} props.settings - Settings used for calculating a proposal.
+ * @param {boolean} [props.isLoading=false] - Whether to show the selector as loading.
+ * @param {onChangeFn} [props.onChange=noop] - On change callback.
+ *
+ * @callback onChangeFn
+ * @param {string} policy
+ */
+const SpacePolicyField = ({
+ settings,
+ isLoading = false,
+ onChange = noop
+}) => {
+ const [isFormOpen, setIsFormOpen] = useState(false);
+ const [spacePolicy, setSpacePolicy] = useState(settings.spacePolicy);
+
+ const openForm = () => setIsFormOpen(true);
+ const closeForm = () => setIsFormOpen(false);
+
+ const onSubmitForm = (policy) => {
+ onChange(policy);
+ setSpacePolicy(policy);
+ closeForm();
+ };
+
+ if (isLoading) return ;
+
+ const description = _("Select how to make free space in the disks selected for allocating the \
+ file systems.");
+
+ return (
+
+ {/* TRANSLATORS: To be completed with the rest of a sentence like "deleting all content" */}
+
{_("Find space")}
+
+
+
+
+
+
+
+
+ {_("Accept")}
+
+
+
+
+
+ );
+};
+
/**
* Section for editing the proposal settings
* @component
@@ -546,6 +647,10 @@ export default function ProposalSettingsSection({
onChange({ encryptionPassword: password });
};
+ const changeSpacePolicy = (policy) => {
+ onChange({ spacePolicy: policy });
+ };
+
const changeVolumes = (volumes) => {
onChange({ volumes });
};
@@ -581,6 +686,11 @@ export default function ProposalSettingsSection({
isLoading={isLoading}
onChange={changeVolumes}
/>
+
);
}
diff --git a/web/src/components/storage/ProposalSettingsSection.test.jsx b/web/src/components/storage/ProposalSettingsSection.test.jsx
index 9652a1bc9d..ec9d1b08cb 100644
--- a/web/src/components/storage/ProposalSettingsSection.test.jsx
+++ b/web/src/components/storage/ProposalSettingsSection.test.jsx
@@ -20,7 +20,7 @@
*/
import React from "react";
-import { screen, within } from "@testing-library/react";
+import { screen, waitFor, within } from "@testing-library/react";
import { plainRender } from "~/test-utils";
import { ProposalSettingsSection } from "~/components/storage";
@@ -503,3 +503,167 @@ describe("Encryption field", () => {
});
});
});
+
+describe("Space policy field", () => {
+ beforeEach(() => {
+ props.settings = { installationDevices: [vda] };
+ });
+
+ describe("if there is no space policy", () => {
+ beforeEach(() => {
+ props.settings.spacePolicy = undefined;
+ });
+
+ it("renders loading content", async () => {
+ plainRender( );
+
+ await waitFor(() => (
+ expect(screen.queryByText(/Find space/)).not.toBeInTheDocument())
+ );
+ screen.getAllByText("PFSkeleton");
+ });
+ });
+
+ describe("if the space policy is set to 'delete'", () => {
+ beforeEach(() => {
+ props.settings.spacePolicy = "delete";
+ });
+
+ it("renders the text about deleting content", () => {
+ plainRender( );
+
+ screen.getByText("Find space");
+ screen.getByRole("button", { name: "deleting all content of the installation device" });
+ });
+
+ describe("if there are more than one disk", () => {
+ beforeEach(() => {
+ props.settings.installationDevices = [vda, md0, md1];
+ });
+
+ it("indicates the number of disks", () => {
+ plainRender( );
+
+ screen.getByText("Find space");
+ screen.getByRole("button", { name: "deleting all content of the 3 selected disks" });
+ });
+ });
+ });
+
+ describe("if the space policy is set to 'resize'", () => {
+ beforeEach(() => {
+ props.settings.spacePolicy = "resize";
+ });
+
+ it("renders the text about resizing content", () => {
+ plainRender( );
+
+ screen.getByText("Find space");
+ screen.getByRole("button", { name: "shrinking partitions of the installation device" });
+ });
+
+ describe("if there are more than one disk", () => {
+ beforeEach(() => {
+ props.settings.installationDevices = [vda, md0, md1];
+ });
+
+ it("indicates the number of disks", () => {
+ plainRender( );
+
+ screen.getByText("Find space");
+ screen.getByRole("button", { name: "shrinking partitions of the 3 selected disks" });
+ });
+ });
+ });
+
+ describe("if the space policy is set to 'keep'", () => {
+ beforeEach(() => {
+ props.settings.spacePolicy = "keep";
+ });
+
+ it("renders the text about keeping content", () => {
+ plainRender( );
+
+ screen.getByText("Find space");
+ screen.getByRole("button", { name: "without modifying any partition" });
+ });
+ });
+
+ describe("when the button for changing the space policy is clicked", () => {
+ beforeEach(() => {
+ props.settings.installationDevices = [vda, md0];
+ props.settings.spacePolicy = "keep";
+ props.onChange = jest.fn();
+ });
+
+ it("opens a popup for selecting the space policy", async () => {
+ const { user } = plainRender( );
+
+ const button = screen.getByRole("button", { name: /without modifying/ });
+ await user.click(button);
+
+ const popup = await screen.findByRole("dialog");
+ within(popup).getByText("Space Policy");
+ within(popup).getByRole("option", { name: /Delete current content/ });
+ within(popup).getByRole("option", { name: /Shrink existing partitions/ });
+ within(popup).getByRole("option", { name: /Use available space/, selected: true });
+ });
+
+ it("allows to show the installation devices", async () => {
+ const { user } = plainRender( );
+
+ const button = screen.getByRole("button", { name: /without modifying/ });
+ await user.click(button);
+ const popup = await screen.findByRole("dialog");
+
+ await waitFor(() => (
+ expect(within(popup).queryByText("/dev/vda")).not.toBeVisible() &&
+ expect(within(popup).queryByText("/dev/md0")).not.toBeVisible()
+ ));
+
+ const toggle = within(popup).getByRole("button", { name: /This will affect/ });
+ await user.click(toggle);
+
+ expect(within(popup).getByText("/dev/vda")).toBeVisible();
+ expect(within(popup).getByText("/dev/md0")).toBeVisible();
+ });
+
+ describe("if the popup is canceled", () => {
+ it("closes the popup without selecting a new space policy", async () => {
+ const { user } = plainRender( );
+
+ const button = screen.getByRole("button", { name: /without modifying/ });
+ await user.click(button);
+
+ const popup = await screen.findByRole("dialog");
+ const option = within(popup).getByRole("option", { name: /Shrink/ });
+
+ await user.click(option);
+ const cancel = within(popup).getByRole("button", { name: "Cancel" });
+ await user.click(cancel);
+
+ expect(props.onChange).not.toHaveBeenCalled();
+ expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
+ });
+ });
+
+ describe("if the popup is accepted", () => {
+ it("closes the popup selecting the new space policy", async () => {
+ const { user } = plainRender( );
+
+ const button = screen.getByRole("button", { name: /without modifying/ });
+ await user.click(button);
+
+ const popup = await screen.findByRole("dialog");
+ const option = within(popup).getByRole("option", { name: /Shrink/ });
+
+ await user.click(option);
+ const accept = within(popup).getByRole("button", { name: "Accept" });
+ await user.click(accept);
+
+ expect(props.onChange).toHaveBeenCalledWith({ spacePolicy: "resize" });
+ expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
+ });
+ });
+ });
+});
diff --git a/web/src/components/storage/ProposalVolumes.jsx b/web/src/components/storage/ProposalVolumes.jsx
index 77d9a2e1c9..140e58e092 100644
--- a/web/src/components/storage/ProposalVolumes.jsx
+++ b/web/src/components/storage/ProposalVolumes.jsx
@@ -207,6 +207,7 @@ const VolumeRow = ({ columns, volume, options, isLoading, onEdit, onDelete }) =>
const text = `${volume.fsType} ${options.lvm ? _("logical volume") : _("partition")}`;
const lockIcon = ;
const snapshotsIcon = ;
+ const transactionalIcon = ;
return (
@@ -216,7 +217,7 @@ const VolumeRow = ({ columns, volume, options, isLoading, onEdit, onDelete }) =>
{/* TRANSLATORS: filesystem flag, it allows creating snapshots */}
{_("with snapshots")}} />
{/* TRANSLATORS: flag for transactional file system */}
- {_("transactional")}} />
+ {_("transactional")}} />
);
};
diff --git a/web/src/components/storage/device-utils.jsx b/web/src/components/storage/device-utils.jsx
index 30900d6017..baf38ffb1a 100644
--- a/web/src/components/storage/device-utils.jsx
+++ b/web/src/components/storage/device-utils.jsx
@@ -32,14 +32,13 @@ import { deviceSize } from "~/components/storage/utils";
* @typedef {import ("~/clients/storage").StorageDevice} StorageDevice
*/
-const ListBox = ({ children, ...props }) => ;
+const ListBox = ({ children, ...props }) => ;
const ListBoxItem = ({ isSelected, children, onClick, ...props }) => {
if (isSelected) props['aria-selected'] = true;
return (
@@ -215,7 +214,7 @@ const DeviceItem = ({ device }) => {
return (
<>
-
+
>
@@ -229,11 +228,11 @@ const DeviceItem = ({ device }) => {
* @param {Object} props
* @param {StorageDevice[]} props.devices - Devices to show.
*/
-const DeviceList = ({ devices }) => {
+const DeviceList = ({ devices, ...props }) => {
return (
-
+
{ devices.map(device => (
-
+
))}
@@ -268,13 +267,14 @@ const DeviceSelector = ({ devices, selected, isMultiple = false, onChange = noop
};
return (
-
+
{ devices.map(device => (
onOptionClick(device.name)}
isSelected={isSelected(device.name)}
- className="cursor-pointer"
+ data-type="storage-device"
>
diff --git a/web/src/components/storage/device-utils.test.jsx b/web/src/components/storage/device-utils.test.jsx
index b23f6af0d8..572ac643f4 100644
--- a/web/src/components/storage/device-utils.test.jsx
+++ b/web/src/components/storage/device-utils.test.jsx
@@ -231,11 +231,22 @@ const renderOptions = (Component) => {
describe("DeviceList", renderOptions(DeviceList));
describe("DeviceList", () => {
- it("renders all options as selected", () => {
- plainRender( );
+ describe("with isSelected prop", () => {
+ it("renders all devices as selected", () => {
+ plainRender( );
- const selectedOptions = screen.queryAllByRole("option", { selected: true });
- expect(selectedOptions.length).toEqual(3);
+ const devices = screen.queryAllByText(/\/dev\//, { selected: true });
+ expect(devices.length).toEqual(3);
+ });
+ });
+
+ describe("without isSelected prop", () => {
+ it("renders all devices as not selected", () => {
+ plainRender( );
+
+ const devices = screen.queryAllByText(/\/dev\//, { selected: false });
+ expect(devices.length).toEqual(3);
+ });
});
});
diff --git a/web/src/components/storage/index.js b/web/src/components/storage/index.js
index bb9b0d03dd..5dd66c988f 100644
--- a/web/src/components/storage/index.js
+++ b/web/src/components/storage/index.js
@@ -31,4 +31,5 @@ export { default as ZFCPPage } from "./ZFCPPage";
export { default as ZFCPDiskForm } from "./ZFCPDiskForm";
export { default as ISCSIPage } from "./ISCSIPage";
export { DeviceList, DeviceSelector } from "./device-utils";
+export { SpacePolicyButton, SpacePolicySelector, SpacePolicyDisksHint } from "./space-policy-utils";
export { default as VolumeForm } from "./VolumeForm";
diff --git a/web/src/components/storage/space-policy-utils.jsx b/web/src/components/storage/space-policy-utils.jsx
new file mode 100644
index 0000000000..fbb5fc5275
--- /dev/null
+++ b/web/src/components/storage/space-policy-utils.jsx
@@ -0,0 +1,195 @@
+/*
+ * Copyright (c) [2023] SUSE LLC
+ *
+ * All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2 of the GNU General Public License as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, contact SUSE LLC.
+ *
+ * To contact SUSE LLC about this file by physical or electronic mail, you may
+ * find current contact information at www.suse.com.
+ */
+
+import React, { useState } from "react";
+
+import { _, n_ } from "~/i18n";
+import { sprintf } from "sprintf-js";
+import { noop } from "~/utils";
+import { Button, ExpandableSection, Hint, HintBody } from "@patternfly/react-core";
+import { DeviceList } from "~/components/storage";
+
+const ListBox = ({ children, ...props }) => ;
+
+const ListBoxItem = ({ isSelected, children, onClick, ...props }) => {
+ if (isSelected) props['aria-selected'] = true;
+
+ return (
+
+ {children}
+
+ );
+};
+
+/**
+ * Content for a space policy item
+ * @component
+ *
+ * @param {Object} props
+ * @param {Locale} props.locale
+ */
+const PolicyItem = ({ policy }) => {
+ const Title = () => {
+ let text;
+
+ switch (policy) {
+ case "delete":
+ // TRANSLATORS: automatic actions to find space for installation in the target disk(s)
+ text = _("Delete current content");
+ break;
+ case "resize":
+ // TRANSLATORS: automatic actions to find space for installation in the target disk(s)
+ text = _("Shrink existing partitions");
+ break;
+ case "keep":
+ // TRANSLATORS: automatic actions to find space for installation in the target disk(s)
+ text = _("Use available space");
+ break;
+ }
+
+ return {text}
;
+ };
+
+ const Description = () => {
+ let text;
+
+ switch (policy) {
+ case "delete":
+ text = _("All partitions will be removed and any data in the disks will be lost.");
+ break;
+ case "resize":
+ text = _("The data is kept, but the current partitions will be resized as needed to make enough space.");
+ break;
+ case "keep":
+ text = _("The data is kept and existing partitions will not be modified. \
+Only the space that is not assigned to any partition will be used.");
+ break;
+ }
+
+ return {text}
;
+ };
+
+ return (
+ <>
+
+
+ >
+ );
+};
+
+/**
+ * Component for selecting a policy to make space.
+ * @component
+ *
+ * @param {Object} props
+ * @param {string} [props.value] - Id of the currently selected policy.
+ * @param {(id: string) => void} [props.onChange] - Callback to be called when the selected policy
+ * changes.
+ */
+const SpacePolicySelector = ({ value, onChange = noop }) => {
+ return (
+
+ { ["delete", "resize", "keep"].map(policy => (
+ onChange(policy)}
+ isSelected={policy === value}
+ >
+
+
+ ))}
+
+ );
+};
+
+const SpacePolicyButton = ({ policy, devices, onClick = noop }) => {
+ const Text = () => {
+ const num = devices.length;
+
+ switch (policy) {
+ case "delete":
+ return sprintf(
+ // TRANSLATORS: This is presented next to the label "Find space", so the whole sentence
+ // would read as "Find space deleting all content[...]"
+ n_(
+ "deleting all content of the installation device",
+ "deleting all content of the %d selected disks",
+ num),
+ num
+ );
+ case "resize":
+ return sprintf(
+ // TRANSLATORS: This is presented next to the label "Find space", so the whole sentence
+ // would read as "Find space shrinking partitions[...]"
+ n_(
+ "shrinking partitions of the installation device",
+ "shrinking partitions of the %d selected disks",
+ num),
+ num
+ );
+ case "keep":
+ // TRANSLATORS: This is presented next to the label "Find space", so the whole sentence
+ // would read as "Find space without modifying any partition".
+ return _("without modifying any partition");
+ }
+ console.log("Unsupported value " + policy);
+ return "error";
+ };
+
+ return ;
+};
+
+const SpacePolicyDisksHint = ({ devices }) => {
+ const [isExpanded, setIsExpanded] = useState(false);
+
+ const label = (num) => {
+ return sprintf(
+ n_(
+ "This will only affect the installation device",
+ "This will affect the %d disks selected for installation",
+ num
+ ),
+ num
+ );
+ };
+
+ const num = devices.length;
+
+ return (
+
+
+ setIsExpanded(!isExpanded)}
+ toggleText={label(num)}
+ >
+
+
+
+
+ );
+};
+
+export { SpacePolicyButton, SpacePolicySelector, SpacePolicyDisksHint };
diff --git a/web/src/context/agama.jsx b/web/src/context/agama.jsx
index 0aa16e811b..5ee0b7d22e 100644
--- a/web/src/context/agama.jsx
+++ b/web/src/context/agama.jsx
@@ -23,6 +23,7 @@
import React from "react";
import { InstallerClientProvider } from "./installer";
+import { InstallerL10nProvider } from "./installerL10n";
import { L10nProvider } from "./l10n";
import { ProductProvider } from "./product";
import { NotificationProvider } from "./notification";
@@ -36,13 +37,15 @@ import { NotificationProvider } from "./notification";
function AgamaProviders({ children }) {
return (
-
-
-
- {children}
-
-
-
+
+
+
+
+ {children}
+
+
+
+
);
}
diff --git a/web/src/context/installerL10n.jsx b/web/src/context/installerL10n.jsx
new file mode 100644
index 0000000000..482843a274
--- /dev/null
+++ b/web/src/context/installerL10n.jsx
@@ -0,0 +1,262 @@
+/*
+ * Copyright (c) [2023] SUSE LLC
+ *
+ * All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2 of the GNU General Public License as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, contact SUSE LLC.
+ *
+ * To contact SUSE LLC about this file by physical or electronic mail, you may
+ * find current contact information at www.suse.com.
+ */
+
+// @ts-check
+
+import React, { useCallback, useEffect, useState } from "react";
+import { useCancellablePromise, locationReload, setLocationSearch } from "~/utils";
+import cockpit from "../lib/cockpit";
+import { useInstallerClient } from "./installer";
+
+const L10nContext = React.createContext(null);
+
+/**
+ * Installer localization context.
+ *
+ * @typedef {object} L10nContext
+ * @property {string|undefined} language - Current language.
+ * @property {(language: string) => void} changeLanguage - Function to change the current language.
+ *
+ * @return {L10nContext}
+ */
+function useInstallerL10n() {
+ const context = React.useContext(L10nContext);
+
+ if (!context) {
+ throw new Error("useInstallerL10n must be used within a InstallerL10nContext");
+ }
+
+ return context;
+}
+
+/**
+ * Current language according to Cockpit (in xx_XX format).
+ *
+ * It takes the language from the CockpitLang cookie.
+ *
+ * @return {string|undefined} Undefined if language is not set.
+ */
+function cockpitLanguage() {
+ // language from cookie, empty string if not set (regexp taken from Cockpit)
+ // https://github.com/cockpit-project/cockpit/blob/98a2e093c42ea8cd2431cf15c7ca0e44bb4ce3f1/pkg/shell/shell-modals.jsx#L91
+ const languageString = decodeURIComponent(document.cookie.replace(/(?:(?:^|.*;\s*)CockpitLang\s*=\s*([^;]*).*$)|^.*$/, "$1"));
+ if (languageString) {
+ return languageString.toLowerCase();
+ }
+}
+
+/**
+ * Helper function for storing the Cockpit language.
+ *
+ * Automatically converts the language from xx_XX to xx-xx, as it is the one used by Cockpit.
+ *
+ * @param {string} language - The new locale (e.g., "cs", "cs_CZ").
+ * @return {boolean} True if the locale was changed.
+ */
+function storeCockpitLanguage(language) {
+ const current = cockpitLanguage();
+ if (current === language) return false;
+
+ // Code taken from Cockpit.
+ const cookie = "CockpitLang=" + encodeURIComponent(language) + "; path=/; expires=Sun, 16 Jul 3567 06:23:41 GMT";
+ document.cookie = cookie;
+ window.localStorage.setItem("cockpit.lang", language);
+ return true;
+}
+
+/**
+ * Returns the language tag from the query string.
+ *
+ * Query supports 'xx-xx', 'xx_xx', 'xx-XX' and 'xx_XX' formats.
+ *
+ * @return {string|undefined} Undefined if not set.
+ */
+function languageFromQuery() {
+ const lang = (new URLSearchParams(window.location.search)).get("lang");
+ if (!lang) return undefined;
+
+ const [language, country] = lang.toLowerCase().split(/[-_]/);
+ return (country) ? `${language}-${country}` : language;
+}
+
+/**
+ * Generates a RFC 5646 (or BCP 78) language tag from a locale.
+ *
+ * @param {string} locale
+ * @return {string}
+ *
+ * @private
+ * @see https://datatracker.ietf.org/doc/html/rfc5646
+ * @see https://www.rfc-editor.org/info/bcp78
+ */
+function languageFromLocale(locale) {
+ return locale.replace("_", "-").toLowerCase();
+}
+
+/**
+ * Converts a RFC 5646 language tag to a locale.
+ *
+ * @param {string} language
+ * @return {string}
+ *
+ * @private
+ * @see https://datatracker.ietf.org/doc/html/rfc5646
+ * @see https://www.rfc-editor.org/info/bcp78
+ */
+function languageToLocale(language) {
+ const [lang, country] = language.split("-");
+ return (country) ? `${lang}_${country.toUpperCase()}` : lang;
+}
+
+/**
+ * List of RFC 5646 (or BCP 78) language tags from the navigator.
+ *
+ * @return {Array}
+ */
+function navigatorLanguages() {
+ return navigator.languages.map(l => l.toLowerCase());
+}
+
+/**
+ * Returns the first supported language from the given list.
+ *
+ * @param {Array} languages
+ * @return {string|undefined} Undefined if none of the given languages is supported.
+ */
+function findSupportedLanguage(languages) {
+ const supported = Object.keys(cockpit.manifests.agama?.locales || {});
+
+ for (const candidate of languages) {
+ const [language, country] = candidate.split("-");
+
+ const match = supported.find(s => {
+ const [supportedLanguage, supportedCountry] = s.split("-");
+ if (language === supportedLanguage) {
+ return country === undefined || country === supportedCountry;
+ } else {
+ return false;
+ }
+ });
+ if (match) return match;
+ }
+}
+
+/**
+ * Reloads the page.
+ *
+ * It uses the window.location.replace instead of the reload function synchronizing the "lang"
+ * argument from the URL if present.
+ *
+ * @param {string} newLanguage
+ */
+function reload(newLanguage) {
+ const query = new URLSearchParams(window.location.search);
+ if (query.has("lang") && query.get("lang") !== newLanguage) {
+ query.set("lang", newLanguage);
+ // Setting location search with a different value makes the browser to navigate to the new URL.
+ setLocationSearch(query.toString());
+ } else {
+ locationReload();
+ }
+}
+
+/**
+ * This provider sets the installer locale. By default, it uses the URL "lang" query parameter or
+ * the preferred locale from the browser and synchronizes the UI and the backend locales. To
+ * activate a new locale it reloads the whole page.
+ *
+ * Additionally, it offers a function to change the current locale.
+ *
+ * The format of the language tag in the query parameter follows the
+ * [RFC 5646](https://datatracker.ietf.org/doc/html/rfc5646) specification.
+ *
+ * @param {object} props
+ * @param {React.ReactNode} [props.children] - Content to display within the wrapper.
+ *
+ * @see useInstallerL10n
+ */
+function InstallerL10nProvider({ children }) {
+ const client = useInstallerClient();
+ const [language, setLanguage] = useState(undefined);
+ const [backendPending, setBackendPending] = useState(false);
+ const { cancellablePromise } = useCancellablePromise();
+
+ const storeInstallerLanguage = useCallback(async (newLanguage) => {
+ if (!client) {
+ setBackendPending(true);
+ return false;
+ }
+
+ const locale = await cancellablePromise(client.l10n.getUILocale());
+ const currentLanguage = languageFromLocale(locale);
+
+ if (currentLanguage !== newLanguage) {
+ // FIXME: fallback to en-US if the language is not supported.
+ await cancellablePromise(client.l10n.setUILocale(languageToLocale(newLanguage)));
+ return true;
+ }
+
+ return false;
+ }, [client, cancellablePromise]);
+
+ const changeLanguage = useCallback(async (lang) => {
+ const wanted = lang || languageFromQuery();
+
+ if (wanted === "xx" || wanted === "xx-xx") {
+ cockpit.language = wanted;
+ setLanguage(wanted);
+ return;
+ }
+
+ const current = cockpitLanguage();
+ const candidateLanguages = [wanted, current].concat(navigatorLanguages()).filter(l => l);
+ const newLanguage = findSupportedLanguage(candidateLanguages) || "en-us";
+
+ let mustReload = storeCockpitLanguage(newLanguage);
+ mustReload = await storeInstallerLanguage(newLanguage) || mustReload;
+
+ if (mustReload) {
+ reload(newLanguage);
+ } else {
+ setLanguage(newLanguage);
+ }
+ }, [storeInstallerLanguage, setLanguage]);
+
+ useEffect(() => {
+ if (!language) changeLanguage();
+ }, [changeLanguage, language]);
+
+ useEffect(() => {
+ if (!client || !backendPending) return;
+
+ storeInstallerLanguage(language);
+ setBackendPending(false);
+ }, [client, language, backendPending, storeInstallerLanguage]);
+
+ return (
+ {children}
+ );
+}
+
+export {
+ InstallerL10nProvider,
+ useInstallerL10n
+};
diff --git a/web/src/context/l10n.test.jsx b/web/src/context/installerL10n.test.jsx
similarity index 77%
rename from web/src/context/l10n.test.jsx
rename to web/src/context/installerL10n.test.jsx
index dd932b6102..fa3cf79d7b 100644
--- a/web/src/context/l10n.test.jsx
+++ b/web/src/context/installerL10n.test.jsx
@@ -25,17 +25,17 @@
import React from "react";
import { render, waitFor, screen } from "@testing-library/react";
-import { L10nProvider } from "~/context/l10n";
+import { InstallerL10nProvider } from "~/context/installerL10n";
import { InstallerClientProvider } from "./installer";
import * as utils from "~/utils";
-const getUILanguageFn = jest.fn().mockResolvedValue();
-const setUILanguageFn = jest.fn().mockResolvedValue();
+const getUILocaleFn = jest.fn().mockResolvedValue();
+const setUILocaleFn = jest.fn().mockResolvedValue();
const client = {
- language: {
- getUILanguage: getUILanguageFn,
- setUILanguage: setUILanguageFn
+ l10n: {
+ getUILocale: getUILocaleFn,
+ setUILocale: setUILocaleFn
},
onDisconnect: jest.fn()
};
@@ -72,7 +72,7 @@ const TranslatedContent = () => {
return <>{text[lang]}>;
};
-describe("L10nProvider", () => {
+describe("InstallerL10nProvider", () => {
beforeAll(() => {
jest.spyOn(utils, "locationReload").mockImplementation(utils.noop);
jest.spyOn(utils, "setLocationSearch");
@@ -95,13 +95,13 @@ describe("L10nProvider", () => {
describe("when the Cockpit language is already set", () => {
beforeEach(() => {
document.cookie = "CockpitLang=en-us; path=/;";
- getUILanguageFn.mockResolvedValueOnce("en_US");
+ getUILocaleFn.mockResolvedValueOnce("en_US");
});
it("displays the children content and does not reload", async () => {
render(
-
+
);
@@ -115,14 +115,14 @@ describe("L10nProvider", () => {
describe("when the Cockpit language is set to an unsupported language", () => {
beforeEach(() => {
document.cookie = "CockpitLang=de-de; path=/;";
- getUILanguageFn.mockResolvedValueOnce("de_DE");
- getUILanguageFn.mockResolvedValueOnce("es_ES");
+ getUILocaleFn.mockResolvedValueOnce("de_DE");
+ getUILocaleFn.mockResolvedValueOnce("es_ES");
});
it("uses the first supported language from the browser", async () => {
render(
-
+
);
@@ -131,27 +131,27 @@ describe("L10nProvider", () => {
// renders again after reloading
render(
-
+
);
await waitFor(() => screen.getByText("hola"));
- expect(setUILanguageFn).toHaveBeenCalledWith("es_ES");
+ expect(setUILocaleFn).toHaveBeenCalledWith("es_ES");
});
});
describe("when the Cockpit language is not set", () => {
beforeEach(() => {
// Ensure both, UI and backend mock languages, are in sync since
- // client.setUILanguage is mocked too.
+ // client.setUILocale is mocked too.
// See navigator.language in the beforeAll at the top of the file.
- getUILanguageFn.mockResolvedValue("es_ES");
+ getUILocaleFn.mockResolvedValue("es_ES");
});
it("sets the preferred language from browser and reloads", async () => {
render(
-
+
);
@@ -160,7 +160,7 @@ describe("L10nProvider", () => {
// renders again after reloading
render(
-
+
);
await waitFor(() => screen.getByText("hola"));
@@ -174,7 +174,7 @@ describe("L10nProvider", () => {
it("sets the first which language matches", async () => {
render(
-
+
);
@@ -183,7 +183,7 @@ describe("L10nProvider", () => {
// renders again after reloading
render(
-
+
);
await waitFor(() => screen.getByText("hola!"));
@@ -200,19 +200,19 @@ describe("L10nProvider", () => {
describe("when the Cockpit language is already set to 'cs-cz'", () => {
beforeEach(() => {
document.cookie = "CockpitLang=cs-cz; path=/;";
- getUILanguageFn.mockResolvedValueOnce("cs_CZ");
+ getUILocaleFn.mockResolvedValueOnce("cs_CZ");
});
it("displays the children content and does not reload", async () => {
render(
-
+
);
// children are displayed
await screen.findByText("ahoj");
- expect(setUILanguageFn).not.toHaveBeenCalled();
+ expect(setUILocaleFn).not.toHaveBeenCalled();
expect(document.cookie).toEqual("CockpitLang=cs-cz");
expect(utils.locationReload).not.toHaveBeenCalled();
@@ -223,15 +223,15 @@ describe("L10nProvider", () => {
describe("when the Cockpit language is set to 'en-us'", () => {
beforeEach(() => {
document.cookie = "CockpitLang=en-us; path=/;";
- getUILanguageFn.mockResolvedValueOnce("en_US");
- getUILanguageFn.mockResolvedValueOnce("cs_CZ");
- setUILanguageFn.mockResolvedValue();
+ getUILocaleFn.mockResolvedValueOnce("en_US");
+ getUILocaleFn.mockResolvedValueOnce("cs_CZ");
+ setUILocaleFn.mockResolvedValue();
});
it("sets the 'cs-cz' language and reloads", async () => {
render(
-
+
);
@@ -240,26 +240,26 @@ describe("L10nProvider", () => {
// renders again after reloading
render(
-
+
);
await waitFor(() => screen.getByText("ahoj"));
- expect(setUILanguageFn).toHaveBeenCalledWith("cs_CZ");
+ expect(setUILocaleFn).toHaveBeenCalledWith("cs_CZ");
});
});
describe("when the Cockpit language is not set", () => {
beforeEach(() => {
- getUILanguageFn.mockResolvedValueOnce("en_US");
- getUILanguageFn.mockResolvedValueOnce("cs_CZ");
- setUILanguageFn.mockResolvedValue();
+ getUILocaleFn.mockResolvedValueOnce("en_US");
+ getUILocaleFn.mockResolvedValueOnce("cs_CZ");
+ setUILocaleFn.mockResolvedValue();
});
it("sets the 'cs-cz' language and reloads", async () => {
render(
-
+
);
@@ -268,12 +268,12 @@ describe("L10nProvider", () => {
// reload the component
render(
-
+
);
await waitFor(() => screen.getByText("ahoj"));
- expect(setUILanguageFn).toHaveBeenCalledWith("cs_CZ");
+ expect(setUILocaleFn).toHaveBeenCalledWith("cs_CZ");
});
});
});
diff --git a/web/src/context/l10n.jsx b/web/src/context/l10n.jsx
index d3a725b2fc..7948ed70c6 100644
--- a/web/src/context/l10n.jsx
+++ b/web/src/context/l10n.jsx
@@ -19,251 +19,106 @@
* find current contact information at www.suse.com.
*/
-// @ts-check
-
-import React, { useCallback, useEffect, useState } from "react";
-import { useCancellablePromise, locationReload, setLocationSearch } from "~/utils";
-import cockpit from "../lib/cockpit";
+import React, { useContext, useEffect, useState } from "react";
+import { useCancellablePromise } from "~/utils";
import { useInstallerClient } from "./installer";
-const L10nContext = React.createContext(null);
-
/**
- * @typedef {object} L10nContext
- * @property {string|undefined} language - current language
- * @property {(lang: string) => void} changeLanguage - function to change the current language
- *
- * @return {L10nContext} L10n context
+ * @typedef {import ("~/clients/l10n").Locale} Locale
+ * @typedef {import ("~/clients/l10n").Keymap} Keymap
+ * @typedef {import ("~/clients/l10n").Timezone} Timezone
*/
-function useL10n() {
- const context = React.useContext(L10nContext);
- if (!context) {
- throw new Error("useL10n must be used within a L10nContext");
- }
-
- return context;
-}
+const L10nContext = React.createContext({});
-/**
- * Returns the current locale according to Cockpit
- *
- * It takes the locale from the CockpitLang cookie.
- *
- * @return {string|undefined} language tag in xx_XX format or undefined if
- * it was not set.
- */
-function cockpitLanguage() {
- // language from cookie, empty string if not set (regexp taken from Cockpit)
- // https://github.com/cockpit-project/cockpit/blob/98a2e093c42ea8cd2431cf15c7ca0e44bb4ce3f1/pkg/shell/shell-modals.jsx#L91
- const languageString = decodeURIComponent(document.cookie.replace(/(?:(?:^|.*;\s*)CockpitLang\s*=\s*([^;]*).*$)|^.*$/, "$1"));
- if (languageString) {
- return languageString.toLowerCase();
- }
-}
-
-/**
- * Helper function for storing the Cockpit language.
- *
- * This function automatically converts the language tag from xx_XX to xx-xx,
- * as it is the one used by Cockpit.
- *
- * @param {string} lang the new language tag (like "cs", "cs_CZ",...)
- * @return {boolean} returns true if the locale changed; false otherwise
- */
-function storeUILanguage(lang) {
- const current = cockpitLanguage();
- if (current === lang) {
- return false;
- }
- // code taken from Cockpit
- const cookie = "CockpitLang=" + encodeURIComponent(lang) + "; path=/; expires=Sun, 16 Jul 3567 06:23:41 GMT";
- document.cookie = cookie;
- window.localStorage.setItem("cockpit.lang", lang);
- return true;
-}
-
-/**
- * Returns the language from the query string.
- *
- * @return {string|undefined} language tag in 'xx-xx' format (or just 'xx') or undefined if it was
- * not set. It supports 'xx-xx', 'xx_xx', 'xx-XX' and 'xx_XX'.
- */
-function languageFromQuery() {
- const lang = (new URLSearchParams(window.location.search)).get("lang");
- if (!lang) return undefined;
+function L10nProvider({ children }) {
+ const client = useInstallerClient();
+ const { cancellablePromise } = useCancellablePromise();
+ const [timezones, setTimezones] = useState();
+ const [selectedTimezone, setSelectedTimezone] = useState();
+ const [locales, setLocales] = useState();
+ const [selectedLocales, setSelectedLocales] = useState();
+ const [keymaps, setKeymaps] = useState();
+ const [selectedKeymap, setSelectedKeymap] = useState();
- const [language, country] = lang.toLowerCase().split(/[-_]/);
- return (country) ? `${language}-${country}` : language;
-}
+ useEffect(() => {
+ const load = async () => {
+ const timezones = await cancellablePromise(client.l10n.timezones());
+ const selectedTimezone = await cancellablePromise(client.l10n.getTimezone());
+ const locales = await cancellablePromise(client.l10n.locales());
+ const selectedLocales = await cancellablePromise(client.l10n.getLocales());
+ const keymaps = await cancellablePromise(client.l10n.keymaps());
+ const selectedKeymap = await cancellablePromise(client.l10n.getKeymap());
+ setTimezones(timezones);
+ setSelectedTimezone(selectedTimezone);
+ setLocales(locales);
+ setSelectedLocales(selectedLocales);
+ setKeymaps(keymaps);
+ setSelectedKeymap(selectedKeymap);
+ };
+
+ if (client) {
+ load().catch(console.error);
+ }
+ }, [cancellablePromise, client, setKeymaps, setLocales, setSelectedKeymap, setSelectedLocales, setSelectedTimezone, setTimezones]);
-/**
- * Converts a language tag from the backend to a one compatible with RFC 5646 or
- * BCP 78
- *
- * @param {string} tag - language tag from the backend
- * @return {string} Language tag compatible with RFC 5646 or BCP 78
- *
- * @private
- * @see https://datatracker.ietf.org/doc/html/rfc5646
- * @see https://www.rfc-editor.org/info/bcp78
- */
-function languageFromBackend(tag) {
- return tag.replace("_", "-").toLowerCase();
-}
+ useEffect(() => {
+ if (!client) return;
-/**
- * Converts a language tag compatible with RFC 5646 to the format used by the backend
- *
- * @param {string} tag - language tag from the backend
- * @return {string} Language tag compatible with the backend
- *
- * @private
- * @see https://datatracker.ietf.org/doc/html/rfc5646
- * @see https://www.rfc-editor.org/info/bcp78
- */
-function languageToBackend(tag) {
- const [language, country] = tag.split("-");
- return (country) ? `${language}_${country.toUpperCase()}` : language;
-}
+ return client.l10n.onTimezoneChange(setSelectedTimezone);
+ }, [client, setSelectedTimezone]);
-/**
- * Returns the list of languages from the navigator in RFC 5646 (or BCP 78)
- * format
- *
- * @return {Array} List of languages from the navigator
- */
-function navigatorLanguages() {
- return navigator.languages.map(l => l.toLowerCase());
-}
+ useEffect(() => {
+ if (!client) return;
-/**
- * Returns the first supported language from the given list.
- *
- * @param {Array} languages - Candidate languages
- * @return {string|undefined} First supported language or undefined if none
- * of the given languages is supported.
- */
-function findSupportedLanguage(languages) {
- const supported = Object.keys(cockpit.manifests.agama?.locales || {});
+ return client.l10n.onLocalesChange(setSelectedLocales);
+ }, [client, setSelectedLocales]);
- for (const candidate of languages) {
- const [language, country] = candidate.split("-");
+ useEffect(() => {
+ if (!client) return;
- const match = supported.find(s => {
- const [supportedLanguage, supportedCountry] = s.split("-");
- if (language === supportedLanguage) {
- return country === undefined || country === supportedCountry;
- } else {
- return false;
- }
- });
- if (match) return match;
- }
-}
+ return client.l10n.onKeymapChange(setSelectedKeymap);
+ }, [client, setSelectedKeymap]);
-/**
- * Reloads the page
- *
- * It uses the window.location.replace instead of the reload function
- * synchronizing the "lang" argument from the URL if present.
- *
- * @param {string} newLanguage
- */
-function reload(newLanguage) {
- const query = new URLSearchParams(window.location.search);
- if (query.has("lang") && query.get("lang") !== newLanguage) {
- query.set("lang", newLanguage);
- // Setting location search with a different value makes the browser to navigate
- // to the new URL.
- setLocationSearch(query.toString());
- } else {
- locationReload();
- }
+ const value = { timezones, selectedTimezone, locales, selectedLocales, keymaps, selectedKeymap };
+ return {children} ;
}
/**
- * This provider sets the application language. By default, it uses the
- * URL "lang" query parameter or the preferred language from the browser and
- * synchronizes the UI and the backend languages. To activate a new language it
- * reloads the whole page.
- *
- * Additionally, it offers a function to change the current language.
- *
- * The format of the language tag follows the
- * [RFC 5646](https://datatracker.ietf.org/doc/html/rfc5646) specification.
- *
- * @param {object} props
- * @param {React.ReactNode} [props.children] - content to display within the wrapper
- * @param {import("~/client").InstallerClient} [props.client] - client
+ * Localization context.
+ * @function
*
- * @see useL10n
+ * @typedef {object} L10nContext
+ * @property {Locale[]} locales
+ * @property {Keymap[]} keymaps
+ * @property {Timezone[]} timezones
+ * @property {Locale[]} selectedLocales
+ * @property {Keymap|undefined} selectedKeymap
+ * @property {Timezone|undefined} selectedTimezone
+ *
+ * @returns {L10nContext}
*/
-function L10nProvider({ children }) {
- const client = useInstallerClient();
- const [language, setLanguage] = useState(undefined);
- const [backendPending, setBackendPending] = useState(false);
- const { cancellablePromise } = useCancellablePromise();
-
- const storeBackendLanguage = useCallback(async languageString => {
- if (!client) {
- setBackendPending(true);
- return false;
- }
-
- const currentLang = await cancellablePromise(client.language.getUILanguage());
- const normalizedLang = languageFromBackend(currentLang);
-
- if (normalizedLang !== languageString) {
- // FIXME: fallback to en-US if the language is not supported.
- await cancellablePromise(
- client.language.setUILanguage(languageToBackend(languageString))
- );
- return true;
- }
- return false;
- }, [client, cancellablePromise]);
-
- const changeLanguage = useCallback(async lang => {
- const wanted = lang || languageFromQuery();
-
- if (wanted === "xx" || wanted === "xx-xx") {
- cockpit.language = wanted;
- setLanguage(wanted);
- return;
- }
-
- const current = cockpitLanguage();
- const candidateLanguages = [wanted, current].concat(navigatorLanguages())
- .filter(l => l);
- const newLanguage = findSupportedLanguage(candidateLanguages) || "en-us";
-
- let mustReload = storeUILanguage(newLanguage);
- mustReload = await storeBackendLanguage(newLanguage) || mustReload;
- if (mustReload) {
- reload(newLanguage);
- } else {
- setLanguage(newLanguage);
- }
- }, [storeBackendLanguage, setLanguage]);
+function useL10n() {
+ const context = useContext(L10nContext);
- useEffect(() => {
- if (!language) changeLanguage();
- }, [changeLanguage, language]);
+ if (!context) {
+ throw new Error("useL10n must be used within a L10nProvider");
+ }
- useEffect(() => {
- if (!client || !backendPending) return;
+ const {
+ timezones = [],
+ selectedTimezone: selectedTimezoneId,
+ locales = [],
+ selectedLocales: selectedLocalesId = [],
+ keymaps = [],
+ selectedKeymap: selectedKeymapId
+ } = context;
- storeBackendLanguage(language);
- setBackendPending(false);
- }, [client, language, backendPending, storeBackendLanguage]);
+ const selectedTimezone = timezones.find(t => t.id === selectedTimezoneId);
+ const selectedLocales = selectedLocalesId.map(id => locales.find(l => l.id === id));
+ const selectedKeymap = keymaps.find(k => k.id === selectedKeymapId);
- return (
- {children}
- );
+ return { timezones, selectedTimezone, locales, selectedLocales, keymaps, selectedKeymap };
}
-export {
- L10nProvider,
- useL10n
-};
+export { L10nProvider, useL10n };
diff --git a/web/src/context/product.jsx b/web/src/context/product.jsx
index 4f476f710a..1a4387c190 100644
--- a/web/src/context/product.jsx
+++ b/web/src/context/product.jsx
@@ -23,6 +23,11 @@ import React, { useContext, useEffect, useState } from "react";
import { useCancellablePromise } from "~/utils";
import { useInstallerClient } from "./installer";
+/**
+ * @typedef {import ("~/clients/software").Product} Product
+ * @typedef {import ("~/clients/software").Registration} Registration
+ */
+
const ProductContext = React.createContext([]);
function ProductProvider({ children }) {
@@ -64,6 +69,18 @@ function ProductProvider({ children }) {
return {children} ;
}
+/**
+ * Product context.
+ * @function
+ *
+ * @typedef {object} ProductContext
+ * @property {Product[]} products
+ * @property {Product|null} selectedProduct
+ * @property {string} selectedId
+ * @property {Registration} registration
+ *
+ * @returns {ProductContext}
+ */
function useProduct() {
const context = useContext(ProductContext);
diff --git a/web/src/manifest.json b/web/src/manifest.json
index 4fba146b5f..225e2391bf 100644
--- a/web/src/manifest.json
+++ b/web/src/manifest.json
@@ -10,10 +10,10 @@
},
"locales": {
"en-us": "English",
- "ja-jp": "日本語",
"fr-fr": "Français",
+ "ja-jp": "日本語",
"nl-nl": "Nederlands",
- "sv-se": "Svenska",
- "es-es": "Español"
+ "es-es": "Español",
+ "sv-se": "Svenska"
}
}
\ No newline at end of file
diff --git a/web/src/test-utils.js b/web/src/test-utils.js
index 1848f5a0e0..8e7a5f143d 100644
--- a/web/src/test-utils.js
+++ b/web/src/test-utils.js
@@ -36,6 +36,7 @@ import { NotificationProvider } from "~/context/notification";
import { Layout } from "~/components/layout";
import { noop } from "./utils";
import cockpit from "./lib/cockpit";
+import { InstallerL10nProvider } from "./context/installerL10n";
import { L10nProvider } from "./context/l10n";
/**
@@ -84,9 +85,11 @@ const Providers = ({ children, withL10n }) => {
if (withL10n) {
return (
-
- {children}
-
+
+
+ {children}
+
+
);
}
diff --git a/web/src/utils.js b/web/src/utils.js
index 3cd85fb228..1b3c9dab65 100644
--- a/web/src/utils.js
+++ b/web/src/utils.js
@@ -162,6 +162,47 @@ const useLocalStorage = (storageKey, fallbackState) => {
return [value, setValue];
};
+/**
+ * Debounce hook.
+ * @function
+ *
+ * Source {@link https://designtechworld.medium.com/create-a-custom-debounce-hook-in-react-114f3f245260}
+ *
+ * @param {function} callback - Function to be called after some delay.
+ * @param {number} delay - Delay in milliseconds.
+ * @returns {function}
+ *
+ * @example
+ *
+ * const log = useDebounce(console.log, 1000);
+ * log("test ", 1) // The message will be logged after at least 1 second.
+ * log("test ", 2) // Subsequent calls cancels pending calls.
+ */
+const useDebounce = (callback, delay) => {
+ const timeoutRef = useRef(null);
+
+ useEffect(() => {
+ // Cleanup the previous timeout on re-render
+ return () => {
+ if (timeoutRef.current) {
+ clearTimeout(timeoutRef.current);
+ }
+ };
+ }, []);
+
+ const debouncedCallback = (...args) => {
+ if (timeoutRef.current) {
+ clearTimeout(timeoutRef.current);
+ }
+
+ timeoutRef.current = setTimeout(() => {
+ callback(...args);
+ }, delay);
+ };
+
+ return debouncedCallback;
+};
+
const hex = (value) => {
const sanitizedValue = value.replaceAll(".", "");
return parseInt(sanitizedValue, 16);
@@ -208,14 +249,67 @@ const setLocationSearch = (query) => {
window.location.search = query;
};
+/**
+ * Time for the given timezone.
+ *
+ * @param {string} timezone - E.g., "Atlantic/Canary".
+ * @param {object} [options]
+ * @param {Date} options.date - Date to take the time from.
+ *
+ * @returns {string|undefined} - Time in 24 hours format (e.g., "23:56"). Undefined for an unknown
+ * timezone.
+ */
+const timezoneTime = (timezone, { date = new Date() }) => {
+ try {
+ const formatter = new Intl.DateTimeFormat(
+ "en-US",
+ { timeZone: timezone, timeStyle: "short", hour12: false }
+ );
+
+ return formatter.format(date);
+ } catch (e) {
+ if (e instanceof RangeError) return undefined;
+
+ throw e;
+ }
+};
+
+/**
+ * UTC offset for the given timezone.
+ *
+ * @param {string} timezone - E.g., "Atlantic/Canary".
+ * @returns {number|undefined} - undefined for an unknown timezone.
+ */
+const timezoneUTCOffset = (timezone) => {
+ try {
+ const date = new Date();
+ const dateLocaleString = date.toLocaleString(
+ "en-US",
+ { timeZone: timezone, timeZoneName: "short" }
+ );
+ const [timezoneName] = dateLocaleString.split(' ').slice(-1);
+ const dateString = date.toString();
+ const offset = Date.parse(`${dateString} UTC`) - Date.parse(`${dateString} ${timezoneName}`);
+
+ return offset / 3600000;
+ } catch (e) {
+ if (e instanceof RangeError) return undefined;
+
+ throw e;
+ }
+};
+
export {
noop,
partition,
classNames,
useCancellablePromise,
useLocalStorage,
+ useDebounce,
hex,
toValidationError,
locationReload,
- setLocationSearch
+ setLocationSearch,
+ timezoneTime,
+ timezoneUTCOffset
};