Skip to content

Commit

Permalink
Merge 580f5ed into a3b4883
Browse files Browse the repository at this point in the history
  • Loading branch information
bram-atmire committed Dec 5, 2018
2 parents a3b4883 + 580f5ed commit ff52c78
Show file tree
Hide file tree
Showing 10 changed files with 271 additions and 5 deletions.
22 changes: 21 additions & 1 deletion config/environment.default.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,5 +52,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,
}]
};
16 changes: 12 additions & 4 deletions src/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,18 @@ export class AppComponent implements OnInit, AfterViewInit {
private authService: AuthService,
private router: Router
) {
// 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();

Expand Down
1 change: 1 addition & 0 deletions src/app/header/header.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
<a class="nav-link" routerLink="/home" routerLinkActive="active"><i class="fa fa-home fa-fw" aria-hidden="true"></i> {{ 'nav.home' | translate }}<span class="sr-only">(current)</span></a>
</li>
</ul>
<ds-lang-switch></ds-lang-switch>
<ds-auth-nav-menu></ds-auth-nav-menu>
</div>
</nav>
Expand Down
13 changes: 13 additions & 0 deletions src/app/shared/lang-switch/lang-switch.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<div ngbDropdown class="navbar-nav" *ngIf="moreThanOneLanguage">
<a href="#" id="dropdownLang" role="button" class="nav-link" (click)="$event.preventDefault()" data-toggle="dropdown" ngbDropdownToggle>
<i class="fa fa-language fa-fw" aria-hidden="true"></i>
{{ currentLangLabel() }}
</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>
Empty file.
163 changes: 163 additions & 0 deletions src/app/shared/lang-switch/lang-switch.component.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
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();
}));

it('should show English in the UI as the label for the language dropdown', async(() => {
spyOn(translate, 'getBrowserLang').and.returnValue('en');
fixture.detectChanges();
// the main link to open up the dropdown should now say English
expect(langSwitchElement.querySelector('a').textContent.trim()).toEqual('English');
}));
});

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();
}));

});

});
49 changes: 49 additions & 0 deletions src/app/shared/lang-switch/lang-switch.component.ts
Original file line number Diff line number Diff line change
@@ -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;
}

}
2 changes: 2 additions & 0 deletions src/app/shared/shared.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ import { EmphasizePipe } from './utils/emphasize.pipe';
import { InputSuggestionsComponent } from './input-suggestions/input-suggestions.component';
import { CapitalizePipe } from './utils/capitalize.pipe';
import { ObjectKeysPipe } from './utils/object-keys-pipe';
import {LangSwitchComponent} from './lang-switch/lang-switch.component';
import { MomentModule } from 'ngx-moment';

const MODULES = [
Expand Down Expand Up @@ -139,6 +140,7 @@ const COMPONENTS = [
DsDatePickerComponent,
ErrorComponent,
FormComponent,
LangSwitchComponent,
LoadingComponent,
LogInComponent,
LogOutComponent,
Expand Down
3 changes: 3 additions & 0 deletions src/config/global-config.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { CacheConfig } from './cache-config.interface';
import { UniversalConfig } from './universal-config.interface';
import { INotificationBoardOptions } from './notifications-config.interfaces';
import { FormConfig } from './form-config.interfaces';
import {LangConfig} from './lang-config.interface';

export interface GlobalConfig extends Config {
ui: ServerConfig;
Expand All @@ -16,4 +17,6 @@ export interface GlobalConfig extends Config {
gaTrackingId: string;
logDirectory: string;
debug: boolean;
defaultLanguage: string;
languages: LangConfig[];
}
7 changes: 7 additions & 0 deletions src/config/lang-config.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { Config } from './config.interface';

export interface LangConfig extends Config {
code: string;
label: string;
active: boolean;
}

0 comments on commit ff52c78

Please sign in to comment.