diff --git a/doc/new_architecture.md b/doc/new_architecture.md new file mode 100644 index 0000000000..a0d9b087e6 --- /dev/null +++ b/doc/new_architecture.md @@ -0,0 +1,173 @@ +# Agama's 2024 architecture + +This document describes the proposal for the new Agama architecture. The reasons for introducing +these changes are recorded in [a discussion in Agama's repository][drop-cockpit]. + +[drop-cockpit]: https://github.com/openSUSE/agama/discussions/1000 + +But before describing how the architecture should look, let's quickly look at the current status. + +## The current architecture + +At this point, Agama is composed of four high-level components: + +* **Agama service**: implements the logic to perform the installation. It is the core component of +Agama and it offers a D-Bus interface. Actually, it is composed of two services: `rubygem-agama` and +`agama-dbus-server`. + +* **Web user interface (`cockpit-agama`)**: a web-based interface that plays the role of a GUI when +using Agama live. + +* **Command Line Interface (`agama-cli`)**: it allows to interact with Agama core and drives the +auto-installation process. + +* **Auto-installation (`autoinstallation`)**: it is composed by a Systemd service (`agama-auto`) and +a script that relies on `agama-cli`. + +In addition to those components, we need to consider Cockpit, which plays a vital role: + +* It makes communication between the web UI and the D-Bus services possible. +* It makes the web UI code available to the browser. +* It takes care of authenticating the user when connecting remotely. Again, it is only relative to +the web UI. + +```mermaid +flowchart LR + subgraph Clients + Browser + CLI + auto + end + + subgraph Cockpit + UI["Web UI"] + end + + subgraph Agama Service + Rust[Agama Rust] + Ruby[Agama Ruby] + end + + Browser <---> UI + UI <--D-Bus--> Rust + UI <--D-Bus--> Ruby + CLI <--D-Bus--> Rust + CLI <--D-Bus--> Ruby + Rust <--D-Bus---> Ruby + auto --> CLI +``` + +## The new architecture + +The proposed architecture is not that different from the current one, but it tries to meet these +goals: + +* Drop our dependency on Cockpit. +* Implement a higher-level API to be consumed by the clients, replacing D-Bus for client-server +communication. Agama will still use D-Bus for IPC between the Rust and Ruby components. + +### Components + +With those goals in mind, we are considering the following components: + +* **Agama core (old `agama-dbus-server`)**: implements the installation logic. It relies heavily on +the Agama YaST service. + +* **Agama YaST service (old `rubygem-agama`)**: it is written in Ruby and has direct access to YaST +libraries. Complex parts, like storage and software handling, are implemented in this component. + +* **HTTP and WebSocket API**: implements the API the clients should use to communicate with Agama. +Under the hood, it still uses D-Bus for communication between Agama core and Agama YaST. + +* **Web user interface (old `cockpit-agama`)**: Agama's graphical user interface. The web server +makes this React application available to the browsers. + +* **Command Line Interface (`agama-cli`)**: it allows interaction with Agama and drives the +auto-installation process. With the new architecture, connecting through the network might be +possible without SSH. + +* **Auto-installation (`autoinstallation`)**: as in the current architecture, it is composed by +a Systemd service (`agama-auto`) and a script that relies on `agama-cli`. + +The following diagram could be better, but it represents the main components and their interactions. + +```mermaid +flowchart LR + subgraph Clients + Browser + CLI + auto + end + + subgraph Agama Core + subgraph Web["Web Server"] + direction TB + UI["Web UI"] + API["HTTP/WS API"] + WS["WebSocket"] + end + + Web <--"Channel" --> Rust + end + Rust <-- "D-Bus" --> YaST["Agama YaST"] + + Browser --> UI + Browser --> API + Browser <---> WS + CLI --> API + CLI <---> WS + auto --> CLI +``` + +### The web-based API + +The new web-based API is divided into two different parts: + +* **HTTP/JSON API**: allows clients to execute actions or query Agama. For instance, it could get +the list of available products, request a new storage proposal or start the installation. About the +approach, something REST-like is the most suitable. Switching to GraphQL or gRPC do not seem to be +needed in our case (todo: write why). + +* **WebSocket**: Agama will use WebSockets to notify clients about any relevant event: progress, +configuration changes, etc. The event's format is not defined yet, but a JSON event containing the +`type` and the `payload/details` should be enough[^topics]. + +[^topics]: Ideally, the clients should be able to subscribe to the topics they are interested in. + But that feature can wait. + +### Encryption + +In the case of a remote installation, the communication between the clients and the server must be +encrypted. Connecting to port 80 (HTTP) should redirect the client to port 443 (HTTPS). + +About the certificate, Agama will use a self-signed certificate unless the user injects its own +certificate (through a kernel command line option). + +NOTE: under discussion. + +### Authentication + +The HTTP interface should allow authentication specifying a user and password that will be checked +against PAM. It is not clear yet, but we might need to check whether the logged user has permissions +(making sure it is `root` or through Polkit). + +On successful authentication, the server generates a [JSON Web Token][jwt] that the client will +include in the subsequent requests. The web client stores the token in an HTTP-only +cookie[^http-only] and the CLI uses a file with restricted permissions. + +[^http-only] HTTP-only cookies cannot be accessed byt client-side JavaScript. + +#### Skipping the authentication + +When using Agama locally in the installation media, it would be unpleasant to ask for a +user/password. For that reason, there must be a mechanism to skip the authentication step. Agama +Live could run a special service that generates a valid token and injects such a token into the +server, the CLI and the web browser. + +## Links + +* https://bugzilla.suse.com/show_bug.cgi?id=1219688 +* https://cheatsheetseries.owasp.org/cheatsheets/JSON_Web_Token_for_Java_Cheat_Sheet.html + +[http-auth]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication +[jwt]: https://jwt.io diff --git a/products.d/microos-desktop.yaml b/products.d/microos-desktop.yaml index 966b65415f..97d351d864 100644 --- a/products.d/microos-desktop.yaml +++ b/products.d/microos-desktop.yaml @@ -12,6 +12,10 @@ description: 'A distribution for the desktop offering automatic updates and # Do not manually change any translations! See README.md for more details. translations: description: + es: Una distribución para escritorio que ofrece actualizaciones automáticas y + reversión sobre los fundamentos de openSUSE MicroOS. Incluye Podman + Container Runtime y permite administrar software usando Gnome Software o + KDE Discover. fr: Une distribution pour le bureau offrant des mises à jour automatiques et un rétablissement au dessus des fondations d'openSUSE MicroOS. Inclut Podman Container Runtime et permet de gérer les logiciels en utilisant Gnome diff --git a/products.d/microos.yaml b/products.d/microos.yaml index 6ddd801f50..a0cec5d775 100644 --- a/products.d/microos.yaml +++ b/products.d/microos.yaml @@ -12,6 +12,11 @@ description: 'A quick, small distribution designed to host container workloads # Do not manually change any translations! See README.md for more details. translations: description: + es: Una distribución pequeña y rápida diseñada para alojar cargas de trabajo de + contenedores con administración y parches automatizados. openSUSE MicroOS + proporciona actualizaciones transaccionales (atómicas) en un sistema de + archivos raíz btrfs de solo lectura. Como distribución de lanzamiento + continuo, el software siempre está actualizado. fr: Une petite distribution rapide conçue pour héberger des charges de travail de conteneurs avec une administration et des correctifs automatisés. openSUSE MicroOS fournit des mises à jour transactionnelles (atomiques) diff --git a/service/lib/agama/storage/manager.rb b/service/lib/agama/storage/manager.rb index 21d7543efe..10ba71ed69 100644 --- a/service/lib/agama/storage/manager.rb +++ b/service/lib/agama/storage/manager.rb @@ -293,12 +293,12 @@ def check_multipath mods = `lsmod`.lines.grep(/dm_multipath/) logger.warn("dm_multipath modules is not loaded") if mods.empty? - conf_file = File.exist?(MULTIPATH_CONFIG) - if conf_file - finder = File.readlines(MULTIPATH_CONFIG).grep(/find_multipaths\s+smart/) - logger.warn("find_multipaths is not set to smart value") if finder.empty? + binary = system("which multipath") + if binary + conf = `multipath -t`.lines.grep(/find_multipaths "smart"/) + logger.warn("multipath: find_multipaths is not set to 'smart'") if conf.empty? else - logger.warn("#{MULTIPATH_CONFIG} does not exist") + logger.warn("multipath is not installed.") end end end diff --git a/web/Makefile b/web/Makefile index 8e27dc0565..07b3cb7f84 100644 --- a/web/Makefile +++ b/web/Makefile @@ -177,15 +177,15 @@ bots: # when you start a new project, use the latest release, and update it from time to time test/common: flock Makefile sh -ec '\ - git fetch --depth=1 https://github.com/cockpit-project/cockpit.git 267; \ + git fetch --depth=1 https://github.com/cockpit-project/cockpit.git 309; \ git checkout --force FETCH_HEAD -- test/common; \ git reset test/common' # checkout Cockpit's PF/React/build library; again this has no API stability guarantee, so check out a stable tag -# TODO: replace the commit with the tag 267 once it is released, which includes cockpit.js as a ES6 module in lib/. +# TODO: replace the commit with the tag 309 once it is released, which includes cockpit.js as a ES6 module in lib/. $(LIB_TEST): flock Makefile sh -ec '\ - git fetch --depth=1 https://github.com/cockpit-project/cockpit.git 267; \ + git fetch --depth=1 https://github.com/cockpit-project/cockpit.git 309; \ git checkout --force FETCH_HEAD -- ../pkg/lib; \ git reset -- ../pkg/lib' mv ../pkg/lib src/ && rmdir ../pkg diff --git a/web/package/cockpit-agama.changes b/web/package/cockpit-agama.changes index 11aa800032..6769afa843 100644 --- a/web/package/cockpit-agama.changes +++ b/web/package/cockpit-agama.changes @@ -1,7 +1,12 @@ +------------------------------------------------------------------- +Mon Feb 12 11:53:29 UTC 2024 - Imobach Gonzalez Sosa + +- Update cockpit.js to version 309 (gh#openSUSE/agama#1038). + ------------------------------------------------------------------- Mon Jan 29 14:34:37 UTC 2024 - Knut Anderssen -- Partly replacing the NetworkManager client by the agama one +- Partly replacing the NetworkManager client by the Agama one (gh#openSUSE/agama#1006). ------------------------------------------------------------------- @@ -9,7 +14,7 @@ Fri Jan 19 09:34:26 UTC 2024 - Nagu - Storage UI: show mount point selector only when the user can change it. (gh#openSUSE/agama#1007) - + ------------------------------------------------------------------- Thu Jan 18 08:33:52 UTC 2024 - Ancor Gonzalez Sosa diff --git a/web/po/cs.po b/web/po/cs.po index a6daa2b978..719771ea29 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: 2024-01-28 02:07+0000\n" +"POT-Creation-Date: 2024-02-07 02:04+0000\n" "PO-Revision-Date: 2023-12-31 20:39+0000\n" "Last-Translator: Ladislav Slezák \n" "Language-Team: Czech \n" "Language-Team: German \n" "Language-Team: Spanish \n" @@ -92,7 +92,7 @@ msgstr "Recargar" #: src/components/core/DevelopmentInfo.jsx:55 msgid "Cockpit server" -msgstr "" +msgstr "Servidor Cockpit" #: src/components/core/FileViewer.jsx:65 msgid "Reading file..." @@ -103,13 +103,12 @@ msgid "Cannot read the file" msgstr "No se puede leer el archivo" #: src/components/core/InstallButton.jsx:37 -#, fuzzy msgid "" "There are some reported issues. Please review them in the previous steps " "before proceeding with the installation." msgstr "" -"Hay algunos problemas reportados. Por favor, consulte [la lista de " -"problemas] antes de continuar con la instalación." +"Hay algunos problemas reportados. Por favor, revíselos en los pasos " +"anteriores antes de continuar con la instalación." #: src/components/core/InstallButton.jsx:49 msgid "Confirm Installation" @@ -173,22 +172,23 @@ msgstr "Instalar" #: src/components/core/InstallationFinished.jsx:41 msgid "TPM sealing requires the new system to be booted directly." -msgstr "" +msgstr "El sellado TPM requiere que el nuevo sistema se inicie directamente." #: src/components/core/InstallationFinished.jsx:46 msgid "" "If a local media was used to run this installer, remove it before the next " "boot." msgstr "" +"Si se utilizó un medio local para ejecutar este instalador, expúlselo antes " +"del próximo inicio." #: src/components/core/InstallationFinished.jsx:50 -#, fuzzy msgid "Hide details" -msgstr "Detalles" +msgstr "Ocultar detalles" #: src/components/core/InstallationFinished.jsx:50 msgid "See more details" -msgstr "" +msgstr "Ver más detalles" #. TRANSLATORS: Do not translate 'abbr' and 'title', they are part of the HTML markup #: src/components/core/InstallationFinished.jsx:55 @@ -198,6 +198,10 @@ msgid "" "first boot of the new system. For that to work, the machine needs to boot " "directly to the new boot loader." msgstr "" +"El último paso para configurar el TPM para abrir automáticamente dispositivos cifrados se llevará a cabo " +"durante el primer inicio del nuevo sistema. Para que eso funcione, la " +"máquina debe iniciarse directamente en el nuevo gestor de arranque." #. TRANSLATORS: page title #: src/components/core/InstallationFinished.jsx:88 @@ -270,9 +274,8 @@ msgid "Show global options" msgstr "Mostrar opciones globales" #: src/components/core/Page.jsx:215 -#, fuzzy msgid "Page Actions" -msgstr "Acciones planeadas" +msgstr "Acciones de página" #: src/components/core/PasswordAndConfirmationInput.jsx:35 msgid "Passwords do not match" @@ -338,9 +341,8 @@ msgid "Installer Options" msgstr "Opciones del instalador" #: src/components/core/Sidebar.jsx:128 -#, fuzzy msgid "Hide installer options" -msgstr "Opciones del instalador" +msgstr "Ocultar las opciones del instalador" #: src/components/core/Sidebar.jsx:136 msgid "Diagnostic tools" @@ -348,24 +350,20 @@ msgstr "Herramientas de diagnóstico" #. TRANSLATORS: Titles used for the popup displaying found section issues #: src/components/core/ValidationErrors.jsx:53 -#, fuzzy msgid "Software issues" -msgstr "Software %s" +msgstr "Problemas de software" #: src/components/core/ValidationErrors.jsx:54 -#, fuzzy msgid "Product issues" -msgstr "Producto" +msgstr "Problemas del producto" #: src/components/core/ValidationErrors.jsx:55 -#, fuzzy msgid "Storage issues" -msgstr "Mostrar los problemas" +msgstr "Problemas de almacenamiento" #: src/components/core/ValidationErrors.jsx:57 -#, fuzzy msgid "Found Issues" -msgstr "Mostrar los problemas" +msgstr "Problemas encontrados" #. TRANSLATORS: %d is replaced with the number of errors found #: src/components/core/ValidationErrors.jsx:77 @@ -403,8 +401,8 @@ msgstr "" "El idioma utilizado por el instalador. El idioma del sistema instalado se " "puede configurar en la página %s." -#. TRANSLATORS: page title #. TRANSLATORS: page section +#. TRANSLATORS: page title #: src/components/l10n/InstallerLocaleSwitcher.jsx:56 #: src/components/l10n/L10nPage.jsx:384 #: src/components/overview/L10nSection.jsx:52 @@ -619,38 +617,39 @@ msgstr "Prefijo IP o máscara de red" msgid "At least one address must be provided for selected mode" msgstr "Se debe proporcionar al menos una dirección para el modo seleccionado" -#: src/components/network/IpSettingsForm.jsx:131 +#. TRANSLATORS: %s is replaced by the iSCSI target node name +#: src/components/network/IpSettingsForm.jsx:133 +#: src/components/storage/iscsi/EditNodeForm.jsx:50 #, c-format -msgid "Edit %s connection" -msgstr "Editar %s conexión" +msgid "Edit %s" +msgstr "Editar %s" #. TRANSLATORS: network connection mode (automatic via DHCP or manual with static IP) -#: src/components/network/IpSettingsForm.jsx:133 -#: src/components/network/IpSettingsForm.jsx:138 -#: src/components/network/IpSettingsForm.jsx:140 +#: src/components/network/IpSettingsForm.jsx:136 +#: src/components/network/IpSettingsForm.jsx:141 +#: src/components/network/IpSettingsForm.jsx:143 msgid "Mode" msgstr "Modo" -#: src/components/network/IpSettingsForm.jsx:144 +#: src/components/network/IpSettingsForm.jsx:147 msgid "Automatic (DHCP)" msgstr "Automático (DHCP)" #. TRANSLATORS: manual network configuration mode with a static IP address -#: src/components/network/IpSettingsForm.jsx:146 +#: src/components/network/IpSettingsForm.jsx:149 #: src/components/storage/iscsi/NodeStartupOptions.js:25 msgid "Manual" msgstr "Manual" #. TRANSLATORS: network gateway configuration -#: src/components/network/IpSettingsForm.jsx:161 #: src/components/network/IpSettingsForm.jsx:164 +#: src/components/network/IpSettingsForm.jsx:167 msgid "Gateway" msgstr "Puerta de enlace" #: src/components/network/NetworkPage.jsx:38 -#, fuzzy msgid "No wired connections found." -msgstr "No se encontraron conexiones por cable" +msgstr "No se encontraron conexiones por cable." #: src/components/network/NetworkPage.jsx:53 msgid "" @@ -666,9 +665,8 @@ msgstr "" "hardware o está deshabilitado." #: src/components/network/NetworkPage.jsx:58 -#, fuzzy msgid "No WiFi connections found." -msgstr "No se encontraron conexiones WiFi" +msgstr "No se encontraron conexiones WiFi." #. TRANSLATORS: button label #: src/components/network/NetworkPage.jsx:70 @@ -679,18 +677,18 @@ msgstr "Conectado a una red WIFI" #. TRANSLATORS: page section title #. TRANSLATORS: page title -#: src/components/network/NetworkPage.jsx:170 -#: src/components/overview/NetworkSection.jsx:102 +#: src/components/network/NetworkPage.jsx:169 +#: src/components/overview/NetworkSection.jsx:83 msgid "Network" msgstr "Red" #. TRANSLATORS: page section -#: src/components/network/NetworkPage.jsx:172 +#: src/components/network/NetworkPage.jsx:171 msgid "Wired networks" msgstr "Redes cableadas" #. TRANSLATORS: page section -#: src/components/network/NetworkPage.jsx:177 +#: src/components/network/NetworkPage.jsx:176 msgid "WiFi networks" msgstr "Redes WIFI" @@ -788,13 +786,13 @@ msgstr "Olvidar red" msgid "The system will use %s as its default language." msgstr "El sistema utilizará %s como su idioma predeterminado." -#: src/components/overview/NetworkSection.jsx:78 +#: src/components/overview/NetworkSection.jsx:59 msgid "No network connections detected" msgstr "No se detectaron conexiones de red" #. TRANSLATORS: header for the list of active network connections, #. %d is replaced by the number of active connections -#: src/components/overview/NetworkSection.jsx:87 +#: src/components/overview/NetworkSection.jsx:68 #, c-format msgid "%d connection set:" msgid_plural "%d connections set:" @@ -812,8 +810,8 @@ msgstr "Resumen de la instalación" msgid "%s (registered)" msgstr "%s (registrado)" -#. TRANSLATORS: page section #. TRANSLATORS: page title +#. TRANSLATORS: page section #: src/components/overview/ProductSection.jsx:71 #: src/components/product/ProductPage.jsx:435 msgid "Product" @@ -828,8 +826,8 @@ msgstr "Leer repositorios de software" msgid "Refresh the repositories" msgstr "Refrescar los repositorios" -#. TRANSLATORS: page title #. TRANSLATORS: page section +#. TRANSLATORS: page title #: src/components/overview/SoftwareSection.jsx:143 #: src/components/software/SoftwarePage.jsx:81 msgid "Software" @@ -1232,9 +1230,8 @@ msgid "Installation device" msgstr "Dispositivo de instalación" #: src/components/storage/ProposalSettingsSection.jsx:154 -#, fuzzy msgid "No devices found." -msgstr "No se encontraron dispositivos" +msgstr "No se encontraron dispositivos." #: src/components/storage/ProposalSettingsSection.jsx:241 msgid "Devices for creating the volume group" @@ -1279,15 +1276,18 @@ msgid "" "system. TPM sealing requires the new system to be booted directly on its " "first run." msgstr "" +"La contraseña no será necesaria para iniciar y acceder a los datos si el " +"TPM puede verificar la " +"integridad del sistema. El sellado TPM requiere que el nuevo sistema se " +"inicie directamente en su primera ejecución." #: src/components/storage/ProposalSettingsSection.jsx:440 msgid "Use the TPM to decrypt automatically on each boot" -msgstr "" +msgstr "Utilice el TPM para descifrar automáticamente en cada arranque" #: src/components/storage/ProposalSettingsSection.jsx:511 -#, fuzzy msgid "Change encryption settings" -msgstr "Configuraciones del cifrado" +msgstr "Cambiar las configuraciones de cifrado" #: src/components/storage/ProposalSettingsSection.jsx:516 #: src/components/storage/ProposalSettingsSection.jsx:537 @@ -1418,9 +1418,8 @@ msgid "File systems to create in your system" msgstr "Sistemas de archivos para crear en su sistema" #: src/components/storage/VolumeForm.jsx:102 -#, fuzzy msgid "Select a value" -msgstr "Seleccionar el idioma" +msgstr "Seleccionar un valor" #. TRANSLATORS: description of Btrfs snapshots feature. #: src/components/storage/VolumeForm.jsx:212 @@ -1590,9 +1589,8 @@ msgid "Deactivated" msgstr "Desactivado" #: src/components/storage/ZFCPPage.jsx:417 -#, fuzzy msgid "No zFCP controllers found." -msgstr "No se encontraron controladores zFCP" +msgstr "No se encontraron controladores zFCP." #: src/components/storage/ZFCPPage.jsx:418 msgid "Please, try to read the zFCP devices again." @@ -1640,9 +1638,8 @@ msgid "Activate zFCP disk" msgstr "Activar disco zFCP" #: src/components/storage/ZFCPPage.jsx:553 -#, fuzzy msgid "No zFCP disks found." -msgstr "No se encontraron discos zFCP" +msgstr "No se encontraron discos zFCP." #. TRANSLATORS: button label #: src/components/storage/ZFCPPage.jsx:572 @@ -1791,12 +1788,6 @@ msgstr "Puerto" msgid "Incorrect port" msgstr "Puerto incorrecto" -#. TRANSLATORS: %s is replaced by the iSCSI target node name -#: src/components/storage/iscsi/EditNodeForm.jsx:50 -#, c-format -msgid "Edit %s" -msgstr "Editar %s" - #: src/components/storage/iscsi/InitiatorForm.jsx:42 msgid "Edit iSCSI Initiator" msgstr "Editar iniciador iSCSI" @@ -1870,9 +1861,8 @@ msgid "Interface" msgstr "Interfaz" #: src/components/storage/iscsi/TargetsSection.jsx:139 -#, fuzzy msgid "No iSCSI targets found." -msgstr "No se encontraron objetivos iSCSI" +msgstr "No se encontraron objetivos iSCSI." #: src/components/storage/iscsi/TargetsSection.jsx:140 msgid "" @@ -1985,9 +1975,8 @@ msgid "PiB" msgstr "PB" #: src/components/users/FirstUser.jsx:44 -#, fuzzy msgid "No user defined yet." -msgstr "Ningún usuario definido todavía" +msgstr "Ningún usuario definido todavía." #: src/components/users/FirstUser.jsx:47 msgid "" @@ -2047,9 +2036,8 @@ msgid "Auto-login" msgstr "Inicio de sesión automático" #: src/components/users/RootAuthMethods.jsx:35 -#, fuzzy msgid "No root authentication method defined yet." -msgstr "Aún no se ha definido ningún método de autenticación de root" +msgstr "Aún no se ha definido ningún método de autenticación de root." #: src/components/users/RootAuthMethods.jsx:38 msgid "" @@ -2147,6 +2135,10 @@ msgstr "Usuario" msgid "Root authentication" msgstr "Autenticación de root" +#, c-format +#~ msgid "Edit %s connection" +#~ msgstr "Editar %s conexión" + #~ msgid "Have a lot of fun! Your openSUSE Development Team." #~ msgstr "Have a lot of fun! El equipo de desarrollo de openSUSE." diff --git a/web/po/fr.po b/web/po/fr.po index c4f81a397f..e82423fefe 100644 --- a/web/po/fr.po +++ b/web/po/fr.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-01-28 02:07+0000\n" +"POT-Creation-Date: 2024-02-07 02:04+0000\n" "PO-Revision-Date: 2024-01-23 12:59+0000\n" "Last-Translator: faila fail \n" "Language-Team: French \n" "Language-Team: Indonesian \n" "Language-Team: Japanese \n" "Language-Team: Macedonian \n" "Language-Team: Dutch \n" "Language-Team: Portuguese (Brazil) \n" "Language-Team: Russian \n" "Language-Team: Swedish \n" "Language-Team: Ukrainian