Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Refactor the way CWS handles translations
  • Loading branch information
lw committed Jun 7, 2015
1 parent 1f8565b commit c12cc76
Show file tree
Hide file tree
Showing 22 changed files with 229 additions and 120 deletions.
2 changes: 1 addition & 1 deletion cms/db/contest.py
Expand Up @@ -68,7 +68,7 @@ class Contest(Base):
nullable=False)

# The list of language codes of the localizations that contestants
# are allowed to use.
# are allowed to use (empty means all).
allowed_localizations = Column(
RepeatedUnicode(),
nullable=False,
Expand Down
8 changes: 7 additions & 1 deletion cms/server/__init__.py
Expand Up @@ -27,6 +27,9 @@
format_amount_of_time, format_dataset_attrs, format_date, \
format_datetime, format_datetime_smart, format_size, format_time, \
format_token_rules, get_score_class, get_url_root
from .locale import \
filter_language_codes, get_system_translations, get_translations, \
wrap_translations_for_tornado


__all__ = [
Expand All @@ -35,5 +38,8 @@
"compute_actual_phase", "encode_for_url", "file_handler_gen", "filter_ascii",
"format_amount_of_time", "format_dataset_attrs", "format_date",
"format_datetime", "format_datetime_smart", "format_size", "format_time",
"format_token_rules", "get_score_class", "get_url_root"
"format_token_rules", "get_score_class", "get_url_root",
# locale
"filter_language_codes", "get_system_translations", "get_translations",
"wrap_translations_for_tornado"
]
105 changes: 21 additions & 84 deletions cms/server/contest/handlers/base.py
Expand Up @@ -5,7 +5,7 @@
# Copyright © 2010-2014 Giovanni Mascellani <mascellani@poisson.phc.unipi.it>
# Copyright © 2010-2015 Stefano Maggiolo <s.maggiolo@gmail.com>
# Copyright © 2010-2012 Matteo Boscariol <boscarim@hotmail.com>
# Copyright © 2012-2014 Luca Wehrstedt <luca.wehrstedt@gmail.com>
# Copyright © 2012-2015 Luca Wehrstedt <luca.wehrstedt@gmail.com>
# Copyright © 2013 Bernard Blackham <bernard@largestprime.net>
# Copyright © 2014 Artem Iglikov <artem.iglikov@gmail.com>
# Copyright © 2014 Fabian Gundlach <320pointsguy@gmail.com>
Expand Down Expand Up @@ -33,15 +33,13 @@
from __future__ import unicode_literals

import logging
import os
import pickle
import socket
import struct
import traceback

from datetime import timedelta

import gettext
import tornado.web

from werkzeug.datastructures import LanguageAccept
Expand All @@ -50,7 +48,7 @@
from cms import config
from cms.db import Contest, Participation, Session, User
from cms.server import CommonRequestHandler, compute_actual_phase, \
file_handler_gen, get_url_root
file_handler_gen, get_url_root, filter_language_codes
from cmscommon.datetime import get_timezone, make_datetime, make_timestamp
from cmscommon.isocodes import translate_language_code, \
translate_language_country_code
Expand Down Expand Up @@ -207,88 +205,29 @@ def get_current_user(self):

def get_user_locale(self):
self.langs = self.application.service.langs
lang_codes = self.langs.keys()

if self.contest.allowed_localizations:
# We just check if a prefix of each language is allowed
# because this way one can just type "en" to include also
# "en-US" (and similar cases with other languages). It's
# the same approach promoted by HTTP in its Accept header
# parsing rules.
# TODO Be more fussy with prefix checking: validate strings
# (match with "[A-Za-z]+(-[A-Za-z]+)*") and verify that the
# prefix is on the dashes.
self.langs = [lang for lang in self.langs if any(
lang.startswith(prefix)
for prefix in self.contest.allowed_localizations)]

if not self.langs:
logger.warning("No allowed localization matches any installed one."
"Resorting to en-US.")
self.langs = ["en-US"]
else:
# TODO Be more fussy with prefix checking: validate strings
# (match with "[A-Za-z]+(-[A-Za-z]+)*") and verify that the
# prefix is on the dashes.
useless = [
prefix for prefix in self.contest.allowed_localizations
if not any(lang.startswith(prefix) for lang in self.langs)]
if useless:
logger.warning("The following allowed localizations don't "
"match any installed one: %s",
",".join(useless))

# TODO We fallback on any available language if none matches:
# we could return 406 Not Acceptable instead.
if len(self.contest.allowed_localizations) > 0:
lang_codes = filter_language_codes(
lang_codes, self.contest.allowed_localizations)

# TODO We fallback on "en" if no language matches: we could
# return 406 Not Acceptable instead.
# Select the one the user likes most.
http_langs = [lang_code.replace("_", "-") for lang_code in lang_codes]
self.browser_lang = parse_accept_header(
self.request.headers.get("Accept-Language", ""),
LanguageAccept).best_match(self.langs, self.langs[0])
LanguageAccept).best_match(http_langs, "en")

self.cookie_lang = self.get_cookie("language", None)

if self.cookie_lang in self.langs:
lang = self.cookie_lang
if self.cookie_lang in http_langs:
lang_code = self.cookie_lang
else:
lang = self.browser_lang

self.set_header("Content-Language", lang)
lang = lang.replace("-", "_")

iso_639_locale = gettext.translation(
"iso_639",
os.path.join(config.iso_codes_prefix, "share", "locale"),
[lang],
fallback=True)
iso_3166_locale = gettext.translation(
"iso_3166",
os.path.join(config.iso_codes_prefix, "share", "locale"),
[lang],
fallback=True)
shared_mime_info_locale = gettext.translation(
"shared-mime-info",
os.path.join(
config.shared_mime_info_prefix, "share", "locale"),
[lang],
fallback=True)
cms_locale = gettext.translation(
"cms",
self.application.service.localization_dir,
[lang],
fallback=True)
cms_locale.add_fallback(iso_639_locale)
cms_locale.add_fallback(iso_3166_locale)
cms_locale.add_fallback(shared_mime_info_locale)

# Add translate method to simulate tornado.Locale's interface
def translate(message, plural_message=None, count=None):
if plural_message is not None:
assert count is not None
return cms_locale.ungettext(message, plural_message, count)
else:
return cms_locale.ugettext(message)
cms_locale.translate = translate

return cms_locale
lang_code = self.browser_lang

self.set_header("Content-Language", lang_code)
return self.langs[lang_code.replace("-", "_")]

@staticmethod
def _get_token_status(obj):
Expand Down Expand Up @@ -352,20 +291,18 @@ def render_params(self):
else:
ret["tokens_tasks"] = 1 # all finite or mixed

ret["langs"] = self.langs

# TODO Now all language names are shown in the active language.
# It would be better to show them in the corresponding one.
ret["lang_names"] = {}
for lang in self.langs:
for lang_code, trans in self.langs.iteritems():
language_name = None
try:
language_name = translate_language_country_code(
lang.replace("-", "_"), self.locale)
lang_code, trans)
except ValueError:
language_name = translate_language_code(
lang.replace("-", "_"), self.locale)
ret["lang_names"][lang] = language_name
lang_code, trans)
ret["lang_names"][lang_code.replace("_", "-")] = language_name

ret["cookie_lang"] = self.cookie_lang
ret["browser_lang"] = self.browser_lang
Expand Down
17 changes: 4 additions & 13 deletions cms/server/contest/server.py
Expand Up @@ -5,7 +5,7 @@
# Copyright © 2010-2014 Giovanni Mascellani <mascellani@poisson.phc.unipi.it>
# Copyright © 2010-2015 Stefano Maggiolo <s.maggiolo@gmail.com>
# Copyright © 2010-2012 Matteo Boscariol <boscarim@hotmail.com>
# Copyright © 2012-2014 Luca Wehrstedt <luca.wehrstedt@gmail.com>
# Copyright © 2012-2015 Luca Wehrstedt <luca.wehrstedt@gmail.com>
# Copyright © 2013 Bernard Blackham <bernard@largestprime.net>
# Copyright © 2014 Artem Iglikov <artem.iglikov@gmail.com>
# Copyright © 2014 Fabian Gundlach <320pointsguy@gmail.com>
Expand Down Expand Up @@ -43,14 +43,13 @@
from __future__ import unicode_literals

import base64
import glob
import logging
import os
import pkg_resources

from cms import ConfigError, ServiceCoord, config
from cms.io import WebService
from cms.db.filecacher import FileCacher
from cms.server import get_translations, wrap_translations_for_tornado

from .handlers import HANDLERS

Expand Down Expand Up @@ -100,16 +99,8 @@ def __init__(self, shard, contest):
self.notifications = {}

# Retrieve the available translations.
if config.installed:
self.localization_dir = os.path.join(
"/", "usr", "local", "share", "locale")
else:
self.localization_dir = os.path.join(
os.path.dirname(__file__), os.pardir, "mo")
self.langs = ["en-US"] + [
path.split("/")[-3].replace("_", "-") for path in glob.glob(
os.path.join(self.localization_dir,
"*", "LC_MESSAGES", "cms.mo"))]
self.langs = {lang_code: wrap_translations_for_tornado(trans)
for lang_code, trans in get_translations().iteritems()}

self.file_cacher = FileCacher(self)
self.evaluation_service = self.connect_to(
Expand Down
2 changes: 1 addition & 1 deletion cms/server/contest/templates/base.html
Expand Up @@ -51,7 +51,7 @@
<div class="navbar-inner">
<div class="container">
<a class="brand" href="{{ url_root }}/">{{ contest.description }}</a>
{% if len(langs) > 1 %}
{% if len(lang_names) > 1 %}
<p class="navbar-text pull-right">
<select id="lang" class="form-control btn btn-info" onchange="utils.switch_lang()">
<option value=""{% if cookie_lang is None %} selected{% end %}>{{ _("Automatic (%s)") % lang_names[browser_lang] }}</option>
Expand Down

0 comments on commit c12cc76

Please sign in to comment.