prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  Session expiring do not display a localized logout message.
  # Bug
  
  ## Description
  
  Like #2391, if a source has their session expire, they will not be shown a localized message when they log out.
  
  ## Steps to Reproduce
  
  Set session expire to 30 seconds. Log in. Set locale to not-english. Wait 30 seconds. Refresh. See no-localized flashed message.
  
  ## Expected Behavior
  
  The logout message is localized.
  
  ## Actual Behavior
  
  It is not.
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `securedrop/source_app/__init__.py`
  Content:
  ```
  1 from datetime import datetime, timedelta
  2 from flask import (Flask, render_template, flash, Markup, request, g, session,
  3                    url_for, redirect)
  4 from flask_babel import gettext
  5 from flask_assets import Environment
  6 from flask_wtf.csrf import CSRFProtect
  7 from jinja2 import evalcontextfilter
  8 from os import path
  9 from sqlalchemy.orm.exc import NoResultFound
  10 
  11 import crypto_util
  12 import i18n
  13 import store
  14 import template_filters
  15 import version
  16 
  17 from db import Source, db_session
  18 from request_that_secures_file_uploads import RequestThatSecuresFileUploads
  19 from source_app import main, info, api
  20 from source_app.decorators import ignore_static
  21 from source_app.utils import logged_in
  22 
  23 
  24 def create_app(config):
  25     app = Flask(__name__,
  26                 template_folder=config.SOURCE_TEMPLATES_DIR,
  27                 static_folder=path.join(config.SECUREDROP_ROOT, 'static'))
  28     app.request_class = RequestThatSecuresFileUploads
  29     app.config.from_object(config.SourceInterfaceFlaskConfig)
  30 
  31     # The default CSRF token expiration is 1 hour. Since large uploads can
  32     # take longer than an hour over Tor, we increase the valid window to 24h.
  33     app.config['WTF_CSRF_TIME_LIMIT'] = 60 * 60 * 24
  34     CSRFProtect(app)
  35 
  36     assets = Environment(app)
  37     app.config['assets'] = assets
  38 
  39     i18n.setup_app(app)
  40 
  41     app.jinja_env.trim_blocks = True
  42     app.jinja_env.lstrip_blocks = True
  43     app.jinja_env.globals['version'] = version.__version__
  44     if getattr(config, 'CUSTOM_HEADER_IMAGE', None):
  45         app.jinja_env.globals['header_image'] = config.CUSTOM_HEADER_IMAGE
  46         app.jinja_env.globals['use_custom_header_image'] = True
  47     else:
  48         app.jinja_env.globals['header_image'] = 'logo.png'
  49         app.jinja_env.globals['use_custom_header_image'] = False
  50 
  51     app.jinja_env.filters['rel_datetime_format'] = \
  52         template_filters.rel_datetime_format
  53     app.jinja_env.filters['nl2br'] = evalcontextfilter(template_filters.nl2br)
  54     app.jinja_env.filters['filesizeformat'] = template_filters.filesizeformat
  55 
  56     for module in [main, info, api]:
  57         app.register_blueprint(module.make_blueprint(config))
  58 
  59     @app.before_request
  60     @ignore_static
  61     def check_tor2web():
  62         # ignore_static here so we only flash a single message warning
  63         # about Tor2Web, corresponding to the initial page load.
  64         if 'X-tor2web' in request.headers:
  65             flash(Markup(gettext(
  66                 '<strong>WARNING:</strong> You appear to be using Tor2Web. '
  67                 'This <strong>does not</strong> provide anonymity. '
  68                 '<a href="{url}">Why is this dangerous?</a>')
  69                 .format(url=url_for('info.tor2web_warning'))),
  70                   "banner-warning")
  71 
  72     @app.before_request
  73     @ignore_static
  74     def setup_g():
  75         """Store commonly used values in Flask's special g object"""
  76         g.locale = i18n.get_locale()
  77         g.text_direction = i18n.get_text_direction(g.locale)
  78         g.html_lang = i18n.locale_to_rfc_5646(g.locale)
  79         g.locales = i18n.get_locale2name()
  80 
  81         if 'expires' in session and datetime.utcnow() >= session['expires']:
  82             session.clear()
  83             msg = render_template('session_timeout.html')
  84             flash(Markup(msg), "important")
  85 
  86         session['expires'] = datetime.utcnow() + \
  87             timedelta(minutes=getattr(config,
  88                                       'SESSION_EXPIRATION_MINUTES',
  89                                       30))
  90 
  91         # ignore_static here because `crypto_util.hash_codename` is scrypt
  92         # (very time consuming), and we don't need to waste time running if
  93         # we're just serving a static resource that won't need to access
  94         # these common values.
  95         if logged_in():
  96             g.codename = session['codename']
  97             g.filesystem_id = crypto_util.hash_codename(g.codename)
  98             try:
  99                 g.source = Source.query \
  100                             .filter(Source.filesystem_id == g.filesystem_id) \
  101                             .one()
  102             except NoResultFound as e:
  103                 app.logger.error(
  104                     "Found no Sources when one was expected: %s" %
  105                     (e,))
  106                 del session['logged_in']
  107                 del session['codename']
  108                 return redirect(url_for('main.index'))
  109             g.loc = store.path(g.filesystem_id)
  110 
  111     @app.teardown_appcontext
  112     def shutdown_session(exception=None):
  113         """Automatically remove database sessions at the end of the request, or
  114         when the application shuts down"""
  115         db_session.remove()
  116 
  117     @app.errorhandler(404)
  118     def page_not_found(error):
  119         return render_template('notfound.html'), 404
  120 
  121     @app.errorhandler(500)
  122     def internal_error(error):
  123         return render_template('error.html'), 500
  124 
  125     return app
  126
  ```
  --- END FILES ---
  
  Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.
  
  Here is an example:
  
  ```diff
  diff --git a/examples/server_async.py b/examples/server_async.py
  --- a/examples/server_async.py
  +++ b/examples/server_async.py
  @@ -313,4 +313,4 @@
   
   
   if __name__ == "__main__":
  -    asyncio.run(run_async_server("."), debug=True)
  +    asyncio.run(run_async_server(), debug=True)
  diff --git a/examples/server_sync.py b/examples/server_sync.py
  --- a/examples/server_sync.py
  +++ b/examples/server_sync.py
  @@ -313,5 +313,5 @@
   
   
   if __name__ == "__main__":
  -    server = run_sync_server(".")
  +    server = run_sync_server()
       server.shutdown()
  
  ```
  
golden_diff: diff --git a/securedrop/source_app/__init__.py b/securedrop/source_app/__init__.py
  --- a/securedrop/source_app/__init__.py
  +++ b/securedrop/source_app/__init__.py
  @@ -79,8 +79,11 @@
           g.locales = i18n.get_locale2name()
   
           if 'expires' in session and datetime.utcnow() >= session['expires']:
  -            session.clear()
               msg = render_template('session_timeout.html')
  +
  +            # clear the session after we render the message so it's localized
  +            session.clear()
  +
               flash(Markup(msg), "important")
   
           session['expires'] = datetime.utcnow() + \
  
verification_info: {"golden_diff": "diff --git a/securedrop/source_app/__init__.py b/securedrop/source_app/__init__.py\n--- a/securedrop/source_app/__init__.py\n+++ b/securedrop/source_app/__init__.py\n@@ -79,8 +79,11 @@\n         g.locales = i18n.get_locale2name()\n \n         if 'expires' in session and datetime.utcnow() >= session['expires']:\n-            session.clear()\n             msg = render_template('session_timeout.html')\n+\n+            # clear the session after we render the message so it's localized\n+            session.clear()\n+\n             flash(Markup(msg), \"important\")\n \n         session['expires'] = datetime.utcnow() + \\\n", "issue": "Session expiring do not display a localized logout message.\n# Bug\r\n\r\n## Description\r\n\r\nLike #2391, if a source has their session expire, they will not be shown a localized message when they log out.\r\n\r\n## Steps to Reproduce\r\n\r\nSet session expire to 30 seconds. Log in. Set locale to not-english. Wait 30 seconds. Refresh. See no-localized flashed message.\r\n\r\n## Expected Behavior\r\n\r\nThe logout message is localized.\r\n\r\n## Actual Behavior\r\n\r\nIt is not.\n", "before_files": [{"content": "from datetime import datetime, timedelta\nfrom flask import (Flask, render_template, flash, Markup, request, g, session,\n                   url_for, redirect)\nfrom flask_babel import gettext\nfrom flask_assets import Environment\nfrom flask_wtf.csrf import CSRFProtect\nfrom jinja2 import evalcontextfilter\nfrom os import path\nfrom sqlalchemy.orm.exc import NoResultFound\n\nimport crypto_util\nimport i18n\nimport store\nimport template_filters\nimport version\n\nfrom db import Source, db_session\nfrom request_that_secures_file_uploads import RequestThatSecuresFileUploads\nfrom source_app import main, info, api\nfrom source_app.decorators import ignore_static\nfrom source_app.utils import logged_in\n\n\ndef create_app(config):\n    app = Flask(__name__,\n                template_folder=config.SOURCE_TEMPLATES_DIR,\n                static_folder=path.join(config.SECUREDROP_ROOT, 'static'))\n    app.request_class = RequestThatSecuresFileUploads\n    app.config.from_object(config.SourceInterfaceFlaskConfig)\n\n    # The default CSRF token expiration is 1 hour. Since large uploads can\n    # take longer than an hour over Tor, we increase the valid window to 24h.\n    app.config['WTF_CSRF_TIME_LIMIT'] = 60 * 60 * 24\n    CSRFProtect(app)\n\n    assets = Environment(app)\n    app.config['assets'] = assets\n\n    i18n.setup_app(app)\n\n    app.jinja_env.trim_blocks = True\n    app.jinja_env.lstrip_blocks = True\n    app.jinja_env.globals['version'] = version.__version__\n    if getattr(config, 'CUSTOM_HEADER_IMAGE', None):\n        app.jinja_env.globals['header_image'] = config.CUSTOM_HEADER_IMAGE\n        app.jinja_env.globals['use_custom_header_image'] = True\n    else:\n        app.jinja_env.globals['header_image'] = 'logo.png'\n        app.jinja_env.globals['use_custom_header_image'] = False\n\n    app.jinja_env.filters['rel_datetime_format'] = \\\n        template_filters.rel_datetime_format\n    app.jinja_env.filters['nl2br'] = evalcontextfilter(template_filters.nl2br)\n    app.jinja_env.filters['filesizeformat'] = template_filters.filesizeformat\n\n    for module in [main, info, api]:\n        app.register_blueprint(module.make_blueprint(config))\n\n    @app.before_request\n    @ignore_static\n    def check_tor2web():\n        # ignore_static here so we only flash a single message warning\n        # about Tor2Web, corresponding to the initial page load.\n        if 'X-tor2web' in request.headers:\n            flash(Markup(gettext(\n                '<strong>WARNING:</strong> You appear to be using Tor2Web. '\n                'This <strong>does not</strong> provide anonymity. '\n                '<a href=\"{url}\">Why is this dangerous?</a>')\n                .format(url=url_for('info.tor2web_warning'))),\n                  \"banner-warning\")\n\n    @app.before_request\n    @ignore_static\n    def setup_g():\n        \"\"\"Store commonly used values in Flask's special g object\"\"\"\n        g.locale = i18n.get_locale()\n        g.text_direction = i18n.get_text_direction(g.locale)\n        g.html_lang = i18n.locale_to_rfc_5646(g.locale)\n        g.locales = i18n.get_locale2name()\n\n        if 'expires' in session and datetime.utcnow() >= session['expires']:\n            session.clear()\n            msg = render_template('session_timeout.html')\n            flash(Markup(msg), \"important\")\n\n        session['expires'] = datetime.utcnow() + \\\n            timedelta(minutes=getattr(config,\n                                      'SESSION_EXPIRATION_MINUTES',\n                                      30))\n\n        # ignore_static here because `crypto_util.hash_codename` is scrypt\n        # (very time consuming), and we don't need to waste time running if\n        # we're just serving a static resource that won't need to access\n        # these common values.\n        if logged_in():\n            g.codename = session['codename']\n            g.filesystem_id = crypto_util.hash_codename(g.codename)\n            try:\n                g.source = Source.query \\\n                            .filter(Source.filesystem_id == g.filesystem_id) \\\n                            .one()\n            except NoResultFound as e:\n                app.logger.error(\n                    \"Found no Sources when one was expected: %s\" %\n                    (e,))\n                del session['logged_in']\n                del session['codename']\n                return redirect(url_for('main.index'))\n            g.loc = store.path(g.filesystem_id)\n\n    @app.teardown_appcontext\n    def shutdown_session(exception=None):\n        \"\"\"Automatically remove database sessions at the end of the request, or\n        when the application shuts down\"\"\"\n        db_session.remove()\n\n    @app.errorhandler(404)\n    def page_not_found(error):\n        return render_template('notfound.html'), 404\n\n    @app.errorhandler(500)\n    def internal_error(error):\n        return render_template('error.html'), 500\n\n    return app\n", "path": "securedrop/source_app/__init__.py"}], "after_files": [{"content": "from datetime import datetime, timedelta\nfrom flask import (Flask, render_template, flash, Markup, request, g, session,\n                   url_for, redirect)\nfrom flask_babel import gettext\nfrom flask_assets import Environment\nfrom flask_wtf.csrf import CSRFProtect\nfrom jinja2 import evalcontextfilter\nfrom os import path\nfrom sqlalchemy.orm.exc import NoResultFound\n\nimport crypto_util\nimport i18n\nimport store\nimport template_filters\nimport version\n\nfrom db import Source, db_session\nfrom request_that_secures_file_uploads import RequestThatSecuresFileUploads\nfrom source_app import main, info, api\nfrom source_app.decorators import ignore_static\nfrom source_app.utils import logged_in\n\n\ndef create_app(config):\n    app = Flask(__name__,\n                template_folder=config.SOURCE_TEMPLATES_DIR,\n                static_folder=path.join(config.SECUREDROP_ROOT, 'static'))\n    app.request_class = RequestThatSecuresFileUploads\n    app.config.from_object(config.SourceInterfaceFlaskConfig)\n\n    # The default CSRF token expiration is 1 hour. Since large uploads can\n    # take longer than an hour over Tor, we increase the valid window to 24h.\n    app.config['WTF_CSRF_TIME_LIMIT'] = 60 * 60 * 24\n    CSRFProtect(app)\n\n    assets = Environment(app)\n    app.config['assets'] = assets\n\n    i18n.setup_app(app)\n\n    app.jinja_env.trim_blocks = True\n    app.jinja_env.lstrip_blocks = True\n    app.jinja_env.globals['version'] = version.__version__\n    if getattr(config, 'CUSTOM_HEADER_IMAGE', None):\n        app.jinja_env.globals['header_image'] = config.CUSTOM_HEADER_IMAGE\n        app.jinja_env.globals['use_custom_header_image'] = True\n    else:\n        app.jinja_env.globals['header_image'] = 'logo.png'\n        app.jinja_env.globals['use_custom_header_image'] = False\n\n    app.jinja_env.filters['rel_datetime_format'] = \\\n        template_filters.rel_datetime_format\n    app.jinja_env.filters['nl2br'] = evalcontextfilter(template_filters.nl2br)\n    app.jinja_env.filters['filesizeformat'] = template_filters.filesizeformat\n\n    for module in [main, info, api]:\n        app.register_blueprint(module.make_blueprint(config))\n\n    @app.before_request\n    @ignore_static\n    def check_tor2web():\n        # ignore_static here so we only flash a single message warning\n        # about Tor2Web, corresponding to the initial page load.\n        if 'X-tor2web' in request.headers:\n            flash(Markup(gettext(\n                '<strong>WARNING:</strong> You appear to be using Tor2Web. '\n                'This <strong>does not</strong> provide anonymity. '\n                '<a href=\"{url}\">Why is this dangerous?</a>')\n                .format(url=url_for('info.tor2web_warning'))),\n                  \"banner-warning\")\n\n    @app.before_request\n    @ignore_static\n    def setup_g():\n        \"\"\"Store commonly used values in Flask's special g object\"\"\"\n        g.locale = i18n.get_locale()\n        g.text_direction = i18n.get_text_direction(g.locale)\n        g.html_lang = i18n.locale_to_rfc_5646(g.locale)\n        g.locales = i18n.get_locale2name()\n\n        if 'expires' in session and datetime.utcnow() >= session['expires']:\n            msg = render_template('session_timeout.html')\n\n            # clear the session after we render the message so it's localized\n            session.clear()\n\n            flash(Markup(msg), \"important\")\n\n        session['expires'] = datetime.utcnow() + \\\n            timedelta(minutes=getattr(config,\n                                      'SESSION_EXPIRATION_MINUTES',\n                                      30))\n\n        # ignore_static here because `crypto_util.hash_codename` is scrypt\n        # (very time consuming), and we don't need to waste time running if\n        # we're just serving a static resource that won't need to access\n        # these common values.\n        if logged_in():\n            g.codename = session['codename']\n            g.filesystem_id = crypto_util.hash_codename(g.codename)\n            try:\n                g.source = Source.query \\\n                            .filter(Source.filesystem_id == g.filesystem_id) \\\n                            .one()\n            except NoResultFound as e:\n                app.logger.error(\n                    \"Found no Sources when one was expected: %s\" %\n                    (e,))\n                del session['logged_in']\n                del session['codename']\n                return redirect(url_for('main.index'))\n            g.loc = store.path(g.filesystem_id)\n\n    @app.teardown_appcontext\n    def shutdown_session(exception=None):\n        \"\"\"Automatically remove database sessions at the end of the request, or\n        when the application shuts down\"\"\"\n        db_session.remove()\n\n    @app.errorhandler(404)\n    def page_not_found(error):\n        return render_template('notfound.html'), 404\n\n    @app.errorhandler(500)\n    def internal_error(error):\n        return render_template('error.html'), 500\n\n    return app\n", "path": "securedrop/source_app/__init__.py"}]}