Merge branch 'release-0.11.0'
[tor-bridgedb.git] / bridgedb / translations.py
blobb6a9ef3db38f23cc08f60765139884f19abf4908
1 # -*- coding: utf-8 ; test-case-name: bridgedb.test.test_translations -*-
3 # This file is part of BridgeDB, a Tor bridge distribution system.
5 # :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis@torproject.org>
6 # :copyright: (c) 2013-2017, Isis Lovecruft
7 # (c) 2007-2017, The Tor Project, Inc.
8 # :license: 3-Clause BSD, see LICENSE for licensing information
10 import gettext
11 import logging
12 import os
13 import re
15 import babel.core
17 from bridgedb import _langs
18 from bridgedb import safelog
19 from bridgedb.parse import headers
22 TRANSLATIONS_DIR = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'i18n')
25 def isLangOverridden(request):
26 """
27 Return True if the `lang' HTTP GET argument is set in the given request.
29 :type request: :api:`twisted.web.server.Request`
30 :param request: An incoming request from a client.
31 :rtype: bool
32 :returns: ``True`` if the given request has a `lang` argument and ``False``
33 otherwise.
34 """
36 return request.args.get("lang", [None])[0] is not None
38 def getSupportedLangs():
39 """Return all supported languages.
41 :rtype: set
42 :returns: A set of language locales, e.g.: set(['el', 'eo', ..., ]).
43 """
45 return _langs.get_langs()
47 def getFirstSupportedLang(langs):
48 """Return the first language in **langs** that we support.
50 :param list langs: All requested languages
51 :rtype: str
52 :returns: A country code for the client's preferred language.
53 """
54 lang = 'en-US'
55 supported = _langs.get_langs()
57 for l in langs:
58 if l in supported:
59 lang = l
60 break
62 # crop locales (like 'en-US') to just the language
64 return lang.split('-')[0]
66 def getLocaleFromHTTPRequest(request):
67 """Retrieve the languages from an HTTP ``Accept-Language:`` header.
69 Parse the languages from the header, use them to install a
70 ``gettext.translation`` chain via :func:`installTranslations`, and lastly
71 return the requested languages.
73 :type request: :api:`twisted.web.server.Request`
74 :param request: An incoming request from a client.
75 :rtype: list
76 :returns: All requested languages.
77 """
78 header = request.getHeader('accept-language')
79 if header is None:
80 logging.debug("Client sent no 'Accept-Language' header. Using fallback.")
81 header = 'en,en-US'
83 langs = list(headers.parseAcceptLanguage(header))
84 if not safelog.safe_logging: # pragma: no cover
85 logging.debug("Client Accept-Language (top 5): %s" % langs[:5])
87 # Check if we got a ?lang=foo argument, and if we did, insert it first
88 chosenLang = request.args.get("lang", [None,])[0]
89 if chosenLang:
90 logging.debug("Client requested language: %r" % chosenLang)
91 langs.insert(0, chosenLang)
93 # normalize languages to be unicode
95 langs = list(map(lambda l: l if isinstance(l, str) else l.decode('utf-8'), langs))
97 installTranslations(langs)
98 return langs
100 def getLocaleFromPlusAddr(address):
101 """See whether the user sent his email to a 'plus' address, for instance to
102 bridges+fa@bridges.torproject.org. Plus addresses are the current
103 mechanism to set the reply language.
105 replyLocale = "en"
106 r = '.*(<)?(\w+\+(\w+)@\w+(?:\.\w+)+)(?(1)>)'
107 match = re.match(r, address)
108 if match:
109 replyLocale = match.group(3)
111 return replyLocale
113 def installTranslations(langs):
114 """Create a ``gettext.translation`` chain for all **langs**.
116 Attempt to install the first language in the **langs** list. If that
117 fails, we receive a ``gettext.NullTranslation`` object, and if it worked
118 then we have a ``gettext.GNUTranslation`` object. Whichever one we end up
119 with, get the other languages and add them as fallbacks to the
120 first. Lastly, install this chain of translations.
122 :param list langs: A list of language codes.
123 :returns: A ``gettext.NullTranslation`` or ``gettext.GNUTranslation`` with
124 fallback languages set.
126 try:
127 language = gettext.translation("bridgedb", localedir=TRANSLATIONS_DIR,
128 languages=langs, fallback=True)
129 for lang in langs:
130 language.add_fallback(
131 gettext.translation("bridgedb", localedir=TRANSLATIONS_DIR,
132 languages=langs, fallback=True))
133 except IOError as error:
134 logging.error(str(error))
136 language.install()
137 return language
139 def usingRTLLang(langs):
140 """Check if we should translate the text into a RTL language.
142 Choose the first language from the **langs** list that we support and
143 return True if it is a RTL language, else return False.
145 :param list langs: An incoming request.
146 :rtype: bool
147 :returns: ``True`` if the preferred language is right-to-left; ``False``
148 otherwise.
151 lang = getFirstSupportedLang(langs)
153 try:
154 return babel.core.Locale.parse(lang).text_direction == "rtl"
155 except ValueError as err:
156 logging.warning("Couldn't parse locale %s: %s" % (lang, err))
157 return False