Source code for assembl.lib.locale
"""Utilities for locale conversion, between posix, iso639 1 & 2;
and for pyramid locale negotiation."""
from builtins import range
from pyramid.i18n import TranslationStringFactory, Localizer
from pyramid.i18n import default_locale_negotiator
from iso639 import (is_valid639_2, is_valid639_1, to_iso639_1)
from .config import get_config
_ = TranslationStringFactory('assembl')
[docs]def get_localizer(request=None):
"""Get the localizer.
Searches the given request, or the current request,
or provides a default locale."""
if request is None:
from pyramid.threadlocal import get_current_request
request = get_current_request()
if request:
localizer = request.localizer
else:
locale_code = get_config().get('available_languages', 'fr_CA en_CA').\
split()[0]
localizer = Localizer(locale_code)
return localizer
def use_underscore(locale):
# Normalize fr-ca to fr_ca
if locale and '-' in locale:
return '_'.join(locale.split('-'))
return locale
def to_posix_string(locale_code):
if not locale_code:
return None
# Normalize fra-ca to fr_CA
locale_code = use_underscore(locale_code)
locale_parts = locale_code.split("_")
# Normalize first component
lang = locale_parts[0]
if is_valid639_1(lang):
posix_lang = lang
elif is_valid639_2(lang):
temp = to_iso639_1(lang)
posix_lang = temp if temp else lang
else:
# Aryan, not sure what case is being covered here
full_name = lang.lower().capitalize()
if is_valid639_2(full_name):
posix_lang = to_iso639_1(full_name)
else:
raise ValueError(
"The input %s in not a valid code to convert to posix format" %
(locale_code,))
locale_parts[0] = posix_lang
if len(locale_parts) > 4:
raise ValueError("This locale has too many parts: "+locale_code)
elif len(locale_parts) == 4:
# Drop dialect. Sorry.
locale_parts.pop()
if len(locale_parts) > 1:
# Normalize Country
if len(locale_parts[-1]) == 2:
locale_parts[-1] = locale_parts[-1].upper()
elif len(locale_parts[-1]) != 4:
raise ValueError(
"The last part is not a script or country: "+locale_code)
# Normalize script
if len(locale_parts[1]) == 4:
locale_parts[1] = locale_parts[1].capitalize()
return "_".join(locale_parts)
def get_language(locale):
return (use_underscore(locale)+'_').split('_')[0]
def get_country(locale):
locale = use_underscore(locale)
if '_' in locale:
return locale.split('_')[1].upper()
# otherwise None
def ensure_locale_has_country(locale):
# assuming a posix locale
if '_' not in locale:
# first look in config
settings = get_config()
available = settings.get('available_languages', 'en_CA fr_CA').split()
avail_langs = {get_language(loc): loc
for loc in reversed(available) if '_' in loc}
locale_with_country = avail_langs.get(locale, None)
if not locale_with_country:
if is_valid639_1(locale):
return locale
return None
return locale_with_country
# TODO: Default countries for languages. Look in pycountry?
return locale
def strip_country(locale):
# assuming a posix locale
if locale == "zh":
return "zh_Hans"
if '_' in locale:
locale = locale.split("_")
if len(locale[-1]) == 2:
locale.pop()
return "_".join(locale)
return locale
def strip_most_countries(locale):
base = strip_country(locale)
if base in ('zh', 'pt'):
return locale
return base
def locale_ancestry(locale):
locale = locale.split("_")
return ["_".join(locale[:i]) for i in range(len(locale), 0, -1)]
def create_mt_code(source_code, target_code):
return "-x-mtfrom-".join((target_code, source_code))
def split_mt_code(locale):
parts = locale.split("-x-mtfrom-")
if len(parts) < 2:
parts.append(None)
assert len(parts) == 2
return parts
_rtl_locales = {"ar", "dv", "ha", "he", "fa", "ps", "ur", "yi"}
def is_rtl(locale):
parts = locale.split("_")
return parts[0] in _rtl_locales or (len(parts) > 1 and parts[1] == 'Arab')
[docs]def locale_compatible(locname1, locname2):
"""Are the two locales similar enough to be substituted
one for the other. Mostly same language/script, disregard country.
"""
# Google special case... should be done upstream ideally.
if locname1 == 'zh':
locname1 = 'zh_Hans'
if locname2 == 'zh':
locname2 = 'zh_Hans'
loc1 = locname1.split("_")
loc2 = locname2.split("_")
for i in range(min(len(loc1), len(loc2))):
if loc1[i] != loc2[i]:
if i and len(loc1[i]) == 2:
# discount difference in country
return i
return False
return i + 1
def any_locale_compatible(locname, locnames):
for l in locnames:
if locale_compatible(l, locname):
return True
return False
def get_preferred_languages(session, user_id):
from ..models import UserLanguagePreference
prefs = (session.query(UserLanguagePreference)
.filter_by(user_id=user_id)
.order_by(UserLanguagePreference.preferred_order))
return [p.locale for p in prefs]
def locale_negotiator(request):
settings = get_config()
available = settings.get('available_languages').split()
locale = (request.cookies.get('_LOCALE_', None) or
request.params.get('_LOCALE_', None))
# TODO: Set User preference in this function.
if not locale:
from pyramid.security import authenticated_userid
from assembl.auth.util import discussion_from_request
from assembl.models import get_session_maker
user_id = authenticated_userid(request)
if user_id:
prefs = get_preferred_languages(get_session_maker()(), user_id)
for locale in prefs:
if locale in available:
break
if '_' not in locale:
locale = ensure_locale_has_country(locale)
if locale and locale in available:
break
else:
locale = None
if locale is None:
discussion = discussion_from_request(request)
if discussion:
for locale in discussion.discussion_locales:
if locale in available:
break
if '_' not in locale:
locale = ensure_locale_has_country(locale)
if locale and locale in available:
break
else:
locale = None
if not locale:
locale = to_posix_string(default_locale_negotiator(request))
if locale and locale not in available:
locale_with_country = ensure_locale_has_country(locale)
if locale_with_country:
locale = locale_with_country
if not locale:
locale = to_posix_string(request.accept_language.best_match(
available, settings.get('pyramid.default_locale_name', 'en')))
request._LOCALE_ = locale
return locale