Repository: bookwyrm-social/bookwyrm

Issue:
language settings don't persist past browser sessions
**Describe the bug**
Language settings don't persist past browser sessions. If I change my language settings, and close my browser without logging out, when I open the browser again, the language has reverted to English. However, if I logout and log back in, bookwyrm is in the language I specified. 

**To Reproduce**
Steps to reproduce the behavior:
( assuming your bookwyrm is already in English)
1. Go to https://bookwyrm.social/preferences/profile
2. Select a language other than English under the Language setting and Save
3. Close the browser window and reopen bookwyrm
4. See that bookwyrm is in English and not the language you selected

**Expected behavior**
I would expect that my language settings are persistent across browser sessions. 

**Instance**
bookwyrm.social

**Additional Context**
I have been able to repro this bug on Safari and Brave on macOS BigSur


Discussion:
I can replicate this in Safari if I compare a private window with a non-private.

@reesporte can you confirm that by step 4 above you mean you are **not** logged in at that point?

Language preferences are set in two places: at the server/instance level via an `env` variable, and per-user. The per-user setting is stored in the database with the other user details, but (presumably so that we don't have to check the database when loading every page) it is also stored in a session cookie. The cookie value is set (a) when the user logs in and/or (b) when the user updates their preferred language (step 2 above).

Once we know that, the behaviour you are seeing makes some sense (albeit not necessarily what you expected). When you log out, the browser session is still active so your language settings cookie persists even though you are no longer logged in. When you close the window, the browser session ends, so when you open a new window without logging in, the language settings are the default for that instance of Bookwyrm, rather than whatever you set in your last session. Once you log in again, a new session cookie should set the language back to your own personal preferred language.

@mouse-reeve do you have any thoughts on the wisdom of using `localStorage` for user language settings instead of a cookie? The upside would be that language settings for a particular instance could persist across sessions even after the user has logged out. A downside I can see might be for public/shared machines that aren't set up sensibly, this would apply those settings to each subsequent user. However that's just my old public librarian brain working overtime, it's an unlikely scenario and once the user logs in their own settings would take over anyway.
@hughrun no in step 4, you _are_ logged in

my guess was that it was being stored in a session cookie or session storage, which would explain the language preference disappearing on window close and re-open. 

i'm thinking setting this in `localStorage` and deleting it on logout is probably best in this situation, or setting a cookie that expires a gazillion years from now, that's also deleted on logout. 

either way, probably shouldn't tie language preferences to the lifetime of the webpage
> @hughrun no in step 4, you are logged in

I don't think I can replicate that, and I'm a bit confused about how it would happen? If your login cookie persists it seems odd that the language setting cookie doesn't. Could this be something to do with how Brave or your Safari setup works?

> i'm thinking setting this in localStorage and deleting it on logout is probably best in this situation, or setting a cookie that expires a gazillion years from now, that's also deleted on logout.

Deleting on logout rather than end of browser session does make sense. The reason I was thinking of _not_ doing that is that the (logged out) homepage will then always be in the default language of the instance, whereas if we were to persist a `localStorage` value the interface would always be in the user's preferred language regardless of whether they are logged in. However my caveats from above still stand so it may not be a sensible idea: it assumes every user of that particular client prefers the same language.
Actually scratch that, I _can_ replicate it: I had only closed the window, not the whole browser. The language cookie is a session cookie whereas the login cookie lasts 2 weeks.
I'm not sure that localstorage will help in this situation, since the translation is handled server side. The session cookie in set in the views when you log in or save settings, using this function: https://github.com/bookwyrm-social/bookwyrm/blob/main/bookwyrm/views/helpers.py#L185

That functionality could happen as middleware, which would ensure the language is always set correctly. This would work that same way that the timezone middleware works: https://github.com/bookwyrm-social/bookwyrm/blob/main/bookwyrm/middleware/timezone_middleware.py
@mouse-reeve what if the language setting cookie was set to last the same amount of time as the log-in cookie?
I think that would do the trick 🤔 
unless the session cookie is cleared when the browser is closed
> unless the session cookie is cleared when the browser is closed

Yes, I guess that's what I meant: can we make it _not_ a session cookie, like the log in cookie uh, isn't?
Oh gotcha. No idea!
i think the answer is yes: in https://github.com/bookwyrm-social/bookwyrm/blob/main/bookwyrm/views/helpers.py#L185 it would look something like 
```python
def set_language(user, response):
    """Updates a user's language"""
    if user.preferred_language:
        translation.activate(user.preferred_language)
    response.set_cookie(settings.LANGUAGE_COOKIE_NAME, user.preferred_language, expires=datetime.now() + timedelta(weeks=6000))
    return response
```
i used 6000 weeks here as a ridiculous expiry time, but i think it should work until the user clears their cookies or resets their language preferences

i also did a quick scan for how the login cookie is set, and couldn't find it so it might be using django magic or something
I was just looking at this too and if I'm reading this right, we can actually just attach the language preference to the login session cookie: https://docs.djangoproject.com/en/3.2/topics/http/sessions/#using-sessions-in-views

```python
def set_language(user, response):
    """Updates a user's language"""
    if user.preferred_language:
        translation.activate(user.preferred_language)
    request.session['preferred_language'] = user.preferred_language
    return response
```
Oh wait, now I see, django wants a `django_language` cookie. So @reesporte's suggestion is better, and maybe we just match the seconds left before the session expires:
```python
def set_language(user, response):
    """Updates a user's language"""
    if user.preferred_language:
        translation.activate(user.preferred_language)
    response.set_cookie(settings.LANGUAGE_COOKIE_NAME, user.preferred_language, expires=request.session.get_expiry_age())
    return response
```

@hughrun i like the idea of matching session expiration date, so it's consistent across the app

i'm thinking it might be easier to just set `expires` to the current time plus [`django.conf.settings.SESSION_COOKIE_AGE`](https://docs.djangoproject.com/en/4.0/ref/settings/#session-cookie-age). 

it would look something like: 
```python
response.set_cookie(settings.LANGUAGE_COOKIE_NAME, user.preferred_language, expires=datetime.now() + timedelta(seconds=django.conf.settings.SESSION_COOKIE_AGE))
```

this way we don't have to update the `set_language` signature to include `request` and can just use a [pre-defined setting](https://docs.djangoproject.com/en/4.0/topics/http/sessions/#browser-length-sessions-vs-persistent-sessions)

Resolving patch:
diff --git a/bookwyrm/views/helpers.py b/bookwyrm/views/helpers.py
--- a/bookwyrm/views/helpers.py
+++ b/bookwyrm/views/helpers.py
@@ -1,12 +1,13 @@
 """ helper functions used in various views """
 import re
-from datetime import datetime
+from datetime import datetime, timedelta
 import dateutil.parser
 import dateutil.tz
 from dateutil.parser import ParserError
 
 from requests import HTTPError
 from django.db.models import Q
+from django.conf import settings as django_settings
 from django.http import Http404
 from django.utils import translation
 
@@ -186,7 +187,11 @@ def set_language(user, response):
     """Updates a user's language"""
     if user.preferred_language:
         translation.activate(user.preferred_language)
-    response.set_cookie(settings.LANGUAGE_COOKIE_NAME, user.preferred_language)
+    response.set_cookie(
+        settings.LANGUAGE_COOKIE_NAME,
+        user.preferred_language,
+        expires=datetime.now() + timedelta(seconds=django_settings.SESSION_COOKIE_AGE),
+    )
     return response
 
 


Test patch: