From c62d70f788c04df47756ccff4c588f3e2dd4f005 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Carlos=20Rodr=C3=ADguez=20Texidor?= Date: Tue, 20 May 2025 16:56:48 +0000 Subject: [PATCH 1/7] [fix] Enable internationalization and localization in settings --- tests/openwisp2/settings.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/openwisp2/settings.py b/tests/openwisp2/settings.py index faa4180e0..7a7370f13 100644 --- a/tests/openwisp2/settings.py +++ b/tests/openwisp2/settings.py @@ -1,5 +1,6 @@ import os import sys +from django.utils.translation import gettext_lazy as _ BASE_DIR = os.path.dirname(os.path.abspath(__file__)) DEBUG = True @@ -109,8 +110,9 @@ TIME_ZONE = 'Europe/Rome' LANGUAGE_CODE = 'en-gb' USE_TZ = True -USE_I18N = False -USE_L10N = False +USE_I18N = True +USE_L10N = True +LOCALE_PATHS = (os.path.join(os.path.dirname(os.path.dirname(BASE_DIR)), 'openwisp_controller', 'locale'),) STATIC_URL = '/static/' MEDIA_URL = '/media/' MEDIA_ROOT = f'{os.path.dirname(BASE_DIR)}/media/' From 396ebf1f0376b2a62ef4e243a48ced0a0ac93da3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Carlos=20Rodr=C3=ADguez=20Texidor?= Date: Tue, 20 May 2025 17:21:15 +0000 Subject: [PATCH 2/7] [fix] Add verbose names to model fields for improved clarity --- openwisp_controller/config/base/base.py | 6 +++++- openwisp_controller/config/base/config.py | 14 ++++++++++++-- openwisp_controller/config/base/device.py | 10 ++++++++-- openwisp_controller/config/base/device_group.py | 13 +++++++++++-- openwisp_controller/config/base/tag.py | 1 + openwisp_controller/config/base/vpn.py | 13 +++++++------ openwisp_controller/subnet_division/base/models.py | 2 +- 7 files changed, 45 insertions(+), 14 deletions(-) diff --git a/openwisp_controller/config/base/base.py b/openwisp_controller/config/base/base.py index 292144279..2b8fe2972 100644 --- a/openwisp_controller/config/base/base.py +++ b/openwisp_controller/config/base/base.py @@ -21,7 +21,11 @@ class BaseModel(TimeStampedEditableModel): Shared logic """ - name = models.CharField(max_length=64, db_index=True) + name = models.CharField( + verbose_name=_('name'), + max_length=64, + db_index=True + ) class Meta: abstract = True diff --git a/openwisp_controller/config/base/config.py b/openwisp_controller/config/base/config.py index bf5f2437d..16dcba9f5 100644 --- a/openwisp_controller/config/base/config.py +++ b/openwisp_controller/config/base/config.py @@ -51,7 +51,9 @@ class AbstractConfig(BaseConfig): """ device = models.OneToOneField( - get_model_name('config', 'Device'), on_delete=models.CASCADE + get_model_name('config', 'Device'), + on_delete=models.CASCADE, + verbose_name=_('device'), ) templates = SortedManyToManyField( get_model_name('config', 'Template'), @@ -68,7 +70,14 @@ class AbstractConfig(BaseConfig): blank=True, ) - STATUS = Choices('modified', 'applied', 'error', 'deactivating', 'deactivated') + STATUS = Choices( + ('modified', _('modified')), + ('applied', _('applied')), + ('error', _('error')), + ('deactivating', _('deactivating')), + ('deactivated'), _('deactivated') + ) + status = StatusField( _('configuration status'), help_text=_( @@ -97,6 +106,7 @@ class AbstractConfig(BaseConfig): ), load_kwargs={'object_pairs_hook': collections.OrderedDict}, dump_kwargs={'indent': 4}, + verbose_name=_('context'), ) _CHECKSUM_CACHE_TIMEOUT = 60 * 60 * 24 * 30 # 10 days diff --git a/openwisp_controller/config/base/device.py b/openwisp_controller/config/base/device.py index e36e34d34..cc18bde1d 100644 --- a/openwisp_controller/config/base/device.py +++ b/openwisp_controller/config/base/device.py @@ -31,6 +31,7 @@ class AbstractDevice(OrgMixin, BaseModel): _changed_checked_fields = ['name', 'group_id', 'management_ip', 'organization_id'] name = models.CharField( + verbose_name=_('name'), max_length=64, unique=False, validators=[device_name_validator], @@ -38,6 +39,7 @@ class AbstractDevice(OrgMixin, BaseModel): help_text=_('must be either a valid hostname or mac address'), ) mac_address = models.CharField( + verbose_name=_('mac address'), max_length=17, db_index=True, unique=False, @@ -50,8 +52,10 @@ class AbstractDevice(OrgMixin, BaseModel): default=None, db_index=True, help_text=_('unique device key'), + verbose_name=_('key'), ) model = models.CharField( + verbose_name=_('model'), max_length=64, blank=True, db_index=True, @@ -71,7 +75,7 @@ class AbstractDevice(OrgMixin, BaseModel): max_length=128, help_text=_('system on chip or CPU info'), ) - notes = models.TextField(blank=True, help_text=_('internal notes')) + notes = models.TextField(verbose_name=_('notes'), blank=True, help_text=_('internal notes')) group = models.ForeignKey( get_model_name('config', 'DeviceGroup'), verbose_name=_('group'), @@ -82,6 +86,7 @@ class AbstractDevice(OrgMixin, BaseModel): # these fields are filled automatically # with data received from devices last_ip = models.GenericIPAddressField( + verbose_name=_('last IP address'), blank=True, null=True, db_index=True, @@ -91,6 +96,7 @@ class AbstractDevice(OrgMixin, BaseModel): ), ) management_ip = models.GenericIPAddressField( + verbose_name=_('management IP address'), blank=True, null=True, db_index=True, @@ -105,7 +111,7 @@ class AbstractDevice(OrgMixin, BaseModel): # This is an internal field which is used to track if # the device has been deactivated. This field should not be changed # directly, use the deactivate() method instead. - _is_deactivated = models.BooleanField(default=False) + _is_deactivated = models.BooleanField(verbose_name=_('is deactivated'), default=False) class Meta: unique_together = ( diff --git a/openwisp_controller/config/base/device_group.py b/openwisp_controller/config/base/device_group.py index 7676a8349..01b7b3a1e 100644 --- a/openwisp_controller/config/base/device_group.py +++ b/openwisp_controller/config/base/device_group.py @@ -20,8 +20,17 @@ class AbstractDeviceGroup(OrgMixin, TimeStampedEditableModel): - name = models.CharField(max_length=60, null=False, blank=False) - description = models.TextField(blank=True, help_text=_('internal notes')) + name = models.CharField( + max_length=60, + null=False, + blank=False, + verbose_name=_('Name') + ) + description = models.TextField( + blank=True, + help_text=_('internal notes'), + verbose_name=_('Description') + ) templates = SortedManyToManyField( get_model_name('config', 'Template'), related_name='device_group_relations', diff --git a/openwisp_controller/config/base/tag.py b/openwisp_controller/config/base/tag.py index f574091cd..3022aebc6 100644 --- a/openwisp_controller/config/base/tag.py +++ b/openwisp_controller/config/base/tag.py @@ -18,6 +18,7 @@ class AbstractTaggedTemplate(GenericUUIDTaggedItemBase, TaggedItemBase): get_model_name('config', 'TemplateTag'), related_name='%(app_label)s_%(class)s_items', on_delete=models.CASCADE, + verbose_name=_('Tag'), ) class Meta: diff --git a/openwisp_controller/config/base/vpn.py b/openwisp_controller/config/base/vpn.py index e2c09dcde..f73967f38 100644 --- a/openwisp_controller/config/base/vpn.py +++ b/openwisp_controller/config/base/vpn.py @@ -66,14 +66,14 @@ class AbstractVpn(ShareableOrgMixinUniqueName, BaseConfig): null=True, on_delete=models.CASCADE, ) - key = KeyField(db_index=True) + key = KeyField(db_index=True, verbose_name=_('Private Key')) backend = models.CharField( _('VPN backend'), choices=app_settings.VPN_BACKENDS, max_length=128, help_text=_('Select VPN configuration backend'), ) - notes = models.TextField(blank=True) + notes = models.TextField(verbose_name=_('Notes'), blank=True) # optional, needed for VPNs which do not support automatic IP allocation subnet = models.ForeignKey( get_model_name('openwisp_ipam', 'Subnet'), @@ -128,11 +128,12 @@ class AbstractVpn(ShareableOrgMixinUniqueName, BaseConfig): '-----END DH PARAMETERS-----\n' ) # needed for wireguard - public_key = models.CharField(blank=True, max_length=44) - private_key = models.CharField(blank=True, max_length=44) + public_key = models.CharField(verbose_name=_('Public key'),blank=True, max_length=44) + private_key = models.CharField(verbose_name=_('Private key'), blank=True, max_length=44) # needed for zerotier - node_id = models.CharField(blank=True, max_length=10) - network_id = models.CharField(blank=True, max_length=16) + node_id = models.CharField(verbose_name=_('Node ID'), blank=True, max_length=10) + network_id = models.CharField( verbose_name=_('Network ID'), blank=True, max_length=16) + __vpn__ = True diff --git a/openwisp_controller/subnet_division/base/models.py b/openwisp_controller/subnet_division/base/models.py index 5578220d2..2612805c4 100644 --- a/openwisp_controller/subnet_division/base/models.py +++ b/openwisp_controller/subnet_division/base/models.py @@ -158,7 +158,7 @@ def _validate_ip_address_consistency(self): { 'number_of_ips': _( f'Generated subnets of size /{self.size} cannot accommodate ' - f'{self.number_of_ips} IP Addresses.' + '{self.number_of_ips} IP Addresses.' ) } ) From b2a8128dbffa2b3d393cdc1e0dd3e40c74d0e0a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Carlos=20Rodr=C3=ADguez=20Texidor?= Date: Tue, 20 May 2025 17:57:55 +0000 Subject: [PATCH 3/7] [fix] Update delete confirmation button text for singular context --- .../templates/admin/config/device/delete_confirmation.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openwisp_controller/config/templates/admin/config/device/delete_confirmation.html b/openwisp_controller/config/templates/admin/config/device/delete_confirmation.html index 951a865a0..402109f98 100644 --- a/openwisp_controller/config/templates/admin/config/device/delete_confirmation.html +++ b/openwisp_controller/config/templates/admin/config/device/delete_confirmation.html @@ -47,7 +47,7 @@ {% endblocktranslate %}

- + From 48c37358f9f42016a110649bd89b26d212f27382 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Carlos=20Rodr=C3=ADguez=20Texidor?= Date: Tue, 20 May 2025 17:58:21 +0000 Subject: [PATCH 4/7] [fix] Enable translation for dashboard menu labels in GeoConfig [fix] Enable translation for menu labels in Config, Geo, and PKI apps --- openwisp_controller/config/apps.py | 8 ++++---- openwisp_controller/geo/apps.py | 6 +++--- openwisp_controller/pki/apps.py | 6 +++--- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/openwisp_controller/config/apps.py b/openwisp_controller/config/apps.py index 6c6ecdf67..ab8ff9342 100644 --- a/openwisp_controller/config/apps.py +++ b/openwisp_controller/config/apps.py @@ -170,22 +170,22 @@ def register_menu_groups(self): register_menu_group( position=30, config={ - 'label': 'Configurations', + 'label': _('Configurations'), 'items': { 1: { - 'label': 'Templates', + 'label': _('Templates'), 'model': get_model_name('config', 'Template'), 'name': 'changelist', 'icon': 'ow-template', }, 2: { - 'label': 'VPN Servers', + 'label': _('VPN Servers'), 'model': get_model_name('config', 'Vpn'), 'name': 'changelist', 'icon': 'ow-vpn', }, 4: { - 'label': 'Device Groups', + 'label': _('Device Groups'), 'model': get_model_name('config', 'DeviceGroup'), 'name': 'changelist', 'icon': 'ow-device-group', diff --git a/openwisp_controller/geo/apps.py b/openwisp_controller/geo/apps.py index dbb1fd9b1..72efffd30 100644 --- a/openwisp_controller/geo/apps.py +++ b/openwisp_controller/geo/apps.py @@ -96,16 +96,16 @@ def register_menu_groups(self): register_menu_group( position=50, config={ - 'label': 'Geographic Info', + 'label': _('Geographic Info'), 'items': { 1: { - 'label': 'Locations', + 'label': _('Locations'), 'model': get_model_name('geo', 'Location'), 'name': 'changelist', 'icon': 'ow-location', }, 2: { - 'label': 'Floorplans', + 'label': _('Floorplans'), 'model': get_model_name('geo', 'FloorPlan'), 'name': 'changelist', 'icon': 'ow-floor', diff --git a/openwisp_controller/pki/apps.py b/openwisp_controller/pki/apps.py index c833084e6..f4caef499 100644 --- a/openwisp_controller/pki/apps.py +++ b/openwisp_controller/pki/apps.py @@ -23,16 +23,16 @@ def register_menu_groups(self): register_menu_group( position=60, config={ - 'label': 'Cas & Certificates', + 'label': _('Cas & Certificates'), 'items': { 1: { - 'label': 'Certification Authorities', + 'label': _('Certification Authorities'), 'model': get_model_name('django_x509', 'Ca'), 'name': 'changelist', 'icon': 'ow-ca', }, 2: { - 'label': 'Certificates', + 'label': _('Certificates'), 'model': get_model_name('django_x509', 'Cert'), 'name': 'changelist', 'icon': 'ow-certificate', From ac1b032e4e4670460f39baccc3fd1655d16fbe78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Carlos=20Rodr=C3=ADguez=20Texidor?= Date: Tue, 20 May 2025 19:14:52 +0000 Subject: [PATCH 5/7] [feat] Add spanish translations messages --- .../locale/es/LC_MESSAGES/django.po | 1705 +++++++++++++++++ 1 file changed, 1705 insertions(+) create mode 100644 openwisp_controller/locale/es/LC_MESSAGES/django.po diff --git a/openwisp_controller/locale/es/LC_MESSAGES/django.po b/openwisp_controller/locale/es/LC_MESSAGES/django.po new file mode 100644 index 000000000..a6f1dda5e --- /dev/null +++ b/openwisp_controller/locale/es/LC_MESSAGES/django.po @@ -0,0 +1,1705 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-05-20 18:12+0000\n" +"PO-Revision-Date: 2025-05-20 15:12-0300\n" +"Last-Translator: \n" +"Language-Team: \n" +"Language: es\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: Poedit 3.6\n" + +#: base.py:30 +msgid "" +"Shared objects are visible to all organizations and must have unique names " +"to avoid confusion." +msgstr "" +"Los objetos compartidos son visibles para todas las organizaciones y deben " +"tener nombres únicos para evitar confusiones." + +#: base.py:35 +#, python-brace-format +msgid "There is already another shared {model_name} with this name." +msgstr "Ya existe otro {model_name} compartido con este nombre." + +#: base.py:40 +#, python-brace-format +msgid "There is already a {model_name} of another organization with this name." +msgstr "Ya existe un {model_name} de otra organización con este nombre." + +#: config/admin.py:74 +msgid "System Defined Variables" +msgstr "Variables definidas por el sistema" + +#: config/admin.py:123 +msgid "Preview configuration" +msgstr "Vista previa de la configuración" + +#: config/admin.py:238 +#, python-brace-format +msgid "Preview for {0} with name {1} failed" +msgstr "Vista previa de {0} con nombre {1} fallida" + +#: config/admin.py:242 +#, python-brace-format +msgid "Preview: request method {0} is not allowed" +msgstr "Vista previa: el método de solicitud {0} no está permitido" + +#: config/admin.py:411 config/base/device_group.py:68 +#: config/base/multitenancy.py:42 +msgid "Configuration Variables" +msgstr "Variables de configuración" + +#: config/admin.py:414 +msgid "" +"In this section it's possible to override the default values of variables " +"defined in templates. If you're not using configuration variables you can " +"safely ignore this section." +msgstr "" +"En esta sección es posible sobreescribir los valores por defecto de las " +"variables definidas en las plantillas. Si no utilizas variables de " +"configuración, puedes ignorar esta sección." + +#: config/admin.py:430 +msgid "Device configuration details" +msgstr "Detalles de configuración del dispositivo" + +#: config/admin.py:444 +msgid "Configuration" +msgstr "Configuración" + +#: config/admin.py:473 +msgid "Group" +msgstr "Grupo" + +#: config/admin.py:619 +msgid "Change group of selected Devices" +msgstr "Cambiar el grupo de los dispositivos seleccionados" + +#: config/admin.py:633 +msgid "Select devices from one organization" +msgstr "Seleccionar dispositivos de una organización" + +#: config/admin.py:656 +msgid "Successfully changed group of selected devices." +msgstr "Se cambió exitosamente el grupo de los dispositivos seleccionados." + +#: config/admin.py:663 +#: config/templates/admin/config/change_device_group.html:23 +#: config/templates/admin/config/change_device_group.html:42 +msgid "Change group" +msgstr "Cambiar de grupo" + +#: config/admin.py:711 +#, python-format +msgid "The device %(devices_html)s was activated successfully." +msgid_plural "" +"The following devices were activated successfully: %(devices_html)s." +msgstr[0] "El dispositivo %(devices_html)s se ha activado correctamente." +msgstr[1] "" +"Los siguientes dispositivos se activaron con éxito: %(devices_html)s." + +#: config/admin.py:761 +msgid "Deactivate selected devices" +msgstr "Desactivar los dispositivos seleccionados" + +#: config/admin.py:765 +msgid "Activate selected devices" +msgstr "Activar los dispositivos seleccionados" + +#: config/admin.py:785 +msgid "Are you sure?" +msgstr "¿Estás seguro?" + +#: config/admin.py:821 +msgid "IP address" +msgstr "Dirección IP" + +#: config/admin.py:828 config/apps.py:395 config/base/config.py:78 +msgid "deactivated" +msgstr "desactivado" + +#: config/admin.py:829 +msgid "unknown" +msgstr "desconocido" + +#: config/admin.py:831 +msgid "config status" +msgstr "estado de la configuración" + +#: config/admin.py:985 +msgid "Shared systemwide (no organization)" +msgstr "Compartido en todo el sistema (sin organización)" + +#: config/admin.py:1002 +msgid "Configuration variables" +msgstr "Variables de configuración" + +#: config/admin.py:1005 +msgid "" +"If you want to use configuration variables in this template, define them " +"here along with their default values. The content of each variable can be " +"overridden in each device." +msgstr "" +"Si deseas usar variables de configuración en esta plantilla, defínelas aquí " +"junto con sus valores predeterminados. El contenido de cada variable puede " +"ser sobrescrito en cada dispositivo." + +#: config/admin.py:1119 +#, python-format +msgid "Errors detected while cloning %s" +msgstr "Errores detectados durante la clonación %s" + +#: config/admin.py:1126 +msgid "Successfully cloned selected templates." +msgstr "Se han clonado correctamente las plantillas seleccionadas." + +#: config/admin.py:1144 +#: config/templates/admin/config/clone_template_form.html:23 +msgid "Clone templates" +msgstr "Clonar plantillas" + +#: config/api/filters.py:36 +msgid "Template backend" +msgstr "Plantilla backend" + +#: config/api/filters.py:37 +msgid "Template type" +msgstr "Tipo de plantilla" + +#: config/api/filters.py:56 +msgid "VPN Backend" +msgstr "Backend VPN" + +#: config/api/filters.py:57 +msgid "VPN Subnet" +msgstr "Subred VPN" + +#: config/api/filters.py:80 +msgid "Invalid UUID format" +msgstr "Formato UUID no válido" + +#: config/api/filters.py:105 +msgid "Device group" +msgstr "Grupo de dispositivos" + +#: config/api/filters.py:106 +msgid "Config template" +msgstr "Plantilla de configuración" + +#: config/api/filters.py:107 +msgid "Config status" +msgstr "Estado de la configuración" + +#: config/api/filters.py:108 +msgid "Config backend" +msgstr "Configurar backend" + +#: config/api/filters.py:137 +msgid "Has devices?" +msgstr "¿Tiene dispositivos?" + +#: config/api/serializers.py:56 +msgid "To select a VPN, set the template type to 'VPN-client'" +msgstr "" +"Para seleccionar una VPN, establezca el tipo de plantilla en \"VPN-cliente\"" + +#: config/api/serializers.py:66 config/base/template.py:187 +msgid "The configuration field cannot be empty." +msgstr "El campo de configuración no puede estar vacío." + +#: config/api/serializers.py:213 config/api/serializers.py:269 +msgid "Configuration in NetJSON format" +msgstr "Configuración en formato NetJSON" + +#: config/api/serializers.py:217 config/api/serializers.py:272 +msgid "Configuration variables in JSON format" +msgstr "Variables de configuración en formato JSON" + +#: config/api/zerotier_service.py:80 +#, python-brace-format +msgid "Failed to connect to the ZeroTier controller, Error: {0}" +msgstr "Error al conectar con el controlador ZeroTier, Error: {0}" + +#: config/api/zerotier_service.py:131 +#, python-brace-format +msgid "Failed to create ZeroTier network, Error: {0}" +msgstr "Error al crear la red ZeroTier, Error: {0}" + +#: config/apps.py:43 +msgid "Network Configuration" +msgstr "Configuración de red" + +#: config/apps.py:173 +msgid "Configurations" +msgstr "Configuración" + +#: config/apps.py:176 +msgid "Templates" +msgstr "Plantillas" + +#: config/apps.py:182 +msgid "VPN Servers" +msgstr "Servidores VPN" + +#: config/apps.py:188 config/base/device_group.py:77 +msgid "Device Groups" +msgstr "Grupos de dispositivos" + +#: config/apps.py:202 +msgid "Configuration ERROR" +msgstr "Error de configuración" + +#: config/apps.py:203 +msgid "encountered an error" +msgstr "ha encontrado un error" + +#: config/apps.py:206 +#, python-brace-format +msgid "" +"[{site.name}] ERROR: \"{notification.target}\" configuration {notification." +"verb}" +msgstr "" +"[{site.name}] ERROR: \"{notification.target}\" configuración {notification." +"verb}" + +#: config/apps.py:210 +#, python-brace-format +msgid "" +"The configuration of [{notification.target}]({notification.target_link}) has " +"{notification.verb}. The last working configuration has been restored from a " +"backup present on the filesystem of the device." +msgstr "" +"La configuración de [{notification.target}]({notification.target_link}) " +"{notification.verb}. Se ha restaurado la última configuración funcional " +"desde una copia de seguridad presente en el sistema de archivos del " +"dispositivo." + +#: config/apps.py:226 +msgid "Device Registration" +msgstr "Registro de dispositivo" + +#: config/apps.py:227 +msgid "registered successfully" +msgstr "se ha registrado con éxito" + +#: config/apps.py:230 +#, python-brace-format +msgid "[{site.name}] SUCCESS: \"{notification.target}\" {notification.verb}" +msgstr "[{site.name}] ÉXITO: \"{notification.target}\" {notification.verb}" + +#: config/apps.py:234 +#, python-brace-format +msgid "" +"{condition} device [{notification.target}]({notification.target_link}) has " +"{notification.verb}." +msgstr "" +"{condition} dispositivo [{notification.target}]({notification.target_link}) " +"ha {notification.verb}." + +#: config/apps.py:244 +msgid "Background API Task ERROR" +msgstr "Error en tarea de API en segundo plano" + +#: config/apps.py:245 +msgid "encountered an unrecoverable error" +msgstr "ha encontrado un error irrecuperable" + +#: config/apps.py:248 +#, python-brace-format +msgid "" +"[{site.name}] ERROR: \"{notification.target}\" - VPN Server {action} API " +"Task {notification.verb}" +msgstr "" +"[{site.name}] ERROR: \"{notification.target}\" - Servidor VPN {action} Tarea " +"API {notification.verb}" + +#: config/apps.py:252 +#, python-brace-format +msgid "" +"Unable to perform {action} operation on the [{notification.target}]" +"({notification.target_link}) VPN server due to an unrecoverable error " +"(status code: {status_code})" +msgstr "" +"No se ha podido realizar la operación {action} en el servidor VPN " +"[{notification.target}]({notification.target_link}) debido a un error " +"irrecuperable (código de estado: {status_code})" + +#: config/apps.py:264 +msgid "Background API Task RECOVERY" +msgstr "Tarea API en segundo plano RECUPERACIÓN" + +#: config/apps.py:265 +msgid "has been completed successfully" +msgstr "se ha completado con éxito" + +#: config/apps.py:268 +#, python-brace-format +msgid "" +"[{site.name}] RECOVERY: \"{notification.target}\" - VPN Server {action} API " +"Task {notification.verb}" +msgstr "" +"[{site.name}] RECUPERACIÓN: \"{notification.target}\" - Servidor VPN " +"{action} Tarea API {notification.verb}" + +#: config/apps.py:272 +#, python-brace-format +msgid "" +"The {action} operation on [{notification.target}]({notification." +"target_link}) {notification.verb}" +msgstr "" +"La operación {action} en [{notification.target}]({notification.target_link}) " +"{notification.verb}" + +#: config/apps.py:377 +msgid "Configuration Status" +msgstr "Estado de la configuración" + +#: config/apps.py:391 config/base/config.py:75 +msgid "applied" +msgstr "aplicado" + +#: config/apps.py:392 config/base/config.py:74 +msgid "modified" +msgstr "modificado" + +#: config/apps.py:393 config/base/config.py:76 +msgid "error" +msgstr "error" + +#: config/apps.py:394 config/base/config.py:77 +msgid "deactivating" +msgstr "desactivar" + +#: config/apps.py:402 +msgid "Device Models" +msgstr "Modelos de dispositivos" + +#: config/apps.py:411 config/apps.py:426 config/apps.py:441 +msgid "undefined" +msgstr "indefinido" + +#: config/apps.py:417 +msgid "Firmware version" +msgstr "Versión de firmware" + +#: config/apps.py:432 +msgid "System type" +msgstr "Tipo de sistema" + +#: config/apps.py:448 +msgid "Groups" +msgstr "Grupos" + +#: config/apps.py:480 +msgid "Active groups" +msgstr "Grupos activos" + +#: config/apps.py:481 +msgid "Empty groups" +msgstr "Grupos vacíos" + +#: config/base/base.py:25 config/base/device.py:34 +msgid "name" +msgstr "nombre" + +#: config/base/base.py:43 +msgid "backend" +msgstr "backend" + +#: config/base/base.py:47 +msgid "" +"Select netjsonconfig backend" +msgstr "" +"Seleccione netjsonconfig backend" + +#: config/base/base.py:52 config/base/config.py:118 +msgid "configuration" +msgstr "configuración" + +#: config/base/base.py:54 +msgid "configuration in NetJSON DeviceConfiguration format" +msgstr "configuración en formato NetJSON DeviceConfiguration" + +#: config/base/base.py:73 +msgid "Unexpected configuration format." +msgstr "Formato de configuración inesperado." + +#: config/base/config.py:37 +#, python-brace-format +msgid "Relationship with {0}" +msgstr "Relación con {0}" + +#: config/base/config.py:56 +msgid "device" +msgstr "dispositivo" + +#: config/base/config.py:61 config/base/device_group.py:37 +#: config/base/template.py:106 +msgid "templates" +msgstr "plantillas" + +#: config/base/config.py:64 +msgid "configuration templates, applied from first to last" +msgstr "plantillas de configuración, aplicadas de la primera a la última" + +#: config/base/config.py:82 +msgid "configuration status" +msgstr "estado de configuración" + +#: config/base/config.py:84 +msgid "" +"\"modified\" means the configuration is not applied yet; \n" +"\"applied\" means the configuration is applied successfully; \n" +"\"error\" means the configuration caused issues and it was rolled back; \n" +"\"deactivating\" means the device has been deactivated and the configuration " +"is being removed; \n" +"\"deactivated\" means the configuration has been removed from the device;" +msgstr "" +"\"modificado\" significa que la configuración aún no se ha aplicado;\n" +"\"aplicada\" significa que la configuración se ha aplicado correctamente;\n" +"\"error\" significa que la configuración ha causado problemas y se ha " +"revertido;\n" +"\"desactivando\" significa que se ha desactivado el dispositivo y se está " +"eliminando la configuración;\n" +"\"desactivado\" significa que la configuración se ha eliminado del " +"dispositivo;" + +#: config/base/config.py:93 +msgid "error reason" +msgstr "motivo del error" + +#: config/base/config.py:95 +msgid "Error reason reported by the device" +msgstr "Motivo del error notificado por el dispositivo" + +#: config/base/config.py:102 +msgid "" +"Additional context (configuration variables) in JSON format" +msgstr "" +"Contexto adicional (variables de configuración) en formato JSON" + +#: config/base/config.py:109 +msgid "context" +msgstr "contexto" + +#: config/base/config.py:119 +msgid "configurations" +msgstr "configuraciones" + +#: config/base/config.py:335 +#, python-brace-format +msgid "" +"The following templates are owned by organizations which do not match the " +"organization of this configuration: {0}" +msgstr "" +"Las siguientes plantillas pertenecen a organizaciones que no coinciden con " +"la organización de esta configuración: {0}" + +#: config/base/config.py:370 +msgid "Required templates cannot be removed from the configuration" +msgstr "Las plantillas necesarias no pueden eliminarse de la configuración" + +#: config/base/config.py:486 config/base/template.py:170 +msgid "the supplied value is not a JSON object" +msgstr "el valor suministrado no es un objeto JSON" + +#: config/base/device.py:39 +msgid "must be either a valid hostname or mac address" +msgstr "debe ser un hostname o una dirección mac válidos" + +#: config/base/device.py:42 +msgid "mac address" +msgstr "dirección mac" + +#: config/base/device.py:47 +msgid "primary mac address" +msgstr "dirección mac principal" + +#: config/base/device.py:54 +msgid "unique device key" +msgstr "clave única del dispositivo" + +#: config/base/device.py:55 +msgid "key" +msgstr "clave" + +#: config/base/device.py:58 +msgid "model" +msgstr "modelo" + +#: config/base/device.py:62 +msgid "device model and manufacturer" +msgstr "modelo y fabricante del dispositivo" + +#: config/base/device.py:65 +msgid "operating system" +msgstr "sistema operativo" + +#: config/base/device.py:69 +msgid "operating system identifier" +msgstr "identificador del sistema operativo" + +#: config/base/device.py:72 +msgid "SOC / CPU" +msgstr "SOC / CPU" + +#: config/base/device.py:76 +msgid "system on chip or CPU info" +msgstr "información del sistema en chip (SoC) o de la CPU" + +#: config/base/device.py:78 +msgid "notes" +msgstr "notas" + +#: config/base/device.py:78 config/base/device_group.py:31 +msgid "internal notes" +msgstr "notas internas" + +#: config/base/device.py:81 config/filters.py:18 +msgid "group" +msgstr "grupo" + +#: config/base/device.py:89 +msgid "last IP address" +msgstr "última dirección IP" + +#: config/base/device.py:94 +msgid "" +"indicates the IP address logged from the last request coming from the device" +msgstr "" +"indica la dirección IP registrada desde la última solicitud procedente del " +"dispositivo" + +#: config/base/device.py:99 +msgid "management IP address" +msgstr "dirección IP de gestión" + +#: config/base/device.py:104 +msgid "" +"IP address used by the system to reach the device when performing any type " +"of push operation or active check. The value of this field is generally sent " +"by the device and hence does not need to be changed, but can be changed or " +"cleared manually if needed." +msgstr "" +"Dirección IP utilizada por el sistema para comunicarse con el dispositivo al " +"realizar cualquier tipo de operación push o verificación activa. El valor de " +"este campo generalmente es enviado por el dispositivo, por lo que no suele " +"ser necesario modificarlo, aunque puede editarse o borrarse manualmente si " +"es necesario." + +#: config/base/device.py:114 +msgid "is deactivated" +msgstr "desactivado" + +#: config/base/device.py:266 +msgid "Device with this Name and Organization already exists." +msgstr "Ya existe un dispositivo con este nombre y organización." + +#: config/base/device_group.py:27 +msgid "Name" +msgstr "Nombre" + +#: config/base/device_group.py:32 +msgid "Description" +msgstr "Descripción" + +#: config/base/device_group.py:41 +msgid "" +"These templates are automatically assigned to the devices that are part of " +"the group. Default and required templates are excluded from this list. If " +"the group of the device is changed, these templates will be automatically " +"removed and the templates of the new group will be assigned." +msgstr "" +"Estas plantillas se asignan automáticamente a los dispositivos que forman " +"parte del grupo. Las plantillas predeterminadas y obligatorias están " +"excluidas de esta lista. Si se cambia el grupo del dispositivo, estas " +"plantillas se eliminarán automáticamente y se asignarán las plantillas del " +"nuevo grupo." + +#: config/base/device_group.py:54 +msgid "" +"Group meta data, use this field to store data which is related to this group " +"and can be retrieved via the REST API." +msgstr "" +"Metadatos del grupo, utilice este campo para almacenar los datos " +"relacionados con este grupo y que se pueden recuperar a través de la API " +"REST." + +#: config/base/device_group.py:57 +msgid "Metadata" +msgstr "Metadatos" + +#: config/base/device_group.py:65 +msgid "" +"This field can be used to add meta data for the group or to add " +"\"Configuration Variables\" to the devices." +msgstr "" +"Este campo puede utilizarse para añadir metadatos para el grupo o para " +"añadir \"Variables de configuración\" a los dispositivos." + +#: config/base/device_group.py:76 +msgid "Device Group" +msgstr "Grupo de dispositivos" + +#: config/base/multitenancy.py:18 config/base/multitenancy.py:73 +msgid "organization" +msgstr "organización" + +#: config/base/multitenancy.py:23 +msgid "auto-registration enabled" +msgstr "registro automático activado" + +#: config/base/multitenancy.py:25 +msgid "Whether automatic registration of devices is enabled or not" +msgstr "Si el registro automático de dispositivos está activado o no" + +#: config/base/multitenancy.py:31 +msgid "shared secret" +msgstr "secreto compartido" + +#: config/base/multitenancy.py:32 +msgid "used for automatic registration of devices" +msgstr "utilizado para el registro automático de dispositivos" + +#: config/base/multitenancy.py:40 +msgid "" +"This field can be used to add \"Configuration Variables\" to the devices." +msgstr "" +"Este campo puede utilizarse para añadir \"Variables de configuración\" a los " +"dispositivos." + +#: config/base/multitenancy.py:46 +msgid "Configuration management settings" +msgstr "Ajustes de gestión de la configuración" + +#: config/base/multitenancy.py:79 +msgid "device limit" +msgstr "límite del dispositivo" + +#: config/base/multitenancy.py:84 +msgid "" +"Maximum number of devices allowed for this organization. \"0\" means " +"unlimited." +msgstr "" +"Número máximo de dispositivos permitidos para esta organización. \"0\" " +"significa ilimitado." + +#: config/base/multitenancy.py:90 +msgid "controller limits" +msgstr "límites del controlador" + +#: config/base/tag.py:12 config/base/tag.py:21 +msgid "Tag" +msgstr "Etiqueta" + +#: config/base/tag.py:13 config/base/tag.py:27 +msgid "Tags" +msgstr "Etiquetas" + +#: config/base/tag.py:26 +msgid "Tagged item" +msgstr "Artículo con etiqueta" + +#: config/base/template.py:18 +msgid "Generic" +msgstr "Genérico" + +#: config/base/template.py:18 +msgid "VPN-client" +msgstr "Cliente VPN" + +#: config/base/template.py:39 +msgid "" +"A comma-separated list of template tags, may be used to ease auto " +"configuration with specific settings (eg: 4G, mesh, WDS, VPN, ecc.)" +msgstr "" +"Una lista separada por comas de las etiquetas de plantilla, se puede " +"utilizar para facilitar la configuración automática con ajustes específicos " +"(por ejemplo: 4G, mesh, WDS, VPN, etc.)" + +#: config/base/template.py:46 +msgid "VPN" +msgstr "VPN" + +#: config/base/template.py:52 +msgid "type" +msgstr "tipo" + +#: config/base/template.py:57 +msgid "template type, determines which features are available" +msgstr "tipo de plantilla, determina qué características están disponibles" + +#: config/base/template.py:60 +msgid "enabled by default" +msgstr "activado por defecto" + +#: config/base/template.py:64 +msgid "whether new configurations will have this template enabled by default" +msgstr "" +"si las nuevas configuraciones tendrán esta plantilla activada por defecto" + +#: config/base/template.py:68 +msgid "required" +msgstr "requerido" + +#: config/base/template.py:72 +msgid "" +"if checked, will force the assignment of this template to all the devices of " +"the organization (if no organization is selected, it will be required for " +"every device in the system)" +msgstr "" +"si está marcada, forzará la asignación de esta plantilla a todos los " +"dispositivos de la organización (si no se selecciona ninguna organización, " +"será necesaria para todos los dispositivos del sistema)" + +#: config/base/template.py:79 +msgid "automatic tunnel provisioning" +msgstr "aprovisionamiento automático de túneles" + +#: config/base/template.py:83 +msgid "" +"whether tunnel specific configuration (cryptographic keys, ip addresses, " +"etc) should be automatically generated and managed behind the scenes for " +"each configuration using this template, valid only for the VPN type" +msgstr "" +"si la configuración específica del túnel (claves criptográficas, direcciones " +"IP, etc.) debe generarse automáticamente y gestionarse entre bastidores para " +"cada configuración que utilice esta plantilla, válida sólo para el tipo de " +"VPN" + +#: config/base/template.py:89 +msgid "Default Values" +msgstr "Valores Predeterminados" + +#: config/base/template.py:93 +msgid "" +"A dictionary containing the default values for the variables used by this " +"template; these default variables will be used during schema validation." +msgstr "" +"Un diccionario que contiene los valores por defecto para las variables " +"utilizadas por esta plantilla; estas variables por defecto se utilizarán " +"durante la validación del esquema." + +#: config/base/template.py:105 config/filters.py:11 +msgid "template" +msgstr "plantilla" + +#: config/base/template.py:174 +msgid "A VPN must be selected when template type is \"VPN\"" +msgstr "Debe seleccionarse una VPN cuando el tipo de plantilla es \"VPN\"" + +#: config/base/vpn.py:52 +msgid "VPN server hostname or ip address" +msgstr "Hostname o dirección IP del servidor VPN" + +#: config/base/vpn.py:56 +msgid "Certification Authority" +msgstr "Autoridad de certificación" + +#: config/base/vpn.py:63 +msgid "x509 Certificate" +msgstr "certificado x509" + +#: config/base/vpn.py:64 +msgid "leave blank to create automatically" +msgstr "dejar en blanco para crear automáticamente" + +#: config/base/vpn.py:69 +msgid "Private Key" +msgstr "Clave Privada" + +#: config/base/vpn.py:71 +msgid "VPN backend" +msgstr "Backend VPN" + +#: config/base/vpn.py:74 +msgid "Select VPN configuration backend" +msgstr "Seleccione el backend de configuración VPN" + +#: config/base/vpn.py:76 +msgid "Notes" +msgstr "Notas" + +#: config/base/vpn.py:80 +msgid "Subnet" +msgstr "Subred" + +#: config/base/vpn.py:81 +msgid "Subnet IP addresses used by VPN clients, if applicable" +msgstr "Direcciones IP de subred utilizadas por los clientes VPN, si procede" + +#: config/base/vpn.py:88 +msgid "Internal IP" +msgstr "IP interna" + +#: config/base/vpn.py:89 +msgid "Internal IP address of the VPN server interface, if applicable" +msgstr "Dirección IP interna de la interfaz del servidor VPN, si procede" + +#: config/base/vpn.py:96 +msgid "Webhook Endpoint" +msgstr "Webhook Endpoint" + +#: config/base/vpn.py:98 +msgid "" +"Webhook to trigger for updating server configuration (e.g. https://openwisp2." +"mydomain.com:8081/trigger-update)" +msgstr "" +"Webhook que se activará para actualizar la configuración del servidor (por " +"ejemplo: https://openwisp2.mydomain.com:8081/trigger-update)" + +#: config/base/vpn.py:105 +msgid "Webhook AuthToken" +msgstr "Token de Autenticación Webhook" + +#: config/base/vpn.py:107 +msgid "" +"Authentication token used for triggering \"Webhook Endpoint\" or for calling " +"\"ZerotierService\" API" +msgstr "" +"Token de autenticación utilizado para activar el \"Webhook Endpoint\" o para " +"llamar a la API \"ZerotierService\"" + +#: config/base/vpn.py:131 +msgid "Public key" +msgstr "Clave Pública" + +#: config/base/vpn.py:132 +msgid "Private key" +msgstr "Clave privada" + +#: config/base/vpn.py:134 +msgid "Node ID" +msgstr "ID de nodo" + +#: config/base/vpn.py:135 +msgid "Network ID" +msgstr "ID de red" + +#: config/base/vpn.py:144 +msgid "VPN server" +msgstr "Servidor VPN" + +#: config/base/vpn.py:145 +msgid "VPN servers" +msgstr "Servidores VPN" + +#: config/base/vpn.py:178 +msgid "Backend cannot be changed because the VPN is currently in use." +msgstr "El backend no se puede cambiar porque la VPN está actualmente en uso." + +#: config/base/vpn.py:190 +msgid "CA is required with this VPN backend" +msgstr "La autoridad certificadora (CA) es requerida con este backend VPN" + +#: config/base/vpn.py:193 +msgid "The selected certificate must match the selected CA." +msgstr "" +"El certificado seleccionado debe coincidir con la autoridad certificadora " +"(CA) seleccionada." + +#: config/base/vpn.py:205 +msgid "Subnet is required for this VPN backend." +msgstr "Se requiere una subred para este backend VPN." + +#: config/base/vpn.py:209 +msgid "VPN IP address must be within the VPN subnet" +msgstr "La dirección IP VPN debe estar dentro de la subred VPN" + +#: config/base/vpn.py:215 +msgid "Auth token is required for this VPN backend" +msgstr "El token de autorización es necesario para este backend VPN" + +#: config/base/vpn.py:226 +msgid "" +"Authorization failed for ZeroTier controller, ensure you are using the " +"correct authorization token" +msgstr "" +"Autorización fallida para el controlador ZeroTier, asegúrese de que está " +"utilizando el token de autorización correcto" + +#: config/base/vpn.py:236 +#, python-brace-format +msgid "" +"Failed to connect to the ZeroTier controller, ensure you are using the " +"correct hostname (error: {0}, status code: {1})" +msgstr "" +"Error al conectar con el controlador ZeroTier, asegúrese de que está " +"utilizando el hostname correcto (error: {0}, código de estado: {1})" + +#: config/base/vpn.py:837 +msgid "VPN client" +msgstr "Cliente VPN" + +#: config/base/vpn.py:838 +msgid "VPN clients" +msgstr "Clientes VPN" + +#: config/controller/views.py:269 +msgid "Reason not reported by the device." +msgstr "Motivo no comunicado por el dispositivo." + +#: config/exceptions.py:12 +msgid "" +"The specified limit is lower than the amount of devices currently held by " +"this organization. Please remove some devices or consider increasing the " +"device limit." +msgstr "" +"El límite especificado es inferior a la cantidad de dispositivos que tiene " +"actualmente esta organización. Por favor, elimine algunos dispositivos o " +"considere aumentar el límite de dispositivos." + +#: config/filters.py:24 +msgid "has devices?" +msgstr "tiene dispositivos?" + +#: config/filters.py:29 geo/admin.py:128 +msgid "No" +msgstr "No" + +#: config/filters.py:30 geo/admin.py:127 +msgid "Yes" +msgstr "Sí" + +#: config/handlers.py:40 +msgid "A new" +msgstr "Un nuevo" + +#: config/handlers.py:40 +msgid "The existing" +msgstr "El ya existente" + +#: config/settings.py:50 +msgid "Serial number" +msgstr "Número de serie" + +#: config/settings.py:51 +msgid "Serial number of this device" +msgstr "Número de serie de este dispositivo" + +#: config/settings.py:55 +msgid "Device" +msgstr "Dispositivo" + +#: config/settings.py:55 +msgid "Devices" +msgstr "Dispositivos" + +#: config/templates/admin/config/change_device_group.html:20 +#: config/templates/admin/config/clone_template_form.html:20 +#: config/templates/admin/config/device_recover_form.html:9 +#: config/templates/reversion/config/revision_form.html:12 +msgid "Home" +msgstr "Inicio" + +#: config/templates/admin/config/change_device_group.html:30 +msgid "What group do you want to assign to the selected devices?" +msgstr "¿Qué grupo desea asignar a los dispositivos seleccionados?" + +#: config/templates/admin/config/change_device_group.html:43 +#: config/templates/admin/config/clone_template_form.html:43 +msgid "Cancel" +msgstr "Cancelar" + +#: config/templates/admin/config/change_form.html:29 +msgid "Download configuration" +msgstr "Descargar configuración" + +#: config/templates/admin/config/change_form.html:47 +msgid "Overview" +msgstr "Resumen" + +#: config/templates/admin/config/change_list_device.html:6 +#: config/templates/admin/config/device_recover_form.html:12 +#, python-format +msgid "Recover deleted %(name)s" +msgstr "Recuperar %(name)s eliminado" + +#: config/templates/admin/config/clone_template_form.html:30 +msgid "What organization do you want clone selected templates to?" +msgstr "¿A qué organización desea clonar las plantillas seleccionadas?" + +#: config/templates/admin/config/clone_template_form.html:42 +msgid "Clone" +msgstr "Clonar" + +#: config/templates/admin/config/device/change_form.html:8 +msgid "This device has been deactivated." +msgstr "Este dispositivo ha sido desactivado." + +#: config/templates/admin/config/device/delete_confirmation.html:11 +#, python-format +msgid "" +"Deleting the %(object_name)s '%(escaped_object)s' would result in deleting " +"related objects, but your account doesn't have permission to delete the " +"following types of objects:" +msgstr "" +"Al eliminar el %(object_name)s '%(escaped_object)s' se eliminarían los " +"objetos relacionados, pero tu cuenta no tiene permiso para eliminar los " +"siguientes tipos de objetos:" + +#: config/templates/admin/config/device/delete_confirmation.html:18 +#, python-format +msgid "" +"Deleting the %(object_name)s '%(escaped_object)s' would require deleting the " +"following protected related objects:" +msgstr "" +"Borrar el %(object_name)s '%(escaped_object)s' requeriría borrar los " +"siguientes objetos relacionados protegidos:" + +#: config/templates/admin/config/device/delete_confirmation.html:31 +msgid "Warning: Device is not fully deactivated." +msgstr "Advertencia: El dispositivo no está totalmente desactivado." + +#: config/templates/admin/config/device/delete_confirmation.html:35 +msgid "" +"\n" +" This device is still in the process of being deactivated,\n" +" meaning its configuration is still present on the device.\n" +" " +msgstr "" +"\n" +" Este dispositivo aún está en proceso de desactivación,\n" +" lo que significa que su configuración aún está presente en " +"el dispositivo.\n" +" " + +#: config/templates/admin/config/device/delete_confirmation.html:41 +msgid "" +"\n" +" To ensure its configuration is removed, please\n" +" wait until its status changes to\n" +" \"deactivated\".
\n" +" If you proceed now, the device will be deleted,\n" +" but its configuration will remain active.\n" +" " +msgstr "" +"\n" +" Para asegurarse de que se elimina su configuración, por " +"favor\n" +" espere hasta que su estado cambie a\n" +" \"desactivado\".
\n" +" Si procede ahora, el dispositivo se eliminará,\n" +" pero su configuración permanecerá activa.\n" +" " + +#: config/templates/admin/config/device/delete_confirmation.html:50 +msgctxt "singular" +msgid "I understand the risks, delete the device" +msgstr "Entiendo los riesgos, borrar el dispositivo" + +#: config/templates/admin/config/device/delete_confirmation.html:51 +#: config/templates/admin/config/device/delete_confirmation.html:70 +#: config/templates/admin/config/device/delete_selected_confirmation.html:55 +#: config/templates/admin/config/device/delete_selected_confirmation.html:76 +msgid "No, take me back" +msgstr "No, volver atrás" + +#: config/templates/admin/config/device/delete_confirmation.html:58 +#, python-format +msgid "" +"Are you sure you want to delete the %(object_name)s\n" +" \"%(escaped_object)s\"? All of the following related items will " +"be deleted:" +msgstr "" +"¿Estás seguro de que quieres borrar el %(object_name)s\n" +" \"%(escaped_object)s\"? Se eliminarán todos los siguientes " +"elementos relacionados:" + +#: config/templates/admin/config/device/delete_confirmation.html:61 +#: config/templates/admin/config/device/delete_selected_confirmation.html:64 +msgid "Objects" +msgstr "Objetos" + +#: config/templates/admin/config/device/delete_confirmation.html:68 +#: config/templates/admin/config/device/delete_selected_confirmation.html:75 +msgid "Yes, I’m sure" +msgstr "Sí, estoy de acuerdo" + +#: config/templates/admin/config/device/delete_selected_confirmation.html:11 +#, python-format +msgid "" +"Deleting the selected %(objects_name)s would result in deleting related " +"objects, but your account doesn't have permission to delete the following " +"types of objects:" +msgstr "" +"Al eliminar el %(objects_name)s seleccionado se eliminarían los objetos " +"relacionados, pero su cuenta no tiene permiso para eliminar los siguientes " +"tipos de objetos:" + +#: config/templates/admin/config/device/delete_selected_confirmation.html:14 +#, python-format +msgid "" +"Deleting the selected %(objects_name)s would require deleting the following " +"protected related objects:" +msgstr "" +"Borrar el %(objects_name)s seleccionado requeriría borrar los siguientes " +"objetos relacionados protegidos:" + +#: config/templates/admin/config/device/delete_selected_confirmation.html:23 +msgid "" +"\n" +" Warning: Device is not fully deactivated.\n" +" " +msgid_plural "" +"\n" +" Warning: Some devices are not fully deactivated.\n" +" " +msgstr[0] "" +"\n" +" Advertencia: El dispositivo no está totalmente " +"desactivado.\n" +" " +msgstr[1] "" +"\n" +" Advertencia: Algunos dispositivos no están totalmente " +"desactivados.\n" +" " + +#: config/templates/admin/config/device/delete_selected_confirmation.html:31 +msgid "" +"\n" +" The device below is either still active or\n" +" in the process of being deactivated:\n" +" " +msgid_plural "" +"\n" +" The devices listed below are either still active\n" +" or in the process of being deactivated:\n" +" " +msgstr[0] "" +"\n" +" El dispositivo que aparece a continuación sigue activo " +"o\n" +" en proceso de desactivación:\n" +" " +msgstr[1] "" +"\n" +" Los dispositivos enumerados a continuación siguen " +"activos\n" +" o en proceso de desactivación:\n" +" " + +#: config/templates/admin/config/device/delete_selected_confirmation.html:41 +msgid "" +"\n" +" To ensure its configuration is removed, please\n" +" wait until its status changes to " +"\"deactivated\".
\n" +" If you proceed now, the device will be deleted,\n" +" but its configuration will remain active.\n" +" " +msgid_plural "" +"\n" +" To ensure their configurations are removed, please\n" +" wait until their status changes to " +"\"deactivated\".
\n" +" If you proceed now, the devices will be deleted,\n" +" but their configurations will remain active.\n" +" " +msgstr[0] "" +"\n" +" Para asegurarse de que se elimina su configuración, por " +"favor\n" +" espere hasta que su estado cambie a " +"\"desactivado\".
\n" +" Si procede ahora, el dispositivo se eliminará,\n" +" pero su configuración permanecerá activa.\n" +" " +msgstr[1] "" +"\n" +" Para asegurarse de que se eliminan sus configuraciones, " +"por favor\n" +" espere hasta que su estado cambie a " +"\"desactivado\".
\n" +" Si procede ahora, los dispositivos se eliminarán,\n" +" pero sus configuraciones permanecerán activas.\n" +" " + +#: config/templates/admin/config/device/delete_selected_confirmation.html:54 +msgid "I understand the risks, delete the device" +msgid_plural "I understand the risks, delete the devices" +msgstr[0] "Entiendo los riesgos, borrar el dispositivo" +msgstr[1] "Entiendo los riesgos, borrar los dispositivos" + +#: config/templates/admin/config/device/delete_selected_confirmation.html:62 +#, python-format +msgid "" +"Are you sure you want to delete the selected %(objects_name)s? All of the " +"following objects and their related items will be deleted:" +msgstr "" +"¿Está seguro de que desea borrar el %(objects_name)s seleccionado? Se " +"eliminarán todos los siguientes objetos y sus elementos relacionados:" + +#: config/templates/admin/config/device_recover_form.html:20 +msgid "Press the save button below to recover this version of the object." +msgstr "" +"Presiona el botón de guardar a continuación para recuperar esta versión del " +"objeto." + +#: config/templates/admin/config/jsonschema-widget.html:3 +msgid "Advanced mode (raw JSON)" +msgstr "Modo avanzado (JSON sin procesar)" + +#: config/templates/admin/config/preview.html:7 +msgid "Close" +msgstr "Cerrar" + +#: config/templates/admin/config/system_context.html:13 +msgid "There are no system defined variables available right now." +msgstr "No hay variables definidas por el sistema disponibles en este momento." + +#: config/templates/admin/config/system_context.html:16 +msgid "Show" +msgstr "Mostrar" + +#: config/templates/reversion/config/revision_form.html:16 +msgid "History" +msgstr "Historial" + +#: config/templates/reversion/config/revision_form.html:17 +#, python-format +msgid "Revert %(verbose_name)s" +msgstr "Revertir %(verbose_name)s" + +#: config/templates/reversion/config/revision_form.html:26 +msgid "Press the save button below to revert to this version of the object." +msgstr "Pulsa el botón de guardar para volver a esta versión del objeto." + +#: config/validators.py:6 +msgid "Key must not contain spaces, dots or slashes." +msgstr "La clave no debe contener espacios, puntos ni barras." + +#: config/validators.py:13 +msgid "Must be a valid mac address." +msgstr "Debe ser una dirección mac válida." + +#: config/validators.py:25 +msgid "Must be either a valid hostname or mac address." +msgstr "Debe ser un hostname o una dirección mac válidos." + +#: config/views.py:74 +msgid "login required" +msgstr "inicio de sesión requerido" + +#: connection/admin.py:80 +msgid "Credentials" +msgstr "Credenciales" + +#: connection/admin.py:104 +msgid "Recent Commands" +msgstr "Comandos recientes" + +#: connection/admin.py:136 +msgid "input" +msgstr "entrada" + +#: connection/admin.py:137 +msgid "output" +msgstr "salida" + +#: connection/apps.py:20 +msgid "Network Device Credentials" +msgstr "Credenciales de dispositivos de red" + +#: connection/apps.py:182 +msgid "Access Credentials" +msgstr "Credenciales de acceso" + +#: connection/base/models.py:95 +msgid "connection type" +msgstr "tipo de conexión" + +#: connection/base/models.py:101 connection/base/models.py:241 +msgid "parameters" +msgstr "parámetros" + +#: connection/base/models.py:103 +msgid "global connection parameters" +msgstr "parámetros de conexión globales" + +#: connection/base/models.py:108 +msgid "auto add" +msgstr "auto add" + +#: connection/base/models.py:111 +msgid "" +"automatically add these credentials to the devices of this organization; if " +"no organization is specified will be added to all the new devices" +msgstr "" +"añadir automáticamente estas credenciales a los dispositivos de esta " +"organización; si no se especifica ninguna organización, se añadirán a todos " +"los dispositivos nuevos" + +#: connection/base/models.py:119 +msgid "Access credentials" +msgstr "Credenciales de acceso" + +#: connection/base/models.py:232 +msgid "update strategy" +msgstr "estrategia de actualización" + +#: connection/base/models.py:233 +msgid "leave blank to determine automatically" +msgstr "dejar en blanco para determinar automáticamente" + +#: connection/base/models.py:245 +msgid "" +"local connection parameters (will override the global parameters if " +"specified)" +msgstr "" +"parámetros locales de conexión (anularán los parámetros globales si se " +"especifican)" + +#: connection/base/models.py:253 +msgid "reason of failure" +msgstr "motivo del fracaso" + +#: connection/base/models.py:257 +msgid "Device connection" +msgstr "Conexión del dispositivo" + +#: connection/base/models.py:258 +msgid "Device connections" +msgstr "Conexiones del dispositivo" + +#: connection/base/models.py:292 +msgid "" +"The organization of these credentials doesn't match the organization of the " +"device" +msgstr "" +"La organización de estas credenciales no coincide con la organización del " +"dispositivo" + +#: connection/base/models.py:306 +#, python-brace-format +msgid "could not determine update strategy automatically, exception: {0}" +msgstr "" +"no se ha podido determinar automáticamente la estrategia de actualización, " +"excepción: {0}" + +#: connection/base/models.py:315 +msgid "" +"the update strategy can be determined automatically only if the device has a " +"configuration specified, because it is inferred from the configuration " +"backend. Please select the update strategy manually." +msgstr "" +"la estrategia de actualización sólo puede determinarse automáticamente si el " +"dispositivo tiene una configuración especificada, ya que se infiere del " +"backend de configuración. Seleccione la estrategia de actualización " +"manualmente." + +#: connection/base/models.py:395 +msgid "in progress" +msgstr "en progreso" + +#: connection/base/models.py:396 +msgid "success" +msgstr "éxito" + +#: connection/base/models.py:397 +msgid "failed" +msgstr "fallido" + +#: connection/base/models.py:434 connection/commands.py:22 +msgid "Command" +msgstr "Comando" + +#: connection/base/models.py:435 +msgid "Commands" +msgstr "Comandos" + +#: connection/base/models.py:456 +msgid "sent on" +msgstr "enviado el" + +#: connection/base/models.py:578 +#, python-brace-format +msgid "Command \"{command}\" returned non-zero exit code: {exit_code}" +msgstr "" +"El comando \"{command}\" devuelve un código de salida distinto de cero: " +"{exit_code}" + +#: connection/commands.py:14 +msgid "Custom commands" +msgstr "Comandos personalizados" + +#: connection/commands.py:16 +msgid "Custom" +msgstr "Personalizado" + +#: connection/commands.py:26 +msgid "Command cannot be empty." +msgstr "El comando no puede estar vacío." + +#: connection/commands.py:35 connection/commands.py:37 +msgid "Reboot" +msgstr "Reiniciar" + +#: connection/commands.py:46 +msgid "Change password" +msgstr "Cambiar contraseña" + +#: connection/commands.py:48 +msgid "Change Password" +msgstr "Cambiar Contraseña" + +#: connection/commands.py:54 +msgid "Password" +msgstr "Contraseña" + +#: connection/commands.py:58 +msgid "Confirm Password" +msgstr "Confirmar contraseña" + +#: connection/commands.py:61 +msgid "Your password must be atleast 6 characters long" +msgstr "Su contraseña debe tener al menos 6 caracteres" + +#: connection/connectors/ssh.py:109 +msgid "" +"Unrecognized or unsupported SSH key algorithm, only RSA and ED25519 are " +"currently supported." +msgstr "" +"Algoritmo de clave SSH no reconocido o no soportado, actualmente sólo se " +"soportan RSA y ED25519." + +#: connection/tasks.py:77 +msgid "Background task time limit exceeded." +msgstr "Se ha superado el límite de tiempo de la tarea en segundo plano." + +#: connection/tasks.py:84 +#, python-brace-format +msgid "Internal system error: {e}" +msgstr "Error interno del sistema: {e}" + +#: geo/admin.py:112 +msgid "Map" +msgstr "Mapa" + +#: geo/admin.py:122 +msgid "has geographic position set?" +msgstr "¿ha fijado posición geográfica?" + +#: geo/api/filters.py:18 +msgid "Has geographic location set?" +msgstr "¿Se ha fijado la ubicación geográfica?" + +#: geo/api/serializers.py:101 +msgid "FloorPlan object with entered ID does not exists." +msgstr "El \"Plano\" con el ID introducido no existe." + +#: geo/api/serializers.py:153 +msgid "Floorplan can only be added with location of the type indoor" +msgstr "El plano sólo puede añadirse con la ubicación del tipo interior" + +#: geo/api/serializers.py:255 +msgid "Location object with entered ID does not exists." +msgstr "El objeto de ubicación con el ID introducido no existe." + +#: geo/apps.py:15 +msgid "Geographic Information" +msgstr "Información Geográfica" + +#: geo/apps.py:55 +msgid "Geographic positioning" +msgstr "Posicionamiento geográfico" + +#: geo/apps.py:84 +msgid "With geographic position" +msgstr "Con posición geográfica" + +#: geo/apps.py:85 +msgid "Without geographic position" +msgstr "Sin posición geográfica" + +#: geo/apps.py:99 +msgid "Geographic Info" +msgstr "Información Geográfica" + +#: geo/apps.py:102 +msgid "Locations" +msgstr "Ubicaciones" + +#: geo/apps.py:108 +msgid "Floorplans" +msgstr "Planos" + +#: pki/api/serializers.py:19 +msgid "additional x509 certificate extensions" +msgstr "extensiones adicionales del certificado x509" + +#: pki/apps.py:16 +msgid "Public Key Infrastructure" +msgstr "Infraestructura de Clave Pública" + +#: pki/apps.py:26 +msgid "Cas & Certificates" +msgstr "CAs & Certificados" + +#: pki/apps.py:29 +msgid "Certification Authorities" +msgstr "Autoridades de certificación" + +#: pki/apps.py:35 +msgid "Certificates" +msgstr "Certificados" + +#: pki/base/models.py:26 +msgid "CA" +msgstr "Autoridad Certificadora (CA)" + +#: subnet_division/admin.py:30 +msgid "" +"Please keep in mind that once the subnet division rule is created changing " +"changing \"Size\", \"Number of Subnets\" or decreasing \"Number of IPs\" " +"will not be possible." +msgstr "" +"Por favor, tenga en cuenta que una vez creada la regla de división de " +"subredes no será posible cambiar el \"Tamaño\", el \"Número de subredes\" o " +"disminuir el \"Número de IPs\"." + +#: subnet_division/admin.py:98 +msgid "See all devices" +msgstr "Ver todos los dispositivos" + +#: subnet_division/apps.py:13 +msgid "Subnet Division" +msgstr "División de subred" + +#: subnet_division/base/models.py:34 +msgid "Label used to calculate the configuration variables" +msgstr "Etiqueta utilizada para calcular las variables de configuración" + +#: subnet_division/base/models.py:37 +msgid "Number of Subnets" +msgstr "Número de subredes" + +#: subnet_division/base/models.py:38 +msgid "Indicates how many subnets will be created" +msgstr "Indica cuántas subredes se crearán" + +#: subnet_division/base/models.py:42 +msgid "Size of subnets" +msgstr "Tamaño de las subredes" + +#: subnet_division/base/models.py:43 +msgid "Indicates the size of each created subnet" +msgstr "Indica el tamaño de cada subred creada" + +#: subnet_division/base/models.py:46 +msgid "Number of IPs" +msgstr "Número de IPs" + +#: subnet_division/base/models.py:47 +msgid "Indicates how many IP addresses will be created for each subnet" +msgstr "Indica cuántas direcciones IP se crearán para cada subred" + +#: subnet_division/base/models.py:85 +msgid "Only alphanumeric characters and underscores are allowed." +msgstr "Sólo se permiten caracteres alfanuméricos y guiones bajos." + +#: subnet_division/base/models.py:92 +msgid "Invalid master subnet." +msgstr "Subred maestra no válida." + +#: subnet_division/base/models.py:98 +msgid "Subnet size cannot be changed" +msgstr "No se puede cambiar el tamaño de la subred" + +#: subnet_division/base/models.py:102 +msgid "Number of IPs cannot be decreased" +msgstr "No se puede reducir el número de IPs" + +#: subnet_division/base/models.py:107 +msgid "Number of Subnets cannot be changed" +msgstr "No se puede cambiar el número de subredes" + +#: subnet_division/base/models.py:120 +#, python-brace-format +msgid "Master subnet cannot accommodate subnets of size /{0}" +msgstr "La subred maestra no puede alojar subredes de tamaño /{0}" + +#: subnet_division/base/models.py:135 +msgid "" +"The master subnet is too small to acommodate the requested \"number of " +"subnets\" plus the reserved subnet, please increase the size of the master " +"subnet or decrease the \"size of subnets\" field." +msgstr "" +"La subred maestra es demasiado pequeña para alojar el \"número de subredes\" " +"solicitado más la subred reservada; aumente el tamaño de la subred maestra o " +"reduzca el campo \"tamaño de las subredes\"." + +#: subnet_division/base/models.py:148 +msgid "Organization should be same as the subnet" +msgstr "La organización debe ser la misma que la subred" + +#: subnet_division/base/models.py:160 +#, python-brace-format +msgid "" +"Generated subnets of size /{self.size} cannot accommodate {self." +"number_of_ips} IP Addresses." +msgstr "" +"Las subredes generadas de tamaño /{self.size} no pueden alojar {self." +"number_of_ips} direcciones IP." + +#: subnet_division/filters.py:17 +msgid "Subnet division rule type" +msgstr "Tipo de regla de división de subred" + +#: subnet_division/filters.py:34 +msgid "subnet" +msgstr "subred" + +#: subnet_division/filters.py:54 +msgid "device name" +msgstr "nombre del dispositivo" + +#: subnet_division/filters.py:71 +msgid "VPN Server" +msgstr "Servidor VPN" + +#: subnet_division/rule_types/base.py:191 +msgid "Automatically generated reserved subnet." +msgstr "Subred reservada generada automáticamente." + +#: subnet_division/rule_types/base.py:215 +#, python-brace-format +msgid "" +"Failed to provision subnets for [{notification.target}]({notification." +"target_link})" +msgstr "" +"Fallo al aprovisionar subredes para [{notification.target}]({notification." +"target_link})" + +#: subnet_division/rule_types/base.py:219 +#, python-brace-format +msgid "" +"The [{notification.action_object}]({notification.action_link}) subnet has " +"run out of space." +msgstr "" +"La subred [{notification.action_object}]({notification.action_link}) se ha " +"quedado sin espacio." + +#: subnet_division/rule_types/base.py:229 subnet_division/tasks.py:64 +#, python-brace-format +msgid "Automatically generated using {division_rule.label} rule." +msgstr "Generado automáticamente mediante la regla {division_rule.label}." From 25f192c15aeb49457d3d9d1b10ce5761549da289 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Carlos=20Rodr=C3=ADguez=20Texidor?= Date: Wed, 21 May 2025 13:12:12 +0000 Subject: [PATCH 6/7] fix: Update translation placeholders for subnet generation error message --- openwisp_controller/locale/es/LC_MESSAGES/django.po | 12 ++++++------ openwisp_controller/subnet_division/base/models.py | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/openwisp_controller/locale/es/LC_MESSAGES/django.po b/openwisp_controller/locale/es/LC_MESSAGES/django.po index a6f1dda5e..89018764b 100644 --- a/openwisp_controller/locale/es/LC_MESSAGES/django.po +++ b/openwisp_controller/locale/es/LC_MESSAGES/django.po @@ -8,8 +8,8 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-05-20 18:12+0000\n" -"PO-Revision-Date: 2025-05-20 15:12-0300\n" +"POT-Creation-Date: 2025-05-20 19:57+0000\n" +"PO-Revision-Date: 2025-05-20 17:51-0300\n" "Last-Translator: \n" "Language-Team: \n" "Language: es\n" @@ -1655,11 +1655,11 @@ msgstr "La organización debe ser la misma que la subred" #: subnet_division/base/models.py:160 #, python-brace-format msgid "" -"Generated subnets of size /{self.size} cannot accommodate {self." -"number_of_ips} IP Addresses." +"Generated subnets of size /{size} cannot accommodate {number_of_ips} IP " +"Addresses." msgstr "" -"Las subredes generadas de tamaño /{self.size} no pueden alojar {self." -"number_of_ips} direcciones IP." +"Las subredes generadas de tamaño /{size} no pueden alojar {number_of_ips} " +"direcciones IP." #: subnet_division/filters.py:17 msgid "Subnet division rule type" diff --git a/openwisp_controller/subnet_division/base/models.py b/openwisp_controller/subnet_division/base/models.py index 2612805c4..fea56ab6c 100644 --- a/openwisp_controller/subnet_division/base/models.py +++ b/openwisp_controller/subnet_division/base/models.py @@ -157,9 +157,9 @@ def _validate_ip_address_consistency(self): raise ValidationError( { 'number_of_ips': _( - f'Generated subnets of size /{self.size} cannot accommodate ' - '{self.number_of_ips} IP Addresses.' - ) + 'Generated subnets of size {size} cannot accommodate ' + '{number_of_ips} IP Addresses.' + ).format(size=self.size, number_of_ips=self.number_of_ips) } ) From 3d2d0cbb0b51e213e223141b9c0d036970d04457 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Carlos=20Rodr=C3=ADguez=20Texidor?= Date: Wed, 21 May 2025 13:12:24 +0000 Subject: [PATCH 7/7] fix: Remove unused translation import in settings.py --- tests/openwisp2/settings.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/openwisp2/settings.py b/tests/openwisp2/settings.py index 7a7370f13..88b19b652 100644 --- a/tests/openwisp2/settings.py +++ b/tests/openwisp2/settings.py @@ -1,6 +1,5 @@ import os import sys -from django.utils.translation import gettext_lazy as _ BASE_DIR = os.path.dirname(os.path.abspath(__file__)) DEBUG = True