Skip to content

Commit 30d14fb

Browse files
author
abhinav
committed
Merge branch 'fix-4241-language-selection-contribute-7.6' into fix-4241-language-selection-contribute-8_x
2 parents ff86e35 + ae4dadf commit 30d14fb

File tree

5 files changed

+121
-40
lines changed

5 files changed

+121
-40
lines changed

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { of } from 'rxjs';
88

99
import { RestRequestMethod } from '../data/rest-request-method';
1010
import { DspaceRestService } from '../dspace-rest/dspace-rest.service';
11+
import { HALEndpointService } from '../shared/hal-endpoint.service';
1112
import { LocaleInterceptor } from './locale.interceptor';
1213
import { LocaleService } from './locale.service';
1314

@@ -23,6 +24,10 @@ describe(`LocaleInterceptor`, () => {
2324
getLanguageCodeList: of(languageList),
2425
});
2526

27+
const mockHalEndpointService = {
28+
getRootHref: jasmine.createSpy('getRootHref'),
29+
};
30+
2631
beforeEach(() => {
2732
TestBed.configureTestingModule({
2833
imports: [HttpClientTestingModule],
@@ -33,6 +38,7 @@ describe(`LocaleInterceptor`, () => {
3338
useClass: LocaleInterceptor,
3439
multi: true,
3540
},
41+
{ provide: HALEndpointService, useValue: mockHalEndpointService },
3642
{ provide: LocaleService, useValue: mockLocaleService },
3743
],
3844
});
@@ -41,7 +47,7 @@ describe(`LocaleInterceptor`, () => {
4147
httpMock = TestBed.inject(HttpTestingController);
4248
localeService = TestBed.inject(LocaleService);
4349

44-
localeService.getCurrentLanguageCode.and.returnValue('en');
50+
localeService.getCurrentLanguageCode.and.returnValue(of('en'));
4551
});
4652

4753
describe('', () => {

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

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,19 @@ import { Observable } from 'rxjs';
99
import {
1010
mergeMap,
1111
scan,
12+
take,
1213
} from 'rxjs/operators';
1314

15+
import { HALEndpointService } from '../shared/hal-endpoint.service';
1416
import { LocaleService } from './locale.service';
1517

1618
@Injectable()
1719
export class LocaleInterceptor implements HttpInterceptor {
1820

19-
constructor(private localeService: LocaleService) {
21+
constructor(
22+
protected halEndpointService: HALEndpointService,
23+
protected localeService: LocaleService,
24+
) {
2025
}
2126

2227
/**
@@ -26,8 +31,9 @@ export class LocaleInterceptor implements HttpInterceptor {
2631
*/
2732
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
2833
let newReq: HttpRequest<any>;
29-
return this.localeService.getLanguageCodeList()
34+
return this.localeService.getLanguageCodeList(req.url === this.halEndpointService.getRootHref())
3035
.pipe(
36+
take(1),
3137
scan((acc: any, value: any) => [...acc, value], []),
3238
mergeMap((languages) => {
3339
// Clone the request to add the new header.

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

Lines changed: 61 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,12 @@ import {
77
TranslateModule,
88
TranslateService,
99
} from '@ngx-translate/core';
10+
import { of } from 'rxjs';
11+
import { TestScheduler } from 'rxjs/testing';
1012

1113
import { CookieServiceMock } from '../../shared/mocks/cookie.service.mock';
1214
import { TranslateLoaderMock } from '../../shared/mocks/translate-loader.mock';
15+
import { EPersonMock2 } from '../../shared/testing/eperson.mock';
1316
import { routeServiceStub } from '../../shared/testing/route-service.stub';
1417
import { AuthService } from '../auth/auth.service';
1518
import { CookieService } from '../services/cookie.service';
@@ -36,6 +39,7 @@ describe('LocaleService test suite', () => {
3639
authService = jasmine.createSpyObj('AuthService', {
3740
isAuthenticated: jasmine.createSpy('isAuthenticated'),
3841
isAuthenticationLoaded: jasmine.createSpy('isAuthenticationLoaded'),
42+
getAuthenticatedUserFromStore: jasmine.createSpy('getAuthenticatedUserFromStore'),
3943
});
4044

4145
const langList = ['en', 'xx', 'de'];
@@ -72,33 +76,80 @@ describe('LocaleService test suite', () => {
7276
});
7377

7478
describe('getCurrentLanguageCode', () => {
79+
let testScheduler: TestScheduler;
80+
7581
beforeEach(() => {
7682
spyOn(translateService, 'getLangs').and.returnValue(langList);
83+
testScheduler = new TestScheduler((actual, expected) => {
84+
// use jasmine to test equality
85+
expect(actual).toEqual(expected);
86+
});
87+
authService.isAuthenticated.and.returnValue(of(false));
88+
authService.isAuthenticationLoaded.and.returnValue(of(false));
7789
});
7890

7991
it('should return the language saved on cookie if it\'s a valid & active language', () => {
8092
spyOnGet.and.returnValue('de');
81-
expect(service.getCurrentLanguageCode()).toBe('de');
93+
testScheduler.run(({ expectObservable }) => {
94+
expectObservable(service.getCurrentLanguageCode()).toBe('(a|)', { a: 'de' });
95+
});
8296
});
8397

8498
it('should return the default language if the cookie language is disabled', () => {
8599
spyOnGet.and.returnValue('disabled');
86-
expect(service.getCurrentLanguageCode()).toBe('en');
100+
testScheduler.run(({ expectObservable }) => {
101+
expectObservable(service.getCurrentLanguageCode()).toBe('(a|)', { a: 'en' });
102+
});
87103
});
88104

89105
it('should return the default language if the cookie language does not exist', () => {
90106
spyOnGet.and.returnValue('does-not-exist');
91-
expect(service.getCurrentLanguageCode()).toBe('en');
107+
testScheduler.run(({ expectObservable }) => {
108+
expectObservable(service.getCurrentLanguageCode()).toBe('(a|)', { a: 'en' });
109+
});
92110
});
93111

94112
it('should return language from browser setting', () => {
95-
spyOn(translateService, 'getBrowserLang').and.returnValue('xx');
96-
expect(service.getCurrentLanguageCode()).toBe('xx');
113+
spyOn(service, 'getLanguageCodeList').and.returnValue(of(['xx', 'en']));
114+
testScheduler.run(({ expectObservable }) => {
115+
expectObservable(service.getCurrentLanguageCode()).toBe('(a|)', { a: 'xx' });
116+
});
117+
});
118+
119+
it('should match language from browser setting case insensitive', () => {
120+
spyOn(service, 'getLanguageCodeList').and.returnValue(of(['DE', 'en']));
121+
testScheduler.run(({ expectObservable }) => {
122+
expectObservable(service.getCurrentLanguageCode()).toBe('(a|)', { a: 'DE' });
123+
});
124+
});
125+
});
126+
127+
describe('getLanguageCodeList', () => {
128+
let testScheduler: TestScheduler;
129+
130+
beforeEach(() => {
131+
spyOn(translateService, 'getLangs').and.returnValue(langList);
132+
testScheduler = new TestScheduler((actual, expected) => {
133+
// use jasmine to test equality
134+
expect(actual).toEqual(expected);
135+
});
136+
});
137+
138+
it('should return default language list without user preferred language when no logged in user', () => {
139+
authService.isAuthenticated.and.returnValue(of(false));
140+
authService.isAuthenticationLoaded.and.returnValue(of(false));
141+
testScheduler.run(({ expectObservable }) => {
142+
expectObservable(service.getLanguageCodeList()).toBe('(a|)', { a: ['en-US;q=1', 'en;q=0.9'] });
143+
});
97144
});
98145

99-
it('should return default language from config', () => {
100-
spyOn(translateService, 'getBrowserLang').and.returnValue('fr');
101-
expect(service.getCurrentLanguageCode()).toBe('en');
146+
it('should return default language list with user preferred language when user is logged in', () => {
147+
authService.isAuthenticated.and.returnValue(of(true));
148+
authService.isAuthenticationLoaded.and.returnValue(of(true));
149+
authService.getAuthenticatedUserFromStore.and.returnValue(of(EPersonMock2));
150+
testScheduler.run(({ expectObservable }) => {
151+
expectObservable(service.getLanguageCodeList()).toBe('(a|)', { a: ['fr;q=0.5', 'en-US;q=1', 'en;q=0.9'] });
152+
});
102153
});
103154
});
104155

@@ -130,14 +181,13 @@ describe('LocaleService test suite', () => {
130181
});
131182

132183
it('should set the current language', () => {
133-
spyOn(service, 'getCurrentLanguageCode').and.returnValue('es');
184+
spyOn(service, 'getCurrentLanguageCode').and.returnValue(of('es'));
134185
service.setCurrentLanguageCode();
135186
expect(translateService.use).toHaveBeenCalledWith('es');
136-
expect(service.saveLanguageCodeToCookie).toHaveBeenCalledWith('es');
137187
});
138188

139189
it('should set the current language on the html tag', () => {
140-
spyOn(service, 'getCurrentLanguageCode').and.returnValue('es');
190+
spyOn(service, 'getCurrentLanguageCode').and.returnValue(of('es'));
141191
service.setCurrentLanguageCode();
142192
expect((service as any).document.documentElement.lang).toEqual('es');
143193
});

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

Lines changed: 43 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@ import { DOCUMENT } from '@angular/common';
22
import {
33
Inject,
44
Injectable,
5+
OnDestroy,
56
} from '@angular/core';
67
import { TranslateService } from '@ngx-translate/core';
78
import {
89
combineLatest,
910
Observable,
1011
of as observableOf,
12+
Subscription,
1113
} from 'rxjs';
1214
import {
1315
map,
@@ -18,6 +20,7 @@ import {
1820
import { LangConfig } from '../../../config/lang-config.interface';
1921
import { environment } from '../../../environments/environment';
2022
import {
23+
hasValue,
2124
isEmpty,
2225
isNotEmpty,
2326
} from '../../shared/empty.util';
@@ -44,13 +47,15 @@ export enum LANG_ORIGIN {
4447
* Service to provide localization handler
4548
*/
4649
@Injectable()
47-
export class LocaleService {
50+
export class LocaleService implements OnDestroy {
4851

4952
/**
5053
* Eperson language metadata
5154
*/
5255
EPERSON_LANG_METADATA = 'eperson.language';
5356

57+
subs: Subscription[] = [];
58+
5459
constructor(
5560
@Inject(NativeWindowService) protected _window: NativeWindowRef,
5661
protected cookie: CookieService,
@@ -64,39 +69,42 @@ export class LocaleService {
6469
/**
6570
* Get the language currently used
6671
*
67-
* @returns {string} The language code
72+
* @returns {Observable<string>} The language code
6873
*/
69-
getCurrentLanguageCode(): string {
74+
getCurrentLanguageCode(): Observable<string> {
7075
// Attempt to get the language from a cookie
71-
let lang = this.getLanguageCodeFromCookie();
76+
const lang = this.getLanguageCodeFromCookie();
7277
if (isEmpty(lang) || environment.languages.find((langConfig: LangConfig) => langConfig.code === lang && langConfig.active) === undefined) {
7378
// Attempt to get the browser language from the user
74-
if (this.translate.getLangs().includes(this.translate.getBrowserLang())) {
75-
lang = this.translate.getBrowserLang();
76-
} else {
77-
lang = environment.defaultLanguage;
78-
}
79+
return this.getLanguageCodeList()
80+
.pipe(
81+
map(browserLangs => {
82+
return browserLangs
83+
.map(browserLang => browserLang.split(';')[0])
84+
.find(browserLang =>
85+
this.translate.getLangs().some(userLang => userLang.toLowerCase() === browserLang.toLowerCase()),
86+
) || environment.defaultLanguage;
87+
}),
88+
);
7989
}
80-
return lang;
90+
return observableOf(lang);
8191
}
8292

8393
/**
8494
* Get the languages list of the user in Accept-Language format
8595
*
8696
* @returns {Observable<string[]>}
8797
*/
88-
getLanguageCodeList(): Observable<string[]> {
98+
getLanguageCodeList(ignoreEPersonSettings = false): Observable<string[]> {
8999
const obs$ = combineLatest([
90100
this.authService.isAuthenticated(),
91101
this.authService.isAuthenticationLoaded(),
92102
]);
93103

94104
return obs$.pipe(
95-
take(1),
96105
mergeMap(([isAuthenticated, isLoaded]) => {
97-
// TODO to enabled again when https://github.com/DSpace/dspace-angular/issues/739 will be resolved
98-
const epersonLang$: Observable<string[]> = observableOf([]);
99-
/* if (isAuthenticated && isLoaded) {
106+
let epersonLang$: Observable<string[]> = observableOf([]);
107+
if (isAuthenticated && isLoaded && !ignoreEPersonSettings) {
100108
epersonLang$ = this.authService.getAuthenticatedUserFromStore().pipe(
101109
take(1),
102110
map((eperson) => {
@@ -109,21 +117,21 @@ export class LocaleService {
109117
!isEmpty(this.translate.currentLang)));
110118
}
111119
return languages;
112-
})
120+
}),
113121
);
114-
}*/
122+
}
115123
return epersonLang$.pipe(
116124
map((epersonLang: string[]) => {
117125
const languages: string[] = [];
126+
if (isNotEmpty(epersonLang)) {
127+
languages.push(...epersonLang);
128+
}
118129
if (this.translate.currentLang) {
119130
languages.push(...this.setQuality(
120131
[this.translate.currentLang],
121132
LANG_ORIGIN.UI,
122133
false));
123134
}
124-
if (isNotEmpty(epersonLang)) {
125-
languages.push(...epersonLang);
126-
}
127135
if (navigator.languages) {
128136
languages.push(...this.setQuality(
129137
Object.assign([], navigator.languages),
@@ -163,11 +171,16 @@ export class LocaleService {
163171
*/
164172
setCurrentLanguageCode(lang?: string): void {
165173
if (isEmpty(lang)) {
166-
lang = this.getCurrentLanguageCode();
174+
this.subs.push(this.getCurrentLanguageCode().subscribe(curLang => {
175+
lang = curLang;
176+
this.translate.use(lang);
177+
this.document.documentElement.lang = lang;
178+
}));
179+
} else {
180+
this.saveLanguageCodeToCookie(lang);
181+
this.translate.use(lang);
182+
this.document.documentElement.lang = lang;
167183
}
168-
this.translate.use(lang);
169-
this.saveLanguageCodeToCookie(lang);
170-
this.document.documentElement.lang = lang;
171184
}
172185

173186
/**
@@ -213,4 +226,10 @@ export class LocaleService {
213226

214227
}
215228

229+
ngOnDestroy(): void {
230+
this.subs
231+
.filter((sub) => hasValue(sub))
232+
.forEach((sub) => sub.unsubscribe());
233+
}
234+
216235
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ export class ServerLocaleService extends LocaleService {
5353
*
5454
* @returns {Observable<string[]>}
5555
*/
56-
getLanguageCodeList(): Observable<string[]> {
56+
getLanguageCodeList(ignoreEPersonSettings = false): Observable<string[]> {
5757
const obs$ = combineLatest([
5858
this.authService.isAuthenticated(),
5959
this.authService.isAuthenticationLoaded(),
@@ -63,7 +63,7 @@ export class ServerLocaleService extends LocaleService {
6363
take(1),
6464
mergeMap(([isAuthenticated, isLoaded]) => {
6565
let epersonLang$: Observable<string[]> = observableOf([]);
66-
if (isAuthenticated && isLoaded) {
66+
if (isAuthenticated && isLoaded && !ignoreEPersonSettings) {
6767
epersonLang$ = this.authService.getAuthenticatedUserFromStore().pipe(
6868
take(1),
6969
map((eperson) => {

0 commit comments

Comments
 (0)