diff --git a/debian/changelog b/debian/changelog index 708ba2fa9..ed2a992ac 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,58 @@ +freedombox (24.10) unstable; urgency=medium + + [ Veiko Aasa ] + * storage: Add an option to include help text to directory selection form + * minidlna: Add media directory selection form + * minidlna: Explicitly include ssdp service to firewall configuration + * minidlna: Do not proxy minidlna web interface over Apache + * minidlna: Restart app when upgrading to reconfigure firewall + + [ gallegonovato ] + * Translated using Weblate (Spanish) + + [ Burak Yavuz ] + * Translated using Weblate (Turkish) + + [ 大王叫我来巡山 ] + * Translated using Weblate (Chinese (Simplified)) + + [ Jiří Podhorecký ] + * Translated using Weblate (Czech) + + [ Ray Kuo ] + * Translated using Weblate (Chinese (Traditional)) + + [ James Valleroy ] + * diagnostics: Add optional component_id to DiagnosticCheck + * app, component: Add repair method + * setup: Add method to run app repair + * diagnostics: Change "Re-run setup" to "Try to repair" + * letsencrypt: Re-obtain certificates during repair + * locale: Update translation strings + * doc: Fetch latest manual + + [ Sunil Mohan Adapa ] + * letsencrypt: Remove unused imports + * nextcloud: Use systemd generator for creating container service + * nextcloud: Create network using systemd generator + * nextcloud: Drop network namespacing in container, use host network + * nextcloud: Use php-fpm container instead of apache container + * nextcloud: Wait on init sync lock + * nextcloud: Pull the image separately before starting systemd unit + * nextcloud: Ship instead of create cron timer related units + * nextcloud: Restart container when dependent services are restarted + * nextcloud: Allow re-running setup + * nextcloud: Implement enable/disable container + * nextcloud: Enable pretty URLs without /index.php in them + * notification: Handle more formatting errors + * nextcloud: Allow re-running setup when app is disabled + * nextcloud: Populated and maintain a list of trusted domains + * nextcloud: Enable app with experimental warning + * nextcloud: Warn that community provides the container not team + * nextcloud: Add fallback for when quadlet is not available + + -- James Valleroy Mon, 06 May 2024 21:00:03 -0400 + freedombox (24.9~bpo12+1) bookworm-backports; urgency=medium * Rebuild for bookworm-backports. diff --git a/doc/manual/en/ReleaseNotes.raw.wiki b/doc/manual/en/ReleaseNotes.raw.wiki index 38943a7f4..0e7742c0d 100644 --- a/doc/manual/en/ReleaseNotes.raw.wiki +++ b/doc/manual/en/ReleaseNotes.raw.wiki @@ -8,6 +8,43 @@ For more technical details, see the [[https://salsa.debian.org/freedombox-team/f The following are the release notes for each !FreedomBox version. +== FreedomBox 24.10 (2024-05-06) == + +=== Highlights === + + * nextcloud: Enable app with experimental warning + * minidlna: Add media directory selection form + +=== Other Changes === + + * app, component: Add repair method + * diagnostics: Add optional component_id to !DiagnosticCheck + * diagnostics: Change "Re-run setup" to "Try to repair" + * letsencrypt: Re-obtain certificates during repair + * letsencrypt: Remove unused imports + * locale: Update translations for Chinese (Simplified), Chinese (Traditional), Czech, Spanish, Turkish + * minidlna: Do not proxy minidlna web interface over Apache + * minidlna: Explicitly include ssdp service to firewall configuration + * minidlna: Restart app when upgrading to reconfigure firewall + * nextcloud: Add fallback for when quadlet is not available + * nextcloud: Allow re-running setup + * nextcloud: Allow re-running setup when app is disabled + * nextcloud: Create network using systemd generator + * nextcloud: Drop network namespacing in container, use host network + * nextcloud: Enable pretty URLs without /index.php in them + * nextcloud: Implement enable/disable container + * nextcloud: Populated and maintain a list of trusted domains + * nextcloud: Pull the image separately before starting systemd unit + * nextcloud: Restart container when dependent services are restarted + * nextcloud: Ship instead of create cron timer related units + * nextcloud: Use php-fpm container instead of apache container + * nextcloud: Use systemd generator for creating container service + * nextcloud: Wait on init sync lock + * nextcloud: Warn that community provides the container not team + * notification: Handle more formatting errors + * setup: Add method to run app repair + * storage: Add an option to include help text to directory selection form + == FreedomBox 24.9 (2024-04-22) == * action_utils, nextcloud: Make podman util more generic diff --git a/doc/manual/es/GitWeb.raw.wiki b/doc/manual/es/GitWeb.raw.wiki index 742f95ddc..9884743ad 100644 --- a/doc/manual/es/GitWeb.raw.wiki +++ b/doc/manual/es/GitWeb.raw.wiki @@ -59,7 +59,9 @@ snapshot = tgz === Enlaces externos === + * Sitio web: https://git-scm.com * Documentación de uso: https://git-scm.com/docs/gitweb + * Wiki de Debian: https://wiki.debian.org/CategoryGit ## END_INCLUDE diff --git a/doc/manual/es/Ikiwiki.raw.wiki b/doc/manual/es/Ikiwiki.raw.wiki index 8d3ed4e3d..931764236 100644 --- a/doc/manual/es/Ikiwiki.raw.wiki +++ b/doc/manual/es/Ikiwiki.raw.wiki @@ -68,7 +68,9 @@ También se pueden instalar temas contribuidos por usuarios desde el Mercado de === Enlaces externos === * Sitio web: https://ikiwiki.info - * Mercado de temas: https://ikiwiki.info/theme_market/ + * Documentación de uso: https://ikiwiki.info + * Foro de usuarios: https://ikiwiki.info/forum/ + * Mercado de temas: https://ikiwiki.info/theme_market/ ## END_INCLUDE diff --git a/doc/manual/es/Infinoted.raw.wiki b/doc/manual/es/Infinoted.raw.wiki index 00d9be635..9c0716221 100644 --- a/doc/manual/es/Infinoted.raw.wiki +++ b/doc/manual/es/Infinoted.raw.wiki @@ -19,12 +19,18 @@ Si tu !FreedomBox está detras de un router necesitarás configurar la redirecci * TCP 6523 -=== Enlaces extenos === +=== Enlaces externos === * Sitio web: https://gobby.github.io/libinfinity - * Software cliente para Gobby: https://gobby.github.io - * Wiki de Gobby: https://github.com/gobby/gobby/wiki +==== Aplicaciones Cliente ==== + +!FreedomBox recomienda algunas aplicaciones cliente. Selecciona su icono en la página de ''Aplicaciones'' y haz clic en el botón'''> Aplicaciones Cliente'''. + + * El cliente principal de Infinoted se llama "Gobby: https://gobby.github.io + * Documentación de uso: https://github.com/gobby/gobby/wiki + * Wiki de Gobby: https://github.com/gobby/gobby/wiki + ## END_INCLUDE Volver a la [[es/FreedomBox/Features|descripción de Funcionalidades]] o a las páginas del [[es/FreedomBox/Manual|manual]]. diff --git a/doc/manual/es/JSXC.raw.wiki b/doc/manual/es/JSXC.raw.wiki index 6db1846d3..c9ed9dd13 100644 --- a/doc/manual/es/JSXC.raw.wiki +++ b/doc/manual/es/JSXC.raw.wiki @@ -46,7 +46,7 @@ Si tu !FreedomBox está detrás de un router y quieres conectarte a otros servid * Sitio web: https://www.jsxc.org * Documentación de uso: https://www.jsxc.org/manual.html - * Manual de !FreedomBox del servidor ejabberd: [[FreedomBox/Manual/ejabberd|https://wiki.debian.org/FreedomBox/Manual/ejabberd]] + * Manual de !FreedomBox del servidor ejabberd: [[es/FreedomBox/Manual/ejabberd|https://wiki.debian.org/es/FreedomBox/Manual/ejabberd]] ## END_INCLUDE diff --git a/doc/manual/es/MatrixSynapse.raw.wiki b/doc/manual/es/MatrixSynapse.raw.wiki index 76d3c9920..322ea8720 100644 --- a/doc/manual/es/MatrixSynapse.raw.wiki +++ b/doc/manual/es/MatrixSynapse.raw.wiki @@ -86,6 +86,13 @@ Si tu !FreedomBox está detrás de un router, necesitarás configurar la redirec * Matrix en el wiki de Debian : https://wiki.debian.org/Matrix * Video tutorial para instalar Matrix Synapse sobre una instancia en la nube: https://youtu.be/8snpMHHbymI + +==== Aplicaciones cliente ==== + +!FreedomBox recomienda algunas aplicaciones cliente. Selecciona su icono en la página de ''Aplicaciones'' y haz clic en el botón'''> Aplicaciones Cliente'''. + + * [[https://wiki.debian.org/Matrix#Clients|Software cliente para Matrix en el wiki de Debian]] + ## END_INCLUDE Volver a la [[es/FreedomBox/Features|descripción de Funcionalidades]] o a las páginas del [[es/FreedomBox/Manual|manual]]. diff --git a/doc/manual/es/ReleaseNotes.raw.wiki b/doc/manual/es/ReleaseNotes.raw.wiki index 38943a7f4..0e7742c0d 100644 --- a/doc/manual/es/ReleaseNotes.raw.wiki +++ b/doc/manual/es/ReleaseNotes.raw.wiki @@ -8,6 +8,43 @@ For more technical details, see the [[https://salsa.debian.org/freedombox-team/f The following are the release notes for each !FreedomBox version. +== FreedomBox 24.10 (2024-05-06) == + +=== Highlights === + + * nextcloud: Enable app with experimental warning + * minidlna: Add media directory selection form + +=== Other Changes === + + * app, component: Add repair method + * diagnostics: Add optional component_id to !DiagnosticCheck + * diagnostics: Change "Re-run setup" to "Try to repair" + * letsencrypt: Re-obtain certificates during repair + * letsencrypt: Remove unused imports + * locale: Update translations for Chinese (Simplified), Chinese (Traditional), Czech, Spanish, Turkish + * minidlna: Do not proxy minidlna web interface over Apache + * minidlna: Explicitly include ssdp service to firewall configuration + * minidlna: Restart app when upgrading to reconfigure firewall + * nextcloud: Add fallback for when quadlet is not available + * nextcloud: Allow re-running setup + * nextcloud: Allow re-running setup when app is disabled + * nextcloud: Create network using systemd generator + * nextcloud: Drop network namespacing in container, use host network + * nextcloud: Enable pretty URLs without /index.php in them + * nextcloud: Implement enable/disable container + * nextcloud: Populated and maintain a list of trusted domains + * nextcloud: Pull the image separately before starting systemd unit + * nextcloud: Restart container when dependent services are restarted + * nextcloud: Ship instead of create cron timer related units + * nextcloud: Use php-fpm container instead of apache container + * nextcloud: Use systemd generator for creating container service + * nextcloud: Wait on init sync lock + * nextcloud: Warn that community provides the container not team + * notification: Handle more formatting errors + * setup: Add method to run app repair + * storage: Add an option to include help text to directory selection form + == FreedomBox 24.9 (2024-04-22) == * action_utils, nextcloud: Make podman util more generic diff --git a/plinth/__init__.py b/plinth/__init__.py index 6c6824e40..e96f61589 100644 --- a/plinth/__init__.py +++ b/plinth/__init__.py @@ -3,4 +3,4 @@ Package init file. """ -__version__ = '24.9' +__version__ = '24.10' diff --git a/plinth/action_utils.py b/plinth/action_utils.py index ac6e617bc..552194756 100644 --- a/plinth/action_utils.py +++ b/plinth/action_utils.py @@ -11,6 +11,8 @@ import subprocess import tempfile from contextlib import contextmanager +import augeas + logger = logging.getLogger(__name__) UWSGI_ENABLED_PATH = '/etc/uwsgi/apps-enabled/{config_name}.ini' @@ -489,59 +491,187 @@ def is_package_manager_busy(): return False -def podman_run(network_name: str, subnet: str, bridge_ip: str, host_port: str, - container_port: str, container_ip: str, container_name: str, - image_name: str, extra_run_options: list[str] | None = None, - extra_network_options: list[str] | None = None): - """Remove, recreate and run a podman container.""" - try: - service_stop(container_name) - subprocess.run(['podman', 'network', 'rm', '--force', network_name], - check=False) - except subprocess.CalledProcessError: - pass +def podman_create(container_name: str, image_name: str, volume_name: str, + volume_path: str, volumes: dict[str, str] | None = None, + env: dict[str, str] | None = None, + binds_to: list[str] | None = None): + """Remove and recreate a podman container.""" + service_stop(f'{volume_name}-volume.service') + service_stop(container_name) - network_create_command = [ - 'podman', 'network', 'create', '--driver', 'bridge', '--subnet', - subnet, '--gateway', bridge_ip, '--dns', bridge_ip, '--interface-name', - network_name, network_name - ] + (extra_network_options or []) + # Data is kept + subprocess.run(['podman', 'volume', 'rm', '--force', volume_name], + check=False) - # Create bridge network - subprocess.run(network_create_command, check=True) + directory = pathlib.Path('/etc/containers/systemd') + directory.mkdir(parents=True, exist_ok=True) - args = [ - 'podman', 'run', '--detach', '--network', network_name, '--ip', - container_ip, '--name', container_name, '--restart', 'unless-stopped', - '--quiet' - ] - # Only listen on localhost. This is to prevent exposing the host port to - # the internet. - args += ['--publish', f'127.0.0.1:{host_port}:{container_port}'] - # Enable automatic updates. - args += ['--label', 'io.containers.autoupdate=registry'] - # If another container with the same name already exists, replace and - # remove it. - args += ['--replace'] - args += (extra_run_options or []) + [image_name] - subprocess.run(args, check=True) + # Fetch the image before creating the container. The systemd service for + # the container won't timeout due to slow internet connectivity. + subprocess.run(['podman', 'image', 'pull', image_name], check=True) - # Create service file for starting/stopping container using systemd + pathlib.Path(volume_path).mkdir(parents=True, exist_ok=True) + # Create storage volume + volume_file = directory / f'{volume_name}.volume' + contents = f'''[Volume] +Device={volume_path} +Driver=local +VolumeName={volume_name} +Options=bind +''' + volume_file.write_text(contents) + + service_file = directory / f'{container_name}.container' + volume_lines = '\n'.join([ + f'Volume={source}:{dest}' for source, dest in (volumes or {}).items() + ]) + env_lines = '\n'.join( + [f'Environment={key}={value}' for key, value in (env or {}).items()]) + bind_lines = '\n'.join(f'BindsTo={service}\nAfter={service}' + for service in (binds_to or [])) + contents = f'''[Unit] +Requires={volume_name}-volume.service +After={volume_name}-volume.service +{bind_lines} + +[Container] +AutoUpdate=registry +ContainerName=%N +{env_lines} +Image={image_name} +Network=host +{volume_lines} + +[Service] +Restart=always + +[Install] +WantedBy=default.target +''' + service_file.write_text(contents) + + # Remove the fallback service file when upgrading from bookworm to trixie. + # Re-running setup should be sufficient. + _podman_create_fallback_service_file(container_name, image_name, + volume_name, volume_path, volumes, + env, binds_to) + + service_daemon_reload() + + +def _podman_create_fallback_service_file(container_name: str, image_name: str, + volume_name: str, volume_path: str, + volumes: dict[str, str] | None = None, + env: dict[str, str] | None = None, + binds_to: list[str] | None = None): + """Create a systemd unit file if systemd generator is not available.""" service_file = pathlib.Path( - '/etc/systemd/system') / f'{container_name}.service' - with service_file.open('wb') as file_handle: - subprocess.run( - ['podman', 'generate', 'systemd', '--new', container_name], - stdout=file_handle, check=True) + f'/etc/systemd/system/{container_name}.service') + + generator = '/usr/lib/systemd/system-generators/podman-system-generator' + if pathlib.Path(generator).exists(): + # If systemd generator is present, during an upgrade, remove the + # .service file (perhaps created when generator is not present). + service_file.unlink(missing_ok=True) + return + + service_file.parent.mkdir(parents=True, exist_ok=True) + bind_lines = '\n'.join(f'BindsTo={service}\nAfter={service}' + for service in (binds_to or [])) + require_mounts_for = '\n'.join((f'RequiresMountsFor={host_path}' + for host_path in (volumes or {}) + if host_path.startswith('/'))) + env_args = ' '.join( + (f'--env {key}={value}' for key, value in (env or {}).items())) + volume_args = ' '.join( + (f'-v {host_path}:{container_path}' + for host_path, container_path in (volumes or {}).items())) + + # Similar to the file quadlet systemd generator produces but with volume + # related commands merged. + contents = f'''[Unit] +{bind_lines} +RequiresMountsFor=%t/containers +{require_mounts_for} + +[Service] +Restart=always +Environment=PODMAN_SYSTEMD_UNIT=%n +KillMode=mixed +ExecStop=/usr/bin/podman rm -v -f -i --cidfile=%t/%N.cid +ExecStopPost=-/usr/bin/podman rm -v -f -i --cidfile=%t/%N.cid +Delegate=yes +Type=notify +NotifyAccess=all +SyslogIdentifier=%N +ExecStartPre=/usr/bin/rm -f %t/%N.cid +ExecStartPre=/usr/bin/podman volume rm --force {volume_name} +ExecStartPre=/usr/bin/podman volume create --driver=local --opt device={volume_path} --opt o=bind {volume_name} +ExecStart=/usr/bin/podman run --name=%N --cidfile=%t/%N.cid --replace --rm --cgroups=split --network=host --sdnotify=conmon --detach --label io.containers.autoupdate=registry {volume_args} {env_args} {image_name} + +[Install] +WantedBy=default.target +''' # noqa: E501 + service_file.write_text(contents, encoding='utf-8') + service_daemon_reload() -def podman_uninstall(container_name: str, network_name: str, volume_name: str, - image_name: str): +def _podman_augeus(container_name: str): + """Return an augues instance to edit container configuration file.""" + aug = augeas.Augeas(flags=augeas.Augeas.NO_LOAD + + augeas.Augeas.NO_MODL_AUTOLOAD) + container = f'/etc/containers/systemd/{container_name}.container' + aug.transform('Systemd', container) + aug.set('/augeas/context', '/files' + container) + aug.load() + return aug + + +def podman_is_enabled(container_name: str) -> bool: + """Return whether the container to start on boot.""" + aug = _podman_augeus(container_name) + aug = _podman_augeus(container_name) + value = 'default.target' + key = 'Install/WantedBy' + return any( + (aug.get(match_ + '/value') == value for match_ in aug.match(key))) + + +def podman_enable(container_name: str): + """Enable container to start on boot.""" + aug = _podman_augeus(container_name) + value = 'default.target' + key = 'Install/WantedBy' + found = any( + (aug.get(match_ + '/value') == value for match_ in aug.match(key))) + if not found: + aug.set(f'{key}[last() +1]/value', value) + aug.save() + + +def podman_disable(container_name: str): + """Disable container to start on boot.""" + aug = _podman_augeus(container_name) + aug.remove('Install/WantedBy') + aug.save() + + +def podman_uninstall(container_name: str, volume_name: str, image_name: str, + volume_path: str): """Remove a podman container's components and systemd unit.""" - subprocess.run(['podman', 'network', 'rm', network_name], check=True) - subprocess.run(['podman', 'volume', 'rm', volume_name], check=True) - subprocess.run(['podman', 'image', 'rm', image_name], check=True) + subprocess.run(['podman', 'volume', 'rm', '--force', volume_name], + check=True) + subprocess.run(['podman', 'image', 'rm', '--ignore', image_name], + check=True) + volume_file = pathlib.Path( + '/etc/containers/systemd/') / f'{volume_name}.volume' + volume_file.unlink(missing_ok=True) + service_file = pathlib.Path( + '/etc/containers/systemd/') / f'{container_name}.container' + service_file.unlink(missing_ok=True) + # Remove fallback service file service_file = pathlib.Path( '/etc/systemd/system/') / f'{container_name}.service' service_file.unlink(missing_ok=True) + shutil.rmtree(volume_path, ignore_errors=True) service_daemon_reload() diff --git a/plinth/app.py b/plinth/app.py index c093ceb32..be936b674 100644 --- a/plinth/app.py +++ b/plinth/app.py @@ -211,7 +211,6 @@ class App: """Update the status of all follower components. Do not query or update the status of the leader components. - """ for component in self.components.values(): if not component.is_leader: @@ -222,15 +221,14 @@ class App: Return value must be a list of results. Each result is a :class:`~plinth.diagnostic_check.DiagnosticCheck` with a - unique check_id, a user visible description of the test, and the - result. The test result is a string enumeration from 'failed', - 'passed', 'error', 'warning' and 'not_done'. + unique check_id, a user visible description of the test, the result, + test parameters, and the component ID. The test result is a string + enumeration from 'failed', 'passed', 'error', 'warning' and 'not_done'. Results are typically collected by diagnosing each component of the app and then supplementing the results with any app level diagnostic tests. Also see :meth:`.has_diagnostics`. - """ results = [] for component in self.components.values(): @@ -255,7 +253,6 @@ class App: it is assumed that it is for implementing diagnostic tests and this method returns True for such an app. Override this method if this default behavior does not fit the needs. - """ # App implements some diagnostics if self.__class__.diagnose is not App.diagnose: @@ -268,6 +265,40 @@ class App: return False + def repair(self, failed_checks: _list_type) -> bool: + """Try to fix failed diagnostics. + + The default implementation asks relevant components to repair, and then + requests re-run setup for the app. + + failed_checks is a list of DiagnosticChecks that had failed or resulted + in a warning. The list will be split up by component_id, and passed to + the appropriate components. Remaining failed diagnostics do not have a + related component, and should be handled by the app. + + Returns whether the app setup should be re-run. + """ + should_rerun_setup = False + + # Group the failed_checks by component + components_failed_checks = collections.defaultdict(list) + for failed_check in failed_checks: + if failed_check.component_id: + components_failed_checks[failed_check.component_id].append( + failed_check) + else: + # There is a failed check with no related component. + should_rerun_setup = True + + # Repair each component that has failed checks + for component_id, component in self.components.items(): + if components_failed_checks[component_id]: + result = component.repair( + components_failed_checks[component_id]) + should_rerun_setup = should_rerun_setup or result + + return should_rerun_setup + class Component: """Interface for an app component. @@ -314,12 +345,11 @@ class Component: Return value must be a list of results. Each result is a :class:`~plinth.diagnostic_check.DiagnosticCheck` with a - unique check_id, a user visible description of the test, and the - result. The test result is a string enumeration from 'failed', - 'passed', 'error', 'warning' and 'not_done'. + unique check_id, a user visible description of the test, the result, + test parameters, and the component ID. The test result is a string + enumeration from 'failed', 'passed', 'error', 'warning' and 'not_done'. Also see :meth:`.has_diagnostics`. - """ return [] @@ -333,10 +363,25 @@ class Component: is assumed that it is for implementing diagnostic tests and this method returns True for such a component. Override this method if this default behavior does not fit the needs. - """ return self.__class__.diagnose is not Component.diagnose + def repair(self, failed_checks: list) -> bool: + """Try to fix failed diagnostics. + + The default implementation only requests re-run setup for the app. + + Returns whether the app setup should be re-run by the caller. + + This method should be overridden by components that implement + diagnose(), if there is a known way to fix failed checks. The return + value can be changed to False to avoid causing a re-run setup. + + failed_checks is a list of DiagnosticChecks related to this component + that had failed or warning result. + """ + return True + class FollowerComponent(Component): """Interface for an app component that follows other components. diff --git a/plinth/config.py b/plinth/config.py index cf15821e1..1b7f6c8f0 100644 --- a/plinth/config.py +++ b/plinth/config.py @@ -104,7 +104,7 @@ class DropinConfigs(app_module.FollowerComponent): parameters: DiagnosticCheckParameters = {'etc_path': str(etc_path)} results.append( DiagnosticCheck(check_id, description, result_string, - parameters)) + parameters, self.component_id)) return results diff --git a/plinth/daemon.py b/plinth/daemon.py index ae946800d..b70d2e099 100644 --- a/plinth/daemon.py +++ b/plinth/daemon.py @@ -110,7 +110,9 @@ class Daemon(app.LeaderComponent): results = [] results.append(self._diagnose_unit_is_running()) for port in self.listen_ports: - results.append(diagnose_port_listening(port[0], port[1])) + results.append( + diagnose_port_listening(port[0], port[1], None, + self.component_id)) return results @@ -124,7 +126,8 @@ class Daemon(app.LeaderComponent): 'service_name': str(self.unit) } - return DiagnosticCheck(check_id, description, result, parameters) + return DiagnosticCheck(check_id, description, result, parameters, + self.component_id) class RelatedDaemon(app.FollowerComponent): @@ -200,7 +203,8 @@ def app_is_running(app_): def diagnose_port_listening( port: int, kind: str = 'tcp', - listen_address: str | None = None) -> DiagnosticCheck: + listen_address: str | None = None, + component_id: str | None = None) -> DiagnosticCheck: """Run a diagnostic on whether a port is being listened on. Kind must be one of inet, inet4, inet6, tcp, tcp4, tcp6, udp, @@ -222,7 +226,7 @@ def diagnose_port_listening( return DiagnosticCheck(check_id, description, Result.PASSED if result else Result.FAILED, - parameters) + parameters, component_id) def _check_port(port: int, kind: str = 'tcp', @@ -272,7 +276,8 @@ def _check_port(port: int, kind: str = 'tcp', def diagnose_netcat(host: str, port: int, remote_input: str = '', - negate: bool = False) -> DiagnosticCheck: + negate: bool = False, + component_id: str | None = None) -> DiagnosticCheck: """Run a diagnostic using netcat.""" try: process = subprocess.Popen(['nc', host, str(port)], @@ -298,4 +303,5 @@ def diagnose_netcat(host: str, port: int, remote_input: str = '', check_id = f'daemon-netcat-negate-{host}-{port}' description = gettext_noop('Cannot connect to {host}:{port}') - return DiagnosticCheck(check_id, description, result, parameters) + return DiagnosticCheck(check_id, description, result, parameters, + component_id) diff --git a/plinth/diagnostic_check.py b/plinth/diagnostic_check.py index f67661421..d3d6164d2 100644 --- a/plinth/diagnostic_check.py +++ b/plinth/diagnostic_check.py @@ -30,6 +30,7 @@ class DiagnosticCheck: description: str result: Result = Result.NOT_DONE parameters: DiagnosticCheckParameters = field(default_factory=dict) + component_id: str | None = None @property def translated_description(self): @@ -65,6 +66,7 @@ class CheckJSONDecoder(json.JSONDecoder): """Convert tagged data to DiagnosticCheck.""" if data.get('__class__') == 'DiagnosticCheck': return DiagnosticCheck(data['check_id'], data['description'], - data['result'], data['parameters']) + data['result'], data['parameters'], + data.get('component_id')) return data diff --git a/plinth/locale/ar/LC_MESSAGES/django.po b/plinth/locale/ar/LC_MESSAGES/django.po index a755190b6..7b2d13c80 100644 --- a/plinth/locale/ar/LC_MESSAGES/django.po +++ b/plinth/locale/ar/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-04-22 20:02-0400\n" +"POT-Creation-Date: 2024-05-06 20:18-0400\n" "PO-Revision-Date: 2023-10-19 06:18+0000\n" "Last-Translator: Shaik \n" "Language-Team: Arabic \n" "Language-Team: Arabic (Saudi Arabia) \n" "Language-Team: Bulgarian 10 знака . " "Оставете полето празно, за да запазите текущата парола." -#: modules/nextcloud/forms.py:38 +#: modules/nextcloud/forms.py:41 msgid "Default phone region" msgstr "" -#: modules/nextcloud/forms.py:39 +#: modules/nextcloud/forms.py:42 msgid "" "The default phone region is required to validate phone numbers in the " "profile settings without a country code." @@ -7875,67 +7872,103 @@ msgstr "Пакетът „{expression}“ е недостъпен за инст msgid "Package {package_name} is the latest version ({latest_version})" msgstr "" -#: package.py:419 +#: package.py:420 msgid "installing" msgstr "" -#: package.py:421 +#: package.py:422 msgid "downloading" msgstr "" -#: package.py:423 +#: package.py:424 msgid "media change" msgstr "" -#: package.py:425 +#: package.py:426 #, python-brace-format msgid "configuration file: {file}" msgstr "" -#: package.py:453 package.py:478 +#: package.py:454 package.py:479 msgid "Timeout waiting for package manager" msgstr "Времето за изчакване на диспечера на пакети е изтекло" -#: setup.py:39 +#: setup.py:42 msgid "Installing app" msgstr "Инсталиране на приложение" -#: setup.py:41 +#: setup.py:44 msgid "Updating app" msgstr "Обновяване на приложение" -#: setup.py:75 +#: setup.py:78 #, python-brace-format msgid "Error installing app: {error}" msgstr "Грешка при инсталиране на приложението: {error}" -#: setup.py:78 +#: setup.py:81 setup.py:151 +#, fuzzy, python-brace-format +#| msgid "Error updating app: {error}" +msgid "Error repairing app: {error}" +msgstr "Грешка при обновяване на приложението: {error}" + +#: setup.py:84 #, python-brace-format msgid "Error updating app: {error}" msgstr "Грешка при обновяване на приложението: {error}" -#: setup.py:82 +#: setup.py:88 msgid "App installed." msgstr "Приложението е инсталирано." -#: setup.py:84 +#: setup.py:92 msgid "App updated" msgstr "Приложението е обновено" -#: setup.py:101 +#: setup.py:110 +#, fuzzy +#| msgid "Updating app" +msgid "Repairing app" +msgstr "Обновяване на приложение" + +#: setup.py:130 +#, fuzzy, python-brace-format +#| msgid "Error uninstalling app: {error}" +msgid "Error running diagnostics: {error}" +msgstr "Грешка при премахване на приложението: {error}" + +#: setup.py:143 +msgid "Skipping repair, no failed checks" +msgstr "" + +#: setup.py:157 +msgid "Re-running setup to complete repairs" +msgstr "" + +#: setup.py:165 +#, fuzzy +#| msgid "App updated" +msgid "App repaired." +msgstr "Приложението е обновено" + +#: setup.py:169 +msgid "App repair completed with errors:\n" +msgstr "" + +#: setup.py:189 msgid "Uninstalling app" msgstr "Премахване на приложение" -#: setup.py:117 +#: setup.py:205 #, python-brace-format msgid "Error uninstalling app: {error}" msgstr "Грешка при премахване на приложението: {error}" -#: setup.py:120 +#: setup.py:208 msgid "App uninstalled." msgstr "Приложението е премахнато." -#: setup.py:493 +#: setup.py:581 msgid "Updating app packages" msgstr "Обновяване на пакетите на приложението" @@ -8278,6 +8311,10 @@ msgstr "Обновяване" msgid "Backup" msgstr "Резервно копие" +#: templates/toolbar.html:53 +msgid "Re-run setup" +msgstr "" + #: templates/toolbar.html:59 templates/toolbar.html:60 #: templates/uninstall.html:30 msgid "Uninstall" @@ -8309,6 +8346,9 @@ msgstr "преди премахване на {app_id}" msgid "Gujarati" msgstr "Гуджарати" +#~ msgid "Cannot test: No domains are configured." +#~ msgstr "Не може да бъде извършена проба: Не са настроени домейни." + #~ msgid "Storage snapshots configuration updated" #~ msgstr "Настройките на моментните снимки на хранилището са променени" diff --git a/plinth/locale/bn/LC_MESSAGES/django.po b/plinth/locale/bn/LC_MESSAGES/django.po index f568bc539..b685e9f14 100644 --- a/plinth/locale/bn/LC_MESSAGES/django.po +++ b/plinth/locale/bn/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-04-22 20:02-0400\n" +"POT-Creation-Date: 2024-05-06 20:18-0400\n" "PO-Revision-Date: 2021-06-16 07:33+0000\n" "Last-Translator: Oymate \n" "Language-Team: Bengali \n" "Language-Team: Czech \n" @@ -16,8 +16,8 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n" -"X-Generator: Weblate 5.2\n" +"Plural-Forms: nplurals=3; plural=((n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2);\n" +"X-Generator: Weblate 5.5.1-dev\n" #: config.py:103 #, python-brace-format @@ -28,27 +28,27 @@ msgstr "Statická konfigurace {etc_path} je nastavena správně" msgid "FreedomBox" msgstr "FreedomBox" -#: daemon.py:122 +#: daemon.py:124 #, python-brace-format msgid "Service {service_name} is running" msgstr "Služba {service_name} je spuštěná" -#: daemon.py:218 +#: daemon.py:222 #, python-brace-format msgid "Listening on {kind} port {listen_address}:{port}" msgstr "Spojení očekáváno na {kind} portu {listen_address}:{port}" -#: daemon.py:221 +#: daemon.py:225 #, python-brace-format msgid "Listening on {kind} port {port}" msgstr "Spojení očekáváno na {kind} portu {port}" -#: daemon.py:291 +#: daemon.py:296 #, python-brace-format msgid "Connect to {host}:{port}" msgstr "Připojit k {host}:{port}" -#: daemon.py:299 +#: daemon.py:304 #, python-brace-format msgid "Cannot connect to {host}:{port}" msgstr "Nedaří se připojit k {host}:{port}" @@ -102,14 +102,12 @@ msgid "Use the language preference set in the browser" msgstr "Použít upřednostňovaný jazyk nastavený ve webovém prohlížeči" #: menu.py:106 -#, fuzzy -#| msgid "Public Visibility" msgid "Visibility" -msgstr "Viditelnost na veřejnosti" +msgstr "Viditelnost" #: menu.py:108 msgid "Data" -msgstr "" +msgstr "Data" #: menu.py:110 templates/base.html:131 msgid "System" @@ -121,10 +119,8 @@ msgid "Security" msgstr "Zabezpečení" #: menu.py:114 -#, fuzzy -#| msgid "Server Administration" msgid "Administration" -msgstr "Správa serveru" +msgstr "Správa" #: middleware.py:131 msgid "System is possibly under heavy load. Please retry later." @@ -143,12 +139,12 @@ msgstr "Webový server" msgid "{box_name} Web Interface (Plinth)" msgstr "Webové rozhraní {box_name} (Plinth)" -#: modules/apache/components.py:159 +#: modules/apache/components.py:162 #, python-brace-format msgid "Access URL {url} on tcp{kind}" msgstr "Přístup na URL adrese {url} na tcp{kind}" -#: modules/apache/components.py:162 +#: modules/apache/components.py:165 #, python-brace-format msgid "Access URL {url}" msgstr "Přístup na URL adrese {url}" @@ -913,7 +909,7 @@ msgstr "Smazat" msgid "Admin" msgstr "správce" -#: modules/bepasty/views.py:88 modules/diagnostics/views.py:52 +#: modules/bepasty/views.py:88 modules/diagnostics/views.py:55 #: modules/nextcloud/views.py:62 modules/searx/views.py:35 #: modules/searx/views.py:46 modules/security/views.py:56 #: modules/snapshot/views.py:158 modules/tor/views.py:73 @@ -1556,28 +1552,20 @@ msgid "Go to diagnostics results" msgstr "Přejít na výsledky diagnostiky" #: modules/diagnostics/forms.py:11 -#, fuzzy -#| msgid "Enable Subdomains" msgid "Enable daily run" -msgstr "Zapnout podřízené domény" +msgstr "Zapnout denní rutiny" #: modules/diagnostics/forms.py:12 -#, fuzzy -#| msgid "When enabled, FreedomBox automatically updates once a day." msgid "When enabled, diagnostic checks will run once a day." -msgstr "Když je zapnuto, FreedomBox se jednou denně automaticky zaktualizuje." +msgstr "Když je zapnuto, diagnostické kontroly proběhnou jednou denně." #: modules/diagnostics/templates/diagnostics.html:11 -#, fuzzy -#| msgid "Diagnostics" msgid "Diagnostics Run" -msgstr "Diagnostika" +msgstr "Spuštění Diagnostiky" #: modules/diagnostics/templates/diagnostics.html:17 -#, fuzzy -#| msgid "Run Diagnostics" msgid "Run Diagnostics Now" -msgstr "Spustit diagnostiku" +msgstr "Spustit diagnostiku teď" #: modules/diagnostics/templates/diagnostics.html:22 msgid "View Results" @@ -1594,9 +1582,8 @@ msgstr "Aplikace: %(app_name)s" #: modules/diagnostics/templates/diagnostics_app.html:20 #: modules/diagnostics/templates/diagnostics_full.html:48 -#: templates/toolbar.html:53 -msgid "Re-run setup" -msgstr "Znovu spustit nastavení" +msgid "Try to repair" +msgstr "" #: modules/diagnostics/templates/diagnostics_app.html:32 msgid "This app does not support diagnostics" @@ -1638,10 +1625,15 @@ msgstr "Vyzkoušet" msgid "Result" msgstr "Výsledek" -#: modules/diagnostics/views.py:111 +#: modules/diagnostics/views.py:114 msgid "Diagnostic Test" msgstr "Diagnostické testy" +#: modules/diagnostics/views.py:144 +#, python-brace-format +msgid "App {app_id} is not installed, cannot repair" +msgstr "" + #: modules/dynamicdns/__init__.py:28 #, python-brace-format msgid "" @@ -1832,7 +1824,7 @@ msgstr "Stav" #: modules/dynamicdns/templates/dynamicdns.html:18 #: modules/email/templates/email.html:35 #: modules/letsencrypt/templates/letsencrypt.html:24 -#: modules/mediawiki/forms.py:64 modules/nextcloud/forms.py:26 +#: modules/mediawiki/forms.py:64 msgid "Domain" msgstr "Doména" @@ -2230,12 +2222,12 @@ msgstr "Existují pravidla přímého prostupu" msgid "Port {name} ({details}) available for internal networks" msgstr "Port {name} ({details}) dostupný pro interní sítě" -#: modules/firewall/components.py:153 +#: modules/firewall/components.py:154 #, python-brace-format msgid "Port {name} ({details}) available for external networks" msgstr "Port {name} ({details}) dostupný pro externí sítě" -#: modules/firewall/components.py:159 +#: modules/firewall/components.py:160 #, python-brace-format msgid "Port {name} ({details}) unavailable for external networks" msgstr "Port {name} {details} je nedostupný pro externí sítě" @@ -3308,10 +3300,6 @@ msgstr "Let's Encrypt" msgid "Certificates" msgstr "Certifikáty" -#: modules/letsencrypt/__init__.py:105 -msgid "Cannot test: No domains are configured." -msgstr "Nelze testovat: Nejsou nakonfigurovány žádné domény." - #: modules/letsencrypt/templates/letsencrypt.html:25 msgid "Certificate Status" msgstr "Stav certifikátu" @@ -3383,10 +3371,9 @@ msgstr "" "trvat, než se změna projeví." #: modules/letsencrypt/views.py:46 -#, fuzzy, python-brace-format -#| msgid "Failed to revoke certificate for domain {domain}: {error}" +#, python-brace-format msgid "Failed to revoke certificate for domain {domain}" -msgstr "Nepodařilo se odvolat platnost certifikátu prodoménu {domain}: {error}" +msgstr "Nepodařilo se odvolat platnost certifikátu pro doménu {domain}" #: modules/letsencrypt/views.py:59 modules/letsencrypt/views.py:77 #, python-brace-format @@ -3394,10 +3381,9 @@ msgid "Certificate successfully obtained for domain {domain}" msgstr "Úspěšně obdržen certifikát pro doménu {domain}" #: modules/letsencrypt/views.py:64 modules/letsencrypt/views.py:82 -#, fuzzy, python-brace-format -#| msgid "Failed to obtain certificate for domain {domain}: {error}" +#, python-brace-format msgid "Failed to obtain certificate for domain {domain}" -msgstr "Nepodařilo se získat certifikát pro doménu {domain}: {error}" +msgstr "Nepodařilo se získat certifikát pro doménu {domain}" #: modules/letsencrypt/views.py:95 #, python-brace-format @@ -3405,10 +3391,9 @@ msgid "Certificate successfully deleted for domain {domain}" msgstr "Certifikát pro doménu {domain} úspěšně smazán" #: modules/letsencrypt/views.py:100 -#, fuzzy, python-brace-format -#| msgid "Failed to delete certificate for domain {domain}: {error}" +#, python-brace-format msgid "Failed to delete certificate for domain {domain}" -msgstr "Nepodařilo se smazat certifikát pro doménu {domain}: {error}" +msgstr "Nepodařilo se smazat certifikát pro doménu {domain}" #: modules/matrixsynapse/__init__.py:26 msgid "" @@ -3833,7 +3818,7 @@ msgstr "Pokud je vypnuto, postavy hráčů nemohou zemřít nebo se zranit." msgid "Address" msgstr "Adresa" -#: modules/minidlna/__init__.py:22 +#: modules/minidlna/__init__.py:20 msgid "" "MiniDLNA is a simple media server software, with the aim of being fully " "compliant with DLNA/UPnP-AV clients. The MiniDLNA daemon serves media files " @@ -3851,34 +3836,23 @@ msgstr "" "televizory a herní systémy (například PS3 a Xbox 360) nebo aplikace, jako " "jsou totem a Kodi." -#: modules/minidlna/__init__.py:44 -msgid "Media streaming server" -msgstr "Server pro streamování médií" - -#: modules/minidlna/__init__.py:47 +#: modules/minidlna/__init__.py:45 msgid "MiniDLNA" msgstr "MiniDLNA" -#: modules/minidlna/__init__.py:48 +#: modules/minidlna/__init__.py:46 msgid "Simple Media Server" msgstr "Simple Media Server" -#: modules/minidlna/forms.py:13 +#: modules/minidlna/forms.py:20 msgid "Media Files Directory" msgstr "Adresář souborů médií" -#: modules/minidlna/forms.py:14 +#: modules/minidlna/forms.py:21 msgid "" "Directory that MiniDLNA Server will read for content. All sub-directories of " -"this will be also scanned for media files. If you change the default ensure " -"that the new directory exists and that is readable from the \"minidlna\" " -"user. Any user media directories (\"/home/username/\") will usually work." +"this will be also scanned for media files." msgstr "" -"Adresář, do kterého bude server MiniDLNA načítat obsah. Všechny jeho " -"podadresáře budou rovněž prohledávány kvůli mediálním souborům. Pokud " -"změníte výchozí nastavení, ujistěte se, že nový adresář existuje a že je " -"čitelný pro uživatele \"minidlna\". Obvykle budou fungovat jakékoli adresáře " -"médií uživatele (\"/home/username/\")." #: modules/minidlna/manifest.py:10 msgid "vlc" @@ -3896,11 +3870,7 @@ msgstr "yaacc" msgid "totem" msgstr "totem" -#: modules/minidlna/views.py:35 -msgid "Specified directory does not exist." -msgstr "Zadaný adresář neexistuje." - -#: modules/minidlna/views.py:38 +#: modules/minidlna/views.py:33 msgid "Updated media directory" msgstr "Aktualizovaný adresář médií" @@ -5180,7 +5150,7 @@ msgstr "Připojení {name} smazáno." msgid "Failed to delete connection: Connection not found." msgstr "Smazání připojení se nezdařilo: Připojení nenalezeno." -#: modules/nextcloud/__init__.py:20 +#: modules/nextcloud/__init__.py:25 msgid "" "Nextcloud is a self-hosted productivity platform which provides private and " "secure functions for file sharing, collaborative work, and more. Nextcloud " @@ -5188,87 +5158,104 @@ msgid "" "and mobile clients. The Nextcloud server provides a well integrated web " "interface." msgstr "" +"Nextcloud je samostatně hostovaná platforma pro produktivitu, která " +"poskytuje soukromé a bezpečné funkce pro sdílení souborů, spolupráci a " +"další. Nextcloud zahrnuje server Nextcloud, klientské aplikace pro stolní " +"počítače a mobilní klienty. Server Nextcloud poskytuje dobře integrované " +"webové rozhraní." -#: modules/nextcloud/__init__.py:25 +#: modules/nextcloud/__init__.py:30 msgid "All users of FreedomBox can use Nextcloud. To perform administrative " msgstr "" +"Nextcloud mohou používat všichni uživatelé služby FreedomBox. Chcete-li " +"provést správu " -#: modules/nextcloud/__init__.py:29 -#, python-brace-format +#: modules/nextcloud/__init__.py:34 +#, fuzzy, python-brace-format +#| msgid "" +#| "Please note that Nextcloud is installed and run inside a container " +#| "provided by the Nextcloud project. Security, quality, privacy and legal " +#| "reviews are done by the upstream project and not by Debian/{box_name}. " +#| "Updates are performed following an independent cycle." msgid "" "Please note that Nextcloud is installed and run inside a container provided " -"by the Nextcloud project. Security, quality, privacy and legal reviews are " +"by the Nextcloud community. Security, quality, privacy and legal reviews are " "done by the upstream project and not by Debian/{box_name}. Updates are " "performed following an independent cycle." msgstr "" +"Upozorňujeme, že Nextcloud je nainstalován a spuštěn v kontejneru " +"poskytnutém projektem Nextcloud. Bezpečnost, kvalitu, ochranu soukromí a " +"právní kontrolu provádí projekt upstream, nikoli Debian/{box_name}. " +"Aktualizace jsou prováděny podle nezávislého cyklu." -#: modules/nextcloud/__init__.py:35 -#, fuzzy -#| msgid "Uninstalling an app is an experimental feature." +#: modules/nextcloud/__init__.py:40 msgid "This app is experimental." -msgstr "Odinstalování aplikace je exprimentální funkce." +msgstr "Tato aplikace je experimentální." -#: modules/nextcloud/__init__.py:53 modules/nextcloud/manifest.py:11 +#: modules/nextcloud/__init__.py:58 modules/nextcloud/manifest.py:11 #: modules/nextcloud/manifest.py:18 -#, fuzzy -#| msgid "Next" msgid "Nextcloud" -msgstr "Další" +msgstr "Nextcloud" -#: modules/nextcloud/__init__.py:55 +#: modules/nextcloud/__init__.py:60 msgid "File Storage & Collaboration" -msgstr "" +msgstr "Ukládání souborů a spolupráce" #: modules/nextcloud/forms.py:19 -#, fuzzy -#| msgid "Hostname set" msgid "Not set" -msgstr "Nastavení názvu stroje" +msgstr "Nenastaveno" + +#: modules/nextcloud/forms.py:26 +#, fuzzy +#| msgid "Server domain" +msgid "Override domain" +msgstr "Doména serveru" #: modules/nextcloud/forms.py:27 -msgid "Examples: \"myfreedombox.example.org\" or \"example.onion\"." -msgstr "" - -#: modules/nextcloud/forms.py:30 #, fuzzy -#| msgid "Administrator Password" +#| msgid "" +#| "Used by MediaWiki to generate URLs that point to the wiki such as in " +#| "footer, feeds and emails. Examples: \"myfreedombox.example.org\" or " +#| "\"example.onion\"." +msgid "" +"Set to the domain or IP address that Nextcloud should be forced to generate " +"URLs with. Should not be needed if a valid domain is used to access " +"Nextcloud. Examples: \"myfreedombox.example.org\" or \"example.onion\"." +msgstr "" +"Používá se v MediaWiki k vytváření adres URL, které odkazují na wiki, " +"například v zápatí, kanálech a e-mailech. Příklad: \"myfreedombox.example." +"org\" nebo \"example.onion\"." + +#: modules/nextcloud/forms.py:33 msgid "Administrator password" msgstr "Heslo k účtu správce" -#: modules/nextcloud/forms.py:31 -#, fuzzy -#| msgid "" -#| "Set a new password for MediaWiki's administrator account (admin). The " -#| "password cannot be a common one and the minimum required length is " -#| "10 characters. Leave this field blank to keep the " -#| "current password." +#: modules/nextcloud/forms.py:34 msgid "" "Optional. Set a new password for Nextcloud's administrator account " "(nextcloud-admin). The password cannot be a common one and the minimum " "required length is 10 characters. Leave this field blank to " "keep the current password." msgstr "" -"Nastavte nové heslo pro účet správce MediaWiki (admin). Heslo nemůže být " -"běžné a minimální požadovaná délka je 10 znaků. Chcete-li " -"zachovat aktuální heslo, ponechte toto pole prázdné." +"Nastavte nové heslo pro účet správce Nextcloud (nextcloud-admin). Heslo " +"nemůže být běžné a minimální požadovaná délka je 10 znaků. " +"Chcete-li zachovat aktuální heslo, ponechte toto pole prázdné." -#: modules/nextcloud/forms.py:38 -#, fuzzy -#| msgid "Default zone is external" +#: modules/nextcloud/forms.py:41 msgid "Default phone region" -msgstr "Výchozí zóna je externí" +msgstr "Výchozí region telefonu" -#: modules/nextcloud/forms.py:39 +#: modules/nextcloud/forms.py:42 msgid "" "The default phone region is required to validate phone numbers in the " "profile settings without a country code." msgstr "" +"Výchozí telefonní předvolba je vyžadována pro ověření telefonních čísel v " +"nastavení profilu bez kódu země." #: modules/nextcloud/views.py:53 -#, fuzzy -#| msgid "Password update failed. Please choose a stronger password" msgid "Password update failed. Please choose a stronger password." -msgstr "Aktualizace hesla se nezdařila. Zvolte prosím silnější heslo" +msgstr "Aktualizace hesla se nezdařila. Zvolte prosím silnější heslo." #: modules/openvpn/__init__.py:20 #, python-brace-format @@ -6769,20 +6756,16 @@ msgid "Created snapshot." msgstr "Zachycený stav pořízen." #: modules/snapshot/views.py:160 -#, fuzzy -#| msgid "Configuration updated." msgid "Configuration update failed." -msgstr "Nastavení aktualizována." +msgstr "Aktualizace konfigurace se nezdařila." #: modules/snapshot/views.py:184 msgid "Deleted selected snapshots" msgstr "Označené zachycené stavy smazány" #: modules/snapshot/views.py:186 -#, fuzzy -#| msgid "Deleting LDAP user failed." msgid "Deleting snapshot failed." -msgstr "Smazání LDAP uživatele se nezdařilo." +msgstr "Smazání snapshotu se nezdařilo." #: modules/snapshot/views.py:189 msgid "Snapshot is currently in use. Please try again later." @@ -7012,18 +6995,16 @@ msgid "" "You cannot save configuration changes. Try rebooting the system. If the " "problem persists after a reboot, check the storage device for errors." msgstr "" +"Změny konfigurace nelze uložit. Zkuste restartovat systém. Pokud problém " +"přetrvává i po restartu, zkontrolujte, zda úložné zařízení neobsahuje chyby." #: modules/storage/__init__.py:382 -#, fuzzy -#| msgid "Root Filesystem" msgid "Read-only root filesystem" -msgstr "Kořenový souborový systém" +msgstr "Kořenový souborový systém pouze pro čtení" #: modules/storage/__init__.py:392 -#, fuzzy -#| msgid "Go to Networks" msgid "Go to Power" -msgstr "Přejít na Sítě" +msgstr "Přejít na Síť" #: modules/storage/forms.py:63 msgid "Invalid directory name." @@ -7134,10 +7115,8 @@ msgid "Device can be safely unplugged." msgstr "Zařízení je možné bezpečně odebrat." #: modules/storage/views.py:93 -#, fuzzy -#| msgid "Error ejecting device: {error_message}" msgid "Error ejecting device." -msgstr "Chyba při vysouvání zařízení: {error_message}" +msgstr "Chyba při vysouvání zařízení." #: modules/syncthing/__init__.py:23 msgid "" @@ -7739,10 +7718,8 @@ msgid "Test distribution upgrade now" msgstr "Otestujte aktualizaci distribuce" #: modules/upgrades/views.py:73 -#, fuzzy -#| msgid "Error when configuring unattended-upgrades: {error}" msgid "Error when configuring unattended-upgrades" -msgstr "Chyba při nastavování bezobslužných aktualizací: {error}" +msgstr "Chyba při nastavování bezobslužných aktualizací" #: modules/upgrades/views.py:120 msgid "Upgrade process started." @@ -7815,6 +7792,7 @@ msgstr "" msgid "" "Optional. Used to send emails to reset password and important notifications." msgstr "" +"Volitelně. Slouží k zasílání e-mailů pro reset hesla a důležitých oznámení." #: modules/users/forms.py:106 msgid "" @@ -8535,77 +8513,112 @@ msgid "Finished: {name}" msgstr "Dokončeno: {name}" #: package.py:206 -#, fuzzy, python-brace-format -#| msgid "Package {expression} is not available for install" +#, python-brace-format msgid "Package {package_expression} is not available for install" -msgstr "Balíček {expression} není k dispozici pro instalaci" +msgstr "Balíček {package_expression} není k dispozici pro instalaci" #: package.py:226 #, python-brace-format msgid "Package {package_name} is the latest version ({latest_version})" msgstr "Balíček {package_name} je nejnovější verze ({latest_version})" -#: package.py:419 +#: package.py:420 msgid "installing" msgstr "Instalace" -#: package.py:421 +#: package.py:422 msgid "downloading" msgstr "stahování" -#: package.py:423 +#: package.py:424 msgid "media change" msgstr "změna média" -#: package.py:425 +#: package.py:426 #, python-brace-format msgid "configuration file: {file}" msgstr "soubor s nastaveními: {file}" -#: package.py:453 package.py:478 +#: package.py:454 package.py:479 msgid "Timeout waiting for package manager" msgstr "Časový limit čekání na správce balíčků" -#: setup.py:39 +#: setup.py:42 msgid "Installing app" msgstr "Instalace aplikací" -#: setup.py:41 +#: setup.py:44 msgid "Updating app" msgstr "Aktualizace aplikací" -#: setup.py:75 +#: setup.py:78 #, python-brace-format msgid "Error installing app: {error}" msgstr "Chyba při instalaci aplikace: {error}" -#: setup.py:78 +#: setup.py:81 setup.py:151 +#, fuzzy, python-brace-format +#| msgid "Error updating app: {error}" +msgid "Error repairing app: {error}" +msgstr "Chyba při aktualizaci aplikace: {error}" + +#: setup.py:84 #, python-brace-format msgid "Error updating app: {error}" msgstr "Chyba při aktualizaci aplikace: {error}" -#: setup.py:82 +#: setup.py:88 msgid "App installed." msgstr "Aplikace nainstalována." -#: setup.py:84 +#: setup.py:92 msgid "App updated" msgstr "Aplikace aktualizována" -#: setup.py:101 +#: setup.py:110 +#, fuzzy +#| msgid "Updating app" +msgid "Repairing app" +msgstr "Aktualizace aplikací" + +#: setup.py:130 +#, fuzzy, python-brace-format +#| msgid "Error uninstalling app: {error}" +msgid "Error running diagnostics: {error}" +msgstr "Chyba při odinstalaci aplikace: {error}" + +#: setup.py:143 +msgid "Skipping repair, no failed checks" +msgstr "" + +#: setup.py:157 +msgid "Re-running setup to complete repairs" +msgstr "" + +#: setup.py:165 +#, fuzzy +#| msgid "App updated" +msgid "App repaired." +msgstr "Aplikace aktualizována" + +#: setup.py:169 +msgid "App repair completed with errors:\n" +msgstr "" + +#: setup.py:189 msgid "Uninstalling app" msgstr "Odinstalování aplikace" -#: setup.py:117 +#: setup.py:205 #, python-brace-format msgid "Error uninstalling app: {error}" msgstr "Chyba při odinstalaci aplikace: {error}" -#: setup.py:120 +#: setup.py:208 msgid "App uninstalled." msgstr "Aplikace odinstalována." -#: setup.py:493 +#: setup.py:581 msgid "Updating app packages" msgstr "Aktualizace balíčků aplikací" @@ -8976,6 +8989,10 @@ msgstr "Aktualizovat" msgid "Backup" msgstr "Záloha" +#: templates/toolbar.html:53 +msgid "Re-run setup" +msgstr "Znovu spustit nastavení" + #: templates/toolbar.html:59 templates/toolbar.html:60 #: templates/uninstall.html:30 msgid "Uninstall" @@ -9007,6 +9024,31 @@ msgstr "před odinstalací {app_id}" msgid "Gujarati" msgstr "gudžarátština" +#~ msgid "Cannot test: No domains are configured." +#~ msgstr "Nelze testovat: Nejsou nakonfigurovány žádné domény." + +#~ msgid "Media streaming server" +#~ msgstr "Server pro streamování médií" + +#~ msgid "" +#~ "Directory that MiniDLNA Server will read for content. All sub-directories " +#~ "of this will be also scanned for media files. If you change the default " +#~ "ensure that the new directory exists and that is readable from the " +#~ "\"minidlna\" user. Any user media directories (\"/home/username/\") will " +#~ "usually work." +#~ msgstr "" +#~ "Adresář, do kterého bude server MiniDLNA načítat obsah. Všechny jeho " +#~ "podadresáře budou rovněž prohledávány kvůli mediálním souborům. Pokud " +#~ "změníte výchozí nastavení, ujistěte se, že nový adresář existuje a že je " +#~ "čitelný pro uživatele \"minidlna\". Obvykle budou fungovat jakékoli " +#~ "adresáře médií uživatele (\"/home/username/\")." + +#~ msgid "Specified directory does not exist." +#~ msgstr "Zadaný adresář neexistuje." + +#~ msgid "Examples: \"myfreedombox.example.org\" or \"example.onion\"." +#~ msgstr "Příklady: \"myfreedombox.example.org\" nebo \"example.onion\"." + #~ msgid "Storage snapshots configuration updated" #~ msgstr "Nastavení zachycování stavů úložiště aktualizováno" diff --git a/plinth/locale/da/LC_MESSAGES/django.po b/plinth/locale/da/LC_MESSAGES/django.po index eea4b4eea..db4c74bb7 100644 --- a/plinth/locale/da/LC_MESSAGES/django.po +++ b/plinth/locale/da/LC_MESSAGES/django.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: FreedomBox UI\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-04-22 20:02-0400\n" +"POT-Creation-Date: 2024-05-06 20:18-0400\n" "PO-Revision-Date: 2022-09-14 17:19+0000\n" "Last-Translator: ikmaak \n" "Language-Team: Danish \n" "Language-Team: German 10 Zeichen lang sein. Lassen Sie das Feld leer, bleibt das " "derzeitige Passwort bestehen." -#: modules/nextcloud/forms.py:38 +#: modules/nextcloud/forms.py:41 #, fuzzy #| msgid "Default zone is external" msgid "Default phone region" msgstr "Standard-Zone ist extern" -#: modules/nextcloud/forms.py:39 +#: modules/nextcloud/forms.py:42 msgid "" "The default phone region is required to validate phone numbers in the " "profile settings without a country code." @@ -8717,67 +8718,103 @@ msgstr "Paket {package_expression} ist nicht zur Installation verfügbar" msgid "Package {package_name} is the latest version ({latest_version})" msgstr "Paket {package_name} ist die aktuellste Version ({latest_version})" -#: package.py:419 +#: package.py:420 msgid "installing" msgstr "Installation läuft" -#: package.py:421 +#: package.py:422 msgid "downloading" msgstr "herunterladen" -#: package.py:423 +#: package.py:424 msgid "media change" msgstr "Medienwechsel" -#: package.py:425 +#: package.py:426 #, python-brace-format msgid "configuration file: {file}" msgstr "Konfigurationsdatei: {file}" -#: package.py:453 package.py:478 +#: package.py:454 package.py:479 msgid "Timeout waiting for package manager" msgstr "Zeitüberschreitung beim Warten auf den Paket-Manager" -#: setup.py:39 +#: setup.py:42 msgid "Installing app" msgstr "Installation der App" -#: setup.py:41 +#: setup.py:44 msgid "Updating app" msgstr "Aktualisieren der App" -#: setup.py:75 +#: setup.py:78 #, python-brace-format msgid "Error installing app: {error}" msgstr "Fehler bei der Installation der App: {error}" -#: setup.py:78 +#: setup.py:81 setup.py:151 +#, fuzzy, python-brace-format +#| msgid "Error updating app: {error}" +msgid "Error repairing app: {error}" +msgstr "Fehler beim Aktualisieren der App: {error}" + +#: setup.py:84 #, python-brace-format msgid "Error updating app: {error}" msgstr "Fehler beim Aktualisieren der App: {error}" -#: setup.py:82 +#: setup.py:88 msgid "App installed." msgstr "App installiert." -#: setup.py:84 +#: setup.py:92 msgid "App updated" msgstr "App aktualisiert" -#: setup.py:101 +#: setup.py:110 +#, fuzzy +#| msgid "Updating app" +msgid "Repairing app" +msgstr "Aktualisieren der App" + +#: setup.py:130 +#, fuzzy, python-brace-format +#| msgid "Error uninstalling app: {error}" +msgid "Error running diagnostics: {error}" +msgstr "Fehler bei der Deinstallation der App: {error}" + +#: setup.py:143 +msgid "Skipping repair, no failed checks" +msgstr "" + +#: setup.py:157 +msgid "Re-running setup to complete repairs" +msgstr "" + +#: setup.py:165 +#, fuzzy +#| msgid "App updated" +msgid "App repaired." +msgstr "App aktualisiert" + +#: setup.py:169 +msgid "App repair completed with errors:\n" +msgstr "" + +#: setup.py:189 msgid "Uninstalling app" msgstr "Deinstallation der App" -#: setup.py:117 +#: setup.py:205 #, python-brace-format msgid "Error uninstalling app: {error}" msgstr "Fehler bei der Deinstallation der App: {error}" -#: setup.py:120 +#: setup.py:208 msgid "App uninstalled." msgstr "App deinstalliert." -#: setup.py:493 +#: setup.py:581 msgid "Updating app packages" msgstr "Aktualisieren von App-Paketen" @@ -9152,6 +9189,10 @@ msgstr "Aktualisieren" msgid "Backup" msgstr "Sicherungskopie" +#: templates/toolbar.html:53 +msgid "Re-run setup" +msgstr "Einrichtung erneut ausführen" + #: templates/toolbar.html:59 templates/toolbar.html:60 #: templates/uninstall.html:30 msgid "Uninstall" @@ -9183,6 +9224,29 @@ msgstr "vor der Deinstallation von {app_id}" msgid "Gujarati" msgstr "Gujarati" +#~ msgid "Cannot test: No domains are configured." +#~ msgstr "Kann nicht testen: Es sind keine Domains konfiguriert." + +#~ msgid "Media streaming server" +#~ msgstr "Medien-Streaming-Server" + +#~ msgid "" +#~ "Directory that MiniDLNA Server will read for content. All sub-directories " +#~ "of this will be also scanned for media files. If you change the default " +#~ "ensure that the new directory exists and that is readable from the " +#~ "\"minidlna\" user. Any user media directories (\"/home/username/\") will " +#~ "usually work." +#~ msgstr "" +#~ "Verzeichnis, in dem MiniDLNA Server nach Inhalten sucht. Alle " +#~ "Unterverzeichnisse davon werden ebenfalls nach Mediendateien durchsucht. " +#~ "Wenn Sie die Standardeinstellung ändern, stellen Sie sicher, dass das " +#~ "neue Verzeichnis vorhanden und für den Benutzer \"minidlna\" lesbar ist. " +#~ "Alle Benutzermedienverzeichnisse (\"/ home / username /\") funktionieren " +#~ "normalerweise." + +#~ msgid "Specified directory does not exist." +#~ msgstr "Das angegebene Verzeichnis ist nicht vorhanden." + #~ msgid "Storage snapshots configuration updated" #~ msgstr "Konfiguration der Speicherauszüge aktualisiert" diff --git a/plinth/locale/django.pot b/plinth/locale/django.pot index 30bfdefa9..efd31b7d5 100644 --- a/plinth/locale/django.pot +++ b/plinth/locale/django.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-04-22 20:02-0400\n" +"POT-Creation-Date: 2024-05-06 20:18-0400\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -26,27 +26,27 @@ msgstr "" msgid "FreedomBox" msgstr "" -#: daemon.py:122 +#: daemon.py:124 #, python-brace-format msgid "Service {service_name} is running" msgstr "" -#: daemon.py:218 +#: daemon.py:222 #, python-brace-format msgid "Listening on {kind} port {listen_address}:{port}" msgstr "" -#: daemon.py:221 +#: daemon.py:225 #, python-brace-format msgid "Listening on {kind} port {port}" msgstr "" -#: daemon.py:291 +#: daemon.py:296 #, python-brace-format msgid "Connect to {host}:{port}" msgstr "" -#: daemon.py:299 +#: daemon.py:304 #, python-brace-format msgid "Cannot connect to {host}:{port}" msgstr "" @@ -133,12 +133,12 @@ msgstr "" msgid "{box_name} Web Interface (Plinth)" msgstr "" -#: modules/apache/components.py:159 +#: modules/apache/components.py:162 #, python-brace-format msgid "Access URL {url} on tcp{kind}" msgstr "" -#: modules/apache/components.py:162 +#: modules/apache/components.py:165 #, python-brace-format msgid "Access URL {url}" msgstr "" @@ -843,7 +843,7 @@ msgstr "" msgid "Admin" msgstr "" -#: modules/bepasty/views.py:88 modules/diagnostics/views.py:52 +#: modules/bepasty/views.py:88 modules/diagnostics/views.py:55 #: modules/nextcloud/views.py:62 modules/searx/views.py:35 #: modules/searx/views.py:46 modules/security/views.py:56 #: modules/snapshot/views.py:158 modules/tor/views.py:73 @@ -1447,8 +1447,7 @@ msgstr "" #: modules/diagnostics/templates/diagnostics_app.html:20 #: modules/diagnostics/templates/diagnostics_full.html:48 -#: templates/toolbar.html:53 -msgid "Re-run setup" +msgid "Try to repair" msgstr "" #: modules/diagnostics/templates/diagnostics_app.html:32 @@ -1488,10 +1487,15 @@ msgstr "" msgid "Result" msgstr "" -#: modules/diagnostics/views.py:111 +#: modules/diagnostics/views.py:114 msgid "Diagnostic Test" msgstr "" +#: modules/diagnostics/views.py:144 +#, python-brace-format +msgid "App {app_id} is not installed, cannot repair" +msgstr "" + #: modules/dynamicdns/__init__.py:28 #, python-brace-format msgid "" @@ -1649,7 +1653,7 @@ msgstr "" #: modules/dynamicdns/templates/dynamicdns.html:18 #: modules/email/templates/email.html:35 #: modules/letsencrypt/templates/letsencrypt.html:24 -#: modules/mediawiki/forms.py:64 modules/nextcloud/forms.py:26 +#: modules/mediawiki/forms.py:64 msgid "Domain" msgstr "" @@ -1997,12 +2001,12 @@ msgstr "" msgid "Port {name} ({details}) available for internal networks" msgstr "" -#: modules/firewall/components.py:153 +#: modules/firewall/components.py:154 #, python-brace-format msgid "Port {name} ({details}) available for external networks" msgstr "" -#: modules/firewall/components.py:159 +#: modules/firewall/components.py:160 #, python-brace-format msgid "Port {name} ({details}) unavailable for external networks" msgstr "" @@ -2920,10 +2924,6 @@ msgstr "" msgid "Certificates" msgstr "" -#: modules/letsencrypt/__init__.py:105 -msgid "Cannot test: No domains are configured." -msgstr "" - #: modules/letsencrypt/templates/letsencrypt.html:25 msgid "Certificate Status" msgstr "" @@ -3361,7 +3361,7 @@ msgstr "" msgid "Address" msgstr "" -#: modules/minidlna/__init__.py:22 +#: modules/minidlna/__init__.py:20 msgid "" "MiniDLNA is a simple media server software, with the aim of being fully " "compliant with DLNA/UPnP-AV clients. The MiniDLNA daemon serves media files " @@ -3372,28 +3372,22 @@ msgid "" "Kodi." msgstr "" -#: modules/minidlna/__init__.py:44 -msgid "Media streaming server" -msgstr "" - -#: modules/minidlna/__init__.py:47 +#: modules/minidlna/__init__.py:45 msgid "MiniDLNA" msgstr "" -#: modules/minidlna/__init__.py:48 +#: modules/minidlna/__init__.py:46 msgid "Simple Media Server" msgstr "" -#: modules/minidlna/forms.py:13 +#: modules/minidlna/forms.py:20 msgid "Media Files Directory" msgstr "" -#: modules/minidlna/forms.py:14 +#: modules/minidlna/forms.py:21 msgid "" "Directory that MiniDLNA Server will read for content. All sub-directories of " -"this will be also scanned for media files. If you change the default ensure " -"that the new directory exists and that is readable from the \"minidlna\" " -"user. Any user media directories (\"/home/username/\") will usually work." +"this will be also scanned for media files." msgstr "" #: modules/minidlna/manifest.py:10 @@ -3412,11 +3406,7 @@ msgstr "" msgid "totem" msgstr "" -#: modules/minidlna/views.py:35 -msgid "Specified directory does not exist." -msgstr "" - -#: modules/minidlna/views.py:38 +#: modules/minidlna/views.py:33 msgid "Updated media directory" msgstr "" @@ -4540,7 +4530,7 @@ msgstr "" msgid "Failed to delete connection: Connection not found." msgstr "" -#: modules/nextcloud/__init__.py:20 +#: modules/nextcloud/__init__.py:25 msgid "" "Nextcloud is a self-hosted productivity platform which provides private and " "secure functions for file sharing, collaborative work, and more. Nextcloud " @@ -4549,29 +4539,29 @@ msgid "" "interface." msgstr "" -#: modules/nextcloud/__init__.py:25 +#: modules/nextcloud/__init__.py:30 msgid "All users of FreedomBox can use Nextcloud. To perform administrative " msgstr "" -#: modules/nextcloud/__init__.py:29 +#: modules/nextcloud/__init__.py:34 #, python-brace-format msgid "" "Please note that Nextcloud is installed and run inside a container provided " -"by the Nextcloud project. Security, quality, privacy and legal reviews are " +"by the Nextcloud community. Security, quality, privacy and legal reviews are " "done by the upstream project and not by Debian/{box_name}. Updates are " "performed following an independent cycle." msgstr "" -#: modules/nextcloud/__init__.py:35 +#: modules/nextcloud/__init__.py:40 msgid "This app is experimental." msgstr "" -#: modules/nextcloud/__init__.py:53 modules/nextcloud/manifest.py:11 +#: modules/nextcloud/__init__.py:58 modules/nextcloud/manifest.py:11 #: modules/nextcloud/manifest.py:18 msgid "Nextcloud" msgstr "" -#: modules/nextcloud/__init__.py:55 +#: modules/nextcloud/__init__.py:60 msgid "File Storage & Collaboration" msgstr "" @@ -4579,15 +4569,22 @@ msgstr "" msgid "Not set" msgstr "" -#: modules/nextcloud/forms.py:27 -msgid "Examples: \"myfreedombox.example.org\" or \"example.onion\"." +#: modules/nextcloud/forms.py:26 +msgid "Override domain" msgstr "" -#: modules/nextcloud/forms.py:30 +#: modules/nextcloud/forms.py:27 +msgid "" +"Set to the domain or IP address that Nextcloud should be forced to generate " +"URLs with. Should not be needed if a valid domain is used to access " +"Nextcloud. Examples: \"myfreedombox.example.org\" or \"example.onion\"." +msgstr "" + +#: modules/nextcloud/forms.py:33 msgid "Administrator password" msgstr "" -#: modules/nextcloud/forms.py:31 +#: modules/nextcloud/forms.py:34 msgid "" "Optional. Set a new password for Nextcloud's administrator account " "(nextcloud-admin). The password cannot be a common one and the minimum " @@ -4595,11 +4592,11 @@ msgid "" "keep the current password." msgstr "" -#: modules/nextcloud/forms.py:38 +#: modules/nextcloud/forms.py:41 msgid "Default phone region" msgstr "" -#: modules/nextcloud/forms.py:39 +#: modules/nextcloud/forms.py:42 msgid "" "The default phone region is required to validate phone numbers in the " "profile settings without a country code." @@ -7427,67 +7424,97 @@ msgstr "" msgid "Package {package_name} is the latest version ({latest_version})" msgstr "" -#: package.py:419 +#: package.py:420 msgid "installing" msgstr "" -#: package.py:421 +#: package.py:422 msgid "downloading" msgstr "" -#: package.py:423 +#: package.py:424 msgid "media change" msgstr "" -#: package.py:425 +#: package.py:426 #, python-brace-format msgid "configuration file: {file}" msgstr "" -#: package.py:453 package.py:478 +#: package.py:454 package.py:479 msgid "Timeout waiting for package manager" msgstr "" -#: setup.py:39 +#: setup.py:42 msgid "Installing app" msgstr "" -#: setup.py:41 +#: setup.py:44 msgid "Updating app" msgstr "" -#: setup.py:75 -#, python-brace-format -msgid "Error installing app: {error}" -msgstr "" - #: setup.py:78 #, python-brace-format -msgid "Error updating app: {error}" +msgid "Error installing app: {error}" msgstr "" -#: setup.py:82 -msgid "App installed." +#: setup.py:81 setup.py:151 +#, python-brace-format +msgid "Error repairing app: {error}" msgstr "" #: setup.py:84 +#, python-brace-format +msgid "Error updating app: {error}" +msgstr "" + +#: setup.py:88 +msgid "App installed." +msgstr "" + +#: setup.py:92 msgid "App updated" msgstr "" -#: setup.py:101 +#: setup.py:110 +msgid "Repairing app" +msgstr "" + +#: setup.py:130 +#, python-brace-format +msgid "Error running diagnostics: {error}" +msgstr "" + +#: setup.py:143 +msgid "Skipping repair, no failed checks" +msgstr "" + +#: setup.py:157 +msgid "Re-running setup to complete repairs" +msgstr "" + +#: setup.py:165 +msgid "App repaired." +msgstr "" + +#: setup.py:169 +msgid "App repair completed with errors:\n" +msgstr "" + +#: setup.py:189 msgid "Uninstalling app" msgstr "" -#: setup.py:117 +#: setup.py:205 #, python-brace-format msgid "Error uninstalling app: {error}" msgstr "" -#: setup.py:120 +#: setup.py:208 msgid "App uninstalled." msgstr "" -#: setup.py:493 +#: setup.py:581 msgid "Updating app packages" msgstr "" @@ -7820,6 +7847,10 @@ msgstr "" msgid "Backup" msgstr "" +#: templates/toolbar.html:53 +msgid "Re-run setup" +msgstr "" + #: templates/toolbar.html:59 templates/toolbar.html:60 #: templates/uninstall.html:30 msgid "Uninstall" diff --git a/plinth/locale/el/LC_MESSAGES/django.po b/plinth/locale/el/LC_MESSAGES/django.po index 848b1d2c0..35cc2f822 100644 --- a/plinth/locale/el/LC_MESSAGES/django.po +++ b/plinth/locale/el/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-04-22 20:02-0400\n" +"POT-Creation-Date: 2024-05-06 20:18-0400\n" "PO-Revision-Date: 2022-09-14 17:20+0000\n" "Last-Translator: ikmaak \n" "Language-Team: Greek \n" "Language-Team: Spanish \n" @@ -17,7 +17,7 @@ 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 5.5-dev\n" +"X-Generator: Weblate 5.5.1-dev\n" #: config.py:103 #, python-brace-format @@ -28,27 +28,27 @@ msgstr "La configuración fija {etc_path} está configurada correctamente" msgid "FreedomBox" msgstr "FreedomBox" -#: daemon.py:122 +#: daemon.py:124 #, python-brace-format msgid "Service {service_name} is running" msgstr "{service_name} se está ejecutando" -#: daemon.py:218 +#: daemon.py:222 #, python-brace-format msgid "Listening on {kind} port {listen_address}:{port}" msgstr "Escuchando en el puerto {kind} {listen_address}:{port}" -#: daemon.py:221 +#: daemon.py:225 #, python-brace-format msgid "Listening on {kind} port {port}" msgstr "Escuchando en el puerto {port} {kind}" -#: daemon.py:291 +#: daemon.py:296 #, python-brace-format msgid "Connect to {host}:{port}" msgstr "Conectar a {host}:{port}" -#: daemon.py:299 +#: daemon.py:304 #, python-brace-format msgid "Cannot connect to {host}:{port}" msgstr "No se pudo conectar a {host}:{port}" @@ -143,12 +143,12 @@ msgstr "Servidor Web" msgid "{box_name} Web Interface (Plinth)" msgstr "Interfaz web (Plinth) de {box_name}" -#: modules/apache/components.py:159 +#: modules/apache/components.py:162 #, python-brace-format msgid "Access URL {url} on tcp{kind}" msgstr "Acceso a {url} en tcp{kind}" -#: modules/apache/components.py:162 +#: modules/apache/components.py:165 #, python-brace-format msgid "Access URL {url}" msgstr "Acceso a {url}" @@ -927,7 +927,7 @@ msgstr "Eliminar" msgid "Admin" msgstr "Admin" -#: modules/bepasty/views.py:88 modules/diagnostics/views.py:52 +#: modules/bepasty/views.py:88 modules/diagnostics/views.py:55 #: modules/nextcloud/views.py:62 modules/searx/views.py:35 #: modules/searx/views.py:46 modules/security/views.py:56 #: modules/snapshot/views.py:158 modules/tor/views.py:73 @@ -1611,9 +1611,8 @@ msgstr "Aplicación: %(app_name)s" #: modules/diagnostics/templates/diagnostics_app.html:20 #: modules/diagnostics/templates/diagnostics_full.html:48 -#: templates/toolbar.html:53 -msgid "Re-run setup" -msgstr "Volver a ejecutar la configuración" +msgid "Try to repair" +msgstr "" #: modules/diagnostics/templates/diagnostics_app.html:32 msgid "This app does not support diagnostics" @@ -1655,10 +1654,15 @@ msgstr "Test" msgid "Result" msgstr "Resultado" -#: modules/diagnostics/views.py:111 +#: modules/diagnostics/views.py:114 msgid "Diagnostic Test" msgstr "Test de diagnóstico" +#: modules/diagnostics/views.py:144 +#, python-brace-format +msgid "App {app_id} is not installed, cannot repair" +msgstr "" + #: modules/dynamicdns/__init__.py:28 #, python-brace-format msgid "" @@ -1845,7 +1849,7 @@ msgstr "Estado" #: modules/dynamicdns/templates/dynamicdns.html:18 #: modules/email/templates/email.html:35 #: modules/letsencrypt/templates/letsencrypt.html:24 -#: modules/mediawiki/forms.py:64 modules/nextcloud/forms.py:26 +#: modules/mediawiki/forms.py:64 msgid "Domain" msgstr "Dominio" @@ -2244,12 +2248,12 @@ msgstr "Existen normas de transferencia directa" msgid "Port {name} ({details}) available for internal networks" msgstr "Puerto {name} ({details}) disponible para redes internas" -#: modules/firewall/components.py:153 +#: modules/firewall/components.py:154 #, python-brace-format msgid "Port {name} ({details}) available for external networks" msgstr "Puerto {name} ({details}) disponible para redes externas" -#: modules/firewall/components.py:159 +#: modules/firewall/components.py:160 #, python-brace-format msgid "Port {name} ({details}) unavailable for external networks" msgstr "Puerto {name} ({details}) no disponible para redes externas" @@ -3332,10 +3336,6 @@ msgstr "Let's Encrypt" msgid "Certificates" msgstr "Certificados" -#: modules/letsencrypt/__init__.py:105 -msgid "Cannot test: No domains are configured." -msgstr "No puedo probar: No hay dominios configurados." - #: modules/letsencrypt/templates/letsencrypt.html:25 msgid "Certificate Status" msgstr "Estado del certificado" @@ -3864,7 +3864,7 @@ msgstr "" msgid "Address" msgstr "Dirección" -#: modules/minidlna/__init__.py:22 +#: modules/minidlna/__init__.py:20 msgid "" "MiniDLNA is a simple media server software, with the aim of being fully " "compliant with DLNA/UPnP-AV clients. The MiniDLNA daemon serves media files " @@ -3881,33 +3881,23 @@ msgstr "" "reproductores portátiles, teléfonos móviles, televisores, consolas como PS3 " "y Xbox o aplicaciones como Totem y Kodi." -#: modules/minidlna/__init__.py:44 -msgid "Media streaming server" -msgstr "Servidor de emisión multimedia" - -#: modules/minidlna/__init__.py:47 +#: modules/minidlna/__init__.py:45 msgid "MiniDLNA" msgstr "MiniDLNA" -#: modules/minidlna/__init__.py:48 +#: modules/minidlna/__init__.py:46 msgid "Simple Media Server" msgstr "Servidor multimedia básico" -#: modules/minidlna/forms.py:13 +#: modules/minidlna/forms.py:20 msgid "Media Files Directory" msgstr "Directorio de archivos multimedia" -#: modules/minidlna/forms.py:14 +#: modules/minidlna/forms.py:21 msgid "" "Directory that MiniDLNA Server will read for content. All sub-directories of " -"this will be also scanned for media files. If you change the default ensure " -"that the new directory exists and that is readable from the \"minidlna\" " -"user. Any user media directories (\"/home/username/\") will usually work." +"this will be also scanned for media files." msgstr "" -"Es la carpeta en el que MiniDLNA buscará los contenidos, incluyendo las " -"subcarpetas. Si modifica su valor asegúrese de que la nueva carpeta existe y " -"es accesible el usuario \"minidlna\". Se pueden usar carpetas en el " -"directorio del usuario (\"/home/username\")." #: modules/minidlna/manifest.py:10 msgid "vlc" @@ -3925,11 +3915,7 @@ msgstr "yaacc" msgid "totem" msgstr "Totem" -#: modules/minidlna/views.py:35 -msgid "Specified directory does not exist." -msgstr "La carpeta especificada no existe." - -#: modules/minidlna/views.py:38 +#: modules/minidlna/views.py:33 msgid "Updated media directory" msgstr "Carpeta multimedia actualizada" @@ -5203,7 +5189,7 @@ msgstr "Conexión {name} eliminada." msgid "Failed to delete connection: Connection not found." msgstr "Ha fallado la eliminación de la conexión: no se encontró." -#: modules/nextcloud/__init__.py:20 +#: modules/nextcloud/__init__.py:25 msgid "" "Nextcloud is a self-hosted productivity platform which provides private and " "secure functions for file sharing, collaborative work, and more. Nextcloud " @@ -5217,51 +5203,74 @@ msgstr "" "aplicaciones cliente para ordenadores de sobremesa y clientes móviles. El " "servidor Nextcloud proporciona una interfaz web bien integrada." -#: modules/nextcloud/__init__.py:25 -#, fuzzy -#| msgid "All users of FreedomBox can use Nextcloud." +#: modules/nextcloud/__init__.py:30 msgid "All users of FreedomBox can use Nextcloud. To perform administrative " -msgstr "Todos los usuarios de FreedomBox pueden utilizar Nextcloud." +msgstr "" +"Todos los usuarios de FreedomBox pueden utilizar Nextcloud. Para realizar " +"tareas administrativas " -#: modules/nextcloud/__init__.py:29 -#, python-brace-format +#: modules/nextcloud/__init__.py:34 +#, fuzzy, python-brace-format +#| msgid "" +#| "Please note that Nextcloud is installed and run inside a container " +#| "provided by the Nextcloud project. Security, quality, privacy and legal " +#| "reviews are done by the upstream project and not by Debian/{box_name}. " +#| "Updates are performed following an independent cycle." msgid "" "Please note that Nextcloud is installed and run inside a container provided " -"by the Nextcloud project. Security, quality, privacy and legal reviews are " +"by the Nextcloud community. Security, quality, privacy and legal reviews are " "done by the upstream project and not by Debian/{box_name}. Updates are " "performed following an independent cycle." msgstr "" +"Ten en cuenta que Nextcloud se instala y ejecuta dentro de un contenedor " +"proporcionado por el proyecto Nextcloud. Las revisiones de seguridad, " +"calidad, privacidad y legales las realiza el proyecto original y no Debian/" +"{box_name} . Las actualizaciones se realizan siguiendo un ciclo " +"independiente." -#: modules/nextcloud/__init__.py:35 -#, fuzzy -#| msgid "Uninstalling an app is an experimental feature." +#: modules/nextcloud/__init__.py:40 msgid "This app is experimental." -msgstr "Desinstalar una aplicación es una función experimental." +msgstr "Esta aplicación es experimental." -#: modules/nextcloud/__init__.py:53 modules/nextcloud/manifest.py:11 +#: modules/nextcloud/__init__.py:58 modules/nextcloud/manifest.py:11 #: modules/nextcloud/manifest.py:18 msgid "Nextcloud" msgstr "Nextcloud" -#: modules/nextcloud/__init__.py:55 +#: modules/nextcloud/__init__.py:60 msgid "File Storage & Collaboration" msgstr "Almacenamiento de archivos y colaboración" #: modules/nextcloud/forms.py:19 -#, fuzzy -#| msgid "Hostname set" msgid "Not set" -msgstr "Asignar nombre de anfitrión" +msgstr "No establecido" + +#: modules/nextcloud/forms.py:26 +#, fuzzy +#| msgid "Server domain" +msgid "Override domain" +msgstr "Dominio del servidor" #: modules/nextcloud/forms.py:27 -msgid "Examples: \"myfreedombox.example.org\" or \"example.onion\"." -msgstr "Ejemplos: \"myfreedombox.example.org\" o \"example.onion\"." +#, fuzzy +#| msgid "" +#| "Used by MediaWiki to generate URLs that point to the wiki such as in " +#| "footer, feeds and emails. Examples: \"myfreedombox.example.org\" or " +#| "\"example.onion\"." +msgid "" +"Set to the domain or IP address that Nextcloud should be forced to generate " +"URLs with. Should not be needed if a valid domain is used to access " +"Nextcloud. Examples: \"myfreedombox.example.org\" or \"example.onion\"." +msgstr "" +"Lo usa MediaWiki para generar URLs que apunten al wiki como piés de página, " +"feeds y enlaces desde e-mail. Ejemplos: \"myfreedombox.ejemplo.org\" o " +"\"ejemplo.onion\"." -#: modules/nextcloud/forms.py:30 +#: modules/nextcloud/forms.py:33 msgid "Administrator password" msgstr "Contraseña del administrador" -#: modules/nextcloud/forms.py:31 +#: modules/nextcloud/forms.py:34 msgid "" "Optional. Set a new password for Nextcloud's administrator account " "(nextcloud-admin). The password cannot be a common one and the minimum " @@ -5273,11 +5282,11 @@ msgstr "" "mínima requerida es 10 caracteres. Deje este campo en " "blanco para conservar la contraseña actual." -#: modules/nextcloud/forms.py:38 +#: modules/nextcloud/forms.py:41 msgid "Default phone region" msgstr "Región telefónica por defecto" -#: modules/nextcloud/forms.py:39 +#: modules/nextcloud/forms.py:42 msgid "" "The default phone region is required to validate phone numbers in the " "profile settings without a country code." @@ -5287,8 +5296,7 @@ msgstr "" #: modules/nextcloud/views.py:53 msgid "Password update failed. Please choose a stronger password." -msgstr "" -"Error en la actualización de la contraseña. Elija una contraseña más segura." +msgstr "Error al actualizar la contraseña. Elija una contraseña más segura." #: modules/openvpn/__init__.py:20 #, python-brace-format @@ -8584,67 +8592,103 @@ msgstr "" msgid "Package {package_name} is the latest version ({latest_version})" msgstr "El paquete {package_name} es la última versión ({latest_version})" -#: package.py:419 +#: package.py:420 msgid "installing" msgstr "instalando" -#: package.py:421 +#: package.py:422 msgid "downloading" msgstr "descargando" -#: package.py:423 +#: package.py:424 msgid "media change" msgstr "cambio de medio" -#: package.py:425 +#: package.py:426 #, python-brace-format msgid "configuration file: {file}" msgstr "archivo de configuración: {file}" -#: package.py:453 package.py:478 +#: package.py:454 package.py:479 msgid "Timeout waiting for package manager" msgstr "Tiempo máximo esperando al administrador de paquetes" -#: setup.py:39 +#: setup.py:42 msgid "Installing app" msgstr "Instalando app" -#: setup.py:41 +#: setup.py:44 msgid "Updating app" msgstr "Actualizando app" -#: setup.py:75 +#: setup.py:78 #, python-brace-format msgid "Error installing app: {error}" msgstr "Error al instalar la app: {error}" -#: setup.py:78 +#: setup.py:81 setup.py:151 +#, fuzzy, python-brace-format +#| msgid "Error updating app: {error}" +msgid "Error repairing app: {error}" +msgstr "Error al actualizar la app: {error}" + +#: setup.py:84 #, python-brace-format msgid "Error updating app: {error}" msgstr "Error al actualizar la app: {error}" -#: setup.py:82 +#: setup.py:88 msgid "App installed." msgstr "App instalada." -#: setup.py:84 +#: setup.py:92 msgid "App updated" msgstr "App actualizada" -#: setup.py:101 +#: setup.py:110 +#, fuzzy +#| msgid "Updating app" +msgid "Repairing app" +msgstr "Actualizando app" + +#: setup.py:130 +#, fuzzy, python-brace-format +#| msgid "Error uninstalling app: {error}" +msgid "Error running diagnostics: {error}" +msgstr "Error desinstalando la aplicación: {error}" + +#: setup.py:143 +msgid "Skipping repair, no failed checks" +msgstr "" + +#: setup.py:157 +msgid "Re-running setup to complete repairs" +msgstr "" + +#: setup.py:165 +#, fuzzy +#| msgid "App updated" +msgid "App repaired." +msgstr "App actualizada" + +#: setup.py:169 +msgid "App repair completed with errors:\n" +msgstr "" + +#: setup.py:189 msgid "Uninstalling app" msgstr "Instalando app" -#: setup.py:117 +#: setup.py:205 #, python-brace-format msgid "Error uninstalling app: {error}" msgstr "Error desinstalando la aplicación: {error}" -#: setup.py:120 +#: setup.py:208 msgid "App uninstalled." msgstr "Aplicación desinstalada." -#: setup.py:493 +#: setup.py:581 msgid "Updating app packages" msgstr "Actualizando los paquetes de la app" @@ -9015,6 +9059,10 @@ msgstr "Actualización" msgid "Backup" msgstr "Copia de seguridad" +#: templates/toolbar.html:53 +msgid "Re-run setup" +msgstr "Volver a ejecutar la configuración" + #: templates/toolbar.html:59 templates/toolbar.html:60 #: templates/uninstall.html:30 msgid "Uninstall" @@ -9046,6 +9094,30 @@ msgstr "antes de desinstalar {app_id}" msgid "Gujarati" msgstr "Gujarati" +#~ msgid "Cannot test: No domains are configured." +#~ msgstr "No puedo probar: No hay dominios configurados." + +#~ msgid "Media streaming server" +#~ msgstr "Servidor de emisión multimedia" + +#~ msgid "" +#~ "Directory that MiniDLNA Server will read for content. All sub-directories " +#~ "of this will be also scanned for media files. If you change the default " +#~ "ensure that the new directory exists and that is readable from the " +#~ "\"minidlna\" user. Any user media directories (\"/home/username/\") will " +#~ "usually work." +#~ msgstr "" +#~ "Es la carpeta en el que MiniDLNA buscará los contenidos, incluyendo las " +#~ "subcarpetas. Si modifica su valor asegúrese de que la nueva carpeta " +#~ "existe y es accesible el usuario \"minidlna\". Se pueden usar carpetas en " +#~ "el directorio del usuario (\"/home/username\")." + +#~ msgid "Specified directory does not exist." +#~ msgstr "La carpeta especificada no existe." + +#~ msgid "Examples: \"myfreedombox.example.org\" or \"example.onion\"." +#~ msgstr "Ejemplos: \"myfreedombox.example.org\" o \"example.onion\"." + #~ msgid "To perform administrative actions, use the " #~ msgstr "Para realizar acciones administrativas, utilice " diff --git a/plinth/locale/fa/LC_MESSAGES/django.po b/plinth/locale/fa/LC_MESSAGES/django.po index f3f086bd2..abc024d27 100644 --- a/plinth/locale/fa/LC_MESSAGES/django.po +++ b/plinth/locale/fa/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-04-22 20:02-0400\n" +"POT-Creation-Date: 2024-05-06 20:18-0400\n" "PO-Revision-Date: 2022-09-14 17:19+0000\n" "Last-Translator: ikmaak \n" "Language-Team: Persian \n" "Language-Team: Plinth Developers \n" "Language-Team: French 10 caractères. Laissez ce champ vide pour " "conserver le mot de passe actuel." -#: modules/nextcloud/forms.py:38 +#: modules/nextcloud/forms.py:41 #, fuzzy #| msgid "Default zone is external" msgid "Default phone region" msgstr "La zone par défaut est externe" -#: modules/nextcloud/forms.py:39 +#: modules/nextcloud/forms.py:42 msgid "" "The default phone region is required to validate phone numbers in the " "profile settings without a country code." @@ -8763,67 +8764,103 @@ msgstr "Le paquet {package_expression} n’est pas disponible à l’installatio msgid "Package {package_name} is the latest version ({latest_version})" msgstr "Le paquet {package_name} est à la dernière version ({latest_version})" -#: package.py:419 +#: package.py:420 msgid "installing" msgstr "installation en cours" -#: package.py:421 +#: package.py:422 msgid "downloading" msgstr "téléchargement en cours" -#: package.py:423 +#: package.py:424 msgid "media change" msgstr "changement de support" -#: package.py:425 +#: package.py:426 #, python-brace-format msgid "configuration file: {file}" msgstr "fichier de configuration : {file}" -#: package.py:453 package.py:478 +#: package.py:454 package.py:479 msgid "Timeout waiting for package manager" msgstr "Aucune réponse du gestionnaire de paquets" -#: setup.py:39 +#: setup.py:42 msgid "Installing app" msgstr "Installation de l’application" -#: setup.py:41 +#: setup.py:44 msgid "Updating app" msgstr "Mise à jour de l’application" -#: setup.py:75 +#: setup.py:78 #, python-brace-format msgid "Error installing app: {error}" msgstr "Erreur lors de l’installation de l’appli : {error}" -#: setup.py:78 +#: setup.py:81 setup.py:151 +#, fuzzy, python-brace-format +#| msgid "Error updating app: {error}" +msgid "Error repairing app: {error}" +msgstr "Erreur lors de la mise à jour de l’appli : {error}" + +#: setup.py:84 #, python-brace-format msgid "Error updating app: {error}" msgstr "Erreur lors de la mise à jour de l’appli : {error}" -#: setup.py:82 +#: setup.py:88 msgid "App installed." msgstr "Application installée." -#: setup.py:84 +#: setup.py:92 msgid "App updated" msgstr "Application mise à jour" -#: setup.py:101 +#: setup.py:110 +#, fuzzy +#| msgid "Updating app" +msgid "Repairing app" +msgstr "Mise à jour de l’application" + +#: setup.py:130 +#, fuzzy, python-brace-format +#| msgid "Error uninstalling app: {error}" +msgid "Error running diagnostics: {error}" +msgstr "Erreur lors de la désinstallation de l’appli : {error}" + +#: setup.py:143 +msgid "Skipping repair, no failed checks" +msgstr "" + +#: setup.py:157 +msgid "Re-running setup to complete repairs" +msgstr "" + +#: setup.py:165 +#, fuzzy +#| msgid "App updated" +msgid "App repaired." +msgstr "Application mise à jour" + +#: setup.py:169 +msgid "App repair completed with errors:\n" +msgstr "" + +#: setup.py:189 msgid "Uninstalling app" msgstr "Désinstallation de l’application" -#: setup.py:117 +#: setup.py:205 #, python-brace-format msgid "Error uninstalling app: {error}" msgstr "Erreur lors de la désinstallation de l’appli : {error}" -#: setup.py:120 +#: setup.py:208 msgid "App uninstalled." msgstr "Application désinstallée." -#: setup.py:493 +#: setup.py:581 msgid "Updating app packages" msgstr "Mise à jour des paquets de l’application" @@ -9200,6 +9237,10 @@ msgstr "Mettre à jour" msgid "Backup" msgstr "Sauvegarder" +#: templates/toolbar.html:53 +msgid "Re-run setup" +msgstr "Exécuter à nouveau la configuration" + #: templates/toolbar.html:59 templates/toolbar.html:60 #: templates/uninstall.html:30 msgid "Uninstall" @@ -9231,6 +9272,29 @@ msgstr "avant la désinstallation de {app_id}" msgid "Gujarati" msgstr "Gujarati" +#~ msgid "Cannot test: No domains are configured." +#~ msgstr "Test impossible : aucun domaine n’est configuré." + +#~ msgid "Media streaming server" +#~ msgstr "Serveur de streaming de médias" + +#~ msgid "" +#~ "Directory that MiniDLNA Server will read for content. All sub-directories " +#~ "of this will be also scanned for media files. If you change the default " +#~ "ensure that the new directory exists and that is readable from the " +#~ "\"minidlna\" user. Any user media directories (\"/home/username/\") will " +#~ "usually work." +#~ msgstr "" +#~ "Répertoire utilisé par MiniDLNA pour trouver les contenus. Tous les sous-" +#~ "répertoires sont également analysés à la recherche de fichiers " +#~ "multimédia. Si vous choisissez un répertoire autre que celui par défaut, " +#~ "assurez-vous qu’il existe et que l’utilisateur « minidlna » puisse le " +#~ "lire. En général cela fonctionne si vous prenez un répertoire utilisateur " +#~ "(\"/home/utilisateur\") comme répertoire de médias." + +#~ msgid "Specified directory does not exist." +#~ msgstr "Le répertoire indiqué n’existe pas." + #~ msgid "Storage snapshots configuration updated" #~ msgstr "Configuration des instantanés de disque mise à jour" diff --git a/plinth/locale/gl/LC_MESSAGES/django.po b/plinth/locale/gl/LC_MESSAGES/django.po index 9500eac5b..b62d5766a 100644 --- a/plinth/locale/gl/LC_MESSAGES/django.po +++ b/plinth/locale/gl/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-04-22 20:02-0400\n" +"POT-Creation-Date: 2024-05-06 20:18-0400\n" "PO-Revision-Date: 2022-12-30 10:51+0000\n" "Last-Translator: gallegonovato \n" "Language-Team: Galician \n" "Language-Team: Gujarati \n" "Language-Team: Hindi %(service_name)s is running." msgid "Service {service_name} is running" msgstr "सर्विस %(service_name)s चल रहा है." -#: daemon.py:218 +#: daemon.py:222 #, python-brace-format msgid "Listening on {kind} port {listen_address}:{port}" msgstr "{kind} में सुन कर पोर्ट {listen_address} :{port}" -#: daemon.py:221 +#: daemon.py:225 #, python-brace-format msgid "Listening on {kind} port {port}" msgstr "{kind} में सुन कर पोर्ट{port}" -#: daemon.py:291 +#: daemon.py:296 #, python-brace-format msgid "Connect to {host}:{port}" msgstr "{host}:{port} से जुड़े" -#: daemon.py:299 +#: daemon.py:304 #, python-brace-format msgid "Cannot connect to {host}:{port}" msgstr "{host}:{port} से नहीं जोड़ सखता" @@ -147,12 +147,12 @@ msgstr "वेब सर्वर" msgid "{box_name} Web Interface (Plinth)" msgstr "{box_name} वेब इंटरफेस (प्लिंथ)" -#: modules/apache/components.py:159 +#: modules/apache/components.py:162 #, python-brace-format msgid "Access URL {url} on tcp{kind}" msgstr "इस यूआरएल का अपयोग करें {url} टीसीपी पर {kind}" -#: modules/apache/components.py:162 +#: modules/apache/components.py:165 #, python-brace-format msgid "Access URL {url}" msgstr "इस यूआरएल का अपयोग करें {url}" @@ -948,7 +948,7 @@ msgstr "हटाईये" msgid "Admin" msgstr "" -#: modules/bepasty/views.py:88 modules/diagnostics/views.py:52 +#: modules/bepasty/views.py:88 modules/diagnostics/views.py:55 #: modules/nextcloud/views.py:62 modules/searx/views.py:35 #: modules/searx/views.py:46 modules/security/views.py:56 #: modules/snapshot/views.py:158 modules/tor/views.py:73 @@ -1657,11 +1657,8 @@ msgstr "" #: modules/diagnostics/templates/diagnostics_app.html:20 #: modules/diagnostics/templates/diagnostics_full.html:48 -#: templates/toolbar.html:53 -#, fuzzy -#| msgid "Start setup" -msgid "Re-run setup" -msgstr "सटअप शुरु करें" +msgid "Try to repair" +msgstr "" #: modules/diagnostics/templates/diagnostics_app.html:32 #, fuzzy @@ -1704,10 +1701,15 @@ msgstr "परीक्षा" msgid "Result" msgstr "परिणाम" -#: modules/diagnostics/views.py:111 +#: modules/diagnostics/views.py:114 msgid "Diagnostic Test" msgstr "निदान परिक्षा" +#: modules/diagnostics/views.py:144 +#, python-brace-format +msgid "App {app_id} is not installed, cannot repair" +msgstr "" + #: modules/dynamicdns/__init__.py:28 #, python-brace-format msgid "" @@ -1907,7 +1909,7 @@ msgstr "स्थिति" #: modules/dynamicdns/templates/dynamicdns.html:18 #: modules/email/templates/email.html:35 #: modules/letsencrypt/templates/letsencrypt.html:24 -#: modules/mediawiki/forms.py:64 modules/nextcloud/forms.py:26 +#: modules/mediawiki/forms.py:64 msgid "Domain" msgstr "डोमेन" @@ -2316,13 +2318,13 @@ msgstr "" msgid "Port {name} ({details}) available for internal networks" msgstr "%(service_name)s सिर्फ आंतरिक नेटवर्क्स पर उपलब्ध है." -#: modules/firewall/components.py:153 +#: modules/firewall/components.py:154 #, fuzzy, python-brace-format #| msgid "%(service_name)s is available only on internal networks." msgid "Port {name} ({details}) available for external networks" msgstr "%(service_name)s सिर्फ आंतरिक नेटवर्क्स पर उपलब्ध है." -#: modules/firewall/components.py:159 +#: modules/firewall/components.py:160 #, python-brace-format msgid "Port {name} ({details}) unavailable for external networks" msgstr "" @@ -3378,10 +3380,6 @@ msgstr "लेटस एंक्रिप्ट" msgid "Certificates" msgstr "प्रमाण पत्र" -#: modules/letsencrypt/__init__.py:105 -msgid "Cannot test: No domains are configured." -msgstr "" - #: modules/letsencrypt/templates/letsencrypt.html:25 msgid "Certificate Status" msgstr "प्रमाणपत्र स्थिति" @@ -3903,7 +3901,7 @@ msgstr "अक्षम होने पर खिलाड़ियों न msgid "Address" msgstr "ऍड्रेस" -#: modules/minidlna/__init__.py:22 +#: modules/minidlna/__init__.py:20 msgid "" "MiniDLNA is a simple media server software, with the aim of being fully " "compliant with DLNA/UPnP-AV clients. The MiniDLNA daemon serves media files " @@ -3914,28 +3912,22 @@ msgid "" "Kodi." msgstr "" -#: modules/minidlna/__init__.py:44 -msgid "Media streaming server" -msgstr "" - -#: modules/minidlna/__init__.py:47 +#: modules/minidlna/__init__.py:45 msgid "MiniDLNA" msgstr "" -#: modules/minidlna/__init__.py:48 +#: modules/minidlna/__init__.py:46 msgid "Simple Media Server" msgstr "" -#: modules/minidlna/forms.py:13 +#: modules/minidlna/forms.py:20 msgid "Media Files Directory" msgstr "" -#: modules/minidlna/forms.py:14 +#: modules/minidlna/forms.py:21 msgid "" "Directory that MiniDLNA Server will read for content. All sub-directories of " -"this will be also scanned for media files. If you change the default ensure " -"that the new directory exists and that is readable from the \"minidlna\" " -"user. Any user media directories (\"/home/username/\") will usually work." +"this will be also scanned for media files." msgstr "" #: modules/minidlna/manifest.py:10 @@ -3954,11 +3946,7 @@ msgstr "" msgid "totem" msgstr "" -#: modules/minidlna/views.py:35 -msgid "Specified directory does not exist." -msgstr "" - -#: modules/minidlna/views.py:38 +#: modules/minidlna/views.py:33 msgid "Updated media directory" msgstr "" @@ -5195,7 +5183,7 @@ msgstr "कनेक्शन {name} हटाया गया." msgid "Failed to delete connection: Connection not found." msgstr "कनेक्शन हटाने में विफल: कनेक्शन नहीं मिला." -#: modules/nextcloud/__init__.py:20 +#: modules/nextcloud/__init__.py:25 msgid "" "Nextcloud is a self-hosted productivity platform which provides private and " "secure functions for file sharing, collaborative work, and more. Nextcloud " @@ -5204,29 +5192,29 @@ msgid "" "interface." msgstr "" -#: modules/nextcloud/__init__.py:25 +#: modules/nextcloud/__init__.py:30 msgid "All users of FreedomBox can use Nextcloud. To perform administrative " msgstr "" -#: modules/nextcloud/__init__.py:29 +#: modules/nextcloud/__init__.py:34 #, python-brace-format msgid "" "Please note that Nextcloud is installed and run inside a container provided " -"by the Nextcloud project. Security, quality, privacy and legal reviews are " +"by the Nextcloud community. Security, quality, privacy and legal reviews are " "done by the upstream project and not by Debian/{box_name}. Updates are " "performed following an independent cycle." msgstr "" -#: modules/nextcloud/__init__.py:35 +#: modules/nextcloud/__init__.py:40 msgid "This app is experimental." msgstr "" -#: modules/nextcloud/__init__.py:53 modules/nextcloud/manifest.py:11 +#: modules/nextcloud/__init__.py:58 modules/nextcloud/manifest.py:11 #: modules/nextcloud/manifest.py:18 msgid "Nextcloud" msgstr "" -#: modules/nextcloud/__init__.py:55 +#: modules/nextcloud/__init__.py:60 msgid "File Storage & Collaboration" msgstr "" @@ -5236,17 +5224,26 @@ msgstr "" msgid "Not set" msgstr "होस्ट नाम सेट हो गया" +#: modules/nextcloud/forms.py:26 +#, fuzzy +#| msgid "Server domain" +msgid "Override domain" +msgstr "सर्वर डोमेन" + #: modules/nextcloud/forms.py:27 -msgid "Examples: \"myfreedombox.example.org\" or \"example.onion\"." +msgid "" +"Set to the domain or IP address that Nextcloud should be forced to generate " +"URLs with. Should not be needed if a valid domain is used to access " +"Nextcloud. Examples: \"myfreedombox.example.org\" or \"example.onion\"." msgstr "" -#: modules/nextcloud/forms.py:30 +#: modules/nextcloud/forms.py:33 #, fuzzy #| msgid "Administrator Password" msgid "Administrator password" msgstr "व्यवस्थापक पासवर्ड" -#: modules/nextcloud/forms.py:31 +#: modules/nextcloud/forms.py:34 #, fuzzy #| msgid "" #| "Set a new password for MediaWiki's administrator account (admin). Leave " @@ -5260,13 +5257,13 @@ msgstr "" "मीडियाविकी एेडमिन अकाउंट के लिये नया पासवर्ड सेट करें (एेडमिन). वर्तमान पासवर्ड रखने के " "लिए इस फ़ील्ड को रिक्त छोड़ें." -#: modules/nextcloud/forms.py:38 +#: modules/nextcloud/forms.py:41 #, fuzzy #| msgid "Default app set" msgid "Default phone region" msgstr "डिफ़ॉल्ट एेप सेट" -#: modules/nextcloud/forms.py:39 +#: modules/nextcloud/forms.py:42 msgid "" "The default phone region is required to validate phone numbers in the " "profile settings without a country code." @@ -8650,80 +8647,116 @@ msgstr "" msgid "Package {package_name} is the latest version ({latest_version})" msgstr "" -#: package.py:419 +#: package.py:420 msgid "installing" msgstr "इंस्टॉलिंग" -#: package.py:421 +#: package.py:422 msgid "downloading" msgstr "डाउनलोडिंग" -#: package.py:423 +#: package.py:424 msgid "media change" msgstr "मीडिया बदलाव" -#: package.py:425 +#: package.py:426 #, python-brace-format msgid "configuration file: {file}" msgstr "कॉंफ़िगरेशन फ़ाइल: {file}" -#: package.py:453 package.py:478 +#: package.py:454 package.py:479 msgid "Timeout waiting for package manager" msgstr "" -#: setup.py:39 +#: setup.py:42 #, fuzzy #| msgid "Install Apps" msgid "Installing app" msgstr "ऐप्लिकेशन इंस्टॉल करें" -#: setup.py:41 +#: setup.py:44 msgid "Updating app" msgstr "" -#: setup.py:75 +#: setup.py:78 #, fuzzy, python-brace-format #| msgid "Error installing application: {error}" msgid "Error installing app: {error}" msgstr "एप्लिकेशन नहीं इंस्टॉल जा सकता: {error}" -#: setup.py:78 +#: setup.py:81 setup.py:151 +#, fuzzy, python-brace-format +#| msgid "Error installing application: {error}" +msgid "Error repairing app: {error}" +msgstr "एप्लिकेशन नहीं इंस्टॉल जा सकता: {error}" + +#: setup.py:84 #, fuzzy, python-brace-format #| msgid "Error installing application: {error}" msgid "Error updating app: {error}" msgstr "एप्लिकेशन नहीं इंस्टॉल जा सकता: {error}" -#: setup.py:82 +#: setup.py:88 #, fuzzy #| msgid "Application installed." msgid "App installed." msgstr "एप्लिकेशन इंस्टॉल हो गया." -#: setup.py:84 +#: setup.py:92 #, fuzzy #| msgid "Last update" msgid "App updated" msgstr "अंतिम अपडेट" -#: setup.py:101 +#: setup.py:110 +#, fuzzy +#| msgid "Sharing" +msgid "Repairing app" +msgstr "शेयरिंग" + +#: setup.py:130 +#, fuzzy, python-brace-format +#| msgid "Error installing application: {error}" +msgid "Error running diagnostics: {error}" +msgstr "एप्लिकेशन नहीं इंस्टॉल जा सकता: {error}" + +#: setup.py:143 +msgid "Skipping repair, no failed checks" +msgstr "" + +#: setup.py:157 +msgid "Re-running setup to complete repairs" +msgstr "" + +#: setup.py:165 +#, fuzzy +#| msgid "Last update" +msgid "App repaired." +msgstr "अंतिम अपडेट" + +#: setup.py:169 +msgid "App repair completed with errors:\n" +msgstr "" + +#: setup.py:189 #, fuzzy #| msgid "Install Apps" msgid "Uninstalling app" msgstr "ऐप्लिकेशन इंस्टॉल करें" -#: setup.py:117 +#: setup.py:205 #, fuzzy, python-brace-format #| msgid "Error installing application: {error}" msgid "Error uninstalling app: {error}" msgstr "एप्लिकेशन नहीं इंस्टॉल जा सकता: {error}" -#: setup.py:120 +#: setup.py:208 #, fuzzy #| msgid "Application installed." msgid "App uninstalled." msgstr "एप्लिकेशन इंस्टॉल हो गया." -#: setup.py:493 +#: setup.py:581 #, fuzzy #| msgid "Upgrade Packages" msgid "Updating app packages" @@ -9100,6 +9133,12 @@ msgstr "अपडेट" msgid "Backup" msgstr "बैकअप" +#: templates/toolbar.html:53 +#, fuzzy +#| msgid "Start setup" +msgid "Re-run setup" +msgstr "सटअप शुरु करें" + #: templates/toolbar.html:59 templates/toolbar.html:60 #: templates/uninstall.html:30 #, fuzzy diff --git a/plinth/locale/hu/LC_MESSAGES/django.po b/plinth/locale/hu/LC_MESSAGES/django.po index 947f8ac2a..9c62eb1b5 100644 --- a/plinth/locale/hu/LC_MESSAGES/django.po +++ b/plinth/locale/hu/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-04-22 20:02-0400\n" +"POT-Creation-Date: 2024-05-06 20:18-0400\n" "PO-Revision-Date: 2022-10-24 18:39+0000\n" "Last-Translator: Sunil Mohan Adapa \n" "Language-Team: Hungarian kell állnia. Hagyd üresen ezt a mezőt, ha meg szeretnéd " "tartani a jelenlegi jelszót." -#: modules/nextcloud/forms.py:38 +#: modules/nextcloud/forms.py:41 #, fuzzy #| msgid "Default app set" msgid "Default phone region" msgstr "Alapértelmezett alkalmazás beállítva" -#: modules/nextcloud/forms.py:39 +#: modules/nextcloud/forms.py:42 msgid "" "The default phone region is required to validate phone numbers in the " "profile settings without a country code." @@ -8700,82 +8700,118 @@ msgstr "" msgid "Package {package_name} is the latest version ({latest_version})" msgstr "A(z) {package_name} a legfrissebb verzió ({latest_version})" -#: package.py:419 +#: package.py:420 msgid "installing" msgstr "telepítés" -#: package.py:421 +#: package.py:422 msgid "downloading" msgstr "letöltés" -#: package.py:423 +#: package.py:424 msgid "media change" msgstr "adathordozó csere" -#: package.py:425 +#: package.py:426 #, python-brace-format msgid "configuration file: {file}" msgstr "konfigurációs fájl: {file}" -#: package.py:453 package.py:478 +#: package.py:454 package.py:479 msgid "Timeout waiting for package manager" msgstr "" -#: setup.py:39 +#: setup.py:42 #, fuzzy #| msgid "Install Apps" msgid "Installing app" msgstr "Alkalmazások telepítése" -#: setup.py:41 +#: setup.py:44 #, fuzzy #| msgid "Updating..." msgid "Updating app" msgstr "Frissítés…" -#: setup.py:75 +#: setup.py:78 #, fuzzy, python-brace-format #| msgid "Error installing application: {error}" msgid "Error installing app: {error}" msgstr "Hiba lépett fel az alkalmazás telepítésekor: {error}" -#: setup.py:78 +#: setup.py:81 setup.py:151 +#, fuzzy, python-brace-format +#| msgid "Error installing application: {error}" +msgid "Error repairing app: {error}" +msgstr "Hiba lépett fel az alkalmazás telepítésekor: {error}" + +#: setup.py:84 #, fuzzy, python-brace-format #| msgid "Error installing application: {error}" msgid "Error updating app: {error}" msgstr "Hiba lépett fel az alkalmazás telepítésekor: {error}" -#: setup.py:82 +#: setup.py:88 #, fuzzy #| msgid "Application installed." msgid "App installed." msgstr "Alkalmazás telepítve." -#: setup.py:84 +#: setup.py:92 #, fuzzy #| msgid "Last update" msgid "App updated" msgstr "Legutolsó frissítés" -#: setup.py:101 +#: setup.py:110 +#, fuzzy +#| msgid "Updating..." +msgid "Repairing app" +msgstr "Frissítés…" + +#: setup.py:130 +#, fuzzy, python-brace-format +#| msgid "Error installing application: {error}" +msgid "Error running diagnostics: {error}" +msgstr "Hiba lépett fel az alkalmazás telepítésekor: {error}" + +#: setup.py:143 +msgid "Skipping repair, no failed checks" +msgstr "" + +#: setup.py:157 +msgid "Re-running setup to complete repairs" +msgstr "" + +#: setup.py:165 +#, fuzzy +#| msgid "Last update" +msgid "App repaired." +msgstr "Legutolsó frissítés" + +#: setup.py:169 +msgid "App repair completed with errors:\n" +msgstr "" + +#: setup.py:189 #, fuzzy #| msgid "Install Apps" msgid "Uninstalling app" msgstr "Alkalmazások telepítése" -#: setup.py:117 +#: setup.py:205 #, fuzzy, python-brace-format #| msgid "Error installing application: {error}" msgid "Error uninstalling app: {error}" msgstr "Hiba lépett fel az alkalmazás telepítésekor: {error}" -#: setup.py:120 +#: setup.py:208 #, fuzzy #| msgid "Application installed." msgid "App uninstalled." msgstr "Alkalmazás telepítve." -#: setup.py:493 +#: setup.py:581 #, fuzzy #| msgid "Upgrade Packages" msgid "Updating app packages" @@ -9153,6 +9189,12 @@ msgstr "Frissítés" msgid "Backup" msgstr "Biztonsági mentések" +#: templates/toolbar.html:53 +#, fuzzy +#| msgid "Start setup" +msgid "Re-run setup" +msgstr "Beállítás elindítása" + #: templates/toolbar.html:59 templates/toolbar.html:60 #: templates/uninstall.html:30 #, fuzzy @@ -9185,6 +9227,28 @@ msgstr "" msgid "Gujarati" msgstr "Gudzsaráti" +#~ msgid "Cannot test: No domains are configured." +#~ msgstr "Sikertelen tesztelés: Nincsenek konfigurált domainek." + +#~ msgid "Media streaming server" +#~ msgstr "Média streaming szerver" + +#~ msgid "" +#~ "Directory that MiniDLNA Server will read for content. All sub-directories " +#~ "of this will be also scanned for media files. If you change the default " +#~ "ensure that the new directory exists and that is readable from the " +#~ "\"minidlna\" user. Any user media directories (\"/home/username/\") will " +#~ "usually work." +#~ msgstr "" +#~ "Könyvtár, amelyet a MiniDLNA-szerver befog olvasni tartalomért. Ennek " +#~ "minden alkönyvtára szintén át lesz vizsgálva médiafájlok után. Ha " +#~ "megváltoztatod az alapértelmezést akkor győződj meg arról is, hogy az új " +#~ "könyvtár létezik és olvasható a \"minidlna\" felhasználó által. Általában " +#~ "bármely felhasználói médiakönyvtár (\"/home/felhasznalonev/\") működik." + +#~ msgid "Specified directory does not exist." +#~ msgstr "A megadott könyvtár nem létezik." + #~ msgid "Storage snapshots configuration updated" #~ msgstr "Tárhelypillanatképek konfigurációja frissítve" diff --git a/plinth/locale/id/LC_MESSAGES/django.po b/plinth/locale/id/LC_MESSAGES/django.po index c7a70b36a..18dee8a28 100644 --- a/plinth/locale/id/LC_MESSAGES/django.po +++ b/plinth/locale/id/LC_MESSAGES/django.po @@ -2,7 +2,7 @@ msgid "" msgstr "" "Project-Id-Version: Indonesian (FreedomBox)\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-04-22 20:02-0400\n" +"POT-Creation-Date: 2024-05-06 20:18-0400\n" "PO-Revision-Date: 2022-09-14 17:19+0000\n" "Last-Translator: ikmaak \n" "Language-Team: Indonesian \n" "Language-Team: Italian \n" "Language-Team: Japanese \n" "Language-Team: Kannada \n" "Language-Team: Lithuanian \n" "Language-Team: Latvian \n" "Language-Team: Norwegian Bokmål \n" "Language-Team: Dutch 10 tekens zijn, en niet makkelijk " "te raden. Laat dit veld leeg om het huidige wachtwoord te behouden." -#: modules/nextcloud/forms.py:38 +#: modules/nextcloud/forms.py:41 #, fuzzy #| msgid "Default zone is external" msgid "Default phone region" msgstr "Standaardzone is extern" -#: modules/nextcloud/forms.py:39 +#: modules/nextcloud/forms.py:42 msgid "" "The default phone region is required to validate phone numbers in the " "profile settings without a country code." @@ -8590,67 +8592,103 @@ msgstr "Pakket {package_expression} is niet beschikbaar voor installatie" msgid "Package {package_name} is the latest version ({latest_version})" msgstr "Pakket {package_name} is de nieuwste versie ({latest_version})" -#: package.py:419 +#: package.py:420 msgid "installing" msgstr "installeren" -#: package.py:421 +#: package.py:422 msgid "downloading" msgstr "downloaden" -#: package.py:423 +#: package.py:424 msgid "media change" msgstr "media wijzigen" -#: package.py:425 +#: package.py:426 #, python-brace-format msgid "configuration file: {file}" msgstr "configuratiebestand: {file}" -#: package.py:453 package.py:478 +#: package.py:454 package.py:479 msgid "Timeout waiting for package manager" msgstr "Time-out wachtend op pakketbeheerder" -#: setup.py:39 +#: setup.py:42 msgid "Installing app" msgstr "Toepassing installeren" -#: setup.py:41 +#: setup.py:44 msgid "Updating app" msgstr "Toepassing updaten" -#: setup.py:75 +#: setup.py:78 #, python-brace-format msgid "Error installing app: {error}" msgstr "Fout bij het installeren van de toepassing: {error}" -#: setup.py:78 +#: setup.py:81 setup.py:151 +#, fuzzy, python-brace-format +#| msgid "Error updating app: {error}" +msgid "Error repairing app: {error}" +msgstr "Fout bij het bijwerken van de toepassing: {error}" + +#: setup.py:84 #, python-brace-format msgid "Error updating app: {error}" msgstr "Fout bij het bijwerken van de toepassing: {error}" -#: setup.py:82 +#: setup.py:88 msgid "App installed." msgstr "De toepassing is geïnstalleerd." -#: setup.py:84 +#: setup.py:92 msgid "App updated" msgstr "Toepassing bijgewerkt" -#: setup.py:101 +#: setup.py:110 +#, fuzzy +#| msgid "Updating app" +msgid "Repairing app" +msgstr "Toepassing updaten" + +#: setup.py:130 +#, fuzzy, python-brace-format +#| msgid "Error uninstalling app: {error}" +msgid "Error running diagnostics: {error}" +msgstr "Fout bij het verwijderen van de toepassing: {error}" + +#: setup.py:143 +msgid "Skipping repair, no failed checks" +msgstr "" + +#: setup.py:157 +msgid "Re-running setup to complete repairs" +msgstr "" + +#: setup.py:165 +#, fuzzy +#| msgid "App updated" +msgid "App repaired." +msgstr "Toepassing bijgewerkt" + +#: setup.py:169 +msgid "App repair completed with errors:\n" +msgstr "" + +#: setup.py:189 msgid "Uninstalling app" msgstr "Toepassing wordt verwijderd" -#: setup.py:117 +#: setup.py:205 #, python-brace-format msgid "Error uninstalling app: {error}" msgstr "Fout bij het verwijderen van de toepassing: {error}" -#: setup.py:120 +#: setup.py:208 msgid "App uninstalled." msgstr "De toepassing is verwijderd." -#: setup.py:493 +#: setup.py:581 msgid "Updating app packages" msgstr "Toepassings-pakketten bijwerken" @@ -9022,6 +9060,10 @@ msgstr "Update" msgid "Backup" msgstr "Back-up" +#: templates/toolbar.html:53 +msgid "Re-run setup" +msgstr "Setup opnieuw uitvoeren" + #: templates/toolbar.html:59 templates/toolbar.html:60 #: templates/uninstall.html:30 msgid "Uninstall" @@ -9053,6 +9095,28 @@ msgstr "voor het verwijderen van {app_id}" msgid "Gujarati" msgstr "Gujarati" +#~ msgid "Cannot test: No domains are configured." +#~ msgstr "Kan niet testen: Er zijn geen domeinen ingesteld." + +#~ msgid "Media streaming server" +#~ msgstr "Mediastreaming server" + +#~ msgid "" +#~ "Directory that MiniDLNA Server will read for content. All sub-directories " +#~ "of this will be also scanned for media files. If you change the default " +#~ "ensure that the new directory exists and that is readable from the " +#~ "\"minidlna\" user. Any user media directories (\"/home/username/\") will " +#~ "usually work." +#~ msgstr "" +#~ "Map die de MiniDLNA Server zal doorzoeken. Alle submappen hiervan worden " +#~ "ook doorzocht op mediabestanden. Als de standaardmap verandert, zorg er " +#~ "dan voor dat de nieuwe map bestaat en dat deze leesbaar is voor de " +#~ "\"minidlna\" -gebruiker. Alle mediamappen van gebruikers binnen (\"/ " +#~ "home / gebruikersnaam /\") zullen meestal werken." + +#~ msgid "Specified directory does not exist." +#~ msgstr "De opgegeven map bestaat niet." + #~ msgid "Storage snapshots configuration updated" #~ msgstr "Opslag van Snapshots configuratie is bijgewerkt" diff --git a/plinth/locale/pl/LC_MESSAGES/django.po b/plinth/locale/pl/LC_MESSAGES/django.po index 88fcae407..7ad04f970 100644 --- a/plinth/locale/pl/LC_MESSAGES/django.po +++ b/plinth/locale/pl/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-04-22 20:02-0400\n" +"POT-Creation-Date: 2024-05-06 20:18-0400\n" "PO-Revision-Date: 2022-09-14 17:19+0000\n" "Last-Translator: ikmaak \n" "Language-Team: Polish \n" "Language-Team: Portuguese \n" "Language-Team: Russian 10 символов. Оставьте это поле пустым, чтобы сохранить " "текущий пароль." -#: modules/nextcloud/forms.py:38 +#: modules/nextcloud/forms.py:41 #, fuzzy #| msgid "Default app set" msgid "Default phone region" msgstr "Приложение по умолчанию настроено" -#: modules/nextcloud/forms.py:39 +#: modules/nextcloud/forms.py:42 msgid "" "The default phone region is required to validate phone numbers in the " "profile settings without a country code." @@ -8710,82 +8710,118 @@ msgstr "Пакет {expression} недоступен для установки" msgid "Package {package_name} is the latest version ({latest_version})" msgstr "Пакет {package_name} последней версией ({latest_version})" -#: package.py:419 +#: package.py:420 msgid "installing" msgstr "Установка" -#: package.py:421 +#: package.py:422 msgid "downloading" msgstr "Загрузка" -#: package.py:423 +#: package.py:424 msgid "media change" msgstr "изменение медиа" -#: package.py:425 +#: package.py:426 #, python-brace-format msgid "configuration file: {file}" msgstr "Файл настроек: {file}" -#: package.py:453 package.py:478 +#: package.py:454 package.py:479 msgid "Timeout waiting for package manager" msgstr "" -#: setup.py:39 +#: setup.py:42 #, fuzzy #| msgid "Install Apps" msgid "Installing app" msgstr "Установка приложений" -#: setup.py:41 +#: setup.py:44 #, fuzzy #| msgid "Updating..." msgid "Updating app" msgstr "Обновляется..." -#: setup.py:75 +#: setup.py:78 #, fuzzy, python-brace-format #| msgid "Error installing application: {error}" msgid "Error installing app: {error}" msgstr "Ошибка при установке приложения: {error}" -#: setup.py:78 +#: setup.py:81 setup.py:151 +#, fuzzy, python-brace-format +#| msgid "Error installing application: {error}" +msgid "Error repairing app: {error}" +msgstr "Ошибка при установке приложения: {error}" + +#: setup.py:84 #, fuzzy, python-brace-format #| msgid "Error installing application: {error}" msgid "Error updating app: {error}" msgstr "Ошибка при установке приложения: {error}" -#: setup.py:82 +#: setup.py:88 #, fuzzy #| msgid "Application installed." msgid "App installed." msgstr "Приложение установлено." -#: setup.py:84 +#: setup.py:92 #, fuzzy #| msgid "Last update" msgid "App updated" msgstr "Последнее обновление" -#: setup.py:101 +#: setup.py:110 +#, fuzzy +#| msgid "Updating..." +msgid "Repairing app" +msgstr "Обновляется..." + +#: setup.py:130 +#, fuzzy, python-brace-format +#| msgid "Error installing application: {error}" +msgid "Error running diagnostics: {error}" +msgstr "Ошибка при установке приложения: {error}" + +#: setup.py:143 +msgid "Skipping repair, no failed checks" +msgstr "" + +#: setup.py:157 +msgid "Re-running setup to complete repairs" +msgstr "" + +#: setup.py:165 +#, fuzzy +#| msgid "Last update" +msgid "App repaired." +msgstr "Последнее обновление" + +#: setup.py:169 +msgid "App repair completed with errors:\n" +msgstr "" + +#: setup.py:189 #, fuzzy #| msgid "Install Apps" msgid "Uninstalling app" msgstr "Установка приложений" -#: setup.py:117 +#: setup.py:205 #, fuzzy, python-brace-format #| msgid "Error installing application: {error}" msgid "Error uninstalling app: {error}" msgstr "Ошибка при установке приложения: {error}" -#: setup.py:120 +#: setup.py:208 #, fuzzy #| msgid "Application installed." msgid "App uninstalled." msgstr "Приложение установлено." -#: setup.py:493 +#: setup.py:581 #, fuzzy #| msgid "Upgrade Packages" msgid "Updating app packages" @@ -9162,6 +9198,12 @@ msgstr "Обновление" msgid "Backup" msgstr "Резервные копии" +#: templates/toolbar.html:53 +#, fuzzy +#| msgid "Start setup" +msgid "Re-run setup" +msgstr "Запуск программы установки" + #: templates/toolbar.html:59 templates/toolbar.html:60 #: templates/uninstall.html:30 #, fuzzy @@ -9194,6 +9236,31 @@ msgstr "" msgid "Gujarati" msgstr "Гуджарати" +#~ msgid "Cannot test: No domains are configured." +#~ msgstr "Невозможно провести тестирование: Не настроены домены." + +#~ msgid "Media streaming server" +#~ msgstr "Сервер потоковой передачи мультимедиа" + +#~ msgid "" +#~ "Directory that MiniDLNA Server will read for content. All sub-directories " +#~ "of this will be also scanned for media files. If you change the default " +#~ "ensure that the new directory exists and that is readable from the " +#~ "\"minidlna\" user. Any user media directories (\"/home/username/\") will " +#~ "usually work." +#~ msgstr "" +#~ "Каталог, который MiniDLNA Server будет читать для содержимого. Все его " +#~ "подкаталоги также будут сканироваться на наличие файлов мультимедиа. Если " +#~ "вы измените значение по умолчанию, убедитесь, что новый каталог " +#~ "существует и доступен для чтения пользователем minidlna. Любые " +#~ "пользовательские медиа-каталоги (\"/home/username/\") обычно работают." + +#~ msgid "Specified directory does not exist." +#~ msgstr "Указанный каталог не существует." + +#~ msgid "Examples: \"myfreedombox.example.org\" or \"example.onion\"." +#~ msgstr "Примеры: «myfreedombox.example.org» или «example.onion»." + #~ msgid "To perform administrative actions, use the " #~ msgstr "Для выполнения административных действий, используйте " diff --git a/plinth/locale/si/LC_MESSAGES/django.po b/plinth/locale/si/LC_MESSAGES/django.po index f8d22995f..6f51cac86 100644 --- a/plinth/locale/si/LC_MESSAGES/django.po +++ b/plinth/locale/si/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-04-22 20:02-0400\n" +"POT-Creation-Date: 2024-05-06 20:18-0400\n" "PO-Revision-Date: 2021-04-27 13:32+0000\n" "Last-Translator: HelaBasa \n" "Language-Team: Sinhala \n" "Language-Team: Slovenian \n" "Language-Team: Albanian 10 shenja. Që të " "mbani fjalëkalimin e tanishëm, lëreni të zbrazët këtë fushë." -#: modules/nextcloud/forms.py:38 +#: modules/nextcloud/forms.py:41 msgid "Default phone region" msgstr "Zonë telefonike parazgjedhje" -#: modules/nextcloud/forms.py:39 +#: modules/nextcloud/forms.py:42 msgid "" "The default phone region is required to validate phone numbers in the " "profile settings without a country code." @@ -8630,67 +8632,103 @@ msgid "Package {package_name} is the latest version ({latest_version})" msgstr "" "Paketa {package_name} gjendet nën versionin më të ri ({latest_version})" -#: package.py:419 +#: package.py:420 msgid "installing" msgstr "po instalohet" -#: package.py:421 +#: package.py:422 msgid "downloading" msgstr "po shkarkohet" -#: package.py:423 +#: package.py:424 msgid "media change" msgstr "ndryshim media" -#: package.py:425 +#: package.py:426 #, python-brace-format msgid "configuration file: {file}" msgstr "kartelë formësimi: {file}" -#: package.py:453 package.py:478 +#: package.py:454 package.py:479 msgid "Timeout waiting for package manager" msgstr "Mbaroi koha teksa pritej për përgjegjës paketash" -#: setup.py:39 +#: setup.py:42 msgid "Installing app" msgstr "Po instalohet aplikacioni" -#: setup.py:41 +#: setup.py:44 msgid "Updating app" msgstr "Po përditësohet aplikacioni" -#: setup.py:75 +#: setup.py:78 #, python-brace-format msgid "Error installing app: {error}" msgstr "Gabim në instalimin e aplikacionit: {error}" -#: setup.py:78 +#: setup.py:81 setup.py:151 +#, fuzzy, python-brace-format +#| msgid "Error updating app: {error}" +msgid "Error repairing app: {error}" +msgstr "Gabim në përditësimin e aplikacionit: {error}" + +#: setup.py:84 #, python-brace-format msgid "Error updating app: {error}" msgstr "Gabim në përditësimin e aplikacionit: {error}" -#: setup.py:82 +#: setup.py:88 msgid "App installed." msgstr "Aplikacioni u instalua." -#: setup.py:84 +#: setup.py:92 msgid "App updated" msgstr "Aplikacioni u përditësua" -#: setup.py:101 +#: setup.py:110 +#, fuzzy +#| msgid "Updating app" +msgid "Repairing app" +msgstr "Po përditësohet aplikacioni" + +#: setup.py:130 +#, fuzzy, python-brace-format +#| msgid "Error uninstalling app: {error}" +msgid "Error running diagnostics: {error}" +msgstr "Gabim në çinstalimin e aplikacionit: {error}" + +#: setup.py:143 +msgid "Skipping repair, no failed checks" +msgstr "" + +#: setup.py:157 +msgid "Re-running setup to complete repairs" +msgstr "" + +#: setup.py:165 +#, fuzzy +#| msgid "App updated" +msgid "App repaired." +msgstr "Aplikacioni u përditësua" + +#: setup.py:169 +msgid "App repair completed with errors:\n" +msgstr "" + +#: setup.py:189 msgid "Uninstalling app" msgstr "Po çinstalohet aplikacion" -#: setup.py:117 +#: setup.py:205 #, python-brace-format msgid "Error uninstalling app: {error}" msgstr "Gabim në çinstalimin e aplikacionit: {error}" -#: setup.py:120 +#: setup.py:208 msgid "App uninstalled." msgstr "Aplikacioni u çinstalua." -#: setup.py:493 +#: setup.py:581 msgid "Updating app packages" msgstr "Po përditësohet paketa aplikacioni" @@ -9061,6 +9099,10 @@ msgstr "Përditësoje" msgid "Backup" msgstr "Kopjeruajtje" +#: templates/toolbar.html:53 +msgid "Re-run setup" +msgstr "Ribëni ujdisjen" + #: templates/toolbar.html:59 templates/toolbar.html:60 #: templates/uninstall.html:30 msgid "Uninstall" @@ -9092,6 +9134,31 @@ msgstr "para çinstalimit të {app_id}" msgid "Gujarati" msgstr "Gujaratase" +#~ msgid "Cannot test: No domains are configured." +#~ msgstr "S’mund të testojë: S’ka përkatësi të formësuara." + +#~ msgid "Media streaming server" +#~ msgstr "Shërbyes transmetimi mediash" + +#~ msgid "" +#~ "Directory that MiniDLNA Server will read for content. All sub-directories " +#~ "of this will be also scanned for media files. If you change the default " +#~ "ensure that the new directory exists and that is readable from the " +#~ "\"minidlna\" user. Any user media directories (\"/home/username/\") will " +#~ "usually work." +#~ msgstr "" +#~ "Drejtoria që Shërbyesi MiniDLNA do të lexojë për lëndë. Krejt " +#~ "nëndrejtoritë e saj do të skanohen gjithashtu për kartela media. Nëse " +#~ "ndryshoni parazgjedhjen, sigurohuni që drejtoria e re ekziston dhe se " +#~ "është e lexueshme nga përdoruesi “minidlna”. Çfarëdo drejtorish media " +#~ "përdoruesish (\"/home/emërpërdoruesi/\") zakonisht do të funksionojnë." + +#~ msgid "Specified directory does not exist." +#~ msgstr "Drejtoria e dhënë s’ekziston." + +#~ msgid "Examples: \"myfreedombox.example.org\" or \"example.onion\"." +#~ msgstr "Shembuj: “myfreedombox.example.org”, ose “example.onion”." + #~ msgid "To perform administrative actions, use the " #~ msgstr "Që të kryeni veprime administrative, përdorni " diff --git a/plinth/locale/sr/LC_MESSAGES/django.po b/plinth/locale/sr/LC_MESSAGES/django.po index 67135d148..cb507c53a 100644 --- a/plinth/locale/sr/LC_MESSAGES/django.po +++ b/plinth/locale/sr/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-04-22 20:02-0400\n" +"POT-Creation-Date: 2024-05-06 20:18-0400\n" "PO-Revision-Date: 2022-09-14 17:20+0000\n" "Last-Translator: ikmaak \n" "Language-Team: Serbian \n" "Language-Team: Swedish . Lämna det här fältet tomt om du vill behålla det nuvarande " "lösenordet." -#: modules/nextcloud/forms.py:38 +#: modules/nextcloud/forms.py:41 #, fuzzy #| msgid "Default zone is external" msgid "Default phone region" msgstr "Standardzonen är extern" -#: modules/nextcloud/forms.py:39 +#: modules/nextcloud/forms.py:42 msgid "" "The default phone region is required to validate phone numbers in the " "profile settings without a country code." @@ -8565,67 +8567,103 @@ msgstr "Paket {package_expression} är inte tillgänglig för installation" msgid "Package {package_name} is the latest version ({latest_version})" msgstr "Paketet {package_name} är den senaste versionen ({latest_version})" -#: package.py:419 +#: package.py:420 msgid "installing" msgstr "Installera" -#: package.py:421 +#: package.py:422 msgid "downloading" msgstr "ladda ner" -#: package.py:423 +#: package.py:424 msgid "media change" msgstr "Mediabyte" -#: package.py:425 +#: package.py:426 #, python-brace-format msgid "configuration file: {file}" msgstr "konfigurationsfil: {file}" -#: package.py:453 package.py:478 +#: package.py:454 package.py:479 msgid "Timeout waiting for package manager" msgstr "Timeout väntar på pakethanteraren" -#: setup.py:39 +#: setup.py:42 msgid "Installing app" msgstr "Installera app" -#: setup.py:41 +#: setup.py:44 msgid "Updating app" msgstr "Uppdatera app" -#: setup.py:75 +#: setup.py:78 #, python-brace-format msgid "Error installing app: {error}" msgstr "Fel vid installation av app: {error}" -#: setup.py:78 +#: setup.py:81 setup.py:151 +#, fuzzy, python-brace-format +#| msgid "Error updating app: {error}" +msgid "Error repairing app: {error}" +msgstr "Fel vid uppdatering av app: {error}" + +#: setup.py:84 #, python-brace-format msgid "Error updating app: {error}" msgstr "Fel vid uppdatering av app: {error}" -#: setup.py:82 +#: setup.py:88 msgid "App installed." msgstr "App installerad." -#: setup.py:84 +#: setup.py:92 msgid "App updated" msgstr "App uppdaterad" -#: setup.py:101 +#: setup.py:110 +#, fuzzy +#| msgid "Updating app" +msgid "Repairing app" +msgstr "Uppdatera app" + +#: setup.py:130 +#, fuzzy, python-brace-format +#| msgid "Error uninstalling app: {error}" +msgid "Error running diagnostics: {error}" +msgstr "Fel vid avinstallation av appen: {error}" + +#: setup.py:143 +msgid "Skipping repair, no failed checks" +msgstr "" + +#: setup.py:157 +msgid "Re-running setup to complete repairs" +msgstr "" + +#: setup.py:165 +#, fuzzy +#| msgid "App updated" +msgid "App repaired." +msgstr "App uppdaterad" + +#: setup.py:169 +msgid "App repair completed with errors:\n" +msgstr "" + +#: setup.py:189 msgid "Uninstalling app" msgstr "Avinstallera app" -#: setup.py:117 +#: setup.py:205 #, python-brace-format msgid "Error uninstalling app: {error}" msgstr "Fel vid avinstallation av appen: {error}" -#: setup.py:120 +#: setup.py:208 msgid "App uninstalled." msgstr "Appen avinstallerad." -#: setup.py:493 +#: setup.py:581 msgid "Updating app packages" msgstr "Uppdatera appaket" @@ -8998,6 +9036,10 @@ msgstr "Uppdatera" msgid "Backup" msgstr "Säkerhetskopia" +#: templates/toolbar.html:53 +msgid "Re-run setup" +msgstr "Kör installation igen" + #: templates/toolbar.html:59 templates/toolbar.html:60 #: templates/uninstall.html:30 msgid "Uninstall" @@ -9029,6 +9071,28 @@ msgstr "innan du avinstallerar {app_id}" msgid "Gujarati" msgstr "Gujarati" +#~ msgid "Cannot test: No domains are configured." +#~ msgstr "Kan inte testa: Inga domäner är konfigurerade." + +#~ msgid "Media streaming server" +#~ msgstr "Media Streaming Server" + +#~ msgid "" +#~ "Directory that MiniDLNA Server will read for content. All sub-directories " +#~ "of this will be also scanned for media files. If you change the default " +#~ "ensure that the new directory exists and that is readable from the " +#~ "\"minidlna\" user. Any user media directories (\"/home/username/\") will " +#~ "usually work." +#~ msgstr "" +#~ "Katalog som MiniDLNA-servern kommer att läsa för innehåll. Alla " +#~ "underkataloger i detta kommer också att skannas för mediefiler. Om du " +#~ "ändrar standardvärdet se till att den nya katalogen finns och som är " +#~ "läsbar från \"minidlna\"-användaren. Alla användar mediekataloger (\"/" +#~ "Home/username/\") kommer vanligtvis att fungera." + +#~ msgid "Specified directory does not exist." +#~ msgstr "Den angivna katalogen finns inte." + #~ msgid "Storage snapshots configuration updated" #~ msgstr "Lagring ögonblicksbildkonfiguration uppdaterad" diff --git a/plinth/locale/ta/LC_MESSAGES/django.po b/plinth/locale/ta/LC_MESSAGES/django.po index d57aa4527..a64ee58f6 100644 --- a/plinth/locale/ta/LC_MESSAGES/django.po +++ b/plinth/locale/ta/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-04-22 20:02-0400\n" +"POT-Creation-Date: 2024-05-06 20:18-0400\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -27,27 +27,27 @@ msgstr "" msgid "FreedomBox" msgstr "" -#: daemon.py:122 +#: daemon.py:124 #, python-brace-format msgid "Service {service_name} is running" msgstr "" -#: daemon.py:218 +#: daemon.py:222 #, python-brace-format msgid "Listening on {kind} port {listen_address}:{port}" msgstr "" -#: daemon.py:221 +#: daemon.py:225 #, python-brace-format msgid "Listening on {kind} port {port}" msgstr "" -#: daemon.py:291 +#: daemon.py:296 #, python-brace-format msgid "Connect to {host}:{port}" msgstr "" -#: daemon.py:299 +#: daemon.py:304 #, python-brace-format msgid "Cannot connect to {host}:{port}" msgstr "" @@ -134,12 +134,12 @@ msgstr "" msgid "{box_name} Web Interface (Plinth)" msgstr "" -#: modules/apache/components.py:159 +#: modules/apache/components.py:162 #, python-brace-format msgid "Access URL {url} on tcp{kind}" msgstr "" -#: modules/apache/components.py:162 +#: modules/apache/components.py:165 #, python-brace-format msgid "Access URL {url}" msgstr "" @@ -844,7 +844,7 @@ msgstr "" msgid "Admin" msgstr "" -#: modules/bepasty/views.py:88 modules/diagnostics/views.py:52 +#: modules/bepasty/views.py:88 modules/diagnostics/views.py:55 #: modules/nextcloud/views.py:62 modules/searx/views.py:35 #: modules/searx/views.py:46 modules/security/views.py:56 #: modules/snapshot/views.py:158 modules/tor/views.py:73 @@ -1448,8 +1448,7 @@ msgstr "" #: modules/diagnostics/templates/diagnostics_app.html:20 #: modules/diagnostics/templates/diagnostics_full.html:48 -#: templates/toolbar.html:53 -msgid "Re-run setup" +msgid "Try to repair" msgstr "" #: modules/diagnostics/templates/diagnostics_app.html:32 @@ -1489,10 +1488,15 @@ msgstr "" msgid "Result" msgstr "" -#: modules/diagnostics/views.py:111 +#: modules/diagnostics/views.py:114 msgid "Diagnostic Test" msgstr "" +#: modules/diagnostics/views.py:144 +#, python-brace-format +msgid "App {app_id} is not installed, cannot repair" +msgstr "" + #: modules/dynamicdns/__init__.py:28 #, python-brace-format msgid "" @@ -1650,7 +1654,7 @@ msgstr "" #: modules/dynamicdns/templates/dynamicdns.html:18 #: modules/email/templates/email.html:35 #: modules/letsencrypt/templates/letsencrypt.html:24 -#: modules/mediawiki/forms.py:64 modules/nextcloud/forms.py:26 +#: modules/mediawiki/forms.py:64 msgid "Domain" msgstr "" @@ -1998,12 +2002,12 @@ msgstr "" msgid "Port {name} ({details}) available for internal networks" msgstr "" -#: modules/firewall/components.py:153 +#: modules/firewall/components.py:154 #, python-brace-format msgid "Port {name} ({details}) available for external networks" msgstr "" -#: modules/firewall/components.py:159 +#: modules/firewall/components.py:160 #, python-brace-format msgid "Port {name} ({details}) unavailable for external networks" msgstr "" @@ -2921,10 +2925,6 @@ msgstr "" msgid "Certificates" msgstr "" -#: modules/letsencrypt/__init__.py:105 -msgid "Cannot test: No domains are configured." -msgstr "" - #: modules/letsencrypt/templates/letsencrypt.html:25 msgid "Certificate Status" msgstr "" @@ -3362,7 +3362,7 @@ msgstr "" msgid "Address" msgstr "" -#: modules/minidlna/__init__.py:22 +#: modules/minidlna/__init__.py:20 msgid "" "MiniDLNA is a simple media server software, with the aim of being fully " "compliant with DLNA/UPnP-AV clients. The MiniDLNA daemon serves media files " @@ -3373,28 +3373,22 @@ msgid "" "Kodi." msgstr "" -#: modules/minidlna/__init__.py:44 -msgid "Media streaming server" -msgstr "" - -#: modules/minidlna/__init__.py:47 +#: modules/minidlna/__init__.py:45 msgid "MiniDLNA" msgstr "" -#: modules/minidlna/__init__.py:48 +#: modules/minidlna/__init__.py:46 msgid "Simple Media Server" msgstr "" -#: modules/minidlna/forms.py:13 +#: modules/minidlna/forms.py:20 msgid "Media Files Directory" msgstr "" -#: modules/minidlna/forms.py:14 +#: modules/minidlna/forms.py:21 msgid "" "Directory that MiniDLNA Server will read for content. All sub-directories of " -"this will be also scanned for media files. If you change the default ensure " -"that the new directory exists and that is readable from the \"minidlna\" " -"user. Any user media directories (\"/home/username/\") will usually work." +"this will be also scanned for media files." msgstr "" #: modules/minidlna/manifest.py:10 @@ -3413,11 +3407,7 @@ msgstr "" msgid "totem" msgstr "" -#: modules/minidlna/views.py:35 -msgid "Specified directory does not exist." -msgstr "" - -#: modules/minidlna/views.py:38 +#: modules/minidlna/views.py:33 msgid "Updated media directory" msgstr "" @@ -4541,7 +4531,7 @@ msgstr "" msgid "Failed to delete connection: Connection not found." msgstr "" -#: modules/nextcloud/__init__.py:20 +#: modules/nextcloud/__init__.py:25 msgid "" "Nextcloud is a self-hosted productivity platform which provides private and " "secure functions for file sharing, collaborative work, and more. Nextcloud " @@ -4550,29 +4540,29 @@ msgid "" "interface." msgstr "" -#: modules/nextcloud/__init__.py:25 +#: modules/nextcloud/__init__.py:30 msgid "All users of FreedomBox can use Nextcloud. To perform administrative " msgstr "" -#: modules/nextcloud/__init__.py:29 +#: modules/nextcloud/__init__.py:34 #, python-brace-format msgid "" "Please note that Nextcloud is installed and run inside a container provided " -"by the Nextcloud project. Security, quality, privacy and legal reviews are " +"by the Nextcloud community. Security, quality, privacy and legal reviews are " "done by the upstream project and not by Debian/{box_name}. Updates are " "performed following an independent cycle." msgstr "" -#: modules/nextcloud/__init__.py:35 +#: modules/nextcloud/__init__.py:40 msgid "This app is experimental." msgstr "" -#: modules/nextcloud/__init__.py:53 modules/nextcloud/manifest.py:11 +#: modules/nextcloud/__init__.py:58 modules/nextcloud/manifest.py:11 #: modules/nextcloud/manifest.py:18 msgid "Nextcloud" msgstr "" -#: modules/nextcloud/__init__.py:55 +#: modules/nextcloud/__init__.py:60 msgid "File Storage & Collaboration" msgstr "" @@ -4580,15 +4570,22 @@ msgstr "" msgid "Not set" msgstr "" -#: modules/nextcloud/forms.py:27 -msgid "Examples: \"myfreedombox.example.org\" or \"example.onion\"." +#: modules/nextcloud/forms.py:26 +msgid "Override domain" msgstr "" -#: modules/nextcloud/forms.py:30 +#: modules/nextcloud/forms.py:27 +msgid "" +"Set to the domain or IP address that Nextcloud should be forced to generate " +"URLs with. Should not be needed if a valid domain is used to access " +"Nextcloud. Examples: \"myfreedombox.example.org\" or \"example.onion\"." +msgstr "" + +#: modules/nextcloud/forms.py:33 msgid "Administrator password" msgstr "" -#: modules/nextcloud/forms.py:31 +#: modules/nextcloud/forms.py:34 msgid "" "Optional. Set a new password for Nextcloud's administrator account " "(nextcloud-admin). The password cannot be a common one and the minimum " @@ -4596,11 +4593,11 @@ msgid "" "keep the current password." msgstr "" -#: modules/nextcloud/forms.py:38 +#: modules/nextcloud/forms.py:41 msgid "Default phone region" msgstr "" -#: modules/nextcloud/forms.py:39 +#: modules/nextcloud/forms.py:42 msgid "" "The default phone region is required to validate phone numbers in the " "profile settings without a country code." @@ -7428,67 +7425,97 @@ msgstr "" msgid "Package {package_name} is the latest version ({latest_version})" msgstr "" -#: package.py:419 +#: package.py:420 msgid "installing" msgstr "" -#: package.py:421 +#: package.py:422 msgid "downloading" msgstr "" -#: package.py:423 +#: package.py:424 msgid "media change" msgstr "" -#: package.py:425 +#: package.py:426 #, python-brace-format msgid "configuration file: {file}" msgstr "" -#: package.py:453 package.py:478 +#: package.py:454 package.py:479 msgid "Timeout waiting for package manager" msgstr "" -#: setup.py:39 +#: setup.py:42 msgid "Installing app" msgstr "" -#: setup.py:41 +#: setup.py:44 msgid "Updating app" msgstr "" -#: setup.py:75 -#, python-brace-format -msgid "Error installing app: {error}" -msgstr "" - #: setup.py:78 #, python-brace-format -msgid "Error updating app: {error}" +msgid "Error installing app: {error}" msgstr "" -#: setup.py:82 -msgid "App installed." +#: setup.py:81 setup.py:151 +#, python-brace-format +msgid "Error repairing app: {error}" msgstr "" #: setup.py:84 +#, python-brace-format +msgid "Error updating app: {error}" +msgstr "" + +#: setup.py:88 +msgid "App installed." +msgstr "" + +#: setup.py:92 msgid "App updated" msgstr "" -#: setup.py:101 +#: setup.py:110 +msgid "Repairing app" +msgstr "" + +#: setup.py:130 +#, python-brace-format +msgid "Error running diagnostics: {error}" +msgstr "" + +#: setup.py:143 +msgid "Skipping repair, no failed checks" +msgstr "" + +#: setup.py:157 +msgid "Re-running setup to complete repairs" +msgstr "" + +#: setup.py:165 +msgid "App repaired." +msgstr "" + +#: setup.py:169 +msgid "App repair completed with errors:\n" +msgstr "" + +#: setup.py:189 msgid "Uninstalling app" msgstr "" -#: setup.py:117 +#: setup.py:205 #, python-brace-format msgid "Error uninstalling app: {error}" msgstr "" -#: setup.py:120 +#: setup.py:208 msgid "App uninstalled." msgstr "" -#: setup.py:493 +#: setup.py:581 msgid "Updating app packages" msgstr "" @@ -7821,6 +7848,10 @@ msgstr "" msgid "Backup" msgstr "" +#: templates/toolbar.html:53 +msgid "Re-run setup" +msgstr "" + #: templates/toolbar.html:59 templates/toolbar.html:60 #: templates/uninstall.html:30 msgid "Uninstall" diff --git a/plinth/locale/te/LC_MESSAGES/django.po b/plinth/locale/te/LC_MESSAGES/django.po index c5d8a7041..ba6589e48 100644 --- a/plinth/locale/te/LC_MESSAGES/django.po +++ b/plinth/locale/te/LC_MESSAGES/django.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: FreedomBox UI\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-04-22 20:02-0400\n" +"POT-Creation-Date: 2024-05-06 20:18-0400\n" "PO-Revision-Date: 2024-02-11 20:14+0000\n" "Last-Translator: Sunil Mohan Adapa \n" "Language-Team: Telugu \n" "Language-Team: Turkish \n" @@ -16,7 +16,7 @@ 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 5.5-dev\n" +"X-Generator: Weblate 5.5.1-dev\n" #: config.py:103 #, python-brace-format @@ -27,28 +27,28 @@ msgstr "Sabit yapılandırma {etc_path} düzgün bir şekilde ayarlanmış" msgid "FreedomBox" msgstr "FreedomBox" -#: daemon.py:122 +#: daemon.py:124 #, python-brace-format msgid "Service {service_name} is running" msgstr "{service_name} hizmeti çalışıyor" -#: daemon.py:218 +#: daemon.py:222 #, python-brace-format msgid "Listening on {kind} port {listen_address}:{port}" msgstr "" "{kind} üzerinde {listen_address}:{port} nolu bağlantı noktasını dinleme" -#: daemon.py:221 +#: daemon.py:225 #, python-brace-format msgid "Listening on {kind} port {port}" msgstr "{kind} üzerinde {port} nolu bağlantı noktasını dinleme" -#: daemon.py:291 +#: daemon.py:296 #, python-brace-format msgid "Connect to {host}:{port}" msgstr "{host}:{port} adresine bağlı" -#: daemon.py:299 +#: daemon.py:304 #, python-brace-format msgid "Cannot connect to {host}:{port}" msgstr "{host}:{port} adresine bağlanamıyor" @@ -139,12 +139,12 @@ msgstr "Web Sunucusu" msgid "{box_name} Web Interface (Plinth)" msgstr "{box_name} Web Arayüzü (Plinth)" -#: modules/apache/components.py:159 +#: modules/apache/components.py:162 #, python-brace-format msgid "Access URL {url} on tcp{kind}" msgstr "Tcp{kind} üzerinde erişim URL'si {url}" -#: modules/apache/components.py:162 +#: modules/apache/components.py:165 #, python-brace-format msgid "Access URL {url}" msgstr "Erişim URL'si {url}" @@ -910,7 +910,7 @@ msgstr "Sil" msgid "Admin" msgstr "Yönetici" -#: modules/bepasty/views.py:88 modules/diagnostics/views.py:52 +#: modules/bepasty/views.py:88 modules/diagnostics/views.py:55 #: modules/nextcloud/views.py:62 modules/searx/views.py:35 #: modules/searx/views.py:46 modules/security/views.py:56 #: modules/snapshot/views.py:158 modules/tor/views.py:73 @@ -1587,9 +1587,8 @@ msgstr "Uygulama: %(app_name)s" #: modules/diagnostics/templates/diagnostics_app.html:20 #: modules/diagnostics/templates/diagnostics_full.html:48 -#: templates/toolbar.html:53 -msgid "Re-run setup" -msgstr "Ayarlamayı yeniden çalıştır" +msgid "Try to repair" +msgstr "" #: modules/diagnostics/templates/diagnostics_app.html:32 msgid "This app does not support diagnostics" @@ -1631,10 +1630,15 @@ msgstr "Deneme" msgid "Result" msgstr "Sonuç" -#: modules/diagnostics/views.py:111 +#: modules/diagnostics/views.py:114 msgid "Diagnostic Test" msgstr "Tanı Denemesi" +#: modules/diagnostics/views.py:144 +#, python-brace-format +msgid "App {app_id} is not installed, cannot repair" +msgstr "" + #: modules/dynamicdns/__init__.py:28 #, python-brace-format msgid "" @@ -1824,7 +1828,7 @@ msgstr "Durum" #: modules/dynamicdns/templates/dynamicdns.html:18 #: modules/email/templates/email.html:35 #: modules/letsencrypt/templates/letsencrypt.html:24 -#: modules/mediawiki/forms.py:64 modules/nextcloud/forms.py:26 +#: modules/mediawiki/forms.py:64 msgid "Domain" msgstr "Etki Alanı" @@ -2226,12 +2230,12 @@ msgstr "Doğrudan geçiş kuralları mevcut" msgid "Port {name} ({details}) available for internal networks" msgstr "Dahili ağlar için {name} ({details}) bağlantı noktası kullanılabilir" -#: modules/firewall/components.py:153 +#: modules/firewall/components.py:154 #, python-brace-format msgid "Port {name} ({details}) available for external networks" msgstr "Harici ağlar için {name} ({details}) bağlantı noktası kullanılabilir" -#: modules/firewall/components.py:159 +#: modules/firewall/components.py:160 #, python-brace-format msgid "Port {name} ({details}) unavailable for external networks" msgstr "Harici ağlar için {name} ({details}) bağlantı noktası kullanılamaz" @@ -3313,10 +3317,6 @@ msgstr "Let's Encrypt" msgid "Certificates" msgstr "Sertifikalar" -#: modules/letsencrypt/__init__.py:105 -msgid "Cannot test: No domains are configured." -msgstr "Denenemiyor: Hiçbir etki alanı yapılandırılmamış." - #: modules/letsencrypt/templates/letsencrypt.html:25 msgid "Certificate Status" msgstr "Sertifika Durumu" @@ -3844,7 +3844,7 @@ msgstr "" msgid "Address" msgstr "Adres" -#: modules/minidlna/__init__.py:22 +#: modules/minidlna/__init__.py:20 msgid "" "MiniDLNA is a simple media server software, with the aim of being fully " "compliant with DLNA/UPnP-AV clients. The MiniDLNA daemon serves media files " @@ -3862,34 +3862,23 @@ msgstr "" "Xbox 360 gibi) gibi ya da totem ve Kodi gibi uygulamalar da dahil olmak " "üzere DLNA Sertifikası geçen tüm cihazlarla uyumludur." -#: modules/minidlna/__init__.py:44 -msgid "Media streaming server" -msgstr "Ortam akış sunucusu" - -#: modules/minidlna/__init__.py:47 +#: modules/minidlna/__init__.py:45 msgid "MiniDLNA" msgstr "MiniDLNA" -#: modules/minidlna/__init__.py:48 +#: modules/minidlna/__init__.py:46 msgid "Simple Media Server" msgstr "Basit Ortam Sunucusu" -#: modules/minidlna/forms.py:13 +#: modules/minidlna/forms.py:20 msgid "Media Files Directory" msgstr "Ortam Dosyaları Dizini" -#: modules/minidlna/forms.py:14 +#: modules/minidlna/forms.py:21 msgid "" "Directory that MiniDLNA Server will read for content. All sub-directories of " -"this will be also scanned for media files. If you change the default ensure " -"that the new directory exists and that is readable from the \"minidlna\" " -"user. Any user media directories (\"/home/username/\") will usually work." +"this will be also scanned for media files." msgstr "" -"MiniDLNA Sunucusunun içerik için okuyacağı dizin. Bunun tüm alt dizinleri de " -"ortam dosyaları için taranacaktır. Eğer varsayılanı değiştirirseniz, yeni " -"dizinin var olduğundan ve \"minidlna\" kullanıcısının okuyabilir olduğundan " -"emin olun. Herhangi bir kullanıcı ortam dizinleri (\"/home/kullanıcıadı/\") " -"genellikle çalışacaktır." #: modules/minidlna/manifest.py:10 msgid "vlc" @@ -3907,11 +3896,7 @@ msgstr "yaacc" msgid "totem" msgstr "totem" -#: modules/minidlna/views.py:35 -msgid "Specified directory does not exist." -msgstr "Belirtilen dizin mevcut değil." - -#: modules/minidlna/views.py:38 +#: modules/minidlna/views.py:33 msgid "Updated media directory" msgstr "Güncellenmiş ortam dizini" @@ -5190,7 +5175,7 @@ msgstr "{name} bağlantısı silindi." msgid "Failed to delete connection: Connection not found." msgstr "Bağlantının silinmesi başarısız oldu: Bağlantı bulunamadı." -#: modules/nextcloud/__init__.py:20 +#: modules/nextcloud/__init__.py:25 msgid "" "Nextcloud is a self-hosted productivity platform which provides private and " "secure functions for file sharing, collaborative work, and more. Nextcloud " @@ -5204,51 +5189,74 @@ msgstr "" "istemci uygulamalarını ve mobil istemcileri içerir. Nextcloud sunucusu iyi " "bütünleştirilmiş bir web arayüzü sağlar." -#: modules/nextcloud/__init__.py:25 -#, fuzzy -#| msgid "All users of FreedomBox can use Nextcloud." +#: modules/nextcloud/__init__.py:30 msgid "All users of FreedomBox can use Nextcloud. To perform administrative " -msgstr "FreedomBox'ın tüm kullanıcıları Nextcloud'u kullanabilir." +msgstr "" +"FreedomBox'ın tüm kullanıcıları Nextcloud'u kullanabilir. Yönetimsel " +"işlemleri gerçekleştirmek için " -#: modules/nextcloud/__init__.py:29 -#, python-brace-format +#: modules/nextcloud/__init__.py:34 +#, fuzzy, python-brace-format +#| msgid "" +#| "Please note that Nextcloud is installed and run inside a container " +#| "provided by the Nextcloud project. Security, quality, privacy and legal " +#| "reviews are done by the upstream project and not by Debian/{box_name}. " +#| "Updates are performed following an independent cycle." msgid "" "Please note that Nextcloud is installed and run inside a container provided " -"by the Nextcloud project. Security, quality, privacy and legal reviews are " +"by the Nextcloud community. Security, quality, privacy and legal reviews are " "done by the upstream project and not by Debian/{box_name}. Updates are " "performed following an independent cycle." msgstr "" +"Lütfen Nextcloud'un Nextcloud projesi tarafından sağlanan bir kapsayıcı " +"içine yüklendiğini ve çalıştırıldığını unutmayın. Güvenlik, kalite, gizlilik " +"ve yasal incelemeler Debian/{box_name} tarafından değil, yukarı yöndeki " +"proje tarafından yapılır. Güncellemeler bağımsız bir döngünün ardından " +"gerçekleştirilir." -#: modules/nextcloud/__init__.py:35 -#, fuzzy -#| msgid "Uninstalling an app is an experimental feature." +#: modules/nextcloud/__init__.py:40 msgid "This app is experimental." -msgstr "Bir uygulamayı kaldırmak, deneysel bir özelliktir." +msgstr "Bu uygulama deneyseldir." -#: modules/nextcloud/__init__.py:53 modules/nextcloud/manifest.py:11 +#: modules/nextcloud/__init__.py:58 modules/nextcloud/manifest.py:11 #: modules/nextcloud/manifest.py:18 msgid "Nextcloud" msgstr "Nextcloud" -#: modules/nextcloud/__init__.py:55 +#: modules/nextcloud/__init__.py:60 msgid "File Storage & Collaboration" msgstr "Dosya Depolama ve İşbirliği" #: modules/nextcloud/forms.py:19 -#, fuzzy -#| msgid "Hostname set" msgid "Not set" -msgstr "Anamakine adı ayarlandı" +msgstr "Ayarlı değil" + +#: modules/nextcloud/forms.py:26 +#, fuzzy +#| msgid "Server domain" +msgid "Override domain" +msgstr "Sunucu etki alanı" #: modules/nextcloud/forms.py:27 -msgid "Examples: \"myfreedombox.example.org\" or \"example.onion\"." -msgstr "Örnekler: \"freedomboxım.ornek.org\" veya \"ornek.onion\"." +#, fuzzy +#| msgid "" +#| "Used by MediaWiki to generate URLs that point to the wiki such as in " +#| "footer, feeds and emails. Examples: \"myfreedombox.example.org\" or " +#| "\"example.onion\"." +msgid "" +"Set to the domain or IP address that Nextcloud should be forced to generate " +"URLs with. Should not be needed if a valid domain is used to access " +"Nextcloud. Examples: \"myfreedombox.example.org\" or \"example.onion\"." +msgstr "" +"MediaWiki tarafından, altbilgi, bildirimler ve e-postalar gibi viki'yi " +"işaret eden URL'ler oluşturmak için kullanılır. Örnekler: \"benimfreedombox." +"ornek.org\" veya \"ornek.onion\"." -#: modules/nextcloud/forms.py:30 +#: modules/nextcloud/forms.py:33 msgid "Administrator password" msgstr "Yönetici parolası" -#: modules/nextcloud/forms.py:31 +#: modules/nextcloud/forms.py:34 msgid "" "Optional. Set a new password for Nextcloud's administrator account " "(nextcloud-admin). The password cannot be a common one and the minimum " @@ -5260,11 +5268,11 @@ msgstr "" "10 karakterdir. Şu anki parolayı korumak için bu alanı boş " "bırakın." -#: modules/nextcloud/forms.py:38 +#: modules/nextcloud/forms.py:41 msgid "Default phone region" msgstr "Varsayılan telefon bölgesi" -#: modules/nextcloud/forms.py:39 +#: modules/nextcloud/forms.py:42 msgid "" "The default phone region is required to validate phone numbers in the " "profile settings without a country code." @@ -8567,67 +8575,103 @@ msgstr "{package_expression} paketi yükleme için mevcut değil" msgid "Package {package_name} is the latest version ({latest_version})" msgstr "{package_name} paketi en son sürümdür ({latest_version})" -#: package.py:419 +#: package.py:420 msgid "installing" msgstr "yükleniyor" -#: package.py:421 +#: package.py:422 msgid "downloading" msgstr "indiriliyor" -#: package.py:423 +#: package.py:424 msgid "media change" msgstr "ortam değiştirme" -#: package.py:425 +#: package.py:426 #, python-brace-format msgid "configuration file: {file}" msgstr "yapılandırma dosyası: {file}" -#: package.py:453 package.py:478 +#: package.py:454 package.py:479 msgid "Timeout waiting for package manager" msgstr "Paket yöneticisini beklerken zaman aşımı oldu" -#: setup.py:39 +#: setup.py:42 msgid "Installing app" msgstr "Uygulama yükleniyor" -#: setup.py:41 +#: setup.py:44 msgid "Updating app" msgstr "Uygulama güncelleniyor" -#: setup.py:75 +#: setup.py:78 #, python-brace-format msgid "Error installing app: {error}" msgstr "Uygulama yüklenirken hata oldu: {error}" -#: setup.py:78 +#: setup.py:81 setup.py:151 +#, fuzzy, python-brace-format +#| msgid "Error updating app: {error}" +msgid "Error repairing app: {error}" +msgstr "Uygulama güncellenirken hata oldu: {error}" + +#: setup.py:84 #, python-brace-format msgid "Error updating app: {error}" msgstr "Uygulama güncellenirken hata oldu: {error}" -#: setup.py:82 +#: setup.py:88 msgid "App installed." msgstr "Uygulama yüklendi." -#: setup.py:84 +#: setup.py:92 msgid "App updated" msgstr "Uygulama güncellendi" -#: setup.py:101 +#: setup.py:110 +#, fuzzy +#| msgid "Updating app" +msgid "Repairing app" +msgstr "Uygulama güncelleniyor" + +#: setup.py:130 +#, fuzzy, python-brace-format +#| msgid "Error uninstalling app: {error}" +msgid "Error running diagnostics: {error}" +msgstr "Uygulama kaldırılırken hata oldu: {error}" + +#: setup.py:143 +msgid "Skipping repair, no failed checks" +msgstr "" + +#: setup.py:157 +msgid "Re-running setup to complete repairs" +msgstr "" + +#: setup.py:165 +#, fuzzy +#| msgid "App updated" +msgid "App repaired." +msgstr "Uygulama güncellendi" + +#: setup.py:169 +msgid "App repair completed with errors:\n" +msgstr "" + +#: setup.py:189 msgid "Uninstalling app" msgstr "Uygulama kaldırılıyor" -#: setup.py:117 +#: setup.py:205 #, python-brace-format msgid "Error uninstalling app: {error}" msgstr "Uygulama kaldırılırken hata oldu: {error}" -#: setup.py:120 +#: setup.py:208 msgid "App uninstalled." msgstr "Uygulama kaldırıldı." -#: setup.py:493 +#: setup.py:581 msgid "Updating app packages" msgstr "Uygulama paketleri güncelleniyor" @@ -8998,6 +9042,10 @@ msgstr "Güncelle" msgid "Backup" msgstr "Yedekle" +#: templates/toolbar.html:53 +msgid "Re-run setup" +msgstr "Ayarlamayı yeniden çalıştır" + #: templates/toolbar.html:59 templates/toolbar.html:60 #: templates/uninstall.html:30 msgid "Uninstall" @@ -9029,6 +9077,31 @@ msgstr "{app_id} kaldırılmadan önce" msgid "Gujarati" msgstr "Gujarati" +#~ msgid "Cannot test: No domains are configured." +#~ msgstr "Denenemiyor: Hiçbir etki alanı yapılandırılmamış." + +#~ msgid "Media streaming server" +#~ msgstr "Ortam akış sunucusu" + +#~ msgid "" +#~ "Directory that MiniDLNA Server will read for content. All sub-directories " +#~ "of this will be also scanned for media files. If you change the default " +#~ "ensure that the new directory exists and that is readable from the " +#~ "\"minidlna\" user. Any user media directories (\"/home/username/\") will " +#~ "usually work." +#~ msgstr "" +#~ "MiniDLNA Sunucusunun içerik için okuyacağı dizin. Bunun tüm alt dizinleri " +#~ "de ortam dosyaları için taranacaktır. Eğer varsayılanı değiştirirseniz, " +#~ "yeni dizinin var olduğundan ve \"minidlna\" kullanıcısının okuyabilir " +#~ "olduğundan emin olun. Herhangi bir kullanıcı ortam dizinleri (\"/home/" +#~ "kullanıcıadı/\") genellikle çalışacaktır." + +#~ msgid "Specified directory does not exist." +#~ msgstr "Belirtilen dizin mevcut değil." + +#~ msgid "Examples: \"myfreedombox.example.org\" or \"example.onion\"." +#~ msgstr "Örnekler: \"freedomboxım.ornek.org\" veya \"ornek.onion\"." + #~ msgid "To perform administrative actions, use the " #~ msgstr "Yönetimsel eylemleri gerçekleştirmek için şunu kullanın: " diff --git a/plinth/locale/uk/LC_MESSAGES/django.po b/plinth/locale/uk/LC_MESSAGES/django.po index 1977efdf8..01d06ba9c 100644 --- a/plinth/locale/uk/LC_MESSAGES/django.po +++ b/plinth/locale/uk/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-04-22 20:02-0400\n" +"POT-Creation-Date: 2024-05-06 20:18-0400\n" "PO-Revision-Date: 2024-02-05 00:01+0000\n" "Last-Translator: Ihor Hordiichuk \n" "Language-Team: Ukrainian 10 " "знаків. Залиште поле порожнім, щоб зберегти поточний пароль." -#: modules/nextcloud/forms.py:38 +#: modules/nextcloud/forms.py:41 #, fuzzy #| msgid "Default zone is external" msgid "Default phone region" msgstr "Усталена зона – зовнішня" -#: modules/nextcloud/forms.py:39 +#: modules/nextcloud/forms.py:42 msgid "" "The default phone region is required to validate phone numbers in the " "profile settings without a country code." @@ -8576,67 +8577,103 @@ msgstr "Пакунок {package_expression} недоступний для вст msgid "Package {package_name} is the latest version ({latest_version})" msgstr "Пакунок {package_name} має останню версію ({latest_version})" -#: package.py:419 +#: package.py:420 msgid "installing" msgstr "встановлення" -#: package.py:421 +#: package.py:422 msgid "downloading" msgstr "завантаження" -#: package.py:423 +#: package.py:424 msgid "media change" msgstr "зміна медія" -#: package.py:425 +#: package.py:426 #, python-brace-format msgid "configuration file: {file}" msgstr "файл конфіґурації: {file}" -#: package.py:453 package.py:478 +#: package.py:454 package.py:479 msgid "Timeout waiting for package manager" msgstr "Час очікування менеджера пакунків" -#: setup.py:39 +#: setup.py:42 msgid "Installing app" msgstr "Встановлення застосунку" -#: setup.py:41 +#: setup.py:44 msgid "Updating app" msgstr "Оновлення застосунку" -#: setup.py:75 +#: setup.py:78 #, python-brace-format msgid "Error installing app: {error}" msgstr "Помилка встановлення застосунку: {error}" -#: setup.py:78 +#: setup.py:81 setup.py:151 +#, fuzzy, python-brace-format +#| msgid "Error updating app: {error}" +msgid "Error repairing app: {error}" +msgstr "Помилка оновлення застосунку: {error}" + +#: setup.py:84 #, python-brace-format msgid "Error updating app: {error}" msgstr "Помилка оновлення застосунку: {error}" -#: setup.py:82 +#: setup.py:88 msgid "App installed." msgstr "Застосунок встановлено." -#: setup.py:84 +#: setup.py:92 msgid "App updated" msgstr "Застосунок оновлено" -#: setup.py:101 +#: setup.py:110 +#, fuzzy +#| msgid "Updating app" +msgid "Repairing app" +msgstr "Оновлення застосунку" + +#: setup.py:130 +#, fuzzy, python-brace-format +#| msgid "Error uninstalling app: {error}" +msgid "Error running diagnostics: {error}" +msgstr "Помилка видалення застосунку: {error}" + +#: setup.py:143 +msgid "Skipping repair, no failed checks" +msgstr "" + +#: setup.py:157 +msgid "Re-running setup to complete repairs" +msgstr "" + +#: setup.py:165 +#, fuzzy +#| msgid "App updated" +msgid "App repaired." +msgstr "Застосунок оновлено" + +#: setup.py:169 +msgid "App repair completed with errors:\n" +msgstr "" + +#: setup.py:189 msgid "Uninstalling app" msgstr "Видалення застосунку" -#: setup.py:117 +#: setup.py:205 #, python-brace-format msgid "Error uninstalling app: {error}" msgstr "Помилка видалення застосунку: {error}" -#: setup.py:120 +#: setup.py:208 msgid "App uninstalled." msgstr "Застосунок видалено." -#: setup.py:493 +#: setup.py:581 msgid "Updating app packages" msgstr "Оновлення пакунків застосунків" @@ -9007,6 +9044,10 @@ msgstr "Оновити" msgid "Backup" msgstr "Резервна копія" +#: templates/toolbar.html:53 +msgid "Re-run setup" +msgstr "Повторно розпочати налаштування" + #: templates/toolbar.html:59 templates/toolbar.html:60 #: templates/uninstall.html:30 msgid "Uninstall" @@ -9038,6 +9079,29 @@ msgstr "перед видаленням {app_id}" msgid "Gujarati" msgstr "Gujarati" +#~ msgid "Cannot test: No domains are configured." +#~ msgstr "Тестування не можливе: Нема налаштованих доменів." + +#~ msgid "Media streaming server" +#~ msgstr "Сервер потокового медія" + +#~ msgid "" +#~ "Directory that MiniDLNA Server will read for content. All sub-directories " +#~ "of this will be also scanned for media files. If you change the default " +#~ "ensure that the new directory exists and that is readable from the " +#~ "\"minidlna\" user. Any user media directories (\"/home/username/\") will " +#~ "usually work." +#~ msgstr "" +#~ "Каталог, який MiniDLNA Server буде зчитувати на предмет вмісту. Всі " +#~ "підкаталоги цього каталогу також будуть проскановані на наявність " +#~ "мультимедійних файлів. Якщо ви змінюєте типові значення, переконайтеся, " +#~ "що новий каталог існує і що він доступний для читання від користувача " +#~ "\"minidlna\". Будь-які каталоги медіафайлів користувача (\"/home/ім'я " +#~ "користувача/\"), як правило, будуть працювати." + +#~ msgid "Specified directory does not exist." +#~ msgstr "Призначений каталог не існує." + #~ msgid "Storage snapshots configuration updated" #~ msgstr "Налаштування зрізів сховища оновлено" diff --git a/plinth/locale/vi/LC_MESSAGES/django.po b/plinth/locale/vi/LC_MESSAGES/django.po index efd3603cc..c2bb7215e 100644 --- a/plinth/locale/vi/LC_MESSAGES/django.po +++ b/plinth/locale/vi/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-04-22 20:02-0400\n" +"POT-Creation-Date: 2024-05-06 20:18-0400\n" "PO-Revision-Date: 2021-07-28 08:34+0000\n" "Last-Translator: bruh \n" "Language-Team: Vietnamese \n" "Language-Team: Chinese (Simplified) \n" "Language-Team: Chinese (Traditional) \n" @@ -17,38 +17,38 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" -"X-Generator: Weblate 5.5-dev\n" +"X-Generator: Weblate 5.5.1-dev\n" #: config.py:103 #, python-brace-format msgid "Static configuration {etc_path} is setup properly" -msgstr "" +msgstr "靜態配置 {etc_path} 已正確設置" #: context_processors.py:23 views.py:116 msgid "FreedomBox" msgstr "自由盒子" -#: daemon.py:122 +#: daemon.py:124 #, python-brace-format msgid "Service {service_name} is running" msgstr "{service_name} 服務執行中" -#: daemon.py:218 +#: daemon.py:222 #, python-brace-format msgid "Listening on {kind} port {listen_address}:{port}" msgstr "正在聆聽 {kind} 埠 {listen_address}:{port}" -#: daemon.py:221 +#: daemon.py:225 #, python-brace-format msgid "Listening on {kind} port {port}" msgstr "正在聆聽 {kind} 埠 {port}" -#: daemon.py:291 +#: daemon.py:296 #, python-brace-format msgid "Connect to {host}:{port}" msgstr "正在連線 {host}:{port}" -#: daemon.py:299 +#: daemon.py:304 #, python-brace-format msgid "Cannot connect to {host}:{port}" msgstr "無法連線到 {host}:{port}" @@ -59,7 +59,7 @@ msgstr "解除安裝前備份應用程式" #: forms.py:37 msgid "Restoring from the backup will restore app data." -msgstr "" +msgstr "從備份中恢復將還原應用程式資料" #: forms.py:39 #, fuzzy @@ -101,7 +101,7 @@ msgstr "使用瀏覽器語言設定" #: menu.py:106 msgid "Visibility" -msgstr "" +msgstr "可見性" #: menu.py:108 msgid "Data" @@ -117,14 +117,12 @@ msgid "Security" msgstr "安全性" #: menu.py:114 -#, fuzzy -#| msgid "Server Administration" msgid "Administration" -msgstr "伺服器管理" +msgstr "管理" #: middleware.py:131 msgid "System is possibly under heavy load. Please retry later." -msgstr "" +msgstr "系統可能負載過重. 請稍後重試." #: modules/apache/__init__.py:32 msgid "Apache HTTP Server" @@ -139,12 +137,12 @@ msgstr "網頁伺服器" msgid "{box_name} Web Interface (Plinth)" msgstr "{box_name} 網頁介面 (Plinth)" -#: modules/apache/components.py:159 +#: modules/apache/components.py:162 #, python-brace-format msgid "Access URL {url} on tcp{kind}" msgstr "透過 TCP {kind} 開啟網址 {url}" -#: modules/apache/components.py:162 +#: modules/apache/components.py:165 #, python-brace-format msgid "Access URL {url}" msgstr "開啟網址 {url}" @@ -491,8 +489,8 @@ msgid "" "To restore a backup on a new %(box_name)s you need the SSH credentials and, " "if chosen, the encryption passphrase." msgstr "" -"此儲存庫的認證存在 %(box_name)s。
如果要備份還原到新的 %(box_name)s 您" -"需要 SSH 認證和(如果您有設定)加密密碼。" +"此儲存庫的認證存在 %(box_name)s。
如果要備份還原到新的 %(box_name)s 您需" +"要 SSH 認證和(如果您有設定)加密密碼。" #: modules/backups/templates/backups_add_remote_repository.html:28 msgid "Create Location" @@ -627,18 +625,13 @@ msgid "How to verify?" msgstr "如何校驗?" #: modules/backups/templates/verify_ssh_hostkey.html:45 -#, fuzzy -#| msgid "" -#| "Run the following command on the SSH host machine. The output should " -#| "match one of the provided options. You can also use dsa, ecdsa, ed25519 " -#| "etc. instead of rsa, by choosing the corresponding file." msgid "" "Run the following command on the SSH host machine. The output should match " "one of the provided options. You can also use DSA, ECDSA, Ed25519 etc. " "instead of RSA, by choosing the corresponding file." msgstr "" -"在 SSH 主機執行下列指令。輸出結果應該符合提供選項之一。您也可以使用 dsa、" -"ecdsa、ed25519 等等。要替換 rsa,可以選擇對應的檔案。" +"在 SSH 主機執行下列指令。輸出結果應該符合提供選項之一。您也可以使用 DSA、" +"ECDSA、Ed25519 等等。要替換 RSA,可以選擇對應的檔案。" #: modules/backups/templates/verify_ssh_hostkey.html:60 msgid "Verify Host" @@ -891,7 +884,7 @@ msgstr "刪除" msgid "Admin" msgstr "管理員" -#: modules/bepasty/views.py:88 modules/diagnostics/views.py:52 +#: modules/bepasty/views.py:88 modules/diagnostics/views.py:55 #: modules/nextcloud/views.py:62 modules/searx/views.py:35 #: modules/searx/views.py:46 modules/security/views.py:56 #: modules/snapshot/views.py:158 modules/tor/views.py:73 @@ -1046,6 +1039,7 @@ msgid "" "Only letters of the English alphabet, numbers and the characters _ . and - " "without spaces or special characters. Example: My_Library_2000" msgstr "" +"只有英文字母、數字和字符 _ . 和 - 不包含空格或特殊字符. 例如:My_Library_2000" #: modules/calibre/forms.py:28 msgid "A library with this name already exists." @@ -1257,25 +1251,25 @@ msgstr "顯示需要更多技術知識的 app 與功能。" #: modules/config/forms.py:104 msgid "System-wide logging" -msgstr "" +msgstr "系統-全域 記錄" #: modules/config/forms.py:105 msgid "Disable logging, for privacy" -msgstr "" +msgstr "為了保護隱私,禁用日誌記錄" #: modules/config/forms.py:107 msgid "Keep some in memory until a restart, for performance" -msgstr "" +msgstr "為了提高性能,保留在記憶體中,直到重新啟動" #: modules/config/forms.py:110 msgid "Write to disk, useful for debugging" -msgstr "" +msgstr "寫入磁碟,用於調試" #: modules/config/forms.py:112 msgid "" "Logs contain information about who accessed the system and debug information " "from various services" -msgstr "" +msgstr "日誌記錄了存取系統的人員以及各種服務的調試資訊" #: modules/config/views.py:49 #, python-brace-format @@ -1530,8 +1524,7 @@ msgstr "" #: modules/diagnostics/templates/diagnostics_app.html:20 #: modules/diagnostics/templates/diagnostics_full.html:48 -#: templates/toolbar.html:53 -msgid "Re-run setup" +msgid "Try to repair" msgstr "" #: modules/diagnostics/templates/diagnostics_app.html:32 @@ -1571,10 +1564,15 @@ msgstr "" msgid "Result" msgstr "" -#: modules/diagnostics/views.py:111 +#: modules/diagnostics/views.py:114 msgid "Diagnostic Test" msgstr "" +#: modules/diagnostics/views.py:144 +#, python-brace-format +msgid "App {app_id} is not installed, cannot repair" +msgstr "" + #: modules/dynamicdns/__init__.py:28 #, python-brace-format msgid "" @@ -1732,7 +1730,7 @@ msgstr "" #: modules/dynamicdns/templates/dynamicdns.html:18 #: modules/email/templates/email.html:35 #: modules/letsencrypt/templates/letsencrypt.html:24 -#: modules/mediawiki/forms.py:64 modules/nextcloud/forms.py:26 +#: modules/mediawiki/forms.py:64 msgid "Domain" msgstr "" @@ -2104,12 +2102,12 @@ msgstr "" msgid "Port {name} ({details}) available for internal networks" msgstr "" -#: modules/firewall/components.py:153 +#: modules/firewall/components.py:154 #, python-brace-format msgid "Port {name} ({details}) available for external networks" msgstr "" -#: modules/firewall/components.py:159 +#: modules/firewall/components.py:160 #, python-brace-format msgid "Port {name} ({details}) unavailable for external networks" msgstr "" @@ -3035,10 +3033,6 @@ msgstr "" msgid "Certificates" msgstr "" -#: modules/letsencrypt/__init__.py:105 -msgid "Cannot test: No domains are configured." -msgstr "" - #: modules/letsencrypt/templates/letsencrypt.html:25 msgid "Certificate Status" msgstr "" @@ -3486,7 +3480,7 @@ msgstr "" msgid "Address" msgstr "" -#: modules/minidlna/__init__.py:22 +#: modules/minidlna/__init__.py:20 msgid "" "MiniDLNA is a simple media server software, with the aim of being fully " "compliant with DLNA/UPnP-AV clients. The MiniDLNA daemon serves media files " @@ -3497,28 +3491,22 @@ msgid "" "Kodi." msgstr "" -#: modules/minidlna/__init__.py:44 -msgid "Media streaming server" -msgstr "" - -#: modules/minidlna/__init__.py:47 +#: modules/minidlna/__init__.py:45 msgid "MiniDLNA" msgstr "" -#: modules/minidlna/__init__.py:48 +#: modules/minidlna/__init__.py:46 msgid "Simple Media Server" msgstr "" -#: modules/minidlna/forms.py:13 +#: modules/minidlna/forms.py:20 msgid "Media Files Directory" msgstr "" -#: modules/minidlna/forms.py:14 +#: modules/minidlna/forms.py:21 msgid "" "Directory that MiniDLNA Server will read for content. All sub-directories of " -"this will be also scanned for media files. If you change the default ensure " -"that the new directory exists and that is readable from the \"minidlna\" " -"user. Any user media directories (\"/home/username/\") will usually work." +"this will be also scanned for media files." msgstr "" #: modules/minidlna/manifest.py:10 @@ -3537,11 +3525,7 @@ msgstr "" msgid "totem" msgstr "" -#: modules/minidlna/views.py:35 -msgid "Specified directory does not exist." -msgstr "" - -#: modules/minidlna/views.py:38 +#: modules/minidlna/views.py:33 msgid "Updated media directory" msgstr "" @@ -4667,7 +4651,7 @@ msgstr "" msgid "Failed to delete connection: Connection not found." msgstr "" -#: modules/nextcloud/__init__.py:20 +#: modules/nextcloud/__init__.py:25 msgid "" "Nextcloud is a self-hosted productivity platform which provides private and " "secure functions for file sharing, collaborative work, and more. Nextcloud " @@ -4676,29 +4660,29 @@ msgid "" "interface." msgstr "" -#: modules/nextcloud/__init__.py:25 +#: modules/nextcloud/__init__.py:30 msgid "All users of FreedomBox can use Nextcloud. To perform administrative " msgstr "" -#: modules/nextcloud/__init__.py:29 +#: modules/nextcloud/__init__.py:34 #, python-brace-format msgid "" "Please note that Nextcloud is installed and run inside a container provided " -"by the Nextcloud project. Security, quality, privacy and legal reviews are " +"by the Nextcloud community. Security, quality, privacy and legal reviews are " "done by the upstream project and not by Debian/{box_name}. Updates are " "performed following an independent cycle." msgstr "" -#: modules/nextcloud/__init__.py:35 +#: modules/nextcloud/__init__.py:40 msgid "This app is experimental." msgstr "" -#: modules/nextcloud/__init__.py:53 modules/nextcloud/manifest.py:11 +#: modules/nextcloud/__init__.py:58 modules/nextcloud/manifest.py:11 #: modules/nextcloud/manifest.py:18 msgid "Nextcloud" msgstr "" -#: modules/nextcloud/__init__.py:55 +#: modules/nextcloud/__init__.py:60 msgid "File Storage & Collaboration" msgstr "" @@ -4708,17 +4692,26 @@ msgstr "" msgid "Not set" msgstr "主機名稱設定" +#: modules/nextcloud/forms.py:26 +#, fuzzy +#| msgid "Invalid domain name" +msgid "Override domain" +msgstr "無效的網域名稱" + #: modules/nextcloud/forms.py:27 -msgid "Examples: \"myfreedombox.example.org\" or \"example.onion\"." +msgid "" +"Set to the domain or IP address that Nextcloud should be forced to generate " +"URLs with. Should not be needed if a valid domain is used to access " +"Nextcloud. Examples: \"myfreedombox.example.org\" or \"example.onion\"." msgstr "" -#: modules/nextcloud/forms.py:30 +#: modules/nextcloud/forms.py:33 #, fuzzy #| msgid "Server Administration" msgid "Administrator password" msgstr "伺服器管理" -#: modules/nextcloud/forms.py:31 +#: modules/nextcloud/forms.py:34 msgid "" "Optional. Set a new password for Nextcloud's administrator account " "(nextcloud-admin). The password cannot be a common one and the minimum " @@ -4726,11 +4719,11 @@ msgid "" "keep the current password." msgstr "" -#: modules/nextcloud/forms.py:38 +#: modules/nextcloud/forms.py:41 msgid "Default phone region" msgstr "" -#: modules/nextcloud/forms.py:39 +#: modules/nextcloud/forms.py:42 msgid "" "The default phone region is required to validate phone numbers in the " "profile settings without a country code." @@ -7579,76 +7572,108 @@ msgstr "" msgid "Package {package_name} is the latest version ({latest_version})" msgstr "" -#: package.py:419 +#: package.py:420 msgid "installing" msgstr "" -#: package.py:421 +#: package.py:422 msgid "downloading" msgstr "" -#: package.py:423 +#: package.py:424 msgid "media change" msgstr "" -#: package.py:425 +#: package.py:426 #, python-brace-format msgid "configuration file: {file}" msgstr "" -#: package.py:453 package.py:478 +#: package.py:454 package.py:479 msgid "Timeout waiting for package manager" msgstr "" -#: setup.py:39 +#: setup.py:42 msgid "Installing app" msgstr "" -#: setup.py:41 +#: setup.py:44 msgid "Updating app" msgstr "" -#: setup.py:75 +#: setup.py:78 #, fuzzy, python-brace-format #| msgid "Error installing application: {error}" msgid "Error installing app: {error}" msgstr "安裝應用遇到錯誤:{error}" -#: setup.py:78 +#: setup.py:81 setup.py:151 +#, fuzzy, python-brace-format +#| msgid "Error installing application: {error}" +msgid "Error repairing app: {error}" +msgstr "安裝應用遇到錯誤:{error}" + +#: setup.py:84 #, fuzzy, python-brace-format #| msgid "Error installing application: {error}" msgid "Error updating app: {error}" msgstr "安裝應用遇到錯誤:{error}" -#: setup.py:82 +#: setup.py:88 #, fuzzy #| msgid "Application installed." msgid "App installed." msgstr "應用已完成安裝。" -#: setup.py:84 +#: setup.py:92 msgid "App updated" msgstr "" -#: setup.py:101 +#: setup.py:110 +msgid "Repairing app" +msgstr "" + +#: setup.py:130 +#, fuzzy, python-brace-format +#| msgid "Error installing application: {error}" +msgid "Error running diagnostics: {error}" +msgstr "安裝應用遇到錯誤:{error}" + +#: setup.py:143 +msgid "Skipping repair, no failed checks" +msgstr "" + +#: setup.py:157 +msgid "Re-running setup to complete repairs" +msgstr "" + +#: setup.py:165 +msgid "App repaired." +msgstr "" + +#: setup.py:169 +msgid "App repair completed with errors:\n" +msgstr "" + +#: setup.py:189 #, fuzzy #| msgid "Error installing application: {error}" msgid "Uninstalling app" msgstr "安裝應用遇到錯誤:{error}" -#: setup.py:117 +#: setup.py:205 #, fuzzy, python-brace-format #| msgid "Error installing application: {error}" msgid "Error uninstalling app: {error}" msgstr "安裝應用遇到錯誤:{error}" -#: setup.py:120 +#: setup.py:208 #, fuzzy #| msgid "Application installed." msgid "App uninstalled." msgstr "應用已完成安裝。" -#: setup.py:493 +#: setup.py:581 msgid "Updating app packages" msgstr "" @@ -7985,6 +8010,10 @@ msgstr "" msgid "Backup" msgstr "Backups 模組" +#: templates/toolbar.html:53 +msgid "Re-run setup" +msgstr "" + #: templates/toolbar.html:59 templates/toolbar.html:60 #: templates/uninstall.html:30 #, fuzzy @@ -8068,11 +8097,6 @@ msgstr "" #~ msgid "Enable" #~ msgstr "啟用域名系統安全擴充 DNSSEC" -#, fuzzy -#~| msgid "Invalid domain name" -#~ msgid "Enter a valid domain" -#~ msgstr "無效的網域名稱" - #, fuzzy #~| msgid "Delete files" #~ msgid "Delete selected" diff --git a/plinth/modules/apache/components.py b/plinth/modules/apache/components.py index 2a9ed9e1d..81f770e6b 100644 --- a/plinth/modules/apache/components.py +++ b/plinth/modules/apache/components.py @@ -69,11 +69,13 @@ class Webserver(app.LeaderComponent): for url in self.urls: if '{host}' in url: results.extend( - diagnose_url_on_all( - url, check_certificate=False, - expect_redirects=self.expect_redirects)) + diagnose_url_on_all(url, check_certificate=False, + expect_redirects=self.expect_redirects, + component_id=self.component_id)) else: - results.append(diagnose_url(url, check_certificate=False)) + results.append( + diagnose_url(url, check_certificate=False, + component_id=self.component_id)) return results @@ -141,7 +143,8 @@ def diagnose_url(url: str, kind: str | None = None, check_certificate: bool = True, extra_options: list[str] | None = None, wrapper: str | None = None, - expected_output: str | None = None) -> DiagnosticCheck: + expected_output: str | None = None, + component_id: str | None = None) -> DiagnosticCheck: """Run a diagnostic on whether a URL is accessible. Kind can be '4' for IPv4 or '6' for IPv6. @@ -161,10 +164,12 @@ def diagnose_url(url: str, kind: str | None = None, check_id = f'apache-url-{url}' description = gettext_noop('Access URL {url}') - return DiagnosticCheck(check_id, description, result, parameters) + return DiagnosticCheck(check_id, description, result, parameters, + component_id) def diagnose_url_on_all(url: str, expect_redirects: bool = False, + component_id: str | None = None, **kwargs) -> list[DiagnosticCheck]: """Run a diagnostic on whether a URL is accessible.""" results = [] @@ -174,7 +179,9 @@ def diagnose_url_on_all(url: str, expect_redirects: bool = False, if not expect_redirects: diagnose_kwargs.setdefault('kind', address['kind']) - results.append(diagnose_url(current_url, **diagnose_kwargs)) + results.append( + diagnose_url(current_url, component_id=component_id, + **diagnose_kwargs)) return results diff --git a/plinth/modules/apache/tests/test_components.py b/plinth/modules/apache/tests/test_components.py index 39d6e1229..9a7a2cd83 100644 --- a/plinth/modules/apache/tests/test_components.py +++ b/plinth/modules/apache/tests/test_components.py @@ -72,13 +72,16 @@ def test_webserver_disable(disable): def test_webserver_diagnose(diagnose_url_on_all, diagnose_url): """Test running diagnostics.""" - def on_all_side_effect(url, check_certificate, expect_redirects): + def on_all_side_effect(url, check_certificate, expect_redirects, + component_id): return [ - DiagnosticCheck('test-all-id', 'test-result-' + url, 'success') + DiagnosticCheck('test-all-id', 'test-result-' + url, 'success', {}, + component_id) ] - def side_effect(url, check_certificate): - return DiagnosticCheck('test-id', 'test-result-' + url, 'success') + def side_effect(url, check_certificate, component_id): + return DiagnosticCheck('test-id', 'test-result-' + url, 'success', {}, + component_id) diagnose_url_on_all.side_effect = on_all_side_effect diagnose_url.side_effect = side_effect @@ -86,19 +89,26 @@ def test_webserver_diagnose(diagnose_url_on_all, diagnose_url): urls=['{host}url1', 'url2'], expect_redirects=True) results = webserver1.diagnose() assert results == [ - DiagnosticCheck('test-all-id', 'test-result-{host}url1', 'success'), - DiagnosticCheck('test-id', 'test-result-url2', 'success') + DiagnosticCheck('test-all-id', 'test-result-{host}url1', 'success', {}, + 'test-webserver'), + DiagnosticCheck('test-id', 'test-result-url2', 'success', {}, + 'test-webserver') ] - diagnose_url_on_all.assert_has_calls( - [call('{host}url1', check_certificate=False, expect_redirects=True)]) - diagnose_url.assert_has_calls([call('url2', check_certificate=False)]) + diagnose_url_on_all.assert_has_calls([ + call('{host}url1', check_certificate=False, expect_redirects=True, + component_id='test-webserver') + ]) + diagnose_url.assert_has_calls( + [call('url2', check_certificate=False, component_id='test-webserver')]) diagnose_url_on_all.reset_mock() webserver2 = Webserver('test-webserver', 'test-config', urls=['{host}url1', 'url2'], expect_redirects=False) results = webserver2.diagnose() - diagnose_url_on_all.assert_has_calls( - [call('{host}url1', check_certificate=False, expect_redirects=False)]) + diagnose_url_on_all.assert_has_calls([ + call('{host}url1', check_certificate=False, expect_redirects=False, + component_id='test-webserver') + ]) @patch('plinth.privileged.service.restart') @@ -244,20 +254,23 @@ def test_diagnose_url(get_addresses, check): 'test-1': 'value-1' }, 'wrapper': 'test-wrapper', - 'expected_output': 'test-expected' + 'expected_output': 'test-expected', + 'component_id': 'test-component', } parameters = {key: args[key] for key in ['url', 'kind']} check.return_value = True result = diagnose_url(**args) assert result == DiagnosticCheck( 'apache-url-kind-https://localhost/test-4', - 'Access URL {url} on tcp{kind}', Result.PASSED, parameters) + 'Access URL {url} on tcp{kind}', Result.PASSED, parameters, + 'test-component') check.return_value = False result = diagnose_url(**args) assert result == DiagnosticCheck( 'apache-url-kind-https://localhost/test-4', - 'Access URL {url} on tcp{kind}', Result.FAILED, parameters) + 'Access URL {url} on tcp{kind}', Result.FAILED, parameters, + 'test-component') del args['kind'] args['url'] = 'https://{host}/test' @@ -287,10 +300,10 @@ def test_diagnose_url(get_addresses, check): assert results == [ DiagnosticCheck('apache-url-kind-https://test-host-1/test-4', 'Access URL {url} on tcp{kind}', Result.PASSED, - parameters[0]), + parameters[0], 'test-component'), DiagnosticCheck('apache-url-kind-https://test-host-2/test-6', 'Access URL {url} on tcp{kind}', Result.PASSED, - parameters[1]), + parameters[1], 'test-component'), ] diff --git a/plinth/modules/diagnostics/__init__.py b/plinth/modules/diagnostics/__init__.py index 13fed1628..f763b239c 100644 --- a/plinth/modules/diagnostics/__init__.py +++ b/plinth/modules/diagnostics/__init__.py @@ -128,7 +128,7 @@ def _run_on_all_enabled_modules(): app_results = { 'diagnosis': [], 'exception': None, - 'show_rerun_setup': False, + 'show_repair': False, } try: @@ -140,7 +140,7 @@ def _run_on_all_enabled_modules(): for check in app_results['diagnosis']: if check.result in [Result.FAILED, Result.WARNING]: - app_results['show_rerun_setup'] = True + app_results['show_repair'] = True break with results_lock: diff --git a/plinth/modules/diagnostics/templates/diagnostics_app.html b/plinth/modules/diagnostics/templates/diagnostics_app.html index ba2eb77e7..70d06526c 100644 --- a/plinth/modules/diagnostics/templates/diagnostics_app.html +++ b/plinth/modules/diagnostics/templates/diagnostics_app.html @@ -12,12 +12,12 @@

{% blocktrans %}App: {{ app_name }}{% endblocktrans %}

- {% if show_rerun_setup %} -
+ {% if show_repair %} + {% csrf_token %} + name="repair" value="{% trans "Try to repair" %}"/>
{% endif %}
diff --git a/plinth/modules/diagnostics/templates/diagnostics_full.html b/plinth/modules/diagnostics/templates/diagnostics_full.html index 836abd505..a71a6ca57 100644 --- a/plinth/modules/diagnostics/templates/diagnostics_full.html +++ b/plinth/modules/diagnostics/templates/diagnostics_full.html @@ -40,12 +40,12 @@ {% endblocktrans %} - {% if app_data.show_rerun_setup %} -
+ {% if app_data.show_repair %} + {% csrf_token %} + name="repair" value="{% trans "Try to repair" %}"/>
{% endif %} diff --git a/plinth/modules/diagnostics/urls.py b/plinth/modules/diagnostics/urls.py index 215e5d070..b89e831a0 100644 --- a/plinth/modules/diagnostics/urls.py +++ b/plinth/modules/diagnostics/urls.py @@ -14,4 +14,6 @@ urlpatterns = [ name='full'), re_path(r'^sys/diagnostics/(?P[1-9a-z\-_]+)/$', views.diagnose_app, name='app'), + re_path(r'^sys/diagnostics/repair/(?P[1-9a-z\-_]+)/$', + views.repair, name='repair'), ] diff --git a/plinth/modules/diagnostics/views.py b/plinth/modules/diagnostics/views.py index 6102db56e..8c8648ac2 100644 --- a/plinth/modules/diagnostics/views.py +++ b/plinth/modules/diagnostics/views.py @@ -7,7 +7,9 @@ import logging from django.contrib import messages from django.http import Http404 +from django.shortcuts import redirect from django.template.response import TemplateResponse +from django.urls import NoReverseMatch, reverse from django.utils.translation import gettext_lazy as _ from django.views.decorators.http import require_POST from django.views.generic import TemplateView @@ -16,6 +18,7 @@ from plinth import operation from plinth.app import App from plinth.diagnostic_check import Result from plinth.modules import diagnostics +from plinth.setup import run_repair_on_app from plinth.views import AppView from .forms import ConfigureForm @@ -100,10 +103,10 @@ def diagnose_app(request, app_id): exception) diagnosis_exception = str(exception) - show_rerun_setup = False + show_repair = False for check in diagnosis: if check.result in [Result.FAILED, Result.WARNING]: - show_rerun_setup = True + show_repair = True break return TemplateResponse( @@ -113,5 +116,34 @@ def diagnose_app(request, app_id): 'app_name': app_name, 'results': diagnosis, 'exception': diagnosis_exception, - 'show_rerun_setup': show_rerun_setup, + 'show_repair': show_repair, }) + + +@require_POST +def repair(request, app_id): + """Try to repair failed diagnostics on an app. + + Allows apps and components to customize the repair method. Re-run setup is + the default if not specified. + """ + try: + app = App.get(app_id) + except KeyError: + raise Http404('App does not exist') + + try: + finish_url = reverse(f'{app_id}:index') + except NoReverseMatch: + # for apps like apache that don't have an index route + finish_url = reverse('diagnostics:index') + + current_version = app.get_setup_version() + if not current_version: + logger.warning('App %s is not installed, cannot repair', app_id) + message = _('App {app_id} is not installed, cannot repair') + messages.error(request, str(message).format(app_id=app_id)) + return redirect(finish_url) + + run_repair_on_app(app_id) + return redirect(finish_url) diff --git a/plinth/modules/firewall/components.py b/plinth/modules/firewall/components.py index e3c381b26..b0f7239b7 100644 --- a/plinth/modules/firewall/components.py +++ b/plinth/modules/firewall/components.py @@ -142,7 +142,8 @@ class Firewall(app.FollowerComponent): 'details': details } results.append( - DiagnosticCheck(check_id, description, result, parameters)) + DiagnosticCheck(check_id, description, result, parameters, + self.component_id)) # External zone if self.is_external: @@ -161,7 +162,8 @@ class Firewall(app.FollowerComponent): parameters = {'name': port, 'details': details} results.append( - DiagnosticCheck(check_id, description, result, parameters)) + DiagnosticCheck(check_id, description, result, parameters, + self.component_id)) return results diff --git a/plinth/modules/firewall/tests/test_components.py b/plinth/modules/firewall/tests/test_components.py index a6d943db9..4d0668f21 100644 --- a/plinth/modules/firewall/tests/test_components.py +++ b/plinth/modules/firewall/tests/test_components.py @@ -161,28 +161,28 @@ def test_diagnose(get_enabled_services, get_port_details): 'networks', Result.PASSED, { 'name': 'test-port1', 'details': '1234/tcp, 1234/udp' - }), + }, 'test-firewall-1'), DiagnosticCheck( 'firewall-port-external-unavailable-test-port1', 'Port {name} ({details}) unavailable for external ' 'networks', Result.PASSED, { 'name': 'test-port1', 'details': '1234/tcp, 1234/udp' - }), + }, 'test-firewall-1'), DiagnosticCheck( 'firewall-port-internal-test-port2', 'Port {name} ({details}) available for internal networks', Result.FAILED, { 'name': 'test-port2', 'details': '2345/udp' - }), + }, 'test-firewall-1'), DiagnosticCheck( 'firewall-port-external-unavailable-test-port2', 'Port {name} ({details}) unavailable for external networks', Result.FAILED, { 'name': 'test-port2', 'details': '2345/udp' - }), + }, 'test-firewall-1'), ] firewall = Firewall('test-firewall-1', ports=['test-port3', 'test-port4'], @@ -195,28 +195,28 @@ def test_diagnose(get_enabled_services, get_port_details): Result.PASSED, { 'name': 'test-port3', 'details': '3456/tcp' - }), + }, 'test-firewall-1'), DiagnosticCheck( 'firewall-port-external-available-test-port3', 'Port {name} ({details}) available for external networks', Result.PASSED, { 'name': 'test-port3', 'details': '3456/tcp' - }), + }, 'test-firewall-1'), DiagnosticCheck( 'firewall-port-internal-test-port4', 'Port {name} ({details}) available for internal networks', Result.FAILED, { 'name': 'test-port4', 'details': '4567/udp' - }), + }, 'test-firewall-1'), DiagnosticCheck( 'firewall-port-external-available-test-port4', 'Port {name} ({details}) available for external networks', Result.FAILED, { 'name': 'test-port4', 'details': '4567/udp' - }), + }, 'test-firewall-1'), ] diff --git a/plinth/modules/letsencrypt/__init__.py b/plinth/modules/letsencrypt/__init__.py index 5464126f0..39c7014af 100644 --- a/plinth/modules/letsencrypt/__init__.py +++ b/plinth/modules/letsencrypt/__init__.py @@ -6,17 +6,17 @@ import logging import pathlib from django.utils.translation import gettext_lazy as _ -from django.utils.translation import gettext_noop from plinth import app as app_module from plinth import cfg, menu from plinth.config import DropinConfigs -from plinth.diagnostic_check import DiagnosticCheck, Result +from plinth.diagnostic_check import DiagnosticCheck from plinth.modules import names from plinth.modules.apache.components import diagnose_url from plinth.modules.backups.components import BackupRestore from plinth.modules.names.components import DomainType from plinth.package import Packages +from plinth.setup import store_error_message from plinth.signals import domain_added, domain_removed, post_app_loading from plinth.utils import format_lazy @@ -96,17 +96,40 @@ class LetsEncryptApp(app_module.App): for domain in names.components.DomainName.list(): if domain.domain_type.can_have_certificate: - results.append(diagnose_url('https://' + domain.name)) - - if not results: - results.append( - DiagnosticCheck( - 'letsencrypt-cannot-test', - gettext_noop('Cannot test: No domains are configured.'), - Result.WARNING)) + result = diagnose_url('https://' + domain.name) + result.check_id = f'letsencrypt-domain-{domain.name}' + result.parameters['domain'] = domain.name + results.append(result) return results + def repair(self, failed_checks: list) -> bool: + """Try to repair failed diagnostics. + + Returns whether the app setup should be re-run. + """ + status = get_status() + + # Obtain/re-obtain certificates for failing domains + for failed_check in failed_checks: + if not failed_check.check_id.startswith('letsencrypt-domain'): + continue + + domain = failed_check.parameters['domain'] + try: + domain_status = status['domains'][domain] + if domain_status.get('certificate_available', False): + certificate_obtain(domain) + else: + certificate_reobtain(domain) + except Exception as error: + # This happens if a non-functional domain is configured. + logger.error('Could not re-obtain certificate: %s', error) + # Add the error message to thread local storage + store_error_message(str(error)) + + return False + def setup(self, old_version): """Install and configure the app.""" super().setup(old_version) diff --git a/plinth/modules/minidlna/__init__.py b/plinth/modules/minidlna/__init__.py index edf97e245..a301b9764 100644 --- a/plinth/modules/minidlna/__init__.py +++ b/plinth/modules/minidlna/__init__.py @@ -2,17 +2,15 @@ """ FreedomBox app to configure minidlna. """ +from django.urls import reverse_lazy from django.utils.translation import gettext_lazy as _ from plinth import app as app_module from plinth import frontpage, menu -from plinth.config import DropinConfigs from plinth.daemon import Daemon from plinth.modules import firewall -from plinth.modules.apache.components import Webserver from plinth.modules.backups.components import BackupRestore from plinth.modules.firewall.components import Firewall -from plinth.modules.users.components import UsersAndGroups from plinth.package import Packages, install from plinth.utils import Version @@ -29,20 +27,20 @@ _description = [ 'such as PS3 and Xbox 360) or applications such as totem and Kodi.') ] +SYSTEM_USER = 'minidlna' + class MiniDLNAApp(app_module.App): """Freedombox app managing miniDlna.""" app_id = 'minidlna' - _version = 5 + _version = 6 def __init__(self) -> None: """Initialize the app components.""" super().__init__() - groups = {'minidlna': _('Media streaming server')} - info = app_module.Info(app_id=self.app_id, version=self._version, name=_('MiniDLNA'), icon_filename='minidlna', short_description=_('Simple Media Server'), @@ -61,29 +59,20 @@ class MiniDLNAApp(app_module.App): ) self.add(menu_item) - shortcut = frontpage.Shortcut('shortcut-minidlna', info.name, - short_description=info.short_description, - description=info.description, - icon=info.icon_filename, - url='/_minidlna/', login_required=True, - allowed_groups=list(groups)) + shortcut = frontpage.Shortcut( + 'shortcut-minidlna', info.name, + short_description=info.short_description, + description=info.description, icon=info.icon_filename, + configure_url=reverse_lazy('minidlna:index'), login_required=True) self.add(shortcut) packages = Packages('packages-minidlna', ['minidlna']) self.add(packages) - dropin_configs = DropinConfigs( - 'dropin-configs-minidlna', - ['/etc/apache2/conf-available/minidlna-freedombox.conf']) - self.add(dropin_configs) - - firewall = Firewall('firewall-minidlna', info.name, ports=['minidlna'], - is_external=False) - self.add(firewall) - - webserver = Webserver('webserver-minidlna', 'minidlna-freedombox', - urls=['https://{host}/_minidlna/']) - self.add(webserver) + firewall_minidlna = Firewall('firewall-minidlna', info.name, + ports=['minidlna', + 'ssdp'], is_external=False) + self.add(firewall_minidlna) daemon = Daemon('daemon-minidlna', 'minidlna') self.add(daemon) @@ -92,10 +81,6 @@ class MiniDLNAApp(app_module.App): **manifest.backup) self.add(backup_restore) - users_and_groups = UsersAndGroups('users-and-groups-minidlna', - groups=groups) - self.add(users_and_groups) - def setup(self, old_version): """Install and configure the app.""" super().setup(old_version) @@ -108,6 +93,19 @@ class MiniDLNAApp(app_module.App): firewall.remove_passthrough('ipv4', '-A', 'INPUT', '-p', 'tcp', '--dport', '8200', '-j', 'REJECT') + if old_version and old_version <= 5: + # Remove minidlna LDAP group and disable minidlna apache config + from plinth.modules.apache import privileged as apache_privileged + from plinth.modules.users import privileged as users_privileged + + users_privileged.remove_group('minidlna') + apache_privileged.disable('minidlna-freedombox', 'config') + + # Restart app to reload firewall + if self.is_enabled(): + self.disable() + self.enable() + if not old_version: self.enable() diff --git a/plinth/modules/minidlna/data/usr/share/freedombox/etc/apache2/conf-available/minidlna-freedombox.conf b/plinth/modules/minidlna/data/usr/share/freedombox/etc/apache2/conf-available/minidlna-freedombox.conf deleted file mode 100644 index aaaf5f876..000000000 --- a/plinth/modules/minidlna/data/usr/share/freedombox/etc/apache2/conf-available/minidlna-freedombox.conf +++ /dev/null @@ -1,9 +0,0 @@ - - Include includes/freedombox-single-sign-on.conf - - - TKTAuthToken "admin" - - - ProxyPass http://localhost:8200/ - diff --git a/plinth/modules/minidlna/forms.py b/plinth/modules/minidlna/forms.py index 556c34e13..388cb19be 100644 --- a/plinth/modules/minidlna/forms.py +++ b/plinth/modules/minidlna/forms.py @@ -3,20 +3,21 @@ FreedomBox configuration form for MiniDLNA server. """ -from django import forms from django.utils.translation import gettext_lazy as _ +from plinth.modules.storage.forms import (DirectorySelectForm, + DirectoryValidator) -class MiniDLNAServerForm(forms.Form): +from . import SYSTEM_USER + + +class MiniDLNAServerForm(DirectorySelectForm): """MiniDLNA server configuration form.""" - media_dir = forms.CharField( - label=_('Media Files Directory'), - help_text=_('Directory that MiniDLNA Server will read for content. All' - ' sub-directories of this will be also scanned for media ' - 'files. ' - 'If you change the default ensure that the new directory ' - 'exists and that is readable from the "minidlna" user. ' - 'Any user media directories ("/home/username/") will ' - 'usually work.'), - required=False, - ) + + def __init__(self, *args, **kw): + validator = DirectoryValidator(username=SYSTEM_USER) + super().__init__( + title=_('Media Files Directory'), help_text=_( + 'Directory that MiniDLNA Server will read for content. All ' + 'sub-directories of this will be also scanned for media files.' + ), default='/var/lib/minidlna', validator=validator, *args, **kw) diff --git a/plinth/modules/minidlna/views.py b/plinth/modules/minidlna/views.py index 16f58fdfa..df2529fa9 100644 --- a/plinth/modules/minidlna/views.py +++ b/plinth/modules/minidlna/views.py @@ -1,8 +1,6 @@ # SPDX-License-Identifier: AGPL-3.0-or-later """Views for the minidlna module.""" -import os - from django.contrib import messages from django.utils.translation import gettext_lazy as _ @@ -21,7 +19,8 @@ class MiniDLNAAppView(AppView): def get_initial(self): """Return initial values of the form.""" initial = super().get_initial() - initial.update({'media_dir': privileged.get_media_dir()}) + initial.update({'storage_path': privileged.get_media_dir()}) + return initial def form_valid(self, form): @@ -29,12 +28,8 @@ class MiniDLNAAppView(AppView): old_config = form.initial new_config = form.cleaned_data - if old_config['media_dir'].strip() != new_config['media_dir']: - if os.path.isdir(new_config['media_dir']) is False: - messages.error(self.request, - _('Specified directory does not exist.')) - else: - privileged.set_media_dir(new_config['media_dir']) - messages.success(self.request, _('Updated media directory')) + if old_config['storage_path'] != new_config['storage_path']: + privileged.set_media_dir(new_config['storage_path']) + messages.success(self.request, _('Updated media directory')) return super().form_valid(form) diff --git a/plinth/modules/nextcloud/__init__.py b/plinth/modules/nextcloud/__init__.py index f21e3f1aa..67f501770 100644 --- a/plinth/modules/nextcloud/__init__.py +++ b/plinth/modules/nextcloud/__init__.py @@ -1,17 +1,22 @@ # SPDX-License-Identifier: AGPL-3.0-or-later """FreedomBox app to configure Nextcloud.""" +import contextlib + from django.utils.translation import gettext_lazy as _ from plinth import app as app_module from plinth import cfg, frontpage, menu from plinth.config import DropinConfigs from plinth.daemon import Daemon, SharedDaemon -from plinth.modules.apache.components import Webserver, diagnose_url +from plinth.modules.apache.components import (Webserver, diagnose_url, + diagnose_url_on_all) from plinth.modules.backups.components import BackupRestore from plinth.modules.firewall.components import (Firewall, FirewallLocalProtection) +from plinth.modules.names.components import DomainName from plinth.package import Packages +from plinth.signals import domain_added, domain_removed from plinth.utils import format_lazy from . import manifest, privileged @@ -27,8 +32,8 @@ _description = [ 'setting a password here.'), format_lazy( _('Please note that Nextcloud is installed and run inside a container ' - 'provided by the Nextcloud project. Security, quality, privacy and ' - 'legal reviews are done by the upstream project and not by ' + 'provided by the Nextcloud community. Security, quality, privacy ' + 'and legal reviews are done by the upstream project and not by ' 'Debian/{box_name}. Updates are performed following an independent ' 'cycle.'), box_name=_(cfg.box_name)), format_lazy('', @@ -87,11 +92,10 @@ class NextcloudApp(app_module.App): self.add(firewall) firewall_local_protection = FirewallLocalProtection( - 'firewall-local-protection-nextcloud', ['8181']) + 'firewall-local-protection-nextcloud', ['9000']) self.add(firewall_local_protection) - webserver = Webserver('webserver-nextcloud', 'nextcloud-freedombox', - urls=['https://{host}/nextcloud/login']) + webserver = Webserver('webserver-nextcloud', 'nextcloud-freedombox') self.add(webserver) daemon = SharedDaemon('shared-daemon-podman-auto-update', @@ -105,7 +109,7 @@ class NextcloudApp(app_module.App): daemon = SharedDaemon('shared-daemon-nextcloud-mysql', 'mysql') self.add(daemon) - daemon = Daemon('daemon-nextcloud', 'nextcloud-freedombox') + daemon = NextcloudDaemon('daemon-nextcloud', 'nextcloud-freedombox') self.add(daemon) daemon = Daemon('daemon-nextcloud-timer', @@ -116,19 +120,33 @@ class NextcloudApp(app_module.App): **manifest.backup) self.add(backup_restore) + @staticmethod + def post_init(): + """Perform post initialization operations.""" + domain_added.connect(_on_domain_added) + domain_removed.connect(_on_domain_removed) + def setup(self, old_version): """Install and configure the app.""" super().setup(old_version) - with self.get_component( - 'shared-daemon-nextcloud-redis').ensure_running(): - with self.get_component( - 'shared-daemon-nextcloud-mysql').ensure_running(): + # Drop-in configs need to be enabled for setup to succeed + self.get_component('dropin-configs-nextcloud').enable() + redis = self.get_component('shared-daemon-nextcloud-redis') + mysql = self.get_component('shared-daemon-nextcloud-mysql') + nextcloud = self.get_component('daemon-nextcloud') + + # Determine whether app should be disabled after setup + should_disable = old_version and not nextcloud.is_enabled() + + with redis.ensure_running(): + with mysql.ensure_running(): # Database needs to be running for successful initialization or # upgrade of Nextcloud database. - - # Drop-in configs need to be enabled for setup to succeed - self.get_component('dropin-configs-nextcloud').enable() privileged.setup() + _set_trusted_domains() + + if should_disable: + self.disable() if not old_version: self.enable() @@ -139,35 +157,105 @@ class NextcloudApp(app_module.App): super().uninstall() def diagnose(self): - """Run diagnostics and return the results.""" + """Run diagnostics and return the results. + + When an override domain is set, that domain and all other addresses are + expected to work. This is because Nextcloud will accept any Host: HTTP + header and then override it with the provided domain name. When + override domain is not set, only the configured trusted domains along + with local IP addresses are allowed. Others are rejected with an error. + """ results = super().diagnose() + + kwargs = {'check_certificate': False} + url = 'https://{domain}/nextcloud/login' + domain = privileged.get_override_domain() + if domain: + results.append(diagnose_url(url.format(domain=domain), **kwargs)) + results += diagnose_url_on_all(url.format(domain='{host}'), + **kwargs) + else: + local_addresses = [('localhost', '4'), ('localhost', '6'), + ('127.0.0.1', '4'), ('[::1]', '6')] + for address, kind in local_addresses: + results.append( + diagnose_url(url.format(domain=address), kind=kind, + **kwargs)) + results.append(diagnose_url('docker.com')) + return results +class NextcloudDaemon(Daemon): + """Component to manage Nextcloud container service.""" + + def is_enabled(self): + """Return if the daemon/unit is enabled.""" + return privileged.is_enabled() + + def enable(self): + """Run operations to enable the daemon/unit.""" + super().enable() + privileged.enable() + + def disable(self): + """Run operations to disable the daemon/unit.""" + super().disable() + privileged.disable() + + class NextcloudBackupRestore(BackupRestore): """Component to backup/restore Nextcloud.""" def backup_pre(self, packet): """Save database contents.""" super().backup_pre(packet) - self.app.get_component('dropin-configs-nextcloud').enable() - mysql = self.app.get_component('shared-daemon-nextcloud-mysql') - redis = self.app.get_component('shared-daemon-nextcloud-redis') - container = self.app.get_component('daemon-nextcloud') - with mysql.ensure_running(): - with redis.ensure_running(): - with container.ensure_running(): - privileged.dump_database() + with _ensure_nextcloud_running(): + privileged.dump_database() def restore_post(self, packet): """Restore database contents.""" super().restore_post(packet) - self.app.get_component('dropin-configs-nextcloud').enable() - mysql = self.app.get_component('shared-daemon-nextcloud-mysql') - redis = self.app.get_component('shared-daemon-nextcloud-redis') - container = self.app.get_component('daemon-nextcloud') - with mysql.ensure_running(): - with redis.ensure_running(): - with container.ensure_running(): - privileged.restore_database() + with _ensure_nextcloud_running(): + privileged.restore_database() + + +def _on_domain_added(sender, domain_type, name='', description='', + services=None, **kwargs): + """Add domain to list of trusted domains.""" + app = app_module.App.get('nextcloud') + if app.needs_setup(): + return + + _set_trusted_domains() + + +def _on_domain_removed(sender, domain_type, name='', **kwargs): + """Update the list of trusted domains.""" + app = app_module.App.get('nextcloud') + if app.needs_setup(): + return + + _set_trusted_domains() + + +def _set_trusted_domains(): + """Set the list of trusted domains.""" + all_domains = DomainName.list_names() + with _ensure_nextcloud_running(): + privileged.set_trusted_domains(list(all_domains)) + + +@contextlib.contextmanager +def _ensure_nextcloud_running(): + """Ensure the nextcloud is running and returns to original state.""" + app = app_module.App.get('nextcloud') + app.get_component('dropin-configs-nextcloud').enable() + mysql = app.get_component('shared-daemon-nextcloud-mysql') + redis = app.get_component('shared-daemon-nextcloud-redis') + container = app.get_component('daemon-nextcloud') + with mysql.ensure_running(): + with redis.ensure_running(): + with container.ensure_running(): + yield diff --git a/plinth/modules/nextcloud/data/usr/lib/systemd/system/nextcloud-cron-freedombox.service b/plinth/modules/nextcloud/data/usr/lib/systemd/system/nextcloud-cron-freedombox.service new file mode 100644 index 000000000..5f24bb040 --- /dev/null +++ b/plinth/modules/nextcloud/data/usr/lib/systemd/system/nextcloud-cron-freedombox.service @@ -0,0 +1,8 @@ +[Unit] +Description=Nextcloud cron.php job +Documentation=https://docs.nextcloud.com/server/stable/admin_manual/configuration_server/background_jobs_configuration.html#systemd + +[Service] +ExecCondition=/usr/bin/podman exec --user www-data nextcloud-freedombox /var/www/html/occ status -e +ExecStart=/usr/bin/podman exec --user www-data nextcloud-freedombox php -f /var/www/html/cron.php +KillMode=process diff --git a/plinth/modules/nextcloud/data/usr/lib/systemd/system/nextcloud-cron-freedombox.timer b/plinth/modules/nextcloud/data/usr/lib/systemd/system/nextcloud-cron-freedombox.timer new file mode 100644 index 000000000..3a5e8bd18 --- /dev/null +++ b/plinth/modules/nextcloud/data/usr/lib/systemd/system/nextcloud-cron-freedombox.timer @@ -0,0 +1,11 @@ +[Unit] +Description=Run Nextcloud cron.php every 5 minutes +Documentation=https://docs.nextcloud.com/server/stable/admin_manual/configuration_server/background_jobs_configuration.html#systemd + +[Timer] +OnBootSec=5min +OnUnitActiveSec=5min +Unit=nextcloud-cron-freedombox.service + +[Install] +WantedBy=timers.target diff --git a/plinth/modules/nextcloud/data/usr/share/freedombox/etc/apache2/conf-available/nextcloud-freedombox.conf b/plinth/modules/nextcloud/data/usr/share/freedombox/etc/apache2/conf-available/nextcloud-freedombox.conf index 8d3758f54..6aa23576c 100644 --- a/plinth/modules/nextcloud/data/usr/share/freedombox/etc/apache2/conf-available/nextcloud-freedombox.conf +++ b/plinth/modules/nextcloud/data/usr/share/freedombox/etc/apache2/conf-available/nextcloud-freedombox.conf @@ -16,10 +16,36 @@ Redirect 301 /.well-known/caldav /nextcloud/remote.php/dav Redirect 301 /.well-known/webfinger /nextcloud/index.php/.well-known/webfinger Redirect 301 /.well-known/nodeinfo /nextcloud/index.php/.well-known/nodeinfo - - ProxyPass http://127.0.0.1:8181 +Alias /nextcloud/ /var/lib/nextcloud/ - ## Send the scheme from user's request to enable Nextcloud to redirect URLs, - ## set cookies, set absolute URLs (if any) properly. - RequestHeader set X-Forwarded-Proto 'https' env=HTTPS - + + ProxyPassMatch "^/nextcloud/(.*\.php(/.*)?)$" "fcgi://localhost:9000/var/www/html/$1" + + + + + + # Enable http authorization headers + + SetEnvIfNoCase ^Authorization$ "(.+)" HTTP_AUTHORIZATION=$1 + + + + # Deny access to raw php sources by default + # To re-enable it's recommended to enable access to the files + # only in specific virtual host or directory + Require all denied + + # Deny access to files without filename (e.g. '.php') + + Require all denied + + + + + Require all granted + + # Allow a limited set of directives in .htaccess files found in /, /config, + # and /data directories of nextcloud. + AllowOverride AuthConfig FileInfo Indexes Limit Options=Indexes,MultiViews + diff --git a/plinth/modules/nextcloud/data/usr/share/freedombox/modules-enabled/nextcloud b/plinth/modules/nextcloud/data/usr/share/freedombox/modules-enabled/nextcloud index 50ee07ded..4a1247e87 100644 --- a/plinth/modules/nextcloud/data/usr/share/freedombox/modules-enabled/nextcloud +++ b/plinth/modules/nextcloud/data/usr/share/freedombox/modules-enabled/nextcloud @@ -1 +1 @@ -#plinth.modules.nextcloud +plinth.modules.nextcloud diff --git a/plinth/modules/nextcloud/forms.py b/plinth/modules/nextcloud/forms.py index 731ac25ec..b5f6d1cb5 100644 --- a/plinth/modules/nextcloud/forms.py +++ b/plinth/modules/nextcloud/forms.py @@ -22,9 +22,12 @@ def _get_phone_regions(): class NextcloudForm(forms.Form): """Nextcloud configuration form.""" - domain = forms.CharField( - label=_('Domain'), required=False, help_text=_( - 'Examples: "myfreedombox.example.org" or "example.onion".')) + override_domain = forms.CharField( + label=_('Override domain'), required=False, help_text=_( + 'Set to the domain or IP address that Nextcloud should be forced ' + 'to generate URLs with. Should not be needed if a valid domain is ' + 'used to access Nextcloud. Examples: "myfreedombox.example.org" ' + 'or "example.onion".')) admin_password = forms.CharField( label=_('Administrator password'), help_text=_( diff --git a/plinth/modules/nextcloud/manifest.py b/plinth/modules/nextcloud/manifest.py index 31a1fce10..fa875a67a 100644 --- a/plinth/modules/nextcloud/manifest.py +++ b/plinth/modules/nextcloud/manifest.py @@ -48,9 +48,7 @@ clients = [{ backup = { 'data': { - 'directories': [ - '/var/lib/containers/storage/volumes/nextcloud-volume-freedombox/' - ], + 'directories': ['/var/lib/nextcloud/'], 'files': ['/var/lib/plinth/backups-data/nextcloud-database.sql'] } } diff --git a/plinth/modules/nextcloud/privileged.py b/plinth/modules/nextcloud/privileged.py index 2021374bc..5770b0c56 100644 --- a/plinth/modules/nextcloud/privileged.py +++ b/plinth/modules/nextcloud/privileged.py @@ -15,13 +15,10 @@ import augeas from plinth import action_utils from plinth.actions import privileged -NETWORK_NAME = 'nextcloud-fbx' -BRIDGE_IP = '172.16.16.1' -CONTAINER_IP = '172.16.16.2' CONTAINER_NAME = 'nextcloud-freedombox' SERVICE_NAME = 'nextcloud-freedombox' -VOLUME_NAME = 'nextcloud-volume-freedombox' -IMAGE_NAME = 'docker.io/library/nextcloud:stable-apache' +VOLUME_NAME = 'nextcloud-freedombox' +IMAGE_NAME = 'docker.io/library/nextcloud:stable-fpm' DB_HOST = 'localhost' DB_NAME = 'nextcloud_fbx' @@ -29,11 +26,7 @@ DB_USER = 'nextcloud_fbx' GUI_ADMIN = 'nextcloud-admin' REDIS_DB = 8 # Don't clash with other redis apps -_volume_path = pathlib.Path( - '/var/lib/containers/storage/volumes/') / VOLUME_NAME -_systemd_location = pathlib.Path('/etc/systemd/system/') -_cron_service_file = _systemd_location / 'nextcloud-cron-freedombox.service' -_cron_timer_file = _systemd_location / 'nextcloud-cron-freedombox.timer' +_data_path = pathlib.Path('/var/lib/nextcloud/') DB_BACKUP_FILE = pathlib.Path( '/var/lib/plinth/backups-data/nextcloud-database.sql') @@ -42,47 +35,40 @@ DB_BACKUP_FILE = pathlib.Path( @privileged def setup(): """Setup Nextcloud configuration.""" - database_password = _generate_secret_key(16) - administrator_password = _generate_secret_key(16) - - # Setup database - _create_database() - _set_database_privileges(database_password) - # Setup redis for caching _redis_listen_socket() - action_utils.podman_run( - network_name=NETWORK_NAME, subnet='172.16.16.0/24', - bridge_ip=BRIDGE_IP, host_port='8181', container_port='80', - container_ip=CONTAINER_IP, container_name=CONTAINER_NAME, - image_name=IMAGE_NAME, extra_run_options=[ - '--volume=/run/mysqld/mysqld.sock:/run/mysqld/mysqld.sock', - '--volume=/run/redis/redis-server.sock:' - '/run/redis/redis-server.sock', - '--volume=/run/slapd/ldapi:/run/slapd/ldapi', - f'--volume={VOLUME_NAME}:/var/www/html', - f'--env=TRUSTED_PROXIES={BRIDGE_IP}', - '--env=OVERWRITEWEBROOT=/nextcloud' - ]) - _configure_firewall(action='add', interface_name=NETWORK_NAME) + volumes = { + '/run/mysqld/mysqld.sock': '/run/mysqld/mysqld.sock', + '/run/redis/redis-server.sock': '/run/redis/redis-server.sock', + '/run/slapd/ldapi': '/run/slapd/ldapi', + VOLUME_NAME: '/var/www/html' + } + env = {'OVERWRITEWEBROOT': '/nextcloud'} + binds_to = ['mariadb.service', 'redis-server.service', 'slapd.service'] + action_utils.podman_create(container_name=CONTAINER_NAME, + image_name=IMAGE_NAME, volume_name=VOLUME_NAME, + volume_path=str(_data_path), volumes=volumes, + env=env, binds_to=binds_to) + action_utils.service_start(CONTAINER_NAME) - # OCC isn't immediately available after the container is spun up. - # Wait until CAN_INSTALL file is available. - timeout = 300 - while timeout > 0: - if (_volume_path / '_data/config/CAN_INSTALL').exists(): - break + _nextcloud_wait_until_ready() - timeout = timeout - 1 - time.sleep(1) + # Setup database + _create_database() + database_password = _get_database_password() + if not database_password: + database_password = _generate_secret_key(16) + _set_database_privileges(database_password) - _nextcloud_setup_wizard(database_password, administrator_password) + # Setup redis configuration _create_redis_config() - _configure_ldap() + # Run setup wizard + _nextcloud_setup_wizard(database_password) - _configure_systemd() + # Setup LDAP configuraiton + _configure_ldap() def _run_in_container( @@ -101,8 +87,26 @@ def _run_occ(*args, **kwargs) -> subprocess.CompletedProcess: @privileged -def get_domain(): - """Return domain name set in Nextcloud.""" +def is_enabled() -> bool: + """Return if the systemd container service is enabled.""" + return action_utils.podman_is_enabled(CONTAINER_NAME) + + +@privileged +def enable(): + """Enable the systemd container service.""" + action_utils.podman_enable(CONTAINER_NAME) + + +@privileged +def disable(): + """Disable the systemd container service.""" + action_utils.podman_disable(CONTAINER_NAME) + + +@privileged +def get_override_domain(): + """Return the domain name that Nextcloud is configured to override with.""" try: domain = _run_occ('config:system:get', 'overwritehost', capture_output=True) @@ -112,22 +116,33 @@ def get_domain(): @privileged -def set_domain(domain_name: str): - """Set Nextcloud domain name.""" +def set_override_domain(domain_name: str): + """Set the domain name that Nextcloud will use to override all domains.""" protocol = 'https' if domain_name.endswith('.onion'): protocol = 'http' if domain_name: _run_occ('config:system:set', 'overwritehost', '--value', domain_name) - + _run_occ('config:system:set', 'overwriteprotocol', '--value', protocol) _run_occ('config:system:set', 'overwrite.cli.url', '--value', f'{protocol}://{domain_name}/nextcloud') + else: + _run_occ('config:system:delete', 'overwritehost') + _run_occ('config:system:delete', 'overwriteprotocol') + _run_occ('config:system:delete', 'overwrite.cli.url') - _run_occ('config:system:set', 'overwriteprotocol', '--value', protocol) + # Restart to apply changes immediately + action_utils.service_restart('nextcloud-freedombox') - # Restart to apply changes immediately - action_utils.service_restart('nextcloud-freedombox') + +@privileged +def set_trusted_domains(domains: list[str]): + """Set the list of trusted domains.""" + _run_occ('config:system:delete', 'trusted_domains') + for index, domain in enumerate(domains): + _run_occ('config:system:set', 'trusted_domains', str(index), '--value', + domain) @privileged @@ -155,14 +170,6 @@ def set_default_phone_region(region: str): _run_occ('config:system:set', 'default_phone_region', '--value', region) -def _configure_firewall(action, interface_name): - subprocess.run([ - 'firewall-cmd', '--permanent', '--zone=trusted', - f'--{action}-interface={interface_name}' - ], check=True) - action_utils.service_restart('firewalld') - - def _database_query(query: str): """Run a database query.""" subprocess.run(['mysql'], input=query.encode(), check=True) @@ -175,9 +182,8 @@ def _create_database(): if _db_file_path.exists(): return - query = f'''CREATE DATABASE {DB_NAME} CHARACTER SET utf8mb4 - COLLATE utf8mb4_general_ci; -''' + query = f'CREATE DATABASE IF NOT EXISTS {DB_NAME} ' \ + 'CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;' _database_query(query) @@ -192,15 +198,39 @@ def _set_database_privileges(db_password: str): _database_query(query) +def _nextcloud_wait_until_ready(): + """Wait for Nextcloud container to get ready.""" + # Nextcloud copies sources from /usr/src/nextcloud to /var/www/html inside + # the container. Nextcloud is served from the latter location. This happens + # on first run of the container and when upgrade happen. + start_time = time.time() + while time.time() < start_time + 300: + if (_data_path / 'version.php').exists(): + break + + time.sleep(1) + + # Wait while Nextcloud is syncing files, running install or performing an + # upgrade by trying to obtain an exclusive on its init-sync.lock. Wrap the + # echo command with the lock so that the lock is immediately released after + # obtaining. We are unable to obtain the lock for 5 minutes, fail and stop + # the setup process. + lock_file = _data_path / 'nextcloud-init-sync.lock' + subprocess.run( + ['flock', '--exclusive', '--wait', '300', lock_file, 'echo'], + check=True) + + def _nextcloud_get_status(): """Return Nextcloud status such installed, in maintenance, etc.""" output = _run_occ('status', '--output=json', capture_output=True) return json.loads(output.stdout) -def _nextcloud_setup_wizard(db_password, admin_password): +def _nextcloud_setup_wizard(db_password: str): """Run the Nextcloud installation wizard and enable cron jobs.""" if not _nextcloud_get_status()['installed']: + admin_password = _generate_secret_key(16) _run_occ('maintenance:install', '--database=mysql', '--database-host=localhost:/run/mysqld/mysqld.sock', f'--database-name={DB_NAME}', f'--database-user={DB_USER}', @@ -211,6 +241,16 @@ def _nextcloud_setup_wizard(db_password, admin_password): # jobs correctly. Cron is the recommended setting. _run_occ('background:cron') + # Enable pretty URLs without /index.php in them. + _run_occ('config:system:set', 'htaccess.RewriteBase', '--value', + '/nextcloud') + _run_occ('config:system:set', 'htaccess.IgnoreFrontController', + '--type=boolean', '--value=true') + # Update the .htaccess file to contain mod_rewrite rules needed for pretty + # URLs. This is automatically re-run by scripts when upgrading to next + # version. + _run_occ('maintenance:update:htaccess') + def _configure_ldap(): _run_occ('app:enable', 'user_ldap') @@ -254,50 +294,14 @@ def _configure_ldap(): _run_occ('ldap:set-config', 's01', key, value) -def _configure_systemd(): - """Create systemd units files for container and cron jobs.""" - # Create service and timer for running periodic php jobs. - doc = 'https://docs.nextcloud.com/server/stable/admin_manual/' \ - 'configuration_server/background_jobs_configuration.html#systemd' - nextcloud_cron_service_content = f''' -[Unit] -Description=Nextcloud cron.php job -Documentation={doc} - -[Service] -ExecCondition=/usr/bin/podman exec --user www-data {CONTAINER_NAME} /var/www/html/occ status -e -ExecStart=/usr/bin/podman exec --user www-data {CONTAINER_NAME} php -f /var/www/html/cron.php -KillMode=process -''' # noqa: E501 - nextcloud_cron_timer_content = '''[Unit] -Description=Run Nextcloud cron.php every 5 minutes -Documentation={doc} - -[Timer] -OnBootSec=5min -OnUnitActiveSec=5min -Unit=nextcloud-cron-freedombox.service - -[Install] -WantedBy=timers.target -''' - _cron_service_file.write_text(nextcloud_cron_service_content) - _cron_timer_file.write_text(nextcloud_cron_timer_content) - - action_utils.service_daemon_reload() - - @privileged def uninstall(): """Uninstall Nextcloud""" _drop_database() - _configure_firewall(action='remove', interface_name=NETWORK_NAME) action_utils.podman_uninstall(container_name=CONTAINER_NAME, - network_name=NETWORK_NAME, volume_name=VOLUME_NAME, - image_name=IMAGE_NAME) - for path in [_cron_service_file, _cron_timer_file]: - path.unlink(missing_ok=True) + image_name=IMAGE_NAME, + volume_path=str(_data_path)) def _drop_database(): @@ -353,7 +357,7 @@ def restore_database(): subprocess.run(['redis-cli', '-n', str(REDIS_DB), 'FLUSHDB', 'SYNC'], check=False) - _set_database_privileges(_get_dbpassword()) + _set_database_privileges(_get_database_password()) # After updating the configuration, a restart seems to be required for the # new DB password be used. @@ -370,20 +374,21 @@ def restore_database(): _run_occ('maintenance:data-fingerprint') -def _get_dbpassword(): - """Return the database password from config.php. +def _get_database_password(): + """Return the database password from config.php or '' if not set. OCC cannot run unless Nextcloud can already connect to the database. """ - code = 'include_once("/var/www/html/config/config.php");' \ - 'print($CONFIG["dbpassword"]);' + code = 'if (file_exists("/var/www/html/config/config.php")) {' \ + 'include_once("/var/www/html/config/config.php");' \ + 'print($CONFIG["dbpassword"] ?? ""); }' return _run_in_container('php', '-r', code, capture_output=True).stdout.decode().strip() def _create_redis_config(): """Create a php file for Redis configuration.""" - config_file = _volume_path / '_data/config/freedombox.config.php' + config_file = _data_path / 'config/freedombox.config.php' file_content = fr''' '\OC\Memcache\Redis', diff --git a/plinth/modules/nextcloud/views.py b/plinth/modules/nextcloud/views.py index 92a1e21f6..eac1fc9cb 100644 --- a/plinth/modules/nextcloud/views.py +++ b/plinth/modules/nextcloud/views.py @@ -24,7 +24,7 @@ class NextcloudAppView(AppView): """Return the values to fill in the form.""" initial = super().get_initial() initial.update({ - 'domain': privileged.get_domain(), + 'override_domain': privileged.get_override_domain(), 'default_phone_region': privileged.get_default_phone_region() or '' }) return initial @@ -39,8 +39,8 @@ class NextcloudAppView(AppView): def _value_changed(key): return old_config.get(key) != new_config.get(key) - if _value_changed('domain'): - privileged.set_domain(new_config['domain']) + if _value_changed('override_domain'): + privileged.set_override_domain(new_config['override_domain']) is_changed = True if new_config['admin_password']: diff --git a/plinth/modules/storage/forms.py b/plinth/modules/storage/forms.py index 232cdc9bb..a8f77103b 100644 --- a/plinth/modules/storage/forms.py +++ b/plinth/modules/storage/forms.py @@ -89,11 +89,11 @@ class DirectorySelectForm(forms.Form): storage_subdir = forms.CharField(label=_('Subdirectory (optional)'), required=False) - def __init__(self, title=None, default='/', validator=DirectoryValidator, - *args, **kwargs): + def __init__(self, title=None, help_text='', default='/', + validator=DirectoryValidator, *args, **kwargs): super().__init__(*args, **kwargs) - if title: - self.fields['storage_dir'].label = title + self.fields['storage_dir'].label = title + self.fields['storage_dir'].help_text = help_text self.validator = validator self.default = default self.set_form_data() diff --git a/plinth/notification.py b/plinth/notification.py index 07aee714c..c1be960e0 100644 --- a/plinth/notification.py +++ b/plinth/notification.py @@ -292,7 +292,7 @@ class Notification(models.StoredNotification): string_ = str(string_) if data: string_ = SafeFormatter().vformat(string_, [], data) - except KeyError as error: + except (KeyError, AttributeError) as error: logger.warning( 'Notification missing required key during translation: %s', error) diff --git a/plinth/package.py b/plinth/package.py index 6bb0a71ae..22a158f41 100644 --- a/plinth/package.py +++ b/plinth/package.py @@ -210,7 +210,7 @@ class Packages(app_module.FollowerComponent): } results.append( DiagnosticCheck(check_id, description, Result.FAILED, - parameters)) + parameters, self.component_id)) continue result = Result.WARNING @@ -230,7 +230,8 @@ class Packages(app_module.FollowerComponent): 'latest_version': str(latest_version) } results.append( - DiagnosticCheck(check_id, description, result, parameters)) + DiagnosticCheck(check_id, description, result, parameters, + self.component_id)) return results diff --git a/plinth/setup.py b/plinth/setup.py index 12729e519..6c8bbf4c8 100644 --- a/plinth/setup.py +++ b/plinth/setup.py @@ -13,6 +13,7 @@ from django.utils.translation import gettext_noop import plinth from plinth import app as app_module +from plinth.diagnostic_check import Result from plinth.package import Packages from plinth.signals import post_setup @@ -26,6 +27,8 @@ _is_first_setup = False is_first_setup_running = False _is_shutting_down = False +thread_local_storage = threading.local() + def run_setup_on_app(app_id, allow_install=True, rerun=False): """Execute the setup process in a thread.""" @@ -50,10 +53,10 @@ def run_setup_on_app(app_id, allow_install=True, rerun=False): thread_data={'allow_install': allow_install}) -def _run_setup_on_app(app, current_version): +def _run_setup_on_app(app, current_version, repair: bool = False): """Execute the setup process.""" logger.info('Setup run: %s', app.app_id) - exception_to_update = None + exception_to_update: Exception | None = None message = None try: current_version = app.get_setup_version() @@ -74,12 +77,17 @@ def _run_setup_on_app(app, current_version): if not current_version: message = gettext_noop('Error installing app: {error}').format( error=exception) + elif repair: + message = gettext_noop('Error repairing app: {error}').format( + error=exception) else: message = gettext_noop('Error updating app: {error}').format( error=exception) else: if not current_version: message = gettext_noop('App installed.') + elif repair: + return else: message = gettext_noop('App updated') @@ -89,6 +97,86 @@ def _run_setup_on_app(app, current_version): operation.on_update(message, exception_to_update) +def run_repair_on_app(app_id): + """Execute the repair process in a thread.""" + app = app_module.App.get(app_id) + current_version = app.get_setup_version() + if not current_version: + logger.warning('App %s is not installed, cannot repair', app_id) + return + + logger.debug('Creating operation to repair app: %s', app_id) + return operation_module.manager.new(f'{app_id}-repair', app_id, + gettext_noop('Repairing app'), + _run_repair_on_app, [app], + show_message=True, + show_notification=True) + + +def _run_repair_on_app(app: app_module.App): + """Execute the repair process.""" + logger.info('Repair run: %s', app.app_id) + message = None + operation = operation_module.Operation.get_operation() + + # Always re-run diagnostics first for this app, to ensure results are + # current. + checks = [] + try: + checks = app.diagnose() + except Exception as exception: + logger.error('Error running %s diagnostics - %s', app.app_id, + exception) + message = gettext_noop('Error running diagnostics: {error}').format( + error=exception) + operation.on_update(message, exception) + return + + # Filter for checks that have failed. + failed_checks = [] + for check in checks: + if check.result in [Result.FAILED, Result.WARNING]: + failed_checks.append(check) + + if not failed_checks: + logger.warning('Skipping repair for %s: no failed checks', app.app_id) + message = gettext_noop('Skipping repair, no failed checks') + operation.on_update(message, None) + return + + try: + should_rerun_setup = app.repair(failed_checks) + except Exception as exception: + logger.error('Repair error: %s: %s %s', app.app_id, message, exception) + message = gettext_noop('Error repairing app: {error}').format( + error=exception) + operation.on_update(message, exception) + return + + if should_rerun_setup: + message = gettext_noop('Re-running setup to complete repairs') + operation.on_update(message, None) + current_version = app.get_setup_version() + _run_setup_on_app(app, current_version, True) + + logger.info('Repair completed: %s', app.app_id) + + # Check for errors in thread local storage + message = gettext_noop('App repaired.') + errors = retrieve_error_messages() + exceptions = None + if errors: + message = gettext_noop('App repair completed with errors:\n') + error_message = '' + for error in errors: + message += str(error) + '\n' + error_message += str(error) + '\n' + + exceptions = Exception(error_message) + + operation.on_update(message, exceptions) + + def run_uninstall_on_app(app_id): """Execute the uninstall process in a thread.""" # App is already uninstalled @@ -565,3 +653,24 @@ def on_package_cache_updated(): """Called by D-Bus service when apt package cache is updated.""" force_upgrader = ForceUpgrader.get_instance() force_upgrader.on_package_cache_updated() + + +def store_error_message(error_message: str): + """Add an error message to thread local storage.""" + try: + thread_local_storage.errors.append(error_message) + except AttributeError: + thread_local_storage.errors = [error_message] + + +def retrieve_error_messages() -> list[str]: + """Retrieve the error messages from thread local storage. + + Errors are cleared after retrieval.""" + try: + errors = thread_local_storage.errors + thread_local_storage.errors = [] + except AttributeError: + errors = [] + + return errors diff --git a/plinth/tests/test_app.py b/plinth/tests/test_app.py index 1ea6fc523..24c46033b 100644 --- a/plinth/tests/test_app.py +++ b/plinth/tests/test_app.py @@ -279,6 +279,38 @@ def test_app_has_diagnostics(app_with_components): assert app.has_diagnostics() +@patch('plinth.setup.run_setup_on_app') +def test_app_repair(_run_setup_on_app, app_with_components): + """Test running repair on an app.""" + component = app_with_components.get_component('test-follower-1') + component.repair = Mock(return_value=True) + + check1 = DiagnosticCheck('check1', 'check1', Result.FAILED, {}) + check2 = DiagnosticCheck('check2', 'check2', Result.WARNING, {}) + check3 = DiagnosticCheck('check3', 'check3', Result.FAILED, {}, + 'test-follower-1') + should_rerun_setup = app_with_components.repair([]) + assert not should_rerun_setup + + should_rerun_setup = app_with_components.repair([check1]) + assert should_rerun_setup + + should_rerun_setup = app_with_components.repair([check2]) + assert should_rerun_setup + + should_rerun_setup = app_with_components.repair([check1, check2]) + assert should_rerun_setup + component.repair.assert_not_called() + + should_rerun_setup = app_with_components.repair([check3]) + assert should_rerun_setup + assert component.repair.mock_calls == [call([check3])] + + component.repair = Mock(return_value=False) + should_rerun_setup = app_with_components.repair([check3]) + assert not should_rerun_setup + + def test_component_initialization(): """Test that component is initialized properly.""" with pytest.raises(ValueError): @@ -340,6 +372,13 @@ def test_component_has_diagnostics(): assert not component.has_diagnostics() +@patch('plinth.setup.run_setup_on_app') +def test_component_repair(_run_setup_on_app): + """Test running repair on component.""" + component = Component('test-component') + assert component.repair(['test-check']) + + def test_follower_component_initialization(): """Test that follower component is initialized properly.""" component = FollowerComponent('test-follower-1') diff --git a/plinth/tests/test_config.py b/plinth/tests/test_config.py index bbab5a343..f0dc12d82 100644 --- a/plinth/tests/test_config.py +++ b/plinth/tests/test_config.py @@ -165,11 +165,13 @@ def test_dropin_config_diagnose_symlinks(dropin_configs, tmp_path): DiagnosticCheck( f'dropin-config-{tmp_path}/etc/test/path1', 'Static configuration {etc_path} is setup properly', - Result.FAILED, {'etc_path': f'{tmp_path}/etc/test/path1'}), + Result.FAILED, {'etc_path': f'{tmp_path}/etc/test/path1'}, + 'test-component'), DiagnosticCheck( f'dropin-config-{tmp_path}/etc/path2', 'Static configuration {etc_path} is setup properly', - Result.FAILED, {'etc_path': f'{tmp_path}/etc/path2'}), + Result.FAILED, {'etc_path': f'{tmp_path}/etc/path2'}, + 'test-component'), ] # Proper symlinks exist diff --git a/plinth/tests/test_daemon.py b/plinth/tests/test_daemon.py index bdf74d3d6..a7019ad2c 100644 --- a/plinth/tests/test_daemon.py +++ b/plinth/tests/test_daemon.py @@ -181,9 +181,9 @@ def test_ensure_running(subprocess_call, subprocess_run, service_is_running, def test_diagnose(port_listening, service_is_running, daemon): """Test running diagnostics.""" - def side_effect(port, kind): + def side_effect(port, kind, _listen_address, component_id): name = f'test-result-{port}-{kind}' - return DiagnosticCheck(name, name, Result.PASSED) + return DiagnosticCheck(name, name, Result.PASSED, {}, component_id) daemon = Daemon('test-daemon', 'test-unit', listen_ports=[(8273, 'tcp4'), (345, 'udp')]) @@ -193,13 +193,16 @@ def test_diagnose(port_listening, service_is_running, daemon): assert results == [ DiagnosticCheck('daemon-running-test-unit', 'Service {service_name} is running', Result.PASSED, - {'service_name': 'test-unit'}), + {'service_name': 'test-unit'}, 'test-daemon'), DiagnosticCheck('test-result-8273-tcp4', 'test-result-8273-tcp4', - Result.PASSED), + Result.PASSED, {}, 'test-daemon'), DiagnosticCheck('test-result-345-udp', 'test-result-345-udp', - Result.PASSED) + Result.PASSED, {}, 'test-daemon') ] - port_listening.assert_has_calls([call(8273, 'tcp4'), call(345, 'udp')]) + port_listening.assert_has_calls([ + call(8273, 'tcp4', None, 'test-daemon'), + call(345, 'udp', None, 'test-daemon') + ]) service_is_running.assert_has_calls([call('test-unit')]) service_is_running.return_value = False @@ -342,12 +345,13 @@ def test_diagnose_netcat(popen): assert popen.mock_calls[2] == call().communicate(input=b'test-input') result = diagnose_netcat('test-host', 3300, remote_input='test-input', - negate=True) + negate=True, component_id='test-component') parameters2 = parameters.copy() parameters2['negate'] = True assert result == DiagnosticCheck('daemon-netcat-negate-test-host-3300', 'Cannot connect to {host}:{port}', - Result.FAILED, parameters2) + Result.FAILED, parameters2, + 'test-component') popen().returncode = 1 result = diagnose_netcat('test-host', 3300, remote_input='test-input') diff --git a/plinth/tests/test_diagnostic_check.py b/plinth/tests/test_diagnostic_check.py index caf6f1199..66bbf3261 100644 --- a/plinth/tests/test_diagnostic_check.py +++ b/plinth/tests/test_diagnostic_check.py @@ -23,13 +23,14 @@ def test_result(): def test_diagnostic_check(): """Test the diagnostic check data class.""" with pytest.raises(TypeError): - DiagnosticCheck() + DiagnosticCheck() # pylint: disable=E1120 check = DiagnosticCheck('some-check-id', 'sample check') assert check.check_id == 'some-check-id' assert check.description == 'sample check' assert check.translated_description == 'sample check' assert check.result == Result.NOT_DONE + assert check.component_id is None assert not check.parameters check = DiagnosticCheck('some-check-id', 'sample check', Result.PASSED) @@ -40,6 +41,10 @@ def test_diagnostic_check(): {'key': 'value'}) assert check.parameters['key'] == 'value' + check = DiagnosticCheck('some-check-id', 'sample check', Result.FAILED, {}, + 'some-component') + assert check.component_id == 'some-component' + def test_translate(): """Test formatting the translated description.""" @@ -58,7 +63,7 @@ def test_json_encoder_decoder(): check_json = json.dumps(check, cls=CheckJSONEncoder) for string in [ '"check_id": "some-check-id"', '"description": "sample check"', - '"result": "passed"', '"parameters": {}', + '"result": "passed"', '"parameters": {}', '"component_id": null', '"__class__": "DiagnosticCheck"' ]: assert string in check_json diff --git a/plinth/tests/test_package.py b/plinth/tests/test_package.py index d5308d88b..3a811e4e4 100644 --- a/plinth/tests/test_package.py +++ b/plinth/tests/test_package.py @@ -284,32 +284,34 @@ def test_diagnose(cache): DiagnosticCheck( 'package-available-package1', 'Package {package_expression} is not available for install', - Result.FAILED, {'package_expression': 'package1'}), + Result.FAILED, {'package_expression': 'package1'}, + 'test-component'), DiagnosticCheck( 'package-latest-package2', 'Package {package_name} is the latest version ({latest_version})', Result.PASSED, { 'package_name': 'package2', 'latest_version': '2.0' - }), + }, 'test-component'), DiagnosticCheck( 'package-latest-package3', 'Package {package_name} is the latest version ({latest_version})', Result.WARNING, { 'package_name': 'package3', 'latest_version': '3.0' - }), + }, 'test-component'), DiagnosticCheck( 'package-available-package4 | package5', 'Package {package_expression} is not available for install', - Result.FAILED, {'package_expression': 'package4 | package5'}), + Result.FAILED, {'package_expression': 'package4 | package5'}, + 'test-component'), DiagnosticCheck( 'package-latest-package7', 'Package {package_name} is the latest version ({latest_version})', Result.PASSED, { 'package_name': 'package7', 'latest_version': '4.0' - }), + }, 'test-component'), ] diff --git a/plinth/tests/test_setup.py b/plinth/tests/test_setup.py new file mode 100644 index 000000000..c8ca312b1 --- /dev/null +++ b/plinth/tests/test_setup.py @@ -0,0 +1,19 @@ +# SPDX-License-Identifier: AGPL-3.0-or-later +""" +Test module for setup module. +""" + +from plinth.setup import store_error_message, retrieve_error_messages + + +def test_store_retrieve_error_message(): + """Test storing and retrieving error messages.""" + store_error_message('error 1') + assert retrieve_error_messages() == ['error 1'] + + store_error_message('error 1') + store_error_message('error 2') + assert retrieve_error_messages() == ['error 1', 'error 2'] + + # errors are cleared after retrieving + assert retrieve_error_messages() == []