Commit 99b883b7 by asadiqbal

WL-243 LMS Language Selection

parent 0c55f17d
...@@ -5,6 +5,8 @@ Middleware for Language Preferences ...@@ -5,6 +5,8 @@ Middleware for Language Preferences
from openedx.core.djangoapps.user_api.preferences.api import get_user_preference from openedx.core.djangoapps.user_api.preferences.api import get_user_preference
from lang_pref import LANGUAGE_KEY from lang_pref import LANGUAGE_KEY
from django.utils.translation import LANGUAGE_SESSION_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): class LanguagePreferenceMiddleware(object):
...@@ -26,3 +28,13 @@ class LanguagePreferenceMiddleware(object): ...@@ -26,3 +28,13 @@ class LanguagePreferenceMiddleware(object):
# Set it to the LANGUAGE_SESSION_KEY (Django-specific session setting governing language pref) # Set it to the LANGUAGE_SESSION_KEY (Django-specific session setting governing language pref)
if user_pref: if user_pref:
request.session[LANGUAGE_SESSION_KEY] = 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 ...@@ -4,9 +4,10 @@ from django.contrib.sessions.middleware import SessionMiddleware
from django.utils.translation import LANGUAGE_SESSION_KEY from django.utils.translation import LANGUAGE_SESSION_KEY
from lang_pref.middleware import LanguagePreferenceMiddleware 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 lang_pref import LANGUAGE_KEY
from student.tests.factories import UserFactory from student.tests.factories import UserFactory
import mock
class TestUserPreferenceMiddleware(TestCase): class TestUserPreferenceMiddleware(TestCase):
...@@ -21,6 +22,7 @@ class TestUserPreferenceMiddleware(TestCase): ...@@ -21,6 +22,7 @@ class TestUserPreferenceMiddleware(TestCase):
self.user = UserFactory.create() self.user = UserFactory.create()
self.request = RequestFactory().get('/somewhere') self.request = RequestFactory().get('/somewhere')
self.request.user = self.user 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) self.session_middleware.process_request(self.request)
def test_no_language_set_in_session_or_prefs(self): def test_no_language_set_in_session_or_prefs(self):
...@@ -46,3 +48,21 @@ class TestUserPreferenceMiddleware(TestCase): ...@@ -46,3 +48,21 @@ class TestUserPreferenceMiddleware(TestCase):
self.middleware.process_request(self.request) self.middleware.process_request(self.request)
self.assertEquals(self.request.session[LANGUAGE_SESSION_KEY], 'eo') 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: ...@@ -124,3 +124,7 @@ else:
<%def name="is_request_in_themed_site()"><% <%def name="is_request_in_themed_site()"><%
return is_request_in_themed_site() return is_request_in_themed_site()
%></%def> %></%def>
<%def name="show_language_selector()"><%
return settings.FEATURES.get('SHOW_LANGUAGE_SELECTOR', False)
%></%def>
<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>
/*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);
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): ...@@ -192,3 +192,14 @@ class DashboardPage(PageObject):
Click on `Account` link. Click on `Account` link.
""" """
self.q(css='.dropdown-menu li a').nth(2).click() 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 ( ...@@ -16,7 +16,9 @@ from ..helpers import (
load_data_str, load_data_str,
generate_course_key, generate_course_key,
select_option_by_value, 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 import BASE_URL
from ...pages.lms.account_settings import AccountSettingsPage from ...pages.lms.account_settings import AccountSettingsPage
...@@ -1130,3 +1132,50 @@ class NotLiveRedirectTest(UniqueCourseTest): ...@@ -1130,3 +1132,50 @@ class NotLiveRedirectTest(UniqueCourseTest):
'The course you are looking for does not start until', 'The course you are looking for does not start until',
page.banner_text 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 @@ ...@@ -88,7 +88,8 @@
"MODE_CREATION_FOR_TESTING": true, "MODE_CREATION_FOR_TESTING": true,
"AUTOMATIC_VERIFY_STUDENT_IDENTITY_FOR_TESTING": true, "AUTOMATIC_VERIFY_STUDENT_IDENTITY_FOR_TESTING": true,
"ENABLE_COURSE_DISCOVERY": true, "ENABLE_COURSE_DISCOVERY": true,
"ENABLE_SPECIAL_EXAMS": true "ENABLE_SPECIAL_EXAMS": true,
"SHOW_LANGUAGE_SELECTOR": true
}, },
"FEEDBACK_SUBMISSION_EMAIL": "", "FEEDBACK_SUBMISSION_EMAIL": "",
"GITHUB_REPO_ROOT": "** OVERRIDDEN **", "GITHUB_REPO_ROOT": "** OVERRIDDEN **",
......
...@@ -367,6 +367,9 @@ FEATURES = { ...@@ -367,6 +367,9 @@ FEATURES = {
# Enable LTI Provider feature. # Enable LTI Provider feature.
'ENABLE_LTI_PROVIDER': False, 'ENABLE_LTI_PROVIDER': False,
# Show LMS Language selector
'SHOW_LANGUAGE_SELECTOR': False,
} }
# Ignore static asset files on import which match this pattern # Ignore static asset files on import which match this pattern
...@@ -1249,6 +1252,7 @@ base_application_js = [ ...@@ -1249,6 +1252,7 @@ base_application_js = [
'js/src/ie_shim.js', 'js/src/ie_shim.js',
'js/src/accessibility_tools.js', 'js/src/accessibility_tools.js',
'js/toggle_login_modal.js', 'js/toggle_login_modal.js',
'js/src/lang_edx.js',
] ]
dashboard_js = ( dashboard_js = (
......
...@@ -114,7 +114,7 @@ ...@@ -114,7 +114,7 @@
this.showNotificationMessage(successMessage); this.showNotificationMessage(successMessage);
if (this.options.refreshPageOnSave) { if (this.options.refreshPageOnSave) {
document.location.reload(); location.reload(true);
} }
var view = this; var view = this;
......
...@@ -118,6 +118,10 @@ header.global { ...@@ -118,6 +118,10 @@ header.global {
margin-top: ($baseline/4); margin-top: ($baseline/4);
padding-left: 0; padding-left: 0;
.settings-language-form {
margin-top: 4px;
}
> .primary { > .primary {
display: block; display: block;
@include float(left); @include float(left);
......
...@@ -11,6 +11,7 @@ from lms.djangoapps.ccx.overrides import get_current_ccx ...@@ -11,6 +11,7 @@ from lms.djangoapps.ccx.overrides import get_current_ccx
from branding import api as branding_api from branding import api as branding_api
# app that handles site status messages # app that handles site status messages
from status.status import get_site_status_msg 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. ## Provide a hook for themes to inject branding on top.
...@@ -75,6 +76,28 @@ site_status_msg = get_site_status_msg(course_id) ...@@ -75,6 +76,28 @@ site_status_msg = get_site_status_msg(course_id)
</%block> </%block>
</ol> </ol>
<ol class="user"> <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"> <li class="primary">
<a href="${reverse('dashboard')}" class="user-link"> <a href="${reverse('dashboard')}" class="user-link">
<span class="sr">${_("Dashboard for:")}</span> <span class="sr">${_("Dashboard for:")}</span>
......
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