diff --git a/apps/account/messages.py b/apps/account/messages.py new file mode 100644 index 0000000..8f2b4be --- /dev/null +++ b/apps/account/messages.py @@ -0,0 +1,97 @@ +ACCOUNT_MESSAGES = { + 'email_already_registered': [ + {'language_code': 'en', 'text': 'This email is already registered.'}, + {'language_code': 'ru', 'text': 'Этот адрес электронной почты уже зарегистрирован.'}, + {'language_code': 'fa', 'text': 'این ایمیل قبلاً ثبت شده است.'}, + ], + 'device_id_required': [ + {'language_code': 'en', 'text': 'Device ID is required for guest users.'}, + {'language_code': 'ru', 'text': 'ID устройства обязателен для гостевых пользователей.'}, + {'language_code': 'fa', 'text': 'ارسال شناسه دستگاه الزامی است.'}, + ], + 'device_id_required_web': [ + {'language_code': 'en', 'text': 'Device ID is required for web guest users.'}, + {'language_code': 'ru', 'text': 'ID устройства обязателен для веб-гостей.'}, + {'language_code': 'fa', 'text': 'ارسال شناسه دستگاه برای کاربران وب الزامی است.'}, + ], + 'otp_sent': [ + {'language_code': 'en', 'text': "The otp code was sent to the user's email"}, + {'language_code': 'ru', 'text': 'Одноразовый код был отправлен на электронную почту пользователя.'}, + {'language_code': 'fa', 'text': 'کد تایید به ایمیل کاربر ارسال شد.'}, + ], + 'verification_data_not_found': [ + {'language_code': 'en', 'text': 'Verification data not found or expired.'}, + {'language_code': 'ru', 'text': 'Данные для подтверждения не найдены или истек срок их действия.'}, + {'language_code': 'fa', 'text': 'اطلاعات تایید یافت نشد یا منقضی شده است.'}, + ], + 'verification_code_expired': [ + {'language_code': 'en', 'text': 'The verification code has expired.'}, + {'language_code': 'ru', 'text': 'Срок действия кода подтверждения истек.'}, + {'language_code': 'fa', 'text': 'کد تایید منقضی شده است.'}, + ], + 'code_not_found': [ + {'language_code': 'en', 'text': 'The verification code is incorrect.'}, + {'language_code': 'ru', 'text': 'Неверный код подтверждения.'}, + {'language_code': 'fa', 'text': 'کد وارد شده صحیح نیست.'}, + ], + 'user_not_exists': [ + {'language_code': 'en', 'text': 'No user exists with this email.'}, + {'language_code': 'ru', 'text': 'Пользователь с такой электронной почтой не существует.'}, + {'language_code': 'fa', 'text': 'کاربری با این ایمیل یافت نشد.'}, + ], + 'password_incorrect': [ + {'language_code': 'en', 'text': 'Password is incorrect.'}, + {'language_code': 'ru', 'text': 'Неверный пароль.'}, + {'language_code': 'fa', 'text': 'رمز عبور اشتباه است.'}, + ], + 'forgot_password_sent': [ + {'language_code': 'en', 'text': 'Forgot password code sent.'}, + {'language_code': 'ru', 'text': 'Код для восстановления пароля отправлен.'}, + {'language_code': 'fa', 'text': 'کد بازیابی رمز عبور ارسال شد.'}, + ], + 'password_reset_success': [ + {'language_code': 'en', 'text': 'Your password has been changed successfully.'}, + {'language_code': 'ru', 'text': 'Ваш пароль был успешно изменен.'}, + {'language_code': 'fa', 'text': 'رمز عبور شما با موفقیت تغییر کرد.'}, + ], + 'account_deleted': [ + {'language_code': 'en', 'text': 'Your account has been deleted.'}, + {'language_code': 'ru', 'text': 'Ваша учетная запись была удалена.'}, + {'language_code': 'fa', 'text': 'حساب کاربری شما حذف شد.'}, + ], + 'user_not_found': [ + {'language_code': 'en', 'text': 'User does not exist.'}, + {'language_code': 'ru', 'text': 'Пользователь не существует.'}, + {'language_code': 'fa', 'text': 'کاربر یافت نشد.'}, + ], + 'fcm_token_required': [ + {'language_code': 'en', 'text': 'FCM token is required.'}, + {'language_code': 'ru', 'text': 'FCM-токен обязателен.'}, + {'language_code': 'fa', 'text': 'ارسال توکن FCM الزامی است.'}, + ], + 'fcm_token_updated': [ + {'language_code': 'en', 'text': 'FCM token updated successfully.'}, + {'language_code': 'ru', 'text': 'FCM-токен успешно обновлен.'}, + {'language_code': 'fa', 'text': 'توکن FCM با موفقیت بروزرسانی شد.'}, + ], + 'login_success': [ + {'language_code': 'en', 'text': 'Login successful.'}, + {'language_code': 'ru', 'text': 'Вход выполнен успешно.'}, + {'language_code': 'fa', 'text': 'ورود موفقیت‌آمیز بود.'}, + ], + 'token_missing': [ + {'language_code': 'en', 'text': 'Token not provided.'}, + {'language_code': 'ru', 'text': 'Токен не предоставлен.'}, + {'language_code': 'fa', 'text': 'توکن ارسال نشده است.'}, + ], + 'token_invalid_expired': [ + {'language_code': 'en', 'text': 'Token invalid or expired.'}, + {'language_code': 'ru', 'text': 'Токен недействителен или срок его действия истек.'}, + {'language_code': 'fa', 'text': 'توکن نامعتبر یا منقضی شده است.'}, + ], + 'token_invalid': [ + {'language_code': 'en', 'text': 'Token is invalid.'}, + {'language_code': 'ru', 'text': 'Токен недействителен.'}, + {'language_code': 'fa', 'text': 'توکن نامعتبر است.'}, + ], +} diff --git a/apps/account/serializers/user.py b/apps/account/serializers/user.py index 90d07ce..b3a5834 100644 --- a/apps/account/serializers/user.py +++ b/apps/account/serializers/user.py @@ -3,6 +3,7 @@ from rest_framework.authtoken.models import Token from django.contrib.auth.password_validation import validate_password from django.utils.translation import gettext_lazy as _ from apps.account.models import User +from apps.account.utils import get_localized_msg from utils import FileFieldSerializer, absolute_url from utils.validators import validate_type_code @@ -89,7 +90,8 @@ class UserRegisterSerializer(serializers.ModelSerializer): def validate_email(self, value): normalized_email = User.objects.normalize_email(value) if User.objects.filter(email=normalized_email).exists(): - raise serializers.ValidationError("This email is already registered.") + msg = get_localized_msg('email_already_registered', self.context.get('request')) + raise serializers.ValidationError(msg) return normalized_email @@ -194,7 +196,8 @@ class UserGuestSerializer(serializers.ModelSerializer): def validate(self, data): # Make sure at least device_id is provided if not data.get('device_id'): - raise serializers.ValidationError({"device_id": "Device ID is required for guest users."}) + msg = get_localized_msg('device_id_required', self.context.get('request')) + raise serializers.ValidationError({"device_id": msg}) return data @@ -211,7 +214,8 @@ class WebUserGuestSerializer(serializers.ModelSerializer): def validate(self, data): # Ensure device_id is provided (generated by view) if not data.get('device_id'): - raise serializers.ValidationError({"device_id": "Device ID is required for web guest users."}) + msg = get_localized_msg('device_id_required_web', self.context.get('request')) + raise serializers.ValidationError({"device_id": msg}) return data diff --git a/apps/account/utils.py b/apps/account/utils.py new file mode 100644 index 0000000..805bfb3 --- /dev/null +++ b/apps/account/utils.py @@ -0,0 +1,33 @@ +from django.utils.translation import get_language +from .messages import ACCOUNT_MESSAGES + +def get_localized_msg(key, request=None, fallback_lang="en"): + """ + Get localized message from ACCOUNT_MESSAGES based on request or active language. + """ + json_list = ACCOUNT_MESSAGES.get(key) + if not json_list: + return key # Fallback to key itself if not found + + # Get language from request (LocaleMiddleware sets LANGUAGE_CODE) + language_code = getattr(request, "LANGUAGE_CODE", None) if request else None + + # Check if 'lang' query param is used (common in this project's mobile apps) + if not language_code and request: + language_code = request.query_params.get('lang') + + if not language_code: + language_code = get_language() or fallback_lang + + # 1) Exact match + for item in json_list: + if item.get("language_code") == language_code: + return item.get("text") + + # 2) Fallback to English + for item in json_list: + if item.get("language_code") == "en": + return item.get("text") + + # 3) First available + return json_list[0].get("text") if json_list else key diff --git a/apps/account/views/auth.py b/apps/account/views/auth.py index a4bf4e3..aac9768 100644 --- a/apps/account/views/auth.py +++ b/apps/account/views/auth.py @@ -11,6 +11,7 @@ from rest_framework.permissions import AllowAny from rest_framework.response import Response from apps.account.serializers import ExchangeTokenSerializer +from apps.account.utils import get_localized_msg from utils import absolute_url from utils.redis import OnlineClassTokenManager @@ -77,25 +78,28 @@ class ExchangeTokenAPIView(GenericAPIView): try: token_data = manager.get_payload(temp_token) except Exception: + msg = get_localized_msg('token_invalid_expired', request) return Response({ 'success': False, - 'message': 'توکن نامعتبر یا منقضی شده است' + 'message': msg }, status=status.HTTP_404_NOT_FOUND) user_id = token_data.get('user_id') if not user_id: + msg = get_localized_msg('token_invalid', request) return Response({ 'success': False, - 'message': 'توکن نامعتبر است' + 'message': msg }, status=status.HTTP_400_BAD_REQUEST) # دریافت کاربر try: user = UserModel.objects.get(id=user_id) except UserModel.DoesNotExist: + msg = get_localized_msg('user_not_found', request) return Response({ 'success': False, - 'message': 'کاربر یافت نشد' + 'message': msg }, status=status.HTTP_404_NOT_FOUND) # حذف توکن موقت (one-time use) @@ -113,9 +117,10 @@ class ExchangeTokenAPIView(GenericAPIView): avatar_url = None # برگرداندن اطلاعات کاربر با token واقعی + msg = get_localized_msg('login_success', request) return Response({ 'success': True, - 'message': 'ورود موفق', + 'message': msg, 'token': auth_token.key, 'user': { 'id': user.id, diff --git a/apps/account/views/user.py b/apps/account/views/user.py index 5c166a7..d90f9fe 100644 --- a/apps/account/views/user.py +++ b/apps/account/views/user.py @@ -22,6 +22,7 @@ from drf_yasg import openapi from rest_framework.exceptions import ValidationError from utils.exceptions import InvaliedCodeVrify, ExpiredCodeException, ServiceUnavailableException +from apps.account.utils import get_localized_msg from apps.account.models import User from apps.account.serializers import UserRegisterSerializer, UserProfileSerializer, UserVerifySerializer, UserLoginSerializer, UserRecoverPasswordSerializer, UserResetPasswordSerializer, UserGuestSerializer,UserFCMSerializer,WebUserGuestSerializer from apps.account.serializers.user_web import WebUserRegisterSerializer @@ -239,7 +240,7 @@ class UserRegisterView(CreateAPIView): return Response( data= { "user": data, - "message": "The otp code was sent to the user's email" + "message": get_localized_msg('otp_sent', request) }, status=status.HTTP_202_ACCEPTED, ) @@ -266,13 +267,15 @@ class UserVerifyView(CreateAPIView): try: verify_data = RedisManager().get_by_redis(data['email']) if not verify_data: - raise ValidationError({"code": "Verification data not found or expired."}) + msg = get_localized_msg('verification_data_not_found', request) + raise ValidationError({"code": msg}) # raise ExpiredCodeException("Verification data not found or expired.") except (ServiceUnavailableException) as e: return AppAPIException({"message": str(e)}, status_code=e.status_code) except ExpiredCodeException: # raise ExpiredCodeException("The verification code has expired.") - raise ValidationError({"code": "The verification code has expired."}) + msg = get_localized_msg('verification_code_expired', request) + raise ValidationError({"code": msg}) code = self.valied_code(data['code'], verify_data['code']) del verify_data['code'] @@ -293,7 +296,8 @@ class UserVerifyView(CreateAPIView): if (current_code and save_code) and ( current_code != save_code): if current_code == "11111": return current_code - raise ValidationError({"code": "code notfound"}) + msg = get_localized_msg('code_not_found', self.request) + raise ValidationError({"code": msg}) return current_code @@ -365,7 +369,7 @@ class WebUserRegisterView(CreateAPIView): "fullname": data.get('fullname'), "email": data.get('email'), }, - "message": "The otp code was sent to the user's email" + "message": get_localized_msg('otp_sent', request) }, status=status.HTTP_202_ACCEPTED, ) @@ -400,12 +404,14 @@ class UserLoginView(CreateAPIView): try: user_obj = User.objects.get(email=email) except User.DoesNotExist: - raise ValidationError({"email": "user not exists with this email"}) + msg = get_localized_msg('user_not_exists', request) + raise ValidationError({"email": msg}) # If user exists, try to authenticate (check password) user = authenticate(request, username=email, password=data['password']) if not user: - raise ValidationError({"password": "password is incorrect"}) + msg = get_localized_msg('password_incorrect', request) + raise ValidationError({"password": msg}) user_timezone = serializer.validated_data.pop('timezone', None) user.last_login = timezone.now() @@ -485,7 +491,7 @@ class UserRecoverPassword(CreateAPIView): "phone_number": str(user.phone_number) if user.phone_number else None, "email": user.email if user.email else None, "avatar": request.build_absolute_uri(user.avatar.url) if user.avatar else None, - "message": "Forgot password code sent" + "message": get_localized_msg('forgot_password_sent', request) }, status=status.HTTP_202_ACCEPTED, ) @@ -513,7 +519,8 @@ class UserResetPassword(CreateAPIView): user.save() # Return a success response - return Response({"message": "Your password has been changed successfully."}, status=status.HTTP_200_OK) + msg = get_localized_msg('password_reset_success', request) + return Response({"message": msg}, status=status.HTTP_200_OK) @@ -532,11 +539,13 @@ class UserDeleteView(APIView): if t := Token.objects.filter(user=user).first(): t.delete() - return Response({"detail": "Your account has been deleted."}, status=status.HTTP_204_NO_CONTENT) + msg = get_localized_msg('account_deleted', request) + return Response({"detail": msg}, status=status.HTTP_204_NO_CONTENT) except Exception: # پیام خطای ثابت برای سایر خطاهای غیرمنتظره - return Response({"detail": "User does not exist."}, status=status.HTTP_404_NOT_FOUND) + msg = get_localized_msg('user_not_found', request) + return Response({"detail": msg}, status=status.HTTP_404_NOT_FOUND) class UpdateFCMView(GenericAPIView): @@ -550,9 +559,11 @@ class UpdateFCMView(GenericAPIView): fcm_token = request.data.get('fcm') if not fcm_token: - return Response({"detail": "FCM token is required."}, status=status.HTTP_200_OK) + msg = get_localized_msg('fcm_token_required', request) + return Response({"detail": msg}, status=status.HTTP_200_OK) user.fcm = fcm_token user.save() - return Response({"detail": "FCM token updated successfully."}, status=status.HTTP_200_OK) \ No newline at end of file + msg = get_localized_msg('fcm_token_updated', request) + return Response({"detail": msg}, status=status.HTTP_200_OK) \ No newline at end of file