mirror of
https://github.com/zulip/zulip.git
synced 2025-11-04 05:53:43 +00:00
i18n: Deal with lazy strings more carefully.
This uses a more specific type `_StrPromise` to replace `Promise` providing typing information for lazy translation strings. In places where the callee evaluates the `_StrPromise` object in all cases we simply force the evaluation with `str()`. This includes `JsonableError` that ends up handled by the error handler middleware, and `internal_send_stream_message` that depends on `check_stream_topic`, requiring the `topic` to be evaluated anyway. In other siuations, the callee is expected to be able to handle `StrPromise` explicitly. Signed-off-by: Zixuan James Li <p359101898@gmail.com>
This commit is contained in:
committed by
Tim Abbott
parent
ab9279aabe
commit
bb9e80d7a2
@@ -1,5 +1,5 @@
|
||||
import datetime
|
||||
from typing import Any, Dict, Iterable, List, Optional, Set, TypedDict
|
||||
from typing import TYPE_CHECKING, Any, Dict, Iterable, List, Optional, Set, TypedDict
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import transaction
|
||||
@@ -66,6 +66,9 @@ from zerver.models import (
|
||||
)
|
||||
from zerver.tornado.django_api import send_event
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from django.utils.functional import _StrPromise as StrPromise
|
||||
|
||||
|
||||
def subscriber_info(user_id: int) -> Dict[str, Any]:
|
||||
return {"id": user_id, "flags": ["read"]}
|
||||
@@ -198,10 +201,10 @@ def send_message_moved_breadcrumbs(
|
||||
user_profile: UserProfile,
|
||||
old_stream: Stream,
|
||||
old_topic: str,
|
||||
old_thread_notification_string: Optional[str],
|
||||
old_thread_notification_string: Optional["StrPromise"],
|
||||
new_stream: Stream,
|
||||
new_topic: Optional[str],
|
||||
new_thread_notification_string: Optional[str],
|
||||
new_thread_notification_string: Optional["StrPromise"],
|
||||
changed_messages_count: int,
|
||||
) -> None:
|
||||
# Since moving content between streams is highly disruptive,
|
||||
|
||||
@@ -844,7 +844,7 @@ def send_change_stream_permission_notification(
|
||||
new_policy=new_policy_name,
|
||||
)
|
||||
internal_send_stream_message(
|
||||
sender, stream, Realm.STREAM_EVENTS_NOTIFICATION_TOPIC, notification_string
|
||||
sender, stream, str(Realm.STREAM_EVENTS_NOTIFICATION_TOPIC), notification_string
|
||||
)
|
||||
|
||||
|
||||
@@ -1030,7 +1030,7 @@ def send_change_stream_post_policy_notification(
|
||||
new_policy=Stream.POST_POLICIES[new_post_policy],
|
||||
)
|
||||
internal_send_stream_message(
|
||||
sender, stream, Realm.STREAM_EVENTS_NOTIFICATION_TOPIC, notification_string
|
||||
sender, stream, str(Realm.STREAM_EVENTS_NOTIFICATION_TOPIC), notification_string
|
||||
)
|
||||
|
||||
|
||||
@@ -1153,7 +1153,7 @@ def do_rename_stream(stream: Stream, new_name: str, user_profile: UserProfile) -
|
||||
internal_send_stream_message(
|
||||
sender,
|
||||
stream,
|
||||
Realm.STREAM_EVENTS_NOTIFICATION_TOPIC,
|
||||
str(Realm.STREAM_EVENTS_NOTIFICATION_TOPIC),
|
||||
_("{user_name} renamed stream {old_stream_name} to {new_stream_name}.").format(
|
||||
user_name=silent_mention_syntax_for_user(user_profile),
|
||||
old_stream_name=f"**{old_name}**",
|
||||
@@ -1190,7 +1190,7 @@ def send_change_stream_description_notification(
|
||||
)
|
||||
|
||||
internal_send_stream_message(
|
||||
sender, stream, Realm.STREAM_EVENTS_NOTIFICATION_TOPIC, notification_string
|
||||
sender, stream, str(Realm.STREAM_EVENTS_NOTIFICATION_TOPIC), notification_string
|
||||
)
|
||||
|
||||
|
||||
@@ -1276,7 +1276,7 @@ def send_change_stream_message_retention_days_notification(
|
||||
summary_line=summary_line,
|
||||
)
|
||||
internal_send_stream_message(
|
||||
sender, stream, Realm.STREAM_EVENTS_NOTIFICATION_TOPIC, notification_string
|
||||
sender, stream, str(Realm.STREAM_EVENTS_NOTIFICATION_TOPIC), notification_string
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -140,7 +140,7 @@ class RegistrationForm(forms.Form):
|
||||
if self.fields["password"].required and not check_password_strength(password):
|
||||
# The frontend code tries to stop the user from submitting the form with a weak password,
|
||||
# but if the user bypasses that protection, this error code path will run.
|
||||
raise ValidationError(PASSWORD_TOO_WEAK_ERROR)
|
||||
raise ValidationError(str(PASSWORD_TOO_WEAK_ERROR))
|
||||
|
||||
return password
|
||||
|
||||
@@ -275,7 +275,7 @@ class LoggingSetPasswordForm(SetPasswordForm):
|
||||
if not check_password_strength(new_password):
|
||||
# The frontend code tries to stop the user from submitting the form with a weak password,
|
||||
# but if the user bypasses that protection, this error code path will run.
|
||||
raise ValidationError(PASSWORD_TOO_WEAK_ERROR)
|
||||
raise ValidationError(str(PASSWORD_TOO_WEAK_ERROR))
|
||||
|
||||
return new_password
|
||||
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
# See https://zulip.readthedocs.io/en/latest/subsystems/hotspots.html
|
||||
# for documentation on this subsystem.
|
||||
from typing import Dict, List
|
||||
from typing import TYPE_CHECKING, Dict, List
|
||||
|
||||
from django.conf import settings
|
||||
from django.utils.functional import Promise
|
||||
from django.utils.translation import gettext_lazy
|
||||
|
||||
from zerver.models import UserHotspot, UserProfile
|
||||
|
||||
INTRO_HOTSPOTS: Dict[str, Dict[str, Promise]] = {
|
||||
if TYPE_CHECKING:
|
||||
from django.utils.functional import _StrPromise as StrPromise
|
||||
|
||||
INTRO_HOTSPOTS: Dict[str, Dict[str, "StrPromise"]] = {
|
||||
"intro_streams": {
|
||||
"title": gettext_lazy("Catch up on a stream"),
|
||||
"description": gettext_lazy(
|
||||
@@ -42,7 +44,7 @@ INTRO_HOTSPOTS: Dict[str, Dict[str, Promise]] = {
|
||||
# We would most likely implement new hotspots in the future that aren't
|
||||
# a part of the initial tutorial. To that end, classifying them into
|
||||
# categories which are aggregated in ALL_HOTSPOTS, seems like a good start.
|
||||
ALL_HOTSPOTS: Dict[str, Dict[str, Promise]] = {
|
||||
ALL_HOTSPOTS: Dict[str, Dict[str, "StrPromise"]] = {
|
||||
**INTRO_HOTSPOTS,
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,23 @@
|
||||
import datetime
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Callable, Dict, List, Optional, Tuple, TypedDict, TypeVar, Union
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
Callable,
|
||||
Dict,
|
||||
List,
|
||||
Optional,
|
||||
Tuple,
|
||||
TypedDict,
|
||||
TypeVar,
|
||||
Union,
|
||||
)
|
||||
|
||||
from django.utils.functional import Promise
|
||||
from typing_extensions import NotRequired
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from django.utils.functional import _StrPromise as StrPromise
|
||||
|
||||
# See zerver/lib/validator.py for more details of Validators,
|
||||
# including many examples
|
||||
ResultT = TypeVar("ResultT")
|
||||
@@ -37,9 +50,11 @@ class ProfileDataElementUpdateDict(TypedDict):
|
||||
|
||||
ProfileData = List[ProfileDataElement]
|
||||
|
||||
FieldElement = Tuple[int, Promise, Validator[ProfileDataElementValue], Callable[[Any], Any], str]
|
||||
ExtendedFieldElement = Tuple[int, Promise, ExtendedValidator, Callable[[Any], Any], str]
|
||||
UserFieldElement = Tuple[int, Promise, RealmUserValidator, Callable[[Any], Any], str]
|
||||
FieldElement = Tuple[
|
||||
int, "StrPromise", Validator[ProfileDataElementValue], Callable[[Any], Any], str
|
||||
]
|
||||
ExtendedFieldElement = Tuple[int, "StrPromise", ExtendedValidator, Callable[[Any], Any], str]
|
||||
UserFieldElement = Tuple[int, "StrPromise", RealmUserValidator, Callable[[Any], Any], str]
|
||||
|
||||
ProfileFieldData = Dict[str, Union[Dict[str, str], str]]
|
||||
|
||||
|
||||
@@ -907,7 +907,7 @@ class Realm(models.Model):
|
||||
|
||||
def ensure_not_on_limited_plan(self) -> None:
|
||||
if self.plan_type == Realm.PLAN_TYPE_LIMITED:
|
||||
raise JsonableError(self.UPGRADE_TEXT_STANDARD)
|
||||
raise JsonableError(str(self.UPGRADE_TEXT_STANDARD))
|
||||
|
||||
@property
|
||||
def subdomain(self) -> str:
|
||||
@@ -1872,7 +1872,7 @@ class UserProfile(AbstractBaseUser, PermissionsMixin, UserBaseSettings):
|
||||
}
|
||||
|
||||
def get_role_name(self) -> str:
|
||||
return self.ROLE_ID_TO_NAME_MAP[self.role]
|
||||
return str(self.ROLE_ID_TO_NAME_MAP[self.role])
|
||||
|
||||
def profile_data(self) -> ProfileData:
|
||||
values = CustomProfileFieldValue.objects.filter(user_profile=self)
|
||||
@@ -4505,7 +4505,9 @@ class CustomProfileField(models.Model):
|
||||
FIELD_CONVERTERS: Dict[int, Callable[[Any], Any]] = {
|
||||
item[0]: item[3] for item in ALL_FIELD_TYPES
|
||||
}
|
||||
FIELD_TYPE_CHOICES: List[Tuple[int, Promise]] = [(item[0], item[1]) for item in ALL_FIELD_TYPES]
|
||||
FIELD_TYPE_CHOICES: List[Tuple[int, "StrPromise"]] = [
|
||||
(item[0], item[1]) for item in ALL_FIELD_TYPES
|
||||
]
|
||||
|
||||
field_type: int = models.PositiveSmallIntegerField(
|
||||
choices=FIELD_TYPE_CHOICES,
|
||||
|
||||
@@ -740,7 +740,7 @@ def send_messages_for_new_subscribers(
|
||||
internal_prep_stream_message(
|
||||
sender=sender,
|
||||
stream=stream,
|
||||
topic=Realm.STREAM_EVENTS_NOTIFICATION_TOPIC,
|
||||
topic=str(Realm.STREAM_EVENTS_NOTIFICATION_TOPIC),
|
||||
content=_(
|
||||
"**{policy}** stream created by {user_name}. **Description:**"
|
||||
).format(
|
||||
|
||||
@@ -719,7 +719,7 @@ def create_user_backend(
|
||||
pass
|
||||
|
||||
if not check_password_strength(password):
|
||||
raise JsonableError(PASSWORD_TOO_WEAK_ERROR)
|
||||
raise JsonableError(str(PASSWORD_TOO_WEAK_ERROR))
|
||||
|
||||
target_user = do_create_user(
|
||||
email,
|
||||
|
||||
Reference in New Issue
Block a user