Skip to content

Commit 7271692

Browse files
authored
Merge pull request DSpace#4905 from atmire/fix-4241-language-selection-contribute-7.6
[Port dspace-7_x] Fix DSpace#4241 language selection
2 parents a655f28 + ae4dadf commit 7271692

5 files changed

Lines changed: 120 additions & 43 deletions

File tree

src/app/core/locale/locale.interceptor.spec.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { HttpClientTestingModule, HttpTestingController, } from '@angular/common
33
import { HTTP_INTERCEPTORS } from '@angular/common/http';
44

55
import { DspaceRestService } from '../dspace-rest/dspace-rest.service';
6+
import { HALEndpointService } from '../shared/hal-endpoint.service';
67
import { RestRequestMethod } from '../data/rest-request-method';
78
import { LocaleService } from './locale.service';
89
import { LocaleInterceptor } from './locale.interceptor';
@@ -20,6 +21,10 @@ describe(`LocaleInterceptor`, () => {
2021
getLanguageCodeList: of(languageList)
2122
});
2223

24+
const mockHalEndpointService = {
25+
getRootHref: jasmine.createSpy('getRootHref'),
26+
};
27+
2328
beforeEach(() => {
2429
TestBed.configureTestingModule({
2530
imports: [HttpClientTestingModule],
@@ -30,6 +35,7 @@ describe(`LocaleInterceptor`, () => {
3035
useClass: LocaleInterceptor,
3136
multi: true,
3237
},
38+
{ provide: HALEndpointService, useValue: mockHalEndpointService },
3339
{ provide: LocaleService, useValue: mockLocaleService },
3440
],
3541
});
@@ -38,7 +44,7 @@ describe(`LocaleInterceptor`, () => {
3844
httpMock = TestBed.inject(HttpTestingController);
3945
localeService = TestBed.inject(LocaleService);
4046

41-
localeService.getCurrentLanguageCode.and.returnValue('en');
47+
localeService.getCurrentLanguageCode.and.returnValue(of('en'));
4248
});
4349

4450
describe('', () => {

src/app/core/locale/locale.interceptor.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,17 @@ import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/c
33

44
import { Observable } from 'rxjs';
55

6+
import { HALEndpointService } from '../shared/hal-endpoint.service';
67
import { LocaleService } from './locale.service';
7-
import { mergeMap, scan } from 'rxjs/operators';
8+
import { mergeMap, scan, take } from 'rxjs/operators';
89

910
@Injectable()
1011
export class LocaleInterceptor implements HttpInterceptor {
1112

12-
constructor(private localeService: LocaleService) {
13+
constructor(
14+
protected halEndpointService: HALEndpointService,
15+
protected localeService: LocaleService,
16+
) {
1317
}
1418

1519
/**
@@ -19,8 +23,9 @@ export class LocaleInterceptor implements HttpInterceptor {
1923
*/
2024
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
2125
let newReq: HttpRequest<any>;
22-
return this.localeService.getLanguageCodeList()
26+
return this.localeService.getLanguageCodeList(req.url === this.halEndpointService.getRootHref())
2327
.pipe(
28+
take(1),
2429
scan((acc: any, value: any) => [...acc, value], []),
2530
mergeMap((languages) => {
2631
// Clone the request to add the new header.

src/app/core/locale/locale.service.spec.ts

Lines changed: 62 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ import { AuthService } from '../auth/auth.service';
1010
import { NativeWindowRef } from '../services/window.service';
1111
import { RouteService } from '../services/route.service';
1212
import { routeServiceStub } from '../../shared/testing/route-service.stub';
13+
import { of } from 'rxjs';
14+
import { TestScheduler } from 'rxjs/testing';
15+
import { EPersonMock2 } from '../../shared/testing/eperson.mock';
1316

1417
describe('LocaleService test suite', () => {
1518
let service: LocaleService;
@@ -25,7 +28,8 @@ describe('LocaleService test suite', () => {
2528

2629
authService = jasmine.createSpyObj('AuthService', {
2730
isAuthenticated: jasmine.createSpy('isAuthenticated'),
28-
isAuthenticationLoaded: jasmine.createSpy('isAuthenticationLoaded')
31+
isAuthenticationLoaded: jasmine.createSpy('isAuthenticationLoaded'),
32+
getAuthenticatedUserFromStore: jasmine.createSpy('getAuthenticatedUserFromStore'),
2933
});
3034

3135
const langList = ['en', 'xx', 'de'];
@@ -62,33 +66,80 @@ describe('LocaleService test suite', () => {
6266
});
6367

6468
describe('getCurrentLanguageCode', () => {
69+
let testScheduler: TestScheduler;
70+
6571
beforeEach(() => {
6672
spyOn(translateService, 'getLangs').and.returnValue(langList);
73+
testScheduler = new TestScheduler((actual, expected) => {
74+
// use jasmine to test equality
75+
expect(actual).toEqual(expected);
76+
});
77+
authService.isAuthenticated.and.returnValue(of(false));
78+
authService.isAuthenticationLoaded.and.returnValue(of(false));
6779
});
6880

6981
it('should return the language saved on cookie if it\'s a valid & active language', () => {
7082
spyOnGet.and.returnValue('de');
71-
expect(service.getCurrentLanguageCode()).toBe('de');
83+
testScheduler.run(({expectObservable}) => {
84+
expectObservable(service.getCurrentLanguageCode()).toBe('(a|)', { a: 'de' });
85+
});
7286
});
7387

7488
it('should return the default language if the cookie language is disabled', () => {
7589
spyOnGet.and.returnValue('disabled');
76-
expect(service.getCurrentLanguageCode()).toBe('en');
90+
testScheduler.run(({expectObservable}) => {
91+
expectObservable(service.getCurrentLanguageCode()).toBe('(a|)', { a: 'en' });
92+
});
7793
});
7894

7995
it('should return the default language if the cookie language does not exist', () => {
8096
spyOnGet.and.returnValue('does-not-exist');
81-
expect(service.getCurrentLanguageCode()).toBe('en');
97+
testScheduler.run(({expectObservable}) => {
98+
expectObservable(service.getCurrentLanguageCode()).toBe('(a|)', { a: 'en' });
99+
});
82100
});
83101

84102
it('should return language from browser setting', () => {
85-
spyOn(translateService, 'getBrowserLang').and.returnValue('xx');
86-
expect(service.getCurrentLanguageCode()).toBe('xx');
103+
spyOn(service, 'getLanguageCodeList').and.returnValue(of(['xx', 'en']));
104+
testScheduler.run(({expectObservable}) => {
105+
expectObservable(service.getCurrentLanguageCode()).toBe('(a|)', { a: 'xx' });
106+
});
107+
});
108+
109+
it('should match language from browser setting case insensitive', () => {
110+
spyOn(service, 'getLanguageCodeList').and.returnValue(of(['DE', 'en']));
111+
testScheduler.run(({expectObservable}) => {
112+
expectObservable(service.getCurrentLanguageCode()).toBe('(a|)', { a: 'DE' });
113+
});
114+
});
115+
});
116+
117+
describe('getLanguageCodeList', () => {
118+
let testScheduler: TestScheduler;
119+
120+
beforeEach(() => {
121+
spyOn(translateService, 'getLangs').and.returnValue(langList);
122+
testScheduler = new TestScheduler((actual, expected) => {
123+
// use jasmine to test equality
124+
expect(actual).toEqual(expected);
125+
});
126+
});
127+
128+
it('should return default language list without user preferred language when no logged in user', () => {
129+
authService.isAuthenticated.and.returnValue(of(false));
130+
authService.isAuthenticationLoaded.and.returnValue(of(false));
131+
testScheduler.run(({expectObservable}) => {
132+
expectObservable(service.getLanguageCodeList()).toBe('(a|)', { a: ['en-US;q=1', 'en;q=0.9'] });
133+
});
87134
});
88135

89-
it('should return default language from config', () => {
90-
spyOn(translateService, 'getBrowserLang').and.returnValue('fr');
91-
expect(service.getCurrentLanguageCode()).toBe('en');
136+
it('should return default language list with user preferred language when user is logged in', () => {
137+
authService.isAuthenticated.and.returnValue(of(true));
138+
authService.isAuthenticationLoaded.and.returnValue(of(true));
139+
authService.getAuthenticatedUserFromStore.and.returnValue(of(EPersonMock2));
140+
testScheduler.run(({expectObservable}) => {
141+
expectObservable(service.getLanguageCodeList()).toBe('(a|)', { a: ['fr;q=0.5', 'en-US;q=1', 'en;q=0.9'] });
142+
});
92143
});
93144
});
94145

@@ -120,14 +171,13 @@ describe('LocaleService test suite', () => {
120171
});
121172

122173
it('should set the current language', () => {
123-
spyOn(service, 'getCurrentLanguageCode').and.returnValue('es');
174+
spyOn(service, 'getCurrentLanguageCode').and.returnValue(of('es'));
124175
service.setCurrentLanguageCode();
125176
expect(translateService.use).toHaveBeenCalledWith('es');
126-
expect(service.saveLanguageCodeToCookie).toHaveBeenCalledWith('es');
127177
});
128178

129179
it('should set the current language on the html tag', () => {
130-
spyOn(service, 'getCurrentLanguageCode').and.returnValue('es');
180+
spyOn(service, 'getCurrentLanguageCode').and.returnValue(of('es'));
131181
service.setCurrentLanguageCode();
132182
expect((service as any).document.documentElement.lang).toEqual('es');
133183
});

src/app/core/locale/locale.service.ts

Lines changed: 41 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
import { Inject, Injectable } from '@angular/core';
1+
import { Inject, Injectable, OnDestroy } from '@angular/core';
22

33
import { TranslateService } from '@ngx-translate/core';
44

5-
import { isEmpty, isNotEmpty } from '../../shared/empty.util';
5+
import { isEmpty, isNotEmpty, hasValue } from '../../shared/empty.util';
66
import { CookieService } from '../services/cookie.service';
77
import { environment } from '../../../environments/environment';
88
import { AuthService } from '../auth/auth.service';
9-
import { combineLatest, Observable, of as observableOf } from 'rxjs';
9+
import { combineLatest, Observable, of as observableOf, Subscription } from 'rxjs';
1010
import { map, mergeMap, take } from 'rxjs/operators';
1111
import { NativeWindowRef, NativeWindowService } from '../services/window.service';
1212
import { RouteService } from '../services/route.service';
@@ -28,13 +28,15 @@ export enum LANG_ORIGIN {
2828
* Service to provide localization handler
2929
*/
3030
@Injectable()
31-
export class LocaleService {
31+
export class LocaleService implements OnDestroy {
3232

3333
/**
3434
* Eperson language metadata
3535
*/
3636
EPERSON_LANG_METADATA = 'eperson.language';
3737

38+
subs: Subscription[] = [];
39+
3840
constructor(
3941
@Inject(NativeWindowService) protected _window: NativeWindowRef,
4042
protected cookie: CookieService,
@@ -48,39 +50,42 @@ export class LocaleService {
4850
/**
4951
* Get the language currently used
5052
*
51-
* @returns {string} The language code
53+
* @returns {Observable<string>} The language code
5254
*/
53-
getCurrentLanguageCode(): string {
55+
getCurrentLanguageCode(): Observable<string> {
5456
// Attempt to get the language from a cookie
5557
let lang = this.getLanguageCodeFromCookie();
5658
if (isEmpty(lang) || environment.languages.find((langConfig: LangConfig) => langConfig.code === lang && langConfig.active) === undefined) {
5759
// Attempt to get the browser language from the user
58-
if (this.translate.getLangs().includes(this.translate.getBrowserLang())) {
59-
lang = this.translate.getBrowserLang();
60-
} else {
61-
lang = environment.defaultLanguage;
62-
}
60+
return this.getLanguageCodeList()
61+
.pipe(
62+
map(browserLangs => {
63+
return browserLangs
64+
.map(browserLang => browserLang.split(';')[0])
65+
.find(browserLang =>
66+
this.translate.getLangs().some(userLang => userLang.toLowerCase() === browserLang.toLowerCase())
67+
) || environment.defaultLanguage;
68+
}),
69+
);
6370
}
64-
return lang;
71+
return observableOf(lang);
6572
}
6673

6774
/**
6875
* Get the languages list of the user in Accept-Language format
6976
*
7077
* @returns {Observable<string[]>}
7178
*/
72-
getLanguageCodeList(): Observable<string[]> {
79+
getLanguageCodeList(ignoreEPersonSettings = false): Observable<string[]> {
7380
const obs$ = combineLatest([
7481
this.authService.isAuthenticated(),
7582
this.authService.isAuthenticationLoaded()
7683
]);
7784

7885
return obs$.pipe(
79-
take(1),
8086
mergeMap(([isAuthenticated, isLoaded]) => {
81-
// TODO to enabled again when https://github.com/DSpace/dspace-angular/issues/739 will be resolved
82-
const epersonLang$: Observable<string[]> = observableOf([]);
83-
/* if (isAuthenticated && isLoaded) {
87+
let epersonLang$: Observable<string[]> = observableOf([]);
88+
if (isAuthenticated && isLoaded && !ignoreEPersonSettings) {
8489
epersonLang$ = this.authService.getAuthenticatedUserFromStore().pipe(
8590
take(1),
8691
map((eperson) => {
@@ -95,19 +100,19 @@ export class LocaleService {
95100
return languages;
96101
})
97102
);
98-
}*/
103+
}
99104
return epersonLang$.pipe(
100105
map((epersonLang: string[]) => {
101106
const languages: string[] = [];
107+
if (isNotEmpty(epersonLang)) {
108+
languages.push(...epersonLang);
109+
}
102110
if (this.translate.currentLang) {
103111
languages.push(...this.setQuality(
104112
[this.translate.currentLang],
105113
LANG_ORIGIN.UI,
106114
false));
107115
}
108-
if (isNotEmpty(epersonLang)) {
109-
languages.push(...epersonLang);
110-
}
111116
if (navigator.languages) {
112117
languages.push(...this.setQuality(
113118
Object.assign([], navigator.languages),
@@ -147,11 +152,16 @@ export class LocaleService {
147152
*/
148153
setCurrentLanguageCode(lang?: string): void {
149154
if (isEmpty(lang)) {
150-
lang = this.getCurrentLanguageCode();
155+
this.subs.push(this.getCurrentLanguageCode().subscribe(curLang => {
156+
lang = curLang;
157+
this.translate.use(lang);
158+
this.document.documentElement.lang = lang;
159+
}));
160+
} else {
161+
this.saveLanguageCodeToCookie(lang);
162+
this.translate.use(lang);
163+
this.document.documentElement.lang = lang;
151164
}
152-
this.translate.use(lang);
153-
this.saveLanguageCodeToCookie(lang);
154-
this.document.documentElement.lang = lang;
155165
}
156166

157167
/**
@@ -197,4 +207,10 @@ export class LocaleService {
197207

198208
}
199209

210+
ngOnDestroy(): void {
211+
this.subs
212+
.filter((sub) => hasValue(sub))
213+
.forEach((sub) => sub.unsubscribe());
214+
}
215+
200216
}

src/app/core/locale/server-locale.service.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ export class ServerLocaleService extends LocaleService {
3131
*
3232
* @returns {Observable<string[]>}
3333
*/
34-
getLanguageCodeList(): Observable<string[]> {
34+
getLanguageCodeList(ignoreEPersonSettings = false): Observable<string[]> {
3535
const obs$ = combineLatest([
3636
this.authService.isAuthenticated(),
3737
this.authService.isAuthenticationLoaded()
@@ -41,7 +41,7 @@ export class ServerLocaleService extends LocaleService {
4141
take(1),
4242
mergeMap(([isAuthenticated, isLoaded]) => {
4343
let epersonLang$: Observable<string[]> = observableOf([]);
44-
if (isAuthenticated && isLoaded) {
44+
if (isAuthenticated && isLoaded && !ignoreEPersonSettings) {
4545
epersonLang$ = this.authService.getAuthenticatedUserFromStore().pipe(
4646
take(1),
4747
map((eperson) => {

0 commit comments

Comments
 (0)