diff --git a/src/app/components/shared/_dialogs/topic-cloud-administration/TopicCloudAdminData.ts b/src/app/components/shared/_dialogs/topic-cloud-administration/TopicCloudAdminData.ts index a082cb09586fe5cf594622d199bc25d5178850dc..1337a08d1e37606c118c08f6ee6244e999e15e97 100644 --- a/src/app/components/shared/_dialogs/topic-cloud-administration/TopicCloudAdminData.ts +++ b/src/app/components/shared/_dialogs/topic-cloud-administration/TopicCloudAdminData.ts @@ -1,3 +1,5 @@ +import { ProfanityFilter } from '../../../../models/room'; + export interface TopicCloudAdminData { blacklist: string[]; wantedLabels: { @@ -5,7 +7,7 @@ export interface TopicCloudAdminData { en: string[]; }; considerVotes: boolean; - profanityFilter: boolean; + profanityFilter: ProfanityFilter; blacklistIsActive: boolean; keywordORfulltext: KeywordOrFulltext; minQuestions: number; diff --git a/src/app/components/shared/_dialogs/topic-cloud-administration/topic-cloud-administration.component.html b/src/app/components/shared/_dialogs/topic-cloud-administration/topic-cloud-administration.component.html index d6c9cbe62dc6a28300ef4e6cfd510f6ed959372c..c9951cbf3bb93834d67ae6ffa22d56b305fc37e1 100644 --- a/src/app/components/shared/_dialogs/topic-cloud-administration/topic-cloud-administration.component.html +++ b/src/app/components/shared/_dialogs/topic-cloud-administration/topic-cloud-administration.component.html @@ -28,22 +28,116 @@ </mat-radio-group> </mat-card> - <mat-slide-toggle [(ngModel)]="considerVotes"> - {{'topic-cloud-dialog.consider-votes' | translate}} - </mat-slide-toggle> - <div *ngIf="isCreatorOrMod"> - <mat-slide-toggle (change)="changeProfanityFilter()" [(ngModel)]="profanityFilter"> - {{'topic-cloud-dialog.profanity' | translate}} - </mat-slide-toggle> - <mat-slide-toggle [(ngModel)]="blacklistIsActive"> - {{'topic-cloud-dialog.hide-blacklist-words' | translate}} + <mat-card style="background: none;"> + <mat-slide-toggle [(ngModel)]="considerVotes"> + {{'topic-cloud-dialog.consider-votes' | translate}} </mat-slide-toggle> + </mat-card> + <div *ngIf="isCreatorOrMod"> + <mat-card style="background: none;"> + <mat-slide-toggle (change)="showMessage('words-will-be-overwritten', $event.checked)" + [(ngModel)]="profanityFilter"> + {{'topic-cloud-dialog.profanity' | translate}} + </mat-slide-toggle> + <mat-slide-toggle *ngIf="profanityFilter" + (change)="showMessage('only-specific-language-will-be-filtered', $event.checked)" + [(ngModel)]="censorLanguageSpecificCheck"> + {{'topic-cloud-dialog.language-specific-filter' | translate}} + </mat-slide-toggle> + <mat-slide-toggle *ngIf="profanityFilter" + (change)="showMessage('partial-words-will-be-filtered', $event.checked)" + [(ngModel)]="censorPartialWordsCheck"> + {{'topic-cloud-dialog.partial-words-filter' | translate}} + </mat-slide-toggle> + + <mat-accordion> + <mat-expansion-panel class="color-background" (opened)="focusInput('test-profanity-input')"> + <mat-expansion-panel-header class="color-background"> + <mat-panel-title> + {{'topic-cloud-dialog.test-profanity' | translate}} + </mat-panel-title> + </mat-expansion-panel-header> + <p> + <mat-form-field *ngIf="censorLanguageSpecificCheck"> + <mat-label>{{'topic-cloud-dialog.language' | translate}}</mat-label> + <mat-select [(value)]="testProfanityLanguage"> + <mat-option value="en">English</mat-option> + <mat-option value="de">German</mat-option> + <mat-option value="fr">French</mat-option> + <mat-option value="ar">Arabic</mat-option> + <mat-option value="ru">Russian</mat-option> + <mat-option value="es">Spanish</mat-option> + <mat-option value="it">Italian</mat-option> + <mat-option value="nl">Netherlandish</mat-option> + <mat-option value="tr">Turkish</mat-option> + <mat-option value="pt">Portuguese</mat-option> + </mat-select> + </mat-form-field> + </p> + <p> + <mat-form-field> + <mat-label>{{'topic-cloud-dialog.word' | translate}}</mat-label> + <input id="test-profanity-input" matInput [(ngModel)]="testProfanityWord"> + </mat-form-field> + </p> + <p>{{'topic-cloud-dialog.preview' | translate}}:</p> + <p>{{getFilteredProfanity()}}</p> + + + <mat-accordion> + <mat-expansion-panel class="color-surface"> + <mat-expansion-panel-header class="color-surface"> + <mat-panel-title> + {{"topic-cloud-dialog.words-in-profanity" | translate}} + </mat-panel-title> + </mat-expansion-panel-header> + <a href="https://raw.githubusercontent.com/LDNOOBW/List-of-Dirty-Naughty-Obscene-and-Otherwise-Bad-Words/master/en" + target="_blank">English</a> + <br> + <a href="https://raw.githubusercontent.com/LDNOOBW/List-of-Dirty-Naughty-Obscene-and-Otherwise-Bad-Words/master/de" + target="_blank">German</a> + <br> + <a href="https://raw.githubusercontent.com/LDNOOBW/List-of-Dirty-Naughty-Obscene-and-Otherwise-Bad-Words/master/fr" + target="_blank">French</a> + <br> + <a href="https://raw.githubusercontent.com/LDNOOBW/List-of-Dirty-Naughty-Obscene-and-Otherwise-Bad-Words/master/ar" + target="_blank">Arabic</a> + <br> + <a href="https://raw.githubusercontent.com/LDNOOBW/List-of-Dirty-Naughty-Obscene-and-Otherwise-Bad-Words/master/ru" + target="_blank">Russian</a> + <br> + <a href="https://raw.githubusercontent.com/LDNOOBW/List-of-Dirty-Naughty-Obscene-and-Otherwise-Bad-Words/master/es" + target="_blank">Spanish</a> + <br> + <a href="https://raw.githubusercontent.com/LDNOOBW/List-of-Dirty-Naughty-Obscene-and-Otherwise-Bad-Words/master/it" + target="_blank">Italian</a> + <br> + <a href="https://raw.githubusercontent.com/LDNOOBW/List-of-Dirty-Naughty-Obscene-and-Otherwise-Bad-Words/master/nl" + target="_blank">Netherlandish</a> + <br> + <a href="https://raw.githubusercontent.com/LDNOOBW/List-of-Dirty-Naughty-Obscene-and-Otherwise-Bad-Words/master/tr" + target="_blank">Turkish</a> + <br> + <a href="https://raw.githubusercontent.com/LDNOOBW/List-of-Dirty-Naughty-Obscene-and-Otherwise-Bad-Words/master/pt" + target="_blank">Portuguese</a> + </mat-expansion-panel> + </mat-accordion> + + </mat-expansion-panel> + </mat-accordion> + + </mat-card> + <mat-card style="background: none;"> + <mat-slide-toggle [(ngModel)]="blacklistIsActive"> + {{'topic-cloud-dialog.hide-blacklist-words' | translate}} + </mat-slide-toggle> + </mat-card> </div> <mat-accordion class="new-profanity-word" multi> <div *ngIf="isCreatorOrMod"> - <mat-expansion-panel class="color-background" (opened)="enterProfanityWord=true; focusInput('bad-word-input')" - (closed)="enterProfanityWord = false"> + <mat-expansion-panel class="color-background" + (opened)="enterProfanityWord=true; focusInput('bad-word-input')" (closed)="enterProfanityWord = false"> <mat-expansion-panel-header class="color-background"> <mat-panel-title> {{'topic-cloud-dialog.edit-profanity-list' | translate}} @@ -127,7 +221,8 @@ <mat-icon>playlist_add_check</mat-icon> {{'topic-cloud-dialog.select-all' | translate}} </mat-label> - <mat-checkbox style="vertical-align: middle;float: right;" [checked]="this.wantedLabels.de.length===this.spacyLabels.de.length"></mat-checkbox> + <mat-checkbox style="vertical-align: middle;float: right;" + [checked]="this.wantedLabels.de.length===this.spacyLabels.de.length"></mat-checkbox> </mat-option> <mat-list-option [value]="label.tag" class="color-on-surface" *ngFor="let label of spacyLabels.de"> @@ -143,7 +238,8 @@ <mat-icon>playlist_add_check</mat-icon> {{'topic-cloud-dialog.select-all' | translate}} </mat-label> - <mat-checkbox style="vertical-align: middle;float: right;" [checked]="this.wantedLabels.en.length===this.spacyLabels.en.length"></mat-checkbox> + <mat-checkbox style="vertical-align: middle;float: right;" + [checked]="this.wantedLabels.en.length===this.spacyLabels.en.length"></mat-checkbox> </mat-option> <mat-list-option [value]="label.tag" class="color-on-surface" *ngFor="let label of spacyLabels.en"> @@ -153,6 +249,9 @@ </mat-tab> </mat-tab-group> </mat-expansion-panel> + </mat-accordion> + + <mat-accordion> <mat-expansion-panel class="color-background"> <mat-expansion-panel-header class="color-background"> <mat-panel-title> @@ -196,6 +295,7 @@ </table> </mat-expansion-panel> </mat-accordion> + </mat-expansion-panel> </mat-accordion> @@ -205,8 +305,8 @@ </mat-label> <div style="margin-left: 10px; margin-top: 6px;"> <mat-form-field [ngClass]="{'search': searchMode, 'smallerInput': deviceType === 'mobile'}"> - <input #searchBox class="searchBox" (input)="searchKeyword()" [(ngModel)]="searchedKeyword" matInput type="text" - placeholder="{{'topic-cloud-dialog.keyword-search' | translate}}"> + <input #searchBox class="searchBox" (input)="searchKeyword()" [(ngModel)]="searchedKeyword" matInput + type="text" placeholder="{{'topic-cloud-dialog.keyword-search' | translate}}"> <button *ngIf="searchedKeyword" (click)="searchedKeyword = ''; searchMode = false;" mat-button matSuffix mat-icon-button aria-label="topic-cloud-dialog.keyword-search"> <mat-icon>close</mat-icon> @@ -217,7 +317,8 @@ <div fxLayoutAlign="center center" style="margin-left: auto; font-weight: bold;"> <mat-icon [ngClass]="{'animation-blink': searchMode}" svgIcon="hashtag" class="oldtypo-h2 comment_tag-icon" matTooltip="{{'topic-cloud-dialog.keyword-counter' | translate}}"></mat-icon> - <p [ngClass]="{'animation-blink': searchMode}" matTooltip="{{'topic-cloud-dialog.keyword-counter' | translate}}"> + <p [ngClass]="{'animation-blink': searchMode}" + matTooltip="{{'topic-cloud-dialog.keyword-counter' | translate}}"> {{searchMode ? filteredKeywords.length : keywords.length}}</p> </div> @@ -239,20 +340,23 @@ <mat-icon>swap_vert</mat-icon> {{'topic-cloud-dialog.sort-count' | translate}} </button> - <button [ngClass]="{'animation-blink': sortMode==='voteCount'}" mat-menu-item (click)="sortQuestions('voteCount')"> + <button [ngClass]="{'animation-blink': sortMode==='voteCount'}" mat-menu-item + (click)="sortQuestions('voteCount')"> <mat-icon>swap_vert</mat-icon> {{'topic-cloud-dialog.sort-vote' | translate}} </button> </mat-menu> - <mat-card class="color-surface" *ngIf="(keywords.length === 0 || (searchMode && filteredKeywords.length === 0))&&!isLoading"> + <mat-card class="color-surface" + *ngIf="(keywords.length === 0 || (searchMode && filteredKeywords.length === 0))&&!isLoading"> <p class="color-on-surface" fxLayoutAlign="center"> {{'topic-cloud-dialog.no-keywords-note' | translate}} </p> </mat-card> <mat-accordion> - <mat-expansion-panel class="color-surface" (opened)="panelOpenState = true" (closed)="panelOpenState = edit = false" + <mat-expansion-panel class="color-surface" (opened)="panelOpenState = true" + (closed)="panelOpenState = edit = false" *ngFor="let keyword of (searchMode ? filteredKeywords : keywords); let i = index" [attr.data-index]="i" matTooltip="{{'topic-cloud-dialog.'+(keyword.keywordType === 0?'keyword-from-spacy':'keyword-from-questioner') | translate}}"> <mat-expansion-panel-header class="color-surface"> @@ -267,7 +371,8 @@ <div *ngFor="let question of keyword.comments"> <mat-divider></mat-divider> <app-topic-dialog-comment [question]="question.body" [keyword]="keyword.keyword" [maxShowedCharachters]="140" - [profanityFilter]="profanityFilter"></app-topic-dialog-comment> + [profanityFilter]="profanityFilter" [languageSpecific]="censorLanguageSpecificCheck" + [partialWords]="censorPartialWordsCheck" [language]="question.language"></app-topic-dialog-comment> </div> <div *ngIf="isCreatorOrMod"> diff --git a/src/app/components/shared/_dialogs/topic-cloud-administration/topic-cloud-administration.component.scss b/src/app/components/shared/_dialogs/topic-cloud-administration/topic-cloud-administration.component.scss index 6dfbfb7662dd6427deebf5144d2d1b40e72e59a1..f672fae74a1e3ad92cd954a468e61a3edd90a22f 100644 --- a/src/app/components/shared/_dialogs/topic-cloud-administration/topic-cloud-administration.component.scss +++ b/src/app/components/shared/_dialogs/topic-cloud-administration/topic-cloud-administration.component.scss @@ -29,6 +29,10 @@ color: var(--red); } +.green { + color: var(--green); +} + .primaryBackground { background-color: var(--primary); } @@ -154,3 +158,15 @@ mat-dialog-content { .comment_tag-icon { height: 18px !important; } + +.testProfanity { + margin-top: 0px; +} + +::ng-deep .ng-animating div mat-accordion mat-expansion-panel mat-expansion-panel-header { + height: 48px; +} +::ng-deep .ng-animating div mat-accordion mat-expansion-panel div.mat-expansion-panel-content { + height: 0px; + visibility: hidden; +} \ No newline at end of file diff --git a/src/app/components/shared/_dialogs/topic-cloud-administration/topic-cloud-administration.component.ts b/src/app/components/shared/_dialogs/topic-cloud-administration/topic-cloud-administration.component.ts index 5416cc4c7aa5ab1b8323ebef5778640fef4c1063..9d1bed55d7c8b9f4e0c0dd033baddfdf73d4db60 100644 --- a/src/app/components/shared/_dialogs/topic-cloud-administration/topic-cloud-administration.component.ts +++ b/src/app/components/shared/_dialogs/topic-cloud-administration/topic-cloud-administration.component.ts @@ -6,13 +6,14 @@ import { UserRole } from '../../../../models/user-roles.enum'; import { TranslateService } from '@ngx-translate/core'; import { LanguageService } from '../../../../services/util/language.service'; import { TopicCloudAdminService } from '../../../../services/util/topic-cloud-admin.service'; +import { ProfanityFilterService } from '../../../../services/util/profanity-filter.service'; import { TopicCloudAdminData, Labels, spacyLabels, KeywordOrFulltext } from './TopicCloudAdminData'; import { User } from '../../../../models/user'; import { Comment } from '../../../../models/comment'; import { CommentService } from '../../../../services/http/comment.service'; import { TSMap } from 'typescript-map'; import { RoomDataService } from '../../../../services/util/room-data.service'; - +import { ProfanityFilter } from '../../../../models/room'; @Component({ selector: 'app-topic-cloud-administration', @@ -22,7 +23,6 @@ import { RoomDataService } from '../../../../services/util/room-data.service'; export class TopicCloudAdministrationComponent implements OnInit, OnDestroy { public panelOpenState = false; public considerVotes: boolean; - public profanityFilter: boolean; public blacklistIsActive: boolean; blacklist: string[] = []; profanitywordlist: string[] = []; @@ -62,6 +62,11 @@ export class TopicCloudAdministrationComponent implements OnInit, OnDestroy { keywords: Keyword[] = []; private topicCloudAdminData: TopicCloudAdminData; + private profanityFilter: boolean; + private censorPartialWordsCheck: boolean; + private censorLanguageSpecificCheck: boolean; + private testProfanityWord: string = undefined; + private testProfanityLanguage = 'de'; constructor( @Inject(MAT_DIALOG_DATA) public data: Data, @@ -72,18 +77,18 @@ export class TopicCloudAdministrationComponent implements OnInit, OnDestroy { private langService: LanguageService, private topicCloudAdminService: TopicCloudAdminService, private commentService: CommentService, - private roomDataService: RoomDataService) { + private roomDataService: RoomDataService, + private profanityFilterService: ProfanityFilterService) { this.langService.langEmitter.subscribe(lang => { this.translateService.use(lang); }); } ngOnInit(): void { - this.isLoading = true; this.deviceType = localStorage.getItem('deviceType'); this.blacklistSubscription = this.topicCloudAdminService.getBlacklist().subscribe(list => this.blacklist = list); - this.profanitywordlist = this.topicCloudAdminService.getProfanityListFromStorage(); - this.profanitylistSubscription = this.topicCloudAdminService.getCustomProfanityList().subscribe(list => { + this.profanitywordlist = this.profanityFilterService.getProfanityListFromStorage(); + this.profanitylistSubscription = this.profanityFilterService.getCustomProfanityList().subscribe(list => { this.profanitywordlist = list; this.refreshKeywords(); }); @@ -142,7 +147,7 @@ export class TopicCloudAdministrationComponent implements OnInit, OnDestroy { const keyword: Keyword = { keyword: _keyword, keywordType: _keywordType, - keywordWithoutProfanity: this.getKeywordWithoutProfanity(_keyword), + keywordWithoutProfanity: this.getKeywordWithoutProfanity(_keyword, comment.language), comments: [comment], vote: comment.score }; @@ -172,17 +177,17 @@ export class TopicCloudAdministrationComponent implements OnInit, OnDestroy { this.isLoading = false; }); this.commentServiceSubscription = this.roomDataService.receiveUpdates([ - {type: 'CommentCreated', finished: true}, - {type: 'CommentDeleted'}, - {type: 'CommentPatched', finished: true, updates: ['score']}, - {type: 'CommentPatched', finished: true, updates: ['upvotes']}, - {type: 'CommentPatched', finished: true, updates: ['downvotes']}, - {type: 'CommentPatched', finished: true, updates: ['keywordsFromSpacy']}, - {type: 'CommentPatched', finished: true, updates: ['keywordsFromQuestioner']}, - {type: 'CommentPatched', finished: true, updates: ['ack']}, - {type: 'CommentPatched', finished: true, updates: ['tag']}, - {type: 'CommentPatched', subtype: 'ack'}, - {finished: true} + { type: 'CommentCreated', finished: true }, + { type: 'CommentDeleted' }, + { type: 'CommentPatched', finished: true, updates: ['score'] }, + { type: 'CommentPatched', finished: true, updates: ['upvotes'] }, + { type: 'CommentPatched', finished: true, updates: ['downvotes'] }, + { type: 'CommentPatched', finished: true, updates: ['keywordsFromSpacy'] }, + { type: 'CommentPatched', finished: true, updates: ['keywordsFromQuestioner'] }, + { type: 'CommentPatched', finished: true, updates: ['ack'] }, + { type: 'CommentPatched', finished: true, updates: ['tag'] }, + { type: 'CommentPatched', subtype: 'ack' }, + { finished: true } ]).subscribe(update => { if (update.type === 'CommentCreated') { this.pushInKeywords(update.comment); @@ -206,42 +211,57 @@ export class TopicCloudAdministrationComponent implements OnInit, OnDestroy { } setAdminData() { - let minQuestionersVerified = +this.minQuestioners; - if (Number.isNaN(minQuestionersVerified) || minQuestionersVerified < 1) { - minQuestionersVerified = 1; - } - let minQuestionsVerified = +this.minQuestions; - if (Number.isNaN(minQuestionsVerified) || minQuestionsVerified < 1) { - minQuestionsVerified = 1; - } - let minUpvotesVerified = +this.minUpvotes; - if (Number.isNaN(minUpvotesVerified) || minUpvotesVerified < 0) { - minUpvotesVerified = 0; + let profFilter = this.profanityFilter ? ProfanityFilter.none : ProfanityFilter.deactivated; + if (this.profanityFilter) { + if (this.censorLanguageSpecificCheck && this.censorPartialWordsCheck) { + profFilter = ProfanityFilter.all; + } else { + profFilter = this.censorLanguageSpecificCheck ? ProfanityFilter.languageSpecific : ProfanityFilter.none; + profFilter = this.censorPartialWordsCheck ? ProfanityFilter.partialWords : profFilter; + } + let minQuestionersVerified = +this.minQuestioners; + if (Number.isNaN(minQuestionersVerified) || minQuestionersVerified < 1) { + minQuestionersVerified = 1; + } + let minQuestionsVerified = +this.minQuestions; + if (Number.isNaN(minQuestionsVerified) || minQuestionsVerified < 1) { + minQuestionsVerified = 1; + } + let minUpvotesVerified = +this.minUpvotes; + if (Number.isNaN(minUpvotesVerified) || minUpvotesVerified < 0) { + minUpvotesVerified = 0; + } + this.topicCloudAdminData = { + blacklist: [], + wantedLabels: { + de: this.wantedLabels.de, + en: this.wantedLabels.en + }, + considerVotes: this.considerVotes, + profanityFilter: profFilter, + blacklistIsActive: this.blacklistIsActive, + keywordORfulltext: KeywordOrFulltext[this.keywordORfulltext], + minQuestioners: minQuestionersVerified, + minQuestions: minQuestionsVerified, + minUpvotes: minUpvotesVerified, + startDate: this.startDate.length ? this.startDate : null, + endDate: this.endDate.length ? this.endDate : null + }; + this.topicCloudAdminService.setAdminData(this.topicCloudAdminData); } - this.topicCloudAdminData = { - blacklist: [], - wantedLabels: { - de: this.wantedLabels.de, - en: this.wantedLabels.en - }, - considerVotes: this.considerVotes, - profanityFilter: this.profanityFilter, - blacklistIsActive: this.blacklistIsActive, - keywordORfulltext: KeywordOrFulltext[this.keywordORfulltext], - minQuestioners: minQuestionersVerified, - minQuestions: minQuestionsVerified, - minUpvotes: minUpvotesVerified, - startDate: this.startDate.length ? this.startDate : null, - endDate: this.endDate.length ? this.endDate : null - }; - this.topicCloudAdminService.setAdminData(this.topicCloudAdminData); } setDefaultAdminData() { this.topicCloudAdminData = TopicCloudAdminService.getDefaultAdminData; if (this.topicCloudAdminData) { this.considerVotes = this.topicCloudAdminData.considerVotes; - this.profanityFilter = this.topicCloudAdminData.profanityFilter; + this.profanityFilter = this.topicCloudAdminData.profanityFilter !== ProfanityFilter.deactivated; + if (this.topicCloudAdminData.profanityFilter === ProfanityFilter.all) { + this.censorLanguageSpecificCheck = this.censorPartialWordsCheck = true; + } else if (this.profanityFilter) { + this.censorLanguageSpecificCheck = this.topicCloudAdminData.profanityFilter === ProfanityFilter.languageSpecific; + this.censorPartialWordsCheck = this.topicCloudAdminData.profanityFilter === ProfanityFilter.partialWords; + } this.blacklistIsActive = this.topicCloudAdminData.blacklistIsActive; this.keywordORfulltext = KeywordOrFulltext[this.topicCloudAdminData.keywordORfulltext]; this.wantedLabels = { @@ -256,8 +276,8 @@ export class TopicCloudAdministrationComponent implements OnInit, OnDestroy { } } - getKeywordWithoutProfanity(keyword: string): string { - return this.topicCloudAdminService.filterProfanityWords(keyword, true, false); + getKeywordWithoutProfanity(keyword: string, lang: string): string { + return this.profanityFilterService.filterProfanityWords(keyword, this.censorPartialWordsCheck, this.censorLanguageSpecificCheck, lang); } sortQuestions(sortMode?: string) { @@ -314,12 +334,12 @@ export class TopicCloudAdministrationComponent implements OnInit, OnDestroy { updateComment(updatedComment: Comment, changes: TSMap<string, any>, messageTranslate?: string) { this.commentService.patchComment(updatedComment, changes).subscribe(_ => { - if (messageTranslate) { - this.translateService.get('topic-cloud-dialog.' + messageTranslate).subscribe(msg => { - this.notificationService.show(msg); - }); - } - }, + if (messageTranslate) { + this.translateService.get('topic-cloud-dialog.' + messageTranslate).subscribe(msg => { + this.notificationService.show(msg); + }); + } + }, error => { this.translateService.get('topic-cloud-dialog.changes-gone-wrong').subscribe(msg => { this.notificationService.show(msg); @@ -368,7 +388,7 @@ export class TopicCloudAdministrationComponent implements OnInit, OnDestroy { openConfirmDialog(msg: string, _confirmLabel: string, keyword: Keyword, mergeTarget?: Keyword) { const translationPart = 'topic-cloud-confirm-dialog.' + msg; const confirmDialogRef = this.confirmDialog.open(TopicCloudConfirmDialogComponent, { - data: {topic: keyword.keyword, message: translationPart, confirmLabel: _confirmLabel} + data: { topic: keyword.keyword, message: translationPart, confirmLabel: _confirmLabel } }); confirmDialogRef.afterClosed().subscribe(result => { @@ -425,7 +445,7 @@ export class TopicCloudAdministrationComponent implements OnInit, OnDestroy { } addProfanityWord() { - this.topicCloudAdminService.addToProfanityList(this.newProfanityWord); + this.profanityFilterService.addToProfanityList(this.newProfanityWord); this.newProfanityWord = undefined; } @@ -435,22 +455,23 @@ export class TopicCloudAdministrationComponent implements OnInit, OnDestroy { } removeWordFromProfanityList(word: string) { - this.topicCloudAdminService.removeFromProfanityList(word); + this.profanityFilterService.removeFromProfanityList(word); } removeWordFromBlacklist(word: string) { this.topicCloudAdminService.removeWordFromBlacklist(word); } - changeProfanityFilter() { - if (this.profanityFilter) { - this.translateService.get('topic-cloud-dialog.words-will-be-overwritten').subscribe(msg => { + showMessage(label: string, event: boolean) { + if (event) { + this.translateService.get('topic-cloud-dialog.' + label).subscribe(msg => { this.notificationService.show(msg); }); if (this.searchMode) { this.searchKeyword(); } } + this.refreshKeywords(); } selectAllDE() { @@ -476,6 +497,14 @@ export class TopicCloudAdministrationComponent implements OnInit, OnDestroy { this.wantedLabels.en = []; } } + + getFilteredProfanity(): string { + if (this.testProfanityWord) { + return this.profanityFilterService.filterProfanityWords(this.testProfanityWord, this.censorPartialWordsCheck, this.censorLanguageSpecificCheck, this.testProfanityLanguage); + } else { + return ''; + } + } } interface Keyword { diff --git a/src/app/components/shared/dialog/topic-dialog-comment/topic-dialog-comment.component.ts b/src/app/components/shared/dialog/topic-dialog-comment/topic-dialog-comment.component.ts index 2c58cb257810e1642c127b8af2f712a3d166b7fe..77b32abda5c7f29d5fa3b4d559e558e36d3ad61a 100644 --- a/src/app/components/shared/dialog/topic-dialog-comment/topic-dialog-comment.component.ts +++ b/src/app/components/shared/dialog/topic-dialog-comment/topic-dialog-comment.component.ts @@ -1,5 +1,6 @@ -import { Component, Input, OnChanges, OnInit, SimpleChange, SimpleChanges } from '@angular/core'; -import { TopicCloudAdminService } from '../../../../services/util/topic-cloud-admin.service'; +import { Component, Input, OnInit } from '@angular/core'; +import { Language } from '../../../../models/comment'; +import { ProfanityFilterService } from '../../../../services/util/profanity-filter.service'; @Component({ selector: 'app-topic-dialog-comment', @@ -9,9 +10,13 @@ import { TopicCloudAdminService } from '../../../../services/util/topic-cloud-ad export class TopicDialogCommentComponent implements OnInit { @Input() question: string; + @Input() language: Language; @Input() keyword: string ; @Input() maxShowedCharachters: number; - @Input() profanityFilter = true; + @Input() profanityFilter: boolean; + @Input() languageSpecific; + @Input() partialWords; + isCollapsed = false; public badWords = []; @@ -22,7 +27,7 @@ export class TopicDialogCommentComponent implements OnInit { public partsShort: string[]; public partsWithoutProfanityShort: string[]; - constructor(private topicCloudAdminService: TopicCloudAdminService) {} + constructor(private profanityFilterService: ProfanityFilterService) {} get partsOfQuestion() { return this.profanityFilter ? this.partsWithoutProfanity : this.parts; @@ -33,17 +38,19 @@ export class TopicDialogCommentComponent implements OnInit { } splitShortQuestion(question: string){ - const cleanedKeyword = this.keyword.replace(/([^a-z0-9]+)/gi, ''); - return question.slice(0, this.maxShowedCharachters).split(new RegExp(cleanedKeyword, 'i')); + return question.slice(0, this.maxShowedCharachters).split(this.keyword); } splitQuestion(question: string){ - const cleanedKeyword = this.keyword.replace(/([^a-z0-9]+)/gi, ''); - return question.split(new RegExp(cleanedKeyword,'i')); + return question.split(this.keyword); } ngOnInit(): void { - this.questionWithoutProfanity = this.topicCloudAdminService.filterProfanityWords(this.question, true, false); + if (!this.language) { + return; + } + this.questionWithoutProfanity = this.profanityFilterService. + filterProfanityWords(this.question, this.partialWords, this.languageSpecific, this.language); this.partsWithoutProfanity = this.splitQuestion(this.questionWithoutProfanity); this.parts = this.splitQuestion(this.question); this.partsWithoutProfanityShort = this.splitShortQuestion(this.questionWithoutProfanity); diff --git a/src/app/components/shared/questionwall/QuestionWallComment.ts b/src/app/components/shared/questionwall/QuestionWallComment.ts index 2ba8c70b625a55cf5157443b6fd62beb0d77718c..b33b8b483adaae07aba4a6e39d7eb51ce04d99a7 100644 --- a/src/app/components/shared/questionwall/QuestionWallComment.ts +++ b/src/app/components/shared/questionwall/QuestionWallComment.ts @@ -1,23 +1,22 @@ import { Comment } from '../../../models/comment'; export class QuestionWallComment { - public static readonly TIME_FORMAT_DE: string[][] = - [ - ['vor % Jahr', 'vor % Jahren'], - ['vor % Monat', 'vor % Monaten'], - ['vor % Tag', 'vor % Tagen'], - ['vor % Stunde', 'vor % Stunden'], - ['vor % Minute', 'vor % Minuten'], - ['vor % Sekunde', 'vor % Sekunden'] - ]; + [ + ['vor % Jahr', 'vor % Jahren'], + ['vor % Monat', 'vor % Monaten'], + ['vor % Tag', 'vor % Tagen'], + ['vor % Stunde', 'vor % Stunden'], + ['vor % Minute', 'vor % Minuten'], + ['vor % Sekunde', 'vor % Sekunden'] + ]; public static readonly TIME_FORMAT_EN: string[][] = [ - ['% year ago', '% years ago'], - ['% month ago', '% months ago'], - ['% day ago', '% days ago'], - ['% hour ago', '% hours ago'], - ['% minute ago', '% minutes ago'], - ['% second ago', '% seconds ago'], + ['% year ago', '% years ago'], + ['% month ago', '% months ago'], + ['% day ago', '% days ago'], + ['% hour ago', '% hours ago'], + ['% minute ago', '% minutes ago'], + ['% second ago', '% seconds ago'], ]; public static currentTimeFormat: string[][] = QuestionWallComment.TIME_FORMAT_EN; @@ -25,18 +24,18 @@ export class QuestionWallComment { public date: Date; public timeAgo: string; - public static updateTimeFormat(lang: string) { - this.currentTimeFormat = this['TIME_FORMAT_' + lang.toUpperCase()]; - } - constructor( public comment: Comment, public old: boolean - ) { + ) { this.date = new Date(comment.timestamp); this.updateTimeAgo(); } + public static updateTimeFormat(lang: string) { + this.currentTimeFormat = this['TIME_FORMAT_' + lang.toUpperCase()]; + } + public update() { } diff --git a/src/app/components/shared/questionwall/question-wall/question-wall.component.ts b/src/app/components/shared/questionwall/question-wall/question-wall.component.ts index b0a18cbaa0736f922d68bb054d97b6ba2e6cf4f1..f4f925129fa452e2389c1b3213f335b250a7e1eb 100644 --- a/src/app/components/shared/questionwall/question-wall/question-wall.component.ts +++ b/src/app/components/shared/questionwall/question-wall/question-wall.component.ts @@ -14,6 +14,7 @@ import { Rescale } from '../../../../models/rescale'; import { QuestionWallKeyEventSupport } from '../QuestionWallKeyEventSupport'; import { MatSliderChange } from '@angular/material/slider'; import { Period } from '../../../../utils/filter-options'; +import { RoomDataService } from '../../../../services/util/room-data.service'; @Component({ selector: 'app-question-wall', @@ -50,18 +51,6 @@ export class QuestionWallComponent implements OnInit, AfterViewInit, OnDestroy { periodsList = Object.values(Period); period: Period = Period.all; - public wrap<E>(e: E, action: (e: E) => void) { - action(e); - } - - public notUndefined<E>(e: E, action: (e: E) => void, elsePart?: () => void) { - if (e) { - action(e); - } else if (elsePart) { - elsePart(); - } - } - constructor( private authenticationService: AuthenticationService, private router: Router, @@ -69,8 +58,9 @@ export class QuestionWallComponent implements OnInit, AfterViewInit, OnDestroy { private roomService: RoomService, private wsCommentService: WsCommentService, private langService: LanguageService, - private translateService: TranslateService - ) { + private translateService: TranslateService, + private roomDataService: RoomDataService + ) { this.keySupport = new QuestionWallKeyEventSupport(); this.roomId = localStorage.getItem('roomId'); this.timeUpdateInterval = setInterval(() => { @@ -82,12 +72,25 @@ export class QuestionWallComponent implements OnInit, AfterViewInit, OnDestroy { }); } + public wrap<E>(e: E, action: (e: E) => void) { + action(e); + } + + public notUndefined<E>(e: E, action: (e: E) => void, elsePart?: () => void) { + if (e) { + action(e); + } else if (elsePart) { + elsePart(); + } + } + ngOnInit(): void { QuestionWallComment.updateTimeFormat(localStorage.getItem('currentLang')); this.translateService.use(localStorage.getItem('currentLang')); - this.commentService.getAckComments(this.roomId).subscribe(e => { + this.roomDataService.getRoomData(this.roomId).subscribe(e => { e.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()); e.forEach(c => { + this.roomDataService.checkProfanity(c); const comment = new QuestionWallComment(c, true); this.comments.push(comment); this.setTimePeriod(this.period); @@ -98,20 +101,28 @@ export class QuestionWallComponent implements OnInit, AfterViewInit, OnDestroy { this.room = e; this.tags = e.tags; }); - this.wsCommentService.getCommentStream(this.roomId).subscribe(e => { - this.commentService.getComment(JSON.parse(e.body).payload.id).subscribe(comment => { - this.notUndefined(this.comments.find(f => f.comment.id === comment.id), qwComment => { - qwComment.comment = comment; - }, () => { - this.wrap(this.pushIncommingComment(comment), qwComment => { - if (this.focusIncommingComments) { - setTimeout(() => this.focusComment(qwComment), 5); - } - }); + this.subscribeCommentStream(); + this.initKeySupport(); + } + + subscribeCommentStream() { + this.roomDataService.receiveUpdates([ + { type: 'CommentCreated', finished: true}, + { type: 'CommentPatched', finished: true, updates: ['upvotes'] }, + { type: 'CommentPatched', finished: true, updates: ['downvotes'] }, + {finished: true} + ]).subscribe(update => { + if (update.type === 'CommentCreated') { + this.wrap(this.pushIncommingComment(update.comment), qwComment => { + if (this.focusIncommingComments) { + setTimeout(() => this.focusComment(qwComment), 5); + } }); - }); + } else if (update.type === 'CommentPatched') { + const qwComment = this.comments.find(f => f.comment.id === update.comment.id); + qwComment.comment = update.comment; + } }); - this.initKeySupport(); } updateCommentsCountOverview(): void { @@ -182,6 +193,8 @@ export class QuestionWallComponent implements OnInit, AfterViewInit, OnDestroy { } pushIncommingComment(comment: Comment): QuestionWallComment { + this.roomDataService.checkProfanity(comment); + console.log(comment); const qwComment = new QuestionWallComment(comment, false); this.comments = [qwComment, ...this.comments]; this.setTimePeriod(this.period); diff --git a/src/app/services/util/profanity-filter.service.spec.ts b/src/app/services/util/profanity-filter.service.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..eedd82f92a4d343ff20c7590b230f0389f0bde1c --- /dev/null +++ b/src/app/services/util/profanity-filter.service.spec.ts @@ -0,0 +1,17 @@ +/*import { TestBed } from '@angular/core/testing'; + +import { ProfanityFilterService } from './profanity-filter.service'; + +describe('ProfanityFilterService', () => { + let service: ProfanityFilterService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(ProfanityFilterService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); +*/ diff --git a/src/app/services/util/profanity-filter.service.ts b/src/app/services/util/profanity-filter.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..9d96d23fcc8828e52ca9371a746181603d6c63f0 --- /dev/null +++ b/src/app/services/util/profanity-filter.service.ts @@ -0,0 +1,93 @@ +import { Injectable } from '@angular/core'; +import { Observable, Subject } from 'rxjs'; +import * as BadWords from 'naughty-words'; + +@Injectable({ + providedIn: 'root' +}) +export class ProfanityFilterService { + + private customProfanityWords: Subject<string[]>; + private readonly profanityKey = 'custom-Profanity-List'; + private profanityWords = []; + + constructor() { + this.customProfanityWords = new Subject<string[]>(); + this.profanityWords = BadWords['en'] + .concat(BadWords['de']) + .concat(BadWords['fr']) + .concat(BadWords['ar']) + .concat(BadWords['ru']) + .concat(BadWords['es']) + .concat(BadWords['it']) + .concat(BadWords['nl']) + .concat(BadWords['pt']) + .concat(BadWords['tr']); + } + + get getProfanityList(): string[] { + return this.getProfanityListFromStorage().concat(this.profanityWords); + } + + getProfanityListFromStorage() { + const list = localStorage.getItem(this.profanityKey); + return list ? JSON.parse(list) : []; + } + + getCustomProfanityList(): Observable<string[]> { + this.customProfanityWords.next(this.getProfanityListFromStorage()); + return this.customProfanityWords.asObservable(); + } + + addToProfanityList(word: string) { + if (word !== undefined) { + const plist = this.getProfanityListFromStorage(); + if (!plist.includes(word.toLowerCase().trim())) { + plist.push(word.toLowerCase().trim()); + localStorage.setItem(this.profanityKey, JSON.stringify(plist)); + this.customProfanityWords.next(plist); + } + } + } + + removeFromProfanityList(word: string) { + const plist = this.getProfanityListFromStorage(); + plist.splice(plist.indexOf(word, 0), 1); + localStorage.setItem(this.profanityKey, JSON.stringify(plist)); + this.customProfanityWords.next(plist); + } + + removeProfanityList() { + localStorage.removeItem(this.profanityKey); + } + + filterProfanityWords(str: string, censorPartialWordsCheck: boolean, censorLanguageSpecificCheck: boolean, lang?: string){ + let filteredString = str; + let profWords = []; + if (censorLanguageSpecificCheck) { + profWords = BadWords[(lang !== 'AUTO' ? lang.toLowerCase() : localStorage.getItem('currentLang'))]; + } else { + profWords = this.profanityWords; + } + // eslint-disable-next-line max-len + const toCensoredString = censorPartialWordsCheck ? str.toLowerCase() : str.toLowerCase().split(/[\s,.]+/); + profWords.concat(this.getProfanityListFromStorage()).forEach(word => { + if (toCensoredString.includes(word)) { + filteredString = this.replaceString(filteredString, word, this.generateCensoredWord(word.length)); + } + }); + return filteredString; + } + + private replaceString(str: string, search: string, replace: string) { + return str.replace(new RegExp(search, 'gi'), replace); + } + + private generateCensoredWord(count: number) { + let res = ''; + for (let i = 0; i < count; i++) { + res += '*'; + } + return res; + } +} diff --git a/src/app/services/util/room-data.service.ts b/src/app/services/util/room-data.service.ts index 8314763856c1437fdbd67da408c87daa27a81d99..498e8c262c4ed96183595d7107d29987cf827212 100644 --- a/src/app/services/util/room-data.service.ts +++ b/src/app/services/util/room-data.service.ts @@ -6,7 +6,7 @@ import { Comment } from '../../models/comment'; import { CommentService } from '../http/comment.service'; import { CorrectWrong } from '../../models/correct-wrong.enum'; import { RoomService } from '../http/room.service'; -import { TopicCloudAdminService } from './topic-cloud-admin.service'; +import { ProfanityFilterService } from './profanity-filter.service'; import { ProfanityFilter, Room } from '../../models/room'; import { WsRoomService } from '../websockets/ws-room.service'; @@ -99,7 +99,7 @@ export class RoomDataService { constructor(private wsCommentService: WsCommentService, private commentService: CommentService, private roomService: RoomService, - private topicCloudAdminService: TopicCloudAdminService, + private profanityFilterService: ProfanityFilterService, private wsRoomService: WsRoomService) { } @@ -173,7 +173,7 @@ export class RoomDataService { private filterCommentOfProfanity(room: Room, comment: Comment): string { const partialWords = room.profanityFilter === ProfanityFilter.all || room.profanityFilter === ProfanityFilter.partialWords; const languageSpecific = room.profanityFilter === ProfanityFilter.all || room.profanityFilter === ProfanityFilter.languageSpecific; - return this.topicCloudAdminService.filterProfanityWords(comment.body, partialWords, languageSpecific, comment.language); + return this.profanityFilterService.filterProfanityWords(comment.body, partialWords, languageSpecific, comment.language); } private removeCommentBodies(key: string) { diff --git a/src/app/services/util/topic-cloud-admin.service.ts b/src/app/services/util/topic-cloud-admin.service.ts index 5943b66952123ed86544df9f51a681023f078338..839a24b50aa5e848ae90557d6bb3455abe60c6ab 100644 --- a/src/app/services/util/topic-cloud-admin.service.ts +++ b/src/app/services/util/topic-cloud-admin.service.ts @@ -1,14 +1,15 @@ import { Injectable } from '@angular/core'; -import * as BadWords from 'naughty-words'; import { TopicCloudAdminData, KeywordOrFulltext, spacyLabels } from '../../components/shared/_dialogs/topic-cloud-administration/TopicCloudAdminData'; import { RoomService } from '../http/room.service'; -import { Room } from '../../models/room'; +import { ProfanityFilter, Room } from '../../models/room'; import { TranslateService } from '@ngx-translate/core'; import { NotificationService } from './notification.service'; +import { WsRoomService } from '..//websockets/ws-room.service'; +import { ProfanityFilterService } from './profanity-filter.service'; import { BehaviorSubject, Observable, Subject } from 'rxjs'; import { Comment } from '../../models/comment'; @@ -19,23 +20,21 @@ export class TopicCloudAdminService { private static readonly adminKey = 'Topic-Cloud-Admin-Data'; private adminData: BehaviorSubject<TopicCloudAdminData>; private blacklist: Subject<string[]>; - private profanityWords = []; - private customProfanityWords: Subject<string[]>; - private readonly profanityKey = 'custom-Profanity-List'; constructor(private roomService: RoomService, private translateService: TranslateService, + private wsRoomService: WsRoomService, + private profanityFilterService: ProfanityFilterService, private notificationService: NotificationService) { this.blacklist = new Subject<string[]>(); + this.wsRoomService.getRoomStream(localStorage.getItem('roomId')).subscribe(msg => { + const message = JSON.parse(msg.body); + const room = message.payload.changes; + if (message.type === 'RoomPatched') { + this.blacklist.next(room.blacklist ? JSON.parse(room.blacklist) : []); + } + }); this.adminData = new BehaviorSubject<TopicCloudAdminData>(TopicCloudAdminService.getDefaultAdminData); - this.customProfanityWords = new Subject<string[]>(); - /* put all arrays of languages together */ - this.profanityWords = BadWords['en'] - .concat(BadWords['de']) - .concat(BadWords['fr']) - .concat(BadWords['ar']) - .concat(BadWords['ru']) - .concat(BadWords['tr']); } static approveKeywordsOfComment(comment: Comment, config: TopicCloudAdminData, keywordFunc: (string) => void) { @@ -82,7 +81,7 @@ export class TopicCloudAdminService { en: this.getDefaultSpacyTagsEN() }, considerVotes: true, - profanityFilter: true, + profanityFilter: ProfanityFilter.none, blacklistIsActive: true, keywordORfulltext: KeywordOrFulltext.both, minQuestioners: 1, @@ -126,8 +125,8 @@ export class TopicCloudAdminService { if (_adminData.blacklistIsActive) { _adminData.blacklist = list; } - if (_adminData.profanityFilter) { - _adminData.blacklist = _adminData.blacklist.concat(this.getProfanityListFromStorage().concat(this.profanityWords)); + if (_adminData.profanityFilter !== ProfanityFilter.deactivated) { + _adminData.blacklist = _adminData.blacklist.concat(this.profanityFilterService.getProfanityList); } localStorage.setItem(TopicCloudAdminService.adminKey, JSON.stringify(_adminData)); this.adminData.next(_adminData); @@ -142,38 +141,6 @@ export class TopicCloudAdminService { return this.blacklist.asObservable(); } - getProfanityListFromStorage() { - const list = localStorage.getItem(this.profanityKey); - return list ? JSON.parse(list) : []; - } - - getCustomProfanityList(): Observable<string[]> { - this.customProfanityWords.next(this.getProfanityListFromStorage()); - return this.customProfanityWords.asObservable(); - } - - addToProfanityList(word: string) { - if (word !== undefined) { - const plist = this.getProfanityListFromStorage(); - if (!plist.includes(word.toLowerCase().trim())) { - plist.push(word.toLowerCase().trim()); - localStorage.setItem(this.profanityKey, JSON.stringify(plist)); - this.customProfanityWords.next(plist); - } - } - } - - removeFromProfanityList(word: string) { - const plist = this.getProfanityListFromStorage(); - plist.splice(plist.indexOf(word, 0), 1); - localStorage.setItem(this.profanityKey, JSON.stringify(plist)); - this.customProfanityWords.next(plist); - } - - removeProfanityList() { - localStorage.removeItem(this.profanityKey); - } - getRoom(): Observable<Room> { return this.roomService.getRoom(localStorage.getItem('roomId')); } @@ -223,33 +190,4 @@ export class TopicCloudAdminService { }); }); } - - filterProfanityWords(str: string, censorPartialWordsCheck: boolean, censorLanguageSpecificCheck: boolean, lang?: string) { - let filteredString = str; - let profWords = []; - if (censorLanguageSpecificCheck) { - profWords = BadWords[(lang !== 'AUTO' ? lang.toLowerCase() : 'de')]; - } else { - profWords = this.profanityWords; - } - const toCensoredString = censorPartialWordsCheck ? str.toLowerCase() : str.toLowerCase().split(' '); - profWords.concat(this.getProfanityListFromStorage()).forEach(word => { - if (toCensoredString.includes(word)) { - filteredString = this.replaceString(filteredString, word, this.generateCensoredWord(word.length)); - } - }); - return filteredString; - } - - private replaceString(str: string, search: string, replace: string) { - return str.replace(new RegExp(search, 'gi'), replace); - } - - private generateCensoredWord(count: number) { - let res = ''; - for (let i = 0; i < count; i++) { - res += '*'; - } - return res; - } } diff --git a/src/assets/i18n/creator/de.json b/src/assets/i18n/creator/de.json index 57c8e698e8a2ef39b83cc6bea222a5674e7ad75a..7cc73ca3ddb6b2b206f83edeed62e04202d2390f 100644 --- a/src/assets/i18n/creator/de.json +++ b/src/assets/i18n/creator/de.json @@ -337,7 +337,9 @@ "partial-words-filter": "Teilwörter filtern", "words-will-be-overwritten": "Profane Wörter werden mit '***' überschrieben", "only-specific-language-will-be-filtered": "Nur die Sprache der Frage wird gefiltert", - "partial-words-will-be-filtered": "Profane Teilwörter werden auch gefiltert" + "partial-words-will-be-filtered": "Profane Teilwörter werden auch gefiltert", + "keyword-from-spacy": "Stichwort von spaCy", + "keyword-from-questioner": "Stichwort vom Fragesteller" }, "session": { "a11y-description": "Gib eine Beschreibung für die Sitzung ein.", @@ -398,9 +400,20 @@ "select-all": "Alle auswählen", "keyword-counter": "Anzahl der Themen", "sort": "Sortieren", - "words-will-be-overwritten": "unanständige Wörter werden mit '***' überschrieben", + "language-specific-filter": "Sprachspezifisch filtern", + "partial-words-filter": "Teilwörter filtern", + "words-will-be-overwritten": "Profane Wörter werden mit '***' überschrieben", + "only-specific-language-will-be-filtered": "Nur die Sprache der Frage wird gefiltert", + "partial-words-will-be-filtered": "Profane Teilwörter werden auch gefiltert", "keyword-from-spacy": "Stichwort von spaCy", "keyword-from-questioner": "Stichwort vom Fragesteller", + "test-profanity": "Profanität testen", + "word": "Wort", + "word-is-profanity": "Wort ist profan", + "word-is-not-profanity": "Wort ist nicht profan", + "words-in-profanity": "Profane Wörter", + "language": "Sprache", + "preview": "Vorschau", "topic-requirement-title": "Themen Anforderung", "topic-requirement-questions": "Minimale Anzahl an Fragen", "topic-requirement-questioners": "Minimale Anzahl an Fragensteller*innen", @@ -408,6 +421,10 @@ "topic-requirement-begin-datetime": "Frühester Kommentar", "topic-requirement-end-datetime": "Letzter Kommentar" }, + "dialog-comment": { + "read-more": "Mehr lesen", + "read-less": "Weniger lesen" + }, "tag-cloud": { "demo-data-topic": "Thema %d", "overview-question-topic-tooltip": "Anzahl gestellter Fragen mit diesem Thema", diff --git a/src/assets/i18n/creator/en.json b/src/assets/i18n/creator/en.json index c820678d45fc7738e65883c394934b72916ff11a..a2873ae08c56f7bbc6d77c14cde457823e6b844f 100644 --- a/src/assets/i18n/creator/en.json +++ b/src/assets/i18n/creator/en.json @@ -408,9 +408,20 @@ "select-all": "Select all", "keyword-counter": "Topic count", "sort": "Sort", + "language-specific-filter": "Filter language-specific", + "partial-words-filter": "Filter partial words", "words-will-be-overwritten": "profane words will be overwritten with '***'", + "only-specific-language-will-be-filtered": "Only the language of the question will be filtered", + "partial-words-will-be-filtered": "Profane partial words will be also filtered", "keyword-from-spacy": "Keyword from spaCy", "keyword-from-questioner": "Keyword from questioner", + "test-profanity": "Test profanity", + "word": "word", + "word-is-profanity": "Word is profane", + "word-is-not-profanity": "Word is not profane", + "words-in-profanity": "Words in profanity filter", + "language": "Language", + "preview": "Preview", "topic-requirement-title": "Topic requirement", "topic-requirement-questions": "Minimum number of questions", "topic-requirement-questioners": "Minimum number of questioners", @@ -418,6 +429,10 @@ "topic-requirement-begin-datetime": "Earliest comment", "topic-requirement-end-datetime": "Last comment" }, + "dialog-comment": { + "read-more": "read more", + "read-less": "read less" + }, "tag-cloud-config":{ "general":"General", "overflow":"Overflow", diff --git a/src/assets/i18n/participant/de.json b/src/assets/i18n/participant/de.json index e6d8203fb6eb8a33cbf733204921f223646b52bb..fb3f73fcb18a89ae63fe49d4617b1b9f874456f6 100644 --- a/src/assets/i18n/participant/de.json +++ b/src/assets/i18n/participant/de.json @@ -291,9 +291,15 @@ "select-all": "Alle auswählen", "keyword-counter": "Anzahl der Themen", "sort": "Sortieren", - "words-will-be-overwritten": "unanständige Wörter werden mit '***' überschrieben", "keyword-from-spacy": "Stichwort von spaCy", "keyword-from-questioner": "Stichwort vom Fragesteller", + "test-profanity": "Profanität testen", + "word": "Wort", + "word-is-profanity": "Wort ist profan", + "word-is-not-profanity": "Wort ist nicht profan", + "words-in-profanity": "Profane Wörter", + "language": "Sprache", + "preview": "Vorschau", "topic-requirement-title": "Themen Anforderung", "topic-requirement-questions": "Minimale Anzahl an Fragen", "topic-requirement-questioners": "Minimale Anzahl an Fragensteller*innen", diff --git a/src/assets/i18n/participant/en.json b/src/assets/i18n/participant/en.json index 7fd4d0e513db60d315ad3bdeda55e81b6fcf839d..17c661d8959354ebb34c7379c67c8d62d56d6cb9 100644 --- a/src/assets/i18n/participant/en.json +++ b/src/assets/i18n/participant/en.json @@ -297,9 +297,15 @@ "select-all": "Select all", "keyword-counter": "Topic count", "sort": "Sort", - "words-will-be-overwritten": "profane words will be overwritten with '***'", "keyword-from-spacy": "Keyword from spaCy", "keyword-from-questioner": "Keyword from questioner", + "test-profanity": "Test profanity", + "word": "word", + "word-is-profanity": "Word is profane", + "word-is-not-profanity": "Word is not profane", + "words-in-profanity": "Words in profanity filter", + "language": "Language", + "preview": "Preview", "topic-requirement-title": "Topic requirement", "topic-requirement-questions": "Minimum number of questions", "topic-requirement-questioners": "Minimum number of questioners",