From 65f79904e7c4c7e60efa51f596ec115f7dd73c39 Mon Sep 17 00:00:00 2001 From: Mirek Simek Date: Tue, 11 Feb 2025 14:44:54 +0100 Subject: [PATCH] i18n: Fix untranslated strings in facet --- .../records/systemfields/entity_reference.py | 7 ++-- invenio_records_resources/resources/errors.py | 35 +++++++++++-------- .../services/custom_fields/errors.py | 20 +++++++---- invenio_records_resources/services/errors.py | 26 +++++++++----- .../services/files/service.py | 5 +-- .../services/records/params/pagination.py | 4 ++- .../services/records/params/querystr.py | 13 ++++--- .../services/records/params/sort.py | 5 ++- .../records/queryparser/transformer.py | 8 ++--- 9 files changed, 76 insertions(+), 47 deletions(-) diff --git a/invenio_records_resources/records/systemfields/entity_reference.py b/invenio_records_resources/records/systemfields/entity_reference.py index 26ebdfec..fa6f33fa 100644 --- a/invenio_records_resources/records/systemfields/entity_reference.py +++ b/invenio_records_resources/records/systemfields/entity_reference.py @@ -8,8 +8,7 @@ """Systemfield for managing referenced entities in request.""" -from functools import partial - +from invenio_i18n import gettext as _ from invenio_records.systemfields import SystemField from ...references.entity_resolvers import EntityProxy @@ -43,7 +42,9 @@ def set_obj(self, instance, obj): # check if the reference is allowed if not self._check_reference(instance, obj): - raise ValueError(f"Invalid reference for '{self.key}': {obj}") + raise ValueError( + _("Invalid reference for '%(key)s': %(obj)s", key=self.key, obj=obj) + ) # set dictionary key and reset the cache self.set_dictkey(instance, obj) diff --git a/invenio_records_resources/resources/errors.py b/invenio_records_resources/resources/errors.py index ee1f3169..ee821f24 100644 --- a/invenio_records_resources/resources/errors.py +++ b/invenio_records_resources/resources/errors.py @@ -9,11 +9,13 @@ # details. """Common Errors handling for Resources.""" + from json import JSONDecodeError import marshmallow as ma from flask import jsonify, make_response, request, url_for from flask_resources import HTTPJSONException, create_error_handler +from invenio_i18n import gettext from invenio_i18n import lazy_gettext as _ from invenio_pidstore.errors import ( PIDAlreadyExists, @@ -116,56 +118,59 @@ class ErrorHandlersMixin: QuerystringValidationError: create_error_handler( HTTPJSONException( code=400, - description="Invalid querystring parameters.", + description=_("Invalid querystring parameters."), ) ), PermissionDeniedError: create_error_handler( HTTPJSONException( code=403, - description="Permission denied.", + description=_("Permission denied."), ) ), RecordPermissionDeniedError: create_error_handler( HTTPJSONException( code=403, - description="Permission denied.", + description=_("Permission denied."), ) ), PIDDeletedError: create_error_handler( HTTPJSONException( code=410, - description="The record has been deleted.", + description=_("The record has been deleted."), ) ), PIDAlreadyExists: create_error_handler( HTTPJSONException( code=400, - description="The persistent identifier is already registered.", + description=_("The persistent identifier is already registered."), ) ), PIDDoesNotExistError: create_error_handler( HTTPJSONException( code=404, - description="The persistent identifier does not exist.", + description=_("The persistent identifier does not exist."), ) ), PIDUnregistered: create_error_handler( HTTPJSONException( code=404, - description="The persistent identifier is not registered.", + description=_("The persistent identifier is not registered."), ) ), PIDRedirectedError: create_pid_redirected_error_handler(), NoResultFound: create_error_handler( HTTPJSONException( code=404, - description="Not found.", + description=_("Not found."), ) ), FacetNotFoundError: create_error_handler( lambda e: HTTPJSONException( code=404, - description=f"Facet {e.vocabulary_id} not found.", + # using gettext here in order to be able to call the format method + description=gettext( + "Facet %(vocabulary_id)s not found.", vocabulary_id=e.vocabulary_id + ), ) ), FileKeyNotFoundError: create_error_handler( @@ -177,19 +182,19 @@ class ErrorHandlersMixin: JSONDecodeError: create_error_handler( HTTPJSONException( code=400, - description="Unable to decode JSON data in request body.", + description=_("Unable to decode JSON data in request body."), ) ), InvalidRelationValue: create_error_handler( HTTPJSONException( code=400, - description="Not a valid value.", + description=_("Not a valid value."), ) ), InvalidCheckValue: create_error_handler( HTTPJSONException( code=400, - description="Not a valid value.", + description=_("Not a valid value."), ) ), search.exceptions.RequestError: create_error_handler( @@ -198,13 +203,15 @@ class ErrorHandlersMixin: FailedFileUploadException: create_error_handler( HTTPJSONException( code=400, - description="The file upload transfer failed, please try again.", + description=_("The file upload transfer failed, please try again."), ) ), FilesCountExceededException: create_error_handler( HTTPJSONException( code=400, - description="Uploading selected files will result in exceeding the max amount per record.", + description=_( + "Uploading selected files will result in exceeding the max amount per record." + ), ) ), } diff --git a/invenio_records_resources/services/custom_fields/errors.py b/invenio_records_resources/services/custom_fields/errors.py index 03d7b780..0476c164 100644 --- a/invenio_records_resources/services/custom_fields/errors.py +++ b/invenio_records_resources/services/custom_fields/errors.py @@ -7,9 +7,10 @@ """Custom Fields for InvenioRDM.""" - from abc import abstractmethod +from invenio_i18n import gettext as _ + class CustomFieldsException(Exception): """Base class for custom fields exceptions.""" @@ -32,9 +33,10 @@ def __init__(self, field_name, given_namespace): @property def description(self): """Exception's description.""" - return ( - f"Namespace {self.given_namespace} is not valid for custom field " - f"{self.field_name}." + return _( + "Namespace %(given_namespace)s is not valid for custom field %(field_name)s.", + given_namespace=self.given_namespace, + field_name=self.field_name, ) @@ -48,7 +50,10 @@ def __init__(self, field_names): @property def description(self): """Exception's description.""" - return f"Custom fields {self.field_names} are not configured." + return _( + "Custom fields %(field_names)s are not configured.", + field_names=self.field_names, + ) class CustomFieldsInvalidArgument(CustomFieldsException): @@ -61,6 +66,7 @@ def __init__(self, arg_name): @property def description(self): """Exception's description.""" - return ( - f"Invalid argument {self.arg_name} passed when initializing custom field." + return _( + "Invalid argument %(arg_name)s passed when initializing custom field.", + arg_name=self.arg_name, ) diff --git a/invenio_records_resources/services/errors.py b/invenio_records_resources/services/errors.py index 705abc40..2d26d2e6 100644 --- a/invenio_records_resources/services/errors.py +++ b/invenio_records_resources/services/errors.py @@ -30,7 +30,10 @@ def __init__(self, action_name=None, record=None, *args, **kwargs): class PermissionDeniedError(PermissionDenied): """Permission denied error.""" - description = "Permission denied." + @property + def description(self): + """Description.""" + return _("Permission denied.") class RevisionIdMismatchError(Exception): @@ -44,9 +47,10 @@ def __init__(self, record_revision_id, expected_revision_id): @property def description(self): """Exception's description.""" - return ( - f"Revision id provided({self.expected_revision_id}) doesn't match " - f"record's one({self.record_revision_id})" + return _( + "Revision id provided(%(expected_revision_id)s) doesn't match record's one(%(record_revision_id)s)", + expected_revision_id=self.expected_revision_id, + record_revision_id=self.record_revision_id, ) @@ -66,7 +70,7 @@ class FacetNotFoundError(Exception): def __init__(self, vocabulary_id): """Initialise error.""" self.vocabulary_id = vocabulary_id - super().__init__(_("Facet {vocab} not found.").format(vocab=vocabulary_id)) + super().__init__(_("Facet %(vocab)s not found.", vocab=vocabulary_id)) class FileKeyNotFoundError(Exception): @@ -75,8 +79,10 @@ class FileKeyNotFoundError(Exception): def __init__(self, recid, file_key): """Constructor.""" super().__init__( - _("Record '{recid}' has no file '{file_key}'.").format( - recid=recid, file_key=file_key + _( + "Record '%(recid)s' has no file '%(file_key)s'.", + recid=recid, + file_key=file_key, ) ) self.recid = recid @@ -89,8 +95,10 @@ class FailedFileUploadException(Exception): def __init__(self, recid, file, file_key): """Constructor.""" super().__init__( - _("Record '{recid}' failed to upload file '{file_key}'.").format( - recid=recid, file_key=file_key + _( + "Record '%(recid)s' failed to upload file '%(file_key)s'.", + recid=recid, + file_key=file_key, ) ) self.recid = recid diff --git a/invenio_records_resources/services/files/service.py b/invenio_records_resources/services/files/service.py index 373f314f..eb58c307 100644 --- a/invenio_records_resources/services/files/service.py +++ b/invenio_records_resources/services/files/service.py @@ -10,6 +10,7 @@ """File Service API.""" from flask import current_app +from invenio_i18n import gettext as _ from ..base import LinksTemplate, Service from ..errors import FailedFileUploadException, FileKeyNotFoundError @@ -257,10 +258,10 @@ def set_file_content( except FailedFileUploadException as e: file = e.file - current_app.logger.exception(f"File upload transfer failed.") + current_app.logger.exception("File upload transfer failed.") # we gracefully fail so that uow can commit the cleanup operation in # FileContentComponent - errors = "File upload transfer failed." + errors = _("File upload transfer failed.") return self.file_result_item( self, diff --git a/invenio_records_resources/services/records/params/pagination.py b/invenio_records_resources/services/records/params/pagination.py index 8f414d01..675d2f8f 100644 --- a/invenio_records_resources/services/records/params/pagination.py +++ b/invenio_records_resources/services/records/params/pagination.py @@ -10,6 +10,8 @@ from copy import deepcopy +from invenio_i18n import gettext as _ + from ....pagination import Pagination from ...errors import QuerystringValidationError from .base import ParamInterpreter @@ -34,6 +36,6 @@ def apply(self, identity, search, params): ) if not p.valid(): - raise QuerystringValidationError("Invalid pagination parameters.") + raise QuerystringValidationError(_("Invalid pagination parameters.")) return search[p.from_idx : p.to_idx] diff --git a/invenio_records_resources/services/records/params/querystr.py b/invenio_records_resources/services/records/params/querystr.py index 85c67153..abcbf235 100644 --- a/invenio_records_resources/services/records/params/querystr.py +++ b/invenio_records_resources/services/records/params/querystr.py @@ -8,9 +8,10 @@ """Query parameter interpreter API.""" -from ...errors import QuerystringValidationError - # Here for backward compatibility +from invenio_i18n import gettext as _ + +from ...errors import QuerystringValidationError from ..queryparser import QueryParser, SuggestQueryParser # noqa from .base import ParamInterpreter @@ -25,8 +26,10 @@ def apply(self, identity, search, params): if q_str and suggest_str: raise QuerystringValidationError( - "You cannot specify both 'q' and 'suggest' parameters at the " - "same time." + _( + "You cannot specify both 'q' and 'suggest' parameters at the " + "same time." + ) ) query_str = q_str @@ -35,7 +38,7 @@ def apply(self, identity, search, params): query_str = suggest_str parser_cls = self.config.suggest_parser_cls if parser_cls is None: - raise QuerystringValidationError("Invalid 'suggest' parameter.") + raise QuerystringValidationError(_("Invalid 'suggest' parameter.")) if query_str: query = parser_cls(identity).parse(query_str) diff --git a/invenio_records_resources/services/records/params/sort.py b/invenio_records_resources/services/records/params/sort.py index 2c038fe0..1e522f66 100644 --- a/invenio_records_resources/services/records/params/sort.py +++ b/invenio_records_resources/services/records/params/sort.py @@ -10,6 +10,7 @@ from copy import deepcopy +from invenio_i18n import gettext as _ from marshmallow import ValidationError from .base import ParamInterpreter @@ -57,5 +58,7 @@ def _compute_sort_fields(self, params): sort = options.get(params["sort"]) if sort is None: - raise ValidationError(f"Invalid sort option '{params['sort']}'.") + raise ValidationError( + _("Invalid sort option '%(sort_option)s'.", sort_option=params["sort"]) + ) return sort["fields"] diff --git a/invenio_records_resources/services/records/queryparser/transformer.py b/invenio_records_resources/services/records/queryparser/transformer.py index a37c326f..7abee18c 100644 --- a/invenio_records_resources/services/records/queryparser/transformer.py +++ b/invenio_records_resources/services/records/queryparser/transformer.py @@ -125,14 +125,12 @@ def visit_search_field(self, node, context): # allows, we don't map any query, we allow it "as is" if not allows: raise QuerystringValidationError( - _("Invalid search field: {field_name}.").format( - field_name=node.name - ) + _("Invalid search field: %(field_name)s.", field_name=node.name) ) # If a allow list exists, the term must be allowed to be queried. - if self._allow_list and not term_name in self._allow_list: + if self._allow_list and term_name not in self._allow_list: raise QuerystringValidationError( - _("Invalid search field: {field_name}.").format(field_name=node.name) + _("Invalid search field: %(field_name)s.", field_name=node.name) ) if field_value_mapper: