How to decide the language from cookies/headers/session in webapp2?

后端 未结 2 419
既然无缘
既然无缘 2021-01-05 06:06

I\'d like to take advantage of webapp2\'s new features for localization that also has locale-specific formatting for time and currency.

Django has a good function c

相关标签:
2条回答
  • 2021-01-05 06:13

    Here's what I do - I have a base request handler that all my request handlers inherit from, then in here I have a constant that contains the available languages, and I override the init method to set the language on each request:

    import webapp2
    from webapp2_extras import i18n
    
    AVAILABLE_LOCALES = ['en_GB', 'es_ES']
    
    class BaseHandler(webapp2.RequestHandler):
        def __init__(self, request, response):
            """ Override the initialiser in order to set the language.
            """
            self.initialize(request, response)
    
            # first, try and set locale from cookie
            locale = request.cookies.get('locale')
            if locale in AVAILABLE_LOCALES:
                i18n.get_i18n().set_locale(locale)
            else:
                # if that failed, try and set locale from accept language header
                header = request.headers.get('Accept-Language', '')  # e.g. en-gb,en;q=0.8,es-es;q=0.5,eu;q=0.3
                locales = [locale.split(';')[0] for locale in header.split(',')]
                for locale in locales:
                    if locale in AVAILABLE_LOCALES:
                        i18n.get_i18n().set_locale(locale)
                        break
                else:
                    # if still no locale set, use the first available one
                    i18n.get_i18n().set_locale(AVAILABLE_LOCALES[0])
    

    First I check the cookie, then the header, finally defaulting to the first available language if a valid one wasn't found.

    To set the cookie, I have a separate controller that looks something like this:

    import base
    
    class Index(base.BaseHandler):
        """ Set the language cookie (if locale is valid), then redirect back to referrer
        """
        def get(self, locale):
            if locale in self.available_locales:
                self.response.set_cookie('locale', locale, max_age = 15724800)  # 26 weeks' worth of seconds
    
            # redirect to referrer or root
            url = self.request.headers.get('Referer', '/')
            self.redirect(url)
    

    So a URL like www.example.com/locale/en_GB would change the locale to en_GB, setting the cookie and returning to the referrer (this has the advantage of being able to switch languages on any page, and have it stay on the same page).

    This method does not take into account partial matches for locales in the header, for instance "en" instead of "en_GB", but seeing as the list of languages I have enabled in the app is fixed (and the locale change URLs are hard-coded in the footer), I'm not too worried about it.

    HTH

    0 讨论(0)
  • 2021-01-05 06:37

    Totally based on fishwebby's answer and with some improvements and some design changes, here's what I do:

    """
    Use this handler instead of webapp2.RequestHandler to support localization.
    Fill the AVAILABLE_LOCALES tuple with the acceptable locales.
    """
    
    
    __author__ = 'Cristian Perez <http://cpr.name>'
    
    
    import webapp2
    from webapp2_extras import i18n
    
    
    AVAILABLE_LOCALES = ('en_US', 'es_ES', 'en', 'es')
    
    
    class LocalizedHandler(webapp2.RequestHandler):
    
        def set_locale_from_param(self):
            locale = self.request.get('locale')
            if locale in AVAILABLE_LOCALES:
                i18n.get_i18n().set_locale(locale)
                # Save locale to cookie for future use
                self.save_locale_to_cookie(locale)
                return True
            return False
    
        def set_locale_from_cookie(self):
            locale = self.request.cookies.get('locale')
            if locale in AVAILABLE_LOCALES:
                i18n.get_i18n().set_locale(locale)
                return True
            return False
    
        def set_locale_from_header(self):
            locale_header = self.request.headers.get('Accept-Language')  # e.g. 'es,en-US;q=0.8,en;q=0.6'
            if locale_header:
                locale_header = locale_header.replace(' ', '')
                # Extract all locales and their preference (q)
                locales = []  # e.g. [('es', 1.0), ('en-US', 0.8), ('en', 0.6)]
                for locale_str in locale_header.split(','):
                    locale_parts = locale_str.split(';q=')
                    locale = locale_parts[0]
                    if len(locale_parts) > 1:
                        locale_q = float(locale_parts[1])
                    else:
                        locale_q = 1.0
                    locales.append((locale, locale_q))
    
                # Sort locales according to preference
                locales.sort(key=lambda locale_tuple: locale_tuple[1], reverse=True)
                # Find first exact match
                for locale in locales:
                    for available_locale in AVAILABLE_LOCALES:
                        if locale[0].replace('-', '_').lower() == available_locale.lower():
                            i18n.get_i18n().set_locale(available_locale)
                            return True
    
                # Find first language match (prefix e.g. 'en' for 'en-GB')
                for locale in locales:
                    for available_locale in AVAILABLE_LOCALES:
                        if locale[0].split('-')[0].lower() == available_locale.lower():
                            i18n.get_i18n().set_locale(available_locale)
                            return True
    
            # There was no match
            return False
    
        def set_locale_default(self):
            i18n.get_i18n().set_locale(AVAILABLE_LOCALES[0])
    
        def save_locale_to_cookie(self, locale):
            self.response.set_cookie('locale', locale)
    
        def __init__(self, request, response):
            """
            Override __init__ in order to set the locale
            Based on: http://stackoverflow.com/a/8522855/423171
            """
    
            # Must call self.initialze when overriding __init__
            # http://webapp-improved.appspot.com/guide/handlers.html#overriding-init
            self.initialize(request, response)
    
            # First, try to set locale from GET parameter (will save it to cookie)
            if not self.set_locale_from_param():
                # Second, try to set locale from cookie
                if not self.set_locale_from_cookie():
                    # Third, try to set locale from Accept-Language header
                    if not self.set_locale_from_header():
                        # Fourth, set locale to first available option
                        self.set_locale_default()
    
    1. It checks for the locale parameter in the URL, and if it exits, it sets a cookie with that locale for future use. In that way you can change the locale anywhere just using that locale parameter, but still avoid the parameter in upcoming requests.

    2. If there is no parameter, it checks for the locale cookie.

    3. If there is no cookie, it checks for the Accept-Language header. Very importantly, it takes into account the q preference factor of the header and also performs some little magic: language prefixes are accepted. For example, if the browser specifies en-GB but it doesn't exist in the AVAILABLE_LOCALES tuple, en will be selected if it exists, which will work by default with en_US if the locales for en do not exist. It also takes care of casing and format (- or _ as separator).

    0 讨论(0)
提交回复
热议问题