Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion src/app/core/locale/locale.interceptor.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { of } from 'rxjs';

import { RestRequestMethod } from '../data/rest-request-method';
import { DspaceRestService } from '../dspace-rest/dspace-rest.service';
import { HALEndpointService } from '../shared/hal-endpoint.service';
import { LocaleInterceptor } from './locale.interceptor';
import { LocaleService } from './locale.service';

Expand All @@ -27,6 +28,10 @@ describe(`LocaleInterceptor`, () => {
getLanguageCodeList: of(languageList),
});

const mockHalEndpointService = {
getRootHref: jasmine.createSpy('getRootHref'),
};

beforeEach(() => {
TestBed.configureTestingModule({
imports: [],
Expand All @@ -37,6 +42,7 @@ describe(`LocaleInterceptor`, () => {
useClass: LocaleInterceptor,
multi: true,
},
{ provide: HALEndpointService, useValue: mockHalEndpointService },
{ provide: LocaleService, useValue: mockLocaleService },
provideHttpClient(withInterceptorsFromDi()),
provideHttpClientTesting(),
Expand All @@ -47,7 +53,7 @@ describe(`LocaleInterceptor`, () => {
httpMock = TestBed.inject(HttpTestingController);
localeService = TestBed.inject(LocaleService);

localeService.getCurrentLanguageCode.and.returnValue('en');
localeService.getCurrentLanguageCode.and.returnValue(of('en'));
});

describe('', () => {
Expand Down
10 changes: 8 additions & 2 deletions src/app/core/locale/locale.interceptor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,19 @@ import { Observable } from 'rxjs';
import {
mergeMap,
scan,
take,
} from 'rxjs/operators';

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

@Injectable()
export class LocaleInterceptor implements HttpInterceptor {

constructor(private localeService: LocaleService) {
constructor(
protected halEndpointService: HALEndpointService,
protected localeService: LocaleService,
) {
}

/**
Expand All @@ -26,8 +31,9 @@ export class LocaleInterceptor implements HttpInterceptor {
*/
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
let newReq: HttpRequest<any>;
return this.localeService.getLanguageCodeList()
return this.localeService.getLanguageCodeList(req.url === this.halEndpointService.getRootHref())
.pipe(
take(1),
scan((acc: any, value: any) => [...acc, value], []),
mergeMap((languages) => {
// Clone the request to add the new header.
Expand Down
72 changes: 61 additions & 11 deletions src/app/core/locale/locale.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,12 @@ import {
TranslateModule,
TranslateService,
} from '@ngx-translate/core';
import { of } from 'rxjs';
import { TestScheduler } from 'rxjs/testing';

import { CookieServiceMock } from '../../shared/mocks/cookie.service.mock';
import { TranslateLoaderMock } from '../../shared/mocks/translate-loader.mock';
import { EPersonMock2 } from '../../shared/testing/eperson.mock';
import { routeServiceStub } from '../../shared/testing/route-service.stub';
import { AuthService } from '../auth/auth.service';
import { CookieService } from '../services/cookie.service';
Expand All @@ -36,6 +39,7 @@ describe('LocaleService', () => {
authService = jasmine.createSpyObj('AuthService', {
isAuthenticated: jasmine.createSpy('isAuthenticated'),
isAuthenticationLoaded: jasmine.createSpy('isAuthenticationLoaded'),
getAuthenticatedUserFromStore: jasmine.createSpy('getAuthenticatedUserFromStore'),
});

const langList = ['en', 'xx', 'de'];
Expand Down Expand Up @@ -72,33 +76,80 @@ describe('LocaleService', () => {
});

describe('getCurrentLanguageCode', () => {
let testScheduler: TestScheduler;

beforeEach(() => {
spyOn(translateService, 'getLangs').and.returnValue(langList);
testScheduler = new TestScheduler((actual, expected) => {
// use jasmine to test equality
expect(actual).toEqual(expected);
});
authService.isAuthenticated.and.returnValue(of(false));
authService.isAuthenticationLoaded.and.returnValue(of(false));
});

it('should return the language saved on cookie if it\'s a valid & active language', () => {
spyOnGet.and.returnValue('de');
expect(service.getCurrentLanguageCode()).toBe('de');
testScheduler.run(({ expectObservable }) => {
expectObservable(service.getCurrentLanguageCode()).toBe('(a|)', { a: 'de' });
});
});

it('should return the fallback language if the cookie language is disabled', () => {
spyOnGet.and.returnValue('disabled');
expect(service.getCurrentLanguageCode()).toBe('en');
testScheduler.run(({ expectObservable }) => {
expectObservable(service.getCurrentLanguageCode()).toBe('(a|)', { a: 'en' });
});
});

it('should return the fallback language if the cookie language does not exist', () => {
spyOnGet.and.returnValue('does-not-exist');
expect(service.getCurrentLanguageCode()).toBe('en');
testScheduler.run(({ expectObservable }) => {
expectObservable(service.getCurrentLanguageCode()).toBe('(a|)', { a: 'en' });
});
});

it('should return language from browser setting', () => {
spyOn(translateService, 'getBrowserLang').and.returnValue('xx');
expect(service.getCurrentLanguageCode()).toBe('xx');
spyOn(service, 'getLanguageCodeList').and.returnValue(of(['xx', 'en']));
testScheduler.run(({ expectObservable }) => {
expectObservable(service.getCurrentLanguageCode()).toBe('(a|)', { a: 'xx' });
});
});

it('should match language from browser setting case insensitive', () => {
spyOn(service, 'getLanguageCodeList').and.returnValue(of(['DE', 'en']));
testScheduler.run(({ expectObservable }) => {
expectObservable(service.getCurrentLanguageCode()).toBe('(a|)', { a: 'DE' });
});
});
});

describe('getLanguageCodeList', () => {
let testScheduler: TestScheduler;

beforeEach(() => {
spyOn(translateService, 'getLangs').and.returnValue(langList);
testScheduler = new TestScheduler((actual, expected) => {
// use jasmine to test equality
expect(actual).toEqual(expected);
});
});

it('should return default language list without user preferred language when no logged in user', () => {
authService.isAuthenticated.and.returnValue(of(false));
authService.isAuthenticationLoaded.and.returnValue(of(false));
testScheduler.run(({ expectObservable }) => {
expectObservable(service.getLanguageCodeList()).toBe('(a|)', { a: ['en-US;q=1', 'en;q=0.9'] });
});
});

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

Expand Down Expand Up @@ -130,14 +181,13 @@ describe('LocaleService', () => {
});

it('should set the current language', () => {
spyOn(service, 'getCurrentLanguageCode').and.returnValue('es');
spyOn(service, 'getCurrentLanguageCode').and.returnValue(of('es'));
service.setCurrentLanguageCode();
expect(translateService.use).toHaveBeenCalledWith('es');
expect(service.saveLanguageCodeToCookie).toHaveBeenCalledWith('es');
});

it('should set the current language on the html tag', () => {
spyOn(service, 'getCurrentLanguageCode').and.returnValue('es');
spyOn(service, 'getCurrentLanguageCode').and.returnValue(of('es'));
service.setCurrentLanguageCode();
expect((service as any).document.documentElement.lang).toEqual('es');
});
Expand Down
67 changes: 43 additions & 24 deletions src/app/core/locale/locale.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ import {
DOCUMENT,
Inject,
Injectable,
OnDestroy,
} from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import {
combineLatest,
Observable,
of,
Subscription,
} from 'rxjs';
import {
map,
Expand All @@ -18,6 +20,7 @@ import {
import { LangConfig } from '../../../config/lang-config.interface';
import { environment } from '../../../environments/environment';
import {
hasValue,
isEmpty,
isNotEmpty,
} from '../../shared/empty.util';
Expand All @@ -44,13 +47,15 @@ export enum LANG_ORIGIN {
* Service to provide localization handler
*/
@Injectable()
export class LocaleService {
export class LocaleService implements OnDestroy {

/**
* Eperson language metadata
*/
EPERSON_LANG_METADATA = 'eperson.language';

subs: Subscription[] = [];

constructor(
@Inject(NativeWindowService) protected _window: NativeWindowRef,
protected cookie: CookieService,
Expand All @@ -64,39 +69,42 @@ export class LocaleService {
/**
* Get the language currently used
*
* @returns {string} The language code
* @returns {Observable<string>} The language code
*/
getCurrentLanguageCode(): string {
getCurrentLanguageCode(): Observable<string> {
// Attempt to get the language from a cookie
let lang = this.getLanguageCodeFromCookie();
const lang = this.getLanguageCodeFromCookie();
if (isEmpty(lang) || environment.languages.find((langConfig: LangConfig) => langConfig.code === lang && langConfig.active) === undefined) {
// Attempt to get the browser language from the user
if (this.translate.getLangs().includes(this.translate.getBrowserLang())) {
lang = this.translate.getBrowserLang();
} else {
lang = environment.fallbackLanguage;
}
return this.getLanguageCodeList()
.pipe(
map(browserLangs => {
return browserLangs
.map(browserLang => browserLang.split(';')[0])
.find(browserLang =>
this.translate.getLangs().some(userLang => userLang.toLowerCase() === browserLang.toLowerCase()),
) || environment.fallbackLanguage;
}),
);
}
return lang;
return of(lang);
}

/**
* Get the languages list of the user in Accept-Language format
*
* @returns {Observable<string[]>}
*/
getLanguageCodeList(): Observable<string[]> {
getLanguageCodeList(ignoreEPersonSettings = false): Observable<string[]> {
const obs$ = combineLatest([
this.authService.isAuthenticated(),
this.authService.isAuthenticationLoaded(),
]);

return obs$.pipe(
take(1),
mergeMap(([isAuthenticated, isLoaded]) => {
// TODO to enabled again when https://github.com/DSpace/dspace-angular/issues/739 will be resolved
const epersonLang$: Observable<string[]> = of([]);
/* if (isAuthenticated && isLoaded) {
let epersonLang$: Observable<string[]> = of([]);
if (isAuthenticated && isLoaded && !ignoreEPersonSettings) {
epersonLang$ = this.authService.getAuthenticatedUserFromStore().pipe(
take(1),
map((eperson) => {
Expand All @@ -109,21 +117,21 @@ export class LocaleService {
!isEmpty(this.translate.getCurrentLang())));
}
return languages;
})
}),
);
}*/
}
return epersonLang$.pipe(
map((epersonLang: string[]) => {
const languages: string[] = [];
if (isNotEmpty(epersonLang)) {
languages.push(...epersonLang);
}
if (this.translate.currentLang) {
languages.push(...this.setQuality(
[this.translate.getCurrentLang()],
LANG_ORIGIN.UI,
false));
}
if (isNotEmpty(epersonLang)) {
languages.push(...epersonLang);
}
if (navigator.languages) {
languages.push(...this.setQuality(
Object.assign([], navigator.languages),
Expand Down Expand Up @@ -163,11 +171,16 @@ export class LocaleService {
*/
setCurrentLanguageCode(lang?: string): void {
if (isEmpty(lang)) {
lang = this.getCurrentLanguageCode();
this.subs.push(this.getCurrentLanguageCode().subscribe(curLang => {
lang = curLang;
this.translate.use(lang);
this.document.documentElement.lang = lang;
}));
} else {
this.saveLanguageCodeToCookie(lang);
this.translate.use(lang);
this.document.documentElement.lang = lang;
}
this.translate.use(lang);
this.saveLanguageCodeToCookie(lang);
this.document.documentElement.lang = lang;
}

/**
Expand Down Expand Up @@ -213,4 +226,10 @@ export class LocaleService {

}

ngOnDestroy(): void {
this.subs
.filter((sub) => hasValue(sub))
.forEach((sub) => sub.unsubscribe());
}

}
2 changes: 1 addition & 1 deletion src/app/core/locale/server-locale.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export class ServerLocaleService extends LocaleService {
*
* @returns {Observable<string[]>}
*/
getLanguageCodeList(): Observable<string[]> {
getLanguageCodeList(ignoreEPersonSettings = false): Observable<string[]> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@AbhinavS96 : It appears this new setting isn't being used in this backport. Shouldn't this be used below on line 66 just like in #4805?

const obs$ = combineLatest([
this.authService.isAuthenticated(),
this.authService.isAuthenticationLoaded(),
Expand Down
Loading