Commit 64f1cae6 by Julia Hansbrough

Merge pull request #2419 from edx/talbs/lms-languagemenu

LMS: Adds Language Selection Menu
parents d6f1ddd8 ff0a6fb7
......@@ -456,8 +456,13 @@ INSTALLED_APPS = (
# Dark-launching languages
'dark_lang',
# Student identity reverification
'reverification',
# User preferences
'user_api',
'django_openid_auth',
)
......
......@@ -36,6 +36,9 @@ urlpatterns = patterns('', # nopep8
url(r'^xmodule/', include('pipeline_js.urls')),
url(r'^heartbeat$', include('heartbeat.urls')),
url(r'^user_api/', include('user_api.urls')),
url(r'^lang_pref/', include('lang_pref.urls')),
)
# User creation and updating views
......
......@@ -19,6 +19,8 @@ class DarkLangConfig(ConfigurationModel):
def released_languages_list(self):
"""
``released_languages`` as a list of language codes.
Example: ['it', 'de-at', 'es', 'pt-br']
"""
if not self.released_languages.strip(): # pylint: disable=no-member
return []
......
"""
Useful information for setting the language preference
"""
# this is the UserPreference key for the user's preferred language
LANGUAGE_KEY = 'pref-lang'
"""
Middleware for Language Preferences
"""
from user_api.models import UserPreference
from lang_pref import LANGUAGE_KEY
class LanguagePreferenceMiddleware(object):
"""
Middleware for user preferences.
Ensures that, once set, a user's preferences are reflected in the page
whenever they are logged in.
"""
def process_request(self, request):
"""
If a user's UserPreference contains a language preference and there is
no language set on the session (i.e. from dark language overrides), use the user's preference.
"""
if request.user.is_authenticated() and 'django_language' not in request.session:
user_pref = UserPreference.get_preference(request.user, LANGUAGE_KEY)
if user_pref:
request.session['django_language'] = user_pref
from django.test import TestCase
from django.test.client import RequestFactory
from django.contrib.sessions.middleware import SessionMiddleware
from lang_pref.middleware import LanguagePreferenceMiddleware
from user_api.models import UserPreference
from lang_pref import LANGUAGE_KEY
from student.tests.factories import UserFactory
class TestUserPreferenceMiddleware(TestCase):
"""
Tests to make sure user preferences are getting properly set in the middleware
"""
def setUp(self):
self.middleware = LanguagePreferenceMiddleware()
self.session_middleware = SessionMiddleware()
self.user = UserFactory.create()
self.request = RequestFactory().get('/somewhere')
self.request.user = self.user
self.session_middleware.process_request(self.request)
def test_no_language_set_in_session_or_prefs(self):
# nothing set in the session or the prefs
self.middleware.process_request(self.request)
self.assertNotIn('django_language', self.request.session)
def test_language_in_user_prefs(self):
# language set in the user preferences and not the session
UserPreference.set_preference(self.user, LANGUAGE_KEY, 'eo')
self.middleware.process_request(self.request)
self.assertEquals(self.request.session['django_language'], 'eo')
def test_language_in_session(self):
# language set in both the user preferences and session,
# session should get precedence
self.request.session['django_language'] = 'en'
UserPreference.set_preference(self.user, LANGUAGE_KEY, 'eo')
self.middleware.process_request(self.request)
self.assertEquals(self.request.session['django_language'], 'en')
"""
Tests for the language setting view
"""
from django.core.urlresolvers import reverse
from django.test import TestCase
from student.tests.factories import UserFactory
from user_api.models import UserPreference
from lang_pref import LANGUAGE_KEY
class TestLanguageSetting(TestCase):
"""
Test setting languages
"""
def test_set_preference_happy(self):
user = UserFactory.create()
self.client.login(username=user.username, password='test')
lang = 'en'
response = self.client.post(reverse('lang_pref_set_language'), {'language': lang})
self.assertEquals(response.status_code, 200)
user_pref = UserPreference.get_preference(user, LANGUAGE_KEY)
self.assertEqual(user_pref, lang)
def test_set_preference_missing_lang(self):
user = UserFactory.create()
self.client.login(username=user.username, password='test')
response = self.client.post(reverse('lang_pref_set_language'))
self.assertEquals(response.status_code, 400)
self.assertIsNone(UserPreference.get_preference(user, LANGUAGE_KEY))
"""
Urls for managing language preferences
"""
from django.conf.urls import patterns, url
urlpatterns = patterns(
'',
url(r'^setlang/', 'lang_pref.views.set_language', name='lang_pref_set_language')
)
"""
Views for accessing language preferences
"""
from django.contrib.auth.decorators import login_required
from django.http import HttpResponse, HttpResponseBadRequest
from user_api.models import UserPreference
from lang_pref import LANGUAGE_KEY
@login_required
def set_language(request):
"""
This view is called when the user would like to set a language preference
"""
user = request.user
lang_pref = request.POST.get('language', None)
if lang_pref:
UserPreference.set_preference(user, LANGUAGE_KEY, lang_pref)
return HttpResponse('{"success": true}')
return HttpResponseBadRequest('no language provided')
......@@ -45,6 +45,7 @@ from student.firebase_token_generator import create_token
from verify_student.models import SoftwareSecurePhotoVerification, MidcourseReverificationWindow
from certificates.models import CertificateStatuses, certificate_status_for_student
from dark_lang.models import DarkLangConfig
from xmodule.course_module import CourseDescriptor
from xmodule.modulestore.exceptions import ItemNotFoundError
......@@ -61,6 +62,8 @@ import external_auth.views
from bulk_email.models import Optout, CourseAuthorization
import shoppingcart
from user_api.models import UserPreference
from lang_pref import LANGUAGE_KEY
import track.views
......@@ -468,23 +471,42 @@ def dashboard(request):
# we'll display the banner
denied_banner = any(item.display for item in reverifications["denied"])
context = {'course_enrollment_pairs': course_enrollment_pairs,
'course_optouts': course_optouts,
'message': message,
'external_auth_map': external_auth_map,
'staff_access': staff_access,
'errored_courses': errored_courses,
'show_courseware_links_for': show_courseware_links_for,
'all_course_modes': course_modes,
'cert_statuses': cert_statuses,
'show_email_settings_for': show_email_settings_for,
'reverifications': reverifications,
'verification_status': verification_status,
'verification_msg': verification_msg,
'show_refund_option_for': show_refund_option_for,
'denied_banner': denied_banner,
'billing_email': settings.PAYMENT_SUPPORT_EMAIL,
}
language_options = DarkLangConfig.current().released_languages_list
# add in the default language if it's not in the list of released languages
if settings.LANGUAGE_CODE not in language_options:
language_options.append(settings.LANGUAGE_CODE)
# try to get the prefered language for the user
cur_lang_code = UserPreference.get_preference(request.user, LANGUAGE_KEY)
if cur_lang_code:
# if the user has a preference, get the name from the code
current_language = settings.LANGUAGE_DICT[cur_lang_code]
else:
# if the user doesn't have a preference, use the default language
current_language = settings.LANGUAGE_DICT[settings.LANGUAGE_CODE]
context = {
'course_enrollment_pairs': course_enrollment_pairs,
'course_optouts': course_optouts,
'message': message,
'external_auth_map': external_auth_map,
'staff_access': staff_access,
'errored_courses': errored_courses,
'show_courseware_links_for': show_courseware_links_for,
'all_course_modes': course_modes,
'cert_statuses': cert_statuses,
'show_email_settings_for': show_email_settings_for,
'reverifications': reverifications,
'verification_status': verification_status,
'verification_msg': verification_msg,
'show_refund_option_for': show_refund_option_for,
'denied_banner': denied_banner,
'billing_email': settings.PAYMENT_SUPPORT_EMAIL,
'language_options': language_options,
'current_language': current_language,
'current_language_code': cur_lang_code,
}
return render_to_response('dashboard.html', context)
......
from django.contrib.auth.models import User
from django.db import models
class UserPreference(models.Model):
"""A user's preference, stored as generic text to be processed by client"""
user = models.ForeignKey(User, db_index=True, related_name="+")
key = models.CharField(max_length=255, db_index=True)
value = models.TextField()
class Meta:
unique_together = ("user", "key")
@classmethod
def set_preference(cls, user, preference_key, preference_value):
"""
Sets the user preference for a given key
"""
user_pref, _ = cls.objects.get_or_create(user=user, key=preference_key)
user_pref.value = preference_value
user_pref.save()
@classmethod
def get_preference(cls, user, preference_key, default=None):
"""
Gets the user preference value for a given key
Returns the given default if there isn't a preference for the given key
"""
try:
user_pref = cls.objects.get(user=user, key=preference_key)
return user_pref.value
except cls.DoesNotExist:
return default
......@@ -2,6 +2,7 @@ from django.db import IntegrityError
from django.test import TestCase
from student.tests.factories import UserFactory
from user_api.tests.factories import UserPreferenceFactory
from user_api.models import UserPreference
class UserPreferenceModelTest(TestCase):
......@@ -26,3 +27,21 @@ class UserPreferenceModelTest(TestCase):
key="testkey3",
value="\xe8\xbf\x99\xe6\x98\xaf\xe4\xb8\xad\xe5\x9b\xbd\xe6\x96\x87\xe5\xad\x97'"
)
def test_get_set_preference(self):
# Checks that you can set a preference and get that preference later
# Also, tests that no preference is returned for keys that are not set
user = UserFactory.create()
key = 'testkey'
value = 'testvalue'
# does a round trip
UserPreference.set_preference(user, key, value)
pref = UserPreference.get_preference(user, key)
self.assertEqual(pref, value)
# get preference for key that doesn't exist for user
pref = UserPreference.get_preference(user, 'testkey_none')
self.assertIsNone(pref)
import base64
from django.contrib.auth.models import User
from django.test import TestCase
from django.test.utils import override_settings
import json
......@@ -17,21 +16,9 @@ USER_PREFERENCE_LIST_URI = "/user_api/v1/user_prefs/"
@override_settings(EDX_API_KEY=TEST_API_KEY)
class UserApiTestCase(TestCase):
def setUp(self):
super(UserApiTestCase, self).setUp()
self.users = [
UserFactory.create(
email="test{0}@test.org".format(i),
profile__name="Test {0}".format(i)
)
for i in range(5)
]
self.prefs = [
UserPreferenceFactory.create(user=self.users[0], key="key0"),
UserPreferenceFactory.create(user=self.users[0], key="key1"),
UserPreferenceFactory.create(user=self.users[1], key="key0")
]
class ApiTestCase(TestCase):
LIST_URI = USER_LIST_URI
def basic_auth(self, username, password):
return {'HTTP_AUTHORIZATION': 'Basic ' + base64.b64encode('%s:%s' % (username, password))}
......@@ -95,11 +82,41 @@ class UserApiTestCase(TestCase):
"""Assert that the given response has the status code 403"""
self.assertEqual(response.status_code, 403)
def assertHttpBadRequest(self, response):
"""Assert that the given response has the status code 400"""
self.assertEqual(response.status_code, 400)
def assertHttpMethodNotAllowed(self, response):
"""Assert that the given response has the status code 405"""
self.assertEqual(response.status_code, 405)
class EmptyUserTestCase(ApiTestCase):
def test_get_list_empty(self):
result = self.get_json(self.LIST_URI)
self.assertEqual(result["count"], 0)
self.assertIsNone(result["next"])
self.assertIsNone(result["previous"])
self.assertEqual(result["results"], [])
class UserApiTestCase(ApiTestCase):
def setUp(self):
super(UserApiTestCase, self).setUp()
self.users = [
UserFactory.create(
email="test{0}@test.org".format(i),
profile__name="Test {0}".format(i)
)
for i in range(5)
]
self.prefs = [
UserPreferenceFactory.create(user=self.users[0], key="key0"),
UserPreferenceFactory.create(user=self.users[0], key="key1"),
UserPreferenceFactory.create(user=self.users[1], key="key0")
]
class UserViewSetTest(UserApiTestCase):
LIST_URI = USER_LIST_URI
......@@ -137,17 +154,10 @@ class UserViewSetTest(UserApiTestCase):
def test_basic_auth(self):
# ensure that having basic auth headers in the mix does not break anything
self.assertHttpOK(
self.request_with_auth("get", self.LIST_URI, **self.basic_auth('someuser', 'somepass')))
self.request_with_auth("get", self.LIST_URI,
**self.basic_auth('someuser', 'somepass')))
self.assertHttpForbidden(
self.client.get(self.LIST_URI, **self.basic_auth('someuser', 'somepass')))
def test_get_list_empty(self):
User.objects.all().delete()
result = self.get_json(self.LIST_URI)
self.assertEqual(result["count"], 0)
self.assertIsNone(result["next"])
self.assertIsNone(result["previous"])
self.assertEqual(result["results"], [])
self.client.get(self.LIST_URI, **self.basic_auth('someuser', 'somepass')))
def test_get_list_nonempty(self):
result = self.get_json(self.LIST_URI)
......@@ -245,14 +255,6 @@ class UserPreferenceViewSetTest(UserApiTestCase):
def test_debug_auth(self):
self.assertHttpOK(self.client.get(self.LIST_URI))
def test_get_list_empty(self):
UserPreference.objects.all().delete()
result = self.get_json(self.LIST_URI)
self.assertEqual(result["count"], 0)
self.assertIsNone(result["next"])
self.assertIsNone(result["previous"])
self.assertEqual(result["results"], [])
def test_get_list_nonempty(self):
result = self.get_json(self.LIST_URI)
self.assertEqual(result["count"], 3)
......
......@@ -4,8 +4,8 @@ from rest_framework import authentication
from rest_framework import filters
from rest_framework import permissions
from rest_framework import viewsets
from user_api.models import UserPreference
from user_api.serializers import UserSerializer, UserPreferenceSerializer
from user_api.models import UserPreference
class ApiKeyHeaderPermission(permissions.BasePermission):
......
# -*- coding: utf-8 -*-
"""
Student dashboard page.
"""
......@@ -18,6 +19,14 @@ class DashboardPage(PageObject):
return self.is_css_present('section.my-courses')
@property
def current_courses_text(self):
text_items = self.css_text('section#my-courses')
if len(text_items) > 0:
return text_items[0]
else:
return ""
@property
def available_courses(self):
"""
Return list of the names of available courses (e.g. "999 edX Demonstration Course")
......@@ -59,3 +68,11 @@ class DashboardPage(PageObject):
return "a.enter-course:nth-of-type({0})".format(link_index + 1)
else:
return None
def change_language(self, code):
"""
Change the language on the dashboard to the language corresponding with `code`.
"""
self.css_click(".edit-language")
self.select_option("language", code)
self.css_click("#submit-lang")
# -*- coding: utf-8 -*-
"""
E2E tests for the LMS.
"""
......@@ -5,7 +6,7 @@ E2E tests for the LMS.
from unittest import skip
from bok_choy.web_app_test import WebAppTest
from bok_choy.promise import EmptyPromise, fulfill_before
from bok_choy.promise import EmptyPromise, fulfill_before, fulfill, Promise
from .helpers import UniqueCourseTest, load_data_str
from ..pages.studio.auto_auth import AutoAuthPage
......@@ -17,6 +18,7 @@ from ..pages.lms.course_info import CourseInfoPage
from ..pages.lms.tab_nav import TabNavPage
from ..pages.lms.course_nav import CourseNavPage
from ..pages.lms.progress import ProgressPage
from ..pages.lms.dashboard import DashboardPage
from ..pages.lms.video import VideoPage
from ..pages.xblock.acid import AcidView
from ..fixtures.course import CourseFixture, XBlockFixtureDesc, CourseUpdateDesc
......@@ -68,6 +70,66 @@ class RegistrationTest(UniqueCourseTest):
self.assertIn(self.course_info['display_name'], course_names)
class LanguageTest(UniqueCourseTest):
"""
Tests that the change language functionality on the dashboard works
"""
@property
def _changed_lang_promise(self):
def _check_func():
text = self.dashboard_page.current_courses_text
return (len(text) > 0, text)
return Promise(_check_func, "language changed")
def setUp(self):
"""
Initiailize dashboard page
"""
super(LanguageTest, self).setUp()
self.dashboard_page = DashboardPage(self.browser)
self.test_new_lang = 'eo'
# This string is unicode for "ÇÜRRÉNT ÇØÜRSÉS", which should appear in our Dummy Esperanto page
# We store the string this way because Selenium seems to try and read in strings from
# the HTML in this format. Ideally we could just store the raw ÇÜRRÉNT ÇØÜRSÉS string here
self.current_courses_text = u'\xc7\xdcRR\xc9NT \xc7\xd6\xdcRS\xc9S'
self.username = "test"
self.password = "testpass"
self.email = "test@example.com"
def test_change_lang(self):
AutoAuthPage(self.browser, course_id=self.course_id).visit()
self.dashboard_page.visit()
# Change language to Dummy Esperanto
self.dashboard_page.change_language(self.test_new_lang)
changed_text = fulfill(self._changed_lang_promise)
# We should see the dummy-language text on the page
self.assertIn(self.current_courses_text, changed_text)
def test_language_persists(self):
auto_auth_page = AutoAuthPage(self.browser, username=self.username, password=self.password, email=self.email, course_id=self.course_id)
auto_auth_page.visit()
self.dashboard_page.visit()
# Change language to Dummy Esperanto
self.dashboard_page.change_language(self.test_new_lang)
# destroy session
self.browser._cookie_manager.delete()
# log back in
auto_auth_page.visit()
self.dashboard_page.visit()
changed_text = fulfill(self._changed_lang_promise)
# We should see the dummy-language text on the page
self.assertIn(self.current_courses_text, changed_text)
class HighLevelTabTest(UniqueCourseTest):
"""
Tests that verify each of the high-level tabs available within a course.
......
[{"pk": 1, "model": "dark_lang.darklangconfig", "fields": {"change_date": "2100-01-30T20:34:20Z", "changed_by": null, "enabled": true, "released_languages": "en,eo"}}]
from django.contrib.auth.models import User
from django.db import models
class UserPreference(models.Model):
"""A user's preference, stored as generic text to be processed by client"""
user = models.ForeignKey(User, db_index=True, related_name="+")
key = models.CharField(max_length=255, db_index=True)
value = models.TextField()
class Meta:
unique_together = ("user", "key")
......@@ -501,7 +501,8 @@ LANGUAGE_CODE = 'en' # http://www.i18nguy.com/unicode/language-identifiers.html
# Sourced from http://www.localeplanet.com/icu/ and wikipedia
LANGUAGES = (
('eo', u'Dummy Language (Esperanto)'), # Dummy language used for testing
('en', u'English'),
('eo', u'Dummy Language (Esperanto)'), # Dummy languaged used for testing
('fake2', u'Fake translations'), # Another dummy language for testing (not pushed to prod)
('ach', u'Acholi'), # Acoli
......@@ -554,6 +555,8 @@ LANGUAGES = (
('zh-tw', u'台灣正體'), # Chinese (Taiwan)
)
LANGUAGE_DICT = dict(LANGUAGES)
USE_I18N = True
USE_L10N = True
......@@ -698,6 +701,10 @@ MIDDLEWARE_CLASSES = (
# Allows us to dark-launch particular languages
'dark_lang.middleware.DarkLangMiddleware',
# Allows us to set user preferences
# should be after DarkLangMiddleware
'lang_pref.middleware.LanguagePreferenceMiddleware',
# Detects user-requested locale from 'accept-language' header in http request
'django.middleware.locale.LocaleMiddleware',
......
......@@ -97,6 +97,26 @@
%ui-depth4 { z-index: 10000; }
%ui-depth5 { z-index: 100000; }
// extends - UI - utility - nth-type style clearing
%wipe-first-child {
&:first-child {
margin-top: 0;
border-top: none;
padding-top: 0;
}
}
// extends - UI - utility - nth-type style clearing
%wipe-last-child {
&:last-child {
margin-bottom: 0;
border-bottom: none;
padding-bottom: 0;
}
}
// extends -hidden elems - screenreaders
%text-sr {
border: 0;
......
......@@ -231,7 +231,8 @@
cursor: default;
pointer-events: none;
box-shadow: none;
:hover, :focus {
&:hover, &:focus {
pointer-events: none;
}
}
......
......@@ -854,6 +854,22 @@
}
}
// status - language
.status-language {
.icon {
@include font-size(17);
display: inline-block;
vertical-align: middle;
margin-right: ($baseline/4);
color: $black;
}
.title .icon {
opacity: 0.75; // needed to overcome bad specificity elsewhere
}
}
// status - verification
.status-verification {
......
......@@ -325,3 +325,37 @@
@extend .modal;
}
// --------------------
// CASE: language settings
.modal-settings-language {
// general reset
.list-input, .list-actions {
@extend %ui-no-list;
}
.settings-language-select .select {
width: 100%;
}
.list-input {
margin-bottom: $baseline;
}
.actions-supplemental {
padding: 0 ($baseline*2) $baseline ($baseline*2);
.list-actions-item {
@extend %t-copy-sub1;
color: $base-font-color;
text-align: center;
}
.action {
display: block;
margin-top: ($baseline/4);
}
}
}
<%! from django.utils.translation import ugettext as _ %>
<%! from django.template import RequestContext %>
<%!
from django.core.urlresolvers import reverse
......@@ -82,6 +83,18 @@
});
});
$("#submit-lang").click(function(event, xhr) {
event.preventDefault();
$.post('/lang_pref/setlang/',
{"language": $('#settings-language-value').val()})
.done(
function(data){
// submit form as normal
$('.settings-language-form').submit();
}
);
});
$("#change_email_form").submit(function(){
var new_email = $('#new_email_field').val();
var new_password = $('#new_email_password').val();
......@@ -193,6 +206,10 @@
</span> <span class="data">${ user.email | h }</span>
</li>
%if len(language_options) > 1:
<%include file='dashboard/_dashboard_info_language.html' />
%endif
% if external_auth_map is None or 'shib' not in external_auth_map.external_domain:
<li class="controls--account">
<span class="title"><div class="icon"></div><a href="#password_reset_complete" rel="leanModal" id="pwd_reset_button">${_("Reset Password")}</a></span>
......@@ -262,10 +279,21 @@
<section id="email-settings-modal" class="modal" aria-hidden="true">
<div class="inner-wrapper" role="dialog" aria-labelledby="email-settings-title">
<button class="close-modal">&#10005; <span class="sr">${_('Close Modal')}</span></button>
<button class="close-modal">&#10005;
<span class="sr">
## Translators: this is a control to allow users to exit out of this modal interface (a menu or piece of UI that takes the full focus of the screen)
${_('Close Modal')}
</span>
</button>
<header>
<h2 id="email-settings-title">${_('Email Settings for {course_number}').format(course_number='<span id="email_settings_course_number"></span>')}<span class="sr">, ${_("modal open")}</span></h2>
<h2 id="email-settings-title">
${_('Email Settings for {course_number}').format(course_number='<span id="email_settings_course_number"></span>')}
<span class="sr">,
## Translators: this text gives status on if the modal interface (a menu or piece of UI that takes the full focus of the screen) is open or not
${_("modal open")}
</span>
</h2>
<hr/>
</header>
......@@ -283,10 +311,21 @@
<section id="password_reset_complete" class="modal" aria-hidden="true">
<div class="inner-wrapper" role="dialog" aria-labelledby="password-reset-email">
<button class="close-modal">&#10005; <span class="sr">${_('Close Modal')}</span></button>
<button class="close-modal">&#10005;
<span class="sr">
## Translators: this is a control to allow users to exit out of this modal interface (a menu or piece of UI that takes the full focus of the screen)
${_('Close Modal')}
</span>
</button>
<header>
<h2 id="password-reset-email">${_('Password Reset Email Sent')}<span class="sr">, ${_("modal open")}</span></h2>
<h2 id="password-reset-email">
${_('Password Reset Email Sent')}
<span class="sr">,
## Translators: this text gives status on if the modal interface (a menu or piece of UI that takes the full focus of the screen) is open or not
${_("modal open")}
</span>
</h2>
<hr/>
</header>
<div>
......@@ -301,10 +340,21 @@
<section id="change_email" class="modal" aria-hidden="true">
<div class="inner-wrapper" role="dialog" aria-labelledby="change_email_title">
<button class="close-modal">&#10005; <span class="sr">${_('Close Modal')}</span></button>
<button class="close-modal">&#10005;
<span class="sr">
## Translators: this is a control to allow users to exit out of this modal interface (a menu or piece of UI that takes the full focus of the screen)
${_('Close Modal')}
</span>
</button>
<header>
<h2><span id="change_email_title">${_("Change Email")}</span><span class="sr">, ${_("modal open")}</span></h2>
<h2>
<span id="change_email_title">${_("Change Email")}</span>
<span class="sr">,
## Translators: this text gives status on if the modal interface (a menu or piece of UI that takes the full focus of the screen) is open or not
${_("modal open")}
</span>
</h2>
<hr/>
</header>
<div id="change_email_body">
......@@ -329,12 +379,25 @@
</div>
</section>
<%include file='modal/_modal-settings-language.html' />
<section id="apply_name_change" class="modal" aria-hidden="true">
<div class="inner-wrapper" role="dialog" aria-labelledby="change-name-title">
<button class="close-modal">&#10005; <span class="sr">${_('Close Modal')}</span></button>
<button class="close-modal">&#10005;
<span class="sr">
## Translators: this is a control to allow users to exit out of this modal interface (a menu or piece of UI that takes the full focus of the screen)
${_('Close Modal')}
</span>
</button>
<header>
<h2 id="change-name-title">${_("Change your name")}<span class="sr">, ${_("modal open")}</span></h2>
<h2 id="change-name-title">
${_("Change your name")}
<span class="sr">,
## Translators: this text gives status on if the modal interface (a menu or piece of UI that takes the full focus of the screen) is open or not
${_("modal open")}
</span>
</h2>
<hr/>
</header>
<div id="change_name_body">
......@@ -360,9 +423,21 @@
<section id="unenroll-modal" class="modal unenroll-modal" aria-hidden="true">
<div class="inner-wrapper" role="alertdialog" aria-labelledy="unenrollment-modal-title">
<button class="close-modal">&#10005; <span class="sr">${_('Close Modal')}</span></button>
<button class="close-modal">&#10005;
<span class="sr">
## Translators: this is a control to allow users to exit out of this modal interface (a menu or piece of UI that takes the full focus of the screen)
${_('Close Modal')}
</span>
</button>
<header>
<h2 id="unenrollment-modal-title">${_('<span id="track-info"></span> {course_number}? <span id="refund-info"></span>').format(course_number='<span id="unenroll_course_number"></span>')}<span class="sr">, ${_("modal open")}</span></h2>
<h2 id="unenrollment-modal-title">
${_('<span id="track-info"></span> {course_number}? <span id="refund-info"></span>').format(course_number='<span id="unenroll_course_number"></span>')}
<span class="sr">,
## Translators: this text gives status on if the modal interface (a menu or piece of UI that takes the full focus of the screen) is open or not
${_("modal open")}
</span>
</h2>
<hr/>
</header>
<div id="unenroll_error" class="modal-form-error"></div>
......
<%! from django.utils.translation import ugettext as _ %>
<%namespace name='static' file='../static_content.html'/>
<li class="status status-language">
<span class="title status-title">
<i class="icon icon-flag-alt"></i>
${_("Preferred Language")}
(<a href="#change_language" rel="leanModal" class="edit-language">${_("edit")}</a>)
</span>
<span class="data">${current_language}</span>
</li>
......@@ -4,7 +4,12 @@
<%! from django.core.urlresolvers import reverse %>
<section id="forgot-password-modal" class="modal" role="dialog" aria-label="${_('Password Reset')}">
<div class="inner-wrapper">
<button class="close-modal">&#10005; <span class="sr">${_('Close Modal')}</span></button>
<button class="close-modal">&#10005;
<span class="sr">
## Translators: this is a control to allow users to exit out of this modal interface (a menu or piece of UI that takes the full focus of the screen)
${_('Close Modal')}
</span>
</button>
<div id="password-reset">
<header>
......
......@@ -16,7 +16,13 @@
<section id="help-modal" class="modal" aria-hidden="true" role="dialog" aria-label="${_("{platform_name} Help").format(platform_name=MicrositeConfiguration.get_microsite_configuration_value("platform_name", settings.PLATFORM_NAME))}">
<div class="inner-wrapper" id="help_wrapper">
## TODO: find a way to refactor this
<button class="close-modal "tabindex="0">&#10005; <span class="sr">${_('Close Modal')}</span></button>
<button class="close-modal "tabindex="0">
&#10005;
<span class="sr">
## Translators: this is a control to allow users to exit out of this modal interface (a menu or piece of UI that takes the full focus of the screen)
${_('Close Modal')}
</span>
</button>
<header>
<h2>${_('{span_start}{platform_name}{span_end} Help').format(span_start='<span class="edx">', span_end='</span>', platform_name=MicrositeConfiguration.get_microsite_configuration_value('platform_name', settings.PLATFORM_NAME))}</h2>
......@@ -54,7 +60,13 @@ discussion_link = get_discussion_link(course) if course else None
</div>
<div class="inner-wrapper" id="feedback_form_wrapper">
<button class="close-modal">&#10005; <span class="sr">${_('Close Modal')}</span></button>
<button class="close-modal">
&#10005;
<span class="sr">
## Translators: this is a control to allow users to exit out of this modal interface (a menu or piece of UI that takes the full focus of the screen)
${_('Close Modal')}
</span>
</button>
<header></header>
......@@ -82,7 +94,13 @@ discussion_link = get_discussion_link(course) if course else None
</div>
<div class="inner-wrapper" id="feedback_success_wrapper" tabindex="0">
<button class="close-modal "tabindex="0">&#10005; <span class="sr">${_('Close Modal')}</span></button>
<button class="close-modal" tabindex="0">
&#10005;
<span class="sr">
## Translators: this is a control to allow users to exit out of this modal interface (a menu or piece of UI that takes the full focus of the screen)
${_('Close Modal')}
</span>
</button>
<header>
<h2>${_('Thank You!')}</h2>
......
......@@ -5,7 +5,12 @@
<section id="login-modal" class="modal login-modal">
<div class="inner-wrapper">
<button class="close-modal">&#10005; <span class="sr">${_('Close Modal')}</span></button>
<button class="close-modal">&#10005;
<span class="sr">
## Translators: this is a control to allow users to exit out of this modal interface (a menu or piece of UI that takes the full focus of the screen)
${_('Close Modal')}
</span>
</button>
<header>
<h2>${_("Log In")}</h2>
......
<%! from django.utils.translation import ugettext as _ %>
<%!
from django.core.urlresolvers import reverse
%>
<%namespace name='static' file='../static_content.html'/>
<section id="change_language" class="modal modal-settings-language" aria-hidden="true">
<div class="inner-wrapper" role="dialog" aria-labelledby="change_language_title">
<button class="close-modal">&#10005;
<span class="sr">
## Translators: this is a control to allow users to exit out of this modal interface (a menu or piece of UI that takes the full focus of the screen)
${_('Close Modal')}
</span>
</button>
<header>
<h2>
<span id="change_language_title">${_("Change Preferred Language")}</span>
<span class="sr">,
## Translators: this text gives status on if the modal interface (a menu or piece of UI that takes the full focus of the screen) is open or not
${_("modal open")}
</span>
</h2>
<hr/>
</header>
<div id="change_language_body">
<form action="/i18n/setlang/" method="post" class="settings-language-form" id="settings-form">
<input type="hidden" name="csrfmiddlewaretoken" value="${csrf_token}">
<ol class="list-input">
<li class="field text settings-language-select" id="settings-language-select">
<label class="label sr" for="settings-language-value">${_("Please choose your preferred language")}</label>
<select class="input select" id="settings-language-value" name="language">
% for abbrv in language_options:
% for language in settings.LANGUAGES:
% if abbrv == language[0]:
% if abbrv == current_language_code:
<option value="${language[0]}" selected="selected">${language[1]}</option>
% else:
<option value="${language[0]}">${language[1]}</option>
% endif
% endif
% endfor
% endfor
</select>
</li>
</ol>
<div class="submit">
<input type="submit" id="submit-lang" value="${_('Save Language Settings')}" />
</div>
</form>
<ul class="list list-actions actions-supplemental">
<li class="list-actions-item">
${_("Don't see your preferred language? {link_start}Volunteer to become a translator!{link_end}").format(link_start='<a class=" action action-volunteer" rel="external" target="_blank" href="https://github.com/edx/edx-platform/blob/master/docs/en_us/developers/source/i18n_translators_guide.rst">', link_end="</a>")}
</li>
</ul>
</div>
</div>
</section>
......@@ -10,7 +10,12 @@
<section id="signup-modal" class="modal signup-modal">
<div class="inner-wrapper">
<button class="close-modal">&#10005; <span class="sr">${_('Close Modal')}</span></button>
<button class="close-modal">&#10005;
<span class="sr">
## Translators: this is a control to allow users to exit out of this modal interface (a menu or piece of UI that takes the full focus of the screen)
${_('Close Modal')}
</span>
</button>
<div id="register">
<header>
......
......@@ -61,7 +61,11 @@ urlpatterns = ('', # nopep8
url(r'^user_api/', include('user_api.urls')),
url(r'^lang_pref/', include('lang_pref.urls')),
url(r'^', include('waffle.urls')),
url(r'^i18n/', include('django.conf.urls.i18n')),
)
# if settings.FEATURES.get("MULTIPLE_ENROLLMENT_ROLES"):
......
......@@ -195,7 +195,7 @@ namespace :'test:bok_choy' do
# Clear any test data already in Mongo or MySQL and invalidate the cache
clear_mongo()
BOK_CHOY_CACHE.flush()
sh(django_admin('lms', 'bok_choy', 'flush', '--noinput'))
sh(django_admin('lms', 'bok_choy', 'loaddata', 'common/test/db_fixtures/*.json'))
# Ensure the test servers are available
puts "Starting test servers...".green
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment