diff --git a/core/controllers/community_dashboard.py b/core/controllers/community_dashboard.py
index 1a127fe9bd4b..e9d617aba76e 100644
--- a/core/controllers/community_dashboard.py
+++ b/core/controllers/community_dashboard.py
@@ -20,6 +20,7 @@
from constants import constants
from core.controllers import acl_decorators
from core.controllers import base
+from core.domain import config_domain
from core.domain import exp_fetchers
from core.domain import opportunity_services
from core.domain import topic_fetchers
@@ -233,3 +234,17 @@ def get(self):
community_rights.can_review_questions
if community_rights else False)
})
+
+
+class FeaturedTranslationLanguagesHandler(base.BaseHandler):
+ """Provides featured translation languages set in admin config."""
+
+ GET_HANDLER_ERROR_RETURN_TYPE = feconf.HANDLER_TYPE_JSON
+
+ @acl_decorators.open_access
+ def get(self):
+ """Handles GET requests."""
+ self.render_json({
+ 'featured_translation_languages':
+ config_domain.FEATURED_TRANSLATION_LANGUAGES.value
+ })
diff --git a/core/controllers/community_dashboard_test.py b/core/controllers/community_dashboard_test.py
index becaa6edaf79..1e97b044acc8 100644
--- a/core/controllers/community_dashboard_test.py
+++ b/core/controllers/community_dashboard_test.py
@@ -17,6 +17,7 @@
from __future__ import absolute_import # pylint: disable=import-only-modules
from __future__ import unicode_literals # pylint: disable=import-only-modules
+from core.domain import config_services
from core.domain import exp_domain
from core.domain import exp_services
from core.domain import story_domain
@@ -452,3 +453,29 @@ def test_user_check_community_rights(self):
'can_review_voiceover_for_language_codes': [],
'can_review_questions': True
})
+
+
+class FeaturedTranslationLanguagesHandlerTest(test_utils.GenericTestBase):
+ """Test for the FeaturedTranslationLanguagesHandler."""
+
+ def test_get_featured_translation_languages(self):
+ response = self.get_json('/retrivefeaturedtranslationlanguages')
+ self.assertEqual(
+ response,
+ {'featured_translation_languages': []}
+ )
+
+ new_value = [
+ {'language_code': 'en', 'explanation': 'Partnership with ABC'}
+ ]
+ config_services.set_property(
+ 'admin',
+ 'featured_translation_languages',
+ new_value
+ )
+
+ response = self.get_json('/retrivefeaturedtranslationlanguages')
+ self.assertEqual(
+ response,
+ {'featured_translation_languages': new_value}
+ )
diff --git a/core/domain/config_domain.py b/core/domain/config_domain.py
index e3ea2d3dea33..a59f80375814 100644
--- a/core/domain/config_domain.py
+++ b/core/domain/config_domain.py
@@ -30,6 +30,27 @@
CMD_CHANGE_PROPERTY_VALUE = 'change_property_value'
+LIST_OF_FEATURED_TRANSLATION_LANGUAGES_DICTS_SCHEMA = {
+ 'type': 'list',
+ 'items': {
+ 'type': 'dict',
+ 'properties': [{
+ 'name': 'language_code',
+ 'schema': {
+ 'type': 'unicode',
+ 'validators': [{
+ 'id': 'is_supported_audio_language_code',
+ }]
+ },
+ }, {
+ 'name': 'explanation',
+ 'schema': {
+ 'type': 'unicode'
+ }
+ }]
+ }
+}
+
SET_OF_STRINGS_SCHEMA = {
'type': 'list',
'items': {
@@ -345,3 +366,9 @@ def get_all_config_property_names(cls):
CLASSROOM_PAGE_IS_SHOWN = ConfigProperty(
'classroom_page_is_shown', BOOL_SCHEMA,
'Show classroom components.', False)
+
+FEATURED_TRANSLATION_LANGUAGES = ConfigProperty(
+ 'featured_translation_languages',
+ LIST_OF_FEATURED_TRANSLATION_LANGUAGES_DICTS_SCHEMA,
+ 'Featured Translation Languages', []
+)
diff --git a/core/templates/components/oppia-angular-root.component.ts b/core/templates/components/oppia-angular-root.component.ts
index 8caa102b757a..de6f74d23d40 100644
--- a/core/templates/components/oppia-angular-root.component.ts
+++ b/core/templates/components/oppia-angular-root.component.ts
@@ -233,6 +233,8 @@ import { ExtensionTagAssemblerService } from
import { ExtractImageFilenamesFromStateService } from
// eslint-disable-next-line max-len
'pages/exploration-player-page/services/extract-image-filenames-from-state.service';
+import { FeaturedTranslationLanguageObjectFactory } from
+ 'domain/opportunity/FeaturedTranslationLanguageObjectFactory';
import { FeedbackMessageSummaryObjectFactory } from
'domain/feedback_message/FeedbackMessageSummaryObjectFactory';
import { FeedbackThreadObjectFactory } from
@@ -706,6 +708,7 @@ export class OppiaAngularRootComponent implements AfterViewInit {
static expressionSyntaxTreeService: ExpressionSyntaxTreeService;
static extensionTagAssemblerService: ExtensionTagAssemblerService;
static extractImageFilenamesFromStateService: ExtractImageFilenamesFromStateService;
+ static featuredTranslationLanguageObjectFactory: FeaturedTranslationLanguageObjectFactory;
static feedbackMessageSummaryObjectFactory: FeedbackMessageSummaryObjectFactory;
static feedbackThreadObjectFactory: FeedbackThreadObjectFactory;
static feedbackThreadSummaryObjectFactory: FeedbackThreadSummaryObjectFactory;
@@ -989,6 +992,7 @@ private explorationTaskObjectFactory: ExplorationTaskObjectFactory,
private expressionSyntaxTreeService: ExpressionSyntaxTreeService,
private extensionTagAssemblerService: ExtensionTagAssemblerService,
private extractImageFilenamesFromStateService: ExtractImageFilenamesFromStateService,
+private featuredTranslationLanguageObjectFactory: FeaturedTranslationLanguageObjectFactory,
private feedbackMessageSummaryObjectFactory: FeedbackMessageSummaryObjectFactory,
private feedbackThreadObjectFactory: FeedbackThreadObjectFactory,
private feedbackThreadSummaryObjectFactory: FeedbackThreadSummaryObjectFactory,
@@ -1273,6 +1277,7 @@ private writtenTranslationsObjectFactory: WrittenTranslationsObjectFactory
OppiaAngularRootComponent.expressionSyntaxTreeService = this.expressionSyntaxTreeService;
OppiaAngularRootComponent.extensionTagAssemblerService = this.extensionTagAssemblerService;
OppiaAngularRootComponent.extractImageFilenamesFromStateService = this.extractImageFilenamesFromStateService;
+ OppiaAngularRootComponent.featuredTranslationLanguageObjectFactory = this.featuredTranslationLanguageObjectFactory;
OppiaAngularRootComponent.feedbackMessageSummaryObjectFactory = this.feedbackMessageSummaryObjectFactory;
OppiaAngularRootComponent.feedbackThreadObjectFactory = this.feedbackThreadObjectFactory;
OppiaAngularRootComponent.feedbackThreadSummaryObjectFactory = this.feedbackThreadSummaryObjectFactory;
diff --git a/core/templates/domain/opportunity/FeaturedTranslationLanguageObjectFactory.ts b/core/templates/domain/opportunity/FeaturedTranslationLanguageObjectFactory.ts
new file mode 100644
index 000000000000..a2552730d6ec
--- /dev/null
+++ b/core/templates/domain/opportunity/FeaturedTranslationLanguageObjectFactory.ts
@@ -0,0 +1,51 @@
+// Copyright 2020 The Oppia Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @fileoverview Factory for creating and mutating instances of frontend
+ * featured translation language domain objects.
+ */
+
+import { downgradeInjectable } from '@angular/upgrade/static';
+import { Injectable } from '@angular/core';
+
+export interface IFeaturedTranslationLanguageBackendDict {
+ 'language_code': string;
+ explanation: string;
+}
+
+export class FeaturedTranslationLanguage {
+ constructor(
+ readonly languageCode: string,
+ readonly explanation: string
+ ) {}
+}
+
+@Injectable({
+ providedIn: 'root'
+})
+export class FeaturedTranslationLanguageObjectFactory {
+ createFromBackendDict(
+ featuredTranslationBackendDict: IFeaturedTranslationLanguageBackendDict
+ ): FeaturedTranslationLanguage {
+ return new FeaturedTranslationLanguage(
+ featuredTranslationBackendDict.language_code,
+ featuredTranslationBackendDict.explanation
+ );
+ }
+}
+
+angular.module('oppia').factory(
+ 'FeaturedTranslationLanguageObjectFactory',
+ downgradeInjectable(FeaturedTranslationLanguageObjectFactory));
diff --git a/core/templates/domain/opportunity/featured-translation-language-object.factory.spec.ts b/core/templates/domain/opportunity/featured-translation-language-object.factory.spec.ts
new file mode 100644
index 000000000000..7f6a6d623490
--- /dev/null
+++ b/core/templates/domain/opportunity/featured-translation-language-object.factory.spec.ts
@@ -0,0 +1,50 @@
+// Copyright 2020 The Oppia Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @fileoverview Tests for FeaturedTranslationLanguageObjectFactory.
+ */
+
+import { TestBed } from '@angular/core/testing';
+
+import {
+ FeaturedTranslationLanguageObjectFactory,
+ FeaturedTranslationLanguage,
+ IFeaturedTranslationLanguageBackendDict
+} from
+ 'domain/opportunity/FeaturedTranslationLanguageObjectFactory';
+
+describe('Featured Translation Language object factory', () => {
+ let featuredTranslationLanguageObjectFactory:
+ FeaturedTranslationLanguageObjectFactory = null;
+ let sampleFTL: FeaturedTranslationLanguage = null;
+
+ beforeEach(() => {
+ featuredTranslationLanguageObjectFactory = TestBed.get(
+ FeaturedTranslationLanguageObjectFactory);
+
+ let sampleFTLDict: IFeaturedTranslationLanguageBackendDict = {
+ language_code: 'en',
+ explanation: 'English'
+ };
+ sampleFTL = featuredTranslationLanguageObjectFactory
+ .createFromBackendDict(sampleFTLDict);
+ });
+
+ it('should correctly evaluate all the values based on backend' +
+ ' dict', function() {
+ expect(sampleFTL.languageCode).toBe('en');
+ expect(sampleFTL.explanation).toBe('English');
+ });
+});
diff --git a/core/templates/pages/community-dashboard-page/community-dashboard-page.component.html b/core/templates/pages/community-dashboard-page/community-dashboard-page.component.html
index cd7a039bab7e..d350107dc0a8 100644
--- a/core/templates/pages/community-dashboard-page/community-dashboard-page.component.html
+++ b/core/templates/pages/community-dashboard-page/community-dashboard-page.component.html
@@ -54,12 +54,10 @@
<[$ctrl.tabsDetails[$ctrl.activeTabName].description]>
- Translate text to:
-
+ Translate to
+
+
@@ -80,13 +78,13 @@
.oppia-dashboard-language-container-label {
color: #4a4a4a;
font-size: 18px;
+ margin-top: 10px;
}
.oppia-dashboard-language-container {
display: flex;
flex-direction: column;
- height: 80px;
margin-left: 10%;
- width: 15%;
+ width: 250px;
}
.oppia-opportunity-language-selector {
background: white;
@@ -94,9 +92,8 @@
height: 35px;
}
.oppia-opportunities-tabs-explanation {
- padding-top: 25px;
+ padding-top: 50px;
position: relative;
- width: 40%;
}
.oppia-opportunities-tabs-explanation::before {
bottom: 65%;
diff --git a/core/templates/pages/community-dashboard-page/community-dashboard-page.component.spec.ts b/core/templates/pages/community-dashboard-page/community-dashboard-page.component.spec.ts
index 623b1c46c8aa..8f04dcb71c27 100644
--- a/core/templates/pages/community-dashboard-page/community-dashboard-page.component.spec.ts
+++ b/core/templates/pages/community-dashboard-page/community-dashboard-page.component.spec.ts
@@ -100,16 +100,6 @@ describe('Community dashboard page', function() {
expect(ctrl.profilePictureDataUrl).toBe(userProfileImage);
});
- it('should get all language codes and its descriptions', function() {
- const allLanguageCodesAndDescriptionsFromConstants = (
- CONSTANTS.SUPPORTED_AUDIO_LANGUAGES.map(language => ({
- id: language.id,
- description: language.description
- })));
- expect(ctrl.languageCodesAndDescriptions).toEqual(
- allLanguageCodesAndDescriptionsFromConstants);
- });
-
it('should change active tab name', function() {
var changedTab = 'translateTextTab';
expect(ctrl.activeTabName).toBe('myContributionTab');
@@ -121,7 +111,7 @@ describe('Community dashboard page', function() {
spyOn(LocalStorageService, 'updateLastSelectedTranslationLanguageCode')
.and.callThrough();
- ctrl.onChangeLanguage();
+ ctrl.onChangeLanguage('hi');
expect(TranslationLanguageService.setActiveLanguageCode)
.toHaveBeenCalledWith('hi');
diff --git a/core/templates/pages/community-dashboard-page/community-dashboard-page.component.ts b/core/templates/pages/community-dashboard-page/community-dashboard-page.component.ts
index 4ccd2a67c0ef..a1aa0d48a3ee 100644
--- a/core/templates/pages/community-dashboard-page/community-dashboard-page.component.ts
+++ b/core/templates/pages/community-dashboard-page/community-dashboard-page.component.ts
@@ -26,6 +26,9 @@ require(
require(
'pages/community-dashboard-page/contributions-and-review/' +
'contributions-and-review.directive.ts');
+require(
+ 'pages/community-dashboard-page/translation-language-selector/' +
+ 'translation-language-selector.component.ts');
require(
'pages/community-dashboard-page/question-opportunities/' +
'question-opportunities.directive.ts');
@@ -73,7 +76,8 @@ angular.module('oppia').component('communityDashboardPage', {
return languageDescriptions;
};
- ctrl.onChangeLanguage = function() {
+ ctrl.onChangeLanguage = function(languageCode: string) {
+ ctrl.languageCode = languageCode;
TranslationLanguageService.setActiveLanguageCode(ctrl.languageCode);
LocalStorageService.updateLastSelectedTranslationLanguageCode(
ctrl.languageCode);
@@ -135,15 +139,6 @@ angular.module('oppia').component('communityDashboardPage', {
}
});
- ctrl.languageCodesAndDescriptions = (
- allAudioLanguageCodes.map(function(languageCode) {
- return {
- id: languageCode,
- description: (
- LanguageUtilService.getAudioLanguageDescription(
- languageCode))
- };
- }));
ctrl.languageCode = (
allAudioLanguageCodes.indexOf(prevSelectedLanguageCode) !== -1 ?
prevSelectedLanguageCode : DEFAULT_OPPORTUNITY_LANGUAGE_CODE);
diff --git a/core/templates/pages/community-dashboard-page/community-dashboard-page.module.ts b/core/templates/pages/community-dashboard-page/community-dashboard-page.module.ts
index 9f5b7ad74c96..29116352864a 100644
--- a/core/templates/pages/community-dashboard-page/community-dashboard-page.module.ts
+++ b/core/templates/pages/community-dashboard-page/community-dashboard-page.module.ts
@@ -26,7 +26,7 @@ angular.module('oppia', [
'toastr', 'ui.bootstrap', 'ui.sortable', 'ui.tree', 'ui.validate'
]);
-import { Component, NgModule, StaticProvider } from '@angular/core';
+import { NgModule, StaticProvider } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { downgradeComponent } from '@angular/upgrade/static';
import { HttpClientModule } from '@angular/common/http';
@@ -39,6 +39,8 @@ import { OppiaAngularRootComponent } from
import { AppConstants } from 'app.constants';
import { CommunityDashboardConstants } from
'pages/community-dashboard-page/community-dashboard-page.constants';
+import { TranslationLanguageSelectorComponent } from
+ './translation-language-selector/translation-language-selector.component';
@NgModule({
imports: [
@@ -47,10 +49,12 @@ import { CommunityDashboardConstants } from
SharedComponentsModule
],
declarations: [
- OppiaAngularRootComponent
+ OppiaAngularRootComponent,
+ TranslationLanguageSelectorComponent
],
entryComponents: [
- OppiaAngularRootComponent
+ OppiaAngularRootComponent,
+ TranslationLanguageSelectorComponent
],
providers: [
AppConstants,
diff --git a/core/templates/pages/community-dashboard-page/services/contribution-opportunities-backend-api.service.spec.ts b/core/templates/pages/community-dashboard-page/services/contribution-opportunities-backend-api.service.spec.ts
index 33c7867d8153..33d4a1a8b5ed 100644
--- a/core/templates/pages/community-dashboard-page/services/contribution-opportunities-backend-api.service.spec.ts
+++ b/core/templates/pages/community-dashboard-page/services/contribution-opportunities-backend-api.service.spec.ts
@@ -29,6 +29,8 @@ import { SkillOpportunityObjectFactory } from
'domain/opportunity/SkillOpportunityObjectFactory';
import { UrlInterpolationService } from
'domain/utilities/url-interpolation.service';
+import { FeaturedTranslationLanguageObjectFactory} from
+ 'domain/opportunity/FeaturedTranslationLanguageObjectFactory';
describe('Contribution Opportunities backend API service', function() {
let contributionOpportunitiesBackendApiService:
@@ -176,4 +178,36 @@ describe('Contribution Opportunities backend API service', function() {
expect(failHandler).not.toHaveBeenCalled();
})
);
+
+ it('should successfully fetch the featured translation languages',
+ fakeAsync(() => {
+ const successHandler = jasmine.createSpy('success');
+ const failHandler = jasmine.createSpy('fail');
+
+ const featuredTranslationLanguageObjectFactory = TestBed.get(
+ FeaturedTranslationLanguageObjectFactory);
+
+ contributionOpportunitiesBackendApiService
+ .fetchFeaturedTranslationLanguages()
+ .then(successHandler, failHandler);
+
+ const req = httpTestingController.expectOne(
+ '/retrivefeaturedtranslationlanguages'
+ );
+ expect(req.request.method).toEqual('GET');
+ req.flush({
+ featured_translation_languages:
+ [{ language_code: 'en', explanation: 'English' }]
+ });
+
+ flushMicrotasks();
+
+ expect(successHandler).toHaveBeenCalledWith([
+ featuredTranslationLanguageObjectFactory.createFromBackendDict(
+ { language_code: 'en', explanation: 'English' }
+ )
+ ]);
+ expect(failHandler).not.toHaveBeenCalled();
+ })
+ );
});
diff --git a/core/templates/pages/community-dashboard-page/services/contribution-opportunities-backend-api.service.ts b/core/templates/pages/community-dashboard-page/services/contribution-opportunities-backend-api.service.ts
index 1270525cdbc0..dab7ee2533f6 100644
--- a/core/templates/pages/community-dashboard-page/services/contribution-opportunities-backend-api.service.ts
+++ b/core/templates/pages/community-dashboard-page/services/contribution-opportunities-backend-api.service.ts
@@ -27,6 +27,10 @@ import { SkillOpportunity } from
'domain/opportunity/SkillOpportunityObjectFactory';
import { UrlInterpolationService } from
'domain/utilities/url-interpolation.service';
+import {
+ FeaturedTranslationLanguageObjectFactory,
+ IFeaturedTranslationLanguageBackendDict,
+} from 'domain/opportunity/FeaturedTranslationLanguageObjectFactory';
const constants = require('constants.ts');
@@ -45,7 +49,9 @@ export class ContributionOpportunitiesBackendApiService {
urlTemplate = '/opportunitiessummaryhandler/';
constructor(
private urlInterpolationService: UrlInterpolationService,
- private http: HttpClient
+ private http: HttpClient,
+ private featuredTranslationLanguageObjectFactory:
+ FeaturedTranslationLanguageObjectFactory
) {}
// TODO(#7165): Replace any with exact type.
@@ -74,7 +80,7 @@ export class ContributionOpportunitiesBackendApiService {
successCallback: (
opportunities?: Array, nextCursor?: string, more?: boolean
) => void,
- errorCallback: (reason?: any) => void
+ errorCallback: (reason: string) => void
): void {
this.http.get(this.urlInterpolationService.interpolateUrl(
this.urlTemplate, { opportunityType }
@@ -129,6 +135,25 @@ export class ContributionOpportunitiesBackendApiService {
params, resolve, reject);
});
}
+
+ async fetchFeaturedTranslationLanguages(): Promise