Как выбрать язык из файлов cookie / заголовков / сессии в webapp2? - PullRequest
6 голосов
/ 15 декабря 2011

Я хотел бы воспользоваться новыми возможностями webapp2 для локализации, которые также имеют специфичное для локали форматирование для времени и валюты.

В Django есть хорошая функция get_language_from_request, которую я использовал до того, как полностью мигрировал в webapp2, и теперь вместо этого я использую i18n из webapp2, и я могу переключаться между локализацией, которую я пишу с помощью gettext, и компилировать в файлы с именем messages.mo, которые мое приложение может читать и отображать. Затем я определил и определил приоритеты следующих способов получить язык пользователя: 1. HTTP GET, например. hl = pt-br для бразильского португальского 2. Переменная HTTP SESSION, которую я называю i18n_language 3. Cookie я должен установить и получить, но я не знаю точно, как 4. HTTP-заголовок, который я мог получить, и здесь я точно не знаю, и я смотрю, как djnango делает это с удобным get_language_from_request, который я использовал, и теперь я прекратил импортировать django, и я все еще хочу эту функциональность для моего теперь кода на основе webapp2.

def get_language_from_request(self, request):
    """
    Analyzes the request to find what language the user wants the system to
    show. If the user requests a sublanguage where we have a main language, we send
    out the main language.
    """
    if self.request.get('hl'):
      self.session['i18n_language'] = self.request.get('hl')
      return self.request.get('hl')

    if self.session:
      lang_code = self.session.get('i18n_language', None)
      if lang_code:
        logging.info('language found in session')
        return lang_code

    lang_code = Cookies(self).get(LANGUAGE_COOKIE_NAME)
    if lang_code:
        logging.info('language found in cookies')
        return lang_code

    accept = os.environ.get('HTTP_ACCEPT_LANGUAGE', '')
    for accept_lang, unused in self.parse_accept_lang_header(accept):
      logging.info('accept_lang:'+accept_lang)
      lang_code = accept_lang

    return lang_code

Я вижу, что код django доступен, но я не знаю, как, например, i18n из webapp2. Должен ли я позаботиться о переходе на другие языки, такие как pt-br, если они отсутствуют, нужно переключиться на pt. mo локализация для pt-br и аналогичные для других диалектов.

На самом деле настройка языка, который я могу сделать с

i18n.get_i18n().set_locale(language)

Я прошу вашей помощи, чтобы расставить приоритеты для различных способов получения языка пользователя, и я также хотел бы узнать ваши идеи о том, как продолжить реализацию. Или вы думаете, что я могу сделать только с помощью переменной сеанса и не быть настолько внимательным в отношении «полного» решения, так как в любом случае я в основном исправляю язык для географического использования, где мои единственные фактически используемые переводы сейчас - это бразильский португальский и английский, но я хочу он хорошо подготовлен для переключения на испанский, русский и другие языки, поэтому я хотел бы иметь возможность переключиться на пользовательский язык и, по крайней мере, сохранить его в сеансе webapp2 и знать, что вы думаете об использовании файлов cookie и заголовков для получения пользователя. язык.

Исходный код, который я использовал для si из django, выглядит так, и я больше не могу его использовать, потому что он заблокирован для файлов django.mo и специально для django

def get_language_from_request(request):
    """
    Analyzes the request to find what language the user wants the system to
    show. Only languages listed in settings.LANGUAGES are taken into account.
    If the user requests a sublanguage where we have a main language, we send
    out the main language.
    """
    global _accepted
    from django.conf import settings
    globalpath = os.path.join(os.path.dirname(sys.modules[settings.__module__].__file__), 'locale')
    supported = dict(settings.LANGUAGES)

    if hasattr(request, 'session'):
        lang_code = request.session.get('django_language', None)
        if lang_code in supported and lang_code is not None and check_for_language(lang_code):
            return lang_code

    lang_code = request.COOKIES.get(settings.LANGUAGE_COOKIE_NAME)

    if lang_code and lang_code not in supported:
        lang_code = lang_code.split('-')[0] # e.g. if fr-ca is not supported fallback to fr

    if lang_code and lang_code in supported and check_for_language(lang_code):
        return lang_code

    accept = request.META.get('HTTP_ACCEPT_LANGUAGE', '')
    for accept_lang, unused in parse_accept_lang_header(accept):
        if accept_lang == '*':
            break

        # We have a very restricted form for our language files (no encoding
        # specifier, since they all must be UTF-8 and only one possible
        # language each time. So we avoid the overhead of gettext.find() and
        # work out the MO file manually.

        # 'normalized' is the root name of the locale in POSIX format (which is
        # the format used for the directories holding the MO files).
        normalized = locale.locale_alias.get(to_locale(accept_lang, True))
        if not normalized:
            continue
        # Remove the default encoding from locale_alias.
        normalized = normalized.split('.')[0]

        if normalized in _accepted:
            # We've seen this locale before and have an MO file for it, so no
            # need to check again.
            return _accepted[normalized]

        for lang, dirname in ((accept_lang, normalized),
                (accept_lang.split('-')[0], normalized.split('_')[0])):
            if lang.lower() not in supported:
                continue
            langfile = os.path.join(globalpath, dirname, 'LC_MESSAGES',
                    'django.mo')
            if os.path.exists(langfile):
                _accepted[normalized] = lang
                return lang

    return settings.LANGUAGE_CODE

Можно ли это делать для каждого запроса? И я думаю, что я должен также установить заголовок на язык self.response.headers['Content-Language'] = language

Согласно моим ожиданиям, я могу взять какую-то функцию непосредственно из django, если я решу использовать заголовки http, но я не понимаю, что она делает, поэтому, возможно, вы можете объяснить мне этот код из django:

def parse_accept_lang_header(lang_string):
    """
    Parses the lang_string, which is the body of an HTTP Accept-Language
    header, and returns a list of (lang, q-value), ordered by 'q' values.

    Any format errors in lang_string results in an empty list being returned.
    """
    result = []
    pieces = accept_language_re.split(lang_string)
    if pieces[-1]:
        return []
    for i in range(0, len(pieces) - 1, 3):
        first, lang, priority = pieces[i : i + 3]
        if first:
            return []
        priority = priority and float(priority) or 1.0
        result.append((lang, priority))
    result.sort(lambda x, y: -cmp(x[1], y[1]))
    return result

Спасибо

Обновление

Я обнаружил, что не могу использовать сеансы в функции инициализации обработчика запросов, возможно, это потому, что объект сеанса еще не создан. Поэтому я поместил код для получения языка из сеанса в функцию рендеринга BaseHandler, и он, похоже, работает. Также было бы неплохо рассмотреть заголовки или значение cookie.

Ответы [ 2 ]

13 голосов
/ 15 декабря 2011

Вот что я делаю - у меня есть базовый обработчик запросов, от которого наследуются все мои обработчики запросов, затем здесь у меня есть константа, содержащая доступные языки, и я переопределяю метод init , чтобы установить язык по каждому запросу:

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])

Сначала я проверяю куки, затем заголовок, наконец, по умолчанию на первый доступный язык, если действительный не был найден.

Чтобы установить cookie, у меня есть отдельный контроллер, который выглядит примерно так:

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)

Таким образом, URL, такой как www.example.com/locale/en_GB, изменил бы локаль на en_GB, установив cookie и вернувшись к рефереру (это дает преимущество в возможности переключения языков на любой странице и сохранения его на той же странице).

Этот метод не учитывает частичные совпадения для локалей в заголовке, например, «en» вместо «en_GB», но виден как фиксированный список языков, которые я включил в приложении (и URL-адреса изменения локали жестко запрограммированы в нижнем колонтитуле), я не слишком беспокоюсь об этом.

НТН

5 голосов
/ 28 июля 2013

Полностью основываясь на ответе fishwebby, с некоторыми улучшениями и некоторыми изменениями дизайна, вот что я делаю:

"""
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. Проверяет параметр locale в URL , и если он выходит, он устанавливает cookie с этим языковым стандартом для будущего использования. В этом как вы можете изменить локаль в любом месте, просто используя это locale параметр, но по-прежнему избегайте параметра в последующих запросах.

  2. Если параметр отсутствует, он проверяет наличие locale cookie .

  3. Если файл cookie отсутствует, проверяется заголовок Accept-Language . Очень важно, что он учитывает q коэффициент предпочтения заголовка, а также выполняет небольшую магию: языковые префиксы принимаются. Например, если браузер указывает en-GB, но его нет в кортеже AVAILABLE_LOCALES, будет выбрано en, если оно существует, которое будет работать по умолчанию с en_US, если локали для en делают не существует. Он также заботится о корпусе и формате (- или _ в качестве разделителя).

...