Repository: netbox-community/netbox
Pull request: 15094 Add missing gettext to error strings for internationalization
### Fixes: #15094 

Was obviously not able to test each individual error message so another set of eyes to double check the formatting would be appreciated.

Problem statement:
Internationalize raised / thrown Error messages
### Proposed Changes

There are a handful of ValidationError that are not internationalized, they can be found searching for:

Error("

In the project.  The main ones are ValidationError

### Justification

These will show to the user so should be internationalized.

Hints from issue discussion:

Commit message: 15049 add missing gettext to error strings

Patch to review:
diff --git a/netbox/circuits/models/circuits.py b/netbox/circuits/models/circuits.py
index 4dc775364cf..7b65d52adde 100644
--- a/netbox/circuits/models/circuits.py
+++ b/netbox/circuits/models/circuits.py
@@ -234,9 +234,9 @@ def clean(self):
 
         # Must define either site *or* provider network
         if self.site is None and self.provider_network is None:
-            raise ValidationError("A circuit termination must attach to either a site or a provider network.")
+            raise ValidationError(_("A circuit termination must attach to either a site or a provider network."))
         if self.site and self.provider_network:
-            raise ValidationError("A circuit termination cannot attach to both a site and a provider network.")
+            raise ValidationError(_("A circuit termination cannot attach to both a site and a provider network."))
 
     def to_objectchange(self, action):
         objectchange = super().to_objectchange(action)
diff --git a/netbox/core/data_backends.py b/netbox/core/data_backends.py
index 9ff0b4d638d..36e05774ef4 100644
--- a/netbox/core/data_backends.py
+++ b/netbox/core/data_backends.py
@@ -102,7 +102,7 @@ def fetch(self):
         try:
             porcelain.clone(self.url, local_path.name, **clone_args)
         except BaseException as e:
-            raise SyncError(f"Fetching remote data failed ({type(e).__name__}): {e}")
+            raise SyncError(_("Fetching remote data failed ({name}): {e}").format(name=type(e).__name__, e=e))
 
         yield local_path.name
 
diff --git a/netbox/core/forms/model_forms.py b/netbox/core/forms/model_forms.py
index 652728734a9..ae891dd59e1 100644
--- a/netbox/core/forms/model_forms.py
+++ b/netbox/core/forms/model_forms.py
@@ -103,9 +103,9 @@ def clean(self):
         super().clean()
 
         if self.cleaned_data.get('upload_file') and self.cleaned_data.get('data_file'):
-            raise forms.ValidationError("Cannot upload a file and sync from an existing file")
+            raise forms.ValidationError(_("Cannot upload a file and sync from an existing file"))
         if not self.cleaned_data.get('upload_file') and not self.cleaned_data.get('data_file'):
-            raise forms.ValidationError("Must upload a file or select a data file to sync")
+            raise forms.ValidationError(_("Must upload a file or select a data file to sync"))
 
         return self.cleaned_data
 
diff --git a/netbox/core/models/data.py b/netbox/core/models/data.py
index 6597a4b4d60..4ceb22ba993 100644
--- a/netbox/core/models/data.py
+++ b/netbox/core/models/data.py
@@ -177,7 +177,7 @@ def sync(self):
         Create/update/delete child DataFiles as necessary to synchronize with the remote source.
         """
         if self.status == DataSourceStatusChoices.SYNCING:
-            raise SyncError("Cannot initiate sync; syncing already in progress.")
+            raise SyncError(_("Cannot initiate sync; syncing already in progress."))
 
         # Emit the pre_sync signal
         pre_sync.send(sender=self.__class__, instance=self)
@@ -190,7 +190,7 @@ def sync(self):
             backend = self.get_backend()
         except ModuleNotFoundError as e:
             raise SyncError(
-                f"There was an error initializing the backend. A dependency needs to be installed: {e}"
+                _("There was an error initializing the backend. A dependency needs to be installed: ") + str(e)
             )
         with backend.fetch() as local_path:
 
diff --git a/netbox/core/models/jobs.py b/netbox/core/models/jobs.py
index 7cc62a15aa2..63ce4ed14ed 100644
--- a/netbox/core/models/jobs.py
+++ b/netbox/core/models/jobs.py
@@ -181,7 +181,7 @@ def terminate(self, status=JobStatusChoices.STATUS_COMPLETED, error=None):
         """
         valid_statuses = JobStatusChoices.TERMINAL_STATE_CHOICES
         if status not in valid_statuses:
-            raise ValueError(f"Invalid status for job termination. Choices are: {', '.join(valid_statuses)}")
+            raise ValueError(_("Invalid status for job termination. Choices are: {choices}").format(choices=', '.join(valid_statuses)))
 
         # Mark the job as completed
         self.status = status
diff --git a/netbox/dcim/fields.py b/netbox/dcim/fields.py
index db1a28d399a..535756bacc5 100644
--- a/netbox/dcim/fields.py
+++ b/netbox/dcim/fields.py
@@ -1,6 +1,7 @@
 from django.contrib.postgres.fields import ArrayField
 from django.core.exceptions import ValidationError
 from django.db import models
+from django.utils.translation import gettext as _
 from netaddr import AddrFormatError, EUI, eui64_unix_expanded, mac_unix_expanded
 
 from .lookups import PathContains
@@ -41,7 +42,7 @@ def to_python(self, value):
         try:
             return EUI(value, version=48, dialect=mac_unix_expanded_uppercase)
         except AddrFormatError:
-            raise ValidationError(f"Invalid MAC address format: {value}")
+            raise ValidationError(_("Invalid MAC address format: {value}").format(value=value))
 
     def db_type(self, connection):
         return 'macaddr'
@@ -67,7 +68,7 @@ def to_python(self, value):
         try:
             return EUI(value, version=64, dialect=eui64_unix_expanded_uppercase)
         except AddrFormatError:
-            raise ValidationError(f"Invalid WWN format: {value}")
+            raise ValidationError(_("Invalid WWN format: {value}").format(value=value))
 
     def db_type(self, connection):
         return 'macaddr8'
diff --git a/netbox/dcim/forms/bulk_import.py b/netbox/dcim/forms/bulk_import.py
index 732bb87ae3e..62fdb8767d0 100644
--- a/netbox/dcim/forms/bulk_import.py
+++ b/netbox/dcim/forms/bulk_import.py
@@ -870,7 +870,9 @@ def clean_enabled(self):
     def clean_vdcs(self):
         for vdc in self.cleaned_data['vdcs']:
             if vdc.device != self.cleaned_data['device']:
-                raise forms.ValidationError(f"VDC {vdc} is not assigned to device {self.cleaned_data['device']}")
+                raise forms.ValidationError(
+                    _("VDC {vdc} is not assigned to device {device}").format(
+                        vdc=vdc, device=self.cleaned_data['device']))
         return self.cleaned_data['vdcs']
 
 
@@ -1075,7 +1077,8 @@ def clean_component_name(self):
             component = model.objects.get(device=device, name=component_name)
             self.instance.component = component
         except ObjectDoesNotExist:
-            raise forms.ValidationError(f"Component not found: {device} - {component_name}")
+            raise forms.ValidationError(_("Component not found: {device} - {component_name}").format(
+                device=device, component_name=component_name))
 
 
 #
@@ -1193,9 +1196,13 @@ def _clean_side(self, side):
             else:
                 termination_object = model.objects.get(device=device, name=name)
             if termination_object.cable is not None and termination_object.cable != self.instance:
-                raise forms.ValidationError(f"Side {side.upper()}: {device} {termination_object} is already connected")
+                raise forms.ValidationError(
+                    _("Side {side_upper}: {device} {termination_object} is already connected").format(
+                        side_upper=side.upper(), device=device, termination_object=termination_object))
         except ObjectDoesNotExist:
-            raise forms.ValidationError(f"{side.upper()} side termination not found: {device} {name}")
+            raise forms.ValidationError(
+                _("{side_upper} side termination not found: {device} {name}").format(
+                    side_upper=side.upper(), device=device, name=name))
 
         setattr(self.instance, f'{side}_terminations', [termination_object])
         return termination_object
diff --git a/netbox/dcim/models/cables.py b/netbox/dcim/models/cables.py
index d1c80d0be69..562c081393a 100644
--- a/netbox/dcim/models/cables.py
+++ b/netbox/dcim/models/cables.py
@@ -160,24 +160,24 @@ def clean(self):
 
         # Validate length and length_unit
         if self.length is not None and not self.length_unit:
-            raise ValidationError("Must specify a unit when setting a cable length")
+            raise ValidationError(_("Must specify a unit when setting a cable length"))
 
         if self.pk is None and (not self.a_terminations or not self.b_terminations):
-            raise ValidationError("Must define A and B terminations when creating a new cable.")
+            raise ValidationError(_("Must define A and B terminations when creating a new cable."))
 
         if self._terminations_modified:
 
             # Check that all termination objects for either end are of the same type
             for terms in (self.a_terminations, self.b_terminations):
                 if len(terms) > 1 and not all(isinstance(t, type(terms[0])) for t in terms[1:]):
-                    raise ValidationError("Cannot connect different termination types to same end of cable.")
+                    raise ValidationError(_("Cannot connect different termination types to same end of cable."))
 
             # Check that termination types are compatible
             if self.a_terminations and self.b_terminations:
                 a_type = self.a_terminations[0]._meta.model_name
                 b_type = self.b_terminations[0]._meta.model_name
                 if b_type not in COMPATIBLE_TERMINATION_TYPES.get(a_type):
-                    raise ValidationError(f"Incompatible termination types: {a_type} and {b_type}")
+                    raise ValidationError(_("Incompatible termination types: ") + str(a_type) + _(" and ") + str(b_type))
 
                 if a_type == b_type:
                     # can't directly use self.a_terminations here as possible they
@@ -323,17 +323,21 @@ def clean(self):
         ).first()
         if existing_termination is not None:
             raise ValidationError(
-                f"Duplicate termination found for {self.termination_type.app_label}.{self.termination_type.model} "
-                f"{self.termination_id}: cable {existing_termination.cable.pk}"
+                _("Duplicate termination found for {app_label}.{model} {termination_id}: cable {cable_pk}".format(
+                    app_label=self.termination_type.app_label,
+                    model=self.termination_type.model,
+                    termination_id=self.termination_id,
+                    cable_pk=existing_termination.cable.pk))
             )
 
         # Validate interface type (if applicable)
         if self.termination_type.model == 'interface' and self.termination.type in NONCONNECTABLE_IFACE_TYPES:
-            raise ValidationError(f"Cables cannot be terminated to {self.termination.get_type_display()} interfaces")
+            raise ValidationError(_("Cables cannot be terminated to {type_display} interfaces").format(
+                type_display=self.termination.get_type_display()))
 
         # A CircuitTermination attached to a ProviderNetwork cannot have a Cable
         if self.termination_type.model == 'circuittermination' and self.termination.provider_network is not None:
-            raise ValidationError("Circuit terminations attached to a provider network may not be cabled.")
+            raise ValidationError(_("Circuit terminations attached to a provider network may not be cabled."))
 
     def save(self, *args, **kwargs):
 
diff --git a/netbox/dcim/tests/test_api.py b/netbox/dcim/tests/test_api.py
index f36b1103313..d02422c6fab 100644
--- a/netbox/dcim/tests/test_api.py
+++ b/netbox/dcim/tests/test_api.py
@@ -1,6 +1,7 @@
 from django.contrib.auth import get_user_model
 from django.test import override_settings
 from django.urls import reverse
+from django.utils.translation import gettext as _
 from rest_framework import status
 
 from dcim.choices import *
@@ -45,7 +46,7 @@ def test_trace(self):
                 name='Peer Device'
             )
             if self.peer_termination_type is None:
-                raise NotImplementedError("Test case must set peer_termination_type")
+                raise NotImplementedError(_("Test case must set peer_termination_type"))
             peer_obj = self.peer_termination_type.objects.create(
                 device=peer_device,
                 name='Peer Termination'
diff --git a/netbox/extras/api/customfields.py b/netbox/extras/api/customfields.py
index ce3b388f40a..6cd3a245e14 100644
--- a/netbox/extras/api/customfields.py
+++ b/netbox/extras/api/customfields.py
@@ -1,4 +1,5 @@
 from django.contrib.contenttypes.models import ContentType
+from django.utils.translation import gettext as _
 from drf_spectacular.utils import extend_schema_field
 from drf_spectacular.types import OpenApiTypes
 from rest_framework.fields import Field
@@ -88,7 +89,7 @@ def to_internal_value(self, data):
                 if serializer.is_valid():
                     data[cf.name] = [obj['id'] for obj in serializer.data] if many else serializer.data['id']
                 else:
-                    raise ValidationError(f"Unknown related object(s): {data[cf.name]}")
+                    raise ValidationError(_("Unknown related object(s): {name}").format(name=data[cf.name]))
 
         # If updating an existing instance, start with existing custom_field_data
         if self.parent.instance:
diff --git a/netbox/extras/api/serializers.py b/netbox/extras/api/serializers.py
index 714c925488a..8f00e11d9c7 100644
--- a/netbox/extras/api/serializers.py
+++ b/netbox/extras/api/serializers.py
@@ -1,5 +1,6 @@
 from django.contrib.auth import get_user_model
 from django.core.exceptions import ObjectDoesNotExist
+from django.utils.translation import gettext as _
 from drf_spectacular.types import OpenApiTypes
 from drf_spectacular.utils import extend_schema_field
 from rest_framework import serializers
@@ -150,7 +151,7 @@ class Meta:
 
     def validate_type(self, value):
         if self.instance and self.instance.type != value:
-            raise serializers.ValidationError('Changing the type of custom fields is not supported.')
+            raise serializers.ValidationError(_('Changing the type of custom fields is not supported.'))
 
         return value
 
@@ -545,12 +546,12 @@ class ReportInputSerializer(serializers.Serializer):
 
     def validate_schedule_at(self, value):
         if value and not self.context['report'].scheduling_enabled:
-            raise serializers.ValidationError("Scheduling is not enabled for this report.")
+            raise serializers.ValidationError(_("Scheduling is not enabled for this report."))
         return value
 
     def validate_interval(self, value):
         if value and not self.context['report'].scheduling_enabled:
-            raise serializers.ValidationError("Scheduling is not enabled for this report.")
+            raise serializers.ValidationError(_("Scheduling is not enabled for this report."))
         return value
 
 
@@ -595,12 +596,12 @@ class ScriptInputSerializer(serializers.Serializer):
 
     def validate_schedule_at(self, value):
         if value and not self.context['script'].scheduling_enabled:
-            raise serializers.ValidationError("Scheduling is not enabled for this script.")
+            raise serializers.ValidationError(_("Scheduling is not enabled for this script."))
         return value
 
     def validate_interval(self, value):
         if value and not self.context['script'].scheduling_enabled:
-            raise serializers.ValidationError("Scheduling is not enabled for this script.")
+            raise serializers.ValidationError(_("Scheduling is not enabled for this script."))
         return value
 
 
diff --git a/netbox/extras/conditions.py b/netbox/extras/conditions.py
index db054149efc..238f3f8ef94 100644
--- a/netbox/extras/conditions.py
+++ b/netbox/extras/conditions.py
@@ -1,5 +1,6 @@
 import functools
 import re
+from django.utils.translation import gettext as _
 
 __all__ = (
     'Condition',
@@ -50,11 +51,12 @@ class Condition:
 
     def __init__(self, attr, value, op=EQ, negate=False):
         if op not in self.OPERATORS:
-            raise ValueError(f"Unknown operator: {op}. Must be one of: {', '.join(self.OPERATORS)}")
+            raise ValueError(_("Unknown operator: {op}. Must be one of: {operators}").format(
+                op=op, operators=', '.join(self.OPERATORS)))
         if type(value) not in self.TYPES:
-            raise ValueError(f"Unsupported value type: {type(value)}")
+            raise ValueError(_("Unsupported value type: {value}").format(value=type(value)))
         if op not in self.TYPES[type(value)]:
-            raise ValueError(f"Invalid type for {op} operation: {type(value)}")
+            raise ValueError(_("Invalid type for {op} operation: {value}").format(op=op, value=type(value)))
 
         self.attr = attr
         self.value = value
@@ -131,14 +133,16 @@ class ConditionSet:
     """
     def __init__(self, ruleset):
         if type(ruleset) is not dict:
-            raise ValueError(f"Ruleset must be a dictionary, not {type(ruleset)}.")
+            raise ValueError(_("Ruleset must be a dictionary, not {ruleset}.").format(ruleset=type(ruleset)))
         if len(ruleset) != 1:
-            raise ValueError(f"Ruleset must have exactly one logical operator (found {len(ruleset)})")
+            raise ValueError(_("Ruleset must have exactly one logical operator (found {ruleset})").format(
+                ruleset=len(ruleset)))
 
         # Determine the logic type
         logic = list(ruleset.keys())[0]
         if type(logic) is not str or logic.lower() not in (AND, OR):
-            raise ValueError(f"Invalid logic type: {logic} (must be '{AND}' or '{OR}')")
+            raise ValueError(_("Invalid logic type: {logic} (must be '{op_and}' or '{op_or}')").format(
+                logic=logic, op_and=AND, op_or=OR))
         self.logic = logic.lower()
 
         # Compile the set of Conditions
diff --git a/netbox/extras/dashboard/utils.py b/netbox/extras/dashboard/utils.py
index 812b8db1b90..20c508b2a5f 100644
--- a/netbox/extras/dashboard/utils.py
+++ b/netbox/extras/dashboard/utils.py
@@ -2,6 +2,7 @@
 
 from django.conf import settings
 from django.core.exceptions import ObjectDoesNotExist
+from django.utils.translation import gettext as _
 
 from netbox.registry import registry
 from extras.constants import DEFAULT_DASHBOARD
@@ -32,7 +33,7 @@ def get_widget_class(name):
     try:
         return registry['widgets'][name]
     except KeyError:
-        raise ValueError(f"Unregistered widget class: {name}")
+        raise ValueError(_("Unregistered widget class: {name}").format(name=name))
 
 
 def get_dashboard(user):
diff --git a/netbox/extras/dashboard/widgets.py b/netbox/extras/dashboard/widgets.py
index 8cfbb4c6157..92e33f0b104 100644
--- a/netbox/extras/dashboard/widgets.py
+++ b/netbox/extras/dashboard/widgets.py
@@ -112,7 +112,8 @@ def render(self, request):
         Params:
             request: The current request
         """
-        raise NotImplementedError(f"{self.__class__} must define a render() method.")
+        raise NotImplementedError(_("{class_name} must define a render() method.").format(
+            class_name=self.__class__))
 
     @property
     def name(self):
@@ -178,7 +179,7 @@ def clean_filters(self):
                 try:
                     dict(data)
                 except TypeError:
-                    raise forms.ValidationError("Invalid format. Object filters must be passed as a dictionary.")
+                    raise forms.ValidationError(_("Invalid format. Object filters must be passed as a dictionary."))
             return data
 
     def render(self, request):
@@ -232,7 +233,7 @@ def clean_url_params(self):
                 try:
                     urlencode(data)
                 except (TypeError, ValueError):
-                    raise forms.ValidationError("Invalid format. URL parameters must be passed as a dictionary.")
+                    raise forms.ValidationError(_("Invalid format. URL parameters must be passed as a dictionary."))
             return data
 
     def render(self, request):
diff --git a/netbox/extras/events.py b/netbox/extras/events.py
index c50f4488dfe..c4e3dc02419 100644
--- a/netbox/extras/events.py
+++ b/netbox/extras/events.py
@@ -6,6 +6,7 @@
 from django.core.exceptions import ObjectDoesNotExist
 from django.utils import timezone
 from django.utils.module_loading import import_string
+from django.utils.translation import gettext as _
 from django_rq import get_queue
 
 from core.models import Job
@@ -129,7 +130,8 @@ def process_event_rules(event_rules, model_name, event, data, username=None, sna
             )
 
         else:
-            raise ValueError(f"Unknown action type for an event rule: {event_rule.action_type}")
+            raise ValueError(_("Unknown action type for an event rule: {action_type}").format(
+                action_type=event_rule.action_type))
 
 
 def process_event_queue(events):
@@ -175,4 +177,4 @@ def flush_events(queue):
                 func = import_string(name)
                 func(queue)
             except Exception as e:
-                logger.error(f"Cannot import events pipeline {name} error: {e}")
+                logger.error(_("Cannot import events pipeline {name} error: {e}").format(name=name, e=e))
diff --git a/netbox/extras/forms/bulk_import.py b/netbox/extras/forms/bulk_import.py
index f8d3ffb7f30..fe70df58463 100644
--- a/netbox/extras/forms/bulk_import.py
+++ b/netbox/extras/forms/bulk_import.py
@@ -202,7 +202,7 @@ def clean(self):
                 try:
                     webhook = Webhook.objects.get(name=action_object)
                 except Webhook.DoesNotExist:
-                    raise forms.ValidationError(f"Webhook {action_object} not found")
+                    raise forms.ValidationError(_("Webhook {action_object} not found").format(action_object=action_object))
                 self.instance.action_object = webhook
             # Script
             elif action_type == EventRuleActionChoices.SCRIPT:
@@ -211,7 +211,7 @@ def clean(self):
                 try:
                     module, script = get_module_and_script(module_name, script_name)
                 except ObjectDoesNotExist:
-                    raise forms.ValidationError(f"Script {action_object} not found")
+                    raise forms.ValidationError(_("Script {action_object} not found").format(action_object=action_object))
                 self.instance.action_object = module
                 self.instance.action_object_type = ContentType.objects.get_for_model(module, for_concrete_model=False)
                 self.instance.action_parameters = {
diff --git a/netbox/extras/management/commands/reindex.py b/netbox/extras/management/commands/reindex.py
index e072c220a05..e20fad0ceb8 100644
--- a/netbox/extras/management/commands/reindex.py
+++ b/netbox/extras/management/commands/reindex.py
@@ -1,5 +1,6 @@
 from django.contrib.contenttypes.models import ContentType
 from django.core.management.base import BaseCommand, CommandError
+from django.utils.translation import gettext as _
 
 from netbox.registry import registry
 from netbox.search.backends import search_backend
@@ -62,7 +63,7 @@ def handle(self, *model_labels, **kwargs):
         # Determine which models to reindex
         indexers = self._get_indexers(*model_labels)
         if not indexers:
-            raise CommandError("No indexers found!")
+            raise CommandError(_("No indexers found!"))
         self.stdout.write(f'Reindexing {len(indexers)} models.')
 
         # Clear all cached values for the specified models (if not being lazy)
diff --git a/netbox/extras/scripts.py b/netbox/extras/scripts.py
index f28465547be..7d86472c9ec 100644
--- a/netbox/extras/scripts.py
+++ b/netbox/extras/scripts.py
@@ -11,6 +11,7 @@
 from django.core.validators import RegexValidator
 from django.db import transaction
 from django.utils.functional import classproperty
+from django.utils.translation import gettext as _
 
 from core.choices import JobStatusChoices
 from core.models import Job
@@ -356,7 +357,7 @@ def _get_vars(cls):
         return ordered_vars
 
     def run(self, data, commit):
-        raise NotImplementedError("The script must define a run() method.")
+        raise NotImplementedError(_("The script must define a run() method."))
 
     # Form rendering
 
@@ -367,11 +368,11 @@ def get_fieldsets(self):
             fieldsets.extend(self.fieldsets)
         else:
             fields = list(name for name, _ in self._get_vars().items())
-            fieldsets.append(('Script Data', fields))
+            fieldsets.append((_('Script Data'), fields))
 
         # Append the default fieldset if defined in the Meta class
         exec_parameters = ('_schedule_at', '_interval', '_commit') if self.scheduling_enabled else ('_commit',)
-        fieldsets.append(('Script Execution Parameters', exec_parameters))
+        fieldsets.append((_('Script Execution Parameters'), exec_parameters))
 
         return fieldsets
 
diff --git a/netbox/ipam/api/views.py b/netbox/ipam/api/views.py
index 4439e82b4ea..62e2b9eca92 100644
--- a/netbox/ipam/api/views.py
+++ b/netbox/ipam/api/views.py
@@ -3,6 +3,7 @@
 from django.core.exceptions import ObjectDoesNotExist, PermissionDenied
 from django.db import transaction
 from django.shortcuts import get_object_or_404
+from django.utils.translation import gettext as _
 from django_pglocks import advisory_lock
 from drf_spectacular.utils import extend_schema
 from netaddr import IPSet
@@ -379,7 +380,7 @@ def prep_object_data(self, requested_objects, available_objects, parent):
                     'vrf': parent.vrf.pk if parent.vrf else None,
                 })
             else:
-                raise ValidationError("Insufficient space is available to accommodate the requested prefix size(s)")
+                raise ValidationError(_("Insufficient space is available to accommodate the requested prefix size(s)"))
 
         return requested_objects
 
diff --git a/netbox/ipam/fields.py b/netbox/ipam/fields.py
index 2d55deae473..0849a3c79b2 100644
--- a/netbox/ipam/fields.py
+++ b/netbox/ipam/fields.py
@@ -1,6 +1,7 @@
 from django.core.exceptions import ValidationError
 from django.core.validators import MinValueValidator, MaxValueValidator
 from django.db import models
+from django.utils.translation import gettext as _
 from netaddr import AddrFormatError, IPNetwork
 
 from . import lookups, validators
@@ -32,7 +33,7 @@ def to_python(self, value):
             # Always return a netaddr.IPNetwork object. (netaddr.IPAddress does not provide a mask.)
             return IPNetwork(value)
         except AddrFormatError:
-            raise ValidationError("Invalid IP address format: {}".format(value))
+            raise ValidationError(_("Invalid IP address format: {}").format(value))
         except (TypeError, ValueError) as e:
             raise ValidationError(e)
 
diff --git a/netbox/ipam/formfields.py b/netbox/ipam/formfields.py
index e8d171d7f95..f8a4ff514bd 100644
--- a/netbox/ipam/formfields.py
+++ b/netbox/ipam/formfields.py
@@ -1,6 +1,7 @@
 from django import forms
 from django.core.exceptions import ValidationError
 from django.core.validators import validate_ipv4_address, validate_ipv6_address
+from django.utils.translation import gettext_lazy as _
 from netaddr import IPAddress, IPNetwork, AddrFormatError
 
 
@@ -10,7 +11,7 @@
 
 class IPAddressFormField(forms.Field):
     default_error_messages = {
-        'invalid': "Enter a valid IPv4 or IPv6 address (without a mask).",
+        'invalid': _("Enter a valid IPv4 or IPv6 address (without a mask)."),
     }
 
     def to_python(self, value):
@@ -28,19 +29,19 @@ def to_python(self, value):
             try:
                 validate_ipv6_address(value)
             except ValidationError:
-                raise ValidationError("Invalid IPv4/IPv6 address format: {}".format(value))
+                raise ValidationError(_("Invalid IPv4/IPv6 address format: {}").format(value))
 
         try:
             return IPAddress(value)
         except ValueError:
-            raise ValidationError('This field requires an IP address without a mask.')
+            raise ValidationError(_('This field requires an IP address without a mask.'))
         except AddrFormatError:
-            raise ValidationError("Please specify a valid IPv4 or IPv6 address.")
+            raise ValidationError(_("Please specify a valid IPv4 or IPv6 address."))
 
 
 class IPNetworkFormField(forms.Field):
     default_error_messages = {
-        'invalid': "Enter a valid IPv4 or IPv6 address (with CIDR mask).",
+        'invalid': _("Enter a valid IPv4 or IPv6 address (with CIDR mask)."),
     }
 
     def to_python(self, value):
@@ -52,9 +53,9 @@ def to_python(self, value):
 
         # Ensure that a subnet mask has been specified. This prevents IPs from defaulting to a /32 or /128.
         if len(value.split('/')) != 2:
-            raise ValidationError('CIDR mask (e.g. /24) is required.')
+            raise ValidationError(_('CIDR mask (e.g. /24) is required.'))
 
         try:
             return IPNetwork(value)
         except AddrFormatError:
-            raise ValidationError("Please specify a valid IPv4 or IPv6 address.")
+            raise ValidationError(_("Please specify a valid IPv4 or IPv6 address."))
diff --git a/netbox/ipam/forms/model_forms.py b/netbox/ipam/forms/model_forms.py
index 34b7c5958fc..c7e3f92a35d 100644
--- a/netbox/ipam/forms/model_forms.py
+++ b/netbox/ipam/forms/model_forms.py
@@ -751,4 +751,4 @@ def clean(self):
             if not self.cleaned_data['description']:
                 self.cleaned_data['description'] = service_template.description
         elif not all(self.cleaned_data[f] for f in ('name', 'protocol', 'ports')):
-            raise forms.ValidationError("Must specify name, protocol, and port(s) if not using a service template.")
+            raise forms.ValidationError(_("Must specify name, protocol, and port(s) if not using a service template."))
diff --git a/netbox/ipam/validators.py b/netbox/ipam/validators.py
index 50faea8b885..07ff245f21f 100644
--- a/netbox/ipam/validators.py
+++ b/netbox/ipam/validators.py
@@ -1,14 +1,15 @@
 from django.core.exceptions import ValidationError
 from django.core.validators import BaseValidator, RegexValidator
+from django.utils.translation import gettext_lazy as _
 
 
 def prefix_validator(prefix):
     if prefix.ip != prefix.cidr.ip:
-        raise ValidationError("{} is not a valid prefix. Did you mean {}?".format(prefix, prefix.cidr))
+        raise ValidationError(_("{} is not a valid prefix. Did you mean {}?").format(prefix, prefix.cidr))
 
 
 class MaxPrefixLengthValidator(BaseValidator):
-    message = 'The prefix length must be less than or equal to %(limit_value)s.'
+    message = _('The prefix length must be less than or equal to %(limit_value)s.')
     code = 'max_prefix_length'
 
     def compare(self, a, b):
@@ -16,7 +17,7 @@ def compare(self, a, b):
 
 
 class MinPrefixLengthValidator(BaseValidator):
-    message = 'The prefix length must be greater than or equal to %(limit_value)s.'
+    message = _('The prefix length must be greater than or equal to %(limit_value)s.')
     code = 'min_prefix_length'
 
     def compare(self, a, b):
@@ -25,6 +26,6 @@ def compare(self, a, b):
 
 DNSValidator = RegexValidator(
     regex=r'^([0-9A-Za-z_-]+|\*)(\.[0-9A-Za-z_-]+)*\.?$',
-    message='Only alphanumeric characters, asterisks, hyphens, periods, and underscores are allowed in DNS names',
+    message=_('Only alphanumeric characters, asterisks, hyphens, periods, and underscores are allowed in DNS names'),
     code='invalid'
 )
diff --git a/netbox/netbox/api/fields.py b/netbox/netbox/api/fields.py
index d6e43ea7533..31cbacca1d9 100644
--- a/netbox/netbox/api/fields.py
+++ b/netbox/netbox/api/fields.py
@@ -1,4 +1,5 @@
 from django.core.exceptions import ObjectDoesNotExist
+from django.utils.translation import gettext as _
 from drf_spectacular.utils import extend_schema_field
 from drf_spectacular.types import OpenApiTypes
 from netaddr import IPNetwork
@@ -58,11 +59,11 @@ def to_internal_value(self, data):
         if data == '':
             if self.allow_blank:
                 return data
-            raise ValidationError("This field may not be blank.")
+            raise ValidationError(_("This field may not be blank."))
 
         # Provide an explicit error message if the request is trying to write a dict or list
         if isinstance(data, (dict, list)):
-            raise ValidationError('Value must be passed directly (e.g. "foo": 123); do not use a dictionary or list.')
+            raise ValidationError(_('Value must be passed directly (e.g. "foo": 123); do not use a dictionary or list.'))
 
         # Check for string representations of boolean/integer values
         if hasattr(data, 'lower'):
@@ -82,7 +83,7 @@ def to_internal_value(self, data):
         except TypeError:  # Input is an unhashable type
             pass
 
-        raise ValidationError(f"{data} is not a valid choice.")
+        raise ValidationError(_("{data} is not a valid choice.").format(data=data))
 
     @property
     def choices(self):
@@ -95,8 +96,8 @@ class ContentTypeField(RelatedField):
     Represent a ContentType as '<app_label>.<model>'
     """
     default_error_messages = {
-        "does_not_exist": "Invalid content type: {content_type}",
-        "invalid": "Invalid value. Specify a content type as '<app_label>.<model_name>'.",
+        "does_not_exist": _("Invalid content type: {content_type}"),
+        "invalid": _("Invalid value. Specify a content type as '<app_label>.<model_name>'."),
     }
 
     def to_internal_value(self, data):
diff --git a/netbox/netbox/api/serializers/nested.py b/netbox/netbox/api/serializers/nested.py
index 95dcd560c38..bbc171c03dd 100644
--- a/netbox/netbox/api/serializers/nested.py
+++ b/netbox/netbox/api/serializers/nested.py
@@ -1,4 +1,5 @@
 from django.core.exceptions import FieldError, MultipleObjectsReturned, ObjectDoesNotExist
+from django.utils.translation import gettext_lazy as _
 from rest_framework import serializers
 from rest_framework.exceptions import ValidationError
 
@@ -30,9 +31,10 @@ def to_internal_value(self, data):
             try:
                 return queryset.get(**params)
             except ObjectDoesNotExist:
-                raise ValidationError(f"Related object not found using the provided attributes: {params}")
+                raise ValidationError(
+                    _("Related object not found using the provided attributes: {params}").format(params=params))
             except MultipleObjectsReturned:
-                raise ValidationError(f"Multiple objects match the provided attributes: {params}")
+                raise ValidationError(_("Multiple objects match the provided attributes: {params}").format(params=params))
             except FieldError as e:
                 raise ValidationError(e)
 
@@ -42,15 +44,14 @@ def to_internal_value(self, data):
             pk = int(data)
         except (TypeError, ValueError):
             raise ValidationError(
-                f"Related objects must be referenced by numeric ID or by dictionary of attributes. Received an "
-                f"unrecognized value: {data}"
+                _("Related objects must be referenced by numeric ID or by dictionary of attributes. Received an unrecognized value: {data}").format(data=data)
             )
 
         # Look up object by PK
         try:
             return self.Meta.model.objects.get(pk=pk)
         except ObjectDoesNotExist:
-            raise ValidationError(f"Related object not found using the provided numeric ID: {pk}")
+            raise ValidationError(_("Related object not found using the provided numeric ID: {pk}").format(pk=pk))
 
 
 # Declared here for use by PrimaryModelSerializer, but should be imported from extras.api.nested_serializers
diff --git a/netbox/netbox/authentication.py b/netbox/netbox/authentication.py
index 1b0d50ca58b..1e53f672944 100644
--- a/netbox/netbox/authentication.py
+++ b/netbox/netbox/authentication.py
@@ -7,6 +7,7 @@
 from django.contrib.auth.models import Group, AnonymousUser
 from django.core.exceptions import ImproperlyConfigured
 from django.db.models import Q
+from django.utils.translation import gettext_lazy as _
 
 from users.constants import CONSTRAINT_TOKEN_USER
 from users.models import ObjectPermission
@@ -132,7 +133,7 @@ def has_perm(self, user_obj, perm, obj=None):
         # Sanity check: Ensure that the requested permission applies to the specified object
         model = obj._meta.concrete_model
         if model._meta.label_lower != '.'.join((app_label, model_name)):
-            raise ValueError(f"Invalid permission {perm} for model {model}")
+            raise ValueError(_("Invalid permission {perm} for model {model}").format(perm=perm, model=model))
 
         # Compile a QuerySet filter that matches all instances of the specified model
         tokens = {
diff --git a/netbox/netbox/config/__init__.py b/netbox/netbox/config/__init__.py
index c536ceadb80..1c16d6769a9 100644
--- a/netbox/netbox/config/__init__.py
+++ b/netbox/netbox/config/__init__.py
@@ -4,6 +4,7 @@
 from django.conf import settings
 from django.core.cache import cache
 from django.db.utils import DatabaseError
+from django.utils.translation import gettext_lazy as _
 
 from .parameters import PARAMS
 
@@ -63,7 +64,7 @@ def __getattr__(self, item):
         if item in self.defaults:
             return self.defaults[item]
 
-        raise AttributeError(f"Invalid configuration parameter: {item}")
+        raise AttributeError(_("Invalid configuration parameter: {item}").format(item=item))
 
     def _populate_from_cache(self):
         """Populate config data from Redis cache"""
diff --git a/netbox/netbox/forms/mixins.py b/netbox/netbox/forms/mixins.py
index d76eb56c82e..3f262df3a6d 100644
--- a/netbox/netbox/forms/mixins.py
+++ b/netbox/netbox/forms/mixins.py
@@ -35,7 +35,8 @@ def _get_content_type(self):
         Return the ContentType of the form's model.
         """
         if not getattr(self, 'model', None):
-            raise NotImplementedError(f"{self.__class__.__name__} must specify a model class.")
+            raise NotImplementedError(_("{class_name} must specify a model class.").format(
+                class_name=self.__class__.__name__))
         return ContentType.objects.get_for_model(self.model)
 
     def _get_custom_fields(self, content_type):
diff --git a/netbox/netbox/models/features.py b/netbox/netbox/models/features.py
index a13b84bed51..263cca597b7 100644
--- a/netbox/netbox/models/features.py
+++ b/netbox/netbox/models/features.py
@@ -275,16 +275,18 @@ def clean(self):
         # Validate all field values
         for field_name, value in self.custom_field_data.items():
             if field_name not in custom_fields:
-                raise ValidationError(f"Unknown field name '{field_name}' in custom field data.")
+                raise ValidationError(_("Unknown field name '{field_name}' in custom field data.").format(
+                    field_name=field_name))
             try:
                 custom_fields[field_name].validate(value)
             except ValidationError as e:
-                raise ValidationError(f"Invalid value for custom field '{field_name}': {e.message}")
+                raise ValidationError(_("Invalid value for custom field '{field_name}': {message}").format(
+                    field_name=field_name, message=e.message))
 
         # Check for missing required values
         for cf in custom_fields.values():
             if cf.required and cf.name not in self.custom_field_data:
-                raise ValidationError(f"Missing required custom field '{cf.name}'.")
+                raise ValidationError(_("Missing required custom field '{name}'.").format(name=cf.name))
 
 
 class CustomLinksMixin(models.Model):
@@ -547,7 +549,8 @@ def sync_data(self):
         Inheriting models must override this method with specific logic to copy data from the assigned DataFile
         to the local instance. This method should *NOT* call save() on the instance.
         """
-        raise NotImplementedError(f"{self.__class__} must implement a sync_data() method.")
+        raise NotImplementedError(_("{class_name} must implement a sync_data() method.").format(
+            class_name=self.__class__))
 
 
 #
diff --git a/netbox/netbox/plugins/navigation.py b/netbox/netbox/plugins/navigation.py
index 2075c97b62c..aae569412d3 100644
--- a/netbox/netbox/plugins/navigation.py
+++ b/netbox/netbox/plugins/navigation.py
@@ -1,6 +1,7 @@
 from netbox.navigation import MenuGroup
 from utilities.choices import ButtonColorChoices
 from django.utils.text import slugify
+from django.utils.translation import gettext as _
 
 __all__ = (
     'PluginMenu',
@@ -42,11 +43,11 @@ def __init__(self, link, link_text, staff_only=False, permissions=None, buttons=
         self.staff_only = staff_only
         if permissions is not None:
             if type(permissions) not in (list, tuple):
-                raise TypeError("Permissions must be passed as a tuple or list.")
+                raise TypeError(_("Permissions must be passed as a tuple or list."))
             self.permissions = permissions
         if buttons is not None:
             if type(buttons) not in (list, tuple):
-                raise TypeError("Buttons must be passed as a tuple or list.")
+                raise TypeError(_("Buttons must be passed as a tuple or list."))
             self.buttons = buttons
 
 
@@ -64,9 +65,9 @@ def __init__(self, link, title, icon_class, color=None, permissions=None):
         self.icon_class = icon_class
         if permissions is not None:
             if type(permissions) not in (list, tuple):
-                raise TypeError("Permissions must be passed as a tuple or list.")
+                raise TypeError(_("Permissions must be passed as a tuple or list."))
             self.permissions = permissions
         if color is not None:
             if color not in ButtonColorChoices.values():
-                raise ValueError("Button color must be a choice within ButtonColorChoices.")
+                raise ValueError(_("Button color must be a choice within ButtonColorChoices."))
             self.color = color
diff --git a/netbox/netbox/plugins/registration.py b/netbox/netbox/plugins/registration.py
index 3be5384415d..2592b4bfe46 100644
--- a/netbox/netbox/plugins/registration.py
+++ b/netbox/netbox/plugins/registration.py
@@ -1,5 +1,6 @@
 import inspect
 
+from django.utils.translation import gettext_lazy as _
 from netbox.registry import registry
 from .navigation import PluginMenu, PluginMenuButton, PluginMenuItem
 from .templates import PluginTemplateExtension
@@ -20,18 +21,24 @@ def register_template_extensions(class_list):
     # Validation
     for template_extension in class_list:
         if not inspect.isclass(template_extension):
-            raise TypeError(f"PluginTemplateExtension class {template_extension} was passed as an instance!")
+            raise TypeError(
+                _("PluginTemplateExtension class {template_extension} was passed as an instance!").format(
+                    template_extension=template_extension))
         if not issubclass(template_extension, PluginTemplateExtension):
-            raise TypeError(f"{template_extension} is not a subclass of netbox.plugins.PluginTemplateExtension!")
+            raise TypeError(
+                _("{template_extension} is not a subclass of netbox.plugins.PluginTemplateExtension!").format(
+                    template_extension=template_extension))
         if template_extension.model is None:
-            raise TypeError(f"PluginTemplateExtension class {template_extension} does not define a valid model!")
+            raise TypeError(
+                _("PluginTemplateExtension class {template_extension} does not define a valid model!").format(
+                    template_extension=template_extension))
 
         registry['plugins']['template_extensions'][template_extension.model].append(template_extension)
 
 
 def register_menu(menu):
     if not isinstance(menu, PluginMenu):
-        raise TypeError(f"{menu} must be an instance of netbox.plugins.PluginMenu")
+        raise TypeError(_("{menu} must be an instance of netbox.plugins.PluginMenu").format(menu=menu))
     registry['plugins']['menus'].append(menu)
 
 
@@ -42,10 +49,12 @@ def register_menu_items(section_name, class_list):
     # Validation
     for menu_link in class_list:
         if not isinstance(menu_link, PluginMenuItem):
-            raise TypeError(f"{menu_link} must be an instance of netbox.plugins.PluginMenuItem")
+            raise TypeError(_("{menu_link} must be an instance of netbox.plugins.PluginMenuItem").format(
+                menu_link=menu_link))
         for button in menu_link.buttons:
             if not isinstance(button, PluginMenuButton):
-                raise TypeError(f"{button} must be an instance of netbox.plugins.PluginMenuButton")
+                raise TypeError(_("{button} must be an instance of netbox.plugins.PluginMenuButton").format(
+                    button=button))
 
     registry['plugins']['menu_items'][section_name] = class_list
 
diff --git a/netbox/netbox/plugins/templates.py b/netbox/netbox/plugins/templates.py
index e9b9a9dca29..85229dbafdf 100644
--- a/netbox/netbox/plugins/templates.py
+++ b/netbox/netbox/plugins/templates.py
@@ -1,4 +1,5 @@
 from django.template.loader import get_template
+from django.utils.translation import gettext as _
 
 __all__ = (
     'PluginTemplateExtension',
@@ -31,7 +32,7 @@ def render(self, template_name, extra_context=None):
         if extra_context is None:
             extra_context = {}
         elif not isinstance(extra_context, dict):
-            raise TypeError("extra_context must be a dictionary")
+            raise TypeError(_("extra_context must be a dictionary"))
 
         return get_template(template_name).render({**self.context, **extra_context})
 
diff --git a/netbox/netbox/registry.py b/netbox/netbox/registry.py
index ad8c18dcfc3..d783647ec8b 100644
--- a/netbox/netbox/registry.py
+++ b/netbox/netbox/registry.py
@@ -1,4 +1,5 @@
 import collections
+from django.utils.translation import gettext as _
 
 
 class Registry(dict):
@@ -10,13 +11,13 @@ def __getitem__(self, key):
         try:
             return super().__getitem__(key)
         except KeyError:
-            raise KeyError(f"Invalid store: {key}")
+            raise KeyError(_("Invalid store: {key}").format(key=key))
 
     def __setitem__(self, key, value):
-        raise TypeError("Cannot add stores to registry after initialization")
+        raise TypeError(_("Cannot add stores to registry after initialization"))
 
     def __delitem__(self, key):
-        raise TypeError("Cannot delete stores from registry")
+        raise TypeError(_("Cannot delete stores from registry"))
 
 
 # Initialize the global registry
diff --git a/netbox/netbox/views/generic/bulk_views.py b/netbox/netbox/views/generic/bulk_views.py
index 615db618174..17c77d3e61c 100644
--- a/netbox/netbox/views/generic/bulk_views.py
+++ b/netbox/netbox/views/generic/bulk_views.py
@@ -14,6 +14,7 @@
 from django.shortcuts import get_object_or_404, redirect, render
 from django.urls import reverse
 from django.utils.safestring import mark_safe
+from django.utils.translation import gettext as _
 from django_tables2.export import TableExport
 
 from extras.models import ExportTemplate
@@ -390,7 +391,7 @@ def create_and_update_objects(self, form, request):
                 try:
                     instance = prefetched_objects[object_id]
                 except KeyError:
-                    form.add_error('data', f"Row {i}: Object with ID {object_id} does not exist")
+                    form.add_error('data', _("Row {i}: Object with ID {object_id} does not exist").format(i=i, object_id=object_id))
                     raise ValidationError('')
 
                 # Take a snapshot for change logging
diff --git a/netbox/netbox/views/generic/object_views.py b/netbox/netbox/views/generic/object_views.py
index 90b6e9495cd..4df85cac402 100644
--- a/netbox/netbox/views/generic/object_views.py
+++ b/netbox/netbox/views/generic/object_views.py
@@ -11,6 +11,7 @@
 from django.urls import reverse
 from django.utils.html import escape
 from django.utils.safestring import mark_safe
+from django.utils.translation import gettext as _
 
 from extras.signals import clear_events
 from utilities.error_handlers import handle_protectederror
@@ -101,7 +102,8 @@ def get_children(self, request, parent):
             request: The current request
             parent: The parent object
         """
-        raise NotImplementedError(f'{self.__class__.__name__} must implement get_children()')
+        raise NotImplementedError(_('{class_name} must implement get_children()').format(
+            class_name=self.__class__.__name__))
 
     def prep_table_data(self, request, queryset, parent):
         """
diff --git a/netbox/utilities/fields.py b/netbox/utilities/fields.py
index 65e01db8152..7747e0101a5 100644
--- a/netbox/utilities/fields.py
+++ b/netbox/utilities/fields.py
@@ -93,7 +93,7 @@ def get_prefetch_queryset(self, instances, queryset=None):
         if type(queryset) is dict:
             restrict_params = queryset
         elif queryset is not None:
-            raise ValueError("Custom queryset can't be used for this lookup.")
+            raise ValueError(_("Custom queryset can't be used for this lookup."))
 
         # For efficiency, group the instances by content type and then do one
         # query per model
diff --git a/netbox/utilities/forms/bulk_import.py b/netbox/utilities/forms/bulk_import.py
index 57362d3ddd5..223766a65dc 100644
--- a/netbox/utilities/forms/bulk_import.py
+++ b/netbox/utilities/forms/bulk_import.py
@@ -49,7 +49,7 @@ def clean(self):
 
         # Determine whether we're reading from form data or an uploaded file
         if self.cleaned_data['data'] and import_method != ImportMethodChoices.DIRECT:
-            raise forms.ValidationError("Form data must be empty when uploading/selecting a file.")
+            raise forms.ValidationError(_("Form data must be empty when uploading/selecting a file."))
         if import_method == ImportMethodChoices.UPLOAD:
             self.upload_file = 'upload_file'
             file = self.files.get('upload_file')
@@ -78,7 +78,7 @@ def clean(self):
         elif format == ImportFormatChoices.YAML:
             self.cleaned_data['data'] = self._clean_yaml(data)
         else:
-            raise forms.ValidationError(f"Unknown data format: {format}")
+            raise forms.ValidationError(_("Unknown data format: {format}").format(format=format))
 
     def _detect_format(self, data):
         """
diff --git a/netbox/utilities/forms/utils.py b/netbox/utilities/forms/utils.py
index de8e2272770..f3abc673fa2 100644
--- a/netbox/utilities/forms/utils.py
+++ b/netbox/utilities/forms/utils.py
@@ -2,6 +2,7 @@
 
 from django import forms
 from django.forms.models import fields_for_model
+from django.utils.translation import gettext as _
 
 from utilities.choices import unpack_grouped_choices
 from utilities.querysets import RestrictedQuerySet
@@ -38,7 +39,7 @@ def parse_numeric_range(string, base=10):
         try:
             begin, end = int(begin.strip(), base=base), int(end.strip(), base=base) + 1
         except ValueError:
-            raise forms.ValidationError(f'Range "{dash_range}" is invalid.')
+            raise forms.ValidationError(_('Range "{dash_range}" is invalid.').format(dash_range=dash_range))
         values.extend(range(begin, end))
     return sorted(set(values))
 
@@ -61,7 +62,7 @@ def parse_alphanumeric_range(string):
             begin, end = dash_range, dash_range
         if begin.isdigit() and end.isdigit():
             if int(begin) >= int(end):
-                raise forms.ValidationError(f'Range "{dash_range}" is invalid.')
+                raise forms.ValidationError(_('Range "{dash_range}" is invalid.').format(dash_range=dash_range))
 
             for n in list(range(int(begin), int(end) + 1)):
                 values.append(n)
@@ -73,10 +74,10 @@ def parse_alphanumeric_range(string):
             else:
                 # Not a valid range (more than a single character)
                 if not len(begin) == len(end) == 1:
-                    raise forms.ValidationError(f'Range "{dash_range}" is invalid.')
+                    raise forms.ValidationError(_('Range "{dash_range}" is invalid.').format(dash_range=dash_range))
 
                 if ord(begin) >= ord(end):
-                    raise forms.ValidationError(f'Range "{dash_range}" is invalid.')
+                    raise forms.ValidationError(_('Range "{dash_range}" is invalid.').format(dash_range=dash_range))
 
                 for n in list(range(ord(begin), ord(end) + 1)):
                     values.append(chr(n))
@@ -221,18 +222,21 @@ def parse_csv(reader):
         if '.' in header:
             field, to_field = header.split('.', 1)
             if field in headers:
-                raise forms.ValidationError(f'Duplicate or conflicting column header for "{field}"')
+                raise forms.ValidationError(_('Duplicate or conflicting column header for "{field}"').format(
+                    field=field))
             headers[field] = to_field
         else:
             if header in headers:
-                raise forms.ValidationError(f'Duplicate or conflicting column header for "{header}"')
+                raise forms.ValidationError(_('Duplicate or conflicting column header for "{header}"').format(
+                    header=header))
             headers[header] = None
 
     # Parse CSV rows into a list of dictionaries mapped from the column headers.
     for i, row in enumerate(reader, start=1):
         if len(row) != len(headers):
             raise forms.ValidationError(
-                f"Row {i}: Expected {len(headers)} columns but found {len(row)}"
+                _("Row {i}: Expected {len_headers} columns but found {len_row}").format(
+                    len_headers=len(headers), len_row=len(row))
             )
         row = [col.strip() for col in row]
         record = dict(zip(headers.keys(), row))
@@ -253,14 +257,16 @@ def validate_csv(headers, fields, required_fields):
             is_update = True
             continue
         if field not in fields:
-            raise forms.ValidationError(f'Unexpected column header "{field}" found.')
+            raise forms.ValidationError(_('Unexpected column header "{field}" found.').format(field=field))
         if to_field and not hasattr(fields[field], 'to_field_name'):
-            raise forms.ValidationError(f'Column "{field}" is not a related object; cannot use dots')
+            raise forms.ValidationError(_('Column "{field}" is not a related object; cannot use dots').format(
+                field=field))
         if to_field and not hasattr(fields[field].queryset.model, to_field):
-            raise forms.ValidationError(f'Invalid related object attribute for column "{field}": {to_field}')
+            raise forms.ValidationError(_('Invalid related object attribute for column "{field}": {to_field}').format(
+                field=field, to_field=to_field))
 
     # Validate required fields (if not an update)
     if not is_update:
         for f in required_fields:
             if f not in headers:
-                raise forms.ValidationError(f'Required column header "{f}" not found.')
+                raise forms.ValidationError(_('Required column header "{f}" not found.').format(f=f))
diff --git a/netbox/utilities/forms/widgets/apiselect.py b/netbox/utilities/forms/widgets/apiselect.py
index e4b02cb1dac..d49cffdc482 100644
--- a/netbox/utilities/forms/widgets/apiselect.py
+++ b/netbox/utilities/forms/widgets/apiselect.py
@@ -3,6 +3,7 @@
 
 from django import forms
 from django.conf import settings
+from django.utils.translation import gettext_lazy as _
 
 __all__ = (
     'APISelect',
@@ -119,7 +120,9 @@ def _add_dynamic_params(self):
                 update = [{'fieldName': f, 'queryParam': q} for (f, q) in self.dynamic_params.items()]
                 self._serialize_params(key, update)
             except IndexError as error:
-                raise RuntimeError(f"Missing required value for dynamic query param: '{self.dynamic_params}'") from error
+                raise RuntimeError(
+                    _("Missing required value for dynamic query param: '{dynamic_params}'").format(
+                        dynamic_params=self.dynamic_params)) from error
 
     def _add_static_params(self):
         """
@@ -132,7 +135,9 @@ def _add_static_params(self):
                 update = [{'queryParam': k, 'queryValue': v} for (k, v) in self.static_params.items()]
                 self._serialize_params(key, update)
             except IndexError as error:
-                raise RuntimeError(f"Missing required value for static query param: '{self.static_params}'") from error
+                raise RuntimeError(
+                    _("Missing required value for static query param: '{static_params}'").format(
+                        static_params=self.static_params)) from error
 
     def add_query_params(self, query_params):
         """
diff --git a/netbox/utilities/permissions.py b/netbox/utilities/permissions.py
index 813a8f94401..c72a72db7e5 100644
--- a/netbox/utilities/permissions.py
+++ b/netbox/utilities/permissions.py
@@ -1,6 +1,7 @@
 from django.conf import settings
 from django.contrib.contenttypes.models import ContentType
 from django.db.models import Q
+from django.utils.translation import gettext_lazy as _
 
 __all__ = (
     'get_permission_for_model',
@@ -36,7 +37,7 @@ def resolve_permission(name):
         action, model_name = codename.rsplit('_', 1)
     except ValueError:
         raise ValueError(
-            f"Invalid permission name: {name}. Must be in the format <app_label>.<action>_<model>"
+            _("Invalid permission name: {name}. Must be in the format <app_label>.<action>_<model>").format(name=name)
         )
 
     return app_label, action, model_name
@@ -53,7 +54,7 @@ def resolve_permission_ct(name):
     try:
         content_type = ContentType.objects.get(app_label=app_label, model=model_name)
     except ContentType.DoesNotExist:
-        raise ValueError(f"Unknown app_label/model_name for {name}")
+        raise ValueError(_("Unknown app_label/model_name for {name}").format(name=name))
 
     return content_type, action
 
diff --git a/netbox/utilities/request.py b/netbox/utilities/request.py
index a5ca145e9b7..3ae01f326c4 100644
--- a/netbox/utilities/request.py
+++ b/netbox/utilities/request.py
@@ -1,3 +1,4 @@
+from django.utils.translation import gettext_lazy as _
 from netaddr import AddrFormatError, IPAddress
 from urllib.parse import urlparse
 
@@ -29,7 +30,7 @@ def get_client_ip(request, additional_headers=()):
                 return IPAddress(ip)
             except AddrFormatError:
                 # We did our best
-                raise ValueError(f"Invalid IP address set for {header}: {ip}")
+                raise ValueError(_("Invalid IP address set for {header}: {ip}").format(header=header, ip=ip))
 
     # Could not determine the client IP address from request headers
     return None
diff --git a/netbox/utilities/tables.py b/netbox/utilities/tables.py
index 654eb02bea0..84e4251f2f7 100644
--- a/netbox/utilities/tables.py
+++ b/netbox/utilities/tables.py
@@ -1,3 +1,4 @@
+from django.utils.translation import gettext_lazy as _
 from netbox.registry import registry
 
 __all__ = (
@@ -43,5 +44,6 @@ def register_table_column(column, name, *tables):
     for table in tables:
         reg = registry['tables'][table]
         if name in reg:
-            raise ValueError(f"A column named {name} is already defined for table {table.__name__}")
+            raise ValueError(_("A column named {name} is already defined for table {table_name}").format(
+                name=name, table_name=table.__name__))
         reg[name] = column
diff --git a/netbox/utilities/testing/views.py b/netbox/utilities/testing/views.py
index 0a84c5d1b18..daa44b905af 100644
--- a/netbox/utilities/testing/views.py
+++ b/netbox/utilities/testing/views.py
@@ -6,6 +6,7 @@
 from django.db.models import ForeignKey
 from django.test import override_settings
 from django.urls import reverse
+from django.utils.translation import gettext as _
 
 from extras.choices import ObjectChangeActionChoices
 from extras.models import ObjectChange
@@ -621,7 +622,7 @@ def test_bulk_import_objects_with_permission(self):
         @override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
         def test_bulk_update_objects_with_permission(self):
             if not hasattr(self, 'csv_update_data'):
-                raise NotImplementedError("The test must define csv_update_data.")
+                raise NotImplementedError(_("The test must define csv_update_data."))
 
             initial_count = self._get_queryset().count()
             array, csv_data = self._get_update_csv_data()
diff --git a/netbox/utilities/utils.py b/netbox/utilities/utils.py
index 05597b80c9b..22c1f5e3810 100644
--- a/netbox/utilities/utils.py
+++ b/netbox/utilities/utils.py
@@ -15,6 +15,7 @@
 from django.utils.datastructures import MultiValueDict
 from django.utils.html import escape
 from django.utils.timezone import localtime
+from django.utils.translation import gettext as _
 from jinja2.sandbox import SandboxedEnvironment
 from mptt.models import MPTTModel
 
@@ -306,13 +307,14 @@ def to_meters(length, unit):
     """
     try:
         if length < 0:
-            raise ValueError("Length must be a positive number")
+            raise ValueError(_("Length must be a positive number"))
     except TypeError:
-        raise TypeError(f"Invalid value '{length}' for length (must be a number)")
+        raise TypeError(_("Invalid value '{length}' for length (must be a number)").format(length=length))
 
     valid_units = CableLengthUnitChoices.values()
     if unit not in valid_units:
-        raise ValueError(f"Unknown unit {unit}. Must be one of the following: {', '.join(valid_units)}")
+        raise ValueError(_("Unknown unit {unit}. Must be one of the following: {valid_units}".format(
+            unit=unit, valid_units=', '.join(valid_units))))
 
     if unit == CableLengthUnitChoices.UNIT_KILOMETER:
         return length * 1000
@@ -326,7 +328,7 @@ def to_meters(length, unit):
         return length * Decimal(0.3048)
     if unit == CableLengthUnitChoices.UNIT_INCH:
         return length * Decimal(0.0254)
-    raise ValueError(f"Unknown unit {unit}. Must be 'km', 'm', 'cm', 'mi', 'ft', or 'in'.")
+    raise ValueError(_("Unknown unit {unit}. Must be 'km', 'm', 'cm', 'mi', 'ft', or 'in'.").format(unit=unit))
 
 
 def to_grams(weight, unit):
@@ -335,13 +337,14 @@ def to_grams(weight, unit):
     """
     try:
         if weight < 0:
-            raise ValueError("Weight must be a positive number")
+            raise ValueError(_("Weight must be a positive number"))
     except TypeError:
-        raise TypeError(f"Invalid value '{weight}' for weight (must be a number)")
+        raise TypeError(_("Invalid value '{weight}' for weight (must be a number)").format(weight=weight))
 
     valid_units = WeightUnitChoices.values()
     if unit not in valid_units:
-        raise ValueError(f"Unknown unit {unit}. Must be one of the following: {', '.join(valid_units)}")
+        raise ValueError(_("Unknown unit {unit}. Must be one of the following: {valid_units}".format(
+            unit=unit, valid_units=', '.join(valid_units))))
 
     if unit == WeightUnitChoices.UNIT_KILOGRAM:
         return weight * 1000
@@ -351,7 +354,7 @@ def to_grams(weight, unit):
         return weight * Decimal(453.592)
     if unit == WeightUnitChoices.UNIT_OUNCE:
         return weight * Decimal(28.3495)
-    raise ValueError(f"Unknown unit {unit}. Must be 'kg', 'g', 'lb', 'oz'.")
+    raise ValueError(_("Unknown unit {unit}. Must be 'kg', 'g', 'lb', 'oz'.").format(unit=unit))
 
 
 def render_jinja2(template_code, context):
diff --git a/netbox/utilities/validators.py b/netbox/utilities/validators.py
index eaea1c34b5e..0e896e52a02 100644
--- a/netbox/utilities/validators.py
+++ b/netbox/utilities/validators.py
@@ -2,6 +2,7 @@
 
 from django.core.exceptions import ValidationError
 from django.core.validators import BaseValidator, RegexValidator, URLValidator, _lazy_re_compile
+from django.utils.translation import gettext_lazy as _
 
 from netbox.config import get_config
 
@@ -61,4 +62,4 @@ def validate_regex(value):
     try:
         re.compile(value)
     except re.error:
-        raise ValidationError(f"{value} is not a valid regular expression.")
+        raise ValidationError(_("{value} is not a valid regular expression.").format(value=value))
diff --git a/netbox/utilities/views.py b/netbox/utilities/views.py
index 589b71f5072..1ed15a744ed 100644
--- a/netbox/utilities/views.py
+++ b/netbox/utilities/views.py
@@ -2,6 +2,7 @@
 from django.core.exceptions import ImproperlyConfigured
 from django.urls import reverse
 from django.urls.exceptions import NoReverseMatch
+from django.utils.translation import gettext_lazy as _
 
 from netbox.registry import registry
 from .permissions import resolve_permission
@@ -34,7 +35,8 @@ def get_required_permission(self):
         """
         Return the specific permission necessary to perform the requested action on an object.
         """
-        raise NotImplementedError(f"{self.__class__.__name__} must implement get_required_permission()")
+        raise NotImplementedError(_("{self.__class__.__name__} must implement get_required_permission()").format(
+            class_name=self.__class__.__name__))
 
     def has_permission(self):
         user = self.request.user
@@ -68,7 +70,8 @@ def get_required_permission(self):
         """
         Return the specific permission necessary to perform the requested action on an object.
         """
-        raise NotImplementedError(f"{self.__class__.__name__} must implement get_required_permission()")
+        raise NotImplementedError(_("{class_name} must implement get_required_permission()").format(
+            class_name=self.__class__.__name__))
 
     def has_permission(self):
         user = self.request.user
@@ -89,8 +92,8 @@ def dispatch(self, request, *args, **kwargs):
 
         if not hasattr(self, 'queryset'):
             raise ImproperlyConfigured(
-                '{} has no queryset defined. ObjectPermissionRequiredMixin may only be used on views which define '
-                'a base queryset'.format(self.__class__.__name__)
+                _('{} has no queryset defined. ObjectPermissionRequiredMixin may only be used on views which define a base queryset').format(
+                    self.__class__.__name__)
             )
 
         if not self.has_permission():
diff --git a/netbox/wireless/utils.py b/netbox/wireless/utils.py
index d98d6a853a0..c68489bd349 100644
--- a/netbox/wireless/utils.py
+++ b/netbox/wireless/utils.py
@@ -1,4 +1,5 @@
 from decimal import Decimal
+from django.utils.translation import gettext_lazy as _
 
 from .choices import WirelessChannelChoices
 
@@ -12,7 +13,7 @@ def get_channel_attr(channel, attr):
     Return the specified attribute of a given WirelessChannelChoices value.
     """
     if channel not in WirelessChannelChoices.values():
-        raise ValueError(f"Invalid channel value: {channel}")
+        raise ValueError(_("Invalid channel value: {channel}").format(channel=channel))
 
     channel_values = channel.split('-')
     attrs = {
@@ -22,6 +23,6 @@ def get_channel_attr(channel, attr):
         'width': Decimal(channel_values[3]),
     }
     if attr not in attrs:
-        raise ValueError(f"Invalid channel attribute: {attr}")
+        raise ValueError(_("Invalid channel attribute: {attr}").format(attr=attr))
 
     return attrs[attr]


Code review comments:
diff_hunk: @@ -870,7 +870,9 @@ def clean_enabled(self):
     def clean_vdcs(self):
         for vdc in self.cleaned_data['vdcs']:
             if vdc.device != self.cleaned_data['device']:
-                raise forms.ValidationError(f"VDC {vdc} is not assigned to device {self.cleaned_data['device']}")
+                raise forms.ValidationError(
+                    _("VDC {vdc} is not assigned to device {device}").format(
+                        vdc=vdc, device=self.cleaned_data['device']))
path: netbox/dcim/forms/bulk_import.py
text: @user1:
```suggestion
                raise forms.ValidationError(
                    _("VDC {vdc} is not assigned to device {device}").format(
                        vdc=vdc, device=self.cleaned_data['device']
                    )
                )
```

diff_hunk: @@ -160,24 +160,24 @@ def clean(self):
 
         # Validate length and length_unit
         if self.length is not None and not self.length_unit:
-            raise ValidationError("Must specify a unit when setting a cable length")
+            raise ValidationError(_("Must specify a unit when setting a cable length"))
 
         if self.pk is None and (not self.a_terminations or not self.b_terminations):
-            raise ValidationError("Must define A and B terminations when creating a new cable.")
+            raise ValidationError(_("Must define A and B terminations when creating a new cable."))
 
         if self._terminations_modified:
 
             # Check that all termination objects for either end are of the same type
             for terms in (self.a_terminations, self.b_terminations):
                 if len(terms) > 1 and not all(isinstance(t, type(terms[0])) for t in terms[1:]):
-                    raise ValidationError("Cannot connect different termination types to same end of cable.")
+                    raise ValidationError(_("Cannot connect different termination types to same end of cable."))
 
             # Check that termination types are compatible
             if self.a_terminations and self.b_terminations:
                 a_type = self.a_terminations[0]._meta.model_name
                 b_type = self.b_terminations[0]._meta.model_name
                 if b_type not in COMPATIBLE_TERMINATION_TYPES.get(a_type):
-                    raise ValidationError(f"Incompatible termination types: {a_type} and {b_type}")
+                    raise ValidationError(_("Incompatible termination types: ") + str(a_type) + _(" and ") + str(b_type))
path: netbox/dcim/models/cables.py
text: @user1:
```suggestion
                    raise ValidationError(
                        _("Incompatible termination types: {type_a} and {type_b}").format(type_a=a_type, type_b=b_type)
                    )
```

diff_hunk: @@ -323,17 +323,21 @@ def clean(self):
         ).first()
         if existing_termination is not None:
             raise ValidationError(
-                f"Duplicate termination found for {self.termination_type.app_label}.{self.termination_type.model} "
-                f"{self.termination_id}: cable {existing_termination.cable.pk}"
+                _("Duplicate termination found for {app_label}.{model} {termination_id}: cable {cable_pk}".format(
+                    app_label=self.termination_type.app_label,
+                    model=self.termination_type.model,
+                    termination_id=self.termination_id,
+                    cable_pk=existing_termination.cable.pk))
path: netbox/dcim/models/cables.py
text: @user1:
```suggestion
                    cable_pk=existing_termination.cable.pk
                )
            )
```

diff_hunk: @@ -102,7 +102,7 @@ def fetch(self):
         try:
             porcelain.clone(self.url, local_path.name, **clone_args)
         except BaseException as e:
-            raise SyncError(f"Fetching remote data failed ({type(e).__name__}): {e}")
+            raise SyncError(_("Fetching remote data failed ({name}): {e}").format(name=type(e).__name__, e=e))
path: netbox/core/data_backends.py
text: @user1:
```suggestion
            raise SyncError(_("Fetching remote data failed ({name}): {error}").format(name=type(e).__name__, error=e))
```

diff_hunk: @@ -323,17 +323,21 @@ def clean(self):
         ).first()
         if existing_termination is not None:
             raise ValidationError(
-                f"Duplicate termination found for {self.termination_type.app_label}.{self.termination_type.model} "
-                f"{self.termination_id}: cable {existing_termination.cable.pk}"
+                _("Duplicate termination found for {app_label}.{model} {termination_id}: cable {cable_pk}".format(
+                    app_label=self.termination_type.app_label,
+                    model=self.termination_type.model,
+                    termination_id=self.termination_id,
+                    cable_pk=existing_termination.cable.pk))
             )
 
         # Validate interface type (if applicable)
         if self.termination_type.model == 'interface' and self.termination.type in NONCONNECTABLE_IFACE_TYPES:
-            raise ValidationError(f"Cables cannot be terminated to {self.termination.get_type_display()} interfaces")
+            raise ValidationError(_("Cables cannot be terminated to {type_display} interfaces").format(
+                type_display=self.termination.get_type_display()))
path: netbox/dcim/models/cables.py
text: @user1:
```suggestion
            raise ValidationError(
                _("Cables cannot be terminated to {type_display} interfaces").format(
                    type_display=self.termination.get_type_display()
                )
            )
```

diff_hunk: @@ -112,7 +112,8 @@ def render(self, request):
         Params:
             request: The current request
         """
-        raise NotImplementedError(f"{self.__class__} must define a render() method.")
+        raise NotImplementedError(_("{class_name} must define a render() method.").format(
+            class_name=self.__class__))
path: netbox/extras/dashboard/widgets.py
text: @user1:
```suggestion
        raise NotImplementedError(_("{class_name} must define a render() method.").format(
            class_name=self.__class__
        ))
```

diff_hunk: @@ -129,7 +130,8 @@ def process_event_rules(event_rules, model_name, event, data, username=None, sna
             )
 
         else:
-            raise ValueError(f"Unknown action type for an event rule: {event_rule.action_type}")
+            raise ValueError(_("Unknown action type for an event rule: {action_type}").format(
+                action_type=event_rule.action_type))
path: netbox/extras/events.py
text: @user1:
```suggestion
            raise ValueError(_("Unknown action type for an event rule: {action_type}").format(
                action_type=event_rule.action_type
            ))
```

diff_hunk: @@ -50,11 +51,12 @@ class Condition:
 
     def __init__(self, attr, value, op=EQ, negate=False):
         if op not in self.OPERATORS:
-            raise ValueError(f"Unknown operator: {op}. Must be one of: {', '.join(self.OPERATORS)}")
+            raise ValueError(_("Unknown operator: {op}. Must be one of: {operators}").format(
+                op=op, operators=', '.join(self.OPERATORS)))
path: netbox/extras/conditions.py
text: @user1:
```suggestion
            raise ValueError(_("Unknown operator: {op}. Must be one of: {operators}").format(
                op=op, operators=', '.join(self.OPERATORS)
            ))
```

diff_hunk: @@ -202,7 +202,7 @@ def clean(self):
                 try:
                     webhook = Webhook.objects.get(name=action_object)
                 except Webhook.DoesNotExist:
-                    raise forms.ValidationError(f"Webhook {action_object} not found")
+                    raise forms.ValidationError(_("Webhook {action_object} not found").format(action_object=action_object))
path: netbox/extras/forms/bulk_import.py
text: @user1:
```suggestion
                    raise forms.ValidationError(_("Webhook {name} not found").format(name=action_object))
```

diff_hunk: @@ -1193,9 +1196,13 @@ def _clean_side(self, side):
             else:
                 termination_object = model.objects.get(device=device, name=name)
             if termination_object.cable is not None and termination_object.cable != self.instance:
-                raise forms.ValidationError(f"Side {side.upper()}: {device} {termination_object} is already connected")
+                raise forms.ValidationError(
+                    _("Side {side_upper}: {device} {termination_object} is already connected").format(
+                        side_upper=side.upper(), device=device, termination_object=termination_object))
path: netbox/dcim/forms/bulk_import.py
text: @user1:
```suggestion
                raise forms.ValidationError(
                    _("Side {side_upper}: {device} {termination_object} is already connected").format(
                        side_upper=side.upper(), device=device, termination_object=termination_object
                    )
                )
```

diff_hunk: @@ -175,4 +177,4 @@ def flush_events(queue):
                 func = import_string(name)
                 func(queue)
             except Exception as e:
-                logger.error(f"Cannot import events pipeline {name} error: {e}")
+                logger.error(_("Cannot import events pipeline {name} error: {e}").format(name=name, e=e))
path: netbox/extras/events.py
text: @user1:
```suggestion
                logger.error(_("Cannot import events pipeline {name} error: {error}").format(name=name, error=e))
```

diff_hunk: @@ -28,19 +29,19 @@ def to_python(self, value):
             try:
                 validate_ipv6_address(value)
             except ValidationError:
-                raise ValidationError("Invalid IPv4/IPv6 address format: {}".format(value))
+                raise ValidationError(_("Invalid IPv4/IPv6 address format: {}").format(value))
path: netbox/ipam/formfields.py
text: @user1:
```suggestion
                raise ValidationError(_("Invalid IPv4/IPv6 address format: {address}").format(address=value))
```

diff_hunk: @@ -1,22 +1,23 @@
 from django.core.exceptions import ValidationError
 from django.core.validators import BaseValidator, RegexValidator
+from django.utils.translation import gettext_lazy as _
 
 
 def prefix_validator(prefix):
     if prefix.ip != prefix.cidr.ip:
-        raise ValidationError("{} is not a valid prefix. Did you mean {}?".format(prefix, prefix.cidr))
+        raise ValidationError(_("{} is not a valid prefix. Did you mean {}?").format(prefix, prefix.cidr))
path: netbox/ipam/validators.py
text: @user1:
```suggestion
        raise ValidationError(
            _("{prefix} is not a valid prefix. Did you mean {suggested}?").format(
                prefix=prefix, suggested=prefix.cidr
            )
        )
```

diff_hunk: @@ -82,7 +83,7 @@ def to_internal_value(self, data):
         except TypeError:  # Input is an unhashable type
             pass
 
-        raise ValidationError(f"{data} is not a valid choice.")
+        raise ValidationError(_("{data} is not a valid choice.").format(data=data))
path: netbox/netbox/api/fields.py
text: @user1:
```suggestion
        raise ValidationError(_("{value} is not a valid choice.").format(value=data))
```

diff_hunk: @@ -42,15 +44,14 @@ def to_internal_value(self, data):
             pk = int(data)
         except (TypeError, ValueError):
             raise ValidationError(
-                f"Related objects must be referenced by numeric ID or by dictionary of attributes. Received an "
-                f"unrecognized value: {data}"
+                _("Related objects must be referenced by numeric ID or by dictionary of attributes. Received an unrecognized value: {data}").format(data=data)
path: netbox/netbox/api/serializers/nested.py
text: @user1:
```suggestion
                _("Related objects must be referenced by numeric ID or by dictionary of attributes. Received an unrecognized value: {value}").format(value=data)
```

diff_hunk: @@ -42,15 +44,14 @@ def to_internal_value(self, data):
             pk = int(data)
         except (TypeError, ValueError):
             raise ValidationError(
-                f"Related objects must be referenced by numeric ID or by dictionary of attributes. Received an "
-                f"unrecognized value: {data}"
+                _("Related objects must be referenced by numeric ID or by dictionary of attributes. Received an unrecognized value: {data}").format(data=data)
             )
 
         # Look up object by PK
         try:
             return self.Meta.model.objects.get(pk=pk)
         except ObjectDoesNotExist:
-            raise ValidationError(f"Related object not found using the provided numeric ID: {pk}")
+            raise ValidationError(_("Related object not found using the provided numeric ID: {pk}").format(pk=pk))
path: netbox/netbox/api/serializers/nested.py
text: @user1:
```suggestion
            raise ValidationError(_("Related object not found using the provided numeric ID: {id}").format(id=pk))
```

diff_hunk: @@ -211,7 +211,7 @@ def clean(self):
                 try:
                     module, script = get_module_and_script(module_name, script_name)
                 except ObjectDoesNotExist:
-                    raise forms.ValidationError(f"Script {action_object} not found")
+                    raise forms.ValidationError(_("Script {action_object} not found").format(action_object=action_object))
path: netbox/extras/forms/bulk_import.py
text: @user1:
```suggestion
                    raise forms.ValidationError(_("Script {name} not found").format(name=action_object))
```

diff_hunk: @@ -35,7 +35,8 @@ def _get_content_type(self):
         Return the ContentType of the form's model.
         """
         if not getattr(self, 'model', None):
-            raise NotImplementedError(f"{self.__class__.__name__} must specify a model class.")
+            raise NotImplementedError(_("{class_name} must specify a model class.").format(
+                class_name=self.__class__.__name__))
path: netbox/netbox/forms/mixins.py
text: @user1:
```suggestion
            raise NotImplementedError(_("{class_name} must specify a model class.").format(
                class_name=self.__class__.__name__
            ))
```

diff_hunk: @@ -275,16 +275,18 @@ def clean(self):
         # Validate all field values
         for field_name, value in self.custom_field_data.items():
             if field_name not in custom_fields:
-                raise ValidationError(f"Unknown field name '{field_name}' in custom field data.")
+                raise ValidationError(_("Unknown field name '{field_name}' in custom field data.").format(
+                    field_name=field_name))
path: netbox/netbox/models/features.py
text: @user1:
```suggestion
                raise ValidationError(_("Unknown field name '{name}' in custom field data.").format(
                    name=field_name
                ))
```

diff_hunk: @@ -132,7 +133,7 @@ def has_perm(self, user_obj, perm, obj=None):
         # Sanity check: Ensure that the requested permission applies to the specified object
         model = obj._meta.concrete_model
         if model._meta.label_lower != '.'.join((app_label, model_name)):
-            raise ValueError(f"Invalid permission {perm} for model {model}")
+            raise ValueError(_("Invalid permission {perm} for model {model}").format(perm=perm, model=model))
path: netbox/netbox/authentication.py
text: @user1:
```suggestion
            raise ValueError(_("Invalid permission {permission} for model {model}").format(
                permission=perm, model=model
            ))
```

diff_hunk: @@ -547,7 +549,8 @@ def sync_data(self):
         Inheriting models must override this method with specific logic to copy data from the assigned DataFile
         to the local instance. This method should *NOT* call save() on the instance.
         """
-        raise NotImplementedError(f"{self.__class__} must implement a sync_data() method.")
+        raise NotImplementedError(_("{class_name} must implement a sync_data() method.").format(
+            class_name=self.__class__))
path: netbox/netbox/models/features.py
text: @user1:
```suggestion
        raise NotImplementedError(_("{class_name} must implement a sync_data() method.").format(
            class_name=self.__class__
        ))
```

diff_hunk: @@ -20,18 +21,24 @@ def register_template_extensions(class_list):
     # Validation
     for template_extension in class_list:
         if not inspect.isclass(template_extension):
-            raise TypeError(f"PluginTemplateExtension class {template_extension} was passed as an instance!")
+            raise TypeError(
+                _("PluginTemplateExtension class {template_extension} was passed as an instance!").format(
+                    template_extension=template_extension))
path: netbox/netbox/plugins/registration.py
text: @user1:
```suggestion
            raise TypeError(
                _("PluginTemplateExtension class {template_extension} was passed as an instance!").format(
                    template_extension=template_extension
                )
            )
```

diff_hunk: @@ -275,16 +275,18 @@ def clean(self):
         # Validate all field values
         for field_name, value in self.custom_field_data.items():
             if field_name not in custom_fields:
-                raise ValidationError(f"Unknown field name '{field_name}' in custom field data.")
+                raise ValidationError(_("Unknown field name '{field_name}' in custom field data.").format(
+                    field_name=field_name))
             try:
                 custom_fields[field_name].validate(value)
             except ValidationError as e:
-                raise ValidationError(f"Invalid value for custom field '{field_name}': {e.message}")
+                raise ValidationError(_("Invalid value for custom field '{field_name}': {message}").format(
+                    field_name=field_name, message=e.message))
path: netbox/netbox/models/features.py
text: @user1:
```suggestion
                raise ValidationError(_("Invalid value for custom field '{name}': {error}").format(
                    name=field_name, error=e.message
                ))
```

diff_hunk: @@ -20,18 +21,24 @@ def register_template_extensions(class_list):
     # Validation
     for template_extension in class_list:
         if not inspect.isclass(template_extension):
-            raise TypeError(f"PluginTemplateExtension class {template_extension} was passed as an instance!")
+            raise TypeError(
+                _("PluginTemplateExtension class {template_extension} was passed as an instance!").format(
+                    template_extension=template_extension))
         if not issubclass(template_extension, PluginTemplateExtension):
-            raise TypeError(f"{template_extension} is not a subclass of netbox.plugins.PluginTemplateExtension!")
+            raise TypeError(
+                _("{template_extension} is not a subclass of netbox.plugins.PluginTemplateExtension!").format(
+                    template_extension=template_extension))
         if template_extension.model is None:
-            raise TypeError(f"PluginTemplateExtension class {template_extension} does not define a valid model!")
+            raise TypeError(
+                _("PluginTemplateExtension class {template_extension} does not define a valid model!").format(
+                    template_extension=template_extension))
path: netbox/netbox/plugins/registration.py
text: @user1:
```suggestion
            raise TypeError(
                _("PluginTemplateExtension class {template_extension} does not define a valid model!").format(
                    template_extension=template_extension
                )
            )
```

diff_hunk: @@ -42,10 +49,12 @@ def register_menu_items(section_name, class_list):
     # Validation
     for menu_link in class_list:
         if not isinstance(menu_link, PluginMenuItem):
-            raise TypeError(f"{menu_link} must be an instance of netbox.plugins.PluginMenuItem")
+            raise TypeError(_("{menu_link} must be an instance of netbox.plugins.PluginMenuItem").format(
+                menu_link=menu_link))
path: netbox/netbox/plugins/registration.py
text: @user1:
```suggestion
            raise TypeError(_("{item} must be an instance of netbox.plugins.PluginMenuItem").format(
                item=menu_link
            ))
```

diff_hunk: @@ -32,7 +33,7 @@ def to_python(self, value):
             # Always return a netaddr.IPNetwork object. (netaddr.IPAddress does not provide a mask.)
             return IPNetwork(value)
         except AddrFormatError:
-            raise ValidationError("Invalid IP address format: {}".format(value))
+            raise ValidationError(_("Invalid IP address format: {}").format(value))
path: netbox/ipam/fields.py
text: @user1:
```suggestion
            raise ValidationError(_("Invalid IP address format: {address}").format(address=value))
```

diff_hunk: @@ -390,7 +391,7 @@ def create_and_update_objects(self, form, request):
                 try:
                     instance = prefetched_objects[object_id]
                 except KeyError:
-                    form.add_error('data', f"Row {i}: Object with ID {object_id} does not exist")
+                    form.add_error('data', _("Row {i}: Object with ID {object_id} does not exist").format(i=i, object_id=object_id))
path: netbox/netbox/views/generic/bulk_views.py
text: @user1:
```suggestion
                    form.add_error('data', _("Row {i}: Object with ID {id} does not exist").format(i=i, id=object_id))
```

diff_hunk: @@ -101,7 +102,8 @@ def get_children(self, request, parent):
             request: The current request
             parent: The parent object
         """
-        raise NotImplementedError(f'{self.__class__.__name__} must implement get_children()')
+        raise NotImplementedError(_('{class_name} must implement get_children()').format(
+            class_name=self.__class__.__name__))
path: netbox/netbox/views/generic/object_views.py
text: @user1:
```suggestion
        raise NotImplementedError(_('{class_name} must implement get_children()').format(
            class_name=self.__class__.__name__
        ))
```

diff_hunk: @@ -42,10 +49,12 @@ def register_menu_items(section_name, class_list):
     # Validation
     for menu_link in class_list:
         if not isinstance(menu_link, PluginMenuItem):
-            raise TypeError(f"{menu_link} must be an instance of netbox.plugins.PluginMenuItem")
+            raise TypeError(_("{menu_link} must be an instance of netbox.plugins.PluginMenuItem").format(
+                menu_link=menu_link))
         for button in menu_link.buttons:
             if not isinstance(button, PluginMenuButton):
-                raise TypeError(f"{button} must be an instance of netbox.plugins.PluginMenuButton")
+                raise TypeError(_("{button} must be an instance of netbox.plugins.PluginMenuButton").format(
+                    button=button))
path: netbox/netbox/plugins/registration.py
text: @user1:
```suggestion
                raise TypeError(_("{button} must be an instance of netbox.plugins.PluginMenuButton").format(
                    button=button
                ))
```

diff_hunk: @@ -61,7 +62,7 @@ def parse_alphanumeric_range(string):
             begin, end = dash_range, dash_range
         if begin.isdigit() and end.isdigit():
             if int(begin) >= int(end):
-                raise forms.ValidationError(f'Range "{dash_range}" is invalid.')
+                raise forms.ValidationError(_('Range "{dash_range}" is invalid.').format(dash_range=dash_range))
path: netbox/utilities/forms/utils.py
text: @user1:
```suggestion
                raise forms.ValidationError(_('Range "{value}" is invalid.').format(value=dash_range))
```

diff_hunk: @@ -73,10 +74,10 @@ def parse_alphanumeric_range(string):
             else:
                 # Not a valid range (more than a single character)
                 if not len(begin) == len(end) == 1:
-                    raise forms.ValidationError(f'Range "{dash_range}" is invalid.')
+                    raise forms.ValidationError(_('Range "{dash_range}" is invalid.').format(dash_range=dash_range))
path: netbox/utilities/forms/utils.py
text: @user1:
```suggestion
                    raise forms.ValidationError(_('Range "{value}" is invalid.').format(value=dash_range))
```

diff_hunk: @@ -38,7 +39,7 @@ def parse_numeric_range(string, base=10):
         try:
             begin, end = int(begin.strip(), base=base), int(end.strip(), base=base) + 1
         except ValueError:
-            raise forms.ValidationError(f'Range "{dash_range}" is invalid.')
+            raise forms.ValidationError(_('Range "{dash_range}" is invalid.').format(dash_range=dash_range))
path: netbox/utilities/forms/utils.py
text: @user1:
```suggestion
            raise forms.ValidationError(_('Range "{value}" is invalid.').format(value=dash_range))
```

diff_hunk: @@ -221,18 +222,21 @@ def parse_csv(reader):
         if '.' in header:
             field, to_field = header.split('.', 1)
             if field in headers:
-                raise forms.ValidationError(f'Duplicate or conflicting column header for "{field}"')
+                raise forms.ValidationError(_('Duplicate or conflicting column header for "{field}"').format(
+                    field=field))
path: netbox/utilities/forms/utils.py
text: @user1:
```suggestion
                raise forms.ValidationError(_('Duplicate or conflicting column header for "{field}"').format(
                    field=field
                ))
```

diff_hunk: @@ -221,18 +222,21 @@ def parse_csv(reader):
         if '.' in header:
             field, to_field = header.split('.', 1)
             if field in headers:
-                raise forms.ValidationError(f'Duplicate or conflicting column header for "{field}"')
+                raise forms.ValidationError(_('Duplicate or conflicting column header for "{field}"').format(
+                    field=field))
             headers[field] = to_field
         else:
             if header in headers:
-                raise forms.ValidationError(f'Duplicate or conflicting column header for "{header}"')
+                raise forms.ValidationError(_('Duplicate or conflicting column header for "{header}"').format(
+                    header=header))
path: netbox/utilities/forms/utils.py
text: @user1:
```suggestion
                raise forms.ValidationError(_('Duplicate or conflicting column header for "{header}"').format(
                    header=header
                ))
```

diff_hunk: @@ -20,18 +21,24 @@ def register_template_extensions(class_list):
     # Validation
     for template_extension in class_list:
         if not inspect.isclass(template_extension):
-            raise TypeError(f"PluginTemplateExtension class {template_extension} was passed as an instance!")
+            raise TypeError(
+                _("PluginTemplateExtension class {template_extension} was passed as an instance!").format(
+                    template_extension=template_extension))
         if not issubclass(template_extension, PluginTemplateExtension):
-            raise TypeError(f"{template_extension} is not a subclass of netbox.plugins.PluginTemplateExtension!")
+            raise TypeError(
+                _("{template_extension} is not a subclass of netbox.plugins.PluginTemplateExtension!").format(
+                    template_extension=template_extension))
path: netbox/netbox/plugins/registration.py
text: @user1:
```suggestion
            raise TypeError(
                _("{template_extension} is not a subclass of netbox.plugins.PluginTemplateExtension!").format(
                    template_extension=template_extension
                )
            )
```

diff_hunk: @@ -253,14 +257,16 @@ def validate_csv(headers, fields, required_fields):
             is_update = True
             continue
         if field not in fields:
-            raise forms.ValidationError(f'Unexpected column header "{field}" found.')
+            raise forms.ValidationError(_('Unexpected column header "{field}" found.').format(field=field))
         if to_field and not hasattr(fields[field], 'to_field_name'):
-            raise forms.ValidationError(f'Column "{field}" is not a related object; cannot use dots')
+            raise forms.ValidationError(_('Column "{field}" is not a related object; cannot use dots').format(
+                field=field))
path: netbox/utilities/forms/utils.py
text: @user1:
```suggestion
            raise forms.ValidationError(_('Column "{field}" is not a related object; cannot use dots').format(
                field=field
            ))
```

diff_hunk: @@ -253,14 +257,16 @@ def validate_csv(headers, fields, required_fields):
             is_update = True
             continue
         if field not in fields:
-            raise forms.ValidationError(f'Unexpected column header "{field}" found.')
+            raise forms.ValidationError(_('Unexpected column header "{field}" found.').format(field=field))
         if to_field and not hasattr(fields[field], 'to_field_name'):
-            raise forms.ValidationError(f'Column "{field}" is not a related object; cannot use dots')
+            raise forms.ValidationError(_('Column "{field}" is not a related object; cannot use dots').format(
+                field=field))
         if to_field and not hasattr(fields[field].queryset.model, to_field):
-            raise forms.ValidationError(f'Invalid related object attribute for column "{field}": {to_field}')
+            raise forms.ValidationError(_('Invalid related object attribute for column "{field}": {to_field}').format(
+                field=field, to_field=to_field))
path: netbox/utilities/forms/utils.py
text: @user1:
```suggestion
            raise forms.ValidationError(_('Invalid related object attribute for column "{field}": {to_field}').format(
                field=field, to_field=to_field
            ))
```

diff_hunk: @@ -221,18 +222,21 @@ def parse_csv(reader):
         if '.' in header:
             field, to_field = header.split('.', 1)
             if field in headers:
-                raise forms.ValidationError(f'Duplicate or conflicting column header for "{field}"')
+                raise forms.ValidationError(_('Duplicate or conflicting column header for "{field}"').format(
+                    field=field))
             headers[field] = to_field
         else:
             if header in headers:
-                raise forms.ValidationError(f'Duplicate or conflicting column header for "{header}"')
+                raise forms.ValidationError(_('Duplicate or conflicting column header for "{header}"').format(
+                    header=header))
             headers[header] = None
 
     # Parse CSV rows into a list of dictionaries mapped from the column headers.
     for i, row in enumerate(reader, start=1):
         if len(row) != len(headers):
             raise forms.ValidationError(
-                f"Row {i}: Expected {len(headers)} columns but found {len(row)}"
+                _("Row {i}: Expected {len_headers} columns but found {len_row}").format(
+                    len_headers=len(headers), len_row=len(row))
path: netbox/utilities/forms/utils.py
text: @user1:
```suggestion
                _("Row {i}: Expected {count_expected} columns but found {count_found}").format(
                    count_expected=len(headers), len_row=len(count_found)
                )
```

diff_hunk: @@ -119,7 +120,9 @@ def _add_dynamic_params(self):
                 update = [{'fieldName': f, 'queryParam': q} for (f, q) in self.dynamic_params.items()]
                 self._serialize_params(key, update)
             except IndexError as error:
-                raise RuntimeError(f"Missing required value for dynamic query param: '{self.dynamic_params}'") from error
+                raise RuntimeError(
+                    _("Missing required value for dynamic query param: '{dynamic_params}'").format(
+                        dynamic_params=self.dynamic_params)) from error
path: netbox/utilities/forms/widgets/apiselect.py
text: @user1:
```suggestion
                raise RuntimeError(
                    _("Missing required value for dynamic query param: '{dynamic_params}'").format(
                        dynamic_params=self.dynamic_params
                    )
                ) from error
```

diff_hunk: @@ -132,7 +135,9 @@ def _add_static_params(self):
                 update = [{'queryParam': k, 'queryValue': v} for (k, v) in self.static_params.items()]
                 self._serialize_params(key, update)
             except IndexError as error:
-                raise RuntimeError(f"Missing required value for static query param: '{self.static_params}'") from error
+                raise RuntimeError(
+                    _("Missing required value for static query param: '{static_params}'").format(
+                        static_params=self.static_params)) from error
path: netbox/utilities/forms/widgets/apiselect.py
text: @user1:
```suggestion
                raise RuntimeError(
                    _("Missing required value for static query param: '{static_params}'").format(
                        static_params=self.static_params
                    )
                ) from error
```

diff_hunk: @@ -43,5 +44,6 @@ def register_table_column(column, name, *tables):
     for table in tables:
         reg = registry['tables'][table]
         if name in reg:
-            raise ValueError(f"A column named {name} is already defined for table {table.__name__}")
+            raise ValueError(_("A column named {name} is already defined for table {table_name}").format(
+                name=name, table_name=table.__name__))
path: netbox/utilities/tables.py
text: @user1:
```suggestion
            raise ValueError(_("A column named {name} is already defined for table {table_name}").format(
                name=name, table_name=table.__name__
            ))
```

diff_hunk: @@ -306,13 +307,14 @@ def to_meters(length, unit):
     """
     try:
         if length < 0:
-            raise ValueError("Length must be a positive number")
+            raise ValueError(_("Length must be a positive number"))
     except TypeError:
-        raise TypeError(f"Invalid value '{length}' for length (must be a number)")
+        raise TypeError(_("Invalid value '{length}' for length (must be a number)").format(length=length))
 
     valid_units = CableLengthUnitChoices.values()
     if unit not in valid_units:
-        raise ValueError(f"Unknown unit {unit}. Must be one of the following: {', '.join(valid_units)}")
+        raise ValueError(_("Unknown unit {unit}. Must be one of the following: {valid_units}".format(
+            unit=unit, valid_units=', '.join(valid_units))))
path: netbox/utilities/utils.py
text: @user1:
Mismatched parentheses
```suggestion
        raise ValueError(
            _("Unknown unit {unit}. Must be one of the following: {valid_units}").format(
                unit=unit, valid_units=', '.join(valid_units)
            )
        )
```

diff_hunk: @@ -335,13 +337,14 @@ def to_grams(weight, unit):
     """
     try:
         if weight < 0:
-            raise ValueError("Weight must be a positive number")
+            raise ValueError(_("Weight must be a positive number"))
     except TypeError:
-        raise TypeError(f"Invalid value '{weight}' for weight (must be a number)")
+        raise TypeError(_("Invalid value '{weight}' for weight (must be a number)").format(weight=weight))
 
     valid_units = WeightUnitChoices.values()
     if unit not in valid_units:
-        raise ValueError(f"Unknown unit {unit}. Must be one of the following: {', '.join(valid_units)}")
+        raise ValueError(_("Unknown unit {unit}. Must be one of the following: {valid_units}".format(
+            unit=unit, valid_units=', '.join(valid_units))))
path: netbox/utilities/utils.py
text: @user1:
Mismatched parentheses
```suggestion
        raise ValueError(
            _("Unknown unit {unit}. Must be one of the following: {valid_units}").format(
                unit=unit, valid_units=', '.join(valid_units)
            )
        )
```

diff_hunk: @@ -253,14 +257,16 @@ def validate_csv(headers, fields, required_fields):
             is_update = True
             continue
         if field not in fields:
-            raise forms.ValidationError(f'Unexpected column header "{field}" found.')
+            raise forms.ValidationError(_('Unexpected column header "{field}" found.').format(field=field))
         if to_field and not hasattr(fields[field], 'to_field_name'):
-            raise forms.ValidationError(f'Column "{field}" is not a related object; cannot use dots')
+            raise forms.ValidationError(_('Column "{field}" is not a related object; cannot use dots').format(
+                field=field))
         if to_field and not hasattr(fields[field].queryset.model, to_field):
-            raise forms.ValidationError(f'Invalid related object attribute for column "{field}": {to_field}')
+            raise forms.ValidationError(_('Invalid related object attribute for column "{field}": {to_field}').format(
+                field=field, to_field=to_field))
 
     # Validate required fields (if not an update)
     if not is_update:
         for f in required_fields:
             if f not in headers:
-                raise forms.ValidationError(f'Required column header "{f}" not found.')
+                raise forms.ValidationError(_('Required column header "{f}" not found.').format(f=f))
path: netbox/utilities/forms/utils.py
text: @user1:
```suggestion
                raise forms.ValidationError(_('Required column header "{header}" not found.').format(header=f))
```

diff_hunk: @@ -68,7 +70,8 @@ def get_required_permission(self):
         """
         Return the specific permission necessary to perform the requested action on an object.
         """
-        raise NotImplementedError(f"{self.__class__.__name__} must implement get_required_permission()")
+        raise NotImplementedError(_("{class_name} must implement get_required_permission()").format(
+            class_name=self.__class__.__name__))
path: netbox/utilities/views.py
text: @user1:
```suggestion
        raise NotImplementedError(_("{class_name} must implement get_required_permission()").format(
            class_name=self.__class__.__name__
        ))
```

diff_hunk: @@ -89,8 +92,8 @@ def dispatch(self, request, *args, **kwargs):
 
         if not hasattr(self, 'queryset'):
             raise ImproperlyConfigured(
-                '{} has no queryset defined. ObjectPermissionRequiredMixin may only be used on views which define '
-                'a base queryset'.format(self.__class__.__name__)
+                _('{} has no queryset defined. ObjectPermissionRequiredMixin may only be used on views which define a base queryset').format(
+                    self.__class__.__name__)
path: netbox/utilities/views.py
text: @user1:
```suggestion
                _(
                    '{class} has no queryset defined. ObjectPermissionRequiredMixin may only be used on views which '
                    'define a base queryset'
                ).format(class=self.__class__.__name__)
```

diff_hunk: @@ -34,7 +35,8 @@ def get_required_permission(self):
         """
         Return the specific permission necessary to perform the requested action on an object.
         """
-        raise NotImplementedError(f"{self.__class__.__name__} must implement get_required_permission()")
+        raise NotImplementedError(_("{self.__class__.__name__} must implement get_required_permission()").format(
+            class_name=self.__class__.__name__))
path: netbox/utilities/views.py
text: @user1:
```suggestion
        raise NotImplementedError(_("{self.__class__.__name__} must implement get_required_permission()").format(
            class_name=self.__class__.__name__
        ))
```

diff_hunk: @@ -73,10 +74,10 @@ def parse_alphanumeric_range(string):
             else:
                 # Not a valid range (more than a single character)
                 if not len(begin) == len(end) == 1:
-                    raise forms.ValidationError(f'Range "{dash_range}" is invalid.')
+                    raise forms.ValidationError(_('Range "{dash_range}" is invalid.').format(dash_range=dash_range))
 
                 if ord(begin) >= ord(end):
-                    raise forms.ValidationError(f'Range "{dash_range}" is invalid.')
+                    raise forms.ValidationError(_('Range "{dash_range}" is invalid.').format(dash_range=dash_range))
path: netbox/utilities/forms/utils.py
text: @user1:
```suggestion
                    raise forms.ValidationError(_('Range "{value}" is invalid.').format(value=dash_range))
```

diff_hunk: @@ -22,6 +23,6 @@ def get_channel_attr(channel, attr):
         'width': Decimal(channel_values[3]),
     }
     if attr not in attrs:
-        raise ValueError(f"Invalid channel attribute: {attr}")
+        raise ValueError(_("Invalid channel attribute: {attr}").format(attr=attr))
path: netbox/wireless/utils.py
text: @user1:
```suggestion
        raise ValueError(_("Invalid channel attribute: {name}").format(name=attr))
```

diff_hunk: @@ -1193,9 +1196,13 @@ def _clean_side(self, side):
             else:
                 termination_object = model.objects.get(device=device, name=name)
             if termination_object.cable is not None and termination_object.cable != self.instance:
-                raise forms.ValidationError(f"Side {side.upper()}: {device} {termination_object} is already connected")
+                raise forms.ValidationError(
+                    _("Side {side_upper}: {device} {termination_object} is already connected").format(
+                        side_upper=side.upper(), device=device, termination_object=termination_object))
         except ObjectDoesNotExist:
-            raise forms.ValidationError(f"{side.upper()} side termination not found: {device} {name}")
+            raise forms.ValidationError(
+                _("{side_upper} side termination not found: {device} {name}").format(
+                    side_upper=side.upper(), device=device, name=name))
path: netbox/dcim/forms/bulk_import.py
text: @user1:
```suggestion
            raise forms.ValidationError(
                _("{side_upper} side termination not found: {device} {name}").format(
                    side_upper=side.upper(), device=device, name=name
                )
            )
```

diff_hunk: @@ -1075,7 +1077,8 @@ def clean_component_name(self):
             component = model.objects.get(device=device, name=component_name)
             self.instance.component = component
         except ObjectDoesNotExist:
-            raise forms.ValidationError(f"Component not found: {device} - {component_name}")
+            raise forms.ValidationError(_("Component not found: {device} - {component_name}").format(
+                device=device, component_name=component_name))
path: netbox/dcim/forms/bulk_import.py
text: @user1:
```suggestion
            raise forms.ValidationError(
                _("Component not found: {device} - {component_name}").format(
                    device=device, component_name=component_name
                )
            )
```