middleware.py 6.65 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12
"""
Middleware for dark-launching languages. These languages won't be used
when determining which translation to give a user based on their browser
header, but can be selected by setting the ``preview-lang`` query parameter
to the language code.

Adding the query parameter ``clear-lang`` will reset the language stored
in the user's session.

This middleware must be placed before the LocaleMiddleware, but after
the SessionMiddleware.
"""
13
from django.conf import settings
14

15
from dark_lang import DARK_LANGUAGE_KEY
16
from dark_lang.models import DarkLangConfig
17 18 19
from openedx.core.djangoapps.user_api.preferences.api import (
    delete_user_preference, get_user_preference, set_user_preference
)
20
from lang_pref import LANGUAGE_KEY
21

22 23
from django.utils.translation.trans_real import parse_accept_lang_header
from django.utils.translation import LANGUAGE_SESSION_KEY
24

25

26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
def dark_parse_accept_lang_header(accept):
    '''
    The use of 'zh-cn' for 'Simplified Chinese' and 'zh-tw' for 'Traditional Chinese'
    are now deprecated, as discussed here: https://code.djangoproject.com/ticket/18419.
    The new language codes 'zh-hans' and 'zh-hant' are now used since django 1.7.
    Although majority of browsers still use the old language codes, some new browsers
    such as IE11 in Windows 8.1 start to use the new ones, which makes the current
    chinese translations of edX don't work properly under these browsers.
    This function can keep compatibility between the old and new language codes. If one
    day edX uses django 1.7 or higher, this function can be modified to support the old
    language codes until there are no browsers use them.
    '''
    browser_langs = parse_accept_lang_header(accept)
    django_langs = []
    for lang, priority in browser_langs:
        lang = CHINESE_LANGUAGE_CODE_MAP.get(lang.lower(), lang)
        django_langs.append((lang, priority))
43

44 45 46 47 48 49 50 51 52
    return django_langs

# If django 1.7 or higher is used, the right-side can be updated with new-style codes.
CHINESE_LANGUAGE_CODE_MAP = {
    # The following are the new-style language codes for chinese language
    'zh-hans': 'zh-CN',     # Chinese (Simplified),
    'zh-hans-cn': 'zh-CN',  # Chinese (Simplified, China)
    'zh-hans-sg': 'zh-CN',  # Chinese (Simplified, Singapore)
    'zh-hant': 'zh-TW',     # Chinese (Traditional)
53
    'zh-hant-hk': 'zh-HK',  # Chinese (Traditional, Hongkong)
54 55 56 57 58 59 60 61
    'zh-hant-mo': 'zh-TW',  # Chinese (Traditional, Macau)
    'zh-hant-tw': 'zh-TW',  # Chinese (Traditional, Taiwan)
    # The following are the old-style language codes that django does not recognize
    'zh-mo': 'zh-TW',       # Chinese (Traditional, Macau)
    'zh-sg': 'zh-CN',       # Chinese (Simplified, Singapore)
}


62 63 64 65
class DarkLangMiddleware(object):
    """
    Middleware for dark-launching languages.

66 67
    This is configured by creating ``DarkLangConfig`` rows in the database,
    using the django admin site.
68 69
    """

70 71 72 73 74
    @property
    def released_langs(self):
        """
        Current list of released languages
        """
75 76 77 78
        language_options = DarkLangConfig.current().released_languages_list
        if settings.LANGUAGE_CODE not in language_options:
            language_options.append(settings.LANGUAGE_CODE)
        return language_options
79 80

    def process_request(self, request):
81 82 83 84 85 86
        """
        Prevent user from requesting un-released languages except by using the preview-lang query string.
        """
        if not DarkLangConfig.current().enabled:
            return

87 88 89
        self._clean_accept_headers(request)
        self._activate_preview_language(request)

90 91 92 93 94 95 96 97 98 99 100
    def _fuzzy_match(self, lang_code):
        """Returns a fuzzy match for lang_code"""
        if lang_code in self.released_langs:
            return lang_code

        lang_prefix = lang_code.partition('-')[0]
        for released_lang in self.released_langs:
            released_prefix = released_lang.partition('-')[0]
            if lang_prefix == released_prefix:
                return released_lang
        return None
101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116

    def _format_accept_value(self, lang, priority=1.0):
        """
        Formats lang and priority into a valid accept header fragment.
        """
        return "{};q={}".format(lang, priority)

    def _clean_accept_headers(self, request):
        """
        Remove any language that is not either in ``self.released_langs`` or
        a territory of one of those languages.
        """
        accept = request.META.get('HTTP_ACCEPT_LANGUAGE', None)
        if accept is None or accept == '*':
            return

117 118 119 120 121 122 123
        new_accept = []
        for lang, priority in dark_parse_accept_lang_header(accept):
            fuzzy_code = self._fuzzy_match(lang.lower())
            if fuzzy_code:
                new_accept.append(self._format_accept_value(fuzzy_code, priority))

        new_accept = ", ".join(new_accept)
124 125 126 127 128 129

        request.META['HTTP_ACCEPT_LANGUAGE'] = new_accept

    def _activate_preview_language(self, request):
        """
        If the request has the get parameter ``preview-lang``,
130
        and that language doesn't appear in ``self.released_langs``,
131
        then set the session LANGUAGE_SESSION_KEY to that language.
132
        """
133
        auth_user = request.user.is_authenticated()
134
        if 'clear-lang' in request.GET:
135 136
            # delete the session language key (if one is set)
            if LANGUAGE_SESSION_KEY in request.session:
137
                del request.session[LANGUAGE_SESSION_KEY]
138 139 140 141 142 143 144 145

            if auth_user:
                # Reset user's dark lang preference to null
                delete_user_preference(request.user, DARK_LANGUAGE_KEY)
                # Get & set user's preferred language
                user_pref = get_user_preference(request.user, LANGUAGE_KEY)
                if user_pref:
                    request.session[LANGUAGE_SESSION_KEY] = user_pref
146
            return
147

148 149
        # Get the user's preview lang - this is either going to be set from a query
        # param `?preview-lang=xx`, or we may have one already set as a dark lang preference.
150
        preview_lang = request.GET.get('preview-lang', None)
151 152 153
        if not preview_lang and auth_user:
            # Get the request user's dark lang preference
            preview_lang = get_user_preference(request.user, DARK_LANGUAGE_KEY)
154

155
        # User doesn't have a dark lang preference, so just return
156 157 158
        if not preview_lang:
            return

159
        # Set the session key to the requested preview lang
160
        request.session[LANGUAGE_SESSION_KEY] = preview_lang
161 162 163 164 165

        # Make sure that we set the requested preview lang as the dark lang preference for the
        # user, so that the lang_pref middleware doesn't clobber away the dark lang preview.
        if auth_user:
            set_user_preference(request.user, DARK_LANGUAGE_KEY, preview_lang)