2
0
Эх сурвалжийг харах

10300 initial translation support use gettext

Arthur 3 жил өмнө
parent
commit
6eba5d4d96
67 өөрчлөгдсөн 1176 нэмэгдсэн , 1118 устгасан
  1. 26 25
      netbox/circuits/filtersets.py
  2. 6 6
      netbox/circuits/forms/bulk_edit.py
  3. 7 6
      netbox/circuits/forms/bulk_import.py
  4. 6 6
      netbox/circuits/forms/model_forms.py
  5. 2 1
      netbox/circuits/models/circuits.py
  6. 4 3
      netbox/dcim/api/serializers.py
  7. 150 149
      netbox/dcim/filtersets.py
  8. 3 2
      netbox/dcim/forms/bulk_create.py
  9. 24 24
      netbox/dcim/forms/bulk_edit.py
  10. 89 88
      netbox/dcim/forms/bulk_import.py
  11. 3 2
      netbox/dcim/forms/common.py
  12. 12 11
      netbox/dcim/forms/connections.py
  13. 39 39
      netbox/dcim/forms/model_forms.py
  14. 11 10
      netbox/dcim/forms/object_create.py
  15. 3 2
      netbox/dcim/forms/object_import.py
  16. 7 6
      netbox/dcim/models/device_component_templates.py
  17. 18 17
      netbox/dcim/models/device_components.py
  18. 13 12
      netbox/dcim/models/devices.py
  19. 2 1
      netbox/dcim/models/power.py
  20. 11 10
      netbox/dcim/models/racks.py
  21. 4 3
      netbox/dcim/models/sites.py
  22. 43 42
      netbox/extras/filtersets.py
  23. 6 5
      netbox/extras/forms/bulk_edit.py
  24. 11 10
      netbox/extras/forms/bulk_import.py
  25. 2 2
      netbox/extras/forms/filtersets.py
  26. 2 1
      netbox/extras/forms/mixins.py
  27. 8 7
      netbox/extras/forms/model_forms.py
  28. 3 2
      netbox/extras/forms/reports.py
  29. 5 4
      netbox/extras/forms/scripts.py
  30. 23 22
      netbox/extras/models/customfields.py
  31. 37 36
      netbox/extras/models/models.py
  32. 2 1
      netbox/extras/tests/dummy_plugin/navigation.py
  33. 96 95
      netbox/ipam/filtersets.py
  34. 2 1
      netbox/ipam/forms/bulk_create.py
  35. 15 14
      netbox/ipam/forms/bulk_edit.py
  36. 43 42
      netbox/ipam/forms/bulk_import.py
  37. 2 2
      netbox/ipam/forms/filtersets.py
  38. 53 52
      netbox/ipam/forms/model_forms.py
  39. 17 16
      netbox/ipam/models/ip.py
  40. 3 2
      netbox/ipam/models/vlans.py
  41. 4 3
      netbox/ipam/models/vrfs.py
  42. 47 46
      netbox/netbox/config/parameters.py
  43. 2 1
      netbox/netbox/filtersets.py
  44. 2 2
      netbox/netbox/forms/__init__.py
  45. 2 1
      netbox/netbox/forms/base.py
  46. 116 115
      netbox/netbox/navigation/menu.py
  47. 7 6
      netbox/netbox/preferences.py
  48. 19 18
      netbox/tenancy/filtersets.py
  49. 5 4
      netbox/tenancy/forms/bulk_import.py
  50. 6 5
      netbox/users/admin/forms.py
  51. 13 12
      netbox/users/filtersets.py
  52. 5 4
      netbox/users/forms.py
  53. 6 5
      netbox/users/models.py
  54. 4 3
      netbox/utilities/forms/fields/csv.py
  55. 3 2
      netbox/utilities/forms/fields/expandable.py
  56. 3 2
      netbox/utilities/forms/fields/fields.py
  57. 7 6
      netbox/utilities/forms/forms.py
  58. 40 39
      netbox/virtualization/filtersets.py
  59. 2 1
      netbox/virtualization/forms/bulk_create.py
  60. 11 10
      netbox/virtualization/forms/bulk_edit.py
  61. 17 16
      netbox/virtualization/forms/bulk_import.py
  62. 2 2
      netbox/virtualization/forms/filtersets.py
  63. 10 9
      netbox/virtualization/forms/model_forms.py
  64. 6 5
      netbox/wireless/forms/bulk_edit.py
  65. 11 10
      netbox/wireless/forms/bulk_import.py
  66. 2 2
      netbox/wireless/forms/filtersets.py
  67. 11 10
      netbox/wireless/forms/model_forms.py

+ 26 - 25
netbox/circuits/filtersets.py

@@ -1,5 +1,6 @@
 import django_filters
 import django_filters
 from django.db.models import Q
 from django.db.models import Q
+from django.utils.translation import gettext as _
 
 
 from dcim.filtersets import CabledObjectFilterSet
 from dcim.filtersets import CabledObjectFilterSet
 from dcim.models import Region, Site, SiteGroup
 from dcim.models import Region, Site, SiteGroup
@@ -24,43 +25,43 @@ class ProviderFilterSet(NetBoxModelFilterSet, ContactModelFilterSet):
         queryset=Region.objects.all(),
         queryset=Region.objects.all(),
         field_name='circuits__terminations__site__region',
         field_name='circuits__terminations__site__region',
         lookup_expr='in',
         lookup_expr='in',
-        label='Region (ID)',
+        label=_('Region (ID)'),
     )
     )
     region = TreeNodeMultipleChoiceFilter(
     region = TreeNodeMultipleChoiceFilter(
         queryset=Region.objects.all(),
         queryset=Region.objects.all(),
         field_name='circuits__terminations__site__region',
         field_name='circuits__terminations__site__region',
         lookup_expr='in',
         lookup_expr='in',
         to_field_name='slug',
         to_field_name='slug',
-        label='Region (slug)',
+        label=_('Region (slug)'),
     )
     )
     site_group_id = TreeNodeMultipleChoiceFilter(
     site_group_id = TreeNodeMultipleChoiceFilter(
         queryset=SiteGroup.objects.all(),
         queryset=SiteGroup.objects.all(),
         field_name='circuits__terminations__site__group',
         field_name='circuits__terminations__site__group',
         lookup_expr='in',
         lookup_expr='in',
-        label='Site group (ID)',
+        label=_('Site group (ID)'),
     )
     )
     site_group = TreeNodeMultipleChoiceFilter(
     site_group = TreeNodeMultipleChoiceFilter(
         queryset=SiteGroup.objects.all(),
         queryset=SiteGroup.objects.all(),
         field_name='circuits__terminations__site__group',
         field_name='circuits__terminations__site__group',
         lookup_expr='in',
         lookup_expr='in',
         to_field_name='slug',
         to_field_name='slug',
-        label='Site group (slug)',
+        label=_('Site group (slug)'),
     )
     )
     site_id = django_filters.ModelMultipleChoiceFilter(
     site_id = django_filters.ModelMultipleChoiceFilter(
         field_name='circuits__terminations__site',
         field_name='circuits__terminations__site',
         queryset=Site.objects.all(),
         queryset=Site.objects.all(),
-        label='Site',
+        label=_('Site'),
     )
     )
     site = django_filters.ModelMultipleChoiceFilter(
     site = django_filters.ModelMultipleChoiceFilter(
         field_name='circuits__terminations__site__slug',
         field_name='circuits__terminations__site__slug',
         queryset=Site.objects.all(),
         queryset=Site.objects.all(),
         to_field_name='slug',
         to_field_name='slug',
-        label='Site (slug)',
+        label=_('Site (slug)'),
     )
     )
     asn_id = django_filters.ModelMultipleChoiceFilter(
     asn_id = django_filters.ModelMultipleChoiceFilter(
         field_name='asns',
         field_name='asns',
         queryset=ASN.objects.all(),
         queryset=ASN.objects.all(),
-        label='ASN (ID)',
+        label=_('ASN (ID)'),
     )
     )
 
 
     class Meta:
     class Meta:
@@ -80,13 +81,13 @@ class ProviderFilterSet(NetBoxModelFilterSet, ContactModelFilterSet):
 class ProviderNetworkFilterSet(NetBoxModelFilterSet):
 class ProviderNetworkFilterSet(NetBoxModelFilterSet):
     provider_id = django_filters.ModelMultipleChoiceFilter(
     provider_id = django_filters.ModelMultipleChoiceFilter(
         queryset=Provider.objects.all(),
         queryset=Provider.objects.all(),
-        label='Provider (ID)',
+        label=_('Provider (ID)'),
     )
     )
     provider = django_filters.ModelMultipleChoiceFilter(
     provider = django_filters.ModelMultipleChoiceFilter(
         field_name='provider__slug',
         field_name='provider__slug',
         queryset=Provider.objects.all(),
         queryset=Provider.objects.all(),
         to_field_name='slug',
         to_field_name='slug',
-        label='Provider (slug)',
+        label=_('Provider (slug)'),
     )
     )
 
 
     class Meta:
     class Meta:
@@ -114,28 +115,28 @@ class CircuitTypeFilterSet(OrganizationalModelFilterSet):
 class CircuitFilterSet(NetBoxModelFilterSet, TenancyFilterSet, ContactModelFilterSet):
 class CircuitFilterSet(NetBoxModelFilterSet, TenancyFilterSet, ContactModelFilterSet):
     provider_id = django_filters.ModelMultipleChoiceFilter(
     provider_id = django_filters.ModelMultipleChoiceFilter(
         queryset=Provider.objects.all(),
         queryset=Provider.objects.all(),
-        label='Provider (ID)',
+        label=_('Provider (ID)'),
     )
     )
     provider = django_filters.ModelMultipleChoiceFilter(
     provider = django_filters.ModelMultipleChoiceFilter(
         field_name='provider__slug',
         field_name='provider__slug',
         queryset=Provider.objects.all(),
         queryset=Provider.objects.all(),
         to_field_name='slug',
         to_field_name='slug',
-        label='Provider (slug)',
+        label=_('Provider (slug)'),
     )
     )
     provider_network_id = django_filters.ModelMultipleChoiceFilter(
     provider_network_id = django_filters.ModelMultipleChoiceFilter(
         field_name='terminations__provider_network',
         field_name='terminations__provider_network',
         queryset=ProviderNetwork.objects.all(),
         queryset=ProviderNetwork.objects.all(),
-        label='ProviderNetwork (ID)',
+        label=_('ProviderNetwork (ID)'),
     )
     )
     type_id = django_filters.ModelMultipleChoiceFilter(
     type_id = django_filters.ModelMultipleChoiceFilter(
         queryset=CircuitType.objects.all(),
         queryset=CircuitType.objects.all(),
-        label='Circuit type (ID)',
+        label=_('Circuit type (ID)'),
     )
     )
     type = django_filters.ModelMultipleChoiceFilter(
     type = django_filters.ModelMultipleChoiceFilter(
         field_name='type__slug',
         field_name='type__slug',
         queryset=CircuitType.objects.all(),
         queryset=CircuitType.objects.all(),
         to_field_name='slug',
         to_field_name='slug',
-        label='Circuit type (slug)',
+        label=_('Circuit type (slug)'),
     )
     )
     status = django_filters.MultipleChoiceFilter(
     status = django_filters.MultipleChoiceFilter(
         choices=CircuitStatusChoices,
         choices=CircuitStatusChoices,
@@ -145,38 +146,38 @@ class CircuitFilterSet(NetBoxModelFilterSet, TenancyFilterSet, ContactModelFilte
         queryset=Region.objects.all(),
         queryset=Region.objects.all(),
         field_name='terminations__site__region',
         field_name='terminations__site__region',
         lookup_expr='in',
         lookup_expr='in',
-        label='Region (ID)',
+        label=_('Region (ID)'),
     )
     )
     region = TreeNodeMultipleChoiceFilter(
     region = TreeNodeMultipleChoiceFilter(
         queryset=Region.objects.all(),
         queryset=Region.objects.all(),
         field_name='terminations__site__region',
         field_name='terminations__site__region',
         lookup_expr='in',
         lookup_expr='in',
         to_field_name='slug',
         to_field_name='slug',
-        label='Region (slug)',
+        label=_('Region (slug)'),
     )
     )
     site_group_id = TreeNodeMultipleChoiceFilter(
     site_group_id = TreeNodeMultipleChoiceFilter(
         queryset=SiteGroup.objects.all(),
         queryset=SiteGroup.objects.all(),
         field_name='terminations__site__group',
         field_name='terminations__site__group',
         lookup_expr='in',
         lookup_expr='in',
-        label='Site group (ID)',
+        label=_('Site group (ID)'),
     )
     )
     site_group = TreeNodeMultipleChoiceFilter(
     site_group = TreeNodeMultipleChoiceFilter(
         queryset=SiteGroup.objects.all(),
         queryset=SiteGroup.objects.all(),
         field_name='terminations__site__group',
         field_name='terminations__site__group',
         lookup_expr='in',
         lookup_expr='in',
         to_field_name='slug',
         to_field_name='slug',
-        label='Site group (slug)',
+        label=_('Site group (slug)'),
     )
     )
     site_id = django_filters.ModelMultipleChoiceFilter(
     site_id = django_filters.ModelMultipleChoiceFilter(
         field_name='terminations__site',
         field_name='terminations__site',
         queryset=Site.objects.all(),
         queryset=Site.objects.all(),
-        label='Site (ID)',
+        label=_('Site (ID)'),
     )
     )
     site = django_filters.ModelMultipleChoiceFilter(
     site = django_filters.ModelMultipleChoiceFilter(
         field_name='terminations__site__slug',
         field_name='terminations__site__slug',
         queryset=Site.objects.all(),
         queryset=Site.objects.all(),
         to_field_name='slug',
         to_field_name='slug',
-        label='Site (slug)',
+        label=_('Site (slug)'),
     )
     )
 
 
     class Meta:
     class Meta:
@@ -199,25 +200,25 @@ class CircuitFilterSet(NetBoxModelFilterSet, TenancyFilterSet, ContactModelFilte
 class CircuitTerminationFilterSet(NetBoxModelFilterSet, CabledObjectFilterSet):
 class CircuitTerminationFilterSet(NetBoxModelFilterSet, CabledObjectFilterSet):
     q = django_filters.CharFilter(
     q = django_filters.CharFilter(
         method='search',
         method='search',
-        label='Search',
+        label=_('Search'),
     )
     )
     circuit_id = django_filters.ModelMultipleChoiceFilter(
     circuit_id = django_filters.ModelMultipleChoiceFilter(
         queryset=Circuit.objects.all(),
         queryset=Circuit.objects.all(),
-        label='Circuit',
+        label=_('Circuit'),
     )
     )
     site_id = django_filters.ModelMultipleChoiceFilter(
     site_id = django_filters.ModelMultipleChoiceFilter(
         queryset=Site.objects.all(),
         queryset=Site.objects.all(),
-        label='Site (ID)',
+        label=_('Site (ID)'),
     )
     )
     site = django_filters.ModelMultipleChoiceFilter(
     site = django_filters.ModelMultipleChoiceFilter(
         field_name='site__slug',
         field_name='site__slug',
         queryset=Site.objects.all(),
         queryset=Site.objects.all(),
         to_field_name='slug',
         to_field_name='slug',
-        label='Site (slug)',
+        label=_('Site (slug)'),
     )
     )
     provider_network_id = django_filters.ModelMultipleChoiceFilter(
     provider_network_id = django_filters.ModelMultipleChoiceFilter(
         queryset=ProviderNetwork.objects.all(),
         queryset=ProviderNetwork.objects.all(),
-        label='ProviderNetwork (ID)',
+        label=_('ProviderNetwork (ID)'),
     )
     )
 
 
     class Meta:
     class Meta:

+ 6 - 6
netbox/circuits/forms/bulk_edit.py

@@ -28,7 +28,7 @@ class ProviderBulkEditForm(NetBoxModelBulkEditForm):
     account = forms.CharField(
     account = forms.CharField(
         max_length=30,
         max_length=30,
         required=False,
         required=False,
-        label='Account number'
+        label=_('Account number')
     )
     )
     description = forms.CharField(
     description = forms.CharField(
         max_length=200,
         max_length=200,
@@ -36,7 +36,7 @@ class ProviderBulkEditForm(NetBoxModelBulkEditForm):
     )
     )
     comments = CommentField(
     comments = CommentField(
         widget=SmallTextarea,
         widget=SmallTextarea,
-        label='Comments'
+        label=_('Comments')
     )
     )
 
 
     model = Provider
     model = Provider
@@ -56,7 +56,7 @@ class ProviderNetworkBulkEditForm(NetBoxModelBulkEditForm):
     service_id = forms.CharField(
     service_id = forms.CharField(
         max_length=100,
         max_length=100,
         required=False,
         required=False,
-        label='Service ID'
+        label=_('Service ID')
     )
     )
     description = forms.CharField(
     description = forms.CharField(
         max_length=200,
         max_length=200,
@@ -64,7 +64,7 @@ class ProviderNetworkBulkEditForm(NetBoxModelBulkEditForm):
     )
     )
     comments = CommentField(
     comments = CommentField(
         widget=SmallTextarea,
         widget=SmallTextarea,
-        label='Comments'
+        label=_('Comments')
     )
     )
 
 
     model = ProviderNetwork
     model = ProviderNetwork
@@ -118,7 +118,7 @@ class CircuitBulkEditForm(NetBoxModelBulkEditForm):
     )
     )
     commit_rate = forms.IntegerField(
     commit_rate = forms.IntegerField(
         required=False,
         required=False,
-        label='Commit rate (Kbps)'
+        label=_('Commit rate (Kbps)')
     )
     )
     description = forms.CharField(
     description = forms.CharField(
         max_length=100,
         max_length=100,
@@ -126,7 +126,7 @@ class CircuitBulkEditForm(NetBoxModelBulkEditForm):
     )
     )
     comments = CommentField(
     comments = CommentField(
         widget=SmallTextarea,
         widget=SmallTextarea,
-        label='Comments'
+        label=_('Comments')
     )
     )
 
 
     model = Circuit
     model = Circuit

+ 7 - 6
netbox/circuits/forms/bulk_import.py

@@ -1,5 +1,6 @@
 from circuits.choices import CircuitStatusChoices
 from circuits.choices import CircuitStatusChoices
 from circuits.models import *
 from circuits.models import *
+from django.utils.translation import gettext as _
 from netbox.forms import NetBoxModelCSVForm
 from netbox.forms import NetBoxModelCSVForm
 from tenancy.models import Tenant
 from tenancy.models import Tenant
 from utilities.forms import CSVChoiceField, CSVModelChoiceField, SlugField
 from utilities.forms import CSVChoiceField, CSVModelChoiceField, SlugField
@@ -26,7 +27,7 @@ class ProviderNetworkCSVForm(NetBoxModelCSVForm):
     provider = CSVModelChoiceField(
     provider = CSVModelChoiceField(
         queryset=Provider.objects.all(),
         queryset=Provider.objects.all(),
         to_field_name='name',
         to_field_name='name',
-        help_text='Assigned provider'
+        help_text=_('Assigned provider')
     )
     )
 
 
     class Meta:
     class Meta:
@@ -43,7 +44,7 @@ class CircuitTypeCSVForm(NetBoxModelCSVForm):
         model = CircuitType
         model = CircuitType
         fields = ('name', 'slug', 'description', 'tags')
         fields = ('name', 'slug', 'description', 'tags')
         help_texts = {
         help_texts = {
-            'name': 'Name of circuit type',
+            'name': _('Name of circuit type'),
         }
         }
 
 
 
 
@@ -51,22 +52,22 @@ class CircuitCSVForm(NetBoxModelCSVForm):
     provider = CSVModelChoiceField(
     provider = CSVModelChoiceField(
         queryset=Provider.objects.all(),
         queryset=Provider.objects.all(),
         to_field_name='name',
         to_field_name='name',
-        help_text='Assigned provider'
+        help_text=_('Assigned provider')
     )
     )
     type = CSVModelChoiceField(
     type = CSVModelChoiceField(
         queryset=CircuitType.objects.all(),
         queryset=CircuitType.objects.all(),
         to_field_name='name',
         to_field_name='name',
-        help_text='Type of circuit'
+        help_text=_('Type of circuit')
     )
     )
     status = CSVChoiceField(
     status = CSVChoiceField(
         choices=CircuitStatusChoices,
         choices=CircuitStatusChoices,
-        help_text='Operational status'
+        help_text=_('Operational status')
     )
     )
     tenant = CSVModelChoiceField(
     tenant = CSVModelChoiceField(
         queryset=Tenant.objects.all(),
         queryset=Tenant.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
-        help_text='Assigned tenant'
+        help_text=_('Assigned tenant')
     )
     )
 
 
     class Meta:
     class Meta:

+ 6 - 6
netbox/circuits/forms/model_forms.py

@@ -39,7 +39,7 @@ class ProviderForm(NetBoxModelForm):
             'name', 'slug', 'account', 'asns', 'description', 'comments', 'tags',
             'name', 'slug', 'account', 'asns', 'description', 'comments', 'tags',
         ]
         ]
         help_texts = {
         help_texts = {
-            'name': "Full name of the provider",
+            'name': _("Full name of the provider"),
         }
         }
 
 
 
 
@@ -98,8 +98,8 @@ class CircuitForm(TenancyForm, NetBoxModelForm):
             'tenant_group', 'tenant', 'comments', 'tags',
             'tenant_group', 'tenant', 'comments', 'tags',
         ]
         ]
         help_texts = {
         help_texts = {
-            'cid': "Unique circuit ID",
-            'commit_rate': "Committed rate",
+            'cid': _("Unique circuit ID"),
+            'commit_rate': _("Committed rate"),
         }
         }
         widgets = {
         widgets = {
             'status': StaticSelect(),
             'status': StaticSelect(),
@@ -157,9 +157,9 @@ class CircuitTerminationForm(NetBoxModelForm):
             'port_speed', 'upstream_speed', 'xconnect_id', 'pp_info', 'description', 'tags',
             'port_speed', 'upstream_speed', 'xconnect_id', 'pp_info', 'description', 'tags',
         ]
         ]
         help_texts = {
         help_texts = {
-            'port_speed': "Physical circuit speed",
-            'xconnect_id': "ID of the local cross-connect",
-            'pp_info': "Patch panel ID and port number(s)"
+            'port_speed': _("Physical circuit speed"),
+            'xconnect_id': _("ID of the local cross-connect"),
+            'pp_info': _("Patch panel ID and port number(s)")
         }
         }
         widgets = {
         widgets = {
             'term_side': StaticSelect(),
             'term_side': StaticSelect(),

+ 2 - 1
netbox/circuits/models/circuits.py

@@ -3,6 +3,7 @@ from django.contrib.contenttypes.fields import GenericRelation
 from django.core.exceptions import ValidationError
 from django.core.exceptions import ValidationError
 from django.db import models
 from django.db import models
 from django.urls import reverse
 from django.urls import reverse
+from django.utils.translation import gettext as _
 
 
 from circuits.choices import *
 from circuits.choices import *
 from dcim.models import CabledObjectModel
 from dcim.models import CabledObjectModel
@@ -168,7 +169,7 @@ class CircuitTermination(
         blank=True,
         blank=True,
         null=True,
         null=True,
         verbose_name='Upstream speed (Kbps)',
         verbose_name='Upstream speed (Kbps)',
-        help_text='Upstream speed, if different from port speed'
+        help_text=_('Upstream speed, if different from port speed')
     )
     )
     xconnect_id = models.CharField(
     xconnect_id = models.CharField(
         max_length=50,
         max_length=50,

+ 4 - 3
netbox/dcim/api/serializers.py

@@ -1,6 +1,7 @@
 import decimal
 import decimal
 
 
 from django.contrib.contenttypes.models import ContentType
 from django.contrib.contenttypes.models import ContentType
+from django.utils.translation import gettext as _
 from drf_yasg.utils import swagger_serializer_method
 from drf_yasg.utils import swagger_serializer_method
 from rest_framework import serializers
 from rest_framework import serializers
 from timezone_field.rest_framework import TimeZoneSerializerField
 from timezone_field.rest_framework import TimeZoneSerializerField
@@ -197,7 +198,7 @@ class RackSerializer(NetBoxModelSerializer):
     status = ChoiceField(choices=RackStatusChoices, required=False)
     status = ChoiceField(choices=RackStatusChoices, required=False)
     role = NestedRackRoleSerializer(required=False, allow_null=True)
     role = NestedRackRoleSerializer(required=False, allow_null=True)
     type = ChoiceField(choices=RackTypeChoices, allow_blank=True, required=False)
     type = ChoiceField(choices=RackTypeChoices, allow_blank=True, required=False)
-    facility_id = serializers.CharField(max_length=50, allow_blank=True, allow_null=True, label='Facility ID',
+    facility_id = serializers.CharField(max_length=50, allow_blank=True, allow_null=True, label=_('Facility ID'),
                                         default=None)
                                         default=None)
     width = ChoiceField(choices=RackWidthChoices, required=False)
     width = ChoiceField(choices=RackWidthChoices, required=False)
     outer_unit = ChoiceField(choices=RackDimensionUnitChoices, allow_blank=True, required=False)
     outer_unit = ChoiceField(choices=RackDimensionUnitChoices, allow_blank=True, required=False)
@@ -311,7 +312,7 @@ class DeviceTypeSerializer(NetBoxModelSerializer):
     u_height = serializers.DecimalField(
     u_height = serializers.DecimalField(
         max_digits=4,
         max_digits=4,
         decimal_places=1,
         decimal_places=1,
-        label='Position (U)',
+        label=_('Position (U)'),
         min_value=0,
         min_value=0,
         default=1.0
         default=1.0
     )
     )
@@ -636,7 +637,7 @@ class DeviceSerializer(NetBoxModelSerializer):
         max_digits=4,
         max_digits=4,
         decimal_places=1,
         decimal_places=1,
         allow_null=True,
         allow_null=True,
-        label='Position (U)',
+        label=_('Position (U)'),
         min_value=decimal.Decimal(0.5),
         min_value=decimal.Decimal(0.5),
         default=None
         default=None
     )
     )

Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 150 - 149
netbox/dcim/filtersets.py


+ 3 - 2
netbox/dcim/forms/bulk_create.py

@@ -1,6 +1,7 @@
 from django import forms
 from django import forms
 
 
 from dcim.models import *
 from dcim.models import *
+from django.utils.translation import gettext as _
 from extras.forms import CustomFieldsMixin
 from extras.forms import CustomFieldsMixin
 from extras.models import Tag
 from extras.models import Tag
 from utilities.forms import BootstrapMixin, DynamicModelMultipleChoiceField, ExpandableNameField, form_from_model
 from utilities.forms import BootstrapMixin, DynamicModelMultipleChoiceField, ExpandableNameField, form_from_model
@@ -105,9 +106,9 @@ class ModuleBayBulkCreateForm(DeviceBulkAddComponentForm):
     field_order = ('name', 'label', 'position_pattern', 'description', 'tags')
     field_order = ('name', 'label', 'position_pattern', 'description', 'tags')
     replication_fields = ('name', 'label', 'position')
     replication_fields = ('name', 'label', 'position')
     position_pattern = ExpandableNameField(
     position_pattern = ExpandableNameField(
-        label='Position',
+        label=_('Position'),
         required=False,
         required=False,
-        help_text='Alphanumeric ranges are supported. (Must match the number of names being created.)'
+        help_text=_('Alphanumeric ranges are supported. (Must match the number of names being created.)')
     )
     )
 
 
 
 

+ 24 - 24
netbox/dcim/forms/bulk_edit.py

@@ -1,6 +1,6 @@
 from django import forms
 from django import forms
-from django.utils.translation import gettext as _
 from django.contrib.auth.models import User
 from django.contrib.auth.models import User
+from django.utils.translation import gettext as _
 from timezone_field import TimeZoneFormField
 from timezone_field import TimeZoneFormField
 
 
 from dcim.choices import *
 from dcim.choices import *
@@ -126,7 +126,7 @@ class SiteBulkEditForm(NetBoxModelBulkEditForm):
     )
     )
     contact_email = forms.EmailField(
     contact_email = forms.EmailField(
         required=False,
         required=False,
-        label='Contact E-mail'
+        label=_('Contact E-mail')
     )
     )
     time_zone = TimeZoneFormField(
     time_zone = TimeZoneFormField(
         choices=add_blank_choice(TimeZoneFormField().choices),
         choices=add_blank_choice(TimeZoneFormField().choices),
@@ -248,7 +248,7 @@ class RackBulkEditForm(NetBoxModelBulkEditForm):
     serial = forms.CharField(
     serial = forms.CharField(
         max_length=50,
         max_length=50,
         required=False,
         required=False,
-        label='Serial Number'
+        label=_('Serial Number')
     )
     )
     asset_tag = forms.CharField(
     asset_tag = forms.CharField(
         max_length=50,
         max_length=50,
@@ -266,12 +266,12 @@ class RackBulkEditForm(NetBoxModelBulkEditForm):
     )
     )
     u_height = forms.IntegerField(
     u_height = forms.IntegerField(
         required=False,
         required=False,
-        label='Height (U)'
+        label=_('Height (U)')
     )
     )
     desc_units = forms.NullBooleanField(
     desc_units = forms.NullBooleanField(
         required=False,
         required=False,
         widget=BulkEditNullBooleanSelect,
         widget=BulkEditNullBooleanSelect,
-        label='Descending units'
+        label=_('Descending units')
     )
     )
     outer_width = forms.IntegerField(
     outer_width = forms.IntegerField(
         required=False,
         required=False,
@@ -380,7 +380,7 @@ class DeviceTypeBulkEditForm(NetBoxModelBulkEditForm):
     is_full_depth = forms.NullBooleanField(
     is_full_depth = forms.NullBooleanField(
         required=False,
         required=False,
         widget=BulkEditNullBooleanSelect(),
         widget=BulkEditNullBooleanSelect(),
-        label='Is full depth'
+        label=_('Is full depth')
     )
     )
     airflow = forms.ChoiceField(
     airflow = forms.ChoiceField(
         choices=add_blank_choice(DeviceAirflowChoices),
         choices=add_blank_choice(DeviceAirflowChoices),
@@ -456,7 +456,7 @@ class DeviceRoleBulkEditForm(NetBoxModelBulkEditForm):
     vm_role = forms.NullBooleanField(
     vm_role = forms.NullBooleanField(
         required=False,
         required=False,
         widget=BulkEditNullBooleanSelect,
         widget=BulkEditNullBooleanSelect,
-        label='VM role'
+        label=_('VM role')
     )
     )
     description = forms.CharField(
     description = forms.CharField(
         max_length=200,
         max_length=200,
@@ -540,7 +540,7 @@ class DeviceBulkEditForm(NetBoxModelBulkEditForm):
     serial = forms.CharField(
     serial = forms.CharField(
         max_length=50,
         max_length=50,
         required=False,
         required=False,
-        label='Serial Number'
+        label=_('Serial Number')
     )
     )
     description = forms.CharField(
     description = forms.CharField(
         max_length=200,
         max_length=200,
@@ -577,7 +577,7 @@ class ModuleBulkEditForm(NetBoxModelBulkEditForm):
     serial = forms.CharField(
     serial = forms.CharField(
         max_length=50,
         max_length=50,
         required=False,
         required=False,
-        label='Serial Number'
+        label=_('Serial Number')
     )
     )
     description = forms.CharField(
     description = forms.CharField(
         max_length=200,
         max_length=200,
@@ -767,7 +767,7 @@ class PowerFeedBulkEditForm(NetBoxModelBulkEditForm):
     )
     )
     comments = CommentField(
     comments = CommentField(
         widget=SmallTextarea,
         widget=SmallTextarea,
-        label='Comments'
+        label=_('Comments')
     )
     )
 
 
     model = PowerFeed
     model = PowerFeed
@@ -838,12 +838,12 @@ class PowerPortTemplateBulkEditForm(BulkEditForm):
     maximum_draw = forms.IntegerField(
     maximum_draw = forms.IntegerField(
         min_value=1,
         min_value=1,
         required=False,
         required=False,
-        help_text="Maximum power draw (watts)"
+        help_text=_("Maximum power draw (watts)")
     )
     )
     allocated_draw = forms.IntegerField(
     allocated_draw = forms.IntegerField(
         min_value=1,
         min_value=1,
         required=False,
         required=False,
-        help_text="Allocated power draw (watts)"
+        help_text=_("Allocated power draw (watts)")
     )
     )
     description = forms.CharField(
     description = forms.CharField(
         required=False
         required=False
@@ -916,7 +916,7 @@ class InterfaceTemplateBulkEditForm(BulkEditForm):
     mgmt_only = forms.NullBooleanField(
     mgmt_only = forms.NullBooleanField(
         required=False,
         required=False,
         widget=BulkEditNullBooleanSelect,
         widget=BulkEditNullBooleanSelect,
-        label='Management only'
+        label=_('Management only')
     )
     )
     description = forms.CharField(
     description = forms.CharField(
         required=False
         required=False
@@ -926,14 +926,14 @@ class InterfaceTemplateBulkEditForm(BulkEditForm):
         required=False,
         required=False,
         initial='',
         initial='',
         widget=StaticSelect(),
         widget=StaticSelect(),
-        label='PoE mode'
+        label=_('PoE mode')
     )
     )
     poe_type = forms.ChoiceField(
     poe_type = forms.ChoiceField(
         choices=add_blank_choice(InterfacePoETypeChoices),
         choices=add_blank_choice(InterfacePoETypeChoices),
         required=False,
         required=False,
         initial='',
         initial='',
         widget=StaticSelect(),
         widget=StaticSelect(),
-        label='PoE type'
+        label=_('PoE type')
     )
     )
 
 
     nullable_fields = ('label', 'description', 'poe_mode', 'poe_type')
     nullable_fields = ('label', 'description', 'poe_mode', 'poe_type')
@@ -1174,31 +1174,31 @@ class InterfaceBulkEditForm(
         query_params={
         query_params={
             'type': 'lag',
             'type': 'lag',
         },
         },
-        label='LAG'
+        label=_('LAG')
     )
     )
     speed = forms.IntegerField(
     speed = forms.IntegerField(
         required=False,
         required=False,
         widget=SelectSpeedWidget(),
         widget=SelectSpeedWidget(),
-        label='Speed'
+        label=_('Speed')
     )
     )
     mgmt_only = forms.NullBooleanField(
     mgmt_only = forms.NullBooleanField(
         required=False,
         required=False,
         widget=BulkEditNullBooleanSelect,
         widget=BulkEditNullBooleanSelect,
-        label='Management only'
+        label=_('Management only')
     )
     )
     poe_mode = forms.ChoiceField(
     poe_mode = forms.ChoiceField(
         choices=add_blank_choice(InterfacePoEModeChoices),
         choices=add_blank_choice(InterfacePoEModeChoices),
         required=False,
         required=False,
         initial='',
         initial='',
         widget=StaticSelect(),
         widget=StaticSelect(),
-        label='PoE mode'
+        label=_('PoE mode')
     )
     )
     poe_type = forms.ChoiceField(
     poe_type = forms.ChoiceField(
         choices=add_blank_choice(InterfacePoETypeChoices),
         choices=add_blank_choice(InterfacePoETypeChoices),
         required=False,
         required=False,
         initial='',
         initial='',
         widget=StaticSelect(),
         widget=StaticSelect(),
-        label='PoE type'
+        label=_('PoE type')
     )
     )
     mark_connected = forms.NullBooleanField(
     mark_connected = forms.NullBooleanField(
         required=False,
         required=False,
@@ -1213,7 +1213,7 @@ class InterfaceBulkEditForm(
     vlan_group = DynamicModelChoiceField(
     vlan_group = DynamicModelChoiceField(
         queryset=VLANGroup.objects.all(),
         queryset=VLANGroup.objects.all(),
         required=False,
         required=False,
-        label='VLAN group'
+        label=_('VLAN group')
     )
     )
     untagged_vlan = DynamicModelChoiceField(
     untagged_vlan = DynamicModelChoiceField(
         queryset=VLAN.objects.all(),
         queryset=VLAN.objects.all(),
@@ -1221,7 +1221,7 @@ class InterfaceBulkEditForm(
         query_params={
         query_params={
             'group_id': '$vlan_group',
             'group_id': '$vlan_group',
         },
         },
-        label='Untagged VLAN'
+        label=_('Untagged VLAN')
     )
     )
     tagged_vlans = DynamicModelMultipleChoiceField(
     tagged_vlans = DynamicModelMultipleChoiceField(
         queryset=VLAN.objects.all(),
         queryset=VLAN.objects.all(),
@@ -1229,12 +1229,12 @@ class InterfaceBulkEditForm(
         query_params={
         query_params={
             'group_id': '$vlan_group',
             'group_id': '$vlan_group',
         },
         },
-        label='Tagged VLANs'
+        label=_('Tagged VLANs')
     )
     )
     vrf = DynamicModelChoiceField(
     vrf = DynamicModelChoiceField(
         queryset=VRF.objects.all(),
         queryset=VRF.objects.all(),
         required=False,
         required=False,
-        label='VRF'
+        label=_('VRF')
     )
     )
 
 
     model = Interface
     model = Interface

+ 89 - 88
netbox/dcim/forms/bulk_import.py

@@ -3,6 +3,7 @@ from django.contrib.contenttypes.models import ContentType
 from django.contrib.postgres.forms.array import SimpleArrayField
 from django.contrib.postgres.forms.array import SimpleArrayField
 from django.core.exceptions import ObjectDoesNotExist
 from django.core.exceptions import ObjectDoesNotExist
 from django.utils.safestring import mark_safe
 from django.utils.safestring import mark_safe
+from django.utils.translation import gettext as _
 
 
 from dcim.choices import *
 from dcim.choices import *
 from dcim.constants import *
 from dcim.constants import *
@@ -52,7 +53,7 @@ class RegionCSVForm(NetBoxModelCSVForm):
         queryset=Region.objects.all(),
         queryset=Region.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
-        help_text='Name of parent region'
+        help_text=_('Name of parent region')
     )
     )
 
 
     class Meta:
     class Meta:
@@ -65,7 +66,7 @@ class SiteGroupCSVForm(NetBoxModelCSVForm):
         queryset=SiteGroup.objects.all(),
         queryset=SiteGroup.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
-        help_text='Name of parent site group'
+        help_text=_('Name of parent site group')
     )
     )
 
 
     class Meta:
     class Meta:
@@ -76,25 +77,25 @@ class SiteGroupCSVForm(NetBoxModelCSVForm):
 class SiteCSVForm(NetBoxModelCSVForm):
 class SiteCSVForm(NetBoxModelCSVForm):
     status = CSVChoiceField(
     status = CSVChoiceField(
         choices=SiteStatusChoices,
         choices=SiteStatusChoices,
-        help_text='Operational status'
+        help_text=_('Operational status')
     )
     )
     region = CSVModelChoiceField(
     region = CSVModelChoiceField(
         queryset=Region.objects.all(),
         queryset=Region.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
-        help_text='Assigned region'
+        help_text=_('Assigned region')
     )
     )
     group = CSVModelChoiceField(
     group = CSVModelChoiceField(
         queryset=SiteGroup.objects.all(),
         queryset=SiteGroup.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
-        help_text='Assigned group'
+        help_text=_('Assigned group')
     )
     )
     tenant = CSVModelChoiceField(
     tenant = CSVModelChoiceField(
         queryset=Tenant.objects.all(),
         queryset=Tenant.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
-        help_text='Assigned tenant'
+        help_text=_('Assigned tenant')
     )
     )
 
 
     class Meta:
     class Meta:
@@ -105,7 +106,7 @@ class SiteCSVForm(NetBoxModelCSVForm):
         )
         )
         help_texts = {
         help_texts = {
             'time_zone': mark_safe(
             'time_zone': mark_safe(
-                'Time zone (<a href="https://en.wikipedia.org/wiki/List_of_tz_database_time_zones">available options</a>)'
+                _('Time zone (<a href="https://en.wikipedia.org/wiki/List_of_tz_database_time_zones">available options</a>)')
             )
             )
         }
         }
 
 
@@ -114,26 +115,26 @@ class LocationCSVForm(NetBoxModelCSVForm):
     site = CSVModelChoiceField(
     site = CSVModelChoiceField(
         queryset=Site.objects.all(),
         queryset=Site.objects.all(),
         to_field_name='name',
         to_field_name='name',
-        help_text='Assigned site'
+        help_text=_('Assigned site')
     )
     )
     parent = CSVModelChoiceField(
     parent = CSVModelChoiceField(
         queryset=Location.objects.all(),
         queryset=Location.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
-        help_text='Parent location',
+        help_text=_('Parent location'),
         error_messages={
         error_messages={
-            'invalid_choice': 'Location not found.',
+            'invalid_choice': _('Location not found.'),
         }
         }
     )
     )
     status = CSVChoiceField(
     status = CSVChoiceField(
         choices=LocationStatusChoices,
         choices=LocationStatusChoices,
-        help_text='Operational status'
+        help_text=_('Operational status')
     )
     )
     tenant = CSVModelChoiceField(
     tenant = CSVModelChoiceField(
         queryset=Tenant.objects.all(),
         queryset=Tenant.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
-        help_text='Assigned tenant'
+        help_text=_('Assigned tenant')
     )
     )
 
 
     class Meta:
     class Meta:
@@ -148,7 +149,7 @@ class RackRoleCSVForm(NetBoxModelCSVForm):
         model = RackRole
         model = RackRole
         fields = ('name', 'slug', 'color', 'description', 'tags')
         fields = ('name', 'slug', 'color', 'description', 'tags')
         help_texts = {
         help_texts = {
-            'color': mark_safe('RGB color in hexadecimal (e.g. <code>00ff00</code>)'),
+            'color': mark_safe(_('RGB color in hexadecimal (e.g. <code>00ff00</code>)')),
         }
         }
 
 
 
 
@@ -166,31 +167,31 @@ class RackCSVForm(NetBoxModelCSVForm):
         queryset=Tenant.objects.all(),
         queryset=Tenant.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
-        help_text='Name of assigned tenant'
+        help_text=_('Name of assigned tenant')
     )
     )
     status = CSVChoiceField(
     status = CSVChoiceField(
         choices=RackStatusChoices,
         choices=RackStatusChoices,
-        help_text='Operational status'
+        help_text=_('Operational status')
     )
     )
     role = CSVModelChoiceField(
     role = CSVModelChoiceField(
         queryset=RackRole.objects.all(),
         queryset=RackRole.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
-        help_text='Name of assigned role'
+        help_text=_('Name of assigned role')
     )
     )
     type = CSVChoiceField(
     type = CSVChoiceField(
         choices=RackTypeChoices,
         choices=RackTypeChoices,
         required=False,
         required=False,
-        help_text='Rack type'
+        help_text=_('Rack type')
     )
     )
     width = forms.ChoiceField(
     width = forms.ChoiceField(
         choices=RackWidthChoices,
         choices=RackWidthChoices,
-        help_text='Rail-to-rail width (in inches)'
+        help_text=_('Rail-to-rail width (in inches)')
     )
     )
     outer_unit = CSVChoiceField(
     outer_unit = CSVChoiceField(
         choices=RackDimensionUnitChoices,
         choices=RackDimensionUnitChoices,
         required=False,
         required=False,
-        help_text='Unit for outer dimensions'
+        help_text=_('Unit for outer dimensions')
     )
     )
 
 
     class Meta:
     class Meta:
@@ -215,29 +216,29 @@ class RackReservationCSVForm(NetBoxModelCSVForm):
     site = CSVModelChoiceField(
     site = CSVModelChoiceField(
         queryset=Site.objects.all(),
         queryset=Site.objects.all(),
         to_field_name='name',
         to_field_name='name',
-        help_text='Parent site'
+        help_text=_('Parent site')
     )
     )
     location = CSVModelChoiceField(
     location = CSVModelChoiceField(
         queryset=Location.objects.all(),
         queryset=Location.objects.all(),
         to_field_name='name',
         to_field_name='name',
         required=False,
         required=False,
-        help_text="Rack's location (if any)"
+        help_text=_("Rack's location (if any)")
     )
     )
     rack = CSVModelChoiceField(
     rack = CSVModelChoiceField(
         queryset=Rack.objects.all(),
         queryset=Rack.objects.all(),
         to_field_name='name',
         to_field_name='name',
-        help_text='Rack'
+        help_text=_('Rack')
     )
     )
     units = SimpleArrayField(
     units = SimpleArrayField(
         base_field=forms.IntegerField(),
         base_field=forms.IntegerField(),
         required=True,
         required=True,
-        help_text='Comma-separated list of individual unit numbers'
+        help_text=_('Comma-separated list of individual unit numbers')
     )
     )
     tenant = CSVModelChoiceField(
     tenant = CSVModelChoiceField(
         queryset=Tenant.objects.all(),
         queryset=Tenant.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
-        help_text='Assigned tenant'
+        help_text=_('Assigned tenant')
     )
     )
 
 
     class Meta:
     class Meta:
@@ -275,7 +276,7 @@ class DeviceRoleCSVForm(NetBoxModelCSVForm):
         model = DeviceRole
         model = DeviceRole
         fields = ('name', 'slug', 'color', 'vm_role', 'description', 'tags')
         fields = ('name', 'slug', 'color', 'vm_role', 'description', 'tags')
         help_texts = {
         help_texts = {
-            'color': mark_safe('RGB color in hexadecimal (e.g. <code>00ff00</code>)'),
+            'color': mark_safe(_('RGB color in hexadecimal (e.g. <code>00ff00</code>)')),
         }
         }
 
 
 
 
@@ -285,7 +286,7 @@ class PlatformCSVForm(NetBoxModelCSVForm):
         queryset=Manufacturer.objects.all(),
         queryset=Manufacturer.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
-        help_text='Limit platform assignments to this manufacturer'
+        help_text=_('Limit platform assignments to this manufacturer')
     )
     )
 
 
     class Meta:
     class Meta:
@@ -297,45 +298,45 @@ class BaseDeviceCSVForm(NetBoxModelCSVForm):
     device_role = CSVModelChoiceField(
     device_role = CSVModelChoiceField(
         queryset=DeviceRole.objects.all(),
         queryset=DeviceRole.objects.all(),
         to_field_name='name',
         to_field_name='name',
-        help_text='Assigned role'
+        help_text=_('Assigned role')
     )
     )
     tenant = CSVModelChoiceField(
     tenant = CSVModelChoiceField(
         queryset=Tenant.objects.all(),
         queryset=Tenant.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
-        help_text='Assigned tenant'
+        help_text=_('Assigned tenant')
     )
     )
     manufacturer = CSVModelChoiceField(
     manufacturer = CSVModelChoiceField(
         queryset=Manufacturer.objects.all(),
         queryset=Manufacturer.objects.all(),
         to_field_name='name',
         to_field_name='name',
-        help_text='Device type manufacturer'
+        help_text=_('Device type manufacturer')
     )
     )
     device_type = CSVModelChoiceField(
     device_type = CSVModelChoiceField(
         queryset=DeviceType.objects.all(),
         queryset=DeviceType.objects.all(),
         to_field_name='model',
         to_field_name='model',
-        help_text='Device type model'
+        help_text=_('Device type model')
     )
     )
     platform = CSVModelChoiceField(
     platform = CSVModelChoiceField(
         queryset=Platform.objects.all(),
         queryset=Platform.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
-        help_text='Assigned platform'
+        help_text=_('Assigned platform')
     )
     )
     status = CSVChoiceField(
     status = CSVChoiceField(
         choices=DeviceStatusChoices,
         choices=DeviceStatusChoices,
-        help_text='Operational status'
+        help_text=_('Operational status')
     )
     )
     virtual_chassis = CSVModelChoiceField(
     virtual_chassis = CSVModelChoiceField(
         queryset=VirtualChassis.objects.all(),
         queryset=VirtualChassis.objects.all(),
         to_field_name='name',
         to_field_name='name',
         required=False,
         required=False,
-        help_text='Virtual chassis'
+        help_text=_('Virtual chassis')
     )
     )
     cluster = CSVModelChoiceField(
     cluster = CSVModelChoiceField(
         queryset=Cluster.objects.all(),
         queryset=Cluster.objects.all(),
         to_field_name='name',
         to_field_name='name',
         required=False,
         required=False,
-        help_text='Virtualization cluster'
+        help_text=_('Virtualization cluster')
     )
     )
 
 
     class Meta:
     class Meta:
@@ -360,29 +361,29 @@ class DeviceCSVForm(BaseDeviceCSVForm):
     site = CSVModelChoiceField(
     site = CSVModelChoiceField(
         queryset=Site.objects.all(),
         queryset=Site.objects.all(),
         to_field_name='name',
         to_field_name='name',
-        help_text='Assigned site'
+        help_text=_('Assigned site')
     )
     )
     location = CSVModelChoiceField(
     location = CSVModelChoiceField(
         queryset=Location.objects.all(),
         queryset=Location.objects.all(),
         to_field_name='name',
         to_field_name='name',
         required=False,
         required=False,
-        help_text="Assigned location (if any)"
+        help_text=_("Assigned location (if any)")
     )
     )
     rack = CSVModelChoiceField(
     rack = CSVModelChoiceField(
         queryset=Rack.objects.all(),
         queryset=Rack.objects.all(),
         to_field_name='name',
         to_field_name='name',
         required=False,
         required=False,
-        help_text="Assigned rack (if any)"
+        help_text=_("Assigned rack (if any)")
     )
     )
     face = CSVChoiceField(
     face = CSVChoiceField(
         choices=DeviceFaceChoices,
         choices=DeviceFaceChoices,
         required=False,
         required=False,
-        help_text='Mounted rack face'
+        help_text=_('Mounted rack face')
     )
     )
     airflow = CSVChoiceField(
     airflow = CSVChoiceField(
         choices=DeviceAirflowChoices,
         choices=DeviceAirflowChoices,
         required=False,
         required=False,
-        help_text='Airflow direction'
+        help_text=_('Airflow direction')
     )
     )
 
 
     class Meta(BaseDeviceCSVForm.Meta):
     class Meta(BaseDeviceCSVForm.Meta):
@@ -442,12 +443,12 @@ class ChildDeviceCSVForm(BaseDeviceCSVForm):
     parent = CSVModelChoiceField(
     parent = CSVModelChoiceField(
         queryset=Device.objects.all(),
         queryset=Device.objects.all(),
         to_field_name='name',
         to_field_name='name',
-        help_text='Parent device'
+        help_text=_('Parent device')
     )
     )
     device_bay = CSVModelChoiceField(
     device_bay = CSVModelChoiceField(
         queryset=DeviceBay.objects.all(),
         queryset=DeviceBay.objects.all(),
         to_field_name='name',
         to_field_name='name',
-        help_text='Device bay in which this device is installed'
+        help_text=_('Device bay in which this device is installed')
     )
     )
 
 
     class Meta(BaseDeviceCSVForm.Meta):
     class Meta(BaseDeviceCSVForm.Meta):
@@ -492,14 +493,14 @@ class ConsolePortCSVForm(NetBoxModelCSVForm):
     type = CSVChoiceField(
     type = CSVChoiceField(
         choices=ConsolePortTypeChoices,
         choices=ConsolePortTypeChoices,
         required=False,
         required=False,
-        help_text='Port type'
+        help_text=_('Port type')
     )
     )
     speed = CSVTypedChoiceField(
     speed = CSVTypedChoiceField(
         choices=ConsolePortSpeedChoices,
         choices=ConsolePortSpeedChoices,
         coerce=int,
         coerce=int,
         empty_value=None,
         empty_value=None,
         required=False,
         required=False,
-        help_text='Port speed in bps'
+        help_text=_('Port speed in bps')
     )
     )
 
 
     class Meta:
     class Meta:
@@ -515,14 +516,14 @@ class ConsoleServerPortCSVForm(NetBoxModelCSVForm):
     type = CSVChoiceField(
     type = CSVChoiceField(
         choices=ConsolePortTypeChoices,
         choices=ConsolePortTypeChoices,
         required=False,
         required=False,
-        help_text='Port type'
+        help_text=_('Port type')
     )
     )
     speed = CSVTypedChoiceField(
     speed = CSVTypedChoiceField(
         choices=ConsolePortSpeedChoices,
         choices=ConsolePortSpeedChoices,
         coerce=int,
         coerce=int,
         empty_value=None,
         empty_value=None,
         required=False,
         required=False,
-        help_text='Port speed in bps'
+        help_text=_('Port speed in bps')
     )
     )
 
 
     class Meta:
     class Meta:
@@ -538,7 +539,7 @@ class PowerPortCSVForm(NetBoxModelCSVForm):
     type = CSVChoiceField(
     type = CSVChoiceField(
         choices=PowerPortTypeChoices,
         choices=PowerPortTypeChoices,
         required=False,
         required=False,
-        help_text='Port type'
+        help_text=_('Port type')
     )
     )
 
 
     class Meta:
     class Meta:
@@ -556,18 +557,18 @@ class PowerOutletCSVForm(NetBoxModelCSVForm):
     type = CSVChoiceField(
     type = CSVChoiceField(
         choices=PowerOutletTypeChoices,
         choices=PowerOutletTypeChoices,
         required=False,
         required=False,
-        help_text='Outlet type'
+        help_text=_('Outlet type')
     )
     )
     power_port = CSVModelChoiceField(
     power_port = CSVModelChoiceField(
         queryset=PowerPort.objects.all(),
         queryset=PowerPort.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
-        help_text='Local power port which feeds this outlet'
+        help_text=_('Local power port which feeds this outlet')
     )
     )
     feed_leg = CSVChoiceField(
     feed_leg = CSVChoiceField(
         choices=PowerOutletFeedLegChoices,
         choices=PowerOutletFeedLegChoices,
         required=False,
         required=False,
-        help_text='Electrical phase (for three-phase circuits)'
+        help_text=_('Electrical phase (for three-phase circuits)')
     )
     )
 
 
     class Meta:
     class Meta:
@@ -606,23 +607,23 @@ class InterfaceCSVForm(NetBoxModelCSVForm):
         queryset=Interface.objects.all(),
         queryset=Interface.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
-        help_text='Parent interface'
+        help_text=_('Parent interface')
     )
     )
     bridge = CSVModelChoiceField(
     bridge = CSVModelChoiceField(
         queryset=Interface.objects.all(),
         queryset=Interface.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
-        help_text='Bridged interface'
+        help_text=_('Bridged interface')
     )
     )
     lag = CSVModelChoiceField(
     lag = CSVModelChoiceField(
         queryset=Interface.objects.all(),
         queryset=Interface.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
-        help_text='Parent LAG interface'
+        help_text=_('Parent LAG interface')
     )
     )
     type = CSVChoiceField(
     type = CSVChoiceField(
         choices=InterfaceTypeChoices,
         choices=InterfaceTypeChoices,
-        help_text='Physical medium'
+        help_text=_('Physical medium')
     )
     )
     duplex = CSVChoiceField(
     duplex = CSVChoiceField(
         choices=InterfaceDuplexChoices,
         choices=InterfaceDuplexChoices,
@@ -631,28 +632,28 @@ class InterfaceCSVForm(NetBoxModelCSVForm):
     poe_mode = CSVChoiceField(
     poe_mode = CSVChoiceField(
         choices=InterfacePoEModeChoices,
         choices=InterfacePoEModeChoices,
         required=False,
         required=False,
-        help_text='PoE mode'
+        help_text=_('PoE mode')
     )
     )
     poe_type = CSVChoiceField(
     poe_type = CSVChoiceField(
         choices=InterfacePoETypeChoices,
         choices=InterfacePoETypeChoices,
         required=False,
         required=False,
-        help_text='PoE type'
+        help_text=_('PoE type')
     )
     )
     mode = CSVChoiceField(
     mode = CSVChoiceField(
         choices=InterfaceModeChoices,
         choices=InterfaceModeChoices,
         required=False,
         required=False,
-        help_text='IEEE 802.1Q operational mode (for L2 interfaces)'
+        help_text=_('IEEE 802.1Q operational mode (for L2 interfaces)')
     )
     )
     vrf = CSVModelChoiceField(
     vrf = CSVModelChoiceField(
         queryset=VRF.objects.all(),
         queryset=VRF.objects.all(),
         required=False,
         required=False,
         to_field_name='rd',
         to_field_name='rd',
-        help_text='Assigned VRF'
+        help_text=_('Assigned VRF')
     )
     )
     rf_role = CSVChoiceField(
     rf_role = CSVChoiceField(
         choices=WirelessRoleChoices,
         choices=WirelessRoleChoices,
         required=False,
         required=False,
-        help_text='Wireless role (AP/station)'
+        help_text=_('Wireless role (AP/station)')
     )
     )
 
 
     class Meta:
     class Meta:
@@ -692,11 +693,11 @@ class FrontPortCSVForm(NetBoxModelCSVForm):
     rear_port = CSVModelChoiceField(
     rear_port = CSVModelChoiceField(
         queryset=RearPort.objects.all(),
         queryset=RearPort.objects.all(),
         to_field_name='name',
         to_field_name='name',
-        help_text='Corresponding rear port'
+        help_text=_('Corresponding rear port')
     )
     )
     type = CSVChoiceField(
     type = CSVChoiceField(
         choices=PortTypeChoices,
         choices=PortTypeChoices,
-        help_text='Physical medium classification'
+        help_text=_('Physical medium classification')
     )
     )
 
 
     class Meta:
     class Meta:
@@ -706,7 +707,7 @@ class FrontPortCSVForm(NetBoxModelCSVForm):
             'description', 'tags'
             'description', 'tags'
         )
         )
         help_texts = {
         help_texts = {
-            'rear_port_position': 'Mapped position on corresponding rear port',
+            'rear_port_position': _('Mapped position on corresponding rear port'),
         }
         }
 
 
     def __init__(self, *args, **kwargs):
     def __init__(self, *args, **kwargs):
@@ -738,7 +739,7 @@ class RearPortCSVForm(NetBoxModelCSVForm):
         to_field_name='name'
         to_field_name='name'
     )
     )
     type = CSVChoiceField(
     type = CSVChoiceField(
-        help_text='Physical medium classification',
+        help_text=_('Physical medium classification'),
         choices=PortTypeChoices,
         choices=PortTypeChoices,
     )
     )
 
 
@@ -746,7 +747,7 @@ class RearPortCSVForm(NetBoxModelCSVForm):
         model = RearPort
         model = RearPort
         fields = ('device', 'name', 'label', 'type', 'color', 'mark_connected', 'positions', 'description', 'tags')
         fields = ('device', 'name', 'label', 'type', 'color', 'mark_connected', 'positions', 'description', 'tags')
         help_texts = {
         help_texts = {
-            'positions': 'Number of front ports which may be mapped'
+            'positions': _('Number of front ports which may be mapped')
         }
         }
 
 
 
 
@@ -770,9 +771,9 @@ class DeviceBayCSVForm(NetBoxModelCSVForm):
         queryset=Device.objects.all(),
         queryset=Device.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
-        help_text='Child device installed within this bay',
+        help_text=_('Child device installed within this bay'),
         error_messages={
         error_messages={
-            'invalid_choice': 'Child device not found.',
+            'invalid_choice': _('Child device not found.'),
         }
         }
     )
     )
 
 
@@ -826,7 +827,7 @@ class InventoryItemCSVForm(NetBoxModelCSVForm):
         queryset=Device.objects.all(),
         queryset=Device.objects.all(),
         to_field_name='name',
         to_field_name='name',
         required=False,
         required=False,
-        help_text='Parent inventory item'
+        help_text=_('Parent inventory item')
     )
     )
 
 
     class Meta:
     class Meta:
@@ -863,7 +864,7 @@ class InventoryItemRoleCSVForm(NetBoxModelCSVForm):
         model = InventoryItemRole
         model = InventoryItemRole
         fields = ('name', 'slug', 'color', 'description')
         fields = ('name', 'slug', 'color', 'description')
         help_texts = {
         help_texts = {
-            'color': mark_safe('RGB color in hexadecimal (e.g. <code>00ff00</code>)'),
+            'color': mark_safe(_('RGB color in hexadecimal (e.g. <code>00ff00</code>)')),
         }
         }
 
 
 
 
@@ -876,53 +877,53 @@ class CableCSVForm(NetBoxModelCSVForm):
     side_a_device = CSVModelChoiceField(
     side_a_device = CSVModelChoiceField(
         queryset=Device.objects.all(),
         queryset=Device.objects.all(),
         to_field_name='name',
         to_field_name='name',
-        help_text='Side A device'
+        help_text=_('Side A device')
     )
     )
     side_a_type = CSVContentTypeField(
     side_a_type = CSVContentTypeField(
         queryset=ContentType.objects.all(),
         queryset=ContentType.objects.all(),
         limit_choices_to=CABLE_TERMINATION_MODELS,
         limit_choices_to=CABLE_TERMINATION_MODELS,
-        help_text='Side A type'
+        help_text=_('Side A type')
     )
     )
     side_a_name = forms.CharField(
     side_a_name = forms.CharField(
-        help_text='Side A component name'
+        help_text=_('Side A component name')
     )
     )
 
 
     # Termination B
     # Termination B
     side_b_device = CSVModelChoiceField(
     side_b_device = CSVModelChoiceField(
         queryset=Device.objects.all(),
         queryset=Device.objects.all(),
         to_field_name='name',
         to_field_name='name',
-        help_text='Side B device'
+        help_text=_('Side B device')
     )
     )
     side_b_type = CSVContentTypeField(
     side_b_type = CSVContentTypeField(
         queryset=ContentType.objects.all(),
         queryset=ContentType.objects.all(),
         limit_choices_to=CABLE_TERMINATION_MODELS,
         limit_choices_to=CABLE_TERMINATION_MODELS,
-        help_text='Side B type'
+        help_text=_('Side B type')
     )
     )
     side_b_name = forms.CharField(
     side_b_name = forms.CharField(
-        help_text='Side B component name'
+        help_text=_('Side B component name')
     )
     )
 
 
     # Cable attributes
     # Cable attributes
     status = CSVChoiceField(
     status = CSVChoiceField(
         choices=LinkStatusChoices,
         choices=LinkStatusChoices,
         required=False,
         required=False,
-        help_text='Connection status'
+        help_text=_('Connection status')
     )
     )
     type = CSVChoiceField(
     type = CSVChoiceField(
         choices=CableTypeChoices,
         choices=CableTypeChoices,
         required=False,
         required=False,
-        help_text='Physical medium classification'
+        help_text=_('Physical medium classification')
     )
     )
     tenant = CSVModelChoiceField(
     tenant = CSVModelChoiceField(
         queryset=Tenant.objects.all(),
         queryset=Tenant.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
-        help_text='Assigned tenant'
+        help_text=_('Assigned tenant')
     )
     )
     length_unit = CSVChoiceField(
     length_unit = CSVChoiceField(
         choices=CableLengthUnitChoices,
         choices=CableLengthUnitChoices,
         required=False,
         required=False,
-        help_text='Length unit'
+        help_text=_('Length unit')
     )
     )
 
 
     class Meta:
     class Meta:
@@ -932,7 +933,7 @@ class CableCSVForm(NetBoxModelCSVForm):
             'status', 'tenant', 'label', 'color', 'length', 'length_unit', 'description', 'comments', 'tags',
             'status', 'tenant', 'label', 'color', 'length', 'length_unit', 'description', 'comments', 'tags',
         ]
         ]
         help_texts = {
         help_texts = {
-            'color': mark_safe('RGB color in hexadecimal (e.g. <code>00ff00</code>)'),
+            'color': mark_safe(_('RGB color in hexadecimal (e.g. <code>00ff00</code>)')),
         }
         }
 
 
     def _clean_side(self, side):
     def _clean_side(self, side):
@@ -981,7 +982,7 @@ class VirtualChassisCSVForm(NetBoxModelCSVForm):
         queryset=Device.objects.all(),
         queryset=Device.objects.all(),
         to_field_name='name',
         to_field_name='name',
         required=False,
         required=False,
-        help_text='Master device'
+        help_text=_('Master device')
     )
     )
 
 
     class Meta:
     class Meta:
@@ -997,7 +998,7 @@ class PowerPanelCSVForm(NetBoxModelCSVForm):
     site = CSVModelChoiceField(
     site = CSVModelChoiceField(
         queryset=Site.objects.all(),
         queryset=Site.objects.all(),
         to_field_name='name',
         to_field_name='name',
-        help_text='Name of parent site'
+        help_text=_('Name of parent site')
     )
     )
     location = CSVModelChoiceField(
     location = CSVModelChoiceField(
         queryset=Location.objects.all(),
         queryset=Location.objects.all(),
@@ -1023,40 +1024,40 @@ class PowerFeedCSVForm(NetBoxModelCSVForm):
     site = CSVModelChoiceField(
     site = CSVModelChoiceField(
         queryset=Site.objects.all(),
         queryset=Site.objects.all(),
         to_field_name='name',
         to_field_name='name',
-        help_text='Assigned site'
+        help_text=_('Assigned site')
     )
     )
     power_panel = CSVModelChoiceField(
     power_panel = CSVModelChoiceField(
         queryset=PowerPanel.objects.all(),
         queryset=PowerPanel.objects.all(),
         to_field_name='name',
         to_field_name='name',
-        help_text='Upstream power panel'
+        help_text=_('Upstream power panel')
     )
     )
     location = CSVModelChoiceField(
     location = CSVModelChoiceField(
         queryset=Location.objects.all(),
         queryset=Location.objects.all(),
         to_field_name='name',
         to_field_name='name',
         required=False,
         required=False,
-        help_text="Rack's location (if any)"
+        help_text=_("Rack's location (if any)")
     )
     )
     rack = CSVModelChoiceField(
     rack = CSVModelChoiceField(
         queryset=Rack.objects.all(),
         queryset=Rack.objects.all(),
         to_field_name='name',
         to_field_name='name',
         required=False,
         required=False,
-        help_text='Rack'
+        help_text=_('Rack')
     )
     )
     status = CSVChoiceField(
     status = CSVChoiceField(
         choices=PowerFeedStatusChoices,
         choices=PowerFeedStatusChoices,
-        help_text='Operational status'
+        help_text=_('Operational status')
     )
     )
     type = CSVChoiceField(
     type = CSVChoiceField(
         choices=PowerFeedTypeChoices,
         choices=PowerFeedTypeChoices,
-        help_text='Primary or redundant'
+        help_text=_('Primary or redundant')
     )
     )
     supply = CSVChoiceField(
     supply = CSVChoiceField(
         choices=PowerFeedSupplyChoices,
         choices=PowerFeedSupplyChoices,
-        help_text='Supply type (AC/DC)'
+        help_text=_('Supply type (AC/DC)')
     )
     )
     phase = CSVChoiceField(
     phase = CSVChoiceField(
         choices=PowerFeedPhaseChoices,
         choices=PowerFeedPhaseChoices,
-        help_text='Single or three-phase'
+        help_text=_('Single or three-phase')
     )
     )
 
 
     class Meta:
     class Meta:

+ 3 - 2
netbox/dcim/forms/common.py

@@ -1,4 +1,5 @@
 from django import forms
 from django import forms
+from django.utils.translation import gettext as _
 
 
 from dcim.choices import *
 from dcim.choices import *
 from dcim.constants import *
 from dcim.constants import *
@@ -12,13 +13,13 @@ class InterfaceCommonForm(forms.Form):
     mac_address = forms.CharField(
     mac_address = forms.CharField(
         empty_value=None,
         empty_value=None,
         required=False,
         required=False,
-        label='MAC address'
+        label=_('MAC address')
     )
     )
     mtu = forms.IntegerField(
     mtu = forms.IntegerField(
         required=False,
         required=False,
         min_value=INTERFACE_MTU_MIN,
         min_value=INTERFACE_MTU_MIN,
         max_value=INTERFACE_MTU_MAX,
         max_value=INTERFACE_MTU_MAX,
-        label='MTU'
+        label=_('MTU')
     )
     )
 
 
     def clean(self):
     def clean(self):

+ 12 - 11
netbox/dcim/forms/connections.py

@@ -1,4 +1,5 @@
 from django import forms
 from django import forms
+from django.utils.translation import gettext as _
 
 
 from circuits.models import Circuit, CircuitTermination, Provider
 from circuits.models import Circuit, CircuitTermination, Provider
 from dcim.models import *
 from dcim.models import *
@@ -16,7 +17,7 @@ def get_cable_form(a_type, b_type):
 
 
                 attrs[f'termination_{cable_end}_region'] = DynamicModelChoiceField(
                 attrs[f'termination_{cable_end}_region'] = DynamicModelChoiceField(
                     queryset=Region.objects.all(),
                     queryset=Region.objects.all(),
-                    label='Region',
+                    label=_('Region'),
                     required=False,
                     required=False,
                     initial_params={
                     initial_params={
                         'sites': f'$termination_{cable_end}_site'
                         'sites': f'$termination_{cable_end}_site'
@@ -24,7 +25,7 @@ def get_cable_form(a_type, b_type):
                 )
                 )
                 attrs[f'termination_{cable_end}_sitegroup'] = DynamicModelChoiceField(
                 attrs[f'termination_{cable_end}_sitegroup'] = DynamicModelChoiceField(
                     queryset=SiteGroup.objects.all(),
                     queryset=SiteGroup.objects.all(),
-                    label='Site group',
+                    label=_('Site group'),
                     required=False,
                     required=False,
                     initial_params={
                     initial_params={
                         'sites': f'$termination_{cable_end}_site'
                         'sites': f'$termination_{cable_end}_site'
@@ -32,7 +33,7 @@ def get_cable_form(a_type, b_type):
                 )
                 )
                 attrs[f'termination_{cable_end}_site'] = DynamicModelChoiceField(
                 attrs[f'termination_{cable_end}_site'] = DynamicModelChoiceField(
                     queryset=Site.objects.all(),
                     queryset=Site.objects.all(),
-                    label='Site',
+                    label=_('Site'),
                     required=False,
                     required=False,
                     query_params={
                     query_params={
                         'region_id': f'$termination_{cable_end}_region',
                         'region_id': f'$termination_{cable_end}_region',
@@ -41,7 +42,7 @@ def get_cable_form(a_type, b_type):
                 )
                 )
                 attrs[f'termination_{cable_end}_location'] = DynamicModelChoiceField(
                 attrs[f'termination_{cable_end}_location'] = DynamicModelChoiceField(
                     queryset=Location.objects.all(),
                     queryset=Location.objects.all(),
-                    label='Location',
+                    label=_('Location'),
                     required=False,
                     required=False,
                     null_option='None',
                     null_option='None',
                     query_params={
                     query_params={
@@ -54,7 +55,7 @@ def get_cable_form(a_type, b_type):
 
 
                     attrs[f'termination_{cable_end}_rack'] = DynamicModelChoiceField(
                     attrs[f'termination_{cable_end}_rack'] = DynamicModelChoiceField(
                         queryset=Rack.objects.all(),
                         queryset=Rack.objects.all(),
-                        label='Rack',
+                        label=_('Rack'),
                         required=False,
                         required=False,
                         null_option='None',
                         null_option='None',
                         initial_params={
                         initial_params={
@@ -67,7 +68,7 @@ def get_cable_form(a_type, b_type):
                     )
                     )
                     attrs[f'termination_{cable_end}_device'] = DynamicModelChoiceField(
                     attrs[f'termination_{cable_end}_device'] = DynamicModelChoiceField(
                         queryset=Device.objects.all(),
                         queryset=Device.objects.all(),
-                        label='Device',
+                        label=_('Device'),
                         required=False,
                         required=False,
                         initial_params={
                         initial_params={
                             f'{term_cls._meta.model_name}s__in': f'${cable_end}_terminations'
                             f'{term_cls._meta.model_name}s__in': f'${cable_end}_terminations'
@@ -93,7 +94,7 @@ def get_cable_form(a_type, b_type):
 
 
                     attrs[f'termination_{cable_end}_powerpanel'] = DynamicModelChoiceField(
                     attrs[f'termination_{cable_end}_powerpanel'] = DynamicModelChoiceField(
                         queryset=PowerPanel.objects.all(),
                         queryset=PowerPanel.objects.all(),
-                        label='Power Panel',
+                        label=_('Power Panel'),
                         required=False,
                         required=False,
                         initial_params={
                         initial_params={
                             'powerfeeds__in': f'${cable_end}_terminations'
                             'powerfeeds__in': f'${cable_end}_terminations'
@@ -105,7 +106,7 @@ def get_cable_form(a_type, b_type):
                     )
                     )
                     attrs[f'{cable_end}_terminations'] = DynamicModelMultipleChoiceField(
                     attrs[f'{cable_end}_terminations'] = DynamicModelMultipleChoiceField(
                         queryset=term_cls.objects.all(),
                         queryset=term_cls.objects.all(),
-                        label='Power Feed',
+                        label=_('Power Feed'),
                         disabled_indicator='_occupied',
                         disabled_indicator='_occupied',
                         query_params={
                         query_params={
                             'power_panel_id': f'$termination_{cable_end}_powerpanel',
                             'power_panel_id': f'$termination_{cable_end}_powerpanel',
@@ -117,7 +118,7 @@ def get_cable_form(a_type, b_type):
 
 
                     attrs[f'termination_{cable_end}_provider'] = DynamicModelChoiceField(
                     attrs[f'termination_{cable_end}_provider'] = DynamicModelChoiceField(
                         queryset=Provider.objects.all(),
                         queryset=Provider.objects.all(),
-                        label='Provider',
+                        label=_('Provider'),
                         initial_params={
                         initial_params={
                             'circuits': f'$termination_{cable_end}_circuit'
                             'circuits': f'$termination_{cable_end}_circuit'
                         },
                         },
@@ -125,7 +126,7 @@ def get_cable_form(a_type, b_type):
                     )
                     )
                     attrs[f'termination_{cable_end}_circuit'] = DynamicModelChoiceField(
                     attrs[f'termination_{cable_end}_circuit'] = DynamicModelChoiceField(
                         queryset=Circuit.objects.all(),
                         queryset=Circuit.objects.all(),
-                        label='Circuit',
+                        label=_('Circuit'),
                         initial_params={
                         initial_params={
                             'terminations__in': f'${cable_end}_terminations'
                             'terminations__in': f'${cable_end}_terminations'
                         },
                         },
@@ -136,7 +137,7 @@ def get_cable_form(a_type, b_type):
                     )
                     )
                     attrs[f'{cable_end}_terminations'] = DynamicModelMultipleChoiceField(
                     attrs[f'{cable_end}_terminations'] = DynamicModelMultipleChoiceField(
                         queryset=term_cls.objects.all(),
                         queryset=term_cls.objects.all(),
-                        label='Side',
+                        label=_('Side'),
                         disabled_indicator='_occupied',
                         disabled_indicator='_occupied',
                         query_params={
                         query_params={
                             'circuit_id': f'$termination_{cable_end}_circuit',
                             'circuit_id': f'$termination_{cable_end}_circuit',

+ 39 - 39
netbox/dcim/forms/model_forms.py

@@ -1,7 +1,7 @@
 from django import forms
 from django import forms
-from django.utils.translation import gettext as _
 from django.contrib.auth.models import User
 from django.contrib.auth.models import User
 from django.contrib.contenttypes.models import ContentType
 from django.contrib.contenttypes.models import ContentType
+from django.utils.translation import gettext as _
 from timezone_field import TimeZoneFormField
 from timezone_field import TimeZoneFormField
 
 
 from dcim.choices import *
 from dcim.choices import *
@@ -163,14 +163,14 @@ class SiteForm(TenancyForm, NetBoxModelForm):
             'time_zone': StaticSelect(),
             'time_zone': StaticSelect(),
         }
         }
         help_texts = {
         help_texts = {
-            'name': "Full name of the site",
-            'facility': "Data center provider and facility (e.g. Equinix NY7)",
-            'time_zone': "Local time zone",
-            'description': "Short description (will appear in sites list)",
-            'physical_address': "Physical location of the building (e.g. for GPS)",
-            'shipping_address': "If different from the physical address",
-            'latitude': "Latitude in decimal format (xx.yyyyyy)",
-            'longitude': "Longitude in decimal format (xx.yyyyyy)"
+            'name': _("Full name of the site"),
+            'facility': _("Data center provider and facility (e.g. Equinix NY7)"),
+            'time_zone': _("Local time zone"),
+            'description': _("Short description (will appear in sites list)"),
+            'physical_address': _("Physical location of the building (e.g. for GPS)"),
+            'shipping_address': _("If different from the physical address"),
+            'latitude': _("Latitude in decimal format (xx.yyyyyy)"),
+            'longitude': _("Longitude in decimal format (xx.yyyyyy)")
         }
         }
 
 
 
 
@@ -282,10 +282,10 @@ class RackForm(TenancyForm, NetBoxModelForm):
             'outer_unit', 'mounting_depth', 'weight', 'weight_unit', 'description', 'comments', 'tags',
             'outer_unit', 'mounting_depth', 'weight', 'weight_unit', 'description', 'comments', 'tags',
         ]
         ]
         help_texts = {
         help_texts = {
-            'site': "The site at which the rack exists",
-            'name': "Organizational rack name",
-            'facility_id': "The unique rack ID assigned by the facility",
-            'u_height': "Height in rack units",
+            'site': _("The site at which the rack exists"),
+            'name': _("Organizational rack name"),
+            'facility_id': _("The unique rack ID assigned by the facility"),
+            'u_height': _("Height in rack units"),
         }
         }
         widgets = {
         widgets = {
             'status': StaticSelect(),
             'status': StaticSelect(),
@@ -335,7 +335,7 @@ class RackReservationForm(TenancyForm, NetBoxModelForm):
     )
     )
     units = NumericArrayField(
     units = NumericArrayField(
         base_field=forms.IntegerField(),
         base_field=forms.IntegerField(),
-        help_text="Comma-separated list of numeric unit IDs. A range may be specified using a hyphen."
+        help_text=_("Comma-separated list of numeric unit IDs. A range may be specified using a hyphen.")
     )
     )
     user = forms.ModelChoiceField(
     user = forms.ModelChoiceField(
         queryset=User.objects.order_by(
         queryset=User.objects.order_by(
@@ -519,7 +519,7 @@ class DeviceForm(TenancyForm, NetBoxModelForm):
     )
     )
     position = forms.DecimalField(
     position = forms.DecimalField(
         required=False,
         required=False,
-        help_text="The lowest-numbered unit occupied by the device",
+        help_text=_("The lowest-numbered unit occupied by the device"),
         widget=APISelect(
         widget=APISelect(
             api_url='/api/dcim/racks/{{rack}}/elevation/',
             api_url='/api/dcim/racks/{{rack}}/elevation/',
             attrs={
             attrs={
@@ -577,13 +577,13 @@ class DeviceForm(TenancyForm, NetBoxModelForm):
     )
     )
     vc_position = forms.IntegerField(
     vc_position = forms.IntegerField(
         required=False,
         required=False,
-        label='Position',
-        help_text="The position in the virtual chassis this device is identified by"
+        label=_('Position'),
+        help_text=_("The position in the virtual chassis this device is identified by")
     )
     )
     vc_priority = forms.IntegerField(
     vc_priority = forms.IntegerField(
         required=False,
         required=False,
-        label='Priority',
-        help_text="The priority of the device in the virtual chassis"
+        label=_('Priority'),
+        help_text=_("The priority of the device in the virtual chassis")
     )
     )
 
 
     class Meta:
     class Meta:
@@ -595,10 +595,10 @@ class DeviceForm(TenancyForm, NetBoxModelForm):
             'description', 'comments', 'tags', 'local_context_data'
             'description', 'comments', 'tags', 'local_context_data'
         ]
         ]
         help_texts = {
         help_texts = {
-            'device_role': "The function this device serves",
-            'serial': "Chassis serial number",
-            'local_context_data': "Local config context data overwrites all source contexts in the final rendered "
-                                  "config context",
+            'device_role': _("The function this device serves"),
+            'serial': _("Chassis serial number"),
+            'local_context_data': _("Local config context data overwrites all source contexts in the final rendered "
+                                    "config context"),
         }
         }
         widgets = {
         widgets = {
             'face': StaticSelect(),
             'face': StaticSelect(),
@@ -695,13 +695,13 @@ class ModuleForm(NetBoxModelForm):
     replicate_components = forms.BooleanField(
     replicate_components = forms.BooleanField(
         required=False,
         required=False,
         initial=True,
         initial=True,
-        help_text="Automatically populate components associated with this module type"
+        help_text=_("Automatically populate components associated with this module type")
     )
     )
 
 
     adopt_components = forms.BooleanField(
     adopt_components = forms.BooleanField(
         required=False,
         required=False,
         initial=False,
         initial=False,
-        help_text="Adopt already existing components"
+        help_text=_("Adopt already existing components")
     )
     )
 
 
     fieldsets = (
     fieldsets = (
@@ -1390,7 +1390,7 @@ class InterfaceForm(InterfaceCommonForm, ModularDeviceComponentForm):
     parent = DynamicModelChoiceField(
     parent = DynamicModelChoiceField(
         queryset=Interface.objects.all(),
         queryset=Interface.objects.all(),
         required=False,
         required=False,
-        label='Parent interface',
+        label=_('Parent interface'),
         query_params={
         query_params={
             'device_id': '$device',
             'device_id': '$device',
         }
         }
@@ -1398,7 +1398,7 @@ class InterfaceForm(InterfaceCommonForm, ModularDeviceComponentForm):
     bridge = DynamicModelChoiceField(
     bridge = DynamicModelChoiceField(
         queryset=Interface.objects.all(),
         queryset=Interface.objects.all(),
         required=False,
         required=False,
-        label='Bridged interface',
+        label=_('Bridged interface'),
         query_params={
         query_params={
             'device_id': '$device',
             'device_id': '$device',
         }
         }
@@ -1406,7 +1406,7 @@ class InterfaceForm(InterfaceCommonForm, ModularDeviceComponentForm):
     lag = DynamicModelChoiceField(
     lag = DynamicModelChoiceField(
         queryset=Interface.objects.all(),
         queryset=Interface.objects.all(),
         required=False,
         required=False,
-        label='LAG interface',
+        label=_('LAG interface'),
         query_params={
         query_params={
             'device_id': '$device',
             'device_id': '$device',
             'type': 'lag',
             'type': 'lag',
@@ -1415,12 +1415,12 @@ class InterfaceForm(InterfaceCommonForm, ModularDeviceComponentForm):
     wireless_lan_group = DynamicModelChoiceField(
     wireless_lan_group = DynamicModelChoiceField(
         queryset=WirelessLANGroup.objects.all(),
         queryset=WirelessLANGroup.objects.all(),
         required=False,
         required=False,
-        label='Wireless LAN group'
+        label=_('Wireless LAN group')
     )
     )
     wireless_lans = DynamicModelMultipleChoiceField(
     wireless_lans = DynamicModelMultipleChoiceField(
         queryset=WirelessLAN.objects.all(),
         queryset=WirelessLAN.objects.all(),
         required=False,
         required=False,
-        label='Wireless LANs',
+        label=_('Wireless LANs'),
         query_params={
         query_params={
             'group_id': '$wireless_lan_group',
             'group_id': '$wireless_lan_group',
         }
         }
@@ -1428,12 +1428,12 @@ class InterfaceForm(InterfaceCommonForm, ModularDeviceComponentForm):
     vlan_group = DynamicModelChoiceField(
     vlan_group = DynamicModelChoiceField(
         queryset=VLANGroup.objects.all(),
         queryset=VLANGroup.objects.all(),
         required=False,
         required=False,
-        label='VLAN group'
+        label=_('VLAN group')
     )
     )
     untagged_vlan = DynamicModelChoiceField(
     untagged_vlan = DynamicModelChoiceField(
         queryset=VLAN.objects.all(),
         queryset=VLAN.objects.all(),
         required=False,
         required=False,
-        label='Untagged VLAN',
+        label=_('Untagged VLAN'),
         query_params={
         query_params={
             'group_id': '$vlan_group',
             'group_id': '$vlan_group',
             'available_on_device': '$device',
             'available_on_device': '$device',
@@ -1442,7 +1442,7 @@ class InterfaceForm(InterfaceCommonForm, ModularDeviceComponentForm):
     tagged_vlans = DynamicModelMultipleChoiceField(
     tagged_vlans = DynamicModelMultipleChoiceField(
         queryset=VLAN.objects.all(),
         queryset=VLAN.objects.all(),
         required=False,
         required=False,
-        label='Tagged VLANs',
+        label=_('Tagged VLANs'),
         query_params={
         query_params={
             'group_id': '$vlan_group',
             'group_id': '$vlan_group',
             'available_on_device': '$device',
             'available_on_device': '$device',
@@ -1451,13 +1451,13 @@ class InterfaceForm(InterfaceCommonForm, ModularDeviceComponentForm):
     vrf = DynamicModelChoiceField(
     vrf = DynamicModelChoiceField(
         queryset=VRF.objects.all(),
         queryset=VRF.objects.all(),
         required=False,
         required=False,
-        label='VRF'
+        label=_('VRF')
     )
     )
 
 
     wwn = forms.CharField(
     wwn = forms.CharField(
         empty_value=None,
         empty_value=None,
         required=False,
         required=False,
-        label='WWN'
+        label=_('WWN')
     )
     )
 
 
     fieldsets = (
     fieldsets = (
@@ -1495,8 +1495,8 @@ class InterfaceForm(InterfaceCommonForm, ModularDeviceComponentForm):
         }
         }
         help_texts = {
         help_texts = {
             'mode': INTERFACE_MODE_HELP_TEXT,
             'mode': INTERFACE_MODE_HELP_TEXT,
-            'rf_channel_frequency': "Populated by selected channel (if set)",
-            'rf_channel_width': "Populated by selected channel (if set)",
+            'rf_channel_frequency': _("Populated by selected channel (if set)"),
+            'rf_channel_width': _("Populated by selected channel (if set)"),
         }
         }
 
 
 
 
@@ -1570,8 +1570,8 @@ class DeviceBayForm(DeviceComponentForm):
 class PopulateDeviceBayForm(BootstrapMixin, forms.Form):
 class PopulateDeviceBayForm(BootstrapMixin, forms.Form):
     installed_device = forms.ModelChoiceField(
     installed_device = forms.ModelChoiceField(
         queryset=Device.objects.all(),
         queryset=Device.objects.all(),
-        label='Child Device',
-        help_text="Child devices must first be created and assigned to the site/rack of the parent device.",
+        label=_('Child Device'),
+        help_text=_("Child devices must first be created and assigned to the site/rack of the parent device."),
         widget=StaticSelect(),
         widget=StaticSelect(),
     )
     )
 
 

+ 11 - 10
netbox/dcim/forms/object_create.py

@@ -1,4 +1,5 @@
 from django import forms
 from django import forms
+from django.utils.translation import gettext as _
 
 
 from dcim.models import *
 from dcim.models import *
 from netbox.forms import NetBoxModelForm
 from netbox.forms import NetBoxModelForm
@@ -39,7 +40,7 @@ class ComponentCreateForm(forms.Form):
     name = ExpandableNameField()
     name = ExpandableNameField()
     label = ExpandableNameField(
     label = ExpandableNameField(
         required=False,
         required=False,
-        help_text='Alphanumeric ranges are supported. (Must match the number of objects being created.)'
+        help_text=_('Alphanumeric ranges are supported. (Must match the number of objects being created.)')
     )
     )
 
 
     # Identify the fields which support replication (i.e. ExpandableNameFields). This is referenced by
     # Identify the fields which support replication (i.e. ExpandableNameFields). This is referenced by
@@ -97,8 +98,8 @@ class InterfaceTemplateCreateForm(ComponentCreateForm, model_forms.InterfaceTemp
 class FrontPortTemplateCreateForm(ComponentCreateForm, model_forms.FrontPortTemplateForm):
 class FrontPortTemplateCreateForm(ComponentCreateForm, model_forms.FrontPortTemplateForm):
     rear_port = forms.MultipleChoiceField(
     rear_port = forms.MultipleChoiceField(
         choices=[],
         choices=[],
-        label='Rear ports',
-        help_text='Select one rear port assignment for each front port being created.',
+        label=_('Rear ports'),
+        help_text=_('Select one rear port assignment for each front port being created.'),
     )
     )
 
 
     # Override fieldsets from FrontPortTemplateForm to omit rear_port_position
     # Override fieldsets from FrontPortTemplateForm to omit rear_port_position
@@ -166,9 +167,9 @@ class DeviceBayTemplateCreateForm(ComponentCreateForm, model_forms.DeviceBayTemp
 
 
 class ModuleBayTemplateCreateForm(ComponentCreateForm, model_forms.ModuleBayTemplateForm):
 class ModuleBayTemplateCreateForm(ComponentCreateForm, model_forms.ModuleBayTemplateForm):
     position = ExpandableNameField(
     position = ExpandableNameField(
-        label='Position',
+        label=_('Position'),
         required=False,
         required=False,
-        help_text='Alphanumeric ranges are supported. (Must match the number of objects being created.)'
+        help_text=_('Alphanumeric ranges are supported. (Must match the number of objects being created.)')
     )
     )
     replication_fields = ('name', 'label', 'position')
     replication_fields = ('name', 'label', 'position')
 
 
@@ -226,8 +227,8 @@ class InterfaceCreateForm(ComponentCreateForm, model_forms.InterfaceForm):
 class FrontPortCreateForm(ComponentCreateForm, model_forms.FrontPortForm):
 class FrontPortCreateForm(ComponentCreateForm, model_forms.FrontPortForm):
     rear_port = forms.MultipleChoiceField(
     rear_port = forms.MultipleChoiceField(
         choices=[],
         choices=[],
-        label='Rear ports',
-        help_text='Select one rear port assignment for each front port being created.',
+        label=_('Rear ports'),
+        help_text=_('Select one rear port assignment for each front port being created.'),
     )
     )
 
 
     # Override fieldsets from FrontPortForm to omit rear_port_position
     # Override fieldsets from FrontPortForm to omit rear_port_position
@@ -290,9 +291,9 @@ class DeviceBayCreateForm(ComponentCreateForm, model_forms.DeviceBayForm):
 
 
 class ModuleBayCreateForm(ComponentCreateForm, model_forms.ModuleBayForm):
 class ModuleBayCreateForm(ComponentCreateForm, model_forms.ModuleBayForm):
     position = ExpandableNameField(
     position = ExpandableNameField(
-        label='Position',
+        label=_('Position'),
         required=False,
         required=False,
-        help_text='Alphanumeric ranges are supported. (Must match the number of objects being created.)'
+        help_text=_('Alphanumeric ranges are supported. (Must match the number of objects being created.)')
     )
     )
     replication_fields = ('name', 'label', 'position')
     replication_fields = ('name', 'label', 'position')
 
 
@@ -352,7 +353,7 @@ class VirtualChassisCreateForm(NetBoxModelForm):
     initial_position = forms.IntegerField(
     initial_position = forms.IntegerField(
         initial=1,
         initial=1,
         required=False,
         required=False,
-        help_text='Position of the first member device. Increases by one for each additional member.'
+        help_text=_('Position of the first member device. Increases by one for each additional member.')
     )
     )
 
 
     class Meta:
     class Meta:

+ 3 - 2
netbox/dcim/forms/object_import.py

@@ -1,4 +1,5 @@
 from django import forms
 from django import forms
+from django.utils.translation import gettext as _
 
 
 from dcim.choices import InterfacePoEModeChoices, InterfacePoETypeChoices, InterfaceTypeChoices, PortTypeChoices
 from dcim.choices import InterfacePoEModeChoices, InterfacePoETypeChoices, InterfaceTypeChoices, PortTypeChoices
 from dcim.models import *
 from dcim.models import *
@@ -115,12 +116,12 @@ class InterfaceTemplateImportForm(ComponentTemplateImportForm):
     poe_mode = forms.ChoiceField(
     poe_mode = forms.ChoiceField(
         choices=InterfacePoEModeChoices,
         choices=InterfacePoEModeChoices,
         required=False,
         required=False,
-        label='PoE mode'
+        label=_('PoE mode')
     )
     )
     poe_type = forms.ChoiceField(
     poe_type = forms.ChoiceField(
         choices=InterfacePoETypeChoices,
         choices=InterfacePoETypeChoices,
         required=False,
         required=False,
-        label='PoE type'
+        label=_('PoE type')
     )
     )
 
 
     class Meta:
     class Meta:

+ 7 - 6
netbox/dcim/models/device_component_templates.py

@@ -3,6 +3,7 @@ from django.contrib.contenttypes.models import ContentType
 from django.core.exceptions import ValidationError
 from django.core.exceptions import ValidationError
 from django.core.validators import MaxValueValidator, MinValueValidator
 from django.core.validators import MaxValueValidator, MinValueValidator
 from django.db import models
 from django.db import models
+from django.utils.translation import gettext as _
 from mptt.models import MPTTModel, TreeForeignKey
 from mptt.models import MPTTModel, TreeForeignKey
 
 
 from dcim.choices import *
 from dcim.choices import *
@@ -52,7 +53,7 @@ class ComponentTemplateModel(WebhooksMixin, ChangeLoggedModel):
     label = models.CharField(
     label = models.CharField(
         max_length=64,
         max_length=64,
         blank=True,
         blank=True,
-        help_text="Physical label"
+        help_text=_("Physical label")
     )
     )
     description = models.CharField(
     description = models.CharField(
         max_length=200,
         max_length=200,
@@ -222,13 +223,13 @@ class PowerPortTemplate(ModularComponentTemplateModel):
         blank=True,
         blank=True,
         null=True,
         null=True,
         validators=[MinValueValidator(1)],
         validators=[MinValueValidator(1)],
-        help_text="Maximum power draw (watts)"
+        help_text=_("Maximum power draw (watts)")
     )
     )
     allocated_draw = models.PositiveSmallIntegerField(
     allocated_draw = models.PositiveSmallIntegerField(
         blank=True,
         blank=True,
         null=True,
         null=True,
         validators=[MinValueValidator(1)],
         validators=[MinValueValidator(1)],
-        help_text="Allocated power draw (watts)"
+        help_text=_("Allocated power draw (watts)")
     )
     )
 
 
     component_model = PowerPort
     component_model = PowerPort
@@ -283,7 +284,7 @@ class PowerOutletTemplate(ModularComponentTemplateModel):
         max_length=50,
         max_length=50,
         choices=PowerOutletFeedLegChoices,
         choices=PowerOutletFeedLegChoices,
         blank=True,
         blank=True,
-        help_text="Phase (for three-phase feeds)"
+        help_text=_("Phase (for three-phase feeds)")
     )
     )
 
 
     component_model = PowerOutlet
     component_model = PowerOutlet
@@ -526,7 +527,7 @@ class ModuleBayTemplate(ComponentTemplateModel):
     position = models.CharField(
     position = models.CharField(
         max_length=30,
         max_length=30,
         blank=True,
         blank=True,
-        help_text='Identifier to reference when renaming installed components'
+        help_text=_('Identifier to reference when renaming installed components')
     )
     )
 
 
     component_model = ModuleBay
     component_model = ModuleBay
@@ -621,7 +622,7 @@ class InventoryItemTemplate(MPTTModel, ComponentTemplateModel):
         max_length=50,
         max_length=50,
         verbose_name='Part ID',
         verbose_name='Part ID',
         blank=True,
         blank=True,
-        help_text='Manufacturer-assigned part identifier'
+        help_text=_('Manufacturer-assigned part identifier')
     )
     )
 
 
     objects = TreeManager()
     objects = TreeManager()

+ 18 - 17
netbox/dcim/models/device_components.py

@@ -7,6 +7,7 @@ from django.core.validators import MaxValueValidator, MinValueValidator
 from django.db import models
 from django.db import models
 from django.db.models import Sum
 from django.db.models import Sum
 from django.urls import reverse
 from django.urls import reverse
+from django.utils.translation import gettext as _
 from mptt.models import MPTTModel, TreeForeignKey
 from mptt.models import MPTTModel, TreeForeignKey
 
 
 from dcim.choices import *
 from dcim.choices import *
@@ -60,7 +61,7 @@ class ComponentModel(NetBoxModel):
     label = models.CharField(
     label = models.CharField(
         max_length=64,
         max_length=64,
         blank=True,
         blank=True,
-        help_text="Physical label"
+        help_text=_("Physical label")
     )
     )
     description = models.CharField(
     description = models.CharField(
         max_length=200,
         max_length=200,
@@ -129,7 +130,7 @@ class CabledObjectModel(models.Model):
     )
     )
     mark_connected = models.BooleanField(
     mark_connected = models.BooleanField(
         default=False,
         default=False,
-        help_text="Treat as if a cable is connected"
+        help_text=_("Treat as if a cable is connected")
     )
     )
 
 
     cable_terminations = GenericRelation(
     cable_terminations = GenericRelation(
@@ -261,13 +262,13 @@ class ConsolePort(ModularComponentModel, CabledObjectModel, PathEndpoint):
         max_length=50,
         max_length=50,
         choices=ConsolePortTypeChoices,
         choices=ConsolePortTypeChoices,
         blank=True,
         blank=True,
-        help_text='Physical port type'
+        help_text=_('Physical port type')
     )
     )
     speed = models.PositiveIntegerField(
     speed = models.PositiveIntegerField(
         choices=ConsolePortSpeedChoices,
         choices=ConsolePortSpeedChoices,
         blank=True,
         blank=True,
         null=True,
         null=True,
-        help_text='Port speed in bits per second'
+        help_text=_('Port speed in bits per second')
     )
     )
 
 
     clone_fields = ('device', 'module', 'type', 'speed')
     clone_fields = ('device', 'module', 'type', 'speed')
@@ -284,13 +285,13 @@ class ConsoleServerPort(ModularComponentModel, CabledObjectModel, PathEndpoint):
         max_length=50,
         max_length=50,
         choices=ConsolePortTypeChoices,
         choices=ConsolePortTypeChoices,
         blank=True,
         blank=True,
-        help_text='Physical port type'
+        help_text=_('Physical port type')
     )
     )
     speed = models.PositiveIntegerField(
     speed = models.PositiveIntegerField(
         choices=ConsolePortSpeedChoices,
         choices=ConsolePortSpeedChoices,
         blank=True,
         blank=True,
         null=True,
         null=True,
-        help_text='Port speed in bits per second'
+        help_text=_('Port speed in bits per second')
     )
     )
 
 
     clone_fields = ('device', 'module', 'type', 'speed')
     clone_fields = ('device', 'module', 'type', 'speed')
@@ -311,19 +312,19 @@ class PowerPort(ModularComponentModel, CabledObjectModel, PathEndpoint):
         max_length=50,
         max_length=50,
         choices=PowerPortTypeChoices,
         choices=PowerPortTypeChoices,
         blank=True,
         blank=True,
-        help_text='Physical port type'
+        help_text=_('Physical port type')
     )
     )
     maximum_draw = models.PositiveSmallIntegerField(
     maximum_draw = models.PositiveSmallIntegerField(
         blank=True,
         blank=True,
         null=True,
         null=True,
         validators=[MinValueValidator(1)],
         validators=[MinValueValidator(1)],
-        help_text="Maximum power draw (watts)"
+        help_text=_("Maximum power draw (watts)")
     )
     )
     allocated_draw = models.PositiveSmallIntegerField(
     allocated_draw = models.PositiveSmallIntegerField(
         blank=True,
         blank=True,
         null=True,
         null=True,
         validators=[MinValueValidator(1)],
         validators=[MinValueValidator(1)],
-        help_text="Allocated power draw (watts)"
+        help_text=_("Allocated power draw (watts)")
     )
     )
 
 
     clone_fields = ('device', 'module', 'maximum_draw', 'allocated_draw')
     clone_fields = ('device', 'module', 'maximum_draw', 'allocated_draw')
@@ -420,7 +421,7 @@ class PowerOutlet(ModularComponentModel, CabledObjectModel, PathEndpoint):
         max_length=50,
         max_length=50,
         choices=PowerOutletTypeChoices,
         choices=PowerOutletTypeChoices,
         blank=True,
         blank=True,
-        help_text='Physical port type'
+        help_text=_('Physical port type')
     )
     )
     power_port = models.ForeignKey(
     power_port = models.ForeignKey(
         to='dcim.PowerPort',
         to='dcim.PowerPort',
@@ -433,7 +434,7 @@ class PowerOutlet(ModularComponentModel, CabledObjectModel, PathEndpoint):
         max_length=50,
         max_length=50,
         choices=PowerOutletFeedLegChoices,
         choices=PowerOutletFeedLegChoices,
         blank=True,
         blank=True,
-        help_text="Phase (for three-phase feeds)"
+        help_text=_("Phase (for three-phase feeds)")
     )
     )
 
 
     clone_fields = ('device', 'module', 'type', 'power_port', 'feed_leg')
     clone_fields = ('device', 'module', 'type', 'power_port', 'feed_leg')
@@ -550,7 +551,7 @@ class Interface(ModularComponentModel, BaseInterface, CabledObjectModel, PathEnd
     mgmt_only = models.BooleanField(
     mgmt_only = models.BooleanField(
         default=False,
         default=False,
         verbose_name='Management only',
         verbose_name='Management only',
-        help_text='This interface is used only for out-of-band management'
+        help_text=_('This interface is used only for out-of-band management')
     )
     )
     speed = models.PositiveIntegerField(
     speed = models.PositiveIntegerField(
         blank=True,
         blank=True,
@@ -567,7 +568,7 @@ class Interface(ModularComponentModel, BaseInterface, CabledObjectModel, PathEnd
         null=True,
         null=True,
         blank=True,
         blank=True,
         verbose_name='WWN',
         verbose_name='WWN',
-        help_text='64-bit World Wide Name'
+        help_text=_('64-bit World Wide Name')
     )
     )
     rf_role = models.CharField(
     rf_role = models.CharField(
         max_length=30,
         max_length=30,
@@ -970,7 +971,7 @@ class ModuleBay(ComponentModel):
     position = models.CharField(
     position = models.CharField(
         max_length=30,
         max_length=30,
         blank=True,
         blank=True,
-        help_text='Identifier to reference when renaming installed components'
+        help_text=_('Identifier to reference when renaming installed components')
     )
     )
 
 
     clone_fields = ('device',)
     clone_fields = ('device',)
@@ -1084,7 +1085,7 @@ class InventoryItem(MPTTModel, ComponentModel):
         max_length=50,
         max_length=50,
         verbose_name='Part ID',
         verbose_name='Part ID',
         blank=True,
         blank=True,
-        help_text='Manufacturer-assigned part identifier'
+        help_text=_('Manufacturer-assigned part identifier')
     )
     )
     serial = models.CharField(
     serial = models.CharField(
         max_length=50,
         max_length=50,
@@ -1097,11 +1098,11 @@ class InventoryItem(MPTTModel, ComponentModel):
         blank=True,
         blank=True,
         null=True,
         null=True,
         verbose_name='Asset tag',
         verbose_name='Asset tag',
-        help_text='A unique tag used to identify this item'
+        help_text=_('A unique tag used to identify this item')
     )
     )
     discovered = models.BooleanField(
     discovered = models.BooleanField(
         default=False,
         default=False,
-        help_text='This item was automatically discovered'
+        help_text=_('This item was automatically discovered')
     )
     )
 
 
     objects = TreeManager()
     objects = TreeManager()

+ 13 - 12
netbox/dcim/models/devices.py

@@ -12,6 +12,7 @@ from django.db.models import F, ProtectedError
 from django.db.models.functions import Lower
 from django.db.models.functions import Lower
 from django.urls import reverse
 from django.urls import reverse
 from django.utils.safestring import mark_safe
 from django.utils.safestring import mark_safe
+from django.utils.translation import gettext as _
 
 
 from dcim.choices import *
 from dcim.choices import *
 from dcim.constants import *
 from dcim.constants import *
@@ -84,7 +85,7 @@ class DeviceType(PrimaryModel, WeightMixin):
     part_number = models.CharField(
     part_number = models.CharField(
         max_length=50,
         max_length=50,
         blank=True,
         blank=True,
-        help_text='Discrete part number (optional)'
+        help_text=_('Discrete part number (optional)')
     )
     )
     u_height = models.DecimalField(
     u_height = models.DecimalField(
         max_digits=4,
         max_digits=4,
@@ -95,15 +96,15 @@ class DeviceType(PrimaryModel, WeightMixin):
     is_full_depth = models.BooleanField(
     is_full_depth = models.BooleanField(
         default=True,
         default=True,
         verbose_name='Is full depth',
         verbose_name='Is full depth',
-        help_text='Device consumes both front and rear rack faces'
+        help_text=_('Device consumes both front and rear rack faces')
     )
     )
     subdevice_role = models.CharField(
     subdevice_role = models.CharField(
         max_length=50,
         max_length=50,
         choices=SubdeviceRoleChoices,
         choices=SubdeviceRoleChoices,
         blank=True,
         blank=True,
         verbose_name='Parent/child status',
         verbose_name='Parent/child status',
-        help_text='Parent devices house child devices in device bays. Leave blank '
-                  'if this device type is neither a parent nor a child.'
+        help_text=_('Parent devices house child devices in device bays. Leave blank '
+                    'if this device type is neither a parent nor a child.')
     )
     )
     airflow = models.CharField(
     airflow = models.CharField(
         max_length=50,
         max_length=50,
@@ -314,7 +315,7 @@ class ModuleType(PrimaryModel, WeightMixin):
     part_number = models.CharField(
     part_number = models.CharField(
         max_length=50,
         max_length=50,
         blank=True,
         blank=True,
-        help_text='Discrete part number (optional)'
+        help_text=_('Discrete part number (optional)')
     )
     )
 
 
     # Generic relations
     # Generic relations
@@ -400,7 +401,7 @@ class DeviceRole(OrganizationalModel):
     vm_role = models.BooleanField(
     vm_role = models.BooleanField(
         default=True,
         default=True,
         verbose_name='VM Role',
         verbose_name='VM Role',
-        help_text='Virtual machines may be assigned to this role'
+        help_text=_('Virtual machines may be assigned to this role')
     )
     )
 
 
     def get_absolute_url(self):
     def get_absolute_url(self):
@@ -419,19 +420,19 @@ class Platform(OrganizationalModel):
         related_name='platforms',
         related_name='platforms',
         blank=True,
         blank=True,
         null=True,
         null=True,
-        help_text='Optionally limit this platform to devices of a certain manufacturer'
+        help_text=_('Optionally limit this platform to devices of a certain manufacturer')
     )
     )
     napalm_driver = models.CharField(
     napalm_driver = models.CharField(
         max_length=50,
         max_length=50,
         blank=True,
         blank=True,
         verbose_name='NAPALM driver',
         verbose_name='NAPALM driver',
-        help_text='The name of the NAPALM driver to use when interacting with devices'
+        help_text=_('The name of the NAPALM driver to use when interacting with devices')
     )
     )
     napalm_args = models.JSONField(
     napalm_args = models.JSONField(
         blank=True,
         blank=True,
         null=True,
         null=True,
         verbose_name='NAPALM arguments',
         verbose_name='NAPALM arguments',
-        help_text='Additional arguments to pass when initiating the NAPALM driver (JSON format)'
+        help_text=_('Additional arguments to pass when initiating the NAPALM driver (JSON format)')
     )
     )
 
 
     def get_absolute_url(self):
     def get_absolute_url(self):
@@ -496,7 +497,7 @@ class Device(PrimaryModel, ConfigContextModel):
         null=True,
         null=True,
         unique=True,
         unique=True,
         verbose_name='Asset tag',
         verbose_name='Asset tag',
-        help_text='A unique tag used to identify this device'
+        help_text=_('A unique tag used to identify this device')
     )
     )
     site = models.ForeignKey(
     site = models.ForeignKey(
         to='dcim.Site',
         to='dcim.Site',
@@ -524,7 +525,7 @@ class Device(PrimaryModel, ConfigContextModel):
         null=True,
         null=True,
         validators=[MinValueValidator(1), MaxValueValidator(99.5)],
         validators=[MinValueValidator(1), MaxValueValidator(99.5)],
         verbose_name='Position (U)',
         verbose_name='Position (U)',
-        help_text='The lowest-numbered unit occupied by the device'
+        help_text=_('The lowest-numbered unit occupied by the device')
     )
     )
     face = models.CharField(
     face = models.CharField(
         max_length=50,
         max_length=50,
@@ -929,7 +930,7 @@ class Module(PrimaryModel, ConfigContextModel):
         null=True,
         null=True,
         unique=True,
         unique=True,
         verbose_name='Asset tag',
         verbose_name='Asset tag',
-        help_text='A unique tag used to identify this device'
+        help_text=_('A unique tag used to identify this device')
     )
     )
 
 
     clone_fields = ('device', 'module_type')
     clone_fields = ('device', 'module_type')

+ 2 - 1
netbox/dcim/models/power.py

@@ -4,6 +4,7 @@ from django.core.exceptions import ValidationError
 from django.core.validators import MaxValueValidator, MinValueValidator
 from django.core.validators import MaxValueValidator, MinValueValidator
 from django.db import models
 from django.db import models
 from django.urls import reverse
 from django.urls import reverse
+from django.utils.translation import gettext as _
 
 
 from dcim.choices import *
 from dcim.choices import *
 from netbox.config import ConfigItem
 from netbox.config import ConfigItem
@@ -125,7 +126,7 @@ class PowerFeed(PrimaryModel, PathEndpoint, CabledObjectModel):
     max_utilization = models.PositiveSmallIntegerField(
     max_utilization = models.PositiveSmallIntegerField(
         validators=[MinValueValidator(1), MaxValueValidator(100)],
         validators=[MinValueValidator(1), MaxValueValidator(100)],
         default=ConfigItem('POWERFEED_DEFAULT_MAX_UTILIZATION'),
         default=ConfigItem('POWERFEED_DEFAULT_MAX_UTILIZATION'),
-        help_text="Maximum permissible draw (percentage)"
+        help_text=_("Maximum permissible draw (percentage)")
     )
     )
     available_power = models.PositiveIntegerField(
     available_power = models.PositiveIntegerField(
         default=0,
         default=0,

+ 11 - 10
netbox/dcim/models/racks.py

@@ -10,6 +10,7 @@ from django.core.validators import MaxValueValidator, MinValueValidator
 from django.db import models
 from django.db import models
 from django.db.models import Count
 from django.db.models import Count
 from django.urls import reverse
 from django.urls import reverse
+from django.utils.translation import gettext as _
 
 
 from dcim.choices import *
 from dcim.choices import *
 from dcim.constants import *
 from dcim.constants import *
@@ -64,7 +65,7 @@ class Rack(PrimaryModel, WeightMixin):
         blank=True,
         blank=True,
         null=True,
         null=True,
         verbose_name='Facility ID',
         verbose_name='Facility ID',
-        help_text='Locally-assigned identifier'
+        help_text=_('Locally-assigned identifier')
     )
     )
     site = models.ForeignKey(
     site = models.ForeignKey(
         to='dcim.Site',
         to='dcim.Site',
@@ -96,7 +97,7 @@ class Rack(PrimaryModel, WeightMixin):
         related_name='racks',
         related_name='racks',
         blank=True,
         blank=True,
         null=True,
         null=True,
-        help_text='Functional role'
+        help_text=_('Functional role')
     )
     )
     serial = models.CharField(
     serial = models.CharField(
         max_length=50,
         max_length=50,
@@ -109,7 +110,7 @@ class Rack(PrimaryModel, WeightMixin):
         null=True,
         null=True,
         unique=True,
         unique=True,
         verbose_name='Asset tag',
         verbose_name='Asset tag',
-        help_text='A unique tag used to identify this rack'
+        help_text=_('A unique tag used to identify this rack')
     )
     )
     type = models.CharField(
     type = models.CharField(
         choices=RackTypeChoices,
         choices=RackTypeChoices,
@@ -121,28 +122,28 @@ class Rack(PrimaryModel, WeightMixin):
         choices=RackWidthChoices,
         choices=RackWidthChoices,
         default=RackWidthChoices.WIDTH_19IN,
         default=RackWidthChoices.WIDTH_19IN,
         verbose_name='Width',
         verbose_name='Width',
-        help_text='Rail-to-rail width'
+        help_text=_('Rail-to-rail width')
     )
     )
     u_height = models.PositiveSmallIntegerField(
     u_height = models.PositiveSmallIntegerField(
         default=RACK_U_HEIGHT_DEFAULT,
         default=RACK_U_HEIGHT_DEFAULT,
         verbose_name='Height (U)',
         verbose_name='Height (U)',
         validators=[MinValueValidator(1), MaxValueValidator(100)],
         validators=[MinValueValidator(1), MaxValueValidator(100)],
-        help_text='Height in rack units'
+        help_text=_('Height in rack units')
     )
     )
     desc_units = models.BooleanField(
     desc_units = models.BooleanField(
         default=False,
         default=False,
         verbose_name='Descending units',
         verbose_name='Descending units',
-        help_text='Units are numbered top-to-bottom'
+        help_text=_('Units are numbered top-to-bottom')
     )
     )
     outer_width = models.PositiveSmallIntegerField(
     outer_width = models.PositiveSmallIntegerField(
         blank=True,
         blank=True,
         null=True,
         null=True,
-        help_text='Outer dimension of rack (width)'
+        help_text=_('Outer dimension of rack (width)')
     )
     )
     outer_depth = models.PositiveSmallIntegerField(
     outer_depth = models.PositiveSmallIntegerField(
         blank=True,
         blank=True,
         null=True,
         null=True,
-        help_text='Outer dimension of rack (depth)'
+        help_text=_('Outer dimension of rack (depth)')
     )
     )
     outer_unit = models.CharField(
     outer_unit = models.CharField(
         max_length=50,
         max_length=50,
@@ -153,8 +154,8 @@ class Rack(PrimaryModel, WeightMixin):
         blank=True,
         blank=True,
         null=True,
         null=True,
         help_text=(
         help_text=(
-            'Maximum depth of a mounted device, in millimeters. For four-post racks, this is the '
-            'distance between the front and rear rails.'
+            _('Maximum depth of a mounted device, in millimeters. For four-post racks, this is the '
+              'distance between the front and rear rails.')
         )
         )
     )
     )
 
 

+ 4 - 3
netbox/dcim/models/sites.py

@@ -2,6 +2,7 @@ from django.contrib.contenttypes.fields import GenericRelation
 from django.core.exceptions import ValidationError
 from django.core.exceptions import ValidationError
 from django.db import models
 from django.db import models
 from django.urls import reverse
 from django.urls import reverse
+from django.utils.translation import gettext as _
 from timezone_field import TimeZoneField
 from timezone_field import TimeZoneField
 
 
 from dcim.choices import *
 from dcim.choices import *
@@ -178,7 +179,7 @@ class Site(PrimaryModel):
     facility = models.CharField(
     facility = models.CharField(
         max_length=50,
         max_length=50,
         blank=True,
         blank=True,
-        help_text='Local facility ID or description'
+        help_text=_('Local facility ID or description')
     )
     )
     asns = models.ManyToManyField(
     asns = models.ManyToManyField(
         to='ipam.ASN',
         to='ipam.ASN',
@@ -201,14 +202,14 @@ class Site(PrimaryModel):
         decimal_places=6,
         decimal_places=6,
         blank=True,
         blank=True,
         null=True,
         null=True,
-        help_text='GPS coordinate (latitude)'
+        help_text=_('GPS coordinate (latitude)')
     )
     )
     longitude = models.DecimalField(
     longitude = models.DecimalField(
         max_digits=9,
         max_digits=9,
         decimal_places=6,
         decimal_places=6,
         blank=True,
         blank=True,
         null=True,
         null=True,
-        help_text='GPS coordinate (longitude)'
+        help_text=_('GPS coordinate (longitude)')
     )
     )
 
 
     # Generic relations
     # Generic relations

+ 43 - 42
netbox/extras/filtersets.py

@@ -2,6 +2,7 @@ import django_filters
 from django.contrib.auth.models import User
 from django.contrib.auth.models import User
 from django.contrib.contenttypes.models import ContentType
 from django.contrib.contenttypes.models import ContentType
 from django.db.models import Q
 from django.db.models import Q
+from django.utils.translation import gettext as _
 
 
 from dcim.models import DeviceRole, DeviceType, Location, Platform, Region, Site, SiteGroup
 from dcim.models import DeviceRole, DeviceType, Location, Platform, Region, Site, SiteGroup
 from netbox.filtersets import BaseFilterSet, ChangeLoggedModelFilterSet, NetBoxModelFilterSet
 from netbox.filtersets import BaseFilterSet, ChangeLoggedModelFilterSet, NetBoxModelFilterSet
@@ -32,7 +33,7 @@ __all__ = (
 class WebhookFilterSet(BaseFilterSet):
 class WebhookFilterSet(BaseFilterSet):
     q = django_filters.CharFilter(
     q = django_filters.CharFilter(
         method='search',
         method='search',
-        label='Search',
+        label=_('Search'),
     )
     )
     content_type_id = MultiValueNumberFilter(
     content_type_id = MultiValueNumberFilter(
         field_name='content_types__id'
         field_name='content_types__id'
@@ -61,7 +62,7 @@ class WebhookFilterSet(BaseFilterSet):
 class CustomFieldFilterSet(BaseFilterSet):
 class CustomFieldFilterSet(BaseFilterSet):
     q = django_filters.CharFilter(
     q = django_filters.CharFilter(
         method='search',
         method='search',
-        label='Search',
+        label=_('Search'),
     )
     )
     type = django_filters.MultipleChoiceFilter(
     type = django_filters.MultipleChoiceFilter(
         choices=CustomFieldTypeChoices
         choices=CustomFieldTypeChoices
@@ -92,7 +93,7 @@ class CustomFieldFilterSet(BaseFilterSet):
 class CustomLinkFilterSet(BaseFilterSet):
 class CustomLinkFilterSet(BaseFilterSet):
     q = django_filters.CharFilter(
     q = django_filters.CharFilter(
         method='search',
         method='search',
-        label='Search',
+        label=_('Search'),
     )
     )
     content_type_id = MultiValueNumberFilter(
     content_type_id = MultiValueNumberFilter(
         field_name='content_types__id'
         field_name='content_types__id'
@@ -119,7 +120,7 @@ class CustomLinkFilterSet(BaseFilterSet):
 class ExportTemplateFilterSet(BaseFilterSet):
 class ExportTemplateFilterSet(BaseFilterSet):
     q = django_filters.CharFilter(
     q = django_filters.CharFilter(
         method='search',
         method='search',
-        label='Search',
+        label=_('Search'),
     )
     )
     content_type_id = MultiValueNumberFilter(
     content_type_id = MultiValueNumberFilter(
         field_name='content_types__id'
         field_name='content_types__id'
@@ -142,7 +143,7 @@ class ExportTemplateFilterSet(BaseFilterSet):
 class SavedFilterFilterSet(BaseFilterSet):
 class SavedFilterFilterSet(BaseFilterSet):
     q = django_filters.CharFilter(
     q = django_filters.CharFilter(
         method='search',
         method='search',
-        label='Search',
+        label=_('Search'),
     )
     )
     content_type_id = MultiValueNumberFilter(
     content_type_id = MultiValueNumberFilter(
         field_name='content_types__id'
         field_name='content_types__id'
@@ -150,13 +151,13 @@ class SavedFilterFilterSet(BaseFilterSet):
     content_types = ContentTypeFilter()
     content_types = ContentTypeFilter()
     user_id = django_filters.ModelMultipleChoiceFilter(
     user_id = django_filters.ModelMultipleChoiceFilter(
         queryset=User.objects.all(),
         queryset=User.objects.all(),
-        label='User (ID)',
+        label=_('User (ID)'),
     )
     )
     user = django_filters.ModelMultipleChoiceFilter(
     user = django_filters.ModelMultipleChoiceFilter(
         field_name='user__username',
         field_name='user__username',
         queryset=User.objects.all(),
         queryset=User.objects.all(),
         to_field_name='username',
         to_field_name='username',
-        label='User (name)',
+        label=_('User (name)'),
     )
     )
     usable = django_filters.BooleanFilter(
     usable = django_filters.BooleanFilter(
         method='_usable'
         method='_usable'
@@ -191,7 +192,7 @@ class SavedFilterFilterSet(BaseFilterSet):
 class ImageAttachmentFilterSet(BaseFilterSet):
 class ImageAttachmentFilterSet(BaseFilterSet):
     q = django_filters.CharFilter(
     q = django_filters.CharFilter(
         method='search',
         method='search',
-        label='Search',
+        label=_('Search'),
     )
     )
     created = django_filters.DateTimeFilter()
     created = django_filters.DateTimeFilter()
     content_type = ContentTypeFilter()
     content_type = ContentTypeFilter()
@@ -211,13 +212,13 @@ class JournalEntryFilterSet(NetBoxModelFilterSet):
     assigned_object_type = ContentTypeFilter()
     assigned_object_type = ContentTypeFilter()
     created_by_id = django_filters.ModelMultipleChoiceFilter(
     created_by_id = django_filters.ModelMultipleChoiceFilter(
         queryset=User.objects.all(),
         queryset=User.objects.all(),
-        label='User (ID)',
+        label=_('User (ID)'),
     )
     )
     created_by = django_filters.ModelMultipleChoiceFilter(
     created_by = django_filters.ModelMultipleChoiceFilter(
         field_name='created_by__username',
         field_name='created_by__username',
         queryset=User.objects.all(),
         queryset=User.objects.all(),
         to_field_name='username',
         to_field_name='username',
-        label='User (name)',
+        label=_('User (name)'),
     )
     )
     kind = django_filters.MultipleChoiceFilter(
     kind = django_filters.MultipleChoiceFilter(
         choices=JournalEntryKindChoices
         choices=JournalEntryKindChoices
@@ -236,7 +237,7 @@ class JournalEntryFilterSet(NetBoxModelFilterSet):
 class TagFilterSet(ChangeLoggedModelFilterSet):
 class TagFilterSet(ChangeLoggedModelFilterSet):
     q = django_filters.CharFilter(
     q = django_filters.CharFilter(
         method='search',
         method='search',
-        label='Search',
+        label=_('Search'),
     )
     )
     content_type = MultiValueCharFilter(
     content_type = MultiValueCharFilter(
         method='_content_type'
         method='_content_type'
@@ -288,138 +289,138 @@ class TagFilterSet(ChangeLoggedModelFilterSet):
 class ConfigContextFilterSet(ChangeLoggedModelFilterSet):
 class ConfigContextFilterSet(ChangeLoggedModelFilterSet):
     q = django_filters.CharFilter(
     q = django_filters.CharFilter(
         method='search',
         method='search',
-        label='Search',
+        label=_('Search'),
     )
     )
     region_id = django_filters.ModelMultipleChoiceFilter(
     region_id = django_filters.ModelMultipleChoiceFilter(
         field_name='regions',
         field_name='regions',
         queryset=Region.objects.all(),
         queryset=Region.objects.all(),
-        label='Region',
+        label=_('Region'),
     )
     )
     region = django_filters.ModelMultipleChoiceFilter(
     region = django_filters.ModelMultipleChoiceFilter(
         field_name='regions__slug',
         field_name='regions__slug',
         queryset=Region.objects.all(),
         queryset=Region.objects.all(),
         to_field_name='slug',
         to_field_name='slug',
-        label='Region (slug)',
+        label=_('Region (slug)'),
     )
     )
     site_group = django_filters.ModelMultipleChoiceFilter(
     site_group = django_filters.ModelMultipleChoiceFilter(
         field_name='site_groups__slug',
         field_name='site_groups__slug',
         queryset=SiteGroup.objects.all(),
         queryset=SiteGroup.objects.all(),
         to_field_name='slug',
         to_field_name='slug',
-        label='Site group (slug)',
+        label=_('Site group (slug)'),
     )
     )
     site_group_id = django_filters.ModelMultipleChoiceFilter(
     site_group_id = django_filters.ModelMultipleChoiceFilter(
         field_name='site_groups',
         field_name='site_groups',
         queryset=SiteGroup.objects.all(),
         queryset=SiteGroup.objects.all(),
-        label='Site group',
+        label=_('Site group'),
     )
     )
     site_id = django_filters.ModelMultipleChoiceFilter(
     site_id = django_filters.ModelMultipleChoiceFilter(
         field_name='sites',
         field_name='sites',
         queryset=Site.objects.all(),
         queryset=Site.objects.all(),
-        label='Site',
+        label=_('Site'),
     )
     )
     site = django_filters.ModelMultipleChoiceFilter(
     site = django_filters.ModelMultipleChoiceFilter(
         field_name='sites__slug',
         field_name='sites__slug',
         queryset=Site.objects.all(),
         queryset=Site.objects.all(),
         to_field_name='slug',
         to_field_name='slug',
-        label='Site (slug)',
+        label=_('Site (slug)'),
     )
     )
     location_id = django_filters.ModelMultipleChoiceFilter(
     location_id = django_filters.ModelMultipleChoiceFilter(
         field_name='locations',
         field_name='locations',
         queryset=Location.objects.all(),
         queryset=Location.objects.all(),
-        label='Location',
+        label=_('Location'),
     )
     )
     location = django_filters.ModelMultipleChoiceFilter(
     location = django_filters.ModelMultipleChoiceFilter(
         field_name='locations__slug',
         field_name='locations__slug',
         queryset=Location.objects.all(),
         queryset=Location.objects.all(),
         to_field_name='slug',
         to_field_name='slug',
-        label='Location (slug)',
+        label=_('Location (slug)'),
     )
     )
     device_type_id = django_filters.ModelMultipleChoiceFilter(
     device_type_id = django_filters.ModelMultipleChoiceFilter(
         field_name='device_types',
         field_name='device_types',
         queryset=DeviceType.objects.all(),
         queryset=DeviceType.objects.all(),
-        label='Device type',
+        label=_('Device type'),
     )
     )
     role_id = django_filters.ModelMultipleChoiceFilter(
     role_id = django_filters.ModelMultipleChoiceFilter(
         field_name='roles',
         field_name='roles',
         queryset=DeviceRole.objects.all(),
         queryset=DeviceRole.objects.all(),
-        label='Role',
+        label=_('Role'),
     )
     )
     role = django_filters.ModelMultipleChoiceFilter(
     role = django_filters.ModelMultipleChoiceFilter(
         field_name='roles__slug',
         field_name='roles__slug',
         queryset=DeviceRole.objects.all(),
         queryset=DeviceRole.objects.all(),
         to_field_name='slug',
         to_field_name='slug',
-        label='Role (slug)',
+        label=_('Role (slug)'),
     )
     )
     platform_id = django_filters.ModelMultipleChoiceFilter(
     platform_id = django_filters.ModelMultipleChoiceFilter(
         field_name='platforms',
         field_name='platforms',
         queryset=Platform.objects.all(),
         queryset=Platform.objects.all(),
-        label='Platform',
+        label=_('Platform'),
     )
     )
     platform = django_filters.ModelMultipleChoiceFilter(
     platform = django_filters.ModelMultipleChoiceFilter(
         field_name='platforms__slug',
         field_name='platforms__slug',
         queryset=Platform.objects.all(),
         queryset=Platform.objects.all(),
         to_field_name='slug',
         to_field_name='slug',
-        label='Platform (slug)',
+        label=_('Platform (slug)'),
     )
     )
     cluster_type_id = django_filters.ModelMultipleChoiceFilter(
     cluster_type_id = django_filters.ModelMultipleChoiceFilter(
         field_name='cluster_types',
         field_name='cluster_types',
         queryset=ClusterType.objects.all(),
         queryset=ClusterType.objects.all(),
-        label='Cluster type',
+        label=_('Cluster type'),
     )
     )
     cluster_type = django_filters.ModelMultipleChoiceFilter(
     cluster_type = django_filters.ModelMultipleChoiceFilter(
         field_name='cluster_types__slug',
         field_name='cluster_types__slug',
         queryset=ClusterType.objects.all(),
         queryset=ClusterType.objects.all(),
         to_field_name='slug',
         to_field_name='slug',
-        label='Cluster type (slug)',
+        label=_('Cluster type (slug)'),
     )
     )
     cluster_group_id = django_filters.ModelMultipleChoiceFilter(
     cluster_group_id = django_filters.ModelMultipleChoiceFilter(
         field_name='cluster_groups',
         field_name='cluster_groups',
         queryset=ClusterGroup.objects.all(),
         queryset=ClusterGroup.objects.all(),
-        label='Cluster group',
+        label=_('Cluster group'),
     )
     )
     cluster_group = django_filters.ModelMultipleChoiceFilter(
     cluster_group = django_filters.ModelMultipleChoiceFilter(
         field_name='cluster_groups__slug',
         field_name='cluster_groups__slug',
         queryset=ClusterGroup.objects.all(),
         queryset=ClusterGroup.objects.all(),
         to_field_name='slug',
         to_field_name='slug',
-        label='Cluster group (slug)',
+        label=_('Cluster group (slug)'),
     )
     )
     cluster_id = django_filters.ModelMultipleChoiceFilter(
     cluster_id = django_filters.ModelMultipleChoiceFilter(
         field_name='clusters',
         field_name='clusters',
         queryset=Cluster.objects.all(),
         queryset=Cluster.objects.all(),
-        label='Cluster',
+        label=_('Cluster'),
     )
     )
     tenant_group_id = django_filters.ModelMultipleChoiceFilter(
     tenant_group_id = django_filters.ModelMultipleChoiceFilter(
         field_name='tenant_groups',
         field_name='tenant_groups',
         queryset=TenantGroup.objects.all(),
         queryset=TenantGroup.objects.all(),
-        label='Tenant group',
+        label=_('Tenant group'),
     )
     )
     tenant_group = django_filters.ModelMultipleChoiceFilter(
     tenant_group = django_filters.ModelMultipleChoiceFilter(
         field_name='tenant_groups__slug',
         field_name='tenant_groups__slug',
         queryset=TenantGroup.objects.all(),
         queryset=TenantGroup.objects.all(),
         to_field_name='slug',
         to_field_name='slug',
-        label='Tenant group (slug)',
+        label=_('Tenant group (slug)'),
     )
     )
     tenant_id = django_filters.ModelMultipleChoiceFilter(
     tenant_id = django_filters.ModelMultipleChoiceFilter(
         field_name='tenants',
         field_name='tenants',
         queryset=Tenant.objects.all(),
         queryset=Tenant.objects.all(),
-        label='Tenant',
+        label=_('Tenant'),
     )
     )
     tenant = django_filters.ModelMultipleChoiceFilter(
     tenant = django_filters.ModelMultipleChoiceFilter(
         field_name='tenants__slug',
         field_name='tenants__slug',
         queryset=Tenant.objects.all(),
         queryset=Tenant.objects.all(),
         to_field_name='slug',
         to_field_name='slug',
-        label='Tenant (slug)',
+        label=_('Tenant (slug)'),
     )
     )
     tag_id = django_filters.ModelMultipleChoiceFilter(
     tag_id = django_filters.ModelMultipleChoiceFilter(
         field_name='tags',
         field_name='tags',
         queryset=Tag.objects.all(),
         queryset=Tag.objects.all(),
-        label='Tag',
+        label=_('Tag'),
     )
     )
     tag = django_filters.ModelMultipleChoiceFilter(
     tag = django_filters.ModelMultipleChoiceFilter(
         field_name='tags__slug',
         field_name='tags__slug',
         queryset=Tag.objects.all(),
         queryset=Tag.objects.all(),
         to_field_name='slug',
         to_field_name='slug',
-        label='Tag (slug)',
+        label=_('Tag (slug)'),
     )
     )
 
 
     class Meta:
     class Meta:
@@ -443,7 +444,7 @@ class ConfigContextFilterSet(ChangeLoggedModelFilterSet):
 class LocalConfigContextFilterSet(django_filters.FilterSet):
 class LocalConfigContextFilterSet(django_filters.FilterSet):
     local_context_data = django_filters.BooleanFilter(
     local_context_data = django_filters.BooleanFilter(
         method='_local_context_data',
         method='_local_context_data',
-        label='Has local config context data',
+        label=_('Has local config context data'),
     )
     )
 
 
     def _local_context_data(self, queryset, name, value):
     def _local_context_data(self, queryset, name, value):
@@ -453,19 +454,19 @@ class LocalConfigContextFilterSet(django_filters.FilterSet):
 class ObjectChangeFilterSet(BaseFilterSet):
 class ObjectChangeFilterSet(BaseFilterSet):
     q = django_filters.CharFilter(
     q = django_filters.CharFilter(
         method='search',
         method='search',
-        label='Search',
+        label=_('Search'),
     )
     )
     time = django_filters.DateTimeFromToRangeFilter()
     time = django_filters.DateTimeFromToRangeFilter()
     changed_object_type = ContentTypeFilter()
     changed_object_type = ContentTypeFilter()
     user_id = django_filters.ModelMultipleChoiceFilter(
     user_id = django_filters.ModelMultipleChoiceFilter(
         queryset=User.objects.all(),
         queryset=User.objects.all(),
-        label='User (ID)',
+        label=_('User (ID)'),
     )
     )
     user = django_filters.ModelMultipleChoiceFilter(
     user = django_filters.ModelMultipleChoiceFilter(
         field_name='user__username',
         field_name='user__username',
         queryset=User.objects.all(),
         queryset=User.objects.all(),
         to_field_name='username',
         to_field_name='username',
-        label='User name',
+        label=_('User name'),
     )
     )
 
 
     class Meta:
     class Meta:
@@ -491,7 +492,7 @@ class ObjectChangeFilterSet(BaseFilterSet):
 class JobResultFilterSet(BaseFilterSet):
 class JobResultFilterSet(BaseFilterSet):
     q = django_filters.CharFilter(
     q = django_filters.CharFilter(
         method='search',
         method='search',
-        label='Search',
+        label=_('Search'),
     )
     )
     created = django_filters.DateTimeFilter()
     created = django_filters.DateTimeFilter()
     created__before = django_filters.DateTimeFilter(
     created__before = django_filters.DateTimeFilter(
@@ -547,7 +548,7 @@ class JobResultFilterSet(BaseFilterSet):
 class ContentTypeFilterSet(django_filters.FilterSet):
 class ContentTypeFilterSet(django_filters.FilterSet):
     q = django_filters.CharFilter(
     q = django_filters.CharFilter(
         method='search',
         method='search',
-        label='Search',
+        label=_('Search'),
     )
     )
 
 
     class Meta:
     class Meta:

+ 6 - 5
netbox/extras/forms/bulk_edit.py

@@ -1,4 +1,5 @@
 from django import forms
 from django import forms
+from django.utils.translation import gettext as _
 
 
 from extras.choices import *
 from extras.choices import *
 from extras.models import *
 from extras.models import *
@@ -37,7 +38,7 @@ class CustomFieldBulkEditForm(BulkEditForm):
         required=False
         required=False
     )
     )
     ui_visibility = forms.ChoiceField(
     ui_visibility = forms.ChoiceField(
-        label="UI visibility",
+        label=_("UI visibility"),
         choices=add_blank_choice(CustomFieldVisibilityChoices),
         choices=add_blank_choice(CustomFieldVisibilityChoices),
         required=False,
         required=False,
         initial='',
         initial='',
@@ -143,23 +144,23 @@ class WebhookBulkEditForm(BulkEditForm):
     http_method = forms.ChoiceField(
     http_method = forms.ChoiceField(
         choices=add_blank_choice(WebhookHttpMethodChoices),
         choices=add_blank_choice(WebhookHttpMethodChoices),
         required=False,
         required=False,
-        label='HTTP method'
+        label=_('HTTP method')
     )
     )
     payload_url = forms.CharField(
     payload_url = forms.CharField(
         required=False,
         required=False,
-        label='Payload URL'
+        label=_('Payload URL')
     )
     )
     ssl_verification = forms.NullBooleanField(
     ssl_verification = forms.NullBooleanField(
         required=False,
         required=False,
         widget=BulkEditNullBooleanSelect(),
         widget=BulkEditNullBooleanSelect(),
-        label='SSL verification'
+        label=_('SSL verification')
     )
     )
     secret = forms.CharField(
     secret = forms.CharField(
         required=False
         required=False
     )
     )
     ca_file_path = forms.CharField(
     ca_file_path = forms.CharField(
         required=False,
         required=False,
-        label='CA file path'
+        label=_('CA file path')
     )
     )
 
 
     nullable_fields = ('secret', 'conditions', 'ca_file_path')
     nullable_fields = ('secret', 'conditions', 'ca_file_path')

+ 11 - 10
netbox/extras/forms/bulk_import.py

@@ -2,6 +2,7 @@ from django import forms
 from django.contrib.contenttypes.models import ContentType
 from django.contrib.contenttypes.models import ContentType
 from django.contrib.postgres.forms import SimpleArrayField
 from django.contrib.postgres.forms import SimpleArrayField
 from django.utils.safestring import mark_safe
 from django.utils.safestring import mark_safe
+from django.utils.translation import gettext as _
 
 
 from extras.choices import CustomFieldVisibilityChoices, CustomFieldTypeChoices
 from extras.choices import CustomFieldVisibilityChoices, CustomFieldTypeChoices
 from extras.models import *
 from extras.models import *
@@ -22,26 +23,26 @@ class CustomFieldCSVForm(CSVModelForm):
     content_types = CSVMultipleContentTypeField(
     content_types = CSVMultipleContentTypeField(
         queryset=ContentType.objects.all(),
         queryset=ContentType.objects.all(),
         limit_choices_to=FeatureQuery('custom_fields'),
         limit_choices_to=FeatureQuery('custom_fields'),
-        help_text="One or more assigned object types"
+        help_text=_("One or more assigned object types")
     )
     )
     type = CSVChoiceField(
     type = CSVChoiceField(
         choices=CustomFieldTypeChoices,
         choices=CustomFieldTypeChoices,
-        help_text='Field data type (e.g. text, integer, etc.)'
+        help_text=_('Field data type (e.g. text, integer, etc.)')
     )
     )
     object_type = CSVContentTypeField(
     object_type = CSVContentTypeField(
         queryset=ContentType.objects.all(),
         queryset=ContentType.objects.all(),
         limit_choices_to=FeatureQuery('custom_fields'),
         limit_choices_to=FeatureQuery('custom_fields'),
         required=False,
         required=False,
-        help_text="Object type (for object or multi-object fields)"
+        help_text=_("Object type (for object or multi-object fields)")
     )
     )
     choices = SimpleArrayField(
     choices = SimpleArrayField(
         base_field=forms.CharField(),
         base_field=forms.CharField(),
         required=False,
         required=False,
-        help_text='Comma-separated list of field choices'
+        help_text=_('Comma-separated list of field choices')
     )
     )
     ui_visibility = CSVChoiceField(
     ui_visibility = CSVChoiceField(
         choices=CustomFieldVisibilityChoices,
         choices=CustomFieldVisibilityChoices,
-        help_text='How the custom field is displayed in the user interface'
+        help_text=_('How the custom field is displayed in the user interface')
     )
     )
 
 
     class Meta:
     class Meta:
@@ -57,7 +58,7 @@ class CustomLinkCSVForm(CSVModelForm):
     content_types = CSVMultipleContentTypeField(
     content_types = CSVMultipleContentTypeField(
         queryset=ContentType.objects.all(),
         queryset=ContentType.objects.all(),
         limit_choices_to=FeatureQuery('custom_links'),
         limit_choices_to=FeatureQuery('custom_links'),
-        help_text="One or more assigned object types"
+        help_text=_("One or more assigned object types")
     )
     )
 
 
     class Meta:
     class Meta:
@@ -72,7 +73,7 @@ class ExportTemplateCSVForm(CSVModelForm):
     content_types = CSVMultipleContentTypeField(
     content_types = CSVMultipleContentTypeField(
         queryset=ContentType.objects.all(),
         queryset=ContentType.objects.all(),
         limit_choices_to=FeatureQuery('export_templates'),
         limit_choices_to=FeatureQuery('export_templates'),
-        help_text="One or more assigned object types"
+        help_text=_("One or more assigned object types")
     )
     )
 
 
     class Meta:
     class Meta:
@@ -85,7 +86,7 @@ class ExportTemplateCSVForm(CSVModelForm):
 class SavedFilterCSVForm(CSVModelForm):
 class SavedFilterCSVForm(CSVModelForm):
     content_types = CSVMultipleContentTypeField(
     content_types = CSVMultipleContentTypeField(
         queryset=ContentType.objects.all(),
         queryset=ContentType.objects.all(),
-        help_text="One or more assigned object types"
+        help_text=_("One or more assigned object types")
     )
     )
 
 
     class Meta:
     class Meta:
@@ -99,7 +100,7 @@ class WebhookCSVForm(CSVModelForm):
     content_types = CSVMultipleContentTypeField(
     content_types = CSVMultipleContentTypeField(
         queryset=ContentType.objects.all(),
         queryset=ContentType.objects.all(),
         limit_choices_to=FeatureQuery('webhooks'),
         limit_choices_to=FeatureQuery('webhooks'),
-        help_text="One or more assigned object types"
+        help_text=_("One or more assigned object types")
     )
     )
 
 
     class Meta:
     class Meta:
@@ -118,5 +119,5 @@ class TagCSVForm(CSVModelForm):
         model = Tag
         model = Tag
         fields = ('name', 'slug', 'color', 'description')
         fields = ('name', 'slug', 'color', 'description')
         help_texts = {
         help_texts = {
-            'color': mark_safe('RGB color in hexadecimal (e.g. <code>00ff00</code>)'),
+            'color': mark_safe(_('RGB color in hexadecimal (e.g. <code>00ff00</code>)')),
         }
         }

+ 2 - 2
netbox/extras/forms/filtersets.py

@@ -41,7 +41,7 @@ class CustomFieldFilterForm(SavedFiltersMixin, FilterForm):
         queryset=ContentType.objects.all(),
         queryset=ContentType.objects.all(),
         limit_choices_to=FeatureQuery('custom_fields'),
         limit_choices_to=FeatureQuery('custom_fields'),
         required=False,
         required=False,
-        label='Object type'
+        label=_('Object type')
     )
     )
     type = MultipleChoiceField(
     type = MultipleChoiceField(
         choices=CustomFieldTypeChoices,
         choices=CustomFieldTypeChoices,
@@ -209,7 +209,7 @@ class WebhookFilterForm(SavedFiltersMixin, FilterForm):
         queryset=ContentType.objects.all(),
         queryset=ContentType.objects.all(),
         limit_choices_to=FeatureQuery('webhooks'),
         limit_choices_to=FeatureQuery('webhooks'),
         required=False,
         required=False,
-        label='Object type'
+        label=_('Object type')
     )
     )
     http_method = MultipleChoiceField(
     http_method = MultipleChoiceField(
         choices=WebhookHttpMethodChoices,
         choices=WebhookHttpMethodChoices,

+ 2 - 1
netbox/extras/forms/mixins.py

@@ -1,5 +1,6 @@
 from django.contrib.contenttypes.models import ContentType
 from django.contrib.contenttypes.models import ContentType
 from django import forms
 from django import forms
+from django.utils.translation import gettext as _
 
 
 from extras.models import *
 from extras.models import *
 from extras.choices import CustomFieldVisibilityChoices
 from extras.choices import CustomFieldVisibilityChoices
@@ -66,7 +67,7 @@ class SavedFiltersMixin(forms.Form):
     filter = DynamicModelMultipleChoiceField(
     filter = DynamicModelMultipleChoiceField(
         queryset=SavedFilter.objects.all(),
         queryset=SavedFilter.objects.all(),
         required=False,
         required=False,
-        label='Saved Filter',
+        label=_('Saved Filter'),
         query_params={
         query_params={
             'usable': True,
             'usable': True,
         }
         }

+ 8 - 7
netbox/extras/forms/model_forms.py

@@ -1,6 +1,7 @@
 from django import forms
 from django import forms
 from django.contrib.contenttypes.models import ContentType
 from django.contrib.contenttypes.models import ContentType
 from django.http import QueryDict
 from django.http import QueryDict
+from django.utils.translation import gettext as _
 
 
 from dcim.models import DeviceRole, DeviceType, Location, Platform, Region, Site, SiteGroup
 from dcim.models import DeviceRole, DeviceType, Location, Platform, Region, Site, SiteGroup
 from extras.choices import *
 from extras.choices import *
@@ -31,14 +32,14 @@ class CustomFieldForm(BootstrapMixin, forms.ModelForm):
     content_types = ContentTypeMultipleChoiceField(
     content_types = ContentTypeMultipleChoiceField(
         queryset=ContentType.objects.all(),
         queryset=ContentType.objects.all(),
         limit_choices_to=FeatureQuery('custom_fields'),
         limit_choices_to=FeatureQuery('custom_fields'),
-        label='Model(s)'
+        label=_('Model(s)')
     )
     )
     object_type = ContentTypeChoiceField(
     object_type = ContentTypeChoiceField(
         queryset=ContentType.objects.all(),
         queryset=ContentType.objects.all(),
         # TODO: Come up with a canonical way to register suitable models
         # TODO: Come up with a canonical way to register suitable models
         limit_choices_to=FeatureQuery('webhooks'),
         limit_choices_to=FeatureQuery('webhooks'),
         required=False,
         required=False,
-        help_text="Type of the related object (for object/multi-object fields only)"
+        help_text=_("Type of the related object (for object/multi-object fields only)")
     )
     )
 
 
     fieldsets = (
     fieldsets = (
@@ -54,8 +55,8 @@ class CustomFieldForm(BootstrapMixin, forms.ModelForm):
         model = CustomField
         model = CustomField
         fields = '__all__'
         fields = '__all__'
         help_texts = {
         help_texts = {
-            'type': "The type of data stored in this field. For object/multi-object fields, select the related object "
-                    "type below."
+            'type': _("The type of data stored in this field. For object/multi-object fields, select the related object "
+                      "type below.")
         }
         }
         widgets = {
         widgets = {
             'type': StaticSelect(),
             'type': StaticSelect(),
@@ -84,9 +85,9 @@ class CustomLinkForm(BootstrapMixin, forms.ModelForm):
             'link_url': forms.Textarea(attrs={'class': 'font-monospace'}),
             'link_url': forms.Textarea(attrs={'class': 'font-monospace'}),
         }
         }
         help_texts = {
         help_texts = {
-            'link_text': 'Jinja2 template code for the link text. Reference the object as <code>{{ object }}</code>. '
-                         'Links which render as empty text will not be displayed.',
-            'link_url': 'Jinja2 template code for the link URL. Reference the object as <code>{{ object }}</code>.',
+            'link_text': _('Jinja2 template code for the link text. Reference the object as <code>{{ object }}</code>. '
+                           'Links which render as empty text will not be displayed.'),
+            'link_url': _('Jinja2 template code for the link URL. Reference the object as <code>{{ object }}</code>.'),
         }
         }
 
 
 
 

+ 3 - 2
netbox/extras/forms/reports.py

@@ -1,4 +1,5 @@
 from django import forms
 from django import forms
+from django.utils.translation import gettext as _
 
 
 from utilities.forms import BootstrapMixin, DateTimePicker
 from utilities.forms import BootstrapMixin, DateTimePicker
 
 
@@ -11,6 +12,6 @@ class ReportForm(BootstrapMixin, forms.Form):
     schedule_at = forms.DateTimeField(
     schedule_at = forms.DateTimeField(
         required=False,
         required=False,
         widget=DateTimePicker(),
         widget=DateTimePicker(),
-        label="Schedule at",
-        help_text="Schedule execution of report to a set time",
+        label=_("Schedule at"),
+        help_text=_("Schedule execution of report to a set time"),
     )
     )

+ 5 - 4
netbox/extras/forms/scripts.py

@@ -1,4 +1,5 @@
 from django import forms
 from django import forms
+from django.utils.translation import gettext as _
 
 
 from utilities.forms import BootstrapMixin, DateTimePicker
 from utilities.forms import BootstrapMixin, DateTimePicker
 
 
@@ -11,14 +12,14 @@ class ScriptForm(BootstrapMixin, forms.Form):
     _commit = forms.BooleanField(
     _commit = forms.BooleanField(
         required=False,
         required=False,
         initial=True,
         initial=True,
-        label="Commit changes",
-        help_text="Commit changes to the database (uncheck for a dry-run)"
+        label=_("Commit changes"),
+        help_text=_("Commit changes to the database (uncheck for a dry-run)")
     )
     )
     _schedule_at = forms.DateTimeField(
     _schedule_at = forms.DateTimeField(
         required=False,
         required=False,
         widget=DateTimePicker(),
         widget=DateTimePicker(),
-        label="Schedule at",
-        help_text="Schedule execution of script to a set time",
+        label=_("Schedule at"),
+        help_text=_("Schedule execution of script to a set time"),
     )
     )
 
 
     def __init__(self, *args, **kwargs):
     def __init__(self, *args, **kwargs):

+ 23 - 22
netbox/extras/models/customfields.py

@@ -11,6 +11,7 @@ from django.db import models
 from django.urls import reverse
 from django.urls import reverse
 from django.utils.html import escape
 from django.utils.html import escape
 from django.utils.safestring import mark_safe
 from django.utils.safestring import mark_safe
+from django.utils.translation import gettext as _
 
 
 from extras.choices import *
 from extras.choices import *
 from extras.utils import FeatureQuery
 from extras.utils import FeatureQuery
@@ -57,25 +58,25 @@ class CustomField(CloningMixin, ExportTemplatesMixin, WebhooksMixin, ChangeLogge
         to=ContentType,
         to=ContentType,
         related_name='custom_fields',
         related_name='custom_fields',
         limit_choices_to=FeatureQuery('custom_fields'),
         limit_choices_to=FeatureQuery('custom_fields'),
-        help_text='The object(s) to which this field applies.'
+        help_text=_('The object(s) to which this field applies.')
     )
     )
     type = models.CharField(
     type = models.CharField(
         max_length=50,
         max_length=50,
         choices=CustomFieldTypeChoices,
         choices=CustomFieldTypeChoices,
         default=CustomFieldTypeChoices.TYPE_TEXT,
         default=CustomFieldTypeChoices.TYPE_TEXT,
-        help_text='The type of data this custom field holds'
+        help_text=_('The type of data this custom field holds')
     )
     )
     object_type = models.ForeignKey(
     object_type = models.ForeignKey(
         to=ContentType,
         to=ContentType,
         on_delete=models.PROTECT,
         on_delete=models.PROTECT,
         blank=True,
         blank=True,
         null=True,
         null=True,
-        help_text='The type of NetBox object this field maps to (for object fields)'
+        help_text=_('The type of NetBox object this field maps to (for object fields)')
     )
     )
     name = models.CharField(
     name = models.CharField(
         max_length=50,
         max_length=50,
         unique=True,
         unique=True,
-        help_text='Internal field name',
+        help_text=_('Internal field name'),
         validators=(
         validators=(
             RegexValidator(
             RegexValidator(
                 regex=r'^[a-z0-9_]+$',
                 regex=r'^[a-z0-9_]+$',
@@ -87,13 +88,13 @@ class CustomField(CloningMixin, ExportTemplatesMixin, WebhooksMixin, ChangeLogge
     label = models.CharField(
     label = models.CharField(
         max_length=50,
         max_length=50,
         blank=True,
         blank=True,
-        help_text='Name of the field as displayed to users (if not provided, '
-                  'the field\'s name will be used)'
+        help_text=_('Name of the field as displayed to users (if not provided, '
+                    'the field\'s name will be used)')
     )
     )
     group_name = models.CharField(
     group_name = models.CharField(
         max_length=50,
         max_length=50,
         blank=True,
         blank=True,
-        help_text="Custom fields within the same group will be displayed together"
+        help_text=_("Custom fields within the same group will be displayed together")
     )
     )
     description = models.CharField(
     description = models.CharField(
         max_length=200,
         max_length=200,
@@ -101,64 +102,64 @@ class CustomField(CloningMixin, ExportTemplatesMixin, WebhooksMixin, ChangeLogge
     )
     )
     required = models.BooleanField(
     required = models.BooleanField(
         default=False,
         default=False,
-        help_text='If true, this field is required when creating new objects '
-                  'or editing an existing object.'
+        help_text=_('If true, this field is required when creating new objects '
+                    'or editing an existing object.')
     )
     )
     search_weight = models.PositiveSmallIntegerField(
     search_weight = models.PositiveSmallIntegerField(
         default=1000,
         default=1000,
-        help_text='Weighting for search. Lower values are considered more important. '
-                  'Fields with a search weight of zero will be ignored.'
+        help_text=_('Weighting for search. Lower values are considered more important. '
+                    'Fields with a search weight of zero will be ignored.')
     )
     )
     filter_logic = models.CharField(
     filter_logic = models.CharField(
         max_length=50,
         max_length=50,
         choices=CustomFieldFilterLogicChoices,
         choices=CustomFieldFilterLogicChoices,
         default=CustomFieldFilterLogicChoices.FILTER_LOOSE,
         default=CustomFieldFilterLogicChoices.FILTER_LOOSE,
-        help_text='Loose matches any instance of a given string; exact '
-                  'matches the entire field.'
+        help_text=_('Loose matches any instance of a given string; exact '
+                    'matches the entire field.')
     )
     )
     default = models.JSONField(
     default = models.JSONField(
         blank=True,
         blank=True,
         null=True,
         null=True,
-        help_text='Default value for the field (must be a JSON value). Encapsulate '
-                  'strings with double quotes (e.g. "Foo").'
+        help_text=_('Default value for the field (must be a JSON value). Encapsulate '
+                    'strings with double quotes (e.g. "Foo").')
     )
     )
     weight = models.PositiveSmallIntegerField(
     weight = models.PositiveSmallIntegerField(
         default=100,
         default=100,
         verbose_name='Display weight',
         verbose_name='Display weight',
-        help_text='Fields with higher weights appear lower in a form.'
+        help_text=_('Fields with higher weights appear lower in a form.')
     )
     )
     validation_minimum = models.IntegerField(
     validation_minimum = models.IntegerField(
         blank=True,
         blank=True,
         null=True,
         null=True,
         verbose_name='Minimum value',
         verbose_name='Minimum value',
-        help_text='Minimum allowed value (for numeric fields)'
+        help_text=_('Minimum allowed value (for numeric fields)')
     )
     )
     validation_maximum = models.IntegerField(
     validation_maximum = models.IntegerField(
         blank=True,
         blank=True,
         null=True,
         null=True,
         verbose_name='Maximum value',
         verbose_name='Maximum value',
-        help_text='Maximum allowed value (for numeric fields)'
+        help_text=_('Maximum allowed value (for numeric fields)')
     )
     )
     validation_regex = models.CharField(
     validation_regex = models.CharField(
         blank=True,
         blank=True,
         validators=[validate_regex],
         validators=[validate_regex],
         max_length=500,
         max_length=500,
         verbose_name='Validation regex',
         verbose_name='Validation regex',
-        help_text='Regular expression to enforce on text field values. Use ^ and $ to force matching of entire string. '
-                  'For example, <code>^[A-Z]{3}$</code> will limit values to exactly three uppercase letters.'
+        help_text=_('Regular expression to enforce on text field values. Use ^ and $ to force matching of entire string. '
+                    'For example, <code>^[A-Z]{3}$</code> will limit values to exactly three uppercase letters.')
     )
     )
     choices = ArrayField(
     choices = ArrayField(
         base_field=models.CharField(max_length=100),
         base_field=models.CharField(max_length=100),
         blank=True,
         blank=True,
         null=True,
         null=True,
-        help_text='Comma-separated list of available choices (for selection fields)'
+        help_text=_('Comma-separated list of available choices (for selection fields)')
     )
     )
     ui_visibility = models.CharField(
     ui_visibility = models.CharField(
         max_length=50,
         max_length=50,
         choices=CustomFieldVisibilityChoices,
         choices=CustomFieldVisibilityChoices,
         default=CustomFieldVisibilityChoices.VISIBILITY_READ_WRITE,
         default=CustomFieldVisibilityChoices.VISIBILITY_READ_WRITE,
         verbose_name='UI visibility',
         verbose_name='UI visibility',
-        help_text='Specifies the visibility of custom field in the UI'
+        help_text=_('Specifies the visibility of custom field in the UI')
     )
     )
 
 
     objects = CustomFieldManager()
     objects = CustomFieldManager()

+ 37 - 36
netbox/extras/models/models.py

@@ -12,6 +12,7 @@ from django.http import HttpResponse, QueryDict
 from django.urls import reverse
 from django.urls import reverse
 from django.utils import timezone
 from django.utils import timezone
 from django.utils.formats import date_format
 from django.utils.formats import date_format
+from django.utils.translation import gettext as _
 from rest_framework.utils.encoders import JSONEncoder
 from rest_framework.utils.encoders import JSONEncoder
 import django_rq
 import django_rq
 
 
@@ -51,7 +52,7 @@ class Webhook(ExportTemplatesMixin, WebhooksMixin, ChangeLoggedModel):
         related_name='webhooks',
         related_name='webhooks',
         verbose_name='Object types',
         verbose_name='Object types',
         limit_choices_to=FeatureQuery('webhooks'),
         limit_choices_to=FeatureQuery('webhooks'),
-        help_text="The object(s) to which this Webhook applies."
+        help_text=_("The object(s) to which this Webhook applies.")
     )
     )
     name = models.CharField(
     name = models.CharField(
         max_length=150,
         max_length=150,
@@ -59,21 +60,21 @@ class Webhook(ExportTemplatesMixin, WebhooksMixin, ChangeLoggedModel):
     )
     )
     type_create = models.BooleanField(
     type_create = models.BooleanField(
         default=False,
         default=False,
-        help_text="Call this webhook when a matching object is created."
+        help_text=_("Call this webhook when a matching object is created.")
     )
     )
     type_update = models.BooleanField(
     type_update = models.BooleanField(
         default=False,
         default=False,
-        help_text="Call this webhook when a matching object is updated."
+        help_text=_("Call this webhook when a matching object is updated.")
     )
     )
     type_delete = models.BooleanField(
     type_delete = models.BooleanField(
         default=False,
         default=False,
-        help_text="Call this webhook when a matching object is deleted."
+        help_text=_("Call this webhook when a matching object is deleted.")
     )
     )
     payload_url = models.CharField(
     payload_url = models.CharField(
         max_length=500,
         max_length=500,
         verbose_name='URL',
         verbose_name='URL',
-        help_text='This URL will be called using the HTTP method defined when the webhook is called. '
-                  'Jinja2 template processing is supported with the same context as the request body.'
+        help_text=_('This URL will be called using the HTTP method defined when the webhook is called. '
+                    'Jinja2 template processing is supported with the same context as the request body.')
     )
     )
     enabled = models.BooleanField(
     enabled = models.BooleanField(
         default=True
         default=True
@@ -88,46 +89,46 @@ class Webhook(ExportTemplatesMixin, WebhooksMixin, ChangeLoggedModel):
         max_length=100,
         max_length=100,
         default=HTTP_CONTENT_TYPE_JSON,
         default=HTTP_CONTENT_TYPE_JSON,
         verbose_name='HTTP content type',
         verbose_name='HTTP content type',
-        help_text='The complete list of official content types is available '
-                  '<a href="https://www.iana.org/assignments/media-types/media-types.xhtml">here</a>.'
+        help_text=_('The complete list of official content types is available '
+                    '<a href="https://www.iana.org/assignments/media-types/media-types.xhtml">here</a>.')
     )
     )
     additional_headers = models.TextField(
     additional_headers = models.TextField(
         blank=True,
         blank=True,
-        help_text="User-supplied HTTP headers to be sent with the request in addition to the HTTP content type. "
-                  "Headers should be defined in the format <code>Name: Value</code>. Jinja2 template processing is "
-                  "supported with the same context as the request body (below)."
+        help_text=_("User-supplied HTTP headers to be sent with the request in addition to the HTTP content type. "
+                    "Headers should be defined in the format <code>Name: Value</code>. Jinja2 template processing is "
+                    "supported with the same context as the request body (below).")
     )
     )
     body_template = models.TextField(
     body_template = models.TextField(
         blank=True,
         blank=True,
-        help_text='Jinja2 template for a custom request body. If blank, a JSON object representing the change will be '
-                  'included. Available context data includes: <code>event</code>, <code>model</code>, '
-                  '<code>timestamp</code>, <code>username</code>, <code>request_id</code>, and <code>data</code>.'
+        help_text=_('Jinja2 template for a custom request body. If blank, a JSON object representing the change will be '
+                    'included. Available context data includes: <code>event</code>, <code>model</code>, '
+                    '<code>timestamp</code>, <code>username</code>, <code>request_id</code>, and <code>data</code>.')
     )
     )
     secret = models.CharField(
     secret = models.CharField(
         max_length=255,
         max_length=255,
         blank=True,
         blank=True,
-        help_text="When provided, the request will include a 'X-Hook-Signature' "
-                  "header containing a HMAC hex digest of the payload body using "
-                  "the secret as the key. The secret is not transmitted in "
-                  "the request."
+        help_text=_("When provided, the request will include a 'X-Hook-Signature' "
+                    "header containing a HMAC hex digest of the payload body using "
+                    "the secret as the key. The secret is not transmitted in "
+                    "the request.")
     )
     )
     conditions = models.JSONField(
     conditions = models.JSONField(
         blank=True,
         blank=True,
         null=True,
         null=True,
-        help_text="A set of conditions which determine whether the webhook will be generated."
+        help_text=_("A set of conditions which determine whether the webhook will be generated.")
     )
     )
     ssl_verification = models.BooleanField(
     ssl_verification = models.BooleanField(
         default=True,
         default=True,
         verbose_name='SSL verification',
         verbose_name='SSL verification',
-        help_text="Enable SSL certificate verification. Disable with caution!"
+        help_text=_("Enable SSL certificate verification. Disable with caution!")
     )
     )
     ca_file_path = models.CharField(
     ca_file_path = models.CharField(
         max_length=4096,
         max_length=4096,
         null=True,
         null=True,
         blank=True,
         blank=True,
         verbose_name='CA File Path',
         verbose_name='CA File Path',
-        help_text='The specific CA certificate file to use for SSL verification. '
-                  'Leave blank to use the system defaults.'
+        help_text=_('The specific CA certificate file to use for SSL verification. '
+                    'Leave blank to use the system defaults.')
     )
     )
 
 
     class Meta:
     class Meta:
@@ -201,7 +202,7 @@ class CustomLink(CloningMixin, ExportTemplatesMixin, WebhooksMixin, ChangeLogged
     content_types = models.ManyToManyField(
     content_types = models.ManyToManyField(
         to=ContentType,
         to=ContentType,
         related_name='custom_links',
         related_name='custom_links',
-        help_text='The object type(s) to which this link applies.'
+        help_text=_('The object type(s) to which this link applies.')
     )
     )
     name = models.CharField(
     name = models.CharField(
         max_length=100,
         max_length=100,
@@ -211,11 +212,11 @@ class CustomLink(CloningMixin, ExportTemplatesMixin, WebhooksMixin, ChangeLogged
         default=True
         default=True
     )
     )
     link_text = models.TextField(
     link_text = models.TextField(
-        help_text="Jinja2 template code for link text"
+        help_text=_("Jinja2 template code for link text")
     )
     )
     link_url = models.TextField(
     link_url = models.TextField(
         verbose_name='Link URL',
         verbose_name='Link URL',
-        help_text="Jinja2 template code for link URL"
+        help_text=_("Jinja2 template code for link URL")
     )
     )
     weight = models.PositiveSmallIntegerField(
     weight = models.PositiveSmallIntegerField(
         default=100
         default=100
@@ -223,17 +224,17 @@ class CustomLink(CloningMixin, ExportTemplatesMixin, WebhooksMixin, ChangeLogged
     group_name = models.CharField(
     group_name = models.CharField(
         max_length=50,
         max_length=50,
         blank=True,
         blank=True,
-        help_text="Links with the same group will appear as a dropdown menu"
+        help_text=_("Links with the same group will appear as a dropdown menu")
     )
     )
     button_class = models.CharField(
     button_class = models.CharField(
         max_length=30,
         max_length=30,
         choices=CustomLinkButtonClassChoices,
         choices=CustomLinkButtonClassChoices,
         default=CustomLinkButtonClassChoices.DEFAULT,
         default=CustomLinkButtonClassChoices.DEFAULT,
-        help_text="The class of the first link in a group will be used for the dropdown button"
+        help_text=_("The class of the first link in a group will be used for the dropdown button")
     )
     )
     new_window = models.BooleanField(
     new_window = models.BooleanField(
         default=False,
         default=False,
-        help_text="Force link to open in a new window"
+        help_text=_("Force link to open in a new window")
     )
     )
 
 
     clone_fields = (
     clone_fields = (
@@ -272,7 +273,7 @@ class ExportTemplate(ExportTemplatesMixin, WebhooksMixin, ChangeLoggedModel):
     content_types = models.ManyToManyField(
     content_types = models.ManyToManyField(
         to=ContentType,
         to=ContentType,
         related_name='export_templates',
         related_name='export_templates',
-        help_text='The object type(s) to which this template applies.'
+        help_text=_('The object type(s) to which this template applies.')
     )
     )
     name = models.CharField(
     name = models.CharField(
         max_length=100
         max_length=100
@@ -282,23 +283,23 @@ class ExportTemplate(ExportTemplatesMixin, WebhooksMixin, ChangeLoggedModel):
         blank=True
         blank=True
     )
     )
     template_code = models.TextField(
     template_code = models.TextField(
-        help_text='Jinja2 template code. The list of objects being exported is passed as a context variable named '
-                  '<code>queryset</code>.'
+        help_text=_('Jinja2 template code. The list of objects being exported is passed as a context variable named '
+                    '<code>queryset</code>.')
     )
     )
     mime_type = models.CharField(
     mime_type = models.CharField(
         max_length=50,
         max_length=50,
         blank=True,
         blank=True,
         verbose_name='MIME type',
         verbose_name='MIME type',
-        help_text='Defaults to <code>text/plain</code>'
+        help_text=_('Defaults to <code>text/plain</code>')
     )
     )
     file_extension = models.CharField(
     file_extension = models.CharField(
         max_length=15,
         max_length=15,
         blank=True,
         blank=True,
-        help_text='Extension to append to the rendered filename'
+        help_text=_('Extension to append to the rendered filename')
     )
     )
     as_attachment = models.BooleanField(
     as_attachment = models.BooleanField(
         default=True,
         default=True,
-        help_text="Download file as attachment"
+        help_text=_("Download file as attachment")
     )
     )
 
 
     class Meta:
     class Meta:
@@ -358,7 +359,7 @@ class SavedFilter(CloningMixin, ExportTemplatesMixin, WebhooksMixin, ChangeLogge
     content_types = models.ManyToManyField(
     content_types = models.ManyToManyField(
         to=ContentType,
         to=ContentType,
         related_name='saved_filters',
         related_name='saved_filters',
-        help_text='The object type(s) to which this filter applies.'
+        help_text=_('The object type(s) to which this filter applies.')
     )
     )
     name = models.CharField(
     name = models.CharField(
         max_length=100,
         max_length=100,
@@ -553,7 +554,7 @@ class JobResult(models.Model):
         related_name='job_results',
         related_name='job_results',
         verbose_name='Object types',
         verbose_name='Object types',
         limit_choices_to=FeatureQuery('job_results'),
         limit_choices_to=FeatureQuery('job_results'),
-        help_text="The object type to which this job result applies",
+        help_text=_("The object type to which this job result applies"),
         on_delete=models.CASCADE,
         on_delete=models.CASCADE,
     )
     )
     created = models.DateTimeField(
     created = models.DateTimeField(

+ 2 - 1
netbox/extras/tests/dummy_plugin/navigation.py

@@ -1,3 +1,4 @@
+from django.utils.translation import gettext as _
 from extras.plugins import PluginMenu, PluginMenuButton, PluginMenuItem
 from extras.plugins import PluginMenu, PluginMenuButton, PluginMenuItem
 
 
 
 
@@ -25,7 +26,7 @@ items = (
 )
 )
 
 
 menu = PluginMenu(
 menu = PluginMenu(
-    label='Dummy',
+    label=_('Dummy'),
     groups=(('Group 1', items),),
     groups=(('Group 1', items),),
 )
 )
 menu_items = items
 menu_items = items

+ 96 - 95
netbox/ipam/filtersets.py

@@ -3,6 +3,7 @@ import netaddr
 from django.contrib.contenttypes.models import ContentType
 from django.contrib.contenttypes.models import ContentType
 from django.core.exceptions import ValidationError
 from django.core.exceptions import ValidationError
 from django.db.models import Q
 from django.db.models import Q
+from django.utils.translation import gettext as _
 from netaddr.core import AddrFormatError
 from netaddr.core import AddrFormatError
 
 
 from dcim.models import Device, Interface, Region, Site, SiteGroup
 from dcim.models import Device, Interface, Region, Site, SiteGroup
@@ -41,24 +42,24 @@ class VRFFilterSet(NetBoxModelFilterSet, TenancyFilterSet):
     import_target_id = django_filters.ModelMultipleChoiceFilter(
     import_target_id = django_filters.ModelMultipleChoiceFilter(
         field_name='import_targets',
         field_name='import_targets',
         queryset=RouteTarget.objects.all(),
         queryset=RouteTarget.objects.all(),
-        label='Import target',
+        label=_('Import target'),
     )
     )
     import_target = django_filters.ModelMultipleChoiceFilter(
     import_target = django_filters.ModelMultipleChoiceFilter(
         field_name='import_targets__name',
         field_name='import_targets__name',
         queryset=RouteTarget.objects.all(),
         queryset=RouteTarget.objects.all(),
         to_field_name='name',
         to_field_name='name',
-        label='Import target (name)',
+        label=_('Import target (name)'),
     )
     )
     export_target_id = django_filters.ModelMultipleChoiceFilter(
     export_target_id = django_filters.ModelMultipleChoiceFilter(
         field_name='export_targets',
         field_name='export_targets',
         queryset=RouteTarget.objects.all(),
         queryset=RouteTarget.objects.all(),
-        label='Export target',
+        label=_('Export target'),
     )
     )
     export_target = django_filters.ModelMultipleChoiceFilter(
     export_target = django_filters.ModelMultipleChoiceFilter(
         field_name='export_targets__name',
         field_name='export_targets__name',
         queryset=RouteTarget.objects.all(),
         queryset=RouteTarget.objects.all(),
         to_field_name='name',
         to_field_name='name',
-        label='Export target (name)',
+        label=_('Export target (name)'),
     )
     )
 
 
     def search(self, queryset, name, value):
     def search(self, queryset, name, value):
@@ -79,24 +80,24 @@ class RouteTargetFilterSet(NetBoxModelFilterSet, TenancyFilterSet):
     importing_vrf_id = django_filters.ModelMultipleChoiceFilter(
     importing_vrf_id = django_filters.ModelMultipleChoiceFilter(
         field_name='importing_vrfs',
         field_name='importing_vrfs',
         queryset=VRF.objects.all(),
         queryset=VRF.objects.all(),
-        label='Importing VRF',
+        label=_('Importing VRF'),
     )
     )
     importing_vrf = django_filters.ModelMultipleChoiceFilter(
     importing_vrf = django_filters.ModelMultipleChoiceFilter(
         field_name='importing_vrfs__rd',
         field_name='importing_vrfs__rd',
         queryset=VRF.objects.all(),
         queryset=VRF.objects.all(),
         to_field_name='rd',
         to_field_name='rd',
-        label='Import VRF (RD)',
+        label=_('Import VRF (RD)'),
     )
     )
     exporting_vrf_id = django_filters.ModelMultipleChoiceFilter(
     exporting_vrf_id = django_filters.ModelMultipleChoiceFilter(
         field_name='exporting_vrfs',
         field_name='exporting_vrfs',
         queryset=VRF.objects.all(),
         queryset=VRF.objects.all(),
-        label='Exporting VRF',
+        label=_('Exporting VRF'),
     )
     )
     exporting_vrf = django_filters.ModelMultipleChoiceFilter(
     exporting_vrf = django_filters.ModelMultipleChoiceFilter(
         field_name='exporting_vrfs__rd',
         field_name='exporting_vrfs__rd',
         queryset=VRF.objects.all(),
         queryset=VRF.objects.all(),
         to_field_name='rd',
         to_field_name='rd',
-        label='Export VRF (RD)',
+        label=_('Export VRF (RD)'),
     )
     )
 
 
     def search(self, queryset, name, value):
     def search(self, queryset, name, value):
@@ -126,17 +127,17 @@ class AggregateFilterSet(NetBoxModelFilterSet, TenancyFilterSet):
     )
     )
     prefix = django_filters.CharFilter(
     prefix = django_filters.CharFilter(
         method='filter_prefix',
         method='filter_prefix',
-        label='Prefix',
+        label=_('Prefix'),
     )
     )
     rir_id = django_filters.ModelMultipleChoiceFilter(
     rir_id = django_filters.ModelMultipleChoiceFilter(
         queryset=RIR.objects.all(),
         queryset=RIR.objects.all(),
-        label='RIR (ID)',
+        label=_('RIR (ID)'),
     )
     )
     rir = django_filters.ModelMultipleChoiceFilter(
     rir = django_filters.ModelMultipleChoiceFilter(
         field_name='rir__slug',
         field_name='rir__slug',
         queryset=RIR.objects.all(),
         queryset=RIR.objects.all(),
         to_field_name='slug',
         to_field_name='slug',
-        label='RIR (slug)',
+        label=_('RIR (slug)'),
     )
     )
 
 
     class Meta:
     class Meta:
@@ -169,24 +170,24 @@ class AggregateFilterSet(NetBoxModelFilterSet, TenancyFilterSet):
 class ASNFilterSet(OrganizationalModelFilterSet, TenancyFilterSet):
 class ASNFilterSet(OrganizationalModelFilterSet, TenancyFilterSet):
     rir_id = django_filters.ModelMultipleChoiceFilter(
     rir_id = django_filters.ModelMultipleChoiceFilter(
         queryset=RIR.objects.all(),
         queryset=RIR.objects.all(),
-        label='RIR (ID)',
+        label=_('RIR (ID)'),
     )
     )
     rir = django_filters.ModelMultipleChoiceFilter(
     rir = django_filters.ModelMultipleChoiceFilter(
         field_name='rir__slug',
         field_name='rir__slug',
         queryset=RIR.objects.all(),
         queryset=RIR.objects.all(),
         to_field_name='slug',
         to_field_name='slug',
-        label='RIR (slug)',
+        label=_('RIR (slug)'),
     )
     )
     site_id = django_filters.ModelMultipleChoiceFilter(
     site_id = django_filters.ModelMultipleChoiceFilter(
         field_name='sites',
         field_name='sites',
         queryset=Site.objects.all(),
         queryset=Site.objects.all(),
-        label='Site (ID)',
+        label=_('Site (ID)'),
     )
     )
     site = django_filters.ModelMultipleChoiceFilter(
     site = django_filters.ModelMultipleChoiceFilter(
         field_name='sites__slug',
         field_name='sites__slug',
         queryset=Site.objects.all(),
         queryset=Site.objects.all(),
         to_field_name='slug',
         to_field_name='slug',
-        label='Site (slug)',
+        label=_('Site (slug)'),
     )
     )
 
 
     class Meta:
     class Meta:
@@ -218,19 +219,19 @@ class PrefixFilterSet(NetBoxModelFilterSet, TenancyFilterSet):
     )
     )
     prefix = MultiValueCharFilter(
     prefix = MultiValueCharFilter(
         method='filter_prefix',
         method='filter_prefix',
-        label='Prefix',
+        label=_('Prefix'),
     )
     )
     within = django_filters.CharFilter(
     within = django_filters.CharFilter(
         method='search_within',
         method='search_within',
-        label='Within prefix',
+        label=_('Within prefix'),
     )
     )
     within_include = django_filters.CharFilter(
     within_include = django_filters.CharFilter(
         method='search_within_include',
         method='search_within_include',
-        label='Within and including prefix',
+        label=_('Within and including prefix'),
     )
     )
     contains = django_filters.CharFilter(
     contains = django_filters.CharFilter(
         method='search_contains',
         method='search_contains',
-        label='Prefixes which contain this prefix or IP',
+        label=_('Prefixes which contain this prefix or IP'),
     )
     )
     depth = MultiValueNumberFilter(
     depth = MultiValueNumberFilter(
         field_name='_depth'
         field_name='_depth'
@@ -252,78 +253,78 @@ class PrefixFilterSet(NetBoxModelFilterSet, TenancyFilterSet):
     )
     )
     vrf_id = django_filters.ModelMultipleChoiceFilter(
     vrf_id = django_filters.ModelMultipleChoiceFilter(
         queryset=VRF.objects.all(),
         queryset=VRF.objects.all(),
-        label='VRF',
+        label=_('VRF'),
     )
     )
     vrf = django_filters.ModelMultipleChoiceFilter(
     vrf = django_filters.ModelMultipleChoiceFilter(
         field_name='vrf__rd',
         field_name='vrf__rd',
         queryset=VRF.objects.all(),
         queryset=VRF.objects.all(),
         to_field_name='rd',
         to_field_name='rd',
-        label='VRF (RD)',
+        label=_('VRF (RD)'),
     )
     )
     present_in_vrf_id = django_filters.ModelChoiceFilter(
     present_in_vrf_id = django_filters.ModelChoiceFilter(
         queryset=VRF.objects.all(),
         queryset=VRF.objects.all(),
         method='filter_present_in_vrf',
         method='filter_present_in_vrf',
-        label='VRF'
+        label=_('VRF')
     )
     )
     present_in_vrf = django_filters.ModelChoiceFilter(
     present_in_vrf = django_filters.ModelChoiceFilter(
         queryset=VRF.objects.all(),
         queryset=VRF.objects.all(),
         method='filter_present_in_vrf',
         method='filter_present_in_vrf',
         to_field_name='rd',
         to_field_name='rd',
-        label='VRF (RD)',
+        label=_('VRF (RD)'),
     )
     )
     region_id = TreeNodeMultipleChoiceFilter(
     region_id = TreeNodeMultipleChoiceFilter(
         queryset=Region.objects.all(),
         queryset=Region.objects.all(),
         field_name='site__region',
         field_name='site__region',
         lookup_expr='in',
         lookup_expr='in',
-        label='Region (ID)',
+        label=_('Region (ID)'),
     )
     )
     region = TreeNodeMultipleChoiceFilter(
     region = TreeNodeMultipleChoiceFilter(
         queryset=Region.objects.all(),
         queryset=Region.objects.all(),
         field_name='site__region',
         field_name='site__region',
         lookup_expr='in',
         lookup_expr='in',
         to_field_name='slug',
         to_field_name='slug',
-        label='Region (slug)',
+        label=_('Region (slug)'),
     )
     )
     site_group_id = TreeNodeMultipleChoiceFilter(
     site_group_id = TreeNodeMultipleChoiceFilter(
         queryset=SiteGroup.objects.all(),
         queryset=SiteGroup.objects.all(),
         field_name='site__group',
         field_name='site__group',
         lookup_expr='in',
         lookup_expr='in',
-        label='Site group (ID)',
+        label=_('Site group (ID)'),
     )
     )
     site_group = TreeNodeMultipleChoiceFilter(
     site_group = TreeNodeMultipleChoiceFilter(
         queryset=SiteGroup.objects.all(),
         queryset=SiteGroup.objects.all(),
         field_name='site__group',
         field_name='site__group',
         lookup_expr='in',
         lookup_expr='in',
         to_field_name='slug',
         to_field_name='slug',
-        label='Site group (slug)',
+        label=_('Site group (slug)'),
     )
     )
     site_id = django_filters.ModelMultipleChoiceFilter(
     site_id = django_filters.ModelMultipleChoiceFilter(
         queryset=Site.objects.all(),
         queryset=Site.objects.all(),
-        label='Site (ID)',
+        label=_('Site (ID)'),
     )
     )
     site = django_filters.ModelMultipleChoiceFilter(
     site = django_filters.ModelMultipleChoiceFilter(
         field_name='site__slug',
         field_name='site__slug',
         queryset=Site.objects.all(),
         queryset=Site.objects.all(),
         to_field_name='slug',
         to_field_name='slug',
-        label='Site (slug)',
+        label=_('Site (slug)'),
     )
     )
     vlan_id = django_filters.ModelMultipleChoiceFilter(
     vlan_id = django_filters.ModelMultipleChoiceFilter(
         queryset=VLAN.objects.all(),
         queryset=VLAN.objects.all(),
-        label='VLAN (ID)',
+        label=_('VLAN (ID)'),
     )
     )
     vlan_vid = django_filters.NumberFilter(
     vlan_vid = django_filters.NumberFilter(
         field_name='vlan__vid',
         field_name='vlan__vid',
-        label='VLAN number (1-4094)',
+        label=_('VLAN number (1-4094)'),
     )
     )
     role_id = django_filters.ModelMultipleChoiceFilter(
     role_id = django_filters.ModelMultipleChoiceFilter(
         queryset=Role.objects.all(),
         queryset=Role.objects.all(),
-        label='Role (ID)',
+        label=_('Role (ID)'),
     )
     )
     role = django_filters.ModelMultipleChoiceFilter(
     role = django_filters.ModelMultipleChoiceFilter(
         field_name='role__slug',
         field_name='role__slug',
         queryset=Role.objects.all(),
         queryset=Role.objects.all(),
         to_field_name='slug',
         to_field_name='slug',
-        label='Role (slug)',
+        label=_('Role (slug)'),
     )
     )
     status = django_filters.MultipleChoiceFilter(
     status = django_filters.MultipleChoiceFilter(
         choices=PrefixStatusChoices,
         choices=PrefixStatusChoices,
@@ -406,27 +407,27 @@ class IPRangeFilterSet(TenancyFilterSet, NetBoxModelFilterSet):
     )
     )
     contains = django_filters.CharFilter(
     contains = django_filters.CharFilter(
         method='search_contains',
         method='search_contains',
-        label='Ranges which contain this prefix or IP',
+        label=_('Ranges which contain this prefix or IP'),
     )
     )
     vrf_id = django_filters.ModelMultipleChoiceFilter(
     vrf_id = django_filters.ModelMultipleChoiceFilter(
         queryset=VRF.objects.all(),
         queryset=VRF.objects.all(),
-        label='VRF',
+        label=_('VRF'),
     )
     )
     vrf = django_filters.ModelMultipleChoiceFilter(
     vrf = django_filters.ModelMultipleChoiceFilter(
         field_name='vrf__rd',
         field_name='vrf__rd',
         queryset=VRF.objects.all(),
         queryset=VRF.objects.all(),
         to_field_name='rd',
         to_field_name='rd',
-        label='VRF (RD)',
+        label=_('VRF (RD)'),
     )
     )
     role_id = django_filters.ModelMultipleChoiceFilter(
     role_id = django_filters.ModelMultipleChoiceFilter(
         queryset=Role.objects.all(),
         queryset=Role.objects.all(),
-        label='Role (ID)',
+        label=_('Role (ID)'),
     )
     )
     role = django_filters.ModelMultipleChoiceFilter(
     role = django_filters.ModelMultipleChoiceFilter(
         field_name='role__slug',
         field_name='role__slug',
         queryset=Role.objects.all(),
         queryset=Role.objects.all(),
         to_field_name='slug',
         to_field_name='slug',
-        label='Role (slug)',
+        label=_('Role (slug)'),
     )
     )
     status = django_filters.MultipleChoiceFilter(
     status = django_filters.MultipleChoiceFilter(
         choices=IPRangeStatusChoices,
         choices=IPRangeStatusChoices,
@@ -468,87 +469,87 @@ class IPAddressFilterSet(NetBoxModelFilterSet, TenancyFilterSet):
     )
     )
     parent = MultiValueCharFilter(
     parent = MultiValueCharFilter(
         method='search_by_parent',
         method='search_by_parent',
-        label='Parent prefix',
+        label=_('Parent prefix'),
     )
     )
     address = MultiValueCharFilter(
     address = MultiValueCharFilter(
         method='filter_address',
         method='filter_address',
-        label='Address',
+        label=_('Address'),
     )
     )
     mask_length = django_filters.NumberFilter(
     mask_length = django_filters.NumberFilter(
         method='filter_mask_length',
         method='filter_mask_length',
-        label='Mask length',
+        label=_('Mask length'),
     )
     )
     vrf_id = django_filters.ModelMultipleChoiceFilter(
     vrf_id = django_filters.ModelMultipleChoiceFilter(
         queryset=VRF.objects.all(),
         queryset=VRF.objects.all(),
-        label='VRF',
+        label=_('VRF'),
     )
     )
     vrf = django_filters.ModelMultipleChoiceFilter(
     vrf = django_filters.ModelMultipleChoiceFilter(
         field_name='vrf__rd',
         field_name='vrf__rd',
         queryset=VRF.objects.all(),
         queryset=VRF.objects.all(),
         to_field_name='rd',
         to_field_name='rd',
-        label='VRF (RD)',
+        label=_('VRF (RD)'),
     )
     )
     present_in_vrf_id = django_filters.ModelChoiceFilter(
     present_in_vrf_id = django_filters.ModelChoiceFilter(
         queryset=VRF.objects.all(),
         queryset=VRF.objects.all(),
         method='filter_present_in_vrf',
         method='filter_present_in_vrf',
-        label='VRF'
+        label=_('VRF')
     )
     )
     present_in_vrf = django_filters.ModelChoiceFilter(
     present_in_vrf = django_filters.ModelChoiceFilter(
         queryset=VRF.objects.all(),
         queryset=VRF.objects.all(),
         method='filter_present_in_vrf',
         method='filter_present_in_vrf',
         to_field_name='rd',
         to_field_name='rd',
-        label='VRF (RD)',
+        label=_('VRF (RD)'),
     )
     )
     device = MultiValueCharFilter(
     device = MultiValueCharFilter(
         method='filter_device',
         method='filter_device',
         field_name='name',
         field_name='name',
-        label='Device (name)',
+        label=_('Device (name)'),
     )
     )
     device_id = MultiValueNumberFilter(
     device_id = MultiValueNumberFilter(
         method='filter_device',
         method='filter_device',
         field_name='pk',
         field_name='pk',
-        label='Device (ID)',
+        label=_('Device (ID)'),
     )
     )
     virtual_machine = MultiValueCharFilter(
     virtual_machine = MultiValueCharFilter(
         method='filter_virtual_machine',
         method='filter_virtual_machine',
         field_name='name',
         field_name='name',
-        label='Virtual machine (name)',
+        label=_('Virtual machine (name)'),
     )
     )
     virtual_machine_id = MultiValueNumberFilter(
     virtual_machine_id = MultiValueNumberFilter(
         method='filter_virtual_machine',
         method='filter_virtual_machine',
         field_name='pk',
         field_name='pk',
-        label='Virtual machine (ID)',
+        label=_('Virtual machine (ID)'),
     )
     )
     interface = django_filters.ModelMultipleChoiceFilter(
     interface = django_filters.ModelMultipleChoiceFilter(
         field_name='interface__name',
         field_name='interface__name',
         queryset=Interface.objects.all(),
         queryset=Interface.objects.all(),
         to_field_name='name',
         to_field_name='name',
-        label='Interface (name)',
+        label=_('Interface (name)'),
     )
     )
     interface_id = django_filters.ModelMultipleChoiceFilter(
     interface_id = django_filters.ModelMultipleChoiceFilter(
         field_name='interface',
         field_name='interface',
         queryset=Interface.objects.all(),
         queryset=Interface.objects.all(),
-        label='Interface (ID)',
+        label=_('Interface (ID)'),
     )
     )
     vminterface = django_filters.ModelMultipleChoiceFilter(
     vminterface = django_filters.ModelMultipleChoiceFilter(
         field_name='vminterface__name',
         field_name='vminterface__name',
         queryset=VMInterface.objects.all(),
         queryset=VMInterface.objects.all(),
         to_field_name='name',
         to_field_name='name',
-        label='VM interface (name)',
+        label=_('VM interface (name)'),
     )
     )
     vminterface_id = django_filters.ModelMultipleChoiceFilter(
     vminterface_id = django_filters.ModelMultipleChoiceFilter(
         field_name='vminterface',
         field_name='vminterface',
         queryset=VMInterface.objects.all(),
         queryset=VMInterface.objects.all(),
-        label='VM interface (ID)',
+        label=_('VM interface (ID)'),
     )
     )
     fhrpgroup_id = django_filters.ModelMultipleChoiceFilter(
     fhrpgroup_id = django_filters.ModelMultipleChoiceFilter(
         field_name='fhrpgroup',
         field_name='fhrpgroup',
         queryset=FHRPGroup.objects.all(),
         queryset=FHRPGroup.objects.all(),
-        label='FHRP group (ID)',
+        label=_('FHRP group (ID)'),
     )
     )
     assigned_to_interface = django_filters.BooleanFilter(
     assigned_to_interface = django_filters.BooleanFilter(
         method='_assigned_to_interface',
         method='_assigned_to_interface',
-        label='Is assigned to an interface',
+        label=_('Is assigned to an interface'),
     )
     )
     status = django_filters.MultipleChoiceFilter(
     status = django_filters.MultipleChoiceFilter(
         choices=IPAddressStatusChoices,
         choices=IPAddressStatusChoices,
@@ -688,27 +689,27 @@ class FHRPGroupAssignmentFilterSet(ChangeLoggedModelFilterSet):
     interface_type = ContentTypeFilter()
     interface_type = ContentTypeFilter()
     group_id = django_filters.ModelMultipleChoiceFilter(
     group_id = django_filters.ModelMultipleChoiceFilter(
         queryset=FHRPGroup.objects.all(),
         queryset=FHRPGroup.objects.all(),
-        label='Group (ID)',
+        label=_('Group (ID)'),
     )
     )
     device = MultiValueCharFilter(
     device = MultiValueCharFilter(
         method='filter_device',
         method='filter_device',
         field_name='name',
         field_name='name',
-        label='Device (name)',
+        label=_('Device (name)'),
     )
     )
     device_id = MultiValueNumberFilter(
     device_id = MultiValueNumberFilter(
         method='filter_device',
         method='filter_device',
         field_name='pk',
         field_name='pk',
-        label='Device (ID)',
+        label=_('Device (ID)'),
     )
     )
     virtual_machine = MultiValueCharFilter(
     virtual_machine = MultiValueCharFilter(
         method='filter_virtual_machine',
         method='filter_virtual_machine',
         field_name='name',
         field_name='name',
-        label='Virtual machine (name)',
+        label=_('Virtual machine (name)'),
     )
     )
     virtual_machine_id = MultiValueNumberFilter(
     virtual_machine_id = MultiValueNumberFilter(
         method='filter_virtual_machine',
         method='filter_virtual_machine',
         field_name='pk',
         field_name='pk',
-        label='Virtual machine (ID)',
+        label=_('Virtual machine (ID)'),
     )
     )
 
 
     class Meta:
     class Meta:
@@ -787,57 +788,57 @@ class VLANFilterSet(NetBoxModelFilterSet, TenancyFilterSet):
         queryset=Region.objects.all(),
         queryset=Region.objects.all(),
         field_name='site__region',
         field_name='site__region',
         lookup_expr='in',
         lookup_expr='in',
-        label='Region (ID)',
+        label=_('Region (ID)'),
     )
     )
     region = TreeNodeMultipleChoiceFilter(
     region = TreeNodeMultipleChoiceFilter(
         queryset=Region.objects.all(),
         queryset=Region.objects.all(),
         field_name='site__region',
         field_name='site__region',
         lookup_expr='in',
         lookup_expr='in',
         to_field_name='slug',
         to_field_name='slug',
-        label='Region (slug)',
+        label=_('Region (slug)'),
     )
     )
     site_group_id = TreeNodeMultipleChoiceFilter(
     site_group_id = TreeNodeMultipleChoiceFilter(
         queryset=SiteGroup.objects.all(),
         queryset=SiteGroup.objects.all(),
         field_name='site__group',
         field_name='site__group',
         lookup_expr='in',
         lookup_expr='in',
-        label='Site group (ID)',
+        label=_('Site group (ID)'),
     )
     )
     site_group = TreeNodeMultipleChoiceFilter(
     site_group = TreeNodeMultipleChoiceFilter(
         queryset=SiteGroup.objects.all(),
         queryset=SiteGroup.objects.all(),
         field_name='site__group',
         field_name='site__group',
         lookup_expr='in',
         lookup_expr='in',
         to_field_name='slug',
         to_field_name='slug',
-        label='Site group (slug)',
+        label=_('Site group (slug)'),
     )
     )
     site_id = django_filters.ModelMultipleChoiceFilter(
     site_id = django_filters.ModelMultipleChoiceFilter(
         queryset=Site.objects.all(),
         queryset=Site.objects.all(),
-        label='Site (ID)',
+        label=_('Site (ID)'),
     )
     )
     site = django_filters.ModelMultipleChoiceFilter(
     site = django_filters.ModelMultipleChoiceFilter(
         field_name='site__slug',
         field_name='site__slug',
         queryset=Site.objects.all(),
         queryset=Site.objects.all(),
         to_field_name='slug',
         to_field_name='slug',
-        label='Site (slug)',
+        label=_('Site (slug)'),
     )
     )
     group_id = django_filters.ModelMultipleChoiceFilter(
     group_id = django_filters.ModelMultipleChoiceFilter(
         queryset=VLANGroup.objects.all(),
         queryset=VLANGroup.objects.all(),
-        label='Group (ID)',
+        label=_('Group (ID)'),
     )
     )
     group = django_filters.ModelMultipleChoiceFilter(
     group = django_filters.ModelMultipleChoiceFilter(
         field_name='group__slug',
         field_name='group__slug',
         queryset=VLANGroup.objects.all(),
         queryset=VLANGroup.objects.all(),
         to_field_name='slug',
         to_field_name='slug',
-        label='Group',
+        label=_('Group'),
     )
     )
     role_id = django_filters.ModelMultipleChoiceFilter(
     role_id = django_filters.ModelMultipleChoiceFilter(
         queryset=Role.objects.all(),
         queryset=Role.objects.all(),
-        label='Role (ID)',
+        label=_('Role (ID)'),
     )
     )
     role = django_filters.ModelMultipleChoiceFilter(
     role = django_filters.ModelMultipleChoiceFilter(
         field_name='role__slug',
         field_name='role__slug',
         queryset=Role.objects.all(),
         queryset=Role.objects.all(),
         to_field_name='slug',
         to_field_name='slug',
-        label='Role (slug)',
+        label=_('Role (slug)'),
     )
     )
     status = django_filters.MultipleChoiceFilter(
     status = django_filters.MultipleChoiceFilter(
         choices=VLANStatusChoices,
         choices=VLANStatusChoices,
@@ -893,23 +894,23 @@ class ServiceTemplateFilterSet(NetBoxModelFilterSet):
 class ServiceFilterSet(NetBoxModelFilterSet):
 class ServiceFilterSet(NetBoxModelFilterSet):
     device_id = django_filters.ModelMultipleChoiceFilter(
     device_id = django_filters.ModelMultipleChoiceFilter(
         queryset=Device.objects.all(),
         queryset=Device.objects.all(),
-        label='Device (ID)',
+        label=_('Device (ID)'),
     )
     )
     device = django_filters.ModelMultipleChoiceFilter(
     device = django_filters.ModelMultipleChoiceFilter(
         field_name='device__name',
         field_name='device__name',
         queryset=Device.objects.all(),
         queryset=Device.objects.all(),
         to_field_name='name',
         to_field_name='name',
-        label='Device (name)',
+        label=_('Device (name)'),
     )
     )
     virtual_machine_id = django_filters.ModelMultipleChoiceFilter(
     virtual_machine_id = django_filters.ModelMultipleChoiceFilter(
         queryset=VirtualMachine.objects.all(),
         queryset=VirtualMachine.objects.all(),
-        label='Virtual machine (ID)',
+        label=_('Virtual machine (ID)'),
     )
     )
     virtual_machine = django_filters.ModelMultipleChoiceFilter(
     virtual_machine = django_filters.ModelMultipleChoiceFilter(
         field_name='virtual_machine__name',
         field_name='virtual_machine__name',
         queryset=VirtualMachine.objects.all(),
         queryset=VirtualMachine.objects.all(),
         to_field_name='name',
         to_field_name='name',
-        label='Virtual machine (name)',
+        label=_('Virtual machine (name)'),
     )
     )
     port = NumericArrayFilter(
     port = NumericArrayFilter(
         field_name='ports',
         field_name='ports',
@@ -939,24 +940,24 @@ class L2VPNFilterSet(NetBoxModelFilterSet, TenancyFilterSet):
     import_target_id = django_filters.ModelMultipleChoiceFilter(
     import_target_id = django_filters.ModelMultipleChoiceFilter(
         field_name='import_targets',
         field_name='import_targets',
         queryset=RouteTarget.objects.all(),
         queryset=RouteTarget.objects.all(),
-        label='Import target',
+        label=_('Import target'),
     )
     )
     import_target = django_filters.ModelMultipleChoiceFilter(
     import_target = django_filters.ModelMultipleChoiceFilter(
         field_name='import_targets__name',
         field_name='import_targets__name',
         queryset=RouteTarget.objects.all(),
         queryset=RouteTarget.objects.all(),
         to_field_name='name',
         to_field_name='name',
-        label='Import target (name)',
+        label=_('Import target (name)'),
     )
     )
     export_target_id = django_filters.ModelMultipleChoiceFilter(
     export_target_id = django_filters.ModelMultipleChoiceFilter(
         field_name='export_targets',
         field_name='export_targets',
         queryset=RouteTarget.objects.all(),
         queryset=RouteTarget.objects.all(),
-        label='Export target',
+        label=_('Export target'),
     )
     )
     export_target = django_filters.ModelMultipleChoiceFilter(
     export_target = django_filters.ModelMultipleChoiceFilter(
         field_name='export_targets__name',
         field_name='export_targets__name',
         queryset=RouteTarget.objects.all(),
         queryset=RouteTarget.objects.all(),
         to_field_name='name',
         to_field_name='name',
-        label='Export target (name)',
+        label=_('Export target (name)'),
     )
     )
 
 
     class Meta:
     class Meta:
@@ -977,92 +978,92 @@ class L2VPNFilterSet(NetBoxModelFilterSet, TenancyFilterSet):
 class L2VPNTerminationFilterSet(NetBoxModelFilterSet):
 class L2VPNTerminationFilterSet(NetBoxModelFilterSet):
     l2vpn_id = django_filters.ModelMultipleChoiceFilter(
     l2vpn_id = django_filters.ModelMultipleChoiceFilter(
         queryset=L2VPN.objects.all(),
         queryset=L2VPN.objects.all(),
-        label='L2VPN (ID)',
+        label=_('L2VPN (ID)'),
     )
     )
     l2vpn = django_filters.ModelMultipleChoiceFilter(
     l2vpn = django_filters.ModelMultipleChoiceFilter(
         field_name='l2vpn__slug',
         field_name='l2vpn__slug',
         queryset=L2VPN.objects.all(),
         queryset=L2VPN.objects.all(),
         to_field_name='slug',
         to_field_name='slug',
-        label='L2VPN (slug)',
+        label=_('L2VPN (slug)'),
     )
     )
     region = MultiValueCharFilter(
     region = MultiValueCharFilter(
         method='filter_region',
         method='filter_region',
         field_name='slug',
         field_name='slug',
-        label='Region (slug)',
+        label=_('Region (slug)'),
     )
     )
     region_id = MultiValueNumberFilter(
     region_id = MultiValueNumberFilter(
         method='filter_region',
         method='filter_region',
         field_name='pk',
         field_name='pk',
-        label='Region (ID)',
+        label=_('Region (ID)'),
     )
     )
     site = MultiValueCharFilter(
     site = MultiValueCharFilter(
         method='filter_site',
         method='filter_site',
         field_name='slug',
         field_name='slug',
-        label='Site (slug)',
+        label=_('Site (slug)'),
     )
     )
     site_id = MultiValueNumberFilter(
     site_id = MultiValueNumberFilter(
         method='filter_site',
         method='filter_site',
         field_name='pk',
         field_name='pk',
-        label='Site (ID)',
+        label=_('Site (ID)'),
     )
     )
     device = django_filters.ModelMultipleChoiceFilter(
     device = django_filters.ModelMultipleChoiceFilter(
         field_name='interface__device__name',
         field_name='interface__device__name',
         queryset=Device.objects.all(),
         queryset=Device.objects.all(),
         to_field_name='name',
         to_field_name='name',
-        label='Device (name)',
+        label=_('Device (name)'),
     )
     )
     device_id = django_filters.ModelMultipleChoiceFilter(
     device_id = django_filters.ModelMultipleChoiceFilter(
         field_name='interface__device',
         field_name='interface__device',
         queryset=Device.objects.all(),
         queryset=Device.objects.all(),
-        label='Device (ID)',
+        label=_('Device (ID)'),
     )
     )
     virtual_machine = django_filters.ModelMultipleChoiceFilter(
     virtual_machine = django_filters.ModelMultipleChoiceFilter(
         field_name='vminterface__virtual_machine__name',
         field_name='vminterface__virtual_machine__name',
         queryset=VirtualMachine.objects.all(),
         queryset=VirtualMachine.objects.all(),
         to_field_name='name',
         to_field_name='name',
-        label='Virtual machine (name)',
+        label=_('Virtual machine (name)'),
     )
     )
     virtual_machine_id = django_filters.ModelMultipleChoiceFilter(
     virtual_machine_id = django_filters.ModelMultipleChoiceFilter(
         field_name='vminterface__virtual_machine',
         field_name='vminterface__virtual_machine',
         queryset=VirtualMachine.objects.all(),
         queryset=VirtualMachine.objects.all(),
-        label='Virtual machine (ID)',
+        label=_('Virtual machine (ID)'),
     )
     )
     interface = django_filters.ModelMultipleChoiceFilter(
     interface = django_filters.ModelMultipleChoiceFilter(
         field_name='interface__name',
         field_name='interface__name',
         queryset=Interface.objects.all(),
         queryset=Interface.objects.all(),
         to_field_name='name',
         to_field_name='name',
-        label='Interface (name)',
+        label=_('Interface (name)'),
     )
     )
     interface_id = django_filters.ModelMultipleChoiceFilter(
     interface_id = django_filters.ModelMultipleChoiceFilter(
         field_name='interface',
         field_name='interface',
         queryset=Interface.objects.all(),
         queryset=Interface.objects.all(),
-        label='Interface (ID)',
+        label=_('Interface (ID)'),
     )
     )
     vminterface = django_filters.ModelMultipleChoiceFilter(
     vminterface = django_filters.ModelMultipleChoiceFilter(
         field_name='vminterface__name',
         field_name='vminterface__name',
         queryset=VMInterface.objects.all(),
         queryset=VMInterface.objects.all(),
         to_field_name='name',
         to_field_name='name',
-        label='VM interface (name)',
+        label=_('VM interface (name)'),
     )
     )
     vminterface_id = django_filters.ModelMultipleChoiceFilter(
     vminterface_id = django_filters.ModelMultipleChoiceFilter(
         field_name='vminterface',
         field_name='vminterface',
         queryset=VMInterface.objects.all(),
         queryset=VMInterface.objects.all(),
-        label='VM Interface (ID)',
+        label=_('VM Interface (ID)'),
     )
     )
     vlan = django_filters.ModelMultipleChoiceFilter(
     vlan = django_filters.ModelMultipleChoiceFilter(
         field_name='vlan__name',
         field_name='vlan__name',
         queryset=VLAN.objects.all(),
         queryset=VLAN.objects.all(),
         to_field_name='name',
         to_field_name='name',
-        label='VLAN (name)',
+        label=_('VLAN (name)'),
     )
     )
     vlan_vid = django_filters.NumberFilter(
     vlan_vid = django_filters.NumberFilter(
         field_name='vlan__vid',
         field_name='vlan__vid',
-        label='VLAN number (1-4094)',
+        label=_('VLAN number (1-4094)'),
     )
     )
     vlan_id = django_filters.ModelMultipleChoiceFilter(
     vlan_id = django_filters.ModelMultipleChoiceFilter(
         field_name='vlan',
         field_name='vlan',
         queryset=VLAN.objects.all(),
         queryset=VLAN.objects.all(),
-        label='VLAN (ID)',
+        label=_('VLAN (ID)'),
     )
     )
     assigned_object_type = ContentTypeFilter()
     assigned_object_type = ContentTypeFilter()
 
 

+ 2 - 1
netbox/ipam/forms/bulk_create.py

@@ -1,4 +1,5 @@
 from django import forms
 from django import forms
+from django.utils.translation import gettext as _
 
 
 from utilities.forms import BootstrapMixin, ExpandableIPAddressField
 from utilities.forms import BootstrapMixin, ExpandableIPAddressField
 
 
@@ -9,5 +10,5 @@ __all__ = (
 
 
 class IPAddressBulkCreateForm(BootstrapMixin, forms.Form):
 class IPAddressBulkCreateForm(BootstrapMixin, forms.Form):
     pattern = ExpandableIPAddressField(
     pattern = ExpandableIPAddressField(
-        label='Address pattern'
+        label=_('Address pattern')
     )
     )

+ 15 - 14
netbox/ipam/forms/bulk_edit.py

@@ -1,4 +1,5 @@
 from django import forms
 from django import forms
+from django.utils.translation import gettext as _
 
 
 from dcim.models import Region, Site, SiteGroup
 from dcim.models import Region, Site, SiteGroup
 from ipam.choices import *
 from ipam.choices import *
@@ -40,7 +41,7 @@ class VRFBulkEditForm(NetBoxModelBulkEditForm):
     enforce_unique = forms.NullBooleanField(
     enforce_unique = forms.NullBooleanField(
         required=False,
         required=False,
         widget=BulkEditNullBooleanSelect(),
         widget=BulkEditNullBooleanSelect(),
-        label='Enforce unique space'
+        label=_('Enforce unique space')
     )
     )
     description = forms.CharField(
     description = forms.CharField(
         max_length=200,
         max_length=200,
@@ -104,7 +105,7 @@ class ASNBulkEditForm(NetBoxModelBulkEditForm):
     rir = DynamicModelChoiceField(
     rir = DynamicModelChoiceField(
         queryset=RIR.objects.all(),
         queryset=RIR.objects.all(),
         required=False,
         required=False,
-        label='RIR'
+        label=_('RIR')
     )
     )
     tenant = DynamicModelChoiceField(
     tenant = DynamicModelChoiceField(
         queryset=Tenant.objects.all(),
         queryset=Tenant.objects.all(),
@@ -130,7 +131,7 @@ class AggregateBulkEditForm(NetBoxModelBulkEditForm):
     rir = DynamicModelChoiceField(
     rir = DynamicModelChoiceField(
         queryset=RIR.objects.all(),
         queryset=RIR.objects.all(),
         required=False,
         required=False,
-        label='RIR'
+        label=_('RIR')
     )
     )
     tenant = DynamicModelChoiceField(
     tenant = DynamicModelChoiceField(
         queryset=Tenant.objects.all(),
         queryset=Tenant.objects.all(),
@@ -191,7 +192,7 @@ class PrefixBulkEditForm(NetBoxModelBulkEditForm):
     vrf = DynamicModelChoiceField(
     vrf = DynamicModelChoiceField(
         queryset=VRF.objects.all(),
         queryset=VRF.objects.all(),
         required=False,
         required=False,
-        label='VRF'
+        label=_('VRF')
     )
     )
     prefix_length = forms.IntegerField(
     prefix_length = forms.IntegerField(
         min_value=PREFIX_LENGTH_MIN,
         min_value=PREFIX_LENGTH_MIN,
@@ -214,12 +215,12 @@ class PrefixBulkEditForm(NetBoxModelBulkEditForm):
     is_pool = forms.NullBooleanField(
     is_pool = forms.NullBooleanField(
         required=False,
         required=False,
         widget=BulkEditNullBooleanSelect(),
         widget=BulkEditNullBooleanSelect(),
-        label='Is a pool'
+        label=_('Is a pool')
     )
     )
     mark_utilized = forms.NullBooleanField(
     mark_utilized = forms.NullBooleanField(
         required=False,
         required=False,
         widget=BulkEditNullBooleanSelect(),
         widget=BulkEditNullBooleanSelect(),
-        label='Treat as 100% utilized'
+        label=_('Treat as 100% utilized')
     )
     )
     description = forms.CharField(
     description = forms.CharField(
         max_length=200,
         max_length=200,
@@ -245,7 +246,7 @@ class IPRangeBulkEditForm(NetBoxModelBulkEditForm):
     vrf = DynamicModelChoiceField(
     vrf = DynamicModelChoiceField(
         queryset=VRF.objects.all(),
         queryset=VRF.objects.all(),
         required=False,
         required=False,
-        label='VRF'
+        label=_('VRF')
     )
     )
     tenant = DynamicModelChoiceField(
     tenant = DynamicModelChoiceField(
         queryset=Tenant.objects.all(),
         queryset=Tenant.objects.all(),
@@ -282,7 +283,7 @@ class IPAddressBulkEditForm(NetBoxModelBulkEditForm):
     vrf = DynamicModelChoiceField(
     vrf = DynamicModelChoiceField(
         queryset=VRF.objects.all(),
         queryset=VRF.objects.all(),
         required=False,
         required=False,
-        label='VRF'
+        label=_('VRF')
     )
     )
     mask_length = forms.IntegerField(
     mask_length = forms.IntegerField(
         min_value=IPADDRESS_MASK_LENGTH_MIN,
         min_value=IPADDRESS_MASK_LENGTH_MIN,
@@ -306,7 +307,7 @@ class IPAddressBulkEditForm(NetBoxModelBulkEditForm):
     dns_name = forms.CharField(
     dns_name = forms.CharField(
         max_length=255,
         max_length=255,
         required=False,
         required=False,
-        label='DNS name'
+        label=_('DNS name')
     )
     )
     description = forms.CharField(
     description = forms.CharField(
         max_length=200,
         max_length=200,
@@ -336,18 +337,18 @@ class FHRPGroupBulkEditForm(NetBoxModelBulkEditForm):
     group_id = forms.IntegerField(
     group_id = forms.IntegerField(
         min_value=0,
         min_value=0,
         required=False,
         required=False,
-        label='Group ID'
+        label=_('Group ID')
     )
     )
     auth_type = forms.ChoiceField(
     auth_type = forms.ChoiceField(
         choices=add_blank_choice(FHRPGroupAuthTypeChoices),
         choices=add_blank_choice(FHRPGroupAuthTypeChoices),
         required=False,
         required=False,
         widget=StaticSelect(),
         widget=StaticSelect(),
-        label='Authentication type'
+        label=_('Authentication type')
     )
     )
     auth_key = forms.CharField(
     auth_key = forms.CharField(
         max_length=255,
         max_length=255,
         required=False,
         required=False,
-        label='Authentication key'
+        label=_('Authentication key')
     )
     )
     name = forms.CharField(
     name = forms.CharField(
         max_length=100,
         max_length=100,
@@ -379,13 +380,13 @@ class VLANGroupBulkEditForm(NetBoxModelBulkEditForm):
         min_value=VLAN_VID_MIN,
         min_value=VLAN_VID_MIN,
         max_value=VLAN_VID_MAX,
         max_value=VLAN_VID_MAX,
         required=False,
         required=False,
-        label='Minimum child VLAN VID'
+        label=_('Minimum child VLAN VID')
     )
     )
     max_vid = forms.IntegerField(
     max_vid = forms.IntegerField(
         min_value=VLAN_VID_MIN,
         min_value=VLAN_VID_MIN,
         max_value=VLAN_VID_MAX,
         max_value=VLAN_VID_MAX,
         required=False,
         required=False,
-        label='Maximum child VLAN VID'
+        label=_('Maximum child VLAN VID')
     )
     )
     description = forms.CharField(
     description = forms.CharField(
         max_length=200,
         max_length=200,

+ 43 - 42
netbox/ipam/forms/bulk_import.py

@@ -1,6 +1,7 @@
 from django import forms
 from django import forms
 from django.contrib.contenttypes.models import ContentType
 from django.contrib.contenttypes.models import ContentType
 from django.core.exceptions import ValidationError
 from django.core.exceptions import ValidationError
+from django.utils.translation import gettext as _
 
 
 from dcim.models import Device, Interface, Site
 from dcim.models import Device, Interface, Site
 from ipam.choices import *
 from ipam.choices import *
@@ -36,7 +37,7 @@ class VRFCSVForm(NetBoxModelCSVForm):
         queryset=Tenant.objects.all(),
         queryset=Tenant.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
-        help_text='Assigned tenant'
+        help_text=_('Assigned tenant')
     )
     )
 
 
     class Meta:
     class Meta:
@@ -49,7 +50,7 @@ class RouteTargetCSVForm(NetBoxModelCSVForm):
         queryset=Tenant.objects.all(),
         queryset=Tenant.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
-        help_text='Assigned tenant'
+        help_text=_('Assigned tenant')
     )
     )
 
 
     class Meta:
     class Meta:
@@ -64,7 +65,7 @@ class RIRCSVForm(NetBoxModelCSVForm):
         model = RIR
         model = RIR
         fields = ('name', 'slug', 'is_private', 'description', 'tags')
         fields = ('name', 'slug', 'is_private', 'description', 'tags')
         help_texts = {
         help_texts = {
-            'name': 'RIR name',
+            'name': _('RIR name'),
         }
         }
 
 
 
 
@@ -72,13 +73,13 @@ class AggregateCSVForm(NetBoxModelCSVForm):
     rir = CSVModelChoiceField(
     rir = CSVModelChoiceField(
         queryset=RIR.objects.all(),
         queryset=RIR.objects.all(),
         to_field_name='name',
         to_field_name='name',
-        help_text='Assigned RIR'
+        help_text=_('Assigned RIR')
     )
     )
     tenant = CSVModelChoiceField(
     tenant = CSVModelChoiceField(
         queryset=Tenant.objects.all(),
         queryset=Tenant.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
-        help_text='Assigned tenant'
+        help_text=_('Assigned tenant')
     )
     )
 
 
     class Meta:
     class Meta:
@@ -90,13 +91,13 @@ class ASNCSVForm(NetBoxModelCSVForm):
     rir = CSVModelChoiceField(
     rir = CSVModelChoiceField(
         queryset=RIR.objects.all(),
         queryset=RIR.objects.all(),
         to_field_name='name',
         to_field_name='name',
-        help_text='Assigned RIR'
+        help_text=_('Assigned RIR')
     )
     )
     tenant = CSVModelChoiceField(
     tenant = CSVModelChoiceField(
         queryset=Tenant.objects.all(),
         queryset=Tenant.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
-        help_text='Assigned tenant'
+        help_text=_('Assigned tenant')
     )
     )
 
 
     class Meta:
     class Meta:
@@ -117,41 +118,41 @@ class PrefixCSVForm(NetBoxModelCSVForm):
         queryset=VRF.objects.all(),
         queryset=VRF.objects.all(),
         to_field_name='name',
         to_field_name='name',
         required=False,
         required=False,
-        help_text='Assigned VRF'
+        help_text=_('Assigned VRF')
     )
     )
     tenant = CSVModelChoiceField(
     tenant = CSVModelChoiceField(
         queryset=Tenant.objects.all(),
         queryset=Tenant.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
-        help_text='Assigned tenant'
+        help_text=_('Assigned tenant')
     )
     )
     site = CSVModelChoiceField(
     site = CSVModelChoiceField(
         queryset=Site.objects.all(),
         queryset=Site.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
-        help_text='Assigned site'
+        help_text=_('Assigned site')
     )
     )
     vlan_group = CSVModelChoiceField(
     vlan_group = CSVModelChoiceField(
         queryset=VLANGroup.objects.all(),
         queryset=VLANGroup.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
-        help_text="VLAN's group (if any)"
+        help_text=_("VLAN's group (if any)")
     )
     )
     vlan = CSVModelChoiceField(
     vlan = CSVModelChoiceField(
         queryset=VLAN.objects.all(),
         queryset=VLAN.objects.all(),
         required=False,
         required=False,
         to_field_name='vid',
         to_field_name='vid',
-        help_text="Assigned VLAN"
+        help_text=_("Assigned VLAN")
     )
     )
     status = CSVChoiceField(
     status = CSVChoiceField(
         choices=PrefixStatusChoices,
         choices=PrefixStatusChoices,
-        help_text='Operational status'
+        help_text=_('Operational status')
     )
     )
     role = CSVModelChoiceField(
     role = CSVModelChoiceField(
         queryset=Role.objects.all(),
         queryset=Role.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
-        help_text='Functional role'
+        help_text=_('Functional role')
     )
     )
 
 
     class Meta:
     class Meta:
@@ -181,23 +182,23 @@ class IPRangeCSVForm(NetBoxModelCSVForm):
         queryset=VRF.objects.all(),
         queryset=VRF.objects.all(),
         to_field_name='name',
         to_field_name='name',
         required=False,
         required=False,
-        help_text='Assigned VRF'
+        help_text=_('Assigned VRF')
     )
     )
     tenant = CSVModelChoiceField(
     tenant = CSVModelChoiceField(
         queryset=Tenant.objects.all(),
         queryset=Tenant.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
-        help_text='Assigned tenant'
+        help_text=_('Assigned tenant')
     )
     )
     status = CSVChoiceField(
     status = CSVChoiceField(
         choices=IPRangeStatusChoices,
         choices=IPRangeStatusChoices,
-        help_text='Operational status'
+        help_text=_('Operational status')
     )
     )
     role = CSVModelChoiceField(
     role = CSVModelChoiceField(
         queryset=Role.objects.all(),
         queryset=Role.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
-        help_text='Functional role'
+        help_text=_('Functional role')
     )
     )
 
 
     class Meta:
     class Meta:
@@ -212,43 +213,43 @@ class IPAddressCSVForm(NetBoxModelCSVForm):
         queryset=VRF.objects.all(),
         queryset=VRF.objects.all(),
         to_field_name='name',
         to_field_name='name',
         required=False,
         required=False,
-        help_text='Assigned VRF'
+        help_text=_('Assigned VRF')
     )
     )
     tenant = CSVModelChoiceField(
     tenant = CSVModelChoiceField(
         queryset=Tenant.objects.all(),
         queryset=Tenant.objects.all(),
         to_field_name='name',
         to_field_name='name',
         required=False,
         required=False,
-        help_text='Assigned tenant'
+        help_text=_('Assigned tenant')
     )
     )
     status = CSVChoiceField(
     status = CSVChoiceField(
         choices=IPAddressStatusChoices,
         choices=IPAddressStatusChoices,
-        help_text='Operational status'
+        help_text=_('Operational status')
     )
     )
     role = CSVChoiceField(
     role = CSVChoiceField(
         choices=IPAddressRoleChoices,
         choices=IPAddressRoleChoices,
         required=False,
         required=False,
-        help_text='Functional role'
+        help_text=_('Functional role')
     )
     )
     device = CSVModelChoiceField(
     device = CSVModelChoiceField(
         queryset=Device.objects.all(),
         queryset=Device.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
-        help_text='Parent device of assigned interface (if any)'
+        help_text=_('Parent device of assigned interface (if any)')
     )
     )
     virtual_machine = CSVModelChoiceField(
     virtual_machine = CSVModelChoiceField(
         queryset=VirtualMachine.objects.all(),
         queryset=VirtualMachine.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
-        help_text='Parent VM of assigned interface (if any)'
+        help_text=_('Parent VM of assigned interface (if any)')
     )
     )
     interface = CSVModelChoiceField(
     interface = CSVModelChoiceField(
         queryset=Interface.objects.none(),  # Can also refer to VMInterface
         queryset=Interface.objects.none(),  # Can also refer to VMInterface
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
-        help_text='Assigned interface'
+        help_text=_('Assigned interface')
     )
     )
     is_primary = forms.BooleanField(
     is_primary = forms.BooleanField(
-        help_text='Make this the primary IP for the assigned device',
+        help_text=_('Make this the primary IP for the assigned device'),
         required=False
         required=False
     )
     )
 
 
@@ -333,7 +334,7 @@ class VLANGroupCSVForm(NetBoxModelCSVForm):
     scope_type = CSVContentTypeField(
     scope_type = CSVContentTypeField(
         queryset=ContentType.objects.filter(model__in=VLANGROUP_SCOPE_TYPES),
         queryset=ContentType.objects.filter(model__in=VLANGROUP_SCOPE_TYPES),
         required=False,
         required=False,
-        label='Scope type (app & model)'
+        label=_('Scope type (app & model)')
     )
     )
     min_vid = forms.IntegerField(
     min_vid = forms.IntegerField(
         min_value=VLAN_VID_MIN,
         min_value=VLAN_VID_MIN,
@@ -361,29 +362,29 @@ class VLANCSVForm(NetBoxModelCSVForm):
         queryset=Site.objects.all(),
         queryset=Site.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
-        help_text='Assigned site'
+        help_text=_('Assigned site')
     )
     )
     group = CSVModelChoiceField(
     group = CSVModelChoiceField(
         queryset=VLANGroup.objects.all(),
         queryset=VLANGroup.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
-        help_text='Assigned VLAN group'
+        help_text=_('Assigned VLAN group')
     )
     )
     tenant = CSVModelChoiceField(
     tenant = CSVModelChoiceField(
         queryset=Tenant.objects.all(),
         queryset=Tenant.objects.all(),
         to_field_name='name',
         to_field_name='name',
         required=False,
         required=False,
-        help_text='Assigned tenant'
+        help_text=_('Assigned tenant')
     )
     )
     status = CSVChoiceField(
     status = CSVChoiceField(
         choices=VLANStatusChoices,
         choices=VLANStatusChoices,
-        help_text='Operational status'
+        help_text=_('Operational status')
     )
     )
     role = CSVModelChoiceField(
     role = CSVModelChoiceField(
         queryset=Role.objects.all(),
         queryset=Role.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
-        help_text='Functional role'
+        help_text=_('Functional role')
     )
     )
 
 
     class Meta:
     class Meta:
@@ -398,7 +399,7 @@ class VLANCSVForm(NetBoxModelCSVForm):
 class ServiceTemplateCSVForm(NetBoxModelCSVForm):
 class ServiceTemplateCSVForm(NetBoxModelCSVForm):
     protocol = CSVChoiceField(
     protocol = CSVChoiceField(
         choices=ServiceProtocolChoices,
         choices=ServiceProtocolChoices,
-        help_text='IP protocol'
+        help_text=_('IP protocol')
     )
     )
 
 
     class Meta:
     class Meta:
@@ -411,17 +412,17 @@ class ServiceCSVForm(NetBoxModelCSVForm):
         queryset=Device.objects.all(),
         queryset=Device.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
-        help_text='Required if not assigned to a VM'
+        help_text=_('Required if not assigned to a VM')
     )
     )
     virtual_machine = CSVModelChoiceField(
     virtual_machine = CSVModelChoiceField(
         queryset=VirtualMachine.objects.all(),
         queryset=VirtualMachine.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
-        help_text='Required if not assigned to a device'
+        help_text=_('Required if not assigned to a device')
     )
     )
     protocol = CSVChoiceField(
     protocol = CSVChoiceField(
         choices=ServiceProtocolChoices,
         choices=ServiceProtocolChoices,
-        help_text='IP protocol'
+        help_text=_('IP protocol')
     )
     )
 
 
     class Meta:
     class Meta:
@@ -437,7 +438,7 @@ class L2VPNCSVForm(NetBoxModelCSVForm):
     )
     )
     type = CSVChoiceField(
     type = CSVChoiceField(
         choices=L2VPNTypeChoices,
         choices=L2VPNTypeChoices,
-        help_text='L2VPN type'
+        help_text=_('L2VPN type')
     )
     )
 
 
     class Meta:
     class Meta:
@@ -450,31 +451,31 @@ class L2VPNTerminationCSVForm(NetBoxModelCSVForm):
         queryset=L2VPN.objects.all(),
         queryset=L2VPN.objects.all(),
         required=True,
         required=True,
         to_field_name='name',
         to_field_name='name',
-        label='L2VPN',
+        label=_('L2VPN'),
     )
     )
     device = CSVModelChoiceField(
     device = CSVModelChoiceField(
         queryset=Device.objects.all(),
         queryset=Device.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
-        help_text='Parent device (for interface)'
+        help_text=_('Parent device (for interface)')
     )
     )
     virtual_machine = CSVModelChoiceField(
     virtual_machine = CSVModelChoiceField(
         queryset=VirtualMachine.objects.all(),
         queryset=VirtualMachine.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
-        help_text='Parent virtual machine (for interface)'
+        help_text=_('Parent virtual machine (for interface)')
     )
     )
     interface = CSVModelChoiceField(
     interface = CSVModelChoiceField(
         queryset=Interface.objects.none(),  # Can also refer to VMInterface
         queryset=Interface.objects.none(),  # Can also refer to VMInterface
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
-        help_text='Assigned interface (device or VM)'
+        help_text=_('Assigned interface (device or VM)')
     )
     )
     vlan = CSVModelChoiceField(
     vlan = CSVModelChoiceField(
         queryset=VLAN.objects.all(),
         queryset=VLAN.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
-        help_text='Assigned VLAN'
+        help_text=_('Assigned VLAN')
     )
     )
 
 
     class Meta:
     class Meta:

+ 2 - 2
netbox/ipam/forms/filtersets.py

@@ -397,13 +397,13 @@ class VLANGroupFilterForm(NetBoxModelFilterSetForm):
         required=False,
         required=False,
         min_value=VLAN_VID_MIN,
         min_value=VLAN_VID_MIN,
         max_value=VLAN_VID_MAX,
         max_value=VLAN_VID_MAX,
-        label='Minimum VID'
+        label=_('Minimum VID')
     )
     )
     max_vid = forms.IntegerField(
     max_vid = forms.IntegerField(
         required=False,
         required=False,
         min_value=VLAN_VID_MIN,
         min_value=VLAN_VID_MIN,
         max_value=VLAN_VID_MAX,
         max_value=VLAN_VID_MAX,
-        label='Maximum VID'
+        label=_('Maximum VID')
     )
     )
     tag = TagFilterField(model)
     tag = TagFilterField(model)
 
 

+ 53 - 52
netbox/ipam/forms/model_forms.py

@@ -1,6 +1,7 @@
 from django import forms
 from django import forms
 from django.contrib.contenttypes.models import ContentType
 from django.contrib.contenttypes.models import ContentType
 from django.core.exceptions import ValidationError
 from django.core.exceptions import ValidationError
+from django.utils.translation import gettext as _
 
 
 from dcim.models import Device, Interface, Location, Rack, Region, Site, SiteGroup
 from dcim.models import Device, Interface, Location, Rack, Region, Site, SiteGroup
 from ipam.choices import *
 from ipam.choices import *
@@ -67,7 +68,7 @@ class VRFForm(TenancyForm, NetBoxModelForm):
             'rd': "RD",
             'rd': "RD",
         }
         }
         help_texts = {
         help_texts = {
-            'rd': "Route distinguisher in any format",
+            'rd': _("Route distinguisher in any format"),
         }
         }
 
 
 
 
@@ -104,7 +105,7 @@ class RIRForm(NetBoxModelForm):
 class AggregateForm(TenancyForm, NetBoxModelForm):
 class AggregateForm(TenancyForm, NetBoxModelForm):
     rir = DynamicModelChoiceField(
     rir = DynamicModelChoiceField(
         queryset=RIR.objects.all(),
         queryset=RIR.objects.all(),
-        label='RIR'
+        label=_('RIR')
     )
     )
     comments = CommentField()
     comments = CommentField()
 
 
@@ -119,8 +120,8 @@ class AggregateForm(TenancyForm, NetBoxModelForm):
             'prefix', 'rir', 'date_added', 'tenant_group', 'tenant', 'description', 'comments', 'tags',
             'prefix', 'rir', 'date_added', 'tenant_group', 'tenant', 'description', 'comments', 'tags',
         ]
         ]
         help_texts = {
         help_texts = {
-            'prefix': "IPv4 or IPv6 network",
-            'rir': "Regional Internet Registry responsible for this prefix",
+            'prefix': _("IPv4 or IPv6 network"),
+            'rir': _("Regional Internet Registry responsible for this prefix"),
         }
         }
         widgets = {
         widgets = {
             'date_added': DatePicker(),
             'date_added': DatePicker(),
@@ -130,11 +131,11 @@ class AggregateForm(TenancyForm, NetBoxModelForm):
 class ASNForm(TenancyForm, NetBoxModelForm):
 class ASNForm(TenancyForm, NetBoxModelForm):
     rir = DynamicModelChoiceField(
     rir = DynamicModelChoiceField(
         queryset=RIR.objects.all(),
         queryset=RIR.objects.all(),
-        label='RIR',
+        label=_('RIR'),
     )
     )
     sites = DynamicModelMultipleChoiceField(
     sites = DynamicModelMultipleChoiceField(
         queryset=Site.objects.all(),
         queryset=Site.objects.all(),
-        label='Sites',
+        label=_('Sites'),
         required=False
         required=False
     )
     )
     comments = CommentField()
     comments = CommentField()
@@ -150,8 +151,8 @@ class ASNForm(TenancyForm, NetBoxModelForm):
             'asn', 'rir', 'sites', 'tenant_group', 'tenant', 'description', 'comments', 'tags'
             'asn', 'rir', 'sites', 'tenant_group', 'tenant', 'description', 'comments', 'tags'
         ]
         ]
         help_texts = {
         help_texts = {
-            'asn': "AS number",
-            'rir': "Regional Internet Registry responsible for this prefix",
+            'asn': _("AS number"),
+            'rir': _("Regional Internet Registry responsible for this prefix"),
         }
         }
         widgets = {
         widgets = {
             'date_added': DatePicker(),
             'date_added': DatePicker(),
@@ -189,7 +190,7 @@ class PrefixForm(TenancyForm, NetBoxModelForm):
     vrf = DynamicModelChoiceField(
     vrf = DynamicModelChoiceField(
         queryset=VRF.objects.all(),
         queryset=VRF.objects.all(),
         required=False,
         required=False,
-        label='VRF'
+        label=_('VRF')
     )
     )
     region = DynamicModelChoiceField(
     region = DynamicModelChoiceField(
         queryset=Region.objects.all(),
         queryset=Region.objects.all(),
@@ -217,7 +218,7 @@ class PrefixForm(TenancyForm, NetBoxModelForm):
     vlan_group = DynamicModelChoiceField(
     vlan_group = DynamicModelChoiceField(
         queryset=VLANGroup.objects.all(),
         queryset=VLANGroup.objects.all(),
         required=False,
         required=False,
-        label='VLAN group',
+        label=_('VLAN group'),
         null_option='None',
         null_option='None',
         query_params={
         query_params={
             'site': '$site'
             'site': '$site'
@@ -229,7 +230,7 @@ class PrefixForm(TenancyForm, NetBoxModelForm):
     vlan = DynamicModelChoiceField(
     vlan = DynamicModelChoiceField(
         queryset=VLAN.objects.all(),
         queryset=VLAN.objects.all(),
         required=False,
         required=False,
-        label='VLAN',
+        label=_('VLAN'),
         query_params={
         query_params={
             'site_id': '$site',
             'site_id': '$site',
             'group_id': '$vlan_group',
             'group_id': '$vlan_group',
@@ -262,7 +263,7 @@ class IPRangeForm(TenancyForm, NetBoxModelForm):
     vrf = DynamicModelChoiceField(
     vrf = DynamicModelChoiceField(
         queryset=VRF.objects.all(),
         queryset=VRF.objects.all(),
         required=False,
         required=False,
-        label='VRF'
+        label=_('VRF')
     )
     )
     role = DynamicModelChoiceField(
     role = DynamicModelChoiceField(
         queryset=Role.objects.all(),
         queryset=Role.objects.all(),
@@ -311,7 +312,7 @@ class IPAddressForm(TenancyForm, NetBoxModelForm):
     vminterface = DynamicModelChoiceField(
     vminterface = DynamicModelChoiceField(
         queryset=VMInterface.objects.all(),
         queryset=VMInterface.objects.all(),
         required=False,
         required=False,
-        label='Interface',
+        label=_('Interface'),
         query_params={
         query_params={
             'virtual_machine_id': '$virtual_machine'
             'virtual_machine_id': '$virtual_machine'
         }
         }
@@ -319,17 +320,17 @@ class IPAddressForm(TenancyForm, NetBoxModelForm):
     fhrpgroup = DynamicModelChoiceField(
     fhrpgroup = DynamicModelChoiceField(
         queryset=FHRPGroup.objects.all(),
         queryset=FHRPGroup.objects.all(),
         required=False,
         required=False,
-        label='FHRP Group'
+        label=_('FHRP Group')
     )
     )
     vrf = DynamicModelChoiceField(
     vrf = DynamicModelChoiceField(
         queryset=VRF.objects.all(),
         queryset=VRF.objects.all(),
         required=False,
         required=False,
-        label='VRF'
+        label=_('VRF')
     )
     )
     nat_region = DynamicModelChoiceField(
     nat_region = DynamicModelChoiceField(
         queryset=Region.objects.all(),
         queryset=Region.objects.all(),
         required=False,
         required=False,
-        label='Region',
+        label=_('Region'),
         initial_params={
         initial_params={
             'sites': '$nat_site'
             'sites': '$nat_site'
         }
         }
@@ -337,7 +338,7 @@ class IPAddressForm(TenancyForm, NetBoxModelForm):
     nat_site_group = DynamicModelChoiceField(
     nat_site_group = DynamicModelChoiceField(
         queryset=SiteGroup.objects.all(),
         queryset=SiteGroup.objects.all(),
         required=False,
         required=False,
-        label='Site group',
+        label=_('Site group'),
         initial_params={
         initial_params={
             'sites': '$nat_site'
             'sites': '$nat_site'
         }
         }
@@ -345,7 +346,7 @@ class IPAddressForm(TenancyForm, NetBoxModelForm):
     nat_site = DynamicModelChoiceField(
     nat_site = DynamicModelChoiceField(
         queryset=Site.objects.all(),
         queryset=Site.objects.all(),
         required=False,
         required=False,
-        label='Site',
+        label=_('Site'),
         query_params={
         query_params={
             'region_id': '$nat_region',
             'region_id': '$nat_region',
             'group_id': '$nat_site_group',
             'group_id': '$nat_site_group',
@@ -354,7 +355,7 @@ class IPAddressForm(TenancyForm, NetBoxModelForm):
     nat_rack = DynamicModelChoiceField(
     nat_rack = DynamicModelChoiceField(
         queryset=Rack.objects.all(),
         queryset=Rack.objects.all(),
         required=False,
         required=False,
-        label='Rack',
+        label=_('Rack'),
         null_option='None',
         null_option='None',
         query_params={
         query_params={
             'site_id': '$site'
             'site_id': '$site'
@@ -363,7 +364,7 @@ class IPAddressForm(TenancyForm, NetBoxModelForm):
     nat_device = DynamicModelChoiceField(
     nat_device = DynamicModelChoiceField(
         queryset=Device.objects.all(),
         queryset=Device.objects.all(),
         required=False,
         required=False,
-        label='Device',
+        label=_('Device'),
         query_params={
         query_params={
             'site_id': '$site',
             'site_id': '$site',
             'rack_id': '$nat_rack',
             'rack_id': '$nat_rack',
@@ -372,12 +373,12 @@ class IPAddressForm(TenancyForm, NetBoxModelForm):
     nat_cluster = DynamicModelChoiceField(
     nat_cluster = DynamicModelChoiceField(
         queryset=Cluster.objects.all(),
         queryset=Cluster.objects.all(),
         required=False,
         required=False,
-        label='Cluster'
+        label=_('Cluster')
     )
     )
     nat_virtual_machine = DynamicModelChoiceField(
     nat_virtual_machine = DynamicModelChoiceField(
         queryset=VirtualMachine.objects.all(),
         queryset=VirtualMachine.objects.all(),
         required=False,
         required=False,
-        label='Virtual Machine',
+        label=_('Virtual Machine'),
         query_params={
         query_params={
             'cluster_id': '$nat_cluster',
             'cluster_id': '$nat_cluster',
         }
         }
@@ -385,12 +386,12 @@ class IPAddressForm(TenancyForm, NetBoxModelForm):
     nat_vrf = DynamicModelChoiceField(
     nat_vrf = DynamicModelChoiceField(
         queryset=VRF.objects.all(),
         queryset=VRF.objects.all(),
         required=False,
         required=False,
-        label='VRF'
+        label=_('VRF')
     )
     )
     nat_inside = DynamicModelChoiceField(
     nat_inside = DynamicModelChoiceField(
         queryset=IPAddress.objects.all(),
         queryset=IPAddress.objects.all(),
         required=False,
         required=False,
-        label='IP Address',
+        label=_('IP Address'),
         query_params={
         query_params={
             'device_id': '$nat_device',
             'device_id': '$nat_device',
             'virtual_machine_id': '$nat_virtual_machine',
             'virtual_machine_id': '$nat_virtual_machine',
@@ -399,7 +400,7 @@ class IPAddressForm(TenancyForm, NetBoxModelForm):
     )
     )
     primary_for_parent = forms.BooleanField(
     primary_for_parent = forms.BooleanField(
         required=False,
         required=False,
-        label='Make this the primary IP for the device/VM'
+        label=_('Make this the primary IP for the device/VM')
     )
     )
     comments = CommentField()
     comments = CommentField()
 
 
@@ -500,7 +501,7 @@ class IPAddressBulkAddForm(TenancyForm, NetBoxModelForm):
     vrf = DynamicModelChoiceField(
     vrf = DynamicModelChoiceField(
         queryset=VRF.objects.all(),
         queryset=VRF.objects.all(),
         required=False,
         required=False,
-        label='VRF'
+        label=_('VRF')
     )
     )
 
 
     class Meta:
     class Meta:
@@ -518,11 +519,11 @@ class IPAddressAssignForm(BootstrapMixin, forms.Form):
     vrf_id = DynamicModelChoiceField(
     vrf_id = DynamicModelChoiceField(
         queryset=VRF.objects.all(),
         queryset=VRF.objects.all(),
         required=False,
         required=False,
-        label='VRF'
+        label=_('VRF')
     )
     )
     q = forms.CharField(
     q = forms.CharField(
         required=False,
         required=False,
-        label='Search',
+        label=_('Search'),
     )
     )
 
 
 
 
@@ -532,16 +533,16 @@ class FHRPGroupForm(NetBoxModelForm):
     ip_vrf = DynamicModelChoiceField(
     ip_vrf = DynamicModelChoiceField(
         queryset=VRF.objects.all(),
         queryset=VRF.objects.all(),
         required=False,
         required=False,
-        label='VRF'
+        label=_('VRF')
     )
     )
     ip_address = IPNetworkFormField(
     ip_address = IPNetworkFormField(
         required=False,
         required=False,
-        label='Address'
+        label=_('Address')
     )
     )
     ip_status = forms.ChoiceField(
     ip_status = forms.ChoiceField(
         choices=add_blank_choice(IPAddressStatusChoices),
         choices=add_blank_choice(IPAddressStatusChoices),
         required=False,
         required=False,
-        label='Status'
+        label=_('Status')
     )
     )
     comments = CommentField()
     comments = CommentField()
 
 
@@ -633,7 +634,7 @@ class VLANGroupForm(NetBoxModelForm):
         initial_params={
         initial_params={
             'sites': '$site'
             'sites': '$site'
         },
         },
-        label='Site group'
+        label=_('Site group')
     )
     )
     site = DynamicModelChoiceField(
     site = DynamicModelChoiceField(
         queryset=Site.objects.all(),
         queryset=Site.objects.all(),
@@ -670,7 +671,7 @@ class VLANGroupForm(NetBoxModelForm):
         initial_params={
         initial_params={
             'clusters': '$cluster'
             'clusters': '$cluster'
         },
         },
-        label='Cluster group'
+        label=_('Cluster group')
     )
     )
     cluster = DynamicModelChoiceField(
     cluster = DynamicModelChoiceField(
         queryset=Cluster.objects.all(),
         queryset=Cluster.objects.all(),
@@ -734,7 +735,7 @@ class VLANForm(TenancyForm, NetBoxModelForm):
         ),
         ),
         required=False,
         required=False,
         widget=StaticSelect,
         widget=StaticSelect,
-        label='Group scope'
+        label=_('Group scope')
     )
     )
     group = DynamicModelChoiceField(
     group = DynamicModelChoiceField(
         queryset=VLANGroup.objects.all(),
         queryset=VLANGroup.objects.all(),
@@ -742,7 +743,7 @@ class VLANForm(TenancyForm, NetBoxModelForm):
         query_params={
         query_params={
             'scope_type': '$scope_type',
             'scope_type': '$scope_type',
         },
         },
-        label='VLAN Group'
+        label=_('VLAN Group')
     )
     )
 
 
     # Site assignment fields
     # Site assignment fields
@@ -752,7 +753,7 @@ class VLANForm(TenancyForm, NetBoxModelForm):
         initial_params={
         initial_params={
             'sites': '$site'
             'sites': '$site'
         },
         },
-        label='Region'
+        label=_('Region')
     )
     )
     sitegroup = DynamicModelChoiceField(
     sitegroup = DynamicModelChoiceField(
         queryset=SiteGroup.objects.all(),
         queryset=SiteGroup.objects.all(),
@@ -760,7 +761,7 @@ class VLANForm(TenancyForm, NetBoxModelForm):
         initial_params={
         initial_params={
             'sites': '$site'
             'sites': '$site'
         },
         },
-        label='Site group'
+        label=_('Site group')
     )
     )
     site = DynamicModelChoiceField(
     site = DynamicModelChoiceField(
         queryset=Site.objects.all(),
         queryset=Site.objects.all(),
@@ -786,12 +787,12 @@ class VLANForm(TenancyForm, NetBoxModelForm):
             'tags',
             'tags',
         ]
         ]
         help_texts = {
         help_texts = {
-            'site': "Leave blank if this VLAN spans multiple sites",
-            'group': "VLAN group (optional)",
-            'vid': "Configured VLAN ID",
-            'name': "Configured VLAN name",
-            'status': "Operational status of this VLAN",
-            'role': "The primary function of this VLAN",
+            'site': _("Leave blank if this VLAN spans multiple sites"),
+            'group': _("VLAN group (optional)"),
+            'vid': _("Configured VLAN ID"),
+            'name': _("Configured VLAN name"),
+            'status': _("Operational status of this VLAN"),
+            'role': _("The primary function of this VLAN"),
         }
         }
         widgets = {
         widgets = {
             'status': StaticSelect(),
             'status': StaticSelect(),
@@ -804,7 +805,7 @@ class ServiceTemplateForm(NetBoxModelForm):
             min_value=SERVICE_PORT_MIN,
             min_value=SERVICE_PORT_MIN,
             max_value=SERVICE_PORT_MAX
             max_value=SERVICE_PORT_MAX
         ),
         ),
-        help_text="Comma-separated list of one or more port numbers. A range may be specified using a hyphen."
+        help_text=_("Comma-separated list of one or more port numbers. A range may be specified using a hyphen.")
     )
     )
     comments = CommentField()
     comments = CommentField()
 
 
@@ -836,12 +837,12 @@ class ServiceForm(NetBoxModelForm):
             min_value=SERVICE_PORT_MIN,
             min_value=SERVICE_PORT_MIN,
             max_value=SERVICE_PORT_MAX
             max_value=SERVICE_PORT_MAX
         ),
         ),
-        help_text="Comma-separated list of one or more port numbers. A range may be specified using a hyphen."
+        help_text=_("Comma-separated list of one or more port numbers. A range may be specified using a hyphen.")
     )
     )
     ipaddresses = DynamicModelMultipleChoiceField(
     ipaddresses = DynamicModelMultipleChoiceField(
         queryset=IPAddress.objects.all(),
         queryset=IPAddress.objects.all(),
         required=False,
         required=False,
-        label='IP Addresses',
+        label=_('IP Addresses'),
         query_params={
         query_params={
             'device_id': '$device',
             'device_id': '$device',
             'virtual_machine_id': '$virtual_machine',
             'virtual_machine_id': '$virtual_machine',
@@ -855,8 +856,8 @@ class ServiceForm(NetBoxModelForm):
             'device', 'virtual_machine', 'name', 'protocol', 'ports', 'ipaddresses', 'description', 'comments', 'tags',
             'device', 'virtual_machine', 'name', 'protocol', 'ports', 'ipaddresses', 'description', 'comments', 'tags',
         ]
         ]
         help_texts = {
         help_texts = {
-            'ipaddresses': "IP address assignment is optional. If no IPs are selected, the service is assumed to be "
-                           "reachable via all IPs assigned to the device.",
+            'ipaddresses': _("IP address assignment is optional. If no IPs are selected, the service is assumed to be "
+                             "reachable via all IPs assigned to the device."),
         }
         }
         widgets = {
         widgets = {
             'protocol': StaticSelect(),
             'protocol': StaticSelect(),
@@ -937,12 +938,12 @@ class L2VPNTerminationForm(NetBoxModelForm):
         queryset=L2VPN.objects.all(),
         queryset=L2VPN.objects.all(),
         required=True,
         required=True,
         query_params={},
         query_params={},
-        label='L2VPN',
+        label=_('L2VPN'),
         fetch_trigger='open'
         fetch_trigger='open'
     )
     )
     device_vlan = DynamicModelChoiceField(
     device_vlan = DynamicModelChoiceField(
         queryset=Device.objects.all(),
         queryset=Device.objects.all(),
-        label="Available on Device",
+        label=_("Available on Device"),
         required=False,
         required=False,
         query_params={}
         query_params={}
     )
     )
@@ -952,7 +953,7 @@ class L2VPNTerminationForm(NetBoxModelForm):
         query_params={
         query_params={
             'available_on_device': '$device_vlan'
             'available_on_device': '$device_vlan'
         },
         },
-        label='VLAN'
+        label=_('VLAN')
     )
     )
     device = DynamicModelChoiceField(
     device = DynamicModelChoiceField(
         queryset=Device.objects.all(),
         queryset=Device.objects.all(),
@@ -977,7 +978,7 @@ class L2VPNTerminationForm(NetBoxModelForm):
         query_params={
         query_params={
             'virtual_machine_id': '$virtual_machine'
             'virtual_machine_id': '$virtual_machine'
         },
         },
-        label='Interface'
+        label=_('Interface')
     )
     )
 
 
     class Meta:
     class Meta:

+ 17 - 16
netbox/ipam/models/ip.py

@@ -6,6 +6,7 @@ from django.db import models
 from django.db.models import F
 from django.db.models import F
 from django.urls import reverse
 from django.urls import reverse
 from django.utils.functional import cached_property
 from django.utils.functional import cached_property
+from django.utils.translation import gettext as _
 
 
 from dcim.fields import ASNField
 from dcim.fields import ASNField
 from dcim.models import Device
 from dcim.models import Device
@@ -64,7 +65,7 @@ class RIR(OrganizationalModel):
     is_private = models.BooleanField(
     is_private = models.BooleanField(
         default=False,
         default=False,
         verbose_name='Private',
         verbose_name='Private',
-        help_text='IP space managed by this RIR is considered private'
+        help_text=_('IP space managed by this RIR is considered private')
     )
     )
 
 
     class Meta:
     class Meta:
@@ -84,7 +85,7 @@ class ASN(PrimaryModel):
     asn = ASNField(
     asn = ASNField(
         unique=True,
         unique=True,
         verbose_name='ASN',
         verbose_name='ASN',
-        help_text='32-bit autonomous system number'
+        help_text=_('32-bit autonomous system number')
     )
     )
     rir = models.ForeignKey(
     rir = models.ForeignKey(
         to='ipam.RIR',
         to='ipam.RIR',
@@ -263,7 +264,7 @@ class Prefix(GetAvailablePrefixesMixin, PrimaryModel):
     assigned to a VLAN where appropriate.
     assigned to a VLAN where appropriate.
     """
     """
     prefix = IPNetworkField(
     prefix = IPNetworkField(
-        help_text='IPv4 or IPv6 network with mask'
+        help_text=_('IPv4 or IPv6 network with mask')
     )
     )
     site = models.ForeignKey(
     site = models.ForeignKey(
         to='dcim.Site',
         to='dcim.Site',
@@ -300,7 +301,7 @@ class Prefix(GetAvailablePrefixesMixin, PrimaryModel):
         choices=PrefixStatusChoices,
         choices=PrefixStatusChoices,
         default=PrefixStatusChoices.STATUS_ACTIVE,
         default=PrefixStatusChoices.STATUS_ACTIVE,
         verbose_name='Status',
         verbose_name='Status',
-        help_text='Operational status of this prefix'
+        help_text=_('Operational status of this prefix')
     )
     )
     role = models.ForeignKey(
     role = models.ForeignKey(
         to='ipam.Role',
         to='ipam.Role',
@@ -308,16 +309,16 @@ class Prefix(GetAvailablePrefixesMixin, PrimaryModel):
         related_name='prefixes',
         related_name='prefixes',
         blank=True,
         blank=True,
         null=True,
         null=True,
-        help_text='The primary function of this prefix'
+        help_text=_('The primary function of this prefix')
     )
     )
     is_pool = models.BooleanField(
     is_pool = models.BooleanField(
         verbose_name='Is a pool',
         verbose_name='Is a pool',
         default=False,
         default=False,
-        help_text='All IP addresses within this prefix are considered usable'
+        help_text=_('All IP addresses within this prefix are considered usable')
     )
     )
     mark_utilized = models.BooleanField(
     mark_utilized = models.BooleanField(
         default=False,
         default=False,
-        help_text="Treat as 100% utilized"
+        help_text=_("Treat as 100% utilized")
     )
     )
 
 
     # Cached depth & child counts
     # Cached depth & child counts
@@ -538,10 +539,10 @@ class IPRange(PrimaryModel):
     A range of IP addresses, defined by start and end addresses.
     A range of IP addresses, defined by start and end addresses.
     """
     """
     start_address = IPAddressField(
     start_address = IPAddressField(
-        help_text='IPv4 or IPv6 address (with mask)'
+        help_text=_('IPv4 or IPv6 address (with mask)')
     )
     )
     end_address = IPAddressField(
     end_address = IPAddressField(
-        help_text='IPv4 or IPv6 address (with mask)'
+        help_text=_('IPv4 or IPv6 address (with mask)')
     )
     )
     size = models.PositiveIntegerField(
     size = models.PositiveIntegerField(
         editable=False
         editable=False
@@ -565,7 +566,7 @@ class IPRange(PrimaryModel):
         max_length=50,
         max_length=50,
         choices=IPRangeStatusChoices,
         choices=IPRangeStatusChoices,
         default=IPRangeStatusChoices.STATUS_ACTIVE,
         default=IPRangeStatusChoices.STATUS_ACTIVE,
-        help_text='Operational status of this range'
+        help_text=_('Operational status of this range')
     )
     )
     role = models.ForeignKey(
     role = models.ForeignKey(
         to='ipam.Role',
         to='ipam.Role',
@@ -573,7 +574,7 @@ class IPRange(PrimaryModel):
         related_name='ip_ranges',
         related_name='ip_ranges',
         blank=True,
         blank=True,
         null=True,
         null=True,
-        help_text='The primary function of this range'
+        help_text=_('The primary function of this range')
     )
     )
 
 
     clone_fields = (
     clone_fields = (
@@ -736,7 +737,7 @@ class IPAddress(PrimaryModel):
     which has a NAT outside IP, that Interface's Device can use either the inside or outside IP as its primary IP.
     which has a NAT outside IP, that Interface's Device can use either the inside or outside IP as its primary IP.
     """
     """
     address = IPAddressField(
     address = IPAddressField(
-        help_text='IPv4 or IPv6 address (with mask)'
+        help_text=_('IPv4 or IPv6 address (with mask)')
     )
     )
     vrf = models.ForeignKey(
     vrf = models.ForeignKey(
         to='ipam.VRF',
         to='ipam.VRF',
@@ -757,13 +758,13 @@ class IPAddress(PrimaryModel):
         max_length=50,
         max_length=50,
         choices=IPAddressStatusChoices,
         choices=IPAddressStatusChoices,
         default=IPAddressStatusChoices.STATUS_ACTIVE,
         default=IPAddressStatusChoices.STATUS_ACTIVE,
-        help_text='The operational status of this IP'
+        help_text=_('The operational status of this IP')
     )
     )
     role = models.CharField(
     role = models.CharField(
         max_length=50,
         max_length=50,
         choices=IPAddressRoleChoices,
         choices=IPAddressRoleChoices,
         blank=True,
         blank=True,
-        help_text='The functional role of this IP'
+        help_text=_('The functional role of this IP')
     )
     )
     assigned_object_type = models.ForeignKey(
     assigned_object_type = models.ForeignKey(
         to=ContentType,
         to=ContentType,
@@ -788,14 +789,14 @@ class IPAddress(PrimaryModel):
         blank=True,
         blank=True,
         null=True,
         null=True,
         verbose_name='NAT (Inside)',
         verbose_name='NAT (Inside)',
-        help_text='The IP for which this address is the "outside" IP'
+        help_text=_('The IP for which this address is the "outside" IP')
     )
     )
     dns_name = models.CharField(
     dns_name = models.CharField(
         max_length=255,
         max_length=255,
         blank=True,
         blank=True,
         validators=[DNSValidator],
         validators=[DNSValidator],
         verbose_name='DNS Name',
         verbose_name='DNS Name',
-        help_text='Hostname or FQDN (not case-sensitive)'
+        help_text=_('Hostname or FQDN (not case-sensitive)')
     )
     )
 
 
     objects = IPAddressManager()
     objects = IPAddressManager()

+ 3 - 2
netbox/ipam/models/vlans.py

@@ -4,6 +4,7 @@ from django.core.exceptions import ValidationError
 from django.core.validators import MaxValueValidator, MinValueValidator
 from django.core.validators import MaxValueValidator, MinValueValidator
 from django.db import models
 from django.db import models
 from django.urls import reverse
 from django.urls import reverse
+from django.utils.translation import gettext as _
 
 
 from dcim.models import Interface
 from dcim.models import Interface
 from ipam.choices import *
 from ipam.choices import *
@@ -50,7 +51,7 @@ class VLANGroup(OrganizationalModel):
             MinValueValidator(VLAN_VID_MIN),
             MinValueValidator(VLAN_VID_MIN),
             MaxValueValidator(VLAN_VID_MAX)
             MaxValueValidator(VLAN_VID_MAX)
         ),
         ),
-        help_text='Lowest permissible ID of a child VLAN'
+        help_text=_('Lowest permissible ID of a child VLAN')
     )
     )
     max_vid = models.PositiveSmallIntegerField(
     max_vid = models.PositiveSmallIntegerField(
         verbose_name='Maximum VLAN ID',
         verbose_name='Maximum VLAN ID',
@@ -59,7 +60,7 @@ class VLANGroup(OrganizationalModel):
             MinValueValidator(VLAN_VID_MIN),
             MinValueValidator(VLAN_VID_MIN),
             MaxValueValidator(VLAN_VID_MAX)
             MaxValueValidator(VLAN_VID_MAX)
         ),
         ),
-        help_text='Highest permissible ID of a child VLAN'
+        help_text=_('Highest permissible ID of a child VLAN')
     )
     )
 
 
     class Meta:
     class Meta:

+ 4 - 3
netbox/ipam/models/vrfs.py

@@ -1,5 +1,6 @@
 from django.db import models
 from django.db import models
 from django.urls import reverse
 from django.urls import reverse
+from django.utils.translation import gettext as _
 
 
 from ipam.constants import *
 from ipam.constants import *
 from netbox.models import PrimaryModel
 from netbox.models import PrimaryModel
@@ -26,7 +27,7 @@ class VRF(PrimaryModel):
         blank=True,
         blank=True,
         null=True,
         null=True,
         verbose_name='Route distinguisher',
         verbose_name='Route distinguisher',
-        help_text='Unique route distinguisher (as defined in RFC 4364)'
+        help_text=_('Unique route distinguisher (as defined in RFC 4364)')
     )
     )
     tenant = models.ForeignKey(
     tenant = models.ForeignKey(
         to='tenancy.Tenant',
         to='tenancy.Tenant',
@@ -38,7 +39,7 @@ class VRF(PrimaryModel):
     enforce_unique = models.BooleanField(
     enforce_unique = models.BooleanField(
         default=True,
         default=True,
         verbose_name='Enforce unique space',
         verbose_name='Enforce unique space',
-        help_text='Prevent duplicate prefixes/IP addresses within this VRF'
+        help_text=_('Prevent duplicate prefixes/IP addresses within this VRF')
     )
     )
     import_targets = models.ManyToManyField(
     import_targets = models.ManyToManyField(
         to='ipam.RouteTarget',
         to='ipam.RouteTarget',
@@ -76,7 +77,7 @@ class RouteTarget(PrimaryModel):
     name = models.CharField(
     name = models.CharField(
         max_length=VRF_RD_MAX_LENGTH,  # Same format options as VRF RD (RFC 4360 section 4)
         max_length=VRF_RD_MAX_LENGTH,  # Same format options as VRF RD (RFC 4360 section 4)
         unique=True,
         unique=True,
-        help_text='Route target value (formatted in accordance with RFC 4360)'
+        help_text=_('Route target value (formatted in accordance with RFC 4360)')
     )
     )
     tenant = models.ForeignKey(
     tenant = models.ForeignKey(
         to='tenancy.Tenant',
         to='tenancy.Tenant',

+ 47 - 46
netbox/netbox/config/parameters.py

@@ -1,5 +1,6 @@
 from django import forms
 from django import forms
 from django.contrib.postgres.forms import SimpleArrayField
 from django.contrib.postgres.forms import SimpleArrayField
+from django.utils.translation import gettext_lazy as _
 
 
 
 
 class ConfigParam:
 class ConfigParam:
@@ -18,9 +19,9 @@ PARAMS = (
     # Banners
     # Banners
     ConfigParam(
     ConfigParam(
         name='BANNER_LOGIN',
         name='BANNER_LOGIN',
-        label='Login banner',
+        label=_('Login banner'),
         default='',
         default='',
-        description="Additional content to display on the login page",
+        description=_("Additional content to display on the login page"),
         field_kwargs={
         field_kwargs={
             'widget': forms.Textarea(
             'widget': forms.Textarea(
                 attrs={'class': 'vLargeTextField'}
                 attrs={'class': 'vLargeTextField'}
@@ -29,9 +30,9 @@ PARAMS = (
     ),
     ),
     ConfigParam(
     ConfigParam(
         name='BANNER_TOP',
         name='BANNER_TOP',
-        label='Top banner',
+        label=_('Top banner'),
         default='',
         default='',
-        description="Additional content to display at the top of every page",
+        description=_("Additional content to display at the top of every page"),
         field_kwargs={
         field_kwargs={
             'widget': forms.Textarea(
             'widget': forms.Textarea(
                 attrs={'class': 'vLargeTextField'}
                 attrs={'class': 'vLargeTextField'}
@@ -40,9 +41,9 @@ PARAMS = (
     ),
     ),
     ConfigParam(
     ConfigParam(
         name='BANNER_BOTTOM',
         name='BANNER_BOTTOM',
-        label='Bottom banner',
+        label=_('Bottom banner'),
         default='',
         default='',
-        description="Additional content to display at the bottom of every page",
+        description=_("Additional content to display at the bottom of every page"),
         field_kwargs={
         field_kwargs={
             'widget': forms.Textarea(
             'widget': forms.Textarea(
                 attrs={'class': 'vLargeTextField'}
                 attrs={'class': 'vLargeTextField'}
@@ -53,69 +54,69 @@ PARAMS = (
     # IPAM
     # IPAM
     ConfigParam(
     ConfigParam(
         name='ENFORCE_GLOBAL_UNIQUE',
         name='ENFORCE_GLOBAL_UNIQUE',
-        label='Globally unique IP space',
+        label=_('Globally unique IP space'),
         default=False,
         default=False,
-        description="Enforce unique IP addressing within the global table",
+        description=_("Enforce unique IP addressing within the global table"),
         field=forms.BooleanField
         field=forms.BooleanField
     ),
     ),
     ConfigParam(
     ConfigParam(
         name='PREFER_IPV4',
         name='PREFER_IPV4',
-        label='Prefer IPv4',
+        label=_('Prefer IPv4'),
         default=False,
         default=False,
-        description="Prefer IPv4 addresses over IPv6",
+        description=_("Prefer IPv4 addresses over IPv6"),
         field=forms.BooleanField
         field=forms.BooleanField
     ),
     ),
 
 
     # Racks
     # Racks
     ConfigParam(
     ConfigParam(
         name='RACK_ELEVATION_DEFAULT_UNIT_HEIGHT',
         name='RACK_ELEVATION_DEFAULT_UNIT_HEIGHT',
-        label='Rack unit height',
+        label=_('Rack unit height'),
         default=22,
         default=22,
-        description="Default unit height for rendered rack elevations",
+        description=_("Default unit height for rendered rack elevations"),
         field=forms.IntegerField
         field=forms.IntegerField
     ),
     ),
     ConfigParam(
     ConfigParam(
         name='RACK_ELEVATION_DEFAULT_UNIT_WIDTH',
         name='RACK_ELEVATION_DEFAULT_UNIT_WIDTH',
-        label='Rack unit width',
+        label=_('Rack unit width'),
         default=220,
         default=220,
-        description="Default unit width for rendered rack elevations",
+        description=_("Default unit width for rendered rack elevations"),
         field=forms.IntegerField
         field=forms.IntegerField
     ),
     ),
 
 
     # Power
     # Power
     ConfigParam(
     ConfigParam(
         name='POWERFEED_DEFAULT_VOLTAGE',
         name='POWERFEED_DEFAULT_VOLTAGE',
-        label='Powerfeed voltage',
+        label=_('Powerfeed voltage'),
         default=120,
         default=120,
-        description="Default voltage for powerfeeds",
+        description=_("Default voltage for powerfeeds"),
         field=forms.IntegerField
         field=forms.IntegerField
     ),
     ),
 
 
     ConfigParam(
     ConfigParam(
         name='POWERFEED_DEFAULT_AMPERAGE',
         name='POWERFEED_DEFAULT_AMPERAGE',
-        label='Powerfeed amperage',
+        label=_('Powerfeed amperage'),
         default=15,
         default=15,
-        description="Default amperage for powerfeeds",
+        description=_("Default amperage for powerfeeds"),
         field=forms.IntegerField
         field=forms.IntegerField
     ),
     ),
 
 
     ConfigParam(
     ConfigParam(
         name='POWERFEED_DEFAULT_MAX_UTILIZATION',
         name='POWERFEED_DEFAULT_MAX_UTILIZATION',
-        label='Powerfeed max utilization',
+        label=_('Powerfeed max utilization'),
         default=80,
         default=80,
-        description="Default max utilization for powerfeeds",
+        description=_("Default max utilization for powerfeeds"),
         field=forms.IntegerField
         field=forms.IntegerField
     ),
     ),
 
 
     # Security
     # Security
     ConfigParam(
     ConfigParam(
         name='ALLOWED_URL_SCHEMES',
         name='ALLOWED_URL_SCHEMES',
-        label='Allowed URL schemes',
+        label=_('Allowed URL schemes'),
         default=(
         default=(
             'file', 'ftp', 'ftps', 'http', 'https', 'irc', 'mailto', 'sftp', 'ssh', 'tel', 'telnet', 'tftp', 'vnc',
             'file', 'ftp', 'ftps', 'http', 'https', 'irc', 'mailto', 'sftp', 'ssh', 'tel', 'telnet', 'tftp', 'vnc',
             'xmpp',
             'xmpp',
         ),
         ),
-        description="Permitted schemes for URLs in user-provided content",
+        description=_("Permitted schemes for URLs in user-provided content"),
         field=SimpleArrayField,
         field=SimpleArrayField,
         field_kwargs={'base_field': forms.CharField()}
         field_kwargs={'base_field': forms.CharField()}
     ),
     ),
@@ -123,13 +124,13 @@ PARAMS = (
     # Pagination
     # Pagination
     ConfigParam(
     ConfigParam(
         name='PAGINATE_COUNT',
         name='PAGINATE_COUNT',
-        label='Default page size',
+        label=_('Default page size'),
         default=50,
         default=50,
         field=forms.IntegerField
         field=forms.IntegerField
     ),
     ),
     ConfigParam(
     ConfigParam(
         name='MAX_PAGE_SIZE',
         name='MAX_PAGE_SIZE',
-        label='Maximum page size',
+        label=_('Maximum page size'),
         default=1000,
         default=1000,
         field=forms.IntegerField
         field=forms.IntegerField
     ),
     ),
@@ -137,9 +138,9 @@ PARAMS = (
     # Validation
     # Validation
     ConfigParam(
     ConfigParam(
         name='CUSTOM_VALIDATORS',
         name='CUSTOM_VALIDATORS',
-        label='Custom validators',
+        label=_('Custom validators'),
         default={},
         default={},
-        description="Custom validation rules (JSON)",
+        description=_("Custom validation rules (JSON)"),
         field=forms.JSONField,
         field=forms.JSONField,
         field_kwargs={
         field_kwargs={
             'widget': forms.Textarea(
             'widget': forms.Textarea(
@@ -151,28 +152,28 @@ PARAMS = (
     # NAPALM
     # NAPALM
     ConfigParam(
     ConfigParam(
         name='NAPALM_USERNAME',
         name='NAPALM_USERNAME',
-        label='NAPALM username',
+        label=_('NAPALM username'),
         default='',
         default='',
-        description="Username to use when connecting to devices via NAPALM"
+        description=_("Username to use when connecting to devices via NAPALM")
     ),
     ),
     ConfigParam(
     ConfigParam(
         name='NAPALM_PASSWORD',
         name='NAPALM_PASSWORD',
-        label='NAPALM password',
+        label=_('NAPALM password'),
         default='',
         default='',
-        description="Password to use when connecting to devices via NAPALM"
+        description=_("Password to use when connecting to devices via NAPALM")
     ),
     ),
     ConfigParam(
     ConfigParam(
         name='NAPALM_TIMEOUT',
         name='NAPALM_TIMEOUT',
-        label='NAPALM timeout',
+        label=_('NAPALM timeout'),
         default=30,
         default=30,
-        description="NAPALM connection timeout (in seconds)",
+        description=_("NAPALM connection timeout (in seconds)"),
         field=forms.IntegerField
         field=forms.IntegerField
     ),
     ),
     ConfigParam(
     ConfigParam(
         name='NAPALM_ARGS',
         name='NAPALM_ARGS',
-        label='NAPALM arguments',
+        label=_('NAPALM arguments'),
         default={},
         default={},
-        description="Additional arguments to pass when invoking a NAPALM driver (as JSON data)",
+        description=_("Additional arguments to pass when invoking a NAPALM driver (as JSON data)"),
         field=forms.JSONField,
         field=forms.JSONField,
         field_kwargs={
         field_kwargs={
             'widget': forms.Textarea(
             'widget': forms.Textarea(
@@ -184,46 +185,46 @@ PARAMS = (
     # User preferences
     # User preferences
     ConfigParam(
     ConfigParam(
         name='DEFAULT_USER_PREFERENCES',
         name='DEFAULT_USER_PREFERENCES',
-        label='Default preferences',
+        label=_('Default preferences'),
         default={},
         default={},
-        description="Default preferences for new users",
+        description=_("Default preferences for new users"),
         field=forms.JSONField
         field=forms.JSONField
     ),
     ),
 
 
     # Miscellaneous
     # Miscellaneous
     ConfigParam(
     ConfigParam(
         name='MAINTENANCE_MODE',
         name='MAINTENANCE_MODE',
-        label='Maintenance mode',
+        label=_('Maintenance mode'),
         default=False,
         default=False,
-        description="Enable maintenance mode",
+        description=_("Enable maintenance mode"),
         field=forms.BooleanField
         field=forms.BooleanField
     ),
     ),
     ConfigParam(
     ConfigParam(
         name='GRAPHQL_ENABLED',
         name='GRAPHQL_ENABLED',
-        label='GraphQL enabled',
+        label=_('GraphQL enabled'),
         default=True,
         default=True,
-        description="Enable the GraphQL API",
+        description=_("Enable the GraphQL API"),
         field=forms.BooleanField
         field=forms.BooleanField
     ),
     ),
     ConfigParam(
     ConfigParam(
         name='CHANGELOG_RETENTION',
         name='CHANGELOG_RETENTION',
-        label='Changelog retention',
+        label=_('Changelog retention'),
         default=90,
         default=90,
-        description="Days to retain changelog history (set to zero for unlimited)",
+        description=_("Days to retain changelog history (set to zero for unlimited)"),
         field=forms.IntegerField
         field=forms.IntegerField
     ),
     ),
     ConfigParam(
     ConfigParam(
         name='JOBRESULT_RETENTION',
         name='JOBRESULT_RETENTION',
-        label='Job result retention',
+        label=_('Job result retention'),
         default=90,
         default=90,
-        description="Days to retain job result history (set to zero for unlimited)",
+        description=_("Days to retain job result history (set to zero for unlimited)"),
         field=forms.IntegerField
         field=forms.IntegerField
     ),
     ),
     ConfigParam(
     ConfigParam(
         name='MAPS_URL',
         name='MAPS_URL',
-        label='Maps URL',
+        label=_('Maps URL'),
         default='https://maps.google.com/?q=',
         default='https://maps.google.com/?q=',
-        description="Base URL for mapping geographic locations"
+        description=_("Base URL for mapping geographic locations")
     ),
     ),
 
 
 )
 )

+ 2 - 1
netbox/netbox/filtersets.py

@@ -5,6 +5,7 @@ from django.db import models
 from django_filters.exceptions import FieldLookupError
 from django_filters.exceptions import FieldLookupError
 from django_filters.utils import get_model_field, resolve_field
 from django_filters.utils import get_model_field, resolve_field
 from django.shortcuts import get_object_or_404
 from django.shortcuts import get_object_or_404
+from django.utils.translation import gettext as _
 
 
 from extras.choices import CustomFieldFilterLogicChoices
 from extras.choices import CustomFieldFilterLogicChoices
 from extras.filters import TagFilter
 from extras.filters import TagFilter
@@ -235,7 +236,7 @@ class NetBoxModelFilterSet(ChangeLoggedModelFilterSet):
     """
     """
     q = django_filters.CharFilter(
     q = django_filters.CharFilter(
         method='search',
         method='search',
-        label='Search',
+        label=_('Search'),
     )
     )
     tag = TagFilter()
     tag = TagFilter()
 
 

+ 2 - 2
netbox/netbox/forms/__init__.py

@@ -17,7 +17,7 @@ LOOKUP_CHOICES = (
 
 
 class SearchForm(BootstrapMixin, forms.Form):
 class SearchForm(BootstrapMixin, forms.Form):
     q = forms.CharField(
     q = forms.CharField(
-        label='Search',
+        label=_('Search'),
         widget=forms.TextInput(
         widget=forms.TextInput(
             attrs={
             attrs={
                 'hx-get': '',
                 'hx-get': '',
@@ -29,7 +29,7 @@ class SearchForm(BootstrapMixin, forms.Form):
     obj_types = forms.MultipleChoiceField(
     obj_types = forms.MultipleChoiceField(
         choices=[],
         choices=[],
         required=False,
         required=False,
-        label='Object type(s)',
+        label=_('Object type(s)'),
         widget=StaticSelectMultiple()
         widget=StaticSelectMultiple()
     )
     )
     lookup = forms.ChoiceField(
     lookup = forms.ChoiceField(

+ 2 - 1
netbox/netbox/forms/base.py

@@ -2,6 +2,7 @@ from django import forms
 from django.contrib.contenttypes.models import ContentType
 from django.contrib.contenttypes.models import ContentType
 from django.core.exceptions import ValidationError
 from django.core.exceptions import ValidationError
 from django.db.models import Q
 from django.db.models import Q
+from django.utils.translation import gettext as _
 
 
 from extras.choices import CustomFieldFilterLogicChoices, CustomFieldTypeChoices, CustomFieldVisibilityChoices
 from extras.choices import CustomFieldFilterLogicChoices, CustomFieldTypeChoices, CustomFieldVisibilityChoices
 from extras.forms.mixins import CustomFieldsMixin, SavedFiltersMixin
 from extras.forms.mixins import CustomFieldsMixin, SavedFiltersMixin
@@ -132,7 +133,7 @@ class NetBoxModelFilterSetForm(BootstrapMixin, CustomFieldsMixin, SavedFiltersMi
     """
     """
     q = forms.CharField(
     q = forms.CharField(
         required=False,
         required=False,
-        label='Search'
+        label=_('Search')
     )
     )
 
 
     def __init__(self, *args, **kwargs):
     def __init__(self, *args, **kwargs):

+ 116 - 115
netbox/netbox/navigation/menu.py

@@ -1,119 +1,120 @@
+from django.utils.translation import gettext as _
+
 from netbox.registry import registry
 from netbox.registry import registry
 from . import *
 from . import *
 
 
-
 #
 #
 # Nav menus
 # Nav menus
 #
 #
 
 
 ORGANIZATION_MENU = Menu(
 ORGANIZATION_MENU = Menu(
-    label='Organization',
+    label=_('Organization'),
     icon_class='mdi mdi-domain',
     icon_class='mdi mdi-domain',
     groups=(
     groups=(
         MenuGroup(
         MenuGroup(
-            label='Sites',
+            label=_('Sites'),
             items=(
             items=(
-                get_model_item('dcim', 'site', 'Sites'),
-                get_model_item('dcim', 'region', 'Regions'),
-                get_model_item('dcim', 'sitegroup', 'Site Groups'),
-                get_model_item('dcim', 'location', 'Locations'),
+                get_model_item('dcim', 'site', _('Sites')),
+                get_model_item('dcim', 'region', _('Regions')),
+                get_model_item('dcim', 'sitegroup', _('Site Groups')),
+                get_model_item('dcim', 'location', _('Locations')),
             ),
             ),
         ),
         ),
         MenuGroup(
         MenuGroup(
-            label='Racks',
+            label=_('Racks'),
             items=(
             items=(
-                get_model_item('dcim', 'rack', 'Racks'),
-                get_model_item('dcim', 'rackrole', 'Rack Roles'),
-                get_model_item('dcim', 'rackreservation', 'Reservations'),
+                get_model_item('dcim', 'rack', _('Racks')),
+                get_model_item('dcim', 'rackrole', _('Rack Roles')),
+                get_model_item('dcim', 'rackreservation', _('Reservations')),
                 MenuItem(
                 MenuItem(
                     link='dcim:rack_elevation_list',
                     link='dcim:rack_elevation_list',
-                    link_text='Elevations',
+                    link_text=_('Elevations'),
                     permissions=['dcim.view_rack']
                     permissions=['dcim.view_rack']
                 ),
                 ),
             ),
             ),
         ),
         ),
         MenuGroup(
         MenuGroup(
-            label='Tenancy',
+            label=_('Tenancy'),
             items=(
             items=(
-                get_model_item('tenancy', 'tenant', 'Tenants'),
-                get_model_item('tenancy', 'tenantgroup', 'Tenant Groups'),
+                get_model_item('tenancy', 'tenant', _('Tenants')),
+                get_model_item('tenancy', 'tenantgroup', _('Tenant Groups')),
             ),
             ),
         ),
         ),
         MenuGroup(
         MenuGroup(
-            label='Contacts',
+            label=_('Contacts'),
             items=(
             items=(
-                get_model_item('tenancy', 'contact', 'Contacts'),
-                get_model_item('tenancy', 'contactgroup', 'Contact Groups'),
-                get_model_item('tenancy', 'contactrole', 'Contact Roles'),
+                get_model_item('tenancy', 'contact', _('Contacts')),
+                get_model_item('tenancy', 'contactgroup', _('Contact Groups')),
+                get_model_item('tenancy', 'contactrole', _('Contact Roles')),
             ),
             ),
         ),
         ),
     ),
     ),
 )
 )
 
 
 DEVICES_MENU = Menu(
 DEVICES_MENU = Menu(
-    label='Devices',
+    label=_('Devices'),
     icon_class='mdi mdi-server',
     icon_class='mdi mdi-server',
     groups=(
     groups=(
         MenuGroup(
         MenuGroup(
-            label='Devices',
+            label=_('Devices'),
             items=(
             items=(
-                get_model_item('dcim', 'device', 'Devices'),
-                get_model_item('dcim', 'module', 'Modules'),
-                get_model_item('dcim', 'devicerole', 'Device Roles'),
-                get_model_item('dcim', 'platform', 'Platforms'),
-                get_model_item('dcim', 'virtualchassis', 'Virtual Chassis'),
-                get_model_item('dcim', 'virtualdevicecontext', 'Virtual Device Contexts'),
+                get_model_item('dcim', 'device', _('Devices')),
+                get_model_item('dcim', 'module', _('Modules')),
+                get_model_item('dcim', 'devicerole', _('Device Roles')),
+                get_model_item('dcim', 'platform', _('Platforms')),
+                get_model_item('dcim', 'virtualchassis', _('Virtual Chassis')),
+                get_model_item('dcim', 'virtualdevicecontext', _('Virtual Device Contexts')),
             ),
             ),
         ),
         ),
         MenuGroup(
         MenuGroup(
-            label='Device Types',
+            label=_('Device Types'),
             items=(
             items=(
-                get_model_item('dcim', 'devicetype', 'Device Types'),
-                get_model_item('dcim', 'moduletype', 'Module Types'),
-                get_model_item('dcim', 'manufacturer', 'Manufacturers'),
+                get_model_item('dcim', 'devicetype', _('Device Types')),
+                get_model_item('dcim', 'moduletype', _('Module Types')),
+                get_model_item('dcim', 'manufacturer', _('Manufacturers')),
             ),
             ),
         ),
         ),
         MenuGroup(
         MenuGroup(
-            label='Device Components',
+            label=_('Device Components'),
             items=(
             items=(
-                get_model_item('dcim', 'interface', 'Interfaces', actions=['import']),
-                get_model_item('dcim', 'frontport', 'Front Ports', actions=['import']),
-                get_model_item('dcim', 'rearport', 'Rear Ports', actions=['import']),
-                get_model_item('dcim', 'consoleport', 'Console Ports', actions=['import']),
-                get_model_item('dcim', 'consoleserverport', 'Console Server Ports', actions=['import']),
-                get_model_item('dcim', 'powerport', 'Power Ports', actions=['import']),
-                get_model_item('dcim', 'poweroutlet', 'Power Outlets', actions=['import']),
-                get_model_item('dcim', 'modulebay', 'Module Bays', actions=['import']),
-                get_model_item('dcim', 'devicebay', 'Device Bays', actions=['import']),
-                get_model_item('dcim', 'inventoryitem', 'Inventory Items', actions=['import']),
-                get_model_item('dcim', 'inventoryitemrole', 'Inventory Item Roles'),
+                get_model_item('dcim', 'interface', _('Interfaces'), actions=['import']),
+                get_model_item('dcim', 'frontport', _('Front Ports'), actions=['import']),
+                get_model_item('dcim', 'rearport', _('Rear Ports'), actions=['import']),
+                get_model_item('dcim', 'consoleport', _('Console Ports'), actions=['import']),
+                get_model_item('dcim', 'consoleserverport', _('Console Server Ports'), actions=['import']),
+                get_model_item('dcim', 'powerport', _('Power Ports'), actions=['import']),
+                get_model_item('dcim', 'poweroutlet', _('Power Outlets'), actions=['import']),
+                get_model_item('dcim', 'modulebay', _('Module Bays'), actions=['import']),
+                get_model_item('dcim', 'devicebay', _('Device Bays'), actions=['import']),
+                get_model_item('dcim', 'inventoryitem', _('Inventory Items'), actions=['import']),
+                get_model_item('dcim', 'inventoryitemrole', _('Inventory Item Roles')),
             ),
             ),
         ),
         ),
     ),
     ),
 )
 )
 
 
 CONNECTIONS_MENU = Menu(
 CONNECTIONS_MENU = Menu(
-    label='Connections',
+    label=_('Connections'),
     icon_class='mdi mdi-connection',
     icon_class='mdi mdi-connection',
     groups=(
     groups=(
         MenuGroup(
         MenuGroup(
-            label='Connections',
+            label=_('Connections'),
             items=(
             items=(
-                get_model_item('dcim', 'cable', 'Cables', actions=['import']),
-                get_model_item('wireless', 'wirelesslink', 'Wireless Links', actions=['import']),
+                get_model_item('dcim', 'cable', _('Cables'), actions=['import']),
+                get_model_item('wireless', 'wirelesslink', _('Wireless Links'), actions=['import']),
                 MenuItem(
                 MenuItem(
                     link='dcim:interface_connections_list',
                     link='dcim:interface_connections_list',
-                    link_text='Interface Connections',
+                    link_text=_('Interface Connections'),
                     permissions=['dcim.view_interface']
                     permissions=['dcim.view_interface']
                 ),
                 ),
                 MenuItem(
                 MenuItem(
                     link='dcim:console_connections_list',
                     link='dcim:console_connections_list',
-                    link_text='Console Connections',
+                    link_text=_('Console Connections'),
                     permissions=['dcim.view_consoleport']
                     permissions=['dcim.view_consoleport']
                 ),
                 ),
                 MenuItem(
                 MenuItem(
                     link='dcim:power_connections_list',
                     link='dcim:power_connections_list',
-                    link_text='Power Connections',
+                    link_text=_('Power Connections'),
                     permissions=['dcim.view_powerport']
                     permissions=['dcim.view_powerport']
                 ),
                 ),
             ),
             ),
@@ -122,192 +123,192 @@ CONNECTIONS_MENU = Menu(
 )
 )
 
 
 WIRELESS_MENU = Menu(
 WIRELESS_MENU = Menu(
-    label='Wireless',
+    label=_('Wireless'),
     icon_class='mdi mdi-wifi',
     icon_class='mdi mdi-wifi',
     groups=(
     groups=(
         MenuGroup(
         MenuGroup(
-            label='Wireless',
+            label=_('Wireless'),
             items=(
             items=(
-                get_model_item('wireless', 'wirelesslan', 'Wireless LANs'),
-                get_model_item('wireless', 'wirelesslangroup', 'Wireless LAN Groups'),
+                get_model_item('wireless', 'wirelesslan', _('Wireless LANs')),
+                get_model_item('wireless', 'wirelesslangroup', _('Wireless LAN Groups')),
             ),
             ),
         ),
         ),
     ),
     ),
 )
 )
 
 
 IPAM_MENU = Menu(
 IPAM_MENU = Menu(
-    label='IPAM',
+    label=_('IPAM'),
     icon_class='mdi mdi-counter',
     icon_class='mdi mdi-counter',
     groups=(
     groups=(
         MenuGroup(
         MenuGroup(
-            label='IP Addresses',
+            label=_('IP Addresses'),
             items=(
             items=(
-                get_model_item('ipam', 'ipaddress', 'IP Addresses'),
-                get_model_item('ipam', 'iprange', 'IP Ranges'),
+                get_model_item('ipam', 'ipaddress', _('IP Addresses')),
+                get_model_item('ipam', 'iprange', _('IP Ranges')),
             ),
             ),
         ),
         ),
         MenuGroup(
         MenuGroup(
-            label='Prefixes',
+            label=_('Prefixes'),
             items=(
             items=(
-                get_model_item('ipam', 'prefix', 'Prefixes'),
-                get_model_item('ipam', 'role', 'Prefix & VLAN Roles'),
+                get_model_item('ipam', 'prefix', _('Prefixes')),
+                get_model_item('ipam', 'role', _('Prefix & VLAN Roles')),
             ),
             ),
         ),
         ),
         MenuGroup(
         MenuGroup(
-            label='ASNs',
+            label=_('ASNs'),
             items=(
             items=(
-                get_model_item('ipam', 'asn', 'ASNs'),
+                get_model_item('ipam', 'asn', _('ASNs')),
             ),
             ),
         ),
         ),
         MenuGroup(
         MenuGroup(
-            label='Aggregates',
+            label=_('Aggregates'),
             items=(
             items=(
-                get_model_item('ipam', 'aggregate', 'Aggregates'),
-                get_model_item('ipam', 'rir', 'RIRs'),
+                get_model_item('ipam', 'aggregate', _('Aggregates')),
+                get_model_item('ipam', 'rir', _('RIRs')),
             ),
             ),
         ),
         ),
         MenuGroup(
         MenuGroup(
-            label='VRFs',
+            label=_('VRFs'),
             items=(
             items=(
-                get_model_item('ipam', 'vrf', 'VRFs'),
-                get_model_item('ipam', 'routetarget', 'Route Targets'),
+                get_model_item('ipam', 'vrf', _('VRFs')),
+                get_model_item('ipam', 'routetarget', _('Route Targets')),
             ),
             ),
         ),
         ),
         MenuGroup(
         MenuGroup(
-            label='VLANs',
+            label=_('VLANs'),
             items=(
             items=(
-                get_model_item('ipam', 'vlan', 'VLANs'),
-                get_model_item('ipam', 'vlangroup', 'VLAN Groups'),
+                get_model_item('ipam', 'vlan', _('VLANs')),
+                get_model_item('ipam', 'vlangroup', _('VLAN Groups')),
             ),
             ),
         ),
         ),
         MenuGroup(
         MenuGroup(
-            label='Other',
+            label=_('Other'),
             items=(
             items=(
-                get_model_item('ipam', 'fhrpgroup', 'FHRP Groups'),
-                get_model_item('ipam', 'servicetemplate', 'Service Templates'),
-                get_model_item('ipam', 'service', 'Services'),
+                get_model_item('ipam', 'fhrpgroup', _('FHRP Groups')),
+                get_model_item('ipam', 'servicetemplate', _('Service Templates')),
+                get_model_item('ipam', 'service', _('Services')),
             ),
             ),
         ),
         ),
     ),
     ),
 )
 )
 
 
 OVERLAY_MENU = Menu(
 OVERLAY_MENU = Menu(
-    label='Overlay',
+    label=_('Overlay'),
     icon_class='mdi mdi-graph-outline',
     icon_class='mdi mdi-graph-outline',
     groups=(
     groups=(
         MenuGroup(
         MenuGroup(
             label='L2VPNs',
             label='L2VPNs',
             items=(
             items=(
-                get_model_item('ipam', 'l2vpn', 'L2VPNs'),
-                get_model_item('ipam', 'l2vpntermination', 'Terminations'),
+                get_model_item('ipam', 'l2vpn', _('L2VPNs')),
+                get_model_item('ipam', 'l2vpntermination', _('Terminations')),
             ),
             ),
         ),
         ),
     ),
     ),
 )
 )
 
 
 VIRTUALIZATION_MENU = Menu(
 VIRTUALIZATION_MENU = Menu(
-    label='Virtualization',
+    label=_('Virtualization'),
     icon_class='mdi mdi-monitor',
     icon_class='mdi mdi-monitor',
     groups=(
     groups=(
         MenuGroup(
         MenuGroup(
-            label='Virtual Machines',
+            label=_('Virtual Machines'),
             items=(
             items=(
-                get_model_item('virtualization', 'virtualmachine', 'Virtual Machines'),
-                get_model_item('virtualization', 'vminterface', 'Interfaces', actions=['import']),
+                get_model_item('virtualization', 'virtualmachine', _('Virtual Machines')),
+                get_model_item('virtualization', 'vminterface', _('Interfaces'), actions=['import']),
             ),
             ),
         ),
         ),
         MenuGroup(
         MenuGroup(
-            label='Clusters',
+            label=_('Clusters'),
             items=(
             items=(
-                get_model_item('virtualization', 'cluster', 'Clusters'),
-                get_model_item('virtualization', 'clustertype', 'Cluster Types'),
-                get_model_item('virtualization', 'clustergroup', 'Cluster Groups'),
+                get_model_item('virtualization', 'cluster', _('Clusters')),
+                get_model_item('virtualization', 'clustertype', _('Cluster Types')),
+                get_model_item('virtualization', 'clustergroup', _('Cluster Groups')),
             ),
             ),
         ),
         ),
     ),
     ),
 )
 )
 
 
 CIRCUITS_MENU = Menu(
 CIRCUITS_MENU = Menu(
-    label='Circuits',
+    label=_('Circuits'),
     icon_class='mdi mdi-transit-connection-variant',
     icon_class='mdi mdi-transit-connection-variant',
     groups=(
     groups=(
         MenuGroup(
         MenuGroup(
-            label='Circuits',
+            label=_('Circuits'),
             items=(
             items=(
-                get_model_item('circuits', 'circuit', 'Circuits'),
-                get_model_item('circuits', 'circuittype', 'Circuit Types'),
+                get_model_item('circuits', 'circuit', _('Circuits')),
+                get_model_item('circuits', 'circuittype', _('Circuit Types')),
             ),
             ),
         ),
         ),
         MenuGroup(
         MenuGroup(
-            label='Providers',
+            label=_('Providers'),
             items=(
             items=(
-                get_model_item('circuits', 'provider', 'Providers'),
-                get_model_item('circuits', 'providernetwork', 'Provider Networks'),
+                get_model_item('circuits', 'provider', _('Providers')),
+                get_model_item('circuits', 'providernetwork', _('Provider Networks')),
             ),
             ),
         ),
         ),
     ),
     ),
 )
 )
 
 
 POWER_MENU = Menu(
 POWER_MENU = Menu(
-    label='Power',
+    label=_('Power'),
     icon_class='mdi mdi-flash',
     icon_class='mdi mdi-flash',
     groups=(
     groups=(
         MenuGroup(
         MenuGroup(
-            label='Power',
+            label=_('Power'),
             items=(
             items=(
-                get_model_item('dcim', 'powerfeed', 'Power Feeds'),
-                get_model_item('dcim', 'powerpanel', 'Power Panels'),
+                get_model_item('dcim', 'powerfeed', _('Power Feeds')),
+                get_model_item('dcim', 'powerpanel', _('Power Panels')),
             ),
             ),
         ),
         ),
     ),
     ),
 )
 )
 
 
 OTHER_MENU = Menu(
 OTHER_MENU = Menu(
-    label='Other',
+    label=_('Other'),
     icon_class='mdi mdi-notification-clear-all',
     icon_class='mdi mdi-notification-clear-all',
     groups=(
     groups=(
         MenuGroup(
         MenuGroup(
-            label='Logging',
+            label=_('Logging'),
             items=(
             items=(
-                get_model_item('extras', 'journalentry', 'Journal Entries', actions=[]),
-                get_model_item('extras', 'objectchange', 'Change Log', actions=[]),
+                get_model_item('extras', 'journalentry', _('Journal Entries'), actions=[]),
+                get_model_item('extras', 'objectchange', _('Change Log'), actions=[]),
             ),
             ),
         ),
         ),
         MenuGroup(
         MenuGroup(
-            label='Customization',
+            label=_('Customization'),
             items=(
             items=(
-                get_model_item('extras', 'customfield', 'Custom Fields'),
-                get_model_item('extras', 'customlink', 'Custom Links'),
-                get_model_item('extras', 'exporttemplate', 'Export Templates'),
-                get_model_item('extras', 'savedfilter', 'Saved Filters'),
+                get_model_item('extras', 'customfield', _('Custom Fields')),
+                get_model_item('extras', 'customlink', _('Custom Links')),
+                get_model_item('extras', 'exporttemplate', _('Export Templates')),
+                get_model_item('extras', 'savedfilter', _('Saved Filters')),
             ),
             ),
         ),
         ),
         MenuGroup(
         MenuGroup(
-            label='Integrations',
+            label=_('Integrations'),
             items=(
             items=(
-                get_model_item('extras', 'webhook', 'Webhooks'),
+                get_model_item('extras', 'webhook', _('Webhooks')),
                 MenuItem(
                 MenuItem(
                     link='extras:report_list',
                     link='extras:report_list',
-                    link_text='Reports',
+                    link_text=_('Reports'),
                     permissions=['extras.view_report']
                     permissions=['extras.view_report']
                 ),
                 ),
                 MenuItem(
                 MenuItem(
                     link='extras:script_list',
                     link='extras:script_list',
-                    link_text='Scripts',
+                    link_text=_('Scripts'),
                     permissions=['extras.view_script']
                     permissions=['extras.view_script']
                 ),
                 ),
                 MenuItem(
                 MenuItem(
                     link='extras:jobresult_list',
                     link='extras:jobresult_list',
-                    link_text='Job Results',
+                    link_text=_('Job Results'),
                     permissions=['extras.view_jobresult'],
                     permissions=['extras.view_jobresult'],
                 ),
                 ),
             ),
             ),
         ),
         ),
         MenuGroup(
         MenuGroup(
-            label='Other',
+            label=_('Other'),
             items=(
             items=(
                 get_model_item('extras', 'tag', 'Tags'),
                 get_model_item('extras', 'tag', 'Tags'),
-                get_model_item('extras', 'configcontext', 'Config Contexts', actions=['add']),
+                get_model_item('extras', 'configcontext', _('Config Contexts'), actions=['add']),
             ),
             ),
         ),
         ),
     ),
     ),
@@ -342,7 +343,7 @@ if registry['plugins']['menu_items']:
         for label, items in registry['plugins']['menu_items'].items()
         for label, items in registry['plugins']['menu_items'].items()
     ]
     ]
     plugins_menu = Menu(
     plugins_menu = Menu(
-        label="Plugins",
+        label=_("Plugins"),
         icon_class="mdi mdi-puzzle",
         icon_class="mdi mdi-puzzle",
         groups=groups
         groups=groups
     )
     )

+ 7 - 6
netbox/netbox/preferences.py

@@ -1,3 +1,4 @@
+from django.utils.translation import gettext as _
 from netbox.registry import registry
 from netbox.registry import registry
 from users.preferences import UserPreference
 from users.preferences import UserPreference
 from utilities.paginator import EnhancedPaginator
 from utilities.paginator import EnhancedPaginator
@@ -13,7 +14,7 @@ PREFERENCES = {
 
 
     # User interface
     # User interface
     'ui.colormode': UserPreference(
     'ui.colormode': UserPreference(
-        label='Color mode',
+        label=_('Color mode'),
         choices=(
         choices=(
             ('light', 'Light'),
             ('light', 'Light'),
             ('dark', 'Dark'),
             ('dark', 'Dark'),
@@ -21,25 +22,25 @@ PREFERENCES = {
         default='light',
         default='light',
     ),
     ),
     'pagination.per_page': UserPreference(
     'pagination.per_page': UserPreference(
-        label='Page length',
+        label=_('Page length'),
         choices=get_page_lengths(),
         choices=get_page_lengths(),
-        description='The number of objects to display per page',
+        description=_('The number of objects to display per page'),
         coerce=lambda x: int(x)
         coerce=lambda x: int(x)
     ),
     ),
     'pagination.placement': UserPreference(
     'pagination.placement': UserPreference(
-        label='Paginator placement',
+        label=_('Paginator placement'),
         choices=(
         choices=(
             ('bottom', 'Bottom'),
             ('bottom', 'Bottom'),
             ('top', 'Top'),
             ('top', 'Top'),
             ('both', 'Both'),
             ('both', 'Both'),
         ),
         ),
-        description='Where the paginator controls will be displayed relative to a table',
+        description=_('Where the paginator controls will be displayed relative to a table'),
         default='bottom'
         default='bottom'
     ),
     ),
 
 
     # Miscellaneous
     # Miscellaneous
     'data_format': UserPreference(
     'data_format': UserPreference(
-        label='Data format',
+        label=_('Data format'),
         choices=(
         choices=(
             ('json', 'JSON'),
             ('json', 'JSON'),
             ('yaml', 'YAML'),
             ('yaml', 'YAML'),

+ 19 - 18
netbox/tenancy/filtersets.py

@@ -1,5 +1,6 @@
 import django_filters
 import django_filters
 from django.db.models import Q
 from django.db.models import Q
+from django.utils.translation import gettext as _
 
 
 from netbox.filtersets import ChangeLoggedModelFilterSet, OrganizationalModelFilterSet, NetBoxModelFilterSet
 from netbox.filtersets import ChangeLoggedModelFilterSet, OrganizationalModelFilterSet, NetBoxModelFilterSet
 from utilities.filters import ContentTypeFilter, TreeNodeMultipleChoiceFilter
 from utilities.filters import ContentTypeFilter, TreeNodeMultipleChoiceFilter
@@ -25,13 +26,13 @@ __all__ = (
 class ContactGroupFilterSet(OrganizationalModelFilterSet):
 class ContactGroupFilterSet(OrganizationalModelFilterSet):
     parent_id = django_filters.ModelMultipleChoiceFilter(
     parent_id = django_filters.ModelMultipleChoiceFilter(
         queryset=ContactGroup.objects.all(),
         queryset=ContactGroup.objects.all(),
-        label='Contact group (ID)',
+        label=_('Contact group (ID)'),
     )
     )
     parent = django_filters.ModelMultipleChoiceFilter(
     parent = django_filters.ModelMultipleChoiceFilter(
         field_name='parent__slug',
         field_name='parent__slug',
         queryset=ContactGroup.objects.all(),
         queryset=ContactGroup.objects.all(),
         to_field_name='slug',
         to_field_name='slug',
-        label='Contact group (slug)',
+        label=_('Contact group (slug)'),
     )
     )
 
 
     class Meta:
     class Meta:
@@ -51,14 +52,14 @@ class ContactFilterSet(NetBoxModelFilterSet):
         queryset=ContactGroup.objects.all(),
         queryset=ContactGroup.objects.all(),
         field_name='group',
         field_name='group',
         lookup_expr='in',
         lookup_expr='in',
-        label='Contact group (ID)',
+        label=_('Contact group (ID)'),
     )
     )
     group = TreeNodeMultipleChoiceFilter(
     group = TreeNodeMultipleChoiceFilter(
         queryset=ContactGroup.objects.all(),
         queryset=ContactGroup.objects.all(),
         field_name='group',
         field_name='group',
         lookup_expr='in',
         lookup_expr='in',
         to_field_name='slug',
         to_field_name='slug',
-        label='Contact group (slug)',
+        label=_('Contact group (slug)'),
     )
     )
 
 
     class Meta:
     class Meta:
@@ -83,17 +84,17 @@ class ContactAssignmentFilterSet(ChangeLoggedModelFilterSet):
     content_type = ContentTypeFilter()
     content_type = ContentTypeFilter()
     contact_id = django_filters.ModelMultipleChoiceFilter(
     contact_id = django_filters.ModelMultipleChoiceFilter(
         queryset=Contact.objects.all(),
         queryset=Contact.objects.all(),
-        label='Contact (ID)',
+        label=_('Contact (ID)'),
     )
     )
     role_id = django_filters.ModelMultipleChoiceFilter(
     role_id = django_filters.ModelMultipleChoiceFilter(
         queryset=ContactRole.objects.all(),
         queryset=ContactRole.objects.all(),
-        label='Contact role (ID)',
+        label=_('Contact role (ID)'),
     )
     )
     role = django_filters.ModelMultipleChoiceFilter(
     role = django_filters.ModelMultipleChoiceFilter(
         field_name='role__slug',
         field_name='role__slug',
         queryset=ContactRole.objects.all(),
         queryset=ContactRole.objects.all(),
         to_field_name='slug',
         to_field_name='slug',
-        label='Contact role (slug)',
+        label=_('Contact role (slug)'),
     )
     )
 
 
     class Meta:
     class Meta:
@@ -105,18 +106,18 @@ class ContactModelFilterSet(django_filters.FilterSet):
     contact = django_filters.ModelMultipleChoiceFilter(
     contact = django_filters.ModelMultipleChoiceFilter(
         field_name='contacts__contact',
         field_name='contacts__contact',
         queryset=Contact.objects.all(),
         queryset=Contact.objects.all(),
-        label='Contact',
+        label=_('Contact'),
     )
     )
     contact_role = django_filters.ModelMultipleChoiceFilter(
     contact_role = django_filters.ModelMultipleChoiceFilter(
         field_name='contacts__role',
         field_name='contacts__role',
         queryset=ContactRole.objects.all(),
         queryset=ContactRole.objects.all(),
-        label='Contact Role'
+        label=_('Contact Role')
     )
     )
     contact_group = TreeNodeMultipleChoiceFilter(
     contact_group = TreeNodeMultipleChoiceFilter(
         queryset=ContactGroup.objects.all(),
         queryset=ContactGroup.objects.all(),
         field_name='contacts__contact__group',
         field_name='contacts__contact__group',
         lookup_expr='in',
         lookup_expr='in',
-        label='Contact group',
+        label=_('Contact group'),
     )
     )
 
 
 
 
@@ -127,13 +128,13 @@ class ContactModelFilterSet(django_filters.FilterSet):
 class TenantGroupFilterSet(OrganizationalModelFilterSet):
 class TenantGroupFilterSet(OrganizationalModelFilterSet):
     parent_id = django_filters.ModelMultipleChoiceFilter(
     parent_id = django_filters.ModelMultipleChoiceFilter(
         queryset=TenantGroup.objects.all(),
         queryset=TenantGroup.objects.all(),
-        label='Tenant group (ID)',
+        label=_('Tenant group (ID)'),
     )
     )
     parent = django_filters.ModelMultipleChoiceFilter(
     parent = django_filters.ModelMultipleChoiceFilter(
         field_name='parent__slug',
         field_name='parent__slug',
         queryset=TenantGroup.objects.all(),
         queryset=TenantGroup.objects.all(),
         to_field_name='slug',
         to_field_name='slug',
-        label='Tenant group (slug)',
+        label=_('Tenant group (slug)'),
     )
     )
 
 
     class Meta:
     class Meta:
@@ -146,14 +147,14 @@ class TenantFilterSet(NetBoxModelFilterSet, ContactModelFilterSet):
         queryset=TenantGroup.objects.all(),
         queryset=TenantGroup.objects.all(),
         field_name='group',
         field_name='group',
         lookup_expr='in',
         lookup_expr='in',
-        label='Tenant group (ID)',
+        label=_('Tenant group (ID)'),
     )
     )
     group = TreeNodeMultipleChoiceFilter(
     group = TreeNodeMultipleChoiceFilter(
         queryset=TenantGroup.objects.all(),
         queryset=TenantGroup.objects.all(),
         field_name='group',
         field_name='group',
         lookup_expr='in',
         lookup_expr='in',
         to_field_name='slug',
         to_field_name='slug',
-        label='Tenant group (slug)',
+        label=_('Tenant group (slug)'),
     )
     )
 
 
     class Meta:
     class Meta:
@@ -179,22 +180,22 @@ class TenancyFilterSet(django_filters.FilterSet):
         queryset=TenantGroup.objects.all(),
         queryset=TenantGroup.objects.all(),
         field_name='tenant__group',
         field_name='tenant__group',
         lookup_expr='in',
         lookup_expr='in',
-        label='Tenant Group (ID)',
+        label=_('Tenant Group (ID)'),
     )
     )
     tenant_group = TreeNodeMultipleChoiceFilter(
     tenant_group = TreeNodeMultipleChoiceFilter(
         queryset=TenantGroup.objects.all(),
         queryset=TenantGroup.objects.all(),
         field_name='tenant__group',
         field_name='tenant__group',
         to_field_name='slug',
         to_field_name='slug',
         lookup_expr='in',
         lookup_expr='in',
-        label='Tenant Group (slug)',
+        label=_('Tenant Group (slug)'),
     )
     )
     tenant_id = django_filters.ModelMultipleChoiceFilter(
     tenant_id = django_filters.ModelMultipleChoiceFilter(
         queryset=Tenant.objects.all(),
         queryset=Tenant.objects.all(),
-        label='Tenant (ID)',
+        label=_('Tenant (ID)'),
     )
     )
     tenant = django_filters.ModelMultipleChoiceFilter(
     tenant = django_filters.ModelMultipleChoiceFilter(
         queryset=Tenant.objects.all(),
         queryset=Tenant.objects.all(),
         field_name='tenant__slug',
         field_name='tenant__slug',
         to_field_name='slug',
         to_field_name='slug',
-        label='Tenant (slug)',
+        label=_('Tenant (slug)'),
     )
     )

+ 5 - 4
netbox/tenancy/forms/bulk_import.py

@@ -1,3 +1,4 @@
+from django.utils.translation import gettext as _
 from netbox.forms import NetBoxModelCSVForm
 from netbox.forms import NetBoxModelCSVForm
 from tenancy.models import *
 from tenancy.models import *
 from utilities.forms import CSVModelChoiceField, SlugField
 from utilities.forms import CSVModelChoiceField, SlugField
@@ -20,7 +21,7 @@ class TenantGroupCSVForm(NetBoxModelCSVForm):
         queryset=TenantGroup.objects.all(),
         queryset=TenantGroup.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
-        help_text='Parent group'
+        help_text=_('Parent group')
     )
     )
     slug = SlugField()
     slug = SlugField()
 
 
@@ -35,7 +36,7 @@ class TenantCSVForm(NetBoxModelCSVForm):
         queryset=TenantGroup.objects.all(),
         queryset=TenantGroup.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
-        help_text='Assigned group'
+        help_text=_('Assigned group')
     )
     )
 
 
     class Meta:
     class Meta:
@@ -52,7 +53,7 @@ class ContactGroupCSVForm(NetBoxModelCSVForm):
         queryset=ContactGroup.objects.all(),
         queryset=ContactGroup.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
-        help_text='Parent group'
+        help_text=_('Parent group')
     )
     )
     slug = SlugField()
     slug = SlugField()
 
 
@@ -74,7 +75,7 @@ class ContactCSVForm(NetBoxModelCSVForm):
         queryset=ContactGroup.objects.all(),
         queryset=ContactGroup.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
-        help_text='Assigned group'
+        help_text=_('Assigned group')
     )
     )
 
 
     class Meta:
     class Meta:

+ 6 - 5
netbox/users/admin/forms.py

@@ -3,6 +3,7 @@ from django.contrib.auth.models import Group, User
 from django.contrib.admin.widgets import FilteredSelectMultiple
 from django.contrib.admin.widgets import FilteredSelectMultiple
 from django.contrib.contenttypes.models import ContentType
 from django.contrib.contenttypes.models import ContentType
 from django.core.exceptions import FieldError, ValidationError
 from django.core.exceptions import FieldError, ValidationError
+from django.utils.translation import gettext as _
 
 
 from users.constants import CONSTRAINT_TOKEN_USER, OBJECTPERMISSION_OBJECT_TYPES
 from users.constants import CONSTRAINT_TOKEN_USER, OBJECTPERMISSION_OBJECT_TYPES
 from users.models import ObjectPermission, Token
 from users.models import ObjectPermission, Token
@@ -46,7 +47,7 @@ class GroupAdminForm(forms.ModelForm):
 class TokenAdminForm(forms.ModelForm):
 class TokenAdminForm(forms.ModelForm):
     key = forms.CharField(
     key = forms.CharField(
         required=False,
         required=False,
-        help_text="If no key is provided, one will be generated automatically."
+        help_text=_("If no key is provided, one will be generated automatically.")
     )
     )
 
 
     class Meta:
     class Meta:
@@ -70,10 +71,10 @@ class ObjectPermissionForm(forms.ModelForm):
         model = ObjectPermission
         model = ObjectPermission
         exclude = []
         exclude = []
         help_texts = {
         help_texts = {
-            'actions': 'Actions granted in addition to those listed above',
-            'constraints': 'JSON expression of a queryset filter that will return only permitted objects. Leave null '
-                           'to match all objects of this type. A list of multiple objects will result in a logical OR '
-                           'operation.'
+            'actions': _('Actions granted in addition to those listed above'),
+            'constraints': _('JSON expression of a queryset filter that will return only permitted objects. Leave null '
+                             'to match all objects of this type. A list of multiple objects will result in a logical OR '
+                             'operation.')
         }
         }
         labels = {
         labels = {
             'actions': 'Additional actions'
             'actions': 'Additional actions'

+ 13 - 12
netbox/users/filtersets.py

@@ -1,6 +1,7 @@
 import django_filters
 import django_filters
 from django.contrib.auth.models import Group, User
 from django.contrib.auth.models import Group, User
 from django.db.models import Q
 from django.db.models import Q
+from django.utils.translation import gettext as _
 
 
 from netbox.filtersets import BaseFilterSet
 from netbox.filtersets import BaseFilterSet
 from users.models import ObjectPermission, Token
 from users.models import ObjectPermission, Token
@@ -15,7 +16,7 @@ __all__ = (
 class GroupFilterSet(BaseFilterSet):
 class GroupFilterSet(BaseFilterSet):
     q = django_filters.CharFilter(
     q = django_filters.CharFilter(
         method='search',
         method='search',
-        label='Search',
+        label=_('Search'),
     )
     )
 
 
     class Meta:
     class Meta:
@@ -31,18 +32,18 @@ class GroupFilterSet(BaseFilterSet):
 class UserFilterSet(BaseFilterSet):
 class UserFilterSet(BaseFilterSet):
     q = django_filters.CharFilter(
     q = django_filters.CharFilter(
         method='search',
         method='search',
-        label='Search',
+        label=_('Search'),
     )
     )
     group_id = django_filters.ModelMultipleChoiceFilter(
     group_id = django_filters.ModelMultipleChoiceFilter(
         field_name='groups',
         field_name='groups',
         queryset=Group.objects.all(),
         queryset=Group.objects.all(),
-        label='Group',
+        label=_('Group'),
     )
     )
     group = django_filters.ModelMultipleChoiceFilter(
     group = django_filters.ModelMultipleChoiceFilter(
         field_name='groups__name',
         field_name='groups__name',
         queryset=Group.objects.all(),
         queryset=Group.objects.all(),
         to_field_name='name',
         to_field_name='name',
-        label='Group (name)',
+        label=_('Group (name)'),
     )
     )
 
 
     class Meta:
     class Meta:
@@ -63,18 +64,18 @@ class UserFilterSet(BaseFilterSet):
 class TokenFilterSet(BaseFilterSet):
 class TokenFilterSet(BaseFilterSet):
     q = django_filters.CharFilter(
     q = django_filters.CharFilter(
         method='search',
         method='search',
-        label='Search',
+        label=_('Search'),
     )
     )
     user_id = django_filters.ModelMultipleChoiceFilter(
     user_id = django_filters.ModelMultipleChoiceFilter(
         field_name='user',
         field_name='user',
         queryset=User.objects.all(),
         queryset=User.objects.all(),
-        label='User',
+        label=_('User'),
     )
     )
     user = django_filters.ModelMultipleChoiceFilter(
     user = django_filters.ModelMultipleChoiceFilter(
         field_name='user__username',
         field_name='user__username',
         queryset=User.objects.all(),
         queryset=User.objects.all(),
         to_field_name='username',
         to_field_name='username',
-        label='User (name)',
+        label=_('User (name)'),
     )
     )
     created = django_filters.DateTimeFilter()
     created = django_filters.DateTimeFilter()
     created__gte = django_filters.DateTimeFilter(
     created__gte = django_filters.DateTimeFilter(
@@ -111,29 +112,29 @@ class TokenFilterSet(BaseFilterSet):
 class ObjectPermissionFilterSet(BaseFilterSet):
 class ObjectPermissionFilterSet(BaseFilterSet):
     q = django_filters.CharFilter(
     q = django_filters.CharFilter(
         method='search',
         method='search',
-        label='Search',
+        label=_('Search'),
     )
     )
     user_id = django_filters.ModelMultipleChoiceFilter(
     user_id = django_filters.ModelMultipleChoiceFilter(
         field_name='users',
         field_name='users',
         queryset=User.objects.all(),
         queryset=User.objects.all(),
-        label='User',
+        label=_('User'),
     )
     )
     user = django_filters.ModelMultipleChoiceFilter(
     user = django_filters.ModelMultipleChoiceFilter(
         field_name='users__username',
         field_name='users__username',
         queryset=User.objects.all(),
         queryset=User.objects.all(),
         to_field_name='username',
         to_field_name='username',
-        label='User (name)',
+        label=_('User (name)'),
     )
     )
     group_id = django_filters.ModelMultipleChoiceFilter(
     group_id = django_filters.ModelMultipleChoiceFilter(
         field_name='groups',
         field_name='groups',
         queryset=Group.objects.all(),
         queryset=Group.objects.all(),
-        label='Group',
+        label=_('Group'),
     )
     )
     group = django_filters.ModelMultipleChoiceFilter(
     group = django_filters.ModelMultipleChoiceFilter(
         field_name='groups__name',
         field_name='groups__name',
         queryset=Group.objects.all(),
         queryset=Group.objects.all(),
         to_field_name='name',
         to_field_name='name',
-        label='Group (name)',
+        label=_('Group (name)'),
     )
     )
 
 
     class Meta:
     class Meta:

+ 5 - 4
netbox/users/forms.py

@@ -3,6 +3,7 @@ from django.conf import settings
 from django.contrib.auth.forms import AuthenticationForm, PasswordChangeForm as DjangoPasswordChangeForm
 from django.contrib.auth.forms import AuthenticationForm, PasswordChangeForm as DjangoPasswordChangeForm
 from django.contrib.postgres.forms import SimpleArrayField
 from django.contrib.postgres.forms import SimpleArrayField
 from django.utils.html import mark_safe
 from django.utils.html import mark_safe
+from django.utils.translation import gettext as _
 
 
 from ipam.formfields import IPNetworkFormField
 from ipam.formfields import IPNetworkFormField
 from netbox.preferences import PREFERENCES
 from netbox.preferences import PREFERENCES
@@ -100,14 +101,14 @@ class UserConfigForm(BootstrapMixin, forms.ModelForm, metaclass=UserConfigFormMe
 class TokenForm(BootstrapMixin, forms.ModelForm):
 class TokenForm(BootstrapMixin, forms.ModelForm):
     key = forms.CharField(
     key = forms.CharField(
         required=False,
         required=False,
-        help_text="If no key is provided, one will be generated automatically."
+        help_text=_("If no key is provided, one will be generated automatically.")
     )
     )
     allowed_ips = SimpleArrayField(
     allowed_ips = SimpleArrayField(
         base_field=IPNetworkFormField(),
         base_field=IPNetworkFormField(),
         required=False,
         required=False,
-        label='Allowed IPs',
-        help_text='Allowed IPv4/IPv6 networks from where the token can be used. Leave blank for no restrictions. '
-                  'Example: <code>10.1.1.0/24,192.168.10.16/32,2001:db8:1::/64</code>',
+        label=_('Allowed IPs'),
+        help_text=_('Allowed IPv4/IPv6 networks from where the token can be used. Leave blank for no restrictions. '
+                    'Example: <code>10.1.1.0/24,192.168.10.16/32,2001:db8:1::/64</code>'),
     )
     )
 
 
     class Meta:
     class Meta:

+ 6 - 5
netbox/users/models.py

@@ -10,6 +10,7 @@ from django.db import models
 from django.db.models.signals import post_save
 from django.db.models.signals import post_save
 from django.dispatch import receiver
 from django.dispatch import receiver
 from django.utils import timezone
 from django.utils import timezone
+from django.utils.translation import gettext as _
 from netaddr import IPNetwork
 from netaddr import IPNetwork
 
 
 from ipam.fields import IPNetworkField
 from ipam.fields import IPNetworkField
@@ -216,7 +217,7 @@ class Token(models.Model):
     )
     )
     write_enabled = models.BooleanField(
     write_enabled = models.BooleanField(
         default=True,
         default=True,
-        help_text='Permit create/update/delete operations using this key'
+        help_text=_('Permit create/update/delete operations using this key')
     )
     )
     description = models.CharField(
     description = models.CharField(
         max_length=200,
         max_length=200,
@@ -227,8 +228,8 @@ class Token(models.Model):
         blank=True,
         blank=True,
         null=True,
         null=True,
         verbose_name='Allowed IPs',
         verbose_name='Allowed IPs',
-        help_text='Allowed IPv4/IPv6 networks from where the token can be used. Leave blank for no restrictions. '
-                  'Ex: "10.1.1.0/24, 192.168.10.16/32, 2001:DB8:1::/64"',
+        help_text=_('Allowed IPv4/IPv6 networks from where the token can be used. Leave blank for no restrictions. '
+                    'Ex: "10.1.1.0/24, 192.168.10.16/32, 2001:DB8:1::/64"'),
     )
     )
 
 
     def __str__(self):
     def __str__(self):
@@ -304,12 +305,12 @@ class ObjectPermission(models.Model):
     )
     )
     actions = ArrayField(
     actions = ArrayField(
         base_field=models.CharField(max_length=30),
         base_field=models.CharField(max_length=30),
-        help_text="The list of actions granted by this permission"
+        help_text=_("The list of actions granted by this permission")
     )
     )
     constraints = models.JSONField(
     constraints = models.JSONField(
         blank=True,
         blank=True,
         null=True,
         null=True,
-        help_text="Queryset filter matching the applicable objects of the selected type(s)"
+        help_text=_("Queryset filter matching the applicable objects of the selected type(s)")
     )
     )
 
 
     objects = RestrictedQuerySet.as_manager()
     objects = RestrictedQuerySet.as_manager()

+ 4 - 3
netbox/utilities/forms/fields/csv.py

@@ -5,6 +5,7 @@ from django import forms
 from django.contrib.contenttypes.models import ContentType
 from django.contrib.contenttypes.models import ContentType
 from django.core.exceptions import MultipleObjectsReturned, ObjectDoesNotExist
 from django.core.exceptions import MultipleObjectsReturned, ObjectDoesNotExist
 from django.db.models import Q
 from django.db.models import Q
+from django.utils.translation import gettext as _
 
 
 from utilities.choices import unpack_grouped_choices
 from utilities.choices import unpack_grouped_choices
 from utilities.forms.utils import parse_csv, validate_csv
 from utilities.forms.utils import parse_csv, validate_csv
@@ -50,9 +51,9 @@ class CSVDataField(forms.CharField):
         if not self.initial:
         if not self.initial:
             self.initial = ','.join(self.required_fields) + '\n'
             self.initial = ','.join(self.required_fields) + '\n'
         if not self.help_text:
         if not self.help_text:
-            self.help_text = 'Enter the list of column headers followed by one line per record to be imported, using ' \
-                             'commas to separate values. Multi-line data and values containing commas may be wrapped ' \
-                             'in double quotes.'
+            self.help_text = _('Enter the list of column headers followed by one line per record to be imported, using '
+                               'commas to separate values. Multi-line data and values containing commas may be wrapped '
+                               'in double quotes.')
 
 
     def to_python(self, value):
     def to_python(self, value):
         reader = csv.reader(StringIO(value.strip()))
         reader = csv.reader(StringIO(value.strip()))

+ 3 - 2
netbox/utilities/forms/fields/expandable.py

@@ -1,6 +1,7 @@
 import re
 import re
 
 
 from django import forms
 from django import forms
+from django.utils.translation import gettext as _
 
 
 from utilities.forms.constants import *
 from utilities.forms.constants import *
 from utilities.forms.utils import expand_alphanumeric_pattern, expand_ipaddress_pattern
 from utilities.forms.utils import expand_alphanumeric_pattern, expand_ipaddress_pattern
@@ -42,8 +43,8 @@ class ExpandableIPAddressField(forms.CharField):
     def __init__(self, *args, **kwargs):
     def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)
         super().__init__(*args, **kwargs)
         if not self.help_text:
         if not self.help_text:
-            self.help_text = 'Specify a numeric range to create multiple IPs.<br />'\
-                             'Example: <code>192.0.2.[1,5,100-254]/24</code>'
+            self.help_text = _('Specify a numeric range to create multiple IPs.<br />'
+                               'Example: <code>192.0.2.[1,5,100-254]/24</code>')
 
 
     def to_python(self, value):
     def to_python(self, value):
         # Hackish address family detection but it's all we have to work with
         # Hackish address family detection but it's all we have to work with

+ 3 - 2
netbox/utilities/forms/fields/fields.py

@@ -4,6 +4,7 @@ from django import forms
 from django.db.models import Count
 from django.db.models import Count
 from django.forms.fields import JSONField as _JSONField, InvalidJSONInput
 from django.forms.fields import JSONField as _JSONField, InvalidJSONInput
 from django.templatetags.static import static
 from django.templatetags.static import static
+from django.utils.translation import gettext as _
 from netaddr import AddrFormatError, EUI
 from netaddr import AddrFormatError, EUI
 
 
 from utilities.forms import widgets
 from utilities.forms import widgets
@@ -45,7 +46,7 @@ class SlugField(forms.SlugField):
         slug_source: Name of the form field from which the slug value will be derived
         slug_source: Name of the form field from which the slug value will be derived
     """
     """
     widget = widgets.SlugWidget
     widget = widgets.SlugWidget
-    help_text = "URL-friendly unique shorthand"
+    help_text = _("URL-friendly unique shorthand")
 
 
     def __init__(self, *, slug_source='name', help_text=help_text, **kwargs):
     def __init__(self, *, slug_source='name', help_text=help_text, **kwargs):
         super().__init__(help_text=help_text, **kwargs)
         super().__init__(help_text=help_text, **kwargs)
@@ -97,7 +98,7 @@ class JSONField(_JSONField):
     def __init__(self, *args, **kwargs):
     def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)
         super().__init__(*args, **kwargs)
         if not self.help_text:
         if not self.help_text:
-            self.help_text = 'Enter context data in <a href="https://json.org/">JSON</a> format.'
+            self.help_text = _('Enter context data in <a href="https://json.org/">JSON</a> format.')
             self.widget.attrs['placeholder'] = ''
             self.widget.attrs['placeholder'] = ''
             self.widget.attrs['class'] = 'font-monospace'
             self.widget.attrs['class'] = 'font-monospace'
 
 

+ 7 - 6
netbox/utilities/forms/forms.py

@@ -5,8 +5,9 @@ from io import StringIO
 
 
 import yaml
 import yaml
 from django import forms
 from django import forms
-from utilities.forms.utils import parse_csv
+from django.utils.translation import gettext as _
 
 
+from utilities.forms.utils import parse_csv
 from .choices import ImportFormatChoices
 from .choices import ImportFormatChoices
 from .widgets import APISelect, APISelectMultiple, ClearableFileInput, StaticSelect
 from .widgets import APISelect, APISelectMultiple, ClearableFileInput, StaticSelect
 
 
@@ -103,7 +104,7 @@ class BulkRenameForm(BootstrapMixin, forms.Form):
     use_regex = forms.BooleanField(
     use_regex = forms.BooleanField(
         required=False,
         required=False,
         initial=True,
         initial=True,
-        label='Use regular expressions'
+        label=_('Use regular expressions')
     )
     )
 
 
     def clean(self):
     def clean(self):
@@ -145,7 +146,7 @@ class ImportForm(BootstrapMixin, forms.Form):
     data = forms.CharField(
     data = forms.CharField(
         required=False,
         required=False,
         widget=forms.Textarea(attrs={'class': 'font-monospace'}),
         widget=forms.Textarea(attrs={'class': 'font-monospace'}),
-        help_text="Enter object data in CSV, JSON or YAML format."
+        help_text=_("Enter object data in CSV, JSON or YAML format.")
     )
     )
     data_file = forms.FileField(
     data_file = forms.FileField(
         label="Data file",
         label="Data file",
@@ -219,7 +220,7 @@ class FilterForm(BootstrapMixin, forms.Form):
     """
     """
     q = forms.CharField(
     q = forms.CharField(
         required=False,
         required=False,
-        label='Search'
+        label=_('Search')
     )
     )
 
 
 
 
@@ -233,7 +234,7 @@ class TableConfigForm(BootstrapMixin, forms.Form):
         widget=forms.SelectMultiple(
         widget=forms.SelectMultiple(
             attrs={'size': 10, 'class': 'form-select'}
             attrs={'size': 10, 'class': 'form-select'}
         ),
         ),
-        label='Available Columns'
+        label=_('Available Columns')
     )
     )
     columns = forms.MultipleChoiceField(
     columns = forms.MultipleChoiceField(
         choices=[],
         choices=[],
@@ -241,7 +242,7 @@ class TableConfigForm(BootstrapMixin, forms.Form):
         widget=forms.SelectMultiple(
         widget=forms.SelectMultiple(
             attrs={'size': 10, 'class': 'form-select'}
             attrs={'size': 10, 'class': 'form-select'}
         ),
         ),
-        label='Selected Columns'
+        label=_('Selected Columns')
     )
     )
 
 
     def __init__(self, table, *args, **kwargs):
     def __init__(self, table, *args, **kwargs):

+ 40 - 39
netbox/virtualization/filtersets.py

@@ -1,5 +1,6 @@
 import django_filters
 import django_filters
 from django.db.models import Q
 from django.db.models import Q
+from django.utils.translation import gettext as _
 
 
 from dcim.models import Device, DeviceRole, Platform, Region, Site, SiteGroup
 from dcim.models import Device, DeviceRole, Platform, Region, Site, SiteGroup
 from extras.filtersets import LocalConfigContextFilterSet
 from extras.filtersets import LocalConfigContextFilterSet
@@ -38,57 +39,57 @@ class ClusterFilterSet(NetBoxModelFilterSet, TenancyFilterSet, ContactModelFilte
         queryset=Region.objects.all(),
         queryset=Region.objects.all(),
         field_name='site__region',
         field_name='site__region',
         lookup_expr='in',
         lookup_expr='in',
-        label='Region (ID)',
+        label=_('Region (ID)'),
     )
     )
     region = TreeNodeMultipleChoiceFilter(
     region = TreeNodeMultipleChoiceFilter(
         queryset=Region.objects.all(),
         queryset=Region.objects.all(),
         field_name='site__region',
         field_name='site__region',
         lookup_expr='in',
         lookup_expr='in',
         to_field_name='slug',
         to_field_name='slug',
-        label='Region (slug)',
+        label=_('Region (slug)'),
     )
     )
     site_group_id = TreeNodeMultipleChoiceFilter(
     site_group_id = TreeNodeMultipleChoiceFilter(
         queryset=SiteGroup.objects.all(),
         queryset=SiteGroup.objects.all(),
         field_name='site__group',
         field_name='site__group',
         lookup_expr='in',
         lookup_expr='in',
-        label='Site group (ID)',
+        label=_('Site group (ID)'),
     )
     )
     site_group = TreeNodeMultipleChoiceFilter(
     site_group = TreeNodeMultipleChoiceFilter(
         queryset=SiteGroup.objects.all(),
         queryset=SiteGroup.objects.all(),
         field_name='site__group',
         field_name='site__group',
         lookup_expr='in',
         lookup_expr='in',
         to_field_name='slug',
         to_field_name='slug',
-        label='Site group (slug)',
+        label=_('Site group (slug)'),
     )
     )
     site_id = django_filters.ModelMultipleChoiceFilter(
     site_id = django_filters.ModelMultipleChoiceFilter(
         queryset=Site.objects.all(),
         queryset=Site.objects.all(),
-        label='Site (ID)',
+        label=_('Site (ID)'),
     )
     )
     site = django_filters.ModelMultipleChoiceFilter(
     site = django_filters.ModelMultipleChoiceFilter(
         field_name='site__slug',
         field_name='site__slug',
         queryset=Site.objects.all(),
         queryset=Site.objects.all(),
         to_field_name='slug',
         to_field_name='slug',
-        label='Site (slug)',
+        label=_('Site (slug)'),
     )
     )
     group_id = django_filters.ModelMultipleChoiceFilter(
     group_id = django_filters.ModelMultipleChoiceFilter(
         queryset=ClusterGroup.objects.all(),
         queryset=ClusterGroup.objects.all(),
-        label='Parent group (ID)',
+        label=_('Parent group (ID)'),
     )
     )
     group = django_filters.ModelMultipleChoiceFilter(
     group = django_filters.ModelMultipleChoiceFilter(
         field_name='group__slug',
         field_name='group__slug',
         queryset=ClusterGroup.objects.all(),
         queryset=ClusterGroup.objects.all(),
         to_field_name='slug',
         to_field_name='slug',
-        label='Parent group (slug)',
+        label=_('Parent group (slug)'),
     )
     )
     type_id = django_filters.ModelMultipleChoiceFilter(
     type_id = django_filters.ModelMultipleChoiceFilter(
         queryset=ClusterType.objects.all(),
         queryset=ClusterType.objects.all(),
-        label='Cluster type (ID)',
+        label=_('Cluster type (ID)'),
     )
     )
     type = django_filters.ModelMultipleChoiceFilter(
     type = django_filters.ModelMultipleChoiceFilter(
         field_name='type__slug',
         field_name='type__slug',
         queryset=ClusterType.objects.all(),
         queryset=ClusterType.objects.all(),
         to_field_name='slug',
         to_field_name='slug',
-        label='Cluster type (slug)',
+        label=_('Cluster type (slug)'),
     )
     )
     status = django_filters.MultipleChoiceFilter(
     status = django_filters.MultipleChoiceFilter(
         choices=ClusterStatusChoices,
         choices=ClusterStatusChoices,
@@ -121,111 +122,111 @@ class VirtualMachineFilterSet(
     cluster_group_id = django_filters.ModelMultipleChoiceFilter(
     cluster_group_id = django_filters.ModelMultipleChoiceFilter(
         field_name='cluster__group',
         field_name='cluster__group',
         queryset=ClusterGroup.objects.all(),
         queryset=ClusterGroup.objects.all(),
-        label='Cluster group (ID)',
+        label=_('Cluster group (ID)'),
     )
     )
     cluster_group = django_filters.ModelMultipleChoiceFilter(
     cluster_group = django_filters.ModelMultipleChoiceFilter(
         field_name='cluster__group__slug',
         field_name='cluster__group__slug',
         queryset=ClusterGroup.objects.all(),
         queryset=ClusterGroup.objects.all(),
         to_field_name='slug',
         to_field_name='slug',
-        label='Cluster group (slug)',
+        label=_('Cluster group (slug)'),
     )
     )
     cluster_type_id = django_filters.ModelMultipleChoiceFilter(
     cluster_type_id = django_filters.ModelMultipleChoiceFilter(
         field_name='cluster__type',
         field_name='cluster__type',
         queryset=ClusterType.objects.all(),
         queryset=ClusterType.objects.all(),
-        label='Cluster type (ID)',
+        label=_('Cluster type (ID)'),
     )
     )
     cluster_type = django_filters.ModelMultipleChoiceFilter(
     cluster_type = django_filters.ModelMultipleChoiceFilter(
         field_name='cluster__type__slug',
         field_name='cluster__type__slug',
         queryset=ClusterType.objects.all(),
         queryset=ClusterType.objects.all(),
         to_field_name='slug',
         to_field_name='slug',
-        label='Cluster type (slug)',
+        label=_('Cluster type (slug)'),
     )
     )
     cluster_id = django_filters.ModelMultipleChoiceFilter(
     cluster_id = django_filters.ModelMultipleChoiceFilter(
         queryset=Cluster.objects.all(),
         queryset=Cluster.objects.all(),
-        label='Cluster (ID)',
+        label=_('Cluster (ID)'),
     )
     )
     cluster = django_filters.ModelMultipleChoiceFilter(
     cluster = django_filters.ModelMultipleChoiceFilter(
         field_name='cluster__name',
         field_name='cluster__name',
         queryset=Cluster.objects.all(),
         queryset=Cluster.objects.all(),
         to_field_name='name',
         to_field_name='name',
-        label='Cluster',
+        label=_('Cluster'),
     )
     )
     device_id = django_filters.ModelMultipleChoiceFilter(
     device_id = django_filters.ModelMultipleChoiceFilter(
         queryset=Device.objects.all(),
         queryset=Device.objects.all(),
-        label='Device (ID)',
+        label=_('Device (ID)'),
     )
     )
     device = django_filters.ModelMultipleChoiceFilter(
     device = django_filters.ModelMultipleChoiceFilter(
         field_name='device__name',
         field_name='device__name',
         queryset=Device.objects.all(),
         queryset=Device.objects.all(),
         to_field_name='name',
         to_field_name='name',
-        label='Device',
+        label=_('Device'),
     )
     )
     region_id = TreeNodeMultipleChoiceFilter(
     region_id = TreeNodeMultipleChoiceFilter(
         queryset=Region.objects.all(),
         queryset=Region.objects.all(),
         field_name='site__region',
         field_name='site__region',
         lookup_expr='in',
         lookup_expr='in',
-        label='Region (ID)',
+        label=_('Region (ID)'),
     )
     )
     region = TreeNodeMultipleChoiceFilter(
     region = TreeNodeMultipleChoiceFilter(
         queryset=Region.objects.all(),
         queryset=Region.objects.all(),
         field_name='site__region',
         field_name='site__region',
         lookup_expr='in',
         lookup_expr='in',
         to_field_name='slug',
         to_field_name='slug',
-        label='Region (slug)',
+        label=_('Region (slug)'),
     )
     )
     site_group_id = TreeNodeMultipleChoiceFilter(
     site_group_id = TreeNodeMultipleChoiceFilter(
         queryset=SiteGroup.objects.all(),
         queryset=SiteGroup.objects.all(),
         field_name='site__group',
         field_name='site__group',
         lookup_expr='in',
         lookup_expr='in',
-        label='Site group (ID)',
+        label=_('Site group (ID)'),
     )
     )
     site_group = TreeNodeMultipleChoiceFilter(
     site_group = TreeNodeMultipleChoiceFilter(
         queryset=SiteGroup.objects.all(),
         queryset=SiteGroup.objects.all(),
         field_name='site__group',
         field_name='site__group',
         lookup_expr='in',
         lookup_expr='in',
         to_field_name='slug',
         to_field_name='slug',
-        label='Site group (slug)',
+        label=_('Site group (slug)'),
     )
     )
     site_id = django_filters.ModelMultipleChoiceFilter(
     site_id = django_filters.ModelMultipleChoiceFilter(
         queryset=Site.objects.all(),
         queryset=Site.objects.all(),
-        label='Site (ID)',
+        label=_('Site (ID)'),
     )
     )
     site = django_filters.ModelMultipleChoiceFilter(
     site = django_filters.ModelMultipleChoiceFilter(
         field_name='site__slug',
         field_name='site__slug',
         queryset=Site.objects.all(),
         queryset=Site.objects.all(),
         to_field_name='slug',
         to_field_name='slug',
-        label='Site (slug)',
+        label=_('Site (slug)'),
     )
     )
     name = MultiValueCharFilter(
     name = MultiValueCharFilter(
         lookup_expr='iexact'
         lookup_expr='iexact'
     )
     )
     role_id = django_filters.ModelMultipleChoiceFilter(
     role_id = django_filters.ModelMultipleChoiceFilter(
         queryset=DeviceRole.objects.all(),
         queryset=DeviceRole.objects.all(),
-        label='Role (ID)',
+        label=_('Role (ID)'),
     )
     )
     role = django_filters.ModelMultipleChoiceFilter(
     role = django_filters.ModelMultipleChoiceFilter(
         field_name='role__slug',
         field_name='role__slug',
         queryset=DeviceRole.objects.all(),
         queryset=DeviceRole.objects.all(),
         to_field_name='slug',
         to_field_name='slug',
-        label='Role (slug)',
+        label=_('Role (slug)'),
     )
     )
     platform_id = django_filters.ModelMultipleChoiceFilter(
     platform_id = django_filters.ModelMultipleChoiceFilter(
         queryset=Platform.objects.all(),
         queryset=Platform.objects.all(),
-        label='Platform (ID)',
+        label=_('Platform (ID)'),
     )
     )
     platform = django_filters.ModelMultipleChoiceFilter(
     platform = django_filters.ModelMultipleChoiceFilter(
         field_name='platform__slug',
         field_name='platform__slug',
         queryset=Platform.objects.all(),
         queryset=Platform.objects.all(),
         to_field_name='slug',
         to_field_name='slug',
-        label='Platform (slug)',
+        label=_('Platform (slug)'),
     )
     )
     mac_address = MultiValueMACAddressFilter(
     mac_address = MultiValueMACAddressFilter(
         field_name='interfaces__mac_address',
         field_name='interfaces__mac_address',
-        label='MAC address',
+        label=_('MAC address'),
     )
     )
     has_primary_ip = django_filters.BooleanFilter(
     has_primary_ip = django_filters.BooleanFilter(
         method='_has_primary_ip',
         method='_has_primary_ip',
-        label='Has a primary IP',
+        label=_('Has a primary IP'),
     )
     )
 
 
     class Meta:
     class Meta:
@@ -251,48 +252,48 @@ class VMInterfaceFilterSet(NetBoxModelFilterSet):
     cluster_id = django_filters.ModelMultipleChoiceFilter(
     cluster_id = django_filters.ModelMultipleChoiceFilter(
         field_name='virtual_machine__cluster',
         field_name='virtual_machine__cluster',
         queryset=Cluster.objects.all(),
         queryset=Cluster.objects.all(),
-        label='Cluster (ID)',
+        label=_('Cluster (ID)'),
     )
     )
     cluster = django_filters.ModelMultipleChoiceFilter(
     cluster = django_filters.ModelMultipleChoiceFilter(
         field_name='virtual_machine__cluster__name',
         field_name='virtual_machine__cluster__name',
         queryset=Cluster.objects.all(),
         queryset=Cluster.objects.all(),
         to_field_name='name',
         to_field_name='name',
-        label='Cluster',
+        label=_('Cluster'),
     )
     )
     virtual_machine_id = django_filters.ModelMultipleChoiceFilter(
     virtual_machine_id = django_filters.ModelMultipleChoiceFilter(
         field_name='virtual_machine',
         field_name='virtual_machine',
         queryset=VirtualMachine.objects.all(),
         queryset=VirtualMachine.objects.all(),
-        label='Virtual machine (ID)',
+        label=_('Virtual machine (ID)'),
     )
     )
     virtual_machine = django_filters.ModelMultipleChoiceFilter(
     virtual_machine = django_filters.ModelMultipleChoiceFilter(
         field_name='virtual_machine__name',
         field_name='virtual_machine__name',
         queryset=VirtualMachine.objects.all(),
         queryset=VirtualMachine.objects.all(),
         to_field_name='name',
         to_field_name='name',
-        label='Virtual machine',
+        label=_('Virtual machine'),
     )
     )
     parent_id = django_filters.ModelMultipleChoiceFilter(
     parent_id = django_filters.ModelMultipleChoiceFilter(
         field_name='parent',
         field_name='parent',
         queryset=VMInterface.objects.all(),
         queryset=VMInterface.objects.all(),
-        label='Parent interface (ID)',
+        label=_('Parent interface (ID)'),
     )
     )
     bridge_id = django_filters.ModelMultipleChoiceFilter(
     bridge_id = django_filters.ModelMultipleChoiceFilter(
         field_name='bridge',
         field_name='bridge',
         queryset=VMInterface.objects.all(),
         queryset=VMInterface.objects.all(),
-        label='Bridged interface (ID)',
+        label=_('Bridged interface (ID)'),
     )
     )
     mac_address = MultiValueMACAddressFilter(
     mac_address = MultiValueMACAddressFilter(
-        label='MAC address',
+        label=_('MAC address'),
     )
     )
     vrf_id = django_filters.ModelMultipleChoiceFilter(
     vrf_id = django_filters.ModelMultipleChoiceFilter(
         field_name='vrf',
         field_name='vrf',
         queryset=VRF.objects.all(),
         queryset=VRF.objects.all(),
-        label='VRF',
+        label=_('VRF'),
     )
     )
     vrf = django_filters.ModelMultipleChoiceFilter(
     vrf = django_filters.ModelMultipleChoiceFilter(
         field_name='vrf__rd',
         field_name='vrf__rd',
         queryset=VRF.objects.all(),
         queryset=VRF.objects.all(),
         to_field_name='rd',
         to_field_name='rd',
-        label='VRF (RD)',
+        label=_('VRF (RD)'),
     )
     )
 
 
     class Meta:
     class Meta:

+ 2 - 1
netbox/virtualization/forms/bulk_create.py

@@ -1,4 +1,5 @@
 from django import forms
 from django import forms
+from django.utils.translation import gettext as _
 
 
 from utilities.forms import BootstrapMixin, ExpandableNameField, form_from_model
 from utilities.forms import BootstrapMixin, ExpandableNameField, form_from_model
 from virtualization.models import VMInterface, VirtualMachine
 from virtualization.models import VMInterface, VirtualMachine
@@ -14,7 +15,7 @@ class VirtualMachineBulkAddComponentForm(BootstrapMixin, forms.Form):
         widget=forms.MultipleHiddenInput()
         widget=forms.MultipleHiddenInput()
     )
     )
     name = ExpandableNameField(
     name = ExpandableNameField(
-        label='Name'
+        label=_('Name')
     )
     )
 
 
     def clean_tags(self):
     def clean_tags(self):

+ 11 - 10
netbox/virtualization/forms/bulk_edit.py

@@ -1,4 +1,5 @@
 from django import forms
 from django import forms
+from django.utils.translation import gettext as _
 
 
 from dcim.choices import InterfaceModeChoices
 from dcim.choices import InterfaceModeChoices
 from dcim.constants import INTERFACE_MTU_MAX, INTERFACE_MTU_MIN
 from dcim.constants import INTERFACE_MTU_MAX, INTERFACE_MTU_MIN
@@ -90,7 +91,7 @@ class ClusterBulkEditForm(NetBoxModelBulkEditForm):
     )
     )
     comments = CommentField(
     comments = CommentField(
         widget=SmallTextarea,
         widget=SmallTextarea,
-        label='Comments'
+        label=_('Comments')
     )
     )
 
 
     model = Cluster
     model = Cluster
@@ -147,15 +148,15 @@ class VirtualMachineBulkEditForm(NetBoxModelBulkEditForm):
     )
     )
     vcpus = forms.IntegerField(
     vcpus = forms.IntegerField(
         required=False,
         required=False,
-        label='vCPUs'
+        label=_('vCPUs')
     )
     )
     memory = forms.IntegerField(
     memory = forms.IntegerField(
         required=False,
         required=False,
-        label='Memory (MB)'
+        label=_('Memory (MB)')
     )
     )
     disk = forms.IntegerField(
     disk = forms.IntegerField(
         required=False,
         required=False,
-        label='Disk (GB)'
+        label=_('Disk (GB)')
     )
     )
     description = forms.CharField(
     description = forms.CharField(
         max_length=200,
         max_length=200,
@@ -163,7 +164,7 @@ class VirtualMachineBulkEditForm(NetBoxModelBulkEditForm):
     )
     )
     comments = CommentField(
     comments = CommentField(
         widget=SmallTextarea,
         widget=SmallTextarea,
-        label='Comments'
+        label=_('Comments')
     )
     )
 
 
     model = VirtualMachine
     model = VirtualMachine
@@ -199,7 +200,7 @@ class VMInterfaceBulkEditForm(NetBoxModelBulkEditForm):
         required=False,
         required=False,
         min_value=INTERFACE_MTU_MIN,
         min_value=INTERFACE_MTU_MIN,
         max_value=INTERFACE_MTU_MAX,
         max_value=INTERFACE_MTU_MAX,
-        label='MTU'
+        label=_('MTU')
     )
     )
     description = forms.CharField(
     description = forms.CharField(
         max_length=100,
         max_length=100,
@@ -213,7 +214,7 @@ class VMInterfaceBulkEditForm(NetBoxModelBulkEditForm):
     vlan_group = DynamicModelChoiceField(
     vlan_group = DynamicModelChoiceField(
         queryset=VLANGroup.objects.all(),
         queryset=VLANGroup.objects.all(),
         required=False,
         required=False,
-        label='VLAN group'
+        label=_('VLAN group')
     )
     )
     untagged_vlan = DynamicModelChoiceField(
     untagged_vlan = DynamicModelChoiceField(
         queryset=VLAN.objects.all(),
         queryset=VLAN.objects.all(),
@@ -221,7 +222,7 @@ class VMInterfaceBulkEditForm(NetBoxModelBulkEditForm):
         query_params={
         query_params={
             'group_id': '$vlan_group',
             'group_id': '$vlan_group',
         },
         },
-        label='Untagged VLAN'
+        label=_('Untagged VLAN')
     )
     )
     tagged_vlans = DynamicModelMultipleChoiceField(
     tagged_vlans = DynamicModelMultipleChoiceField(
         queryset=VLAN.objects.all(),
         queryset=VLAN.objects.all(),
@@ -229,12 +230,12 @@ class VMInterfaceBulkEditForm(NetBoxModelBulkEditForm):
         query_params={
         query_params={
             'group_id': '$vlan_group',
             'group_id': '$vlan_group',
         },
         },
-        label='Tagged VLANs'
+        label=_('Tagged VLANs')
     )
     )
     vrf = DynamicModelChoiceField(
     vrf = DynamicModelChoiceField(
         queryset=VRF.objects.all(),
         queryset=VRF.objects.all(),
         required=False,
         required=False,
-        label='VRF'
+        label=_('VRF')
     )
     )
 
 
     model = VMInterface
     model = VMInterface

+ 17 - 16
netbox/virtualization/forms/bulk_import.py

@@ -1,5 +1,6 @@
 from dcim.choices import InterfaceModeChoices
 from dcim.choices import InterfaceModeChoices
 from dcim.models import Device, DeviceRole, Platform, Site
 from dcim.models import Device, DeviceRole, Platform, Site
+from django.utils.translation import gettext as _
 from ipam.models import VRF
 from ipam.models import VRF
 from netbox.forms import NetBoxModelCSVForm
 from netbox.forms import NetBoxModelCSVForm
 from tenancy.models import Tenant
 from tenancy.models import Tenant
@@ -36,29 +37,29 @@ class ClusterCSVForm(NetBoxModelCSVForm):
     type = CSVModelChoiceField(
     type = CSVModelChoiceField(
         queryset=ClusterType.objects.all(),
         queryset=ClusterType.objects.all(),
         to_field_name='name',
         to_field_name='name',
-        help_text='Type of cluster'
+        help_text=_('Type of cluster')
     )
     )
     group = CSVModelChoiceField(
     group = CSVModelChoiceField(
         queryset=ClusterGroup.objects.all(),
         queryset=ClusterGroup.objects.all(),
         to_field_name='name',
         to_field_name='name',
         required=False,
         required=False,
-        help_text='Assigned cluster group'
+        help_text=_('Assigned cluster group')
     )
     )
     status = CSVChoiceField(
     status = CSVChoiceField(
         choices=ClusterStatusChoices,
         choices=ClusterStatusChoices,
-        help_text='Operational status'
+        help_text=_('Operational status')
     )
     )
     site = CSVModelChoiceField(
     site = CSVModelChoiceField(
         queryset=Site.objects.all(),
         queryset=Site.objects.all(),
         to_field_name='name',
         to_field_name='name',
         required=False,
         required=False,
-        help_text='Assigned site'
+        help_text=_('Assigned site')
     )
     )
     tenant = CSVModelChoiceField(
     tenant = CSVModelChoiceField(
         queryset=Tenant.objects.all(),
         queryset=Tenant.objects.all(),
         to_field_name='name',
         to_field_name='name',
         required=False,
         required=False,
-        help_text='Assigned tenant'
+        help_text=_('Assigned tenant')
     )
     )
 
 
     class Meta:
     class Meta:
@@ -69,25 +70,25 @@ class ClusterCSVForm(NetBoxModelCSVForm):
 class VirtualMachineCSVForm(NetBoxModelCSVForm):
 class VirtualMachineCSVForm(NetBoxModelCSVForm):
     status = CSVChoiceField(
     status = CSVChoiceField(
         choices=VirtualMachineStatusChoices,
         choices=VirtualMachineStatusChoices,
-        help_text='Operational status'
+        help_text=_('Operational status')
     )
     )
     site = CSVModelChoiceField(
     site = CSVModelChoiceField(
         queryset=Site.objects.all(),
         queryset=Site.objects.all(),
         to_field_name='name',
         to_field_name='name',
         required=False,
         required=False,
-        help_text='Assigned site'
+        help_text=_('Assigned site')
     )
     )
     cluster = CSVModelChoiceField(
     cluster = CSVModelChoiceField(
         queryset=Cluster.objects.all(),
         queryset=Cluster.objects.all(),
         to_field_name='name',
         to_field_name='name',
         required=False,
         required=False,
-        help_text='Assigned cluster'
+        help_text=_('Assigned cluster')
     )
     )
     device = CSVModelChoiceField(
     device = CSVModelChoiceField(
         queryset=Device.objects.all(),
         queryset=Device.objects.all(),
         to_field_name='name',
         to_field_name='name',
         required=False,
         required=False,
-        help_text='Assigned device within cluster'
+        help_text=_('Assigned device within cluster')
     )
     )
     role = CSVModelChoiceField(
     role = CSVModelChoiceField(
         queryset=DeviceRole.objects.filter(
         queryset=DeviceRole.objects.filter(
@@ -95,19 +96,19 @@ class VirtualMachineCSVForm(NetBoxModelCSVForm):
         ),
         ),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
-        help_text='Functional role'
+        help_text=_('Functional role')
     )
     )
     tenant = CSVModelChoiceField(
     tenant = CSVModelChoiceField(
         queryset=Tenant.objects.all(),
         queryset=Tenant.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
-        help_text='Assigned tenant'
+        help_text=_('Assigned tenant')
     )
     )
     platform = CSVModelChoiceField(
     platform = CSVModelChoiceField(
         queryset=Platform.objects.all(),
         queryset=Platform.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
-        help_text='Assigned platform'
+        help_text=_('Assigned platform')
     )
     )
 
 
     class Meta:
     class Meta:
@@ -127,24 +128,24 @@ class VMInterfaceCSVForm(NetBoxModelCSVForm):
         queryset=VMInterface.objects.all(),
         queryset=VMInterface.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
-        help_text='Parent interface'
+        help_text=_('Parent interface')
     )
     )
     bridge = CSVModelChoiceField(
     bridge = CSVModelChoiceField(
         queryset=VMInterface.objects.all(),
         queryset=VMInterface.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
-        help_text='Bridged interface'
+        help_text=_('Bridged interface')
     )
     )
     mode = CSVChoiceField(
     mode = CSVChoiceField(
         choices=InterfaceModeChoices,
         choices=InterfaceModeChoices,
         required=False,
         required=False,
-        help_text='IEEE 802.1Q operational mode (for L2 interfaces)'
+        help_text=_('IEEE 802.1Q operational mode (for L2 interfaces)')
     )
     )
     vrf = CSVModelChoiceField(
     vrf = CSVModelChoiceField(
         queryset=VRF.objects.all(),
         queryset=VRF.objects.all(),
         required=False,
         required=False,
         to_field_name='rd',
         to_field_name='rd',
-        help_text='Assigned VRF'
+        help_text=_('Assigned VRF')
     )
     )
 
 
     class Meta:
     class Meta:

+ 2 - 2
netbox/virtualization/forms/filtersets.py

@@ -160,11 +160,11 @@ class VirtualMachineFilterForm(
     )
     )
     mac_address = forms.CharField(
     mac_address = forms.CharField(
         required=False,
         required=False,
-        label='MAC address'
+        label=_('MAC address')
     )
     )
     has_primary_ip = forms.NullBooleanField(
     has_primary_ip = forms.NullBooleanField(
         required=False,
         required=False,
-        label='Has a primary IP',
+        label=_('Has a primary IP'),
         widget=StaticSelect(
         widget=StaticSelect(
             choices=BOOLEAN_WITH_BLANK_CHOICES
             choices=BOOLEAN_WITH_BLANK_CHOICES
         )
         )

+ 10 - 9
netbox/virtualization/forms/model_forms.py

@@ -1,6 +1,7 @@
 from django import forms
 from django import forms
 from django.contrib.contenttypes.models import ContentType
 from django.contrib.contenttypes.models import ContentType
 from django.core.exceptions import ValidationError
 from django.core.exceptions import ValidationError
+from django.utils.translation import gettext as _
 
 
 from dcim.forms.common import InterfaceCommonForm
 from dcim.forms.common import InterfaceCommonForm
 from dcim.forms.model_forms import INTERFACE_MODE_HELP_TEXT
 from dcim.forms.model_forms import INTERFACE_MODE_HELP_TEXT
@@ -204,7 +205,7 @@ class VirtualMachineForm(TenancyForm, NetBoxModelForm):
             'cluster_id': '$cluster',
             'cluster_id': '$cluster',
             'site_id': '$site',
             'site_id': '$site',
         },
         },
-        help_text="Optionally pin this VM to a specific host device within the cluster"
+        help_text=_("Optionally pin this VM to a specific host device within the cluster")
     )
     )
     role = DynamicModelChoiceField(
     role = DynamicModelChoiceField(
         queryset=DeviceRole.objects.all(),
         queryset=DeviceRole.objects.all(),
@@ -240,8 +241,8 @@ class VirtualMachineForm(TenancyForm, NetBoxModelForm):
             'local_context_data',
             'local_context_data',
         ]
         ]
         help_texts = {
         help_texts = {
-            'local_context_data': "Local config context data overwrites all sources contexts in the final rendered "
-                                  "config context",
+            'local_context_data': _("Local config context data overwrites all sources contexts in the final rendered "
+                                    "config context"),
         }
         }
         widgets = {
         widgets = {
             "status": StaticSelect(),
             "status": StaticSelect(),
@@ -297,7 +298,7 @@ class VMInterfaceForm(InterfaceCommonForm, NetBoxModelForm):
     parent = DynamicModelChoiceField(
     parent = DynamicModelChoiceField(
         queryset=VMInterface.objects.all(),
         queryset=VMInterface.objects.all(),
         required=False,
         required=False,
-        label='Parent interface',
+        label=_('Parent interface'),
         query_params={
         query_params={
             'virtual_machine_id': '$virtual_machine',
             'virtual_machine_id': '$virtual_machine',
         }
         }
@@ -305,7 +306,7 @@ class VMInterfaceForm(InterfaceCommonForm, NetBoxModelForm):
     bridge = DynamicModelChoiceField(
     bridge = DynamicModelChoiceField(
         queryset=VMInterface.objects.all(),
         queryset=VMInterface.objects.all(),
         required=False,
         required=False,
-        label='Bridged interface',
+        label=_('Bridged interface'),
         query_params={
         query_params={
             'virtual_machine_id': '$virtual_machine',
             'virtual_machine_id': '$virtual_machine',
         }
         }
@@ -313,12 +314,12 @@ class VMInterfaceForm(InterfaceCommonForm, NetBoxModelForm):
     vlan_group = DynamicModelChoiceField(
     vlan_group = DynamicModelChoiceField(
         queryset=VLANGroup.objects.all(),
         queryset=VLANGroup.objects.all(),
         required=False,
         required=False,
-        label='VLAN group'
+        label=_('VLAN group')
     )
     )
     untagged_vlan = DynamicModelChoiceField(
     untagged_vlan = DynamicModelChoiceField(
         queryset=VLAN.objects.all(),
         queryset=VLAN.objects.all(),
         required=False,
         required=False,
-        label='Untagged VLAN',
+        label=_('Untagged VLAN'),
         query_params={
         query_params={
             'group_id': '$vlan_group',
             'group_id': '$vlan_group',
             'available_on_virtualmachine': '$virtual_machine',
             'available_on_virtualmachine': '$virtual_machine',
@@ -327,7 +328,7 @@ class VMInterfaceForm(InterfaceCommonForm, NetBoxModelForm):
     tagged_vlans = DynamicModelMultipleChoiceField(
     tagged_vlans = DynamicModelMultipleChoiceField(
         queryset=VLAN.objects.all(),
         queryset=VLAN.objects.all(),
         required=False,
         required=False,
-        label='Tagged VLANs',
+        label=_('Tagged VLANs'),
         query_params={
         query_params={
             'group_id': '$vlan_group',
             'group_id': '$vlan_group',
             'available_on_virtualmachine': '$virtual_machine',
             'available_on_virtualmachine': '$virtual_machine',
@@ -336,7 +337,7 @@ class VMInterfaceForm(InterfaceCommonForm, NetBoxModelForm):
     vrf = DynamicModelChoiceField(
     vrf = DynamicModelChoiceField(
         queryset=VRF.objects.all(),
         queryset=VRF.objects.all(),
         required=False,
         required=False,
-        label='VRF'
+        label=_('VRF')
     )
     )
 
 
     fieldsets = (
     fieldsets = (

+ 6 - 5
netbox/wireless/forms/bulk_edit.py

@@ -1,4 +1,5 @@
 from django import forms
 from django import forms
+from django.utils.translation import gettext as _
 
 
 from dcim.choices import LinkStatusChoices
 from dcim.choices import LinkStatusChoices
 from ipam.models import VLAN
 from ipam.models import VLAN
@@ -45,12 +46,12 @@ class WirelessLANBulkEditForm(NetBoxModelBulkEditForm):
     vlan = DynamicModelChoiceField(
     vlan = DynamicModelChoiceField(
         queryset=VLAN.objects.all(),
         queryset=VLAN.objects.all(),
         required=False,
         required=False,
-        label='VLAN'
+        label=_('VLAN')
     )
     )
     ssid = forms.CharField(
     ssid = forms.CharField(
         max_length=SSID_MAX_LENGTH,
         max_length=SSID_MAX_LENGTH,
         required=False,
         required=False,
-        label='SSID'
+        label=_('SSID')
     )
     )
     tenant = DynamicModelChoiceField(
     tenant = DynamicModelChoiceField(
         queryset=Tenant.objects.all(),
         queryset=Tenant.objects.all(),
@@ -66,7 +67,7 @@ class WirelessLANBulkEditForm(NetBoxModelBulkEditForm):
     )
     )
     auth_psk = forms.CharField(
     auth_psk = forms.CharField(
         required=False,
         required=False,
-        label='Pre-shared key'
+        label=_('Pre-shared key')
     )
     )
     description = forms.CharField(
     description = forms.CharField(
         max_length=200,
         max_length=200,
@@ -91,7 +92,7 @@ class WirelessLinkBulkEditForm(NetBoxModelBulkEditForm):
     ssid = forms.CharField(
     ssid = forms.CharField(
         max_length=SSID_MAX_LENGTH,
         max_length=SSID_MAX_LENGTH,
         required=False,
         required=False,
-        label='SSID'
+        label=_('SSID')
     )
     )
     status = forms.ChoiceField(
     status = forms.ChoiceField(
         choices=add_blank_choice(LinkStatusChoices),
         choices=add_blank_choice(LinkStatusChoices),
@@ -111,7 +112,7 @@ class WirelessLinkBulkEditForm(NetBoxModelBulkEditForm):
     )
     )
     auth_psk = forms.CharField(
     auth_psk = forms.CharField(
         required=False,
         required=False,
-        label='Pre-shared key'
+        label=_('Pre-shared key')
     )
     )
     description = forms.CharField(
     description = forms.CharField(
         max_length=200,
         max_length=200,

+ 11 - 10
netbox/wireless/forms/bulk_import.py

@@ -1,3 +1,4 @@
+from django.utils.translation import gettext as _
 from dcim.choices import LinkStatusChoices
 from dcim.choices import LinkStatusChoices
 from dcim.models import Interface
 from dcim.models import Interface
 from ipam.models import VLAN
 from ipam.models import VLAN
@@ -19,7 +20,7 @@ class WirelessLANGroupCSVForm(NetBoxModelCSVForm):
         queryset=WirelessLANGroup.objects.all(),
         queryset=WirelessLANGroup.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
-        help_text='Parent group'
+        help_text=_('Parent group')
     )
     )
     slug = SlugField()
     slug = SlugField()
 
 
@@ -33,7 +34,7 @@ class WirelessLANCSVForm(NetBoxModelCSVForm):
         queryset=WirelessLANGroup.objects.all(),
         queryset=WirelessLANGroup.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
-        help_text='Assigned group'
+        help_text=_('Assigned group')
     )
     )
     status = CSVChoiceField(
     status = CSVChoiceField(
         choices=WirelessLANStatusChoices,
         choices=WirelessLANStatusChoices,
@@ -43,23 +44,23 @@ class WirelessLANCSVForm(NetBoxModelCSVForm):
         queryset=VLAN.objects.all(),
         queryset=VLAN.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
-        help_text='Bridged VLAN'
+        help_text=_('Bridged VLAN')
     )
     )
     tenant = CSVModelChoiceField(
     tenant = CSVModelChoiceField(
         queryset=Tenant.objects.all(),
         queryset=Tenant.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
-        help_text='Assigned tenant'
+        help_text=_('Assigned tenant')
     )
     )
     auth_type = CSVChoiceField(
     auth_type = CSVChoiceField(
         choices=WirelessAuthTypeChoices,
         choices=WirelessAuthTypeChoices,
         required=False,
         required=False,
-        help_text='Authentication type'
+        help_text=_('Authentication type')
     )
     )
     auth_cipher = CSVChoiceField(
     auth_cipher = CSVChoiceField(
         choices=WirelessAuthCipherChoices,
         choices=WirelessAuthCipherChoices,
         required=False,
         required=False,
-        help_text='Authentication cipher'
+        help_text=_('Authentication cipher')
     )
     )
 
 
     class Meta:
     class Meta:
@@ -73,7 +74,7 @@ class WirelessLANCSVForm(NetBoxModelCSVForm):
 class WirelessLinkCSVForm(NetBoxModelCSVForm):
 class WirelessLinkCSVForm(NetBoxModelCSVForm):
     status = CSVChoiceField(
     status = CSVChoiceField(
         choices=LinkStatusChoices,
         choices=LinkStatusChoices,
-        help_text='Connection status'
+        help_text=_('Connection status')
     )
     )
     interface_a = CSVModelChoiceField(
     interface_a = CSVModelChoiceField(
         queryset=Interface.objects.all()
         queryset=Interface.objects.all()
@@ -85,17 +86,17 @@ class WirelessLinkCSVForm(NetBoxModelCSVForm):
         queryset=Tenant.objects.all(),
         queryset=Tenant.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
-        help_text='Assigned tenant'
+        help_text=_('Assigned tenant')
     )
     )
     auth_type = CSVChoiceField(
     auth_type = CSVChoiceField(
         choices=WirelessAuthTypeChoices,
         choices=WirelessAuthTypeChoices,
         required=False,
         required=False,
-        help_text='Authentication type'
+        help_text=_('Authentication type')
     )
     )
     auth_cipher = CSVChoiceField(
     auth_cipher = CSVChoiceField(
         choices=WirelessAuthCipherChoices,
         choices=WirelessAuthCipherChoices,
         required=False,
         required=False,
-        help_text='Authentication cipher'
+        help_text=_('Authentication cipher')
     )
     )
 
 
     class Meta:
     class Meta:

+ 2 - 2
netbox/wireless/forms/filtersets.py

@@ -35,7 +35,7 @@ class WirelessLANFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
     )
     )
     ssid = forms.CharField(
     ssid = forms.CharField(
         required=False,
         required=False,
-        label='SSID'
+        label=_('SSID')
     )
     )
     group_id = DynamicModelMultipleChoiceField(
     group_id = DynamicModelMultipleChoiceField(
         queryset=WirelessLANGroup.objects.all(),
         queryset=WirelessLANGroup.objects.all(),
@@ -74,7 +74,7 @@ class WirelessLinkFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
     )
     )
     ssid = forms.CharField(
     ssid = forms.CharField(
         required=False,
         required=False,
-        label='SSID'
+        label=_('SSID')
     )
     )
     status = forms.ChoiceField(
     status = forms.ChoiceField(
         required=False,
         required=False,

+ 11 - 10
netbox/wireless/forms/model_forms.py

@@ -1,3 +1,4 @@
+from django.utils.translation import gettext as _
 from dcim.models import Device, Interface, Location, Region, Site, SiteGroup
 from dcim.models import Device, Interface, Location, Region, Site, SiteGroup
 from ipam.models import VLAN, VLANGroup
 from ipam.models import VLAN, VLANGroup
 from netbox.forms import NetBoxModelForm
 from netbox.forms import NetBoxModelForm
@@ -63,7 +64,7 @@ class WirelessLANForm(TenancyForm, NetBoxModelForm):
     vlan_group = DynamicModelChoiceField(
     vlan_group = DynamicModelChoiceField(
         queryset=VLANGroup.objects.all(),
         queryset=VLANGroup.objects.all(),
         required=False,
         required=False,
-        label='VLAN group',
+        label=_('VLAN group'),
         null_option='None',
         null_option='None',
         query_params={
         query_params={
             'site': '$site'
             'site': '$site'
@@ -75,7 +76,7 @@ class WirelessLANForm(TenancyForm, NetBoxModelForm):
     vlan = DynamicModelChoiceField(
     vlan = DynamicModelChoiceField(
         queryset=VLAN.objects.all(),
         queryset=VLAN.objects.all(),
         required=False,
         required=False,
-        label='VLAN',
+        label=_('VLAN'),
         query_params={
         query_params={
             'site_id': '$site',
             'site_id': '$site',
             'group_id': '$vlan_group',
             'group_id': '$vlan_group',
@@ -107,7 +108,7 @@ class WirelessLinkForm(TenancyForm, NetBoxModelForm):
     site_a = DynamicModelChoiceField(
     site_a = DynamicModelChoiceField(
         queryset=Site.objects.all(),
         queryset=Site.objects.all(),
         required=False,
         required=False,
-        label='Site',
+        label=_('Site'),
         initial_params={
         initial_params={
             'devices': '$device_a',
             'devices': '$device_a',
         }
         }
@@ -118,7 +119,7 @@ class WirelessLinkForm(TenancyForm, NetBoxModelForm):
             'site_id': '$site_a',
             'site_id': '$site_a',
         },
         },
         required=False,
         required=False,
-        label='Location',
+        label=_('Location'),
         initial_params={
         initial_params={
             'devices': '$device_a',
             'devices': '$device_a',
         }
         }
@@ -130,7 +131,7 @@ class WirelessLinkForm(TenancyForm, NetBoxModelForm):
             'location_id': '$location_a',
             'location_id': '$location_a',
         },
         },
         required=False,
         required=False,
-        label='Device',
+        label=_('Device'),
         initial_params={
         initial_params={
             'interfaces': '$interface_a'
             'interfaces': '$interface_a'
         }
         }
@@ -142,12 +143,12 @@ class WirelessLinkForm(TenancyForm, NetBoxModelForm):
             'device_id': '$device_a',
             'device_id': '$device_a',
         },
         },
         disabled_indicator='_occupied',
         disabled_indicator='_occupied',
-        label='Interface'
+        label=_('Interface')
     )
     )
     site_b = DynamicModelChoiceField(
     site_b = DynamicModelChoiceField(
         queryset=Site.objects.all(),
         queryset=Site.objects.all(),
         required=False,
         required=False,
-        label='Site',
+        label=_('Site'),
         initial_params={
         initial_params={
             'devices': '$device_b',
             'devices': '$device_b',
         }
         }
@@ -158,7 +159,7 @@ class WirelessLinkForm(TenancyForm, NetBoxModelForm):
             'site_id': '$site_b',
             'site_id': '$site_b',
         },
         },
         required=False,
         required=False,
-        label='Location',
+        label=_('Location'),
         initial_params={
         initial_params={
             'devices': '$device_b',
             'devices': '$device_b',
         }
         }
@@ -170,7 +171,7 @@ class WirelessLinkForm(TenancyForm, NetBoxModelForm):
             'location_id': '$location_b',
             'location_id': '$location_b',
         },
         },
         required=False,
         required=False,
-        label='Device',
+        label=_('Device'),
         initial_params={
         initial_params={
             'interfaces': '$interface_b'
             'interfaces': '$interface_b'
         }
         }
@@ -182,7 +183,7 @@ class WirelessLinkForm(TenancyForm, NetBoxModelForm):
             'device_id': '$device_b',
             'device_id': '$device_b',
         },
         },
         disabled_indicator='_occupied',
         disabled_indicator='_occupied',
-        label='Interface'
+        label=_('Interface')
     )
     )
     comments = CommentField()
     comments = CommentField()
 
 

Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно