WL-243 LMS Language Selection
This commit is contained in:
@@ -5,6 +5,8 @@ Middleware for Language Preferences
|
||||
from openedx.core.djangoapps.user_api.preferences.api import get_user_preference
|
||||
from lang_pref import LANGUAGE_KEY
|
||||
from django.utils.translation import LANGUAGE_SESSION_KEY
|
||||
from django.utils.translation.trans_real import parse_accept_lang_header
|
||||
from lang_pref.api import released_languages
|
||||
|
||||
|
||||
class LanguagePreferenceMiddleware(object):
|
||||
@@ -26,3 +28,13 @@ class LanguagePreferenceMiddleware(object):
|
||||
# Set it to the LANGUAGE_SESSION_KEY (Django-specific session setting governing language pref)
|
||||
if user_pref:
|
||||
request.session[LANGUAGE_SESSION_KEY] = user_pref
|
||||
else:
|
||||
# Setting the session language to the browser language, if it is supported.
|
||||
preferred_language = request.META.get('HTTP_ACCEPT_LANGUAGE', '')
|
||||
lang_headers = [seq[0] for seq in parse_accept_lang_header(preferred_language)]
|
||||
languages = released_languages()
|
||||
for browser_lang in lang_headers:
|
||||
if browser_lang in [seq[0] for seq in languages]:
|
||||
if request.session.get(LANGUAGE_SESSION_KEY, None) != browser_lang:
|
||||
request.session[LANGUAGE_SESSION_KEY] = unicode(browser_lang)
|
||||
break
|
||||
|
||||
@@ -4,9 +4,10 @@ from django.contrib.sessions.middleware import SessionMiddleware
|
||||
from django.utils.translation import LANGUAGE_SESSION_KEY
|
||||
|
||||
from lang_pref.middleware import LanguagePreferenceMiddleware
|
||||
from openedx.core.djangoapps.user_api.preferences.api import set_user_preference
|
||||
from openedx.core.djangoapps.user_api.preferences.api import set_user_preference, get_user_preference
|
||||
from lang_pref import LANGUAGE_KEY
|
||||
from student.tests.factories import UserFactory
|
||||
import mock
|
||||
|
||||
|
||||
class TestUserPreferenceMiddleware(TestCase):
|
||||
@@ -21,6 +22,7 @@ class TestUserPreferenceMiddleware(TestCase):
|
||||
self.user = UserFactory.create()
|
||||
self.request = RequestFactory().get('/somewhere')
|
||||
self.request.user = self.user
|
||||
self.request.META['HTTP_ACCEPT_LANGUAGE'] = 'ar;q=1.0' # pylint: disable=no-member
|
||||
self.session_middleware.process_request(self.request)
|
||||
|
||||
def test_no_language_set_in_session_or_prefs(self):
|
||||
@@ -46,3 +48,21 @@ class TestUserPreferenceMiddleware(TestCase):
|
||||
self.middleware.process_request(self.request)
|
||||
|
||||
self.assertEquals(self.request.session[LANGUAGE_SESSION_KEY], 'eo')
|
||||
|
||||
@mock.patch('lang_pref.middleware.released_languages',
|
||||
mock.Mock(return_value=[('eo', 'dummy Esperanto'), ('ar', 'arabic')]))
|
||||
def test_supported_browser_language_in_session(self):
|
||||
"""
|
||||
test: browser language should be set in user preferences if it is supported by system.
|
||||
"""
|
||||
self.assertEquals(get_user_preference(self.request.user, LANGUAGE_KEY), None)
|
||||
self.middleware.process_request(self.request)
|
||||
self.assertEqual(self.request.session[LANGUAGE_SESSION_KEY], 'ar') # pylint: disable=no-member
|
||||
|
||||
@mock.patch('lang_pref.middleware.released_languages', mock.Mock(return_value=[('en', 'english')]))
|
||||
def test_browser_language_not_be_in_session(self):
|
||||
"""
|
||||
test: browser language should not be set in user preferences if it is not supported by system.
|
||||
"""
|
||||
self.middleware.process_request(self.request)
|
||||
self.assertNotEqual(self.request.session.get(LANGUAGE_SESSION_KEY), 'ar') # pylint: disable=no-member
|
||||
|
||||
@@ -124,3 +124,7 @@ else:
|
||||
<%def name="is_request_in_themed_site()"><%
|
||||
return is_request_in_themed_site()
|
||||
%></%def>
|
||||
|
||||
<%def name="show_language_selector()"><%
|
||||
return settings.FEATURES.get('SHOW_LANGUAGE_SELECTOR', False)
|
||||
%></%def>
|
||||
|
||||
9
common/static/js/fixtures/lang-edx-fixture.html
Normal file
9
common/static/js/fixtures/lang-edx-fixture.html
Normal file
@@ -0,0 +1,9 @@
|
||||
<form action="/i18n/setlang/" method="post" class="settings-language-form" id="language-settings-form">
|
||||
<input title="preference api" type="hidden" id="preference-api-url" value="/api/user/v1/preferences/test1/">
|
||||
<label><span class="sr">Choose Language</span>
|
||||
<select class="input select" id="settings-language-value" name="language">
|
||||
<option value="en" selected="selected">English</option>
|
||||
<option value="ar" >Arabic</option>
|
||||
</select>
|
||||
</label>
|
||||
</form>
|
||||
60
common/static/js/spec/lang_edx_spec.js
Normal file
60
common/static/js/spec/lang_edx_spec.js
Normal file
@@ -0,0 +1,60 @@
|
||||
/*global Language:true*/
|
||||
(function() {
|
||||
'use strict';
|
||||
describe('Language change test for lang-edx.js', function () {
|
||||
|
||||
var lang_selector,
|
||||
deferred;
|
||||
|
||||
beforeEach(function () {
|
||||
loadFixtures('js/fixtures/lang-edx-fixture.html');
|
||||
lang_selector = $('#settings-language-value');
|
||||
deferred = $.Deferred();
|
||||
});
|
||||
|
||||
it("can spy on language selector change event", function () {
|
||||
spyOnEvent(lang_selector, 'change');
|
||||
lang_selector.trigger('change');
|
||||
expect('change').toHaveBeenTriggeredOn(lang_selector);
|
||||
});
|
||||
|
||||
it("should make an AJAX request to the correct URL", function () {
|
||||
spyOn($, 'ajax').andReturn(deferred);
|
||||
Language.init();
|
||||
lang_selector.trigger('change');
|
||||
expect($.ajax.mostRecentCall.args[0].url).toEqual("/api/user/v1/preferences/test1/");
|
||||
});
|
||||
|
||||
it("should make an AJAX request with correct type", function () {
|
||||
spyOn($, 'ajax').andReturn(deferred);
|
||||
Language.init();
|
||||
lang_selector.trigger('change');
|
||||
expect($.ajax.mostRecentCall.args[0].type).toEqual("PATCH");
|
||||
});
|
||||
|
||||
it("should make an AJAX request with correct data", function () {
|
||||
spyOn($, 'ajax').andReturn(deferred);
|
||||
Language.init();
|
||||
lang_selector.val('ar');
|
||||
lang_selector.trigger('change');
|
||||
expect($.ajax.mostRecentCall.args[0].data).toEqual('{"pref-lang":"ar"}');
|
||||
|
||||
// change to 'en' from 'ar'
|
||||
lang_selector.val('en');
|
||||
lang_selector.trigger('change');
|
||||
expect($.ajax.mostRecentCall.args[0].data).toEqual('{"pref-lang":"en"}');
|
||||
});
|
||||
|
||||
it("should call refresh on ajax failure", function () {
|
||||
spyOn($, 'ajax').andCallFake(function () {
|
||||
var d = $.Deferred();
|
||||
d.reject();
|
||||
return d.promise();
|
||||
});
|
||||
Language.init();
|
||||
spyOn(Language, 'refresh');
|
||||
lang_selector.trigger('change');
|
||||
expect(Language.refresh).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
}).call(this);
|
||||
55
common/static/js/src/lang_edx.js
Normal file
55
common/static/js/src/lang_edx.js
Normal file
@@ -0,0 +1,55 @@
|
||||
var edx = edx || {},
|
||||
Language = (function() {
|
||||
'use strict';
|
||||
var preference_api_url,
|
||||
settings_language_selector,
|
||||
self = null;
|
||||
return {
|
||||
init: function() {
|
||||
preference_api_url = $('#preference-api-url').val();
|
||||
settings_language_selector = $('#settings-language-value');
|
||||
self = this;
|
||||
this.listenForLanguagePreferenceChange();
|
||||
},
|
||||
|
||||
/**
|
||||
* Listener on changing language from selector.
|
||||
* Send an ajax request to save user language preferences.
|
||||
*/
|
||||
listenForLanguagePreferenceChange: function() {
|
||||
settings_language_selector.change(function(event) {
|
||||
var language = this.value;
|
||||
event.preventDefault();
|
||||
$.ajax({
|
||||
type: 'PATCH',
|
||||
data: JSON.stringify({'pref-lang': language}) ,
|
||||
url: preference_api_url,
|
||||
dataType: 'json',
|
||||
contentType: "application/merge-patch+json",
|
||||
beforeSend: function (xhr) {
|
||||
xhr.setRequestHeader("X-CSRFToken", $('#csrf_token').val());
|
||||
}
|
||||
}).done(function () {
|
||||
// User language preference has been set successfully
|
||||
// Now submit the form in success callback.
|
||||
$("#language-settings-form").submit();
|
||||
}).fail(function() {
|
||||
self.refresh();
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* refresh the page.
|
||||
*/
|
||||
refresh: function () {
|
||||
// reloading the page so we can get the latest state of realsesd languages from model
|
||||
location.reload();
|
||||
}
|
||||
|
||||
};
|
||||
})();
|
||||
$(document).ready(function () {
|
||||
'use strict';
|
||||
Language.init();
|
||||
});
|
||||
@@ -192,3 +192,14 @@ class DashboardPage(PageObject):
|
||||
Click on `Account` link.
|
||||
"""
|
||||
self.q(css='.dropdown-menu li a').nth(2).click()
|
||||
|
||||
@property
|
||||
def language_selector(self):
|
||||
"""
|
||||
return language selector
|
||||
"""
|
||||
self.wait_for_element_visibility(
|
||||
'#settings-language-value',
|
||||
'Language selector element is available'
|
||||
)
|
||||
return self.q(css='#settings-language-value')
|
||||
|
||||
@@ -16,7 +16,9 @@ from ..helpers import (
|
||||
load_data_str,
|
||||
generate_course_key,
|
||||
select_option_by_value,
|
||||
element_has_text
|
||||
element_has_text,
|
||||
select_option_by_text,
|
||||
get_selected_option_text
|
||||
)
|
||||
from ...pages.lms import BASE_URL
|
||||
from ...pages.lms.account_settings import AccountSettingsPage
|
||||
@@ -1130,3 +1132,50 @@ class NotLiveRedirectTest(UniqueCourseTest):
|
||||
'The course you are looking for does not start until',
|
||||
page.banner_text
|
||||
)
|
||||
|
||||
|
||||
@attr('shard_1')
|
||||
class LMSLanguageTest(UniqueCourseTest):
|
||||
""" Test suite for the LMS Language """
|
||||
def setUp(self):
|
||||
super(LMSLanguageTest, self).setUp()
|
||||
self.dashboard_page = DashboardPage(self.browser)
|
||||
self.account_settings = AccountSettingsPage(self.browser)
|
||||
AutoAuthPage(self.browser).visit()
|
||||
|
||||
def test_lms_language_change(self):
|
||||
"""
|
||||
Scenario: Ensure that language selection is working fine.
|
||||
First I go to the user dashboard page in LMS. I can see 'English' is selected by default.
|
||||
Then I choose 'Dummy Language' from drop down (at top of the page).
|
||||
Then I visit the student account settings page and I can see the language has been updated to 'Dummy Language'
|
||||
in both drop downs.
|
||||
After that I select the 'English' language and visit the dashboard page again.
|
||||
Then I can see that top level language selector persist its value to 'English'.
|
||||
"""
|
||||
self.dashboard_page.visit()
|
||||
language_selector = self.dashboard_page.language_selector
|
||||
self.assertEqual(
|
||||
get_selected_option_text(language_selector),
|
||||
u'English'
|
||||
)
|
||||
|
||||
select_option_by_text(language_selector, 'Dummy Language (Esperanto)')
|
||||
self.dashboard_page.wait_for_ajax()
|
||||
self.account_settings.visit()
|
||||
self.assertEqual(self.account_settings.value_for_dropdown_field('pref-lang'), u'Dummy Language (Esperanto)')
|
||||
self.assertEqual(
|
||||
get_selected_option_text(language_selector),
|
||||
u'Dummy Language (Esperanto)'
|
||||
)
|
||||
|
||||
# changed back to English language.
|
||||
select_option_by_text(language_selector, 'English')
|
||||
self.account_settings.wait_for_ajax()
|
||||
self.assertEqual(self.account_settings.value_for_dropdown_field('pref-lang'), u'English')
|
||||
|
||||
self.dashboard_page.visit()
|
||||
self.assertEqual(
|
||||
get_selected_option_text(language_selector),
|
||||
u'English'
|
||||
)
|
||||
|
||||
@@ -88,7 +88,8 @@
|
||||
"MODE_CREATION_FOR_TESTING": true,
|
||||
"AUTOMATIC_VERIFY_STUDENT_IDENTITY_FOR_TESTING": true,
|
||||
"ENABLE_COURSE_DISCOVERY": true,
|
||||
"ENABLE_SPECIAL_EXAMS": true
|
||||
"ENABLE_SPECIAL_EXAMS": true,
|
||||
"SHOW_LANGUAGE_SELECTOR": true
|
||||
},
|
||||
"FEEDBACK_SUBMISSION_EMAIL": "",
|
||||
"GITHUB_REPO_ROOT": "** OVERRIDDEN **",
|
||||
|
||||
@@ -367,6 +367,9 @@ FEATURES = {
|
||||
|
||||
# Enable LTI Provider feature.
|
||||
'ENABLE_LTI_PROVIDER': False,
|
||||
|
||||
# Show LMS Language selector
|
||||
'SHOW_LANGUAGE_SELECTOR': False,
|
||||
}
|
||||
|
||||
# Ignore static asset files on import which match this pattern
|
||||
@@ -1249,6 +1252,7 @@ base_application_js = [
|
||||
'js/src/ie_shim.js',
|
||||
'js/src/accessibility_tools.js',
|
||||
'js/toggle_login_modal.js',
|
||||
'js/src/lang_edx.js',
|
||||
]
|
||||
|
||||
dashboard_js = (
|
||||
|
||||
@@ -114,7 +114,7 @@
|
||||
this.showNotificationMessage(successMessage);
|
||||
|
||||
if (this.options.refreshPageOnSave) {
|
||||
document.location.reload();
|
||||
location.reload(true);
|
||||
}
|
||||
|
||||
var view = this;
|
||||
|
||||
@@ -118,6 +118,10 @@ header.global {
|
||||
margin-top: ($baseline/4);
|
||||
padding-left: 0;
|
||||
|
||||
.settings-language-form {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
> .primary {
|
||||
display: block;
|
||||
@include float(left);
|
||||
|
||||
@@ -11,6 +11,7 @@ from lms.djangoapps.ccx.overrides import get_current_ccx
|
||||
from branding import api as branding_api
|
||||
# app that handles site status messages
|
||||
from status.status import get_site_status_msg
|
||||
from lang_pref.api import released_languages
|
||||
%>
|
||||
|
||||
## Provide a hook for themes to inject branding on top.
|
||||
@@ -75,6 +76,28 @@ site_status_msg = get_site_status_msg(course_id)
|
||||
</%block>
|
||||
</ol>
|
||||
<ol class="user">
|
||||
% if static.show_language_selector():
|
||||
<% languages = released_languages() %>
|
||||
% if len(languages) > 1:
|
||||
<li class="primary">
|
||||
<form action="/i18n/setlang/" method="post" class="settings-language-form" id="language-settings-form">
|
||||
<input type="hidden" id="csrf_token" name="csrfmiddlewaretoken" value="${csrf_token}">
|
||||
<input title="preference api" type="hidden" id="preference-api-url" value="${reverse('preferences_api', kwargs={'username': user.username})}">
|
||||
<label><span class="sr">${_("Choose Language")}</span>
|
||||
<select class="input select" id="settings-language-value" name="language">
|
||||
% for language in languages:
|
||||
% if language[0] == LANGUAGE_CODE:
|
||||
<option value="${language[0]}" selected="selected">${language[1]}</option>
|
||||
% else:
|
||||
<option value="${language[0]}" >${language[1]}</option>
|
||||
% endif
|
||||
% endfor
|
||||
</select>
|
||||
</label>
|
||||
</form>
|
||||
</li>
|
||||
% endif
|
||||
% endif
|
||||
<li class="primary">
|
||||
<a href="${reverse('dashboard')}" class="user-link">
|
||||
<span class="sr">${_("Dashboard for:")}</span>
|
||||
|
||||
Reference in New Issue
Block a user