mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 18:14:17 +00:00
Merge pull request #329 from bram-atmire/issue-298-language-switch-2018-11-18-rebase-squash
Issue 298 Language switch
This commit is contained in:
@@ -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,
|
||||
}]
|
||||
};
|
||||
|
@@ -60,10 +60,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();
|
||||
|
||||
|
@@ -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()"
|
||||
|
12
src/app/shared/lang-switch/lang-switch.component.html
Normal file
12
src/app/shared/lang-switch/lang-switch.component.html
Normal 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>
|
3
src/app/shared/lang-switch/lang-switch.component.scss
Normal file
3
src/app/shared/lang-switch/lang-switch.component.scss
Normal file
@@ -0,0 +1,3 @@
|
||||
.dropdown-toggle::after {
|
||||
display:none;
|
||||
}
|
156
src/app/shared/lang-switch/lang-switch.component.spec.ts
Normal file
156
src/app/shared/lang-switch/lang-switch.component.spec.ts
Normal 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();
|
||||
}));
|
||||
|
||||
});
|
||||
|
||||
});
|
49
src/app/shared/lang-switch/lang-switch.component.ts
Normal file
49
src/app/shared/lang-switch/lang-switch.component.ts
Normal 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;
|
||||
}
|
||||
|
||||
}
|
@@ -86,6 +86,7 @@ import { CapitalizePipe } from './utils/capitalize.pipe';
|
||||
import { ObjectKeysPipe } from './utils/object-keys-pipe';
|
||||
import { MomentModule } from 'ngx-moment';
|
||||
import { MenuModule } from './menu/menu.module';
|
||||
import {LangSwitchComponent} from './lang-switch/lang-switch.component';
|
||||
|
||||
const MODULES = [
|
||||
// Do NOT include UniversalModule, HttpModule, or JsonpModule here
|
||||
@@ -140,6 +141,7 @@ const COMPONENTS = [
|
||||
DsDatePickerComponent,
|
||||
ErrorComponent,
|
||||
FormComponent,
|
||||
LangSwitchComponent,
|
||||
LoadingComponent,
|
||||
LogInComponent,
|
||||
LogOutComponent,
|
||||
|
@@ -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;
|
||||
@@ -16,4 +17,6 @@ export interface GlobalConfig extends Config {
|
||||
gaTrackingId: string;
|
||||
logDirectory: string;
|
||||
debug: boolean;
|
||||
defaultLanguage: string;
|
||||
languages: LangConfig[];
|
||||
}
|
||||
|
12
src/config/lang-config.interface.ts
Normal file
12
src/config/lang-config.interface.ts
Normal 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;
|
||||
}
|
Reference in New Issue
Block a user