Updated PLANS.md, AGENTS.md, and arabic-localization.md to reflect the “foundations now, full translations later” approach and marked progress accordingly.
Implemented localization foundations across backend and frontend (locale settings/middleware, preferred language, i18n wiring, RTL support, minimal Arabic UI strings, Accept-Language). Added targeted backend and frontend tests plus a risks note for pending full translation coverage.
This commit is contained in:
@@ -8,6 +8,8 @@ from django.conf import settings
|
||||
from django.contrib.auth.hashers import check_password, make_password
|
||||
from django.utils import timezone
|
||||
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from apps.accounts.models import OtpChannel, OtpPurpose, PhoneOTP
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -21,13 +23,13 @@ class OtpSendResult:
|
||||
|
||||
class OtpRateLimitError(RuntimeError):
|
||||
def __init__(self, retry_after_seconds: int):
|
||||
super().__init__("Too many OTP requests. Try again later.")
|
||||
super().__init__(_("Too many OTP requests. Try again later."))
|
||||
self.retry_after_seconds = retry_after_seconds
|
||||
|
||||
|
||||
class OtpCooldownError(RuntimeError):
|
||||
def __init__(self, retry_after_seconds: int):
|
||||
super().__init__("Please wait before requesting another code.")
|
||||
super().__init__(_("Please wait before requesting another code."))
|
||||
self.retry_after_seconds = retry_after_seconds
|
||||
|
||||
|
||||
@@ -56,17 +58,17 @@ class TwilioOtpProvider(BaseOtpProvider):
|
||||
|
||||
def _assert_config(self) -> None:
|
||||
if not self.account_sid or not self.auth_token or not self.from_number:
|
||||
raise ValueError("Twilio credentials are not configured")
|
||||
raise ValueError(_("Twilio credentials are not configured"))
|
||||
|
||||
def send_sms(self, to_number: str, message: str) -> None:
|
||||
self._assert_config()
|
||||
raise NotImplementedError("Twilio SMS adapter not implemented yet")
|
||||
raise NotImplementedError(_("Twilio SMS adapter not implemented yet"))
|
||||
|
||||
def send_whatsapp(self, to_number: str, message: str) -> None:
|
||||
self._assert_config()
|
||||
if not self.whatsapp_from:
|
||||
raise ValueError("Twilio WhatsApp sender is not configured")
|
||||
raise NotImplementedError("Twilio WhatsApp adapter not implemented yet")
|
||||
raise ValueError(_("Twilio WhatsApp sender is not configured"))
|
||||
raise NotImplementedError(_("Twilio WhatsApp adapter not implemented yet"))
|
||||
|
||||
|
||||
class UnifonicOtpProvider(BaseOtpProvider):
|
||||
@@ -77,17 +79,17 @@ class UnifonicOtpProvider(BaseOtpProvider):
|
||||
|
||||
def _assert_config(self) -> None:
|
||||
if not self.app_sid or not self.sender_id:
|
||||
raise ValueError("Unifonic credentials are not configured")
|
||||
raise ValueError(_("Unifonic credentials are not configured"))
|
||||
|
||||
def send_sms(self, to_number: str, message: str) -> None:
|
||||
self._assert_config()
|
||||
raise NotImplementedError("Unifonic SMS adapter not implemented yet")
|
||||
raise NotImplementedError(_("Unifonic SMS adapter not implemented yet"))
|
||||
|
||||
def send_whatsapp(self, to_number: str, message: str) -> None:
|
||||
self._assert_config()
|
||||
if not self.whatsapp_sender:
|
||||
raise ValueError("Unifonic WhatsApp sender is not configured")
|
||||
raise NotImplementedError("Unifonic WhatsApp adapter not implemented yet")
|
||||
raise ValueError(_("Unifonic WhatsApp sender is not configured"))
|
||||
raise NotImplementedError(_("Unifonic WhatsApp adapter not implemented yet"))
|
||||
|
||||
|
||||
PROVIDERS = {
|
||||
@@ -101,7 +103,7 @@ def get_provider() -> BaseOtpProvider:
|
||||
provider_key = settings.OTP_PROVIDER
|
||||
provider_cls = PROVIDERS.get(provider_key)
|
||||
if not provider_cls:
|
||||
raise ValueError(f"Unknown OTP provider: {provider_key}")
|
||||
raise ValueError(_("Unknown OTP provider: %(provider)s") % {"provider": provider_key})
|
||||
return provider_cls()
|
||||
|
||||
|
||||
@@ -147,13 +149,15 @@ def create_and_send_otp(phone_number: str, channel: str, purpose: str = OtpPurpo
|
||||
expires_at=PhoneOTP.expiry_at(),
|
||||
)
|
||||
|
||||
message = f"Your verification code is {code}. It expires in {settings.OTP_EXPIRY_MINUTES} minutes."
|
||||
message = _(
|
||||
"Your verification code is %(code)s. It expires in %(minutes)s minutes."
|
||||
) % {"code": code, "minutes": settings.OTP_EXPIRY_MINUTES}
|
||||
if channel == OtpChannel.SMS:
|
||||
provider.send_sms(phone_number, message)
|
||||
elif channel == OtpChannel.WHATSAPP:
|
||||
provider.send_whatsapp(phone_number, message)
|
||||
else:
|
||||
raise ValueError("Unsupported OTP channel")
|
||||
raise ValueError(_("Unsupported OTP channel"))
|
||||
|
||||
return OtpSendResult(request_id=str(otp.id), expires_at=otp.expires_at.isoformat())
|
||||
|
||||
|
||||
Reference in New Issue
Block a user