Merge remote-tracking branch 'remotes/origin/master' into submission

# Conflicts:
#	src/app/shared/shared.module.ts
This commit is contained in:
Giuseppe Digilio
2019-01-25 10:00:52 +01:00
10 changed files with 273 additions and 8 deletions

View File

@@ -114,5 +114,25 @@ module.exports = {
// Log directory
logDirectory: '.',
// NOTE: will log all redux actions and transfers in console
debug: false
debug: false,
// Default Language in which the UI will be rendered if the user's browser language is not an active language
defaultLanguage: 'en',
// Languages. DSpace Angular holds a message catalog for each of the following languages. When set to active, users will be able to switch to the use of this language in the user interface.
languages: [{
code: 'en',
label: 'English',
active: true,
}, {
code: 'de',
label: 'Deutsch',
active: true,
}, {
code: 'cs',
label: 'Čeština',
active: true,
}, {
code: 'nl',
label: 'Nederlands',
active: false,
}]
};

View File

@@ -62,10 +62,18 @@ export class AppComponent implements OnInit, AfterViewInit {
private menuService: MenuService,
private windowService: HostWindowService
) {
// this language will be used as a fallback when a translation isn't found in the current language
translate.setDefaultLang('en');
// the lang to use, if the lang isn't available, it will use the current loader to get them
translate.use('en');
// Load all the languages that are defined as active from the config file
translate.addLangs(config.languages.filter((LangConfig) => LangConfig.active === true).map((a) => a.code));
// Load the default language from the config file
translate.setDefaultLang(config.defaultLanguage);
// Attempt to get the browser language from the user
if (translate.getLangs().includes(translate.getBrowserLang())) {
translate.use(translate.getBrowserLang());
} else {
translate.use(config.defaultLanguage);
}
metadata.listenForRouteChange();

View File

@@ -6,7 +6,7 @@
<nav class="navbar navbar-light navbar-expand-md float-right px-0">
<a href="#" class="px-1"><i class="fas fa-search fa-lg fa-fw" [title]="'nav.search' | translate"></i></a>
<a href="#" class="px-1"><i class="fas fa-globe-asia fa-lg fa-fw" [title]="'nav.language' | translate"></i></a>
<ds-lang-switch></ds-lang-switch>
<ds-auth-nav-menu></ds-auth-nav-menu>
<div class="pl-2">
<button class="navbar-toggler" type="button" (click)="toggleNavbar()"

View File

@@ -0,0 +1,12 @@
<div ngbDropdown class="navbar-nav" *ngIf="moreThanOneLanguage">
<a href="#" id="dropdownLang" role="button" class="px-1" (click)="$event.preventDefault()" data-toggle="dropdown" ngbDropdownToggle>
<i class="fas fa-globe-asia fa-lg fa-fw" [title]="'nav.language' | translate"></i>
</a>
<ul ngbDropdownMenu class="dropdown-menu" aria-labelledby="dropdownLang">
<li class="dropdown-item" #langSelect *ngFor="let lang of translate.getLangs()"
(click)="translate.use(lang)"
[class.active]="lang === translate.currentLang">
{{ langLabel(lang) }}
</li>
</ul>
</div>

View File

@@ -0,0 +1,3 @@
.dropdown-toggle::after {
display:none;
}

View File

@@ -0,0 +1,156 @@
import {LangSwitchComponent} from './lang-switch.component';
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
import {DebugElement, NO_ERRORS_SCHEMA} from '@angular/core';
import {TranslateLoader, TranslateModule, TranslateService} from '@ngx-translate/core';
import {HttpClientTestingModule, HttpTestingController} from '@angular/common/http/testing';
import { GLOBAL_CONFIG } from '../../../config';
import {LangConfig} from '../../../config/lang-config.interface';
import {Observable, of} from 'rxjs';
// This test is completely independent from any message catalogs or keys in the codebase
// The translation module is instantiated with these bogus messages that we aren't using anyway.
// Double quotes are mandatory in JSON, so de-activating the tslint rule checking for single quotes here.
/* tslint:disable:quotemark */
// JSON for the language files has double quotes around all literals
/* tslint:disable:object-literal-key-quotes */
class CustomLoader implements TranslateLoader {
getTranslation(lang: string): Observable<any> {
return of({
"footer": {
"copyright": "copyright © 2002-{{ year }}",
"link.dspace": "DSpace software",
"link.duraspace": "DuraSpace"
}
});
}
}
/* tslint:enable:quotemark */
/* tslint:enable:object-literal-key-quotes */
describe('LangSwitchComponent', () => {
describe('with English and Deutsch activated, English as default', () => {
let component: LangSwitchComponent;
let fixture: ComponentFixture<LangSwitchComponent>;
let de: DebugElement;
let langSwitchElement: HTMLElement;
let translate: TranslateService;
let http: HttpTestingController;
beforeEach(async(() => {
const mockConfig = {
languages: [{
code: 'en',
label: 'English',
active: true,
}, {
code: 'de',
label: 'Deutsch',
active: true,
}]
};
TestBed.configureTestingModule({
imports: [HttpClientTestingModule, TranslateModule.forRoot(
{
loader: {provide: TranslateLoader, useClass: CustomLoader}
}
)],
declarations: [LangSwitchComponent],
schemas: [NO_ERRORS_SCHEMA],
providers: [TranslateService, {provide: GLOBAL_CONFIG, useValue: mockConfig}]
}).compileComponents()
.then(() => {
translate = TestBed.get(TranslateService);
translate.addLangs(mockConfig.languages.filter((langConfig:LangConfig) => langConfig.active === true).map((a) => a.code));
translate.setDefaultLang('en');
translate.use('en');
http = TestBed.get(HttpTestingController);
fixture = TestBed.createComponent(LangSwitchComponent);
component = fixture.componentInstance;
de = fixture.debugElement;
langSwitchElement = de.nativeElement;
});
}));
it('should create', () => {
expect(component).toBeDefined();
});
it('should identify English as the label for the current active language in the component', async(() => {
fixture.detectChanges();
expect(component.currentLangLabel()).toEqual('English');
}));
it('should be initialized with more than one language active', async(() => {
fixture.detectChanges();
expect(component.moreThanOneLanguage).toBeTruthy();
}));
it('should define the main A HREF in the UI', (() => {
expect(langSwitchElement.querySelector('a')).toBeDefined();
}));
});
describe('with English as the only active and also default language', () => {
let component: LangSwitchComponent;
let fixture: ComponentFixture<LangSwitchComponent>;
let de: DebugElement;
let langSwitchElement: HTMLElement;
let translate: TranslateService;
let http: HttpTestingController;
beforeEach(async(() => {
const mockConfig = {
languages: [{
code: 'en',
label: 'English',
active: true,
}, {
code: 'de',
label: 'Deutsch',
active: false
}]
};
TestBed.configureTestingModule({
imports: [HttpClientTestingModule, TranslateModule.forRoot(
{
loader: {provide: TranslateLoader, useClass: CustomLoader}
}
)],
declarations: [LangSwitchComponent],
schemas: [NO_ERRORS_SCHEMA],
providers: [TranslateService, {provide: GLOBAL_CONFIG, useValue: mockConfig}]
}).compileComponents();
translate = TestBed.get(TranslateService);
translate.addLangs(mockConfig.languages.filter((MyLangConfig) => MyLangConfig.active === true).map((a) => a.code));
translate.setDefaultLang('en');
translate.use('en');
http = TestBed.get(HttpTestingController);
}));
beforeEach(() => {
fixture = TestBed.createComponent(LangSwitchComponent);
component = fixture.componentInstance;
de = fixture.debugElement;
langSwitchElement = de.nativeElement;
});
it('should create', () => {
expect(component).toBeDefined();
});
it('should not define the main header for the language switch, as it should be invisible', (() => {
expect(langSwitchElement.querySelector('a')).toBeNull();
}));
});
});

View File

@@ -0,0 +1,49 @@
import {Component, Inject, OnInit} from '@angular/core';
import { GLOBAL_CONFIG, GlobalConfig } from '../../../config';
import {TranslateService} from '@ngx-translate/core';
import {LangConfig} from '../../../config/lang-config.interface';
@Component({
selector: 'ds-lang-switch',
styleUrls: ['lang-switch.component.scss'],
templateUrl: 'lang-switch.component.html',
})
/**
* Component representing a switch for changing the interface language throughout the application
* If only one language is active, the component will disappear as there are no languages to switch to.
*/
export class LangSwitchComponent implements OnInit {
// All of the languages that are active, meaning that a user can switch between them.
activeLangs: LangConfig[];
// A language switch only makes sense if there is more than one active language to switch between.
moreThanOneLanguage: boolean;
constructor(
@Inject(GLOBAL_CONFIG) public config: GlobalConfig,
public translate: TranslateService
) {
}
ngOnInit(): void {
this.activeLangs = this.config.languages.filter((MyLangConfig) => MyLangConfig.active === true);
this.moreThanOneLanguage = (this.activeLangs.length > 1);
}
/**
* Returns the label for the current language
*/
currentLangLabel(): string {
return this.activeLangs.find((MyLangConfig) => MyLangConfig.code === this.translate.currentLang).label;
}
/**
* Returns the label for a specific language code
*/
langLabel(langcode: string): string {
return this.activeLangs.find((MyLangConfig) => MyLangConfig.code === langcode).label;
}
}

View File

@@ -25,7 +25,6 @@ import { ItemListElementComponent } from './object-list/item-list-element/item-l
import { SearchResultListElementComponent } from './object-list/search-result-list-element/search-result-list-element.component';
import { WrapperListElementComponent } from './object-list/wrapper-list-element/wrapper-list-element.component';
import { ObjectListComponent } from './object-list/object-list.component';
import { CollectionGridElementComponent } from './object-grid/collection-grid-element/collection-grid-element.component';
import { CommunityGridElementComponent } from './object-grid/community-grid-element/community-grid-element.component';
import { ItemGridElementComponent } from './object-grid/item-grid-element/item-grid-element.component';
@@ -71,6 +70,7 @@ import { DsDynamicListComponent } from './form/builder/ds-dynamic-form-ui/models
import { DsDynamicFormGroupComponent } from './form/builder/ds-dynamic-form-ui/models/form-group/dynamic-form-group.component';
import { DsDynamicFormArrayComponent } from './form/builder/ds-dynamic-form-ui/models/array-group/dynamic-form-array.component';
import { DsDynamicRelationGroupComponent } from './form/builder/ds-dynamic-form-ui/models/relation-group/dynamic-relation-group.components';
import { DsDatePickerInlineComponent } from './form/builder/ds-dynamic-form-ui/models/date-picker-inline/dynamic-date-picker-inline.component';
import { SortablejsModule } from 'angular-sortablejs';
import { NumberPickerComponent } from './number-picker/number-picker.component';
import { DsDatePickerComponent } from './form/builder/ds-dynamic-form-ui/models/date-picker/date-picker.component';
@@ -89,7 +89,7 @@ import { ObjectKeysPipe } from './utils/object-keys-pipe';
import { MomentModule } from 'ngx-moment';
import { AuthorityConfidenceStateDirective } from './authority-confidence/authority-confidence-state.directive';
import { MenuModule } from './menu/menu.module';
import { DsDatePickerInlineComponent } from './form/builder/ds-dynamic-form-ui/models/date-picker-inline/dynamic-date-picker-inline.component';
import { LangSwitchComponent } from './lang-switch/lang-switch.component';
const MODULES = [
// Do NOT include UniversalModule, HttpModule, or JsonpModule here
@@ -146,8 +146,10 @@ const COMPONENTS = [
DsDatePickerComponent,
DsDynamicFormGroupComponent,
DsDynamicFormArrayComponent,
DsDatePickerInlineComponent,
ErrorComponent,
FormComponent,
LangSwitchComponent,
LoadingComponent,
LogInComponent,
LogOutComponent,

View File

@@ -5,6 +5,7 @@ import { UniversalConfig } from './universal-config.interface';
import { INotificationBoardOptions } from './notifications-config.interfaces';
import { SubmissionConfig } from './submission-config.interface';
import { FormConfig } from './form-config.interfaces';
import {LangConfig} from './lang-config.interface';
export interface GlobalConfig extends Config {
ui: ServerConfig;
@@ -18,4 +19,6 @@ export interface GlobalConfig extends Config {
gaTrackingId: string;
logDirectory: string;
debug: boolean;
defaultLanguage: string;
languages: LangConfig[];
}

View File

@@ -0,0 +1,12 @@
import { Config } from './config.interface';
/**
* An interface to represent a language in the configuration. A LangConfig has a code which should be the official
* language code for the language (e.g. fr), a label which should be the name of the language in that language
* (e.g. Français), and a boolean to determine whether or not it should be listed in the language select.
*/
export interface LangConfig extends Config {
code: string;
label: string;
active: boolean;
}