commit ed438d0c2b00f02c29b60e561d37f241f6a6a4b8
Author: Isis Lovecruft <isis(a)torproject.org>
Date: Wed Apr 16 16:22:40 2014 +0000
Move locate determination and translation code into separate module.
* ADD new bridgedb.translations module with the following methods:
- getFirstSupportedLang():
Essentially the same as the original HTTPServer.getAssumedChosenLang()
function, except that the determination of which languages are
supported uses _lang.getLangs() instead (this has been in the _lang
module for a long time, and was meant for that purpose).
- getLocaleFromHTTPRequest():
This is taken mostly from the original setLocaleFromRequestHeader()
(in the HTTPServer module) except that there was an extra function in
the EmailServer module, called getLocaleFromRequest() ― which was not
in use anywhere ― which included the parsing of '?lang=' HTTP GET/POST
arguments. The parsing of '?lang=' was taken from that function and
added to the original functionality of
setLocaleFromRequestHeader(). This fixes part of ticket #9678:
"'Select Language' button on bridges.tpo".
- getLocaleFromPlusAddr():
Exactly the same as the original in the EmailServer module.
- installTranslations():
Produces a gettext translation object, with fallback languages chained
to it in order of client preference.
- usingRTLLang():
Essentially the same as the original in the HTTPServer module, except
that the parsing of supported langs again uses the _lang.getLangs()
function, as above.
* FIXES part of #9678: "'Select Language' button to bridges.tpo"
---
lib/bridgedb/EmailServer.py | 29 ++--------
lib/bridgedb/HTTPServer.py | 94 +++-----------------------------
lib/bridgedb/translations.py | 122 ++++++++++++++++++++++++++++++++++++++++++
3 files changed, 132 insertions(+), 113 deletions(-)
diff --git a/lib/bridgedb/EmailServer.py b/lib/bridgedb/EmailServer.py
index f9f43bb..c58ec53 100644
--- a/lib/bridgedb/EmailServer.py
+++ b/lib/bridgedb/EmailServer.py
@@ -30,6 +30,7 @@ from bridgedb.Dist import BadEmail, TooSoonEmail, IgnoreEmail
from bridgedb import Dist
from bridgedb import I18n
from bridgedb import safelog
+from bridgedb import translations
from bridgedb.Filters import filterBridgesByIP6
from bridgedb.Filters import filterBridgesByIP4
from bridgedb.Filters import filterBridgesByTransport
@@ -94,8 +95,8 @@ def getMailResponse(lines, ctx):
# Look up the locale part in the 'To:' address, if there is one and get
# the appropriate Translation object
- lang = getLocaleFromPlusAddr(clientToaddr)
- t = I18n.getLang(lang)
+ lang = translations.getLocaleFromPlusAddr(clientToaddr)
+ t = translations.installTranslations(lang)
try:
_, addrdomain = Dist.extractAddrSpec(clientAddr.lower())
@@ -287,30 +288,6 @@ def replyToMail(lines, ctx):
reactor.connectTCP(ctx.smtpServer, ctx.smtpPort, factory)
return d
-def getLocaleFromPlusAddr(address):
- """See whether the user sent his email to a 'plus' address, for
- instance to bridgedb+fa@tpo. Plus addresses are the current
- mechanism to set the reply language
- """
- replyLocale = "en"
- r = '.*(<)?(\w+\+(\w+)@\w+(?:\.\w+)+)(?(1)>)'
- match = re.match(r, address)
- if match:
- replyLocale = match.group(3)
-
- return replyLocale
-
-def getLocaleFromRequest(request):
- # See if we did get a request for a certain locale, otherwise fall back
- # to 'en':
- # Try evaluating the path /foo first, then check if we got a ?lang=foo
- default_lang = lang = "en"
- if len(request.path) > 1:
- lang = request.path[1:]
- if lang == default_lang:
- lang = request.args.get("lang", [default_lang])
- lang = lang[0]
- return I18n.getLang(lang)
class MailContext(object):
"""Helper object that holds information used by email subsystem."""
diff --git a/lib/bridgedb/HTTPServer.py b/lib/bridgedb/HTTPServer.py
index 5e3ba37..79772c3 100644
--- a/lib/bridgedb/HTTPServer.py
+++ b/lib/bridgedb/HTTPServer.py
@@ -35,6 +35,7 @@ import bridgedb.I18n as I18n
from bridgedb import captcha
from bridgedb import crypto
+from bridgedb import translations
from bridgedb import txrecaptcha
from bridgedb.Filters import filterBridgesByIP4
from bridgedb.Filters import filterBridgesByIP6
@@ -541,9 +542,10 @@ class WebResourceOptions(resource.Resource):
def render_GET(self, request):
rtl = False
+ langs = translations.getLocaleFromHTTPRequest(request)
try:
- rtl = usingRTLLang(request)
+ rtl = translations.usingRTLLang(langs)
except Exception as err: # pragma: no cover
logging.exception(err)
@@ -664,7 +666,8 @@ class WebResourceBridges(resource.Resource):
if countryCode:
logging.debug("Client request from GeoIP CC: %s" % countryCode)
- rtl = usingRTLLang(request)
+ langs = translations.getLocaleFromHTTPRequest(request)
+ rtl = translations.usingRTLLang(langs)
if rtl:
logging.debug("Rendering RTL response.")
@@ -784,9 +787,10 @@ class WebRoot(resource.Resource):
:param request: An incoming request.
"""
rtl = False
+ langs = translations.getLocaleFromHTTPRequest(request)
try:
- rtl = usingRTLLang(request)
+ rtl = translations.usingRTLLang(langs)
except Exception as err:
logging.exception(err)
logging.error("The gettext files were not properly installed.")
@@ -883,87 +887,3 @@ def addWebServer(cfg, dist, sched):
raise SystemExit(error)
return site
-
-def usingRTLLang(request):
- """Check if we should translate the text into a RTL language
-
- Retrieve the headers from the request. Obtain the Accept-Language header
- and decide if we need to translate the text. Install the requisite
- languages via gettext, if so. Then, manually check which languages we
- support. Choose the first language from the header that we support and
- return True if it is a RTL language, else return False.
-
- :type request: :api:`twisted.web.server.Request`
- :param request: An incoming request.
- :rtype: bool
- :returns: ``True`` if the preferred language is right-to-left; ``False``
- otherwise.
- """
- langs = setLocaleFromRequestHeader(request)
-
- # Grab only the language (first two characters) so we know if the language
- # is read right-to-left
- #langs = [ lang[:2] for lang in langs ]
- lang = getAssumedChosenLang(langs)
- if lang in rtl_langs:
- return True
- return False
-
-def getAssumedChosenLang(langs):
- """Return the first language in **langs** that we support.
-
- :param list langs: All requested languages
- :rtype: str
- :returns: A country code for the client's preferred language.
- """
- i18npath = os.path.join(os.path.dirname(__file__), 'i18n')
- path = filepath.FilePath(i18npath)
- assert path.isdir()
-
- lang = 'en-US'
- supp_langs = path.listdir() + ['en']
- for l in langs:
- if l in supp_langs:
- lang = l
- break
- return lang
-
-def setLocaleFromRequestHeader(request):
- """Retrieve the languages from the accept-language header and install them.
-
- Parse the languages in the header, and attempt to install the first one in
- the list. If that fails, we receive a :class:`gettext.NullTranslation`
- object, if it worked then we have a :class:`gettext.GNUTranslation`
- object. Whichever one we end up with, get the other languages and add them
- as fallbacks to the first. Lastly, install this chain of translations.
-
- :type request: :api:`twisted.web.server.Request`
- :param request: An incoming request from a client.
- :rtype: list
- :returns: All requested languages.
- """
- logging.debug("Getting client 'Accept-Language' header...")
- header = request.getHeader('accept-language')
-
- if header is None:
- logging.debug("Client sent no 'Accept-Language' header. Using fallback.")
- header = 'en,en-US'
-
- localedir = os.path.join(os.path.dirname(__file__), 'i18n/')
- langs = headers.parseAcceptLanguage(header)
- ## XXX the 'Accept-Language' header is potentially identifying
- logging.debug("Client Accept-Language (top 5): %s" % langs[:5])
-
- try:
- language = gettext.translation("bridgedb", localedir=localedir,
- languages=langs, fallback=True)
- for lang in langs:
- language.add_fallback(gettext.translation("bridgedb",
- localedir=localedir,
- languages=langs,
- fallback=True))
- except IOError as error:
- logging.error(error.message)
-
- language.install(unicode=True)
- return langs
diff --git a/lib/bridgedb/translations.py b/lib/bridgedb/translations.py
new file mode 100644
index 0000000..3085938
--- /dev/null
+++ b/lib/bridgedb/translations.py
@@ -0,0 +1,122 @@
+# -*- coding: utf-8 ; test-case-name: bridgedb.test.test_translations -*-
+#
+# This file is part of BridgeDB, a Tor bridge distribution system.
+#
+# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis(a)torproject.org>
+# :copyright: (c) 2013-2014, Isis Lovecruft
+# (c) 2007-2014, The Tor Project, Inc.
+# :license: 3-Clause BSD, see LICENSE for licensing information
+
+import gettext
+import logging
+import os
+import re
+
+from bridgedb import _langs
+from bridgedb import safelog
+from bridgedb.parse import headers
+
+
+TRANSLATIONS_DIR = os.path.join(os.path.dirname(__file__), 'i18n')
+
+
+def getFirstSupportedLang(langs):
+ """Return the first language in **langs** that we support.
+
+ :param list langs: All requested languages
+ :rtype: str
+ :returns: A country code for the client's preferred language.
+ """
+ lang = 'en-US'
+ supported = _langs.get_langs()
+
+ for l in langs:
+ if l in supported:
+ lang = l
+ break
+ return lang
+
+def getLocaleFromHTTPRequest(request):
+ """Retrieve the languages from an HTTP ``Accept-Language:`` header.
+
+ Parse the languages from the header, use them to install a
+ ``gettext.translation`` chain via :func:`installTranslations`, and lastly
+ return the requested languages.
+
+ :type request: :api:`twisted.web.server.Request`
+ :param request: An incoming request from a client.
+ :rtype: list
+ :returns: All requested languages.
+ """
+ header = request.getHeader('accept-language')
+ if header is None:
+ logging.debug("Client sent no 'Accept-Language' header. Using fallback.")
+ header = 'en,en-US'
+
+ langs = headers.parseAcceptLanguage(header)
+ if not safelog.safe_logging: # pragma: no cover
+ logging.debug("Client Accept-Language (top 5): %s" % langs[:5])
+
+ # Check if we got a ?lang=foo argument, and if we did, insert it first
+ chosenLang = request.args.get("lang", [None,])[0]
+ if chosenLang:
+ logging.debug("Client requested language: %r" % chosenLang)
+ langs.insert(0, chosenLang)
+
+ installTranslations(langs)
+ return langs
+
+def getLocaleFromPlusAddr(address):
+ """See whether the user sent his email to a 'plus' address, for instance to
+ bridges+fa(a)bridges.torproject.org. Plus addresses are the current
+ mechanism to set the reply language.
+ """
+ replyLocale = "en"
+ r = '.*(<)?(\w+\+(\w+)@\w+(?:\.\w+)+)(?(1)>)'
+ match = re.match(r, address)
+ if match:
+ replyLocale = match.group(3)
+
+ return replyLocale
+
+def installTranslations(langs):
+ """Create a ``gettext.translation`` chain for all **langs**.
+
+ Attempt to install the first language in the **langs** list. If that
+ fails, we receive a ``gettext.NullTranslation`` object, and if it worked
+ then we have a ``gettext.GNUTranslation`` object. Whichever one we end up
+ with, get the other languages and add them as fallbacks to the
+ first. Lastly, install this chain of translations.
+
+ :param list langs: A list of language codes.
+ :returns: A ``gettext.NullTranslation`` or ``gettext.GNUTranslation`` with
+ fallback languages set.
+ """
+ try:
+ language = gettext.translation("bridgedb", localedir=TRANSLATIONS_DIR,
+ languages=langs, fallback=True)
+ for lang in langs:
+ language.add_fallback(
+ gettext.translation("bridgedb", localedir=TRANSLATIONS_DIR,
+ languages=langs, fallback=True))
+ except IOError as error:
+ logging.error(error.message)
+
+ language.install(unicode=True)
+ return language
+
+def usingRTLLang(langs):
+ """Check if we should translate the text into a RTL language.
+
+ Choose the first language from the **langs** list that we support and
+ return True if it is a RTL language, else return False.
+
+ :param list langs: An incoming request.
+ :rtype: bool
+ :returns: ``True`` if the preferred language is right-to-left; ``False``
+ otherwise.
+ """
+ lang = getFirstSupportedLang(langs)
+ if lang in _langs.RTL_LANGS:
+ return True
+ return False