Repository: internetarchive/openlibrary

Issue:
Login errors not localized
### Problem

Login errors are only displayed in English, regardless of chosen language.

#### Evidence / Screenshot
![localized_login_errors](https://github.com/internetarchive/openlibrary/assets/28732543/3c71e35d-21cd-4b5c-96ba-b862820ee169)
_Highlighted area displays non-localized strings._

#### Relevant URL(s)
https://openlibrary.org/account/login


### Reproducing the bug

1. Go to the `/account/login` page
2. Select a language other than English from the language dropper, found on the page's banner
3. Log in with invalid credentials

* Expected behavior: Error message is written in the selected language
* Actual behavior: Error message is English


### Context

- Browser (Chrome, Safari, Firefox, etc): All
- OS (Windows, Mac, etc): All
- Logged in (Y/N): N
- Environment (prod, dev, local): prod


### Notes from this Issue's Lead

#### Proposal & constraints
<!-- What is the proposed solution / implementation? Is there a precedent of this approach succeeding elsewhere? -->
Replace hard-coded login errors with localized strings.

#### Related files
<!-- Files related to this issue; this is super useful for new contributors who might want to help! If you're not sure, leave this blank; a maintainer will add them. -->
The error strings can be found in the `LOGIN_ERRORS` `dict`, here:
https://github.com/internetarchive/openlibrary/blob/a2f111c8cf6b52e2e30f7ae1fb009b76d3c92b5e/openlibrary/plugins/upstream/account.py#L60-L76

Keys of this `dict` should remain as they are --- only the values should change.

#### Stakeholders
<!-- @ tag stakeholders of this bug -->
@rebecca-shoptaw 


Discussion:
Hi, I would like to work on this issue.
@RBrar66 Great, it's all yours! You can read up more fully on the internationalization syntax as needed in the [wiki](https://github.com/internetarchive/openlibrary/wiki#internationalization-i18n-developers-guide), but for reference here is an example of a correctly internationalized Python string:
https://github.com/internetarchive/openlibrary/blob/6816776da0a228675bd818d3ac2a4b47a9b9f8ac/openlibrary/plugins/upstream/account.py#L536-L537

All the `i18n` process requires here is wrapping the to-be-translated string in `_(' ')` like the example above, i.e. `"invalid_email": "The email address you entered is invalid", ` would become `"invalid_email": _('The email address you entered is invalid'), `

Thanks so much and let me know if you have any questions!

Resolving patch:
diff --git a/openlibrary/plugins/upstream/account.py b/openlibrary/plugins/upstream/account.py
--- a/openlibrary/plugins/upstream/account.py
+++ b/openlibrary/plugins/upstream/account.py
@@ -57,21 +57,38 @@
 create_link_doc = accounts.create_link_doc
 sendmail = accounts.sendmail
 
-LOGIN_ERRORS = {
-    "invalid_email": "The email address you entered is invalid",
-    "account_blocked": "This account has been blocked",
-    "account_locked": "This account has been blocked",
-    "account_not_found": "No account was found with this email. Please try again",
-    "account_incorrect_password": "The password you entered is incorrect. Please try again",
-    "account_bad_password": "Wrong password. Please try again",
-    "account_not_verified": "Please verify your Open Library account before logging in",
-    "ia_account_not_verified": "Please verify your Internet Archive account before logging in",
-    "missing_fields": "Please fill out all fields and try again",
-    "email_registered": "This email is already registered",
-    "username_registered": "This username is already registered",
-    "max_retries_exceeded": "A problem occurred and we were unable to log you in.",
-    "invalid_s3keys": "Login attempted with invalid Internet Archive s3 credentials.",
-}
+
+def get_login_error(error_key):
+    """Nesting the LOGIN_ERRORS dictionary inside a function prevents
+    an AttributeError with the web.ctx.lang library"""
+    LOGIN_ERRORS = {
+        "invalid_email": _('The email address you entered is invalid'),
+        "account_blocked": _('This account has been blocked'),
+        "account_locked": _('This account has been locked'),
+        "account_not_found": _(
+            'No account was found with this email. Please try again'
+        ),
+        "account_incorrect_password": _(
+            'The password you entered is incorrect. Please try again'
+        ),
+        "account_bad_password": _('Wrong password. Please try again'),
+        "account_not_verified": _(
+            'Please verify your Open Library account before logging in'
+        ),
+        "ia_account_not_verified": _(
+            'Please verify your Internet Archive account before logging in'
+        ),
+        "missing_fields": _('Please fill out all fields and try again'),
+        "email_registered": _('This email is already registered'),
+        "username_registered": _('This username is already registered'),
+        "max_retries_exceeded": _(
+            'A problem occurred and we were unable to log you in.'
+        ),
+        "invalid_s3keys": _(
+            'Login attempted with invalid Internet Archive s3 credentials.'
+        ),
+    }
+    return LOGIN_ERRORS[error_key]
 
 
 class availability(delegate.page):
@@ -300,7 +317,7 @@ def POST(self):
                     username=f.username.value, email=f.email.value
                 )
             except ValueError:
-                f.note = LOGIN_ERRORS['max_retries_exceeded']
+                f.note = get_login_error('max_retries_exceeded')
 
         return render['account/create'](f)
 
@@ -341,7 +358,7 @@ def POST(self):
             if error:
                 resp = {
                     'error': error,
-                    'errorDisplayString': LOGIN_ERRORS[error],
+                    'errorDisplayString': get_login_error(error),
                 }
                 raise olib.code.BadRequest(json.dumps(resp))
             expires = 3600 * 24 * 365 if remember.lower() == 'true' else ""
@@ -381,7 +398,7 @@ class account_login(delegate.page):
     def render_error(self, error_key, i):
         f = forms.Login()
         f.fill(i)
-        f.note = LOGIN_ERRORS[error_key]
+        f.note = get_login_error(error_key)
         return render.login(f)
 
     def GET(self):


Test patch: