Skip to content

Commit 9d46eb0

Browse files
authored
Merge pull request #262 from TheJacksonLaboratory/feature/translations
Feature/translations
2 parents 185dad9 + 19d5bd4 commit 9d46eb0

File tree

18 files changed

+49217
-59
lines changed

18 files changed

+49217
-59
lines changed

client/package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

client/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "hpo-web",
3-
"version": "1.8.11",
3+
"version": "1.8.12",
44
"license": "MIT",
55
"scripts": {
66
"ng": "ng",

client/src/app/browser/browser.module.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,17 @@
22
import {NgModule} from '@angular/core';
33
import {CommonModule} from '@angular/common';
44
import {FormsModule, ReactiveFormsModule} from '@angular/forms';
5+
import { TranslatePipe } from '../shared/pipes/translate.pipe';
56
import {BrowserRoutingModule} from './browser-routing.module';
67
// Material Modules
78
import {GlobalMaterialModules} from '../shared/modules/global.module';
89
// Services
910
import {SearchService} from '../shared/search/service/search.service';
11+
import {LanguageService} from './services/language/language.service';
1012
import {TermService} from './services/term/term.service';
1113
import {GeneService} from './services/gene/gene.service';
1214
import {DiseaseService} from './services/disease/disease.service';
15+
import {OntologyService} from './services/ontology/ontology.service';
1316
// Components
1417
import {TermComponent} from './pages/term/term.component';
1518
import {DiseaseComponent} from './pages/disease/disease.component';
@@ -30,10 +33,10 @@ import {ProfileSearchComponent} from './pages/profile-search/profile-search.comp
3033
GlobalMaterialModules,
3134
ExtrasModule
3235
],
33-
providers: [SearchService, TermService, GeneService, DiseaseService, DialogService],
36+
providers: [SearchService, TermService, GeneService, DiseaseService, DialogService, OntologyService],
3437
declarations: [TermComponent, DiseaseComponent,
3538
GeneComponent,
36-
SearchResultsComponent, DialogExcelDownloadComponent, ProfileSearchComponent]
39+
SearchResultsComponent, DialogExcelDownloadComponent, ProfileSearchComponent, TranslatePipe]
3740
})
3841
export class BrowserHPOModule {
3942
}

client/src/app/browser/models/models.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ export interface Term {
6363
treeCountWidth?: number;
6464
treeMargin?: number;
6565
pubmedXrefs: Array<any>;
66+
translations?: Translation[];
6667
}
6768

6869
export interface TermTree {
@@ -133,3 +134,15 @@ export interface TeamMember {
133134
link?: string;
134135
alumni?: boolean;
135136
}
137+
138+
139+
export interface Translation extends Language {
140+
id?: string;
141+
name: string;
142+
status: string;
143+
}
144+
145+
export interface Language {
146+
language: string;
147+
language_long: string;
148+
}

client/src/app/browser/pages/term/term.component.css

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,3 +223,30 @@
223223
color: lightgray;
224224
display: inline-block;
225225
}
226+
227+
.translation-chip {
228+
background-color: #f1f0f0;
229+
border-radius: 5px;
230+
}
231+
232+
img.mat-chip-avatar::before {
233+
width: 24px;
234+
background: #4f8aa2;
235+
height: 24px;
236+
border-radius: 50%;
237+
justify-content: center;
238+
align-items: center;
239+
display: flex;
240+
overflow: hidden;
241+
object-fit: cover;
242+
content: ' ';
243+
}
244+
245+
.translate-btn {
246+
color: white;
247+
margin: 0 0 0 .5rem;
248+
}
249+
250+
.translate-btn .material-icons {
251+
font-size: 18px;
252+
}

client/src/app/browser/pages/term/term.component.html

Lines changed: 33 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -18,20 +18,20 @@ <h3 class="card-title">Hierarchy</h3>
1818
<span class="tree-counts" [ngStyle]="{'margin-left': '-100px', 'margin-right': '5px'}"
1919
matTooltip="{{parent.childrenCount}}" *ngIf="parent.childrenCount != 0"
2020
matTooltipPosition="above"> </span>
21-
<a class="link" routerLink="../{{parent.ontologyId}}">{{parent.name}}</a>
21+
<a class="link" routerLink="../{{parent.ontologyId}}">{{parent.name | translate: parent.translations: selectedLanguage.language}}</a>
2222
</li>
2323
<li>
2424
<div class="term">
2525
<span class="tree-counts" [ngStyle]="{'margin-left': '-115px'}"
2626
*ngIf="treeData.termCount != 0" matTooltip="{{treeData.termCount}}"
2727
matTooltipPosition="above"> </span>
28-
<strong>{{term.name}}</strong>
28+
<strong>{{term.name | translate: term.translations: selectedLanguage.language}}</strong>
2929
</div>
3030
<ul>
3131
<li *ngFor="let child of treeData.children" class="children">
3232
<span class="tree-counts" [ngStyle]="setTreeStyles(child)" *ngIf="child.childrenCount != 0"
3333
matTooltip="{{child.childrenCount}}" matTooltipPosition="above"> </span>
34-
<a class="link" routerLink="../{{child.ontologyId}}">{{child.name}}</a>
34+
<a class="link" routerLink="../{{child.ontologyId}}">{{child.name | translate: child.translations: selectedLanguage.language}}</a>
3535
</li>
3636
</ul>
3737
</li>
@@ -49,38 +49,48 @@ <h3 class="card-title">Hierarchy</h3>
4949
<mat-progress-bar class="loading-result-details" color="primary" mode="indeterminate">
5050
</mat-progress-bar>
5151
</div>
52-
<div *ngIf="treeData">
52+
<div *ngIf="treeData && term">
5353
<div>
54-
<span class="item-title">{{termTitle}}&nbsp;</span>
54+
<span class="item-title">{{termTitle | translate: term.translations: selectedLanguage.language}}&nbsp;</span>
5555
<span class="item-id" *ngIf="paramId != term.id">
56-
<span class="crossout">{{paramId}}
57-
</span><i class="material-icons obsolete-arrow">arrow_right_alt</i>
58-
</span>
56+
<span class="crossout">{{paramId}}</span>
57+
<i class="material-icons obsolete-arrow">arrow_right_alt</i></span>
5958
<span class="item-id">{{term.id}}</span>
6059
<!-- <i matTooltip="Copy purl to clipboard!" matTooltipPosition="above"
6160
(click)="copyToClipboard(term.purl)" class="material-icons copyToClipboard">content_copy</i>-->
61+
<br>
62+
<mat-chip-list class="translation-chips" aria-label="Language Selection" multiple="false">
63+
<mat-chip class="translation-chip" *ngFor="let lang of languages" color="primary"
64+
(click)="changeLanguage(lang)" [selected]="selectedLanguage.language === lang.language" [disableRipple]="true">
65+
<img matChipAvatar src="https://unpkg.com/language-icons@0.3.0/icons/{{lang.language}}.svg" alt="{{lang.language}}"/>
66+
{{lang.language_long}}
67+
</mat-chip>
68+
</mat-chip-list>
6269
</div>
63-
6470
<hr class="underline-hr">
65-
<p><br><i>{{term.definition}}</i></p>
71+
<div>
72+
<p>
73+
{{term.definition != "" ? term.definition : "No definition found."}}
74+
</p>
75+
</div>
6676
<mat-list>
6777
<mat-divider></mat-divider>
6878
<mat-list-item>
69-
<strong>Synonyms:</strong> &nbsp;&nbsp; <i
70-
*ngFor="let item of term.synonyms; let isLast=last">{{item}}{{isLast ? '' : ', '}}</i>
79+
<strong>Synonyms:</strong> &nbsp;&nbsp;
80+
<i *ngFor="let item of term.synonyms; let isLast=last">{{item}}{{isLast ? '' : "\t-\t"}}</i>
7181
</mat-list-item>
7282
<mat-divider></mat-divider>
7383
<mat-list-item *ngIf="term.comment">
7484
<strong>Comment:</strong> &nbsp;&nbsp; {{term.comment}}
7585
</mat-list-item>
7686
<mat-divider></mat-divider>
77-
<mat-list-item *ngIf="term.pubmedXrefs.length > 0">
78-
<strong>Pubmed References:</strong> &nbsp;&nbsp;
79-
<span *ngFor="let item of term.pubmedXrefs; let isLast=last">
80-
<a href="https://www.ncbi.nlm.nih.gov/pubmed/{{item.id}}" target="_blank"><i> {{item.whole}}</i></a>
81-
{{isLast ? '' : ', '}}
82-
</span>
83-
</mat-list-item>
87+
<!-- <mat-list-item *ngIf="term.pubmedXrefs.length > 0">-->
88+
<!-- <strong>Pubmed References:</strong> &nbsp;&nbsp;-->
89+
<!-- <span *ngFor="let item of term.pubmedXrefs; let isLast=last">-->
90+
<!-- <a href="https://www.ncbi.nlm.nih.gov/pubmed/{{item.id}}" target="_blank"><i> {{item.whole}}</i></a>-->
91+
<!-- {{isLast ? '' : ', '}}-->
92+
<!-- </span>-->
93+
<!-- </mat-list-item>-->
8494
<mat-list-item *ngIf="term.xrefs.length > 0">
8595
<strong>Cross References:</strong> &nbsp;&nbsp;
8696
<i *ngFor="let item of term.xrefs; let isLast=last">{{item}}{{isLast ? '' : ', '}}</i>
@@ -91,12 +101,15 @@ <h3 class="card-title">Hierarchy</h3>
91101
[disabled]="this.diseaseAssocCount == 0 && this.geneAssocCount == 0"
92102
(click)="downloadDialog()"><span class="material-icons">get_app</span> Export Associations
93103
</button>
104+
<a class="translate-btn" href="https://obophenotype.github.io/hpo-translations/" mat-stroked-button color="primary" target="_blank">
105+
<span class="material-icons">open_in_new</span> Translate your language
106+
</a>
94107
</div>
95108
</mat-card-content>
96109
</mat-card>
97110

98111
<!-- TABS -->
99-
<div class="hpo-group-tab">
112+
<div class="hpo-group-tab" *ngIf="term">
100113
<mat-tab-group selectedIndex="0" class="term-details-tab-group mat-elevation-z6">
101114
<!-- Disease association -->
102115
<mat-tab label="Disease Associations">

client/src/app/browser/pages/term/term.component.ts

Lines changed: 75 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@ import {ActivatedRoute, Params, Router} from '@angular/router';
33
import {MatTableDataSource} from '@angular/material/table';
44
import {MatPaginator} from '@angular/material/paginator';
55
import {MatSort} from '@angular/material/sort';
6-
import {forkJoin as observableForkJoin} from 'rxjs';
7-
import {switchMap} from 'rxjs/operators';
6+
import {forkJoin, forkJoin as observableForkJoin} from 'rxjs';
7+
import { of } from 'rxjs/internal/observable/of';
8+
import { catchError, switchMap } from 'rxjs/operators';
9+
import { LanguageService } from '../../services/language/language.service';
810
import {TermService} from '../../services/term/term.service';
9-
import {Disease, Gene, LoincEntry, Term, TermTree} from '../../models/models';
11+
import {Disease, Gene, Language, LoincEntry, Term, TermTree, Translation} from '../../models/models';
1012
import {DialogService} from '../../../shared/dialog-excel-download/dialog.service';
11-
13+
import {OntologyService} from "../../services/ontology/ontology.service";
1214

1315
@Component({
1416
selector: 'app-term',
@@ -19,10 +21,7 @@ export class TermComponent implements OnInit {
1921
termTitle: string;
2022
query: string;
2123
paramId: string;
22-
term: Term = {
23-
'id': '', 'name': '', 'definition': '', 'altTermIds': [], 'comment': '', 'synonyms': [],
24-
'isObsolete': true, 'xrefs': [], 'pubmedXrefs': [], 'purl': ''
25-
};
24+
term: Term;
2625
geneColumns = ['geneId', 'dbDiseases'];
2726
geneSource: MatTableDataSource<Gene>;
2827
geneAssocCount: number;
@@ -48,13 +47,15 @@ export class TermComponent implements OnInit {
4847
overlay = false;
4948
displayAllDiseaseAssc = false;
5049
displayAllGeneAssc = false;
50+
languages: Language[];
51+
selectedLanguage: Language = {language: "en", language_long: "English"}
5152

5253
@ViewChild('diseasePaginator', {static: true}) diseasePaginator: MatPaginator;
5354
@ViewChild('genePaginator', {static: true}) genePaginator: MatPaginator;
5455
@ViewChild(MatSort) sort: MatSort;
5556

56-
constructor(private route: ActivatedRoute, private termService: TermService, private dialogService: DialogService,
57-
private router: Router) {
57+
constructor(private route: ActivatedRoute, private termService: TermService, private ontologyService: OntologyService,
58+
private dialogService: DialogService, private languageService: LanguageService, private router: Router) {
5859
}
5960

6061
ngOnInit() {
@@ -100,26 +101,28 @@ export class TermComponent implements OnInit {
100101
}
101102

102103
refreshData(query: string) {
103-
this.termService.searchTerm(query)
104-
.subscribe((data) => {
105-
this.setDefaults(data.details);
106-
const maxTermWidth = 100;
107-
this.treeData = data.relations;
108-
this.treeData.maxTermWidth = maxTermWidth;
109-
const termCount = this.treeData.termCount;
110-
this.treeData.children.sort((a, b) => a.childrenCount > b.childrenCount ? (-1) : 1);
111-
this.treeData.children.map(term => {
112-
const percent = term.childrenCount / termCount;
113-
const newWidth = Math.ceil(maxTermWidth * percent);
114-
const newMargin = -115 + ((maxTermWidth - newWidth) - 5);
115-
term.treeCountWidth = newWidth;
116-
term.treeMargin = newMargin;
117-
});
118-
this.termTitle = this.term.name;
119-
}, (error) => {
120-
// Error bubbles up
121-
console.log(error);
104+
forkJoin( {
105+
hpo: this.termService.searchTerm(query),
106+
term: this.ontologyService.term(query).pipe(catchError(e => { console.error(e); return of(undefined)})),
107+
parents: this.ontologyService.parents(query).pipe(catchError(e => of([]))),
108+
children: this.ontologyService.children(query).pipe(catchError(e => of([])))
109+
}).subscribe(({hpo, term, parents, children}) => {
110+
this.setDefaults(term);
111+
const maxTermWidth = 100;
112+
hpo.relations = this.joinTranslation(hpo.relations, parents, children);
113+
this.treeData = hpo.relations;
114+
this.treeData.maxTermWidth = maxTermWidth;
115+
const termCount = this.treeData.termCount;
116+
this.treeData.children.sort((a, b) => a.childrenCount > b.childrenCount ? (-1) : 1);
117+
this.treeData.children.map(term => {
118+
const percent = term.childrenCount / termCount;
119+
const newWidth = Math.ceil(maxTermWidth * percent);
120+
const newMargin = -115 + ((maxTermWidth - newWidth) - 5);
121+
term.treeCountWidth = newWidth;
122+
term.treeMargin = newMargin;
122123
});
124+
this.termTitle = this.term.name;
125+
});
123126
}
124127

125128
reloadDiseaseAssociations(offset: string, max: string) {
@@ -158,12 +161,48 @@ export class TermComponent implements OnInit {
158161
this.term.definition = (term.definition != null) ? term.definition : 'Sorry this term has no definition.';
159162
this.term.purl = 'http://purl.obolibrary.org/obo/' + term.id.replace(':', '_');
160163
this.term.xrefs = (term.xrefs != null) ? term.xrefs : [];
161-
this.term.pubmedXrefs = (term.pubmedXrefs != null) ? term.pubmedXrefs.map(pmid => {
162-
return {whole: pmid, id: pmid.split(':')[1]};
163-
}) : [];
164+
165+
if (term.translations != undefined && term.translations.length > 0){
166+
// Get unique set of languages
167+
this.languages = [...new Set(term.translations.map((t) => {
168+
return {language: t.language, language_long: t.language_long}
169+
}))];
170+
171+
// Add english default
172+
this.languages.unshift(this.languageService.default);
173+
174+
// If the active language is updated set it.
175+
this.languageService.active$.subscribe((active) => {
176+
const exist = this.languages.some(li => li.language == active.language);
177+
if (exist) {
178+
this.selectedLanguage = active;
179+
} else {
180+
this.languageService.change(this.languageService.default);
181+
}
182+
});
183+
}
164184
}
165185
}
166186

187+
joinTranslation(relations, parents, children){
188+
relations.children.map((child) => {
189+
children.map((childT) => {
190+
if(child.ontologyId == childT.id){
191+
child.translations = childT.translations
192+
}
193+
});
194+
});
195+
196+
relations.parents.map((parent) => {
197+
parents.map((parentT) => {
198+
if(parent.ontologyId == parentT.id){
199+
parent.translations = parentT.translations
200+
}
201+
});
202+
});
203+
return relations;
204+
}
205+
167206
showAllDiseases(event) {
168207
this.assocLoading = true;
169208
this.reloadDiseaseAssociations('0', '-1');
@@ -217,6 +256,10 @@ export class TermComponent implements OnInit {
217256
setTreeStyles(child: Term): any {
218257
return {'width': child.treeCountWidth + 'px', 'margin-left': child.treeMargin + 'px', 'margin-right': '20px'};
219258
}
259+
260+
changeLanguage(language){
261+
this.languageService.change(language);
262+
}
220263
}
221264

222265

0 commit comments

Comments
 (0)