[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]
[tor-commits] [bridgedb/develop] Add a language switcher to BridgeDB's web UI.
commit 166e899529784cb979ea9517066783c492a2f143
Author: Philipp Winter <phw@xxxxxxxxx>
Date: Mon Sep 30 16:06:18 2019 -0700
Add a language switcher to BridgeDB's web UI.
So far, BridgeDB looked at the user's Accept-Language request header to
decide what language to use in its web interface. Not everybody likes
that, so we should provide an option to override this behaviour. This
patch adds a language switcher to BridgeDB's web interface. It sits at
the top right and lets the user choose their language.
Some implementation considerations:
* The patch uses BridgeDB's "lang" HTTP GET argument to pass the chosen
language from one page to another. This allows us to avoid cookies.
* We allow the user to pick any language that BridgeDB supports,
regardless of how complete the translations are.
* Each language in the language switcher is translated to the respective
language, i.e., it says "español" instead of "spanish".
This patch fixes <https://bugs.torproject.org/26543>.
---
CHANGELOG | 7 +++
bridgedb/distributors/https/server.py | 55 +++++++++++++++++++++-
.../https/templates/assets/css/main.css | 6 +++
bridgedb/distributors/https/templates/base.html | 17 ++++++-
bridgedb/distributors/https/templates/bridges.html | 14 ++++--
bridgedb/distributors/https/templates/captcha.html | 2 +-
bridgedb/distributors/https/templates/howto.html | 2 +-
bridgedb/distributors/https/templates/index.html | 12 ++++-
bridgedb/distributors/https/templates/options.html | 9 +++-
bridgedb/test/test_https_server.py | 13 +++++
bridgedb/translations.py | 22 +++++++++
11 files changed, 148 insertions(+), 11 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index be4c6d2..4fe1afc 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,10 @@
+Changes in version A.B.C - YYYY-MM-DD
+
+ * FIXES https://bugs.torproject.org/26543
+ Implement a language switcher that allows users to override the locale
+ that BridgeDB automatically selects by inspecting the client's request
+ headers.
+
Changes in version 0.8.3 - 2019-10-03
* FIXES https://bugs.torproject.org/31903
diff --git a/bridgedb/distributors/https/server.py b/bridgedb/distributors/https/server.py
index 81bc353..0fb8014 100644
--- a/bridgedb/distributors/https/server.py
+++ b/bridgedb/distributors/https/server.py
@@ -28,6 +28,7 @@ import random
import re
import time
import os
+import operator
from functools import partial
@@ -37,6 +38,8 @@ import mako.exceptions
from mako.template import Template
from mako.lookup import TemplateLookup
+import babel.core
+
from twisted.internet import defer
from twisted.internet import reactor
from twisted.internet import task
@@ -87,6 +90,9 @@ logging.debug("Set template root to %s" % TEMPLATE_DIR)
#: Localisations which BridgeDB supports which should be rendered right-to-left.
rtl_langs = ('ar', 'he', 'fa', 'gu_IN', 'ku')
+#: A list of supported language tuples. Use getSortedLangList() to read this variable.
+supported_langs = []
+
# We use our metrics singleton to keep track of BridgeDB metrics such as
# "number of failed HTTPS bridge requests."
metrix = metrics.HTTPSMetrics()
@@ -156,6 +162,45 @@ def redirectMaliciousRequest(request):
return request
+def getSortedLangList(rebuild=False):
+ """
+ Build and return a list of tuples that contains all of BridgeDB's supported
+ languages, e.g.: [("az", "AzÉ?rbaycan"), ("ca", "Català "), ..., ].
+
+ :param rebuild bool: Force a rebuild of ``supported_langs`` if the argument
+ is set to ``True``. The default is ``False``.
+ :rtype: list
+ :returns: A list of tuples of the form (language-locale, language). The
+ list is sorted alphabetically by language. We use this list to
+ provide a language switcher in BridgeDB's web interface.
+ """
+
+ # If we already compiled our languages, return them right away.
+ global supported_langs
+ if supported_langs and not rebuild:
+ return supported_langs
+ logging.debug("Building supported languages for language switcher.")
+
+ langDict = {}
+ for l in translations.getSupportedLangs():
+
+ # We don't support 'en_GB', and 'en' and 'en_US' are the same. 'zh_HK'
+ # is very similar to 'zh_TW' and we also lack translators for it, so we
+ # drop the locale: <https://bugs.torproject.org/26543#comment:17>
+ if l in ("en_GB", "en_US", "zh_HK"):
+ continue
+
+ try:
+ langDict[l] = "%s" % (babel.core.Locale.parse(l).display_name.capitalize())
+ except Exception as err:
+ logging.warning("Failed to create language switcher option for %s: %s" % (l, err))
+
+ # Sort languages alphabetically.
+ supported_langs = sorted(langDict.items(), key=operator.itemgetter(1))
+
+ return supported_langs
+
+
class MaliciousRequest(Exception):
"""Raised when we received a possibly malicious request."""
@@ -345,7 +390,11 @@ class TranslatedTemplateResource(CustomErrorHandlingResource, CSPResource):
langs = translations.getLocaleFromHTTPRequest(request)
rtl = translations.usingRTLLang(langs)
template = lookup.get_template(self.template)
- rendered = template.render(strings, rtl=rtl, lang=langs[0])
+ rendered = template.render(strings,
+ getSortedLangList(),
+ rtl=rtl,
+ lang=langs[0],
+ langOverride=translations.isLangOverridden(request))
except Exception as err: # pragma: no cover
rendered = replaceErrorPage(request, err)
request.setHeader("Content-Type", "text/html; charset=utf-8")
@@ -469,8 +518,10 @@ class CaptchaProtectedResource(CustomErrorHandlingResource, CSPResource):
imgstr = 'data:image/jpeg;base64,%s' % base64.b64encode(image)
template = lookup.get_template('captcha.html')
rendered = template.render(strings,
+ getSortedLangList(),
rtl=rtl,
lang=langs[0],
+ langOverride=translations.isLangOverridden(request),
imgstr=imgstr,
challenge_field=challenge)
except Exception as err:
@@ -994,8 +1045,10 @@ class BridgesResource(CustomErrorHandlingResource, CSPResource):
rtl = translations.usingRTLLang(langs)
template = lookup.get_template('bridges.html')
rendered = template.render(strings,
+ getSortedLangList(),
rtl=rtl,
lang=langs[0],
+ langOverride=translations.isLangOverridden(request),
answer=bridgeLines,
qrcode=qrcode)
except Exception as err:
diff --git a/bridgedb/distributors/https/templates/assets/css/main.css b/bridgedb/distributors/https/templates/assets/css/main.css
index 2b06a40..72a3205 100644
--- a/bridgedb/distributors/https/templates/assets/css/main.css
+++ b/bridgedb/distributors/https/templates/assets/css/main.css
@@ -439,3 +439,9 @@ div.bridge-lines.-webkit-scrollbar-thumb.horizontal{
}
@media (min-width: 1px) and (max-width: 480px), handheld {
}
+
+.dropdown:hover .dropdown-menu {
+ display: block;
+ height: 350px;
+ overflow: auto;
+}
diff --git a/bridgedb/distributors/https/templates/base.html b/bridgedb/distributors/https/templates/base.html
index 423b43c..00997b2 100644
--- a/bridgedb/distributors/https/templates/base.html
+++ b/bridgedb/distributors/https/templates/base.html
@@ -1,7 +1,7 @@
## -*- coding: utf-8 -*-
<%namespace name="base" file="base.html" inheritable="True"/>
-<%page args="strings, rtl=False, lang='en', **kwargs"/>
+<%page args="strings, langs, rtl=False, lang='en', langOverride=False, **kwargs"/>
<!DOCTYPE html>
<html lang="${lang}">
@@ -37,6 +37,19 @@
<a class="navbar-brand" href="../">BridgeDB</a>
</div>
<ul class="nav navbar-nav pull-right">
+ <li class="dropdown">
+ <a href="#" class="dropdown-toggle" role="button">
+ ${_("Language")}<span class="caret"></span>
+ </a>
+ <ul class="dropdown-menu">
+ % for tuple in langs:
+ <li>
+ <a href="?lang=${tuple[0]}">${tuple[1]} (${tuple[0]})</a>
+ </li>
+ % endfor
+ </ul>
+ </li>
+
<li>
<a href="https://www.torproject.org">The Tor Project</a>
</li>
@@ -44,7 +57,7 @@
</div>
</div>
-${next.body(strings, rtl=rtl, lang=lang, **kwargs)}
+${next.body(strings, langs, rtl=rtl, lang=lang, langOverride=langOverride, **kwargs)}
<div class="faq">
<div class="row-fluid marketing">
diff --git a/bridgedb/distributors/https/templates/bridges.html b/bridgedb/distributors/https/templates/bridges.html
index 076f930..a7503f5 100644
--- a/bridgedb/distributors/https/templates/bridges.html
+++ b/bridgedb/distributors/https/templates/bridges.html
@@ -1,7 +1,7 @@
## -*- coding: utf-8 -*-
<%inherit file="base.html"/>
-<%page args="strings, rtl=False, lang='en', answer=0, qrcode=0, **kwargs"/>
+<%page args="strings, langs, rtl=False, lang='en', langOverride=False, answer=0, qrcode=0, **kwargs"/>
</div>
@@ -129,9 +129,15 @@ ${_("""Uh oh, spaghettios!""")}
</p>
<p>
${_("""There currently aren't any bridges available...""")}
- ${_(""" Perhaps you should try %s going back %s and choosing a""" \
- """ different bridge type!""") % \
- ("""<a class="alert-link" href="options">""", """</a>""")}
+ % if langOverride:
+ ${_(""" Perhaps you should try %s going back %s and choosing a""" \
+ """ different bridge type!""") % \
+ ("""<a class="alert-link" href="options?lang="""+lang+""">""", """</a>""")}
+ % else:
+ ${_(""" Perhaps you should try %s going back %s and choosing a""" \
+ """ different bridge type!""") % \
+ ("""<a class="alert-link" href="options">""", """</a>""")}
+ % endif
</p>
</div>
</div>
diff --git a/bridgedb/distributors/https/templates/captcha.html b/bridgedb/distributors/https/templates/captcha.html
index 33c1d45..1faed49 100644
--- a/bridgedb/distributors/https/templates/captcha.html
+++ b/bridgedb/distributors/https/templates/captcha.html
@@ -1,7 +1,7 @@
## -*- coding: utf-8 -*-
<%inherit file="base.html"/>
-<%page args="strings, rtl=False, lang='en', imgstr=0, captcha_challenge=0, **kwargs"/>
+<%page args="strings, langs, rtl=False, lang='en', langOverride=False, imgstr=0, captcha_challenge=0, **kwargs"/>
<div class="container-narrow" id="captcha-submission-container">
<div class="container-fluid container-fluid-inner-5">
diff --git a/bridgedb/distributors/https/templates/howto.html b/bridgedb/distributors/https/templates/howto.html
index 24e4980..70fca6a 100644
--- a/bridgedb/distributors/https/templates/howto.html
+++ b/bridgedb/distributors/https/templates/howto.html
@@ -1,7 +1,7 @@
## -*- coding: utf-8 -*-
<%inherit file="base.html"/>
-<%page args="strings, rtl=False, lang='en', **kwargs"/>
+<%page args="strings, langs, rtl=False, lang='en', langOverride=False, **kwargs"/>
<br />
diff --git a/bridgedb/distributors/https/templates/index.html b/bridgedb/distributors/https/templates/index.html
index 269b2ae..2752c5b 100644
--- a/bridgedb/distributors/https/templates/index.html
+++ b/bridgedb/distributors/https/templates/index.html
@@ -1,7 +1,7 @@
## -*- coding: utf-8 -*-
<%inherit file="base.html"/>
-<%page args="strings, rtl=False, lang='en', **kwargs"/>
+<%page args="strings, langs, rtl=False, lang='en', langOverride=False, **kwargs"/>
<div class="main-steps">
<div class="step row" id="step-1">
@@ -24,7 +24,11 @@
<span class="step-title">
${_("Step %s2%s") % ("""<u>""", """</u>""")}</span>
<span class="step-text">
+ % if langOverride:
+ ${_("Get %s bridges %s") % ("""<a href="/options?lang="""+lang+"""" accesskey="2">""", "</a>")}</span>
+ % else:
${_("Get %s bridges %s") % ("""<a href="/options" accesskey="2">""", "</a>")}</span>
+ % endif
</span>
</div>
</div>
@@ -35,9 +39,15 @@
<span class="step-title">
${_("Step %s3%s") % ("""<u>""", """</u>""")}</span>
<span class="step-text">
+ % if langOverride:
+ ${_("""Now %s add the bridges to Tor Browser %s""") % \
+ ("""<a href="/howto?lang="""+lang+"""" accesskey="3">""",
+ """</a>""")}</span>
+ % else:
${_("""Now %s add the bridges to Tor Browser %s""") % \
("""<a href="/howto" accesskey="3">""",
"""</a>""")}</span>
+ % endif
</span>
</div>
</div>
diff --git a/bridgedb/distributors/https/templates/options.html b/bridgedb/distributors/https/templates/options.html
index 040d523..b9ae948 100644
--- a/bridgedb/distributors/https/templates/options.html
+++ b/bridgedb/distributors/https/templates/options.html
@@ -1,7 +1,7 @@
## -*- coding: utf-8 -*-
<%inherit file="base.html"/>
-<%page args="strings, rtl=False, lang='en', **kwargs"/>
+<%page args="strings, langs, rtl=False, lang='en', langOverride=False, **kwargs"/>
<div class="container-fluid container-fluid-outer-96">
<!--<div class="container-fluid step-semi-transparent">-->
@@ -26,7 +26,11 @@
<div class="container-fluid container-fluid-outer">
<div class="container-fluid-inner-5">
<p class="bs-component">
+ % if langOverride:
+ <a href="./bridges?lang=${lang}">
+ % else:
<a href="./bridges">
+ % endif
<button class="btn btn-success btn-lg btn-block"
id="just-give-me-bridges-btn"
type="button"
@@ -54,6 +58,9 @@
<!-- BEGIN bridge options selection form -->
<form class="form-horizontal" id="advancedOptions" action="bridges" method="GET">
<fieldset>
+ % if langOverride:
+ <input type="hidden" id="lang" name="lang" value="${lang}">
+ % endif
<div class="container-fluid" id="instructions">
<legend id="advanced-options-legend">
<br />
diff --git a/bridgedb/test/test_https_server.py b/bridgedb/test/test_https_server.py
index d68b880..945ea06 100644
--- a/bridgedb/test/test_https_server.py
+++ b/bridgedb/test/test_https_server.py
@@ -27,6 +27,7 @@ from twisted.trial import unittest
from twisted.web.resource import Resource
from twisted.web.test import requesthelper
+from bridgedb import translations
from bridgedb.distributors.https import server
from bridgedb.schedule import ScheduledInterval
@@ -43,6 +44,18 @@ logging.disable(50)
#server.logging.getLogger().setLevel(10)
+class GetSortedLangListTests(unittest.TestCase):
+ """Tests for :func:`bridgedb.distributors.https.server.getSortedLangList`."""
+
+ def test_getSortedLangList(self):
+ """getSortedLangList should return a list of tuples containing sorted
+ locales and languages."""
+ origFunc = translations.getSupportedLangs
+ translations.getSupportedLangs = lambda: ["en", "de"]
+ l = server.getSortedLangList(rebuild=True)
+ self.assertEqual(l, [("de", u"Deutsch"), ("en", u"English")])
+ translations.getSupportedLangs = origFunc
+
class ReplaceErrorPageTests(unittest.TestCase):
"""Tests for :func:`bridgedb.distributors.https.server.replaceErrorPage`."""
diff --git a/bridgedb/translations.py b/bridgedb/translations.py
index 7429b60..447e808 100644
--- a/bridgedb/translations.py
+++ b/bridgedb/translations.py
@@ -20,6 +20,28 @@ from bridgedb.parse import headers
TRANSLATIONS_DIR = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'i18n')
+def isLangOverridden(request):
+ """
+ Return True if the `lang' HTTP GET argument is set in the given request.
+
+ :type request: :api:`twisted.web.server.Request`
+ :param request: An incoming request from a client.
+ :rtype: bool
+ :returns: ``True`` if the given request has a `lang` argument and ``False``
+ otherwise.
+ """
+
+ return request.args.get("lang", [None])[0] is not None
+
+def getSupportedLangs():
+ """Return all supported languages.
+
+ :rtype: set
+ :returns: A set of language locales, e.g.: set(['el', 'eo', ..., ]).
+ """
+
+ return _langs.get_langs()
+
def getFirstSupportedLang(langs):
"""Return the first language in **langs** that we support.
_______________________________________________
tor-commits mailing list
tor-commits@xxxxxxxxxxxxxxxxxxxx
https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-commits