Add support for beta languages.

Whenever user selects a beta language on account settings page,
show a page level warning message that this language is not
fully translated along with action to revert.

LEARNER-5654
This commit is contained in:
Waheed Ahmed
2018-04-25 13:36:34 +05:00
parent 945debb00f
commit d76a2463fa
16 changed files with 330 additions and 91 deletions

View File

@@ -39,6 +39,8 @@ define(['underscore'], function(_) {
options: FIELD_OPTIONS
}, language: {
options: FIELD_OPTIONS
}, beta_language: {
options: []
}, level_of_education: {
options: FIELD_OPTIONS
}, password: {

View File

@@ -28,7 +28,8 @@
edxSupportUrl,
extendedProfileFields,
displayAccountDeletion,
isSecondaryEmailFeatureEnabled
isSecondaryEmailFeatureEnabled,
betaLanguage
) {
var $accountSettingsElement, userAccountModel, userPreferencesModel, aboutSectionsData,
accountsSectionData, ordersSectionData, accountSettingsView, showAccountSettingsPage,
@@ -36,7 +37,8 @@
emailFieldView, secondaryEmailFieldView, socialFields, accountDeletionFields, platformData,
aboutSectionMessageType, aboutSectionMessage, fullnameFieldView, countryFieldView,
fullNameFieldData, emailFieldData, secondaryEmailFieldData, countryFieldData, additionalFields,
fieldItem, emailFieldViewIndex;
fieldItem, emailFieldViewIndex, focusId,
tabIndex = 0;
$accountSettingsElement = $('.wrapper-account-settings');
@@ -418,15 +420,24 @@
accountsTabSections: accountsSectionData,
ordersTabSections: ordersSectionData
},
userPreferencesModel: userPreferencesModel
userPreferencesModel: userPreferencesModel,
betaLanguage: betaLanguage
});
accountSettingsView.render();
if( $.cookie('focus_id')) {
$($.cookie('focus_id')).attr({"tabindex": 0});
$($.cookie('focus_id')).focus();
focusId = $.cookie('focus_id');
if (focusId) {
if (~focusId.indexOf('beta-language')) {
tabIndex = -1;
// Scroll to top of selected element
$('html, body').animate({
scrollTop: $(focusId).offset().top
}, 'slow');
}
$(focusId).attr({tabindex: tabIndex}).focus();
// Deleting the cookie
document.cookie = "focus_id=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/account;";
document.cookie = 'focus_id=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/account;';
}
showAccountSettingsPage = function() {
// Record that the account settings page was viewed.

View File

@@ -50,6 +50,22 @@
}),
LanguagePreferenceFieldView: FieldViews.DropdownFieldView.extend({
fieldTemplate: field_dropdown_account_template,
initialize: function(options) {
this._super(options); // eslint-disable-line no-underscore-dangle
this.listenTo(this.model, 'revertValue', this.revertValue);
},
revertValue: function(event) {
var attributes = {},
oldPrefLang = $(event.target).data('old-lang-code');
if (oldPrefLang) {
attributes['pref-lang'] = oldPrefLang;
this.saveAttributes(attributes);
}
},
saveSucceeded: function() {
var data = {
language: this.modelValue(),

View File

@@ -42,7 +42,8 @@
],
events: {
'click .account-nav-link': 'switchTab',
'keydown .account-nav-link': 'keydownHandler'
'keydown .account-nav-link': 'keydownHandler',
'click .btn-alert-primary': 'revertValue'
},
initialize: function(options) {
@@ -51,10 +52,37 @@
},
render: function() {
var tabName,
var tabName, betaLangMessage, helpTranslateText, helpTranslateLink, betaLangCode, oldLangCode,
view = this;
if (!_.isEmpty(view.options.betaLanguage) && $.cookie('old-pref-lang')) {
betaLangMessage = HtmlUtils.interpolateHtml(
gettext('You have set your language to {beta_language}, which is currently not fully translated. You can help us translate this language fully by joining the Transifex community and adding translations from English for learners that speak {beta_language}.'), // eslint-disable-line max-len
{
beta_language: view.options.betaLanguage.name
}
);
helpTranslateText = HtmlUtils.interpolateHtml(
gettext('Help Translate into {beta_language}'),
{
beta_language: view.options.betaLanguage.name
}
);
betaLangCode = this.options.betaLanguage.code.split('-');
betaLangCode = betaLangCode[0] + '_' + betaLangCode[1].toUpperCase();
helpTranslateLink = 'https://www.transifex.com/open-edx/edx-platform/translate/#' + betaLangCode;
oldLangCode = $.cookie('old-pref-lang');
// Deleting the cookie
document.cookie = 'old-pref-lang=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/account;';
$.cookie('focus_id', '#beta-language-message');
}
HtmlUtils.setHtml(this.$el, HtmlUtils.template(accountSettingsTemplate)({
accountSettingsTabs: this.accountSettingsTabs
accountSettingsTabs: this.accountSettingsTabs,
HtmlUtils: HtmlUtils,
message: betaLangMessage,
helpTranslateText: helpTranslateText,
helpTranslateLink: helpTranslateLink,
oldLangCode: oldLangCode
}));
_.each(view.accountSettingsTabs, function(tab) {
tabName = tab.name;
@@ -108,6 +136,10 @@
showLoadingError: function() {
this.$('.ui-loading-error').removeClass('is-hidden');
},
revertValue: function(event) {
this.options.userPreferencesModel.trigger('revertValue', event);
}
});

View File

@@ -495,6 +495,7 @@
saveValue: function() {
var attributes = {};
attributes[this.options.valueAttribute] = this.fieldValue();
$.cookie('old-pref-lang', this.modelValue());
this.saveAttributes(attributes);
},

View File

@@ -5,6 +5,7 @@
// * +Container - Account Settings
// * +Main - Header
// * +Settings Section
// * +Alert Messages
// +Container - Account Settings
@@ -135,59 +136,6 @@
color: $alert-color;
}
.account-settings-section-message {
font-size: 16px;
line-height: 22px;
margin-top: 15px;
margin-bottom: 30px;
.alert-message {
color: #292b2c;
font-family: $font-family-sans-serif;
position: relative;
padding: 10px 10px 10px 35px;
border: 1px solid transparent;
border-radius: 0;
box-shadow: none;
margin-bottom: 8px;
& > .fa {
position: absolute;
left: 11px;
top: 13px;
font-size: 16px;
}
span {
display: block;
a {
text-decoration: underline;
}
}
}
.success {
background-color: #ecfaec;
border-color: #b9edb9;
}
.info {
background-color: #d8edf8;
border-color: #bbdff2;
}
.warning {
background-color: #fcf8e3;
border-color: #faebcc;
}
.error {
background-color: #f2dede;
border-color: #ebccd1;
}
}
.account-settings-section-body {
.u-field {
border-bottom: 2px solid $m-gray-l4;
@@ -637,3 +585,97 @@
}
}
}
// * +Alert Messages
.account-settings-message,
.account-settings-section-message {
font-size: 16px;
line-height: 22px;
margin-top: 15px;
margin-bottom: 30px;
.alert-message {
color: #292b2c;
font-family: $font-family-sans-serif;
position: relative;
padding: 10px 10px 10px 35px;
border: 1px solid transparent;
border-radius: 0;
box-shadow: none;
margin-bottom: 8px;
& > .fa {
position: absolute;
left: 11px;
top: 13px;
font-size: 16px;
}
span {
display: block;
a {
text-decoration: underline;
}
}
}
.success {
background-color: #ecfaec;
border-color: #b9edb9;
}
.info {
background-color: #d8edf8;
border-color: #bbdff2;
}
.warning {
background-color: #fcf8e3;
border-color: #faebcc;
}
.error {
background-color: #f2dede;
border-color: #ebccd1;
}
}
.account-settings-message {
margin-bottom: 0px;
.alert-message {
padding: 10px;
.alert-actions {
margin-top: 10px;
.btn-alert-primary {
@extend %btn-primary-blue;
@include font-size(18);
border: 1px solid $m-blue-d3;
border-radius: 3px;
box-shadow: none;
padding: 11px 14px;
line-height: normal;
}
.btn-alert-secondary {
@extend %ui-clear-button;
// set styles
@extend %btn-pl-default-base;
@include font-size(18);
background-color: white;
border: 1px solid $blue;
color: $blue;
padding: 11px 14px;
line-height: normal;
}
}
}
}

View File

@@ -68,7 +68,8 @@ from openedx.core.djangoapps.user_api.accounts.utils import is_secondary_email_f
edxSupportUrl,
extendedProfileFields,
displayAccountDeletion,
isSecondaryEmailFeatureEnabled
isSecondaryEmailFeatureEnabled,
${ beta_language | n, dump_js_escaped_json },
);
</%static:require_module>

View File

@@ -1,5 +1,16 @@
<main id="main" aria-label="Content" tabindex="-1">
<div class="account-settings-container">
<% if (message) { %>
<div class="account-settings-message">
<div id="beta-language-message" class="alert-message warning" aria-live="assertive" role="alert">
<span><%= HtmlUtils.ensureHtml(message) %></span>
<div class="alert-actions">
<button class="btn-alert-primary" data-old-lang-code="<%- oldLangCode %>"><%- gettext('Switch Language Back') %></button>
<a href="<%- helpTranslateLink %>" target="_blank" class="btn-alert-secondary"><%= HtmlUtils.ensureHtml(helpTranslateText) %></a>
</div>
</div>
</div>
<% } %>
<div class="wrapper-header">
<h2 class="header-title"><%- gettext("Account Settings") %></h2>
<div class="left list-inline account-nav" role="tablist">

View File

@@ -69,6 +69,16 @@ class DarkLangMiddleware(object):
language_options.append(settings.LANGUAGE_CODE)
return language_options
@property
def beta_langs(self):
"""
Current list of released languages
"""
language_options = DarkLangConfig.current().beta_languages_list
if settings.LANGUAGE_CODE not in language_options:
language_options.append(settings.LANGUAGE_CODE)
return language_options
def process_request(self, request):
"""
Prevent user from requesting un-released languages except by using the preview-lang query string.
@@ -82,11 +92,18 @@ class DarkLangMiddleware(object):
def _fuzzy_match(self, lang_code):
"""Returns a fuzzy match for lang_code"""
match = None
if lang_code in self.released_langs:
dark_lang_config = DarkLangConfig.current()
if dark_lang_config.enable_beta_languages:
langs = self.released_langs + self.beta_langs
else:
langs = self.released_langs
if lang_code in langs:
match = lang_code
else:
lang_prefix = lang_code.partition('-')[0]
for released_lang in self.released_langs:
for released_lang in langs:
released_prefix = released_lang.partition('-')[0]
if lang_prefix == released_prefix:
match = released_lang

View File

@@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.12 on 2018-04-25 07:59
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('dark_lang', '0002_data__enable_on_install'),
]
operations = [
migrations.AddField(
model_name='darklangconfig',
name='beta_languages',
field=models.TextField(blank=True, help_text=b'A comma-separated list of language codes to release to the public as beta languages.'),
),
migrations.AddField(
model_name='darklangconfig',
name='enable_beta_languages',
field=models.BooleanField(default=False, help_text=b'Enable partially supported languages to display in language drop down.'),
),
]

View File

@@ -13,6 +13,14 @@ class DarkLangConfig(ConfigurationModel):
blank=True,
help_text="A comma-separated list of language codes to release to the public."
)
enable_beta_languages = models.BooleanField(
default=False,
help_text="Enable partially supported languages to display in language drop down."
)
beta_languages = models.TextField(
blank=True,
help_text="A comma-separated list of language codes to release to the public as beta languages."
)
def __unicode__(self):
return u"DarkLangConfig()"
@@ -31,3 +39,18 @@ class DarkLangConfig(ConfigurationModel):
# Put in alphabetical order
languages.sort()
return languages
@property
def beta_languages_list(self):
"""
``released_languages`` as a list of language codes.
Example: ['it', 'de-at', 'es', 'pt-br']
"""
if not self.beta_languages.strip():
return []
languages = [lang.lower().strip() for lang in self.beta_languages.split(',')]
# Put in alphabetical order
languages.sort()
return languages

View File

@@ -46,12 +46,20 @@ def released_languages():
[Language(code='en', name=u'English'), Language(code='fr', name=u'Français')]
"""
released_language_codes = DarkLangConfig.current().released_languages_list
dark_lang_config = DarkLangConfig.current()
released_language_codes = dark_lang_config.released_languages_list
default_language_code = settings.LANGUAGE_CODE
if default_language_code not in released_language_codes:
released_language_codes.append(default_language_code)
released_language_codes.sort()
if dark_lang_config.enable_beta_languages:
beta_language_codes = dark_lang_config.beta_languages_list
if beta_language_codes not in released_language_codes:
released_language_codes = released_language_codes + beta_language_codes
released_language_codes.sort()
# Intersect the list of valid language tuples with the list
# of released language codes

View File

@@ -14,6 +14,7 @@ from openedx.core.djangolib.testing.utils import CacheIsolationTestCase
EN = language_api.Language('en', 'English')
ES_419 = language_api.Language('es-419', u'Español (Latinoamérica)')
LT_LT = language_api.Language('lt-lt', u'Lietuvių (Lietuva)')
@ddt.ddt
@@ -98,3 +99,22 @@ class LanguageApiTest(CacheIsolationTestCase):
self.assertEqual("cs", all_languages[1][0])
self.assertEqual(u"Hollandais", all_languages[0][1])
self.assertEqual(u"Tchèque", all_languages[1][1])
def test_beta_languages(self):
"""
Tests for the beta languages.
"""
with override_settings(LANGUAGES=[EN, ES_419, LT_LT], LANGUAGE_CODE='en'):
user = User()
user.save()
DarkLangConfig(
released_languages='es-419',
changed_by=user,
enabled=True,
beta_languages='lt-lt',
enable_beta_languages=True
).save()
released_languages = language_api.released_languages()
expected_languages = [EN, ES_419, LT_LT]
self.assertEqual(released_languages, expected_languages)

View File

@@ -15,9 +15,11 @@ from edxmako.shortcuts import render_to_response
from lms.djangoapps.commerce.models import CommerceConfiguration
from lms.djangoapps.commerce.utils import EcommerceService
from openedx.core.djangoapps.commerce.utils import ecommerce_api_client
from openedx.core.djangoapps.dark_lang.models import DarkLangConfig
from openedx.core.djangoapps.lang_pref.api import all_languages, released_languages
from openedx.core.djangoapps.programs.models import ProgramsApiConfig
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
from openedx.core.djangoapps.user_api.preferences.api import get_user_preferences
from openedx.core.lib.edx_api_utils import get_edx_api_data
from openedx.core.lib.time_zone_utils import TIME_ZONE_CHOICES
from openedx.features.enterprise_support.api import get_enterprise_customer_for_learner
@@ -74,6 +76,15 @@ def account_settings_context(request):
# it will be broken if exception raised
user_orders = []
beta_language = {}
dark_lang_config = DarkLangConfig.current()
if dark_lang_config.enable_beta_languages:
user_preferences = get_user_preferences(user)
pref_language = user_preferences.get('pref-lang')
if pref_language in dark_lang_config.beta_languages_list:
beta_language['code'] = pref_language
beta_language['name'] = settings.LANGUAGE_DICT.get(pref_language)
context = {
'auth': {},
'duplicate_provider': None,
@@ -111,6 +122,7 @@ def account_settings_context(request):
'ENABLE_ACCOUNT_DELETION', settings.FEATURES.get('ENABLE_ACCOUNT_DELETION', False)
),
'extended_profile_fields': _get_extended_profile_fields(),
'beta_language': beta_language
}
enterprise_customer = get_enterprise_customer_for_learner(site=request.site, user=request.user)

View File

@@ -7,13 +7,17 @@ from django.contrib.messages.middleware import MessageMiddleware
from django.urls import reverse
from django.http import HttpRequest
from django.test import TestCase
from django.test.utils import override_settings
from edx_rest_api_client import exceptions
from lms.djangoapps.commerce.models import CommerceConfiguration
from lms.djangoapps.commerce.tests import factories
from lms.djangoapps.commerce.tests.mocks import mock_get_orders
from openedx.core.djangoapps.dark_lang.models import DarkLangConfig
from openedx.core.djangoapps.lang_pref.tests.test_api import EN, LT_LT
from openedx.core.djangoapps.programs.tests.mixins import ProgramsApiConfigMixin
from openedx.core.djangoapps.site_configuration.tests.factories import SiteFactory
from openedx.core.djangoapps.user_api.tests.factories import UserPreferenceFactory
from openedx.core.djangolib.testing.utils import skip_unless_lms
from openedx.core.djangoapps.user_api.accounts.settings_views import account_settings_context, get_user_orders
from student.tests.factories import UserFactory
@@ -60,35 +64,47 @@ class AccountSettingsViewTest(ThirdPartyAuthTestMixin, TestCase, ProgramsApiConf
@mock.patch('openedx.features.enterprise_support.api.get_enterprise_customer_for_learner')
def test_context(self, mock_get_enterprise_customer_for_learner):
self.request.site = SiteFactory.create()
UserPreferenceFactory(user=self.user, key='pref-lang', value='lt-lt')
DarkLangConfig(
released_languages='en',
changed_by=self.user,
enabled=True,
beta_languages='lt-lt',
enable_beta_languages=True
).save()
mock_get_enterprise_customer_for_learner.return_value = {}
context = account_settings_context(self.request)
user_accounts_api_url = reverse("accounts_api", kwargs={'username': self.user.username})
self.assertEqual(context['user_accounts_api_url'], user_accounts_api_url)
with override_settings(LANGUAGES=[EN, LT_LT], LANGUAGE_CODE='en'):
context = account_settings_context(self.request)
user_preferences_api_url = reverse('preferences_api', kwargs={'username': self.user.username})
self.assertEqual(context['user_preferences_api_url'], user_preferences_api_url)
user_accounts_api_url = reverse("accounts_api", kwargs={'username': self.user.username})
self.assertEqual(context['user_accounts_api_url'], user_accounts_api_url)
for attribute in self.FIELDS:
self.assertIn(attribute, context['fields'])
user_preferences_api_url = reverse('preferences_api', kwargs={'username': self.user.username})
self.assertEqual(context['user_preferences_api_url'], user_preferences_api_url)
self.assertEqual(
context['user_accounts_api_url'], reverse("accounts_api", kwargs={'username': self.user.username})
)
self.assertEqual(
context['user_preferences_api_url'], reverse('preferences_api', kwargs={'username': self.user.username})
)
for attribute in self.FIELDS:
self.assertIn(attribute, context['fields'])
self.assertEqual(context['duplicate_provider'], 'facebook')
self.assertEqual(context['auth']['providers'][0]['name'], 'Facebook')
self.assertEqual(context['auth']['providers'][1]['name'], 'Google')
self.assertEqual(
context['user_accounts_api_url'], reverse("accounts_api", kwargs={'username': self.user.username})
)
self.assertEqual(
context['user_preferences_api_url'], reverse('preferences_api', kwargs={'username': self.user.username})
)
self.assertEqual(context['sync_learner_profile_data'], False)
self.assertEqual(context['edx_support_url'], settings.SUPPORT_SITE_LINK)
self.assertEqual(context['enterprise_name'], None)
self.assertEqual(
context['enterprise_readonly_account_fields'], {'fields': settings.ENTERPRISE_READONLY_ACCOUNT_FIELDS}
)
self.assertEqual(context['duplicate_provider'], 'facebook')
self.assertEqual(context['auth']['providers'][0]['name'], 'Facebook')
self.assertEqual(context['auth']['providers'][1]['name'], 'Google')
self.assertEqual(context['sync_learner_profile_data'], False)
self.assertEqual(context['edx_support_url'], settings.SUPPORT_SITE_LINK)
self.assertEqual(context['enterprise_name'], None)
self.assertEqual(
context['enterprise_readonly_account_fields'], {'fields': settings.ENTERPRISE_READONLY_ACCOUNT_FIELDS}
)
expected_beta_language = {'code': 'lt-lt', 'name': settings.LANGUAGE_DICT.get('lt-lt')}
self.assertEqual(context['beta_language'], expected_beta_language)
@mock.patch('openedx.core.djangoapps.user_api.accounts.settings_views.get_enterprise_customer_for_learner')
@mock.patch('openedx.features.enterprise_support.utils.third_party_auth.provider.Registry.get')

View File

@@ -57,8 +57,10 @@ class Command(BaseCommand):
# Make sure we're changing to a code that actually exists. Presumably it's safe to move away from a code that
# doesn't.
dark_lang_config = DarkLangConfig.current()
langs = [lang_code[0] for lang_code in settings.LANGUAGES]
langs += DarkLangConfig.current().released_languages_list
langs += dark_lang_config.released_languages_list
langs += dark_lang_config.beta_languages_list if dark_lang_config.enable_beta_languages else []
if new_lang_code not in langs:
raise CommandError('{} is not a configured language code in settings.LANGUAGES '