diff --git a/.gitignore b/.gitignore index f9418f1..c5b7758 100644 --- a/.gitignore +++ b/.gitignore @@ -40,3 +40,5 @@ Thumbs.db .nx/cache .angular + +*.nps \ No newline at end of file diff --git a/.prettierrc b/.prettierrc index b4acb25..67e7f90 100644 --- a/.prettierrc +++ b/.prettierrc @@ -5,5 +5,9 @@ "tabWidth": 2, "bracketSpacing": true, "endOfLine": "lf", - "semi": true + "semi": true, + "arrowParens": "always", + "bracketSameLine": false, + "insertPragma": false, + "useTabs": false } diff --git a/nwaifu-ui/src/lib/components/button/button.component.scss b/nwaifu-ui/src/lib/components/button/button.component.scss index 30dcfc8..76dc2f7 100644 --- a/nwaifu-ui/src/lib/components/button/button.component.scss +++ b/nwaifu-ui/src/lib/components/button/button.component.scss @@ -7,7 +7,7 @@ button { color: #f5f6fa; border-radius: 15px; transition: ease-in-out 0.2s; - margin: 2rem; + width: 100%; &:hover, &:active { transform: scale(1.2); diff --git a/nwaifu-ui/src/lib/components/button/button.component.ts b/nwaifu-ui/src/lib/components/button/button.component.ts index fc06d8d..1ecf567 100644 --- a/nwaifu-ui/src/lib/components/button/button.component.ts +++ b/nwaifu-ui/src/lib/components/button/button.component.ts @@ -9,7 +9,7 @@ import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; imports: [CommonModule], changeDetection: ChangeDetectionStrategy.OnPush, }) -export class ButtonComponent { +export class NWUIButtonComponent { @Input() disabled = false; - @Input() type: string | undefined; + @Input() type = 'button'; } diff --git a/nwaifu-ui/src/lib/components/index.ts b/nwaifu-ui/src/lib/components/index.ts index eaf5eea..770f736 100644 --- a/nwaifu-ui/src/lib/components/index.ts +++ b/nwaifu-ui/src/lib/components/index.ts @@ -1 +1,2 @@ export * from './button'; +export * from './textarea'; diff --git a/nwaifu-ui/src/lib/components/textarea/index.ts b/nwaifu-ui/src/lib/components/textarea/index.ts new file mode 100644 index 0000000..8457e35 --- /dev/null +++ b/nwaifu-ui/src/lib/components/textarea/index.ts @@ -0,0 +1 @@ +export * from './textarea.component'; diff --git a/nwaifu-ui/src/lib/components/textarea/textarea.component.html b/nwaifu-ui/src/lib/components/textarea/textarea.component.html new file mode 100644 index 0000000..611e770 --- /dev/null +++ b/nwaifu-ui/src/lib/components/textarea/textarea.component.html @@ -0,0 +1,5 @@ +
+ @if(!value) { + + } +
diff --git a/nwaifu-ui/src/lib/components/textarea/textarea.component.scss b/nwaifu-ui/src/lib/components/textarea/textarea.component.scss new file mode 100644 index 0000000..3eeba0f --- /dev/null +++ b/nwaifu-ui/src/lib/components/textarea/textarea.component.scss @@ -0,0 +1,20 @@ +.nwui-textarea { + border-radius: 8px; + height: auto; + color: #efdee0; + outline: none; + padding-inline: 2rem 1rem; + background-color: transparent; + border: 2px solid #e4bdc3; + font-size: 1rem; + font-weight: inherit; + min-height: 3rem; + line-height: 3rem; + &:focus { + border: 2px solid #efdee0; + } + &.disabled { + opacity: 0.5; + } + word-wrap: break-word; +} diff --git a/nwaifu-ui/src/lib/components/textarea/textarea.component.ts b/nwaifu-ui/src/lib/components/textarea/textarea.component.ts new file mode 100644 index 0000000..34d3c12 --- /dev/null +++ b/nwaifu-ui/src/lib/components/textarea/textarea.component.ts @@ -0,0 +1,36 @@ +import { CommonModule } from '@angular/common'; +import { AfterViewInit, Component, ElementRef, EventEmitter, Input, Output, ViewChild } from '@angular/core'; + +@Component({ + selector: 'nwui-textarea', + imports: [CommonModule], + standalone: true, + styleUrls: ['./textarea.component.scss'], + templateUrl: './textarea.component.html', + // changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class NWUITextAreaComponent implements AfterViewInit { + @Input() disabled = false; + @Input() value = ''; + @Input() contenteditable = true; + // eslint-disable-next-line @typescript-eslint/no-empty-function + @Output() leave = new EventEmitter(); + @ViewChild('ref') ref: ElementRef | null = null; + + get className(): string { + return `nwui-textarea ${this.disabled && !this.contenteditable ? 'disabled' : ''}`; + } + + leaveFn() { + if (this.ref) { + const target = this.ref.nativeElement; + const text = target.textContent || ''; + if (this.leave) this.leave.emit(text); + target.textContent = text; + } + } + + ngAfterViewInit(): void { + if (this.ref && this.value) this.ref.nativeElement.textContent = this.value; + } +} diff --git a/src/app/app.component.html b/src/app/app.component.html index 46eaa7b..1b012e9 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -8,4 +8,3 @@ - diff --git a/src/app/app.component.scss b/src/app/app.component.scss index e69de29..4971974 100644 --- a/src/app/app.component.scss +++ b/src/app/app.component.scss @@ -0,0 +1,9 @@ +.btns { + display: flex; + flex-direction: row; + justify-content: flex-start; + align-items: center; + nwui-button { + margin: 2rem; + } +} diff --git a/src/app/app.component.ts b/src/app/app.component.ts index bec11d2..2476aa3 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,12 +1,13 @@ import { Component, OnInit, ViewChild } from '@angular/core'; -import { RouterModule } from '@angular/router'; -import { ButtonComponent } from '@nwaifu-ui'; +import { NWUIButtonComponent } from '@nwaifu-ui'; import { TextListComponent } from './components/text_list/text_list.component'; -import { TranslateData } from './dto/translate_data.dto'; +import { LocalStorageKeys } from './consts'; +import { NpsFile, TranslateData } from './dto/translate_data.dto'; +import { saveOriginalFile } from './lib/file_tools'; import { parse } from './lib/parser'; @Component({ standalone: true, - imports: [RouterModule, TextListComponent, ButtonComponent], + imports: [TextListComponent, NWUIButtonComponent], selector: 'app-root', templateUrl: './app.component.html', styleUrl: './app.component.scss', @@ -17,14 +18,14 @@ export class AppComponent implements OnInit { @ViewChild('fileInput') fileInput: HTMLInputElement | null = null; ngOnInit(): void { - const data = localStorage.getItem('translations'); + const data = localStorage.getItem(LocalStorageKeys.TRANSLATIONS); if (data) { try { this.elements = JSON.parse(data); } catch (e) { console.error(e); alert('Error while loading'); - localStorage.removeItem('translations'); + localStorage.removeItem(LocalStorageKeys.TRANSLATIONS); this.elements = []; } } @@ -33,12 +34,17 @@ export class AppComponent implements OnInit { submitFile($event: Event) { const target = $event.target as HTMLInputElement; if (target.files) { - const file = target.files?.[0]; + const file = target.files[0]; if (file) { const reader = new FileReader(); reader.onload = () => { if (reader.result) { this.onFileLoaded(reader.result.toString()); + const original_file: NpsFile = { + file_name: file.name, + original_text: reader.result.toString(), + }; + saveOriginalFile(original_file); target.value = ''; } }; @@ -54,6 +60,6 @@ export class AppComponent implements OnInit { onClearClicked() { this.elements = []; - localStorage.removeItem('translations'); + localStorage.removeItem(LocalStorageKeys.TRANSLATIONS); } } diff --git a/src/app/app.config.ts b/src/app/app.config.ts index 98b4000..1c0c942 100644 --- a/src/app/app.config.ts +++ b/src/app/app.config.ts @@ -1,9 +1,6 @@ import { provideHttpClient } from '@angular/common/http'; import { ApplicationConfig } from '@angular/core'; -import { provideRouter } from '@angular/router'; -import { appRoutes } from './app.routes'; -import { TranslateService } from './services/translate.service'; export const appConfig: ApplicationConfig = { - providers: [provideHttpClient(), provideRouter(appRoutes), TranslateService], + providers: [provideHttpClient()], }; diff --git a/src/app/components/translate_block/translate_block.component.html b/src/app/components/translate_block/translate_block.component.html index 3ab8e45..7c60d57 100644 --- a/src/app/components/translate_block/translate_block.component.html +++ b/src/app/components/translate_block/translate_block.component.html @@ -1,12 +1,18 @@

{{ index + 1 }}

-
{{ item.english_text }}
-
+ {{ item.english_text }} + {{ item.translated_text }}
- Translate - - + Translate + Edit + Clear
diff --git a/src/app/components/translate_block/translate_block.component.scss b/src/app/components/translate_block/translate_block.component.scss index 4203fee..6c2a4ac 100644 --- a/src/app/components/translate_block/translate_block.component.scss +++ b/src/app/components/translate_block/translate_block.component.scss @@ -1,34 +1,3 @@ -.translated-text { - border-radius: 8px; - height: auto; - color: #efdee0; - outline: none; - padding-inline: 2rem 1rem; - background-color: transparent; - border: 2px solid #e4bdc3; - font-size: 1rem; - font-weight: inherit; - min-height: 3rem; - line-height: 3rem; - &:focus { - border: 2px solid #efdee0; - } - &:read-only { - opacity: 0.5; - } - word-wrap: break-word; -} - -.english-text { - color: #efdee0; - border: 2px solid #e4bdc3; - word-wrap: break-word; - border-radius: 8px; - padding-inline: 2rem 1rem; - line-height: 3rem; - background-color: transparent; -} - .element { display: flex; background-color: #413738; @@ -62,24 +31,9 @@ display: flex; flex-direction: column; gap: 1rem; - .btn { - outline: none; - border: none; - cursor: pointer; - background-color: #5b3f45; - padding: 1em 1.5em; - color: #f5f6fa; - border-radius: 15px; - transition: ease-in-out 0.2s; - &:hover { - transform: scale(1.2); - } - &:disabled { - opacity: 0.5; - &:hover { - cursor: not-allowed; - transform: scale(1); - } - } + align-items: center; + padding-inline: 1rem; + nwui-button { + width: 100%; } } diff --git a/src/app/components/translate_block/translate_block.component.ts b/src/app/components/translate_block/translate_block.component.ts index 9bd7d8d..002449a 100644 --- a/src/app/components/translate_block/translate_block.component.ts +++ b/src/app/components/translate_block/translate_block.component.ts @@ -1,25 +1,28 @@ import { CommonModule } from '@angular/common'; -import { AfterViewInit, ChangeDetectorRef, Component, ElementRef, Input, OnInit, ViewChild } from '@angular/core'; -import { ButtonComponent } from '@nwaifu-ui'; +import { Component, ElementRef, Input, OnInit, ViewChild } from '@angular/core'; +import { NWUIButtonComponent, NWUITextAreaComponent } from '@nwaifu-ui'; +import { LocalStorageKeys } from 'src/app/consts'; import { TranslateData } from '../../dto/translate_data.dto'; -import { TranslatePipe } from '../../pipes/translate.pipe'; +import { ETranslateService } from '../../services/translate.enums'; +import { TranslateService } from '../../services/translate.service'; @Component({ selector: 'app-translate-block', templateUrl: './translate_block.component.html', styleUrls: ['./translate_block.component.scss'], standalone: true, - imports: [CommonModule, ButtonComponent], - providers: [TranslatePipe], + imports: [CommonModule, NWUIButtonComponent, NWUITextAreaComponent], + providers: [TranslateService], }) -export class TranslateBlockComponent implements OnInit, AfterViewInit { +export class TranslateBlockComponent implements OnInit { @ViewChild('translatedText') translatedText: ElementRef | null = null; - @Input() item: TranslateData = { english_text: '', translated_text: '' }; - @Input() index = 0; + @Input({ required: true }) item: TranslateData = { english_text: '', translated_text: '' }; + @Input({ required: true }) index = 0; + translateLoading = false; isEditing = false; saveChanges() { - const data: TranslateData[] = JSON.parse(localStorage.getItem('translations') ?? '[]'); + const data: TranslateData[] = JSON.parse(localStorage.getItem(LocalStorageKeys.TRANSLATIONS) ?? '[]'); if (!data.length) { alert('No data'); return; @@ -32,32 +35,38 @@ export class TranslateBlockComponent implements OnInit, AfterViewInit { this.isEditing = this.item.translated_text === ''; } - constructor(private translatePipe: TranslatePipe, private changeDetectionRef: ChangeDetectorRef) {} - async sendToTranslate() { - const text = await this.translatePipe.transform(this.item.english_text); - this.item.translated_text = text; - if (this.translatedText) this.translatedText.nativeElement.textContent = text; - this.isEditing = false; - this.saveChanges(); + constructor(private translateService: TranslateService) {} + + private sendToTranslate(service: ETranslateService = ETranslateService.GOOGLE) { + this.translateLoading = true; + this.translateService.translate(this.item.english_text, service).subscribe((text) => { + this.item.translated_text = text; + this.isEditing = false; + this.translateLoading = false; + this.saveChanges(); + }); } - saveTranslate() { + sendToGoogleTranslate() { + this.sendToTranslate(ETranslateService.GOOGLE); + } + + sendToDeeplTranslate() { + this.sendToTranslate(ETranslateService.DEEPL); + } + + sendToPromptTranslate() { + this.sendToTranslate(ETranslateService.PROMPT); + } + + saveTranslate(text: string) { this.isEditing = false; - if (this.translatedText) { - this.item.translated_text = this.translatedText.nativeElement.textContent || ''; - this.translatedText.nativeElement.textContent = ''; - this.translatedText.nativeElement.textContent = this.item.translated_text; - } + this.item.translated_text = text; this.saveChanges(); } clear() { this.item.translated_text = ''; - if (this.translatedText) this.translatedText.nativeElement.textContent = ''; this.isEditing = true; this.saveChanges(); } - - ngAfterViewInit(): void { - if (this.translatedText) this.translatedText.nativeElement.textContent = this.item.translated_text; - } } diff --git a/src/app/consts.ts b/src/app/consts.ts new file mode 100644 index 0000000..c55e089 --- /dev/null +++ b/src/app/consts.ts @@ -0,0 +1,4 @@ +export enum LocalStorageKeys { + TRANSLATIONS = 'translations', + ORIGINAL_FILE = 'original_file', +} diff --git a/src/app/dto/translate_data.dto.ts b/src/app/dto/translate_data.dto.ts index 588f7cb..9e4ed71 100644 --- a/src/app/dto/translate_data.dto.ts +++ b/src/app/dto/translate_data.dto.ts @@ -2,3 +2,8 @@ export interface TranslateData { english_text: string; translated_text: string; } +export interface NpsFile { + file_name: string; + original_text: string; + translated_text?: string; +} diff --git a/src/app/lib/file_tools.ts b/src/app/lib/file_tools.ts new file mode 100644 index 0000000..7e3c721 --- /dev/null +++ b/src/app/lib/file_tools.ts @@ -0,0 +1,6 @@ +import { LocalStorageKeys } from '../consts'; +import { NpsFile } from '../dto/translate_data.dto'; + +export function saveOriginalFile(file: NpsFile) { + localStorage.setItem(LocalStorageKeys.ORIGINAL_FILE, JSON.stringify(file)); +} diff --git a/src/app/pipes/translate.pipe.ts b/src/app/pipes/translate.pipe.ts deleted file mode 100644 index e4400b3..0000000 --- a/src/app/pipes/translate.pipe.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Pipe, PipeTransform } from '@angular/core'; -import { TranslateService } from '../services/translate.service'; - -@Pipe({ name: 'translate', standalone: true, pure: false }) -export class TranslatePipe implements PipeTransform { - constructor(private translateService: TranslateService) {} - transform(text: string) { - return this.translateService.translate(text); - } -} diff --git a/src/app/services/translate.dto.ts b/src/app/services/translate.dto.ts new file mode 100644 index 0000000..550d3c5 --- /dev/null +++ b/src/app/services/translate.dto.ts @@ -0,0 +1 @@ +export type GoogleTranslateResponse = Array>; diff --git a/src/app/services/translate.enums.ts b/src/app/services/translate.enums.ts new file mode 100644 index 0000000..ebf02ef --- /dev/null +++ b/src/app/services/translate.enums.ts @@ -0,0 +1,5 @@ +export enum ETranslateService { + GOOGLE = 'google', + DEEPL = 'deepl', + PROMPT = 'prompt', +} diff --git a/src/app/services/translate.service.ts b/src/app/services/translate.service.ts index 95d0dd8..ca5c693 100644 --- a/src/app/services/translate.service.ts +++ b/src/app/services/translate.service.ts @@ -1,27 +1,34 @@ import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; +import { Observable, map } from 'rxjs'; +import { GoogleTranslateResponse } from './translate.dto'; +import { ETranslateService } from './translate.enums'; @Injectable({ providedIn: 'root' }) export class TranslateService { constructor(private http: HttpClient) {} - translate(text: string): Promise { - return new Promise((resolve) => { - this.http - .get( - 'https://translate.googleapis.com/translate_a/single?client=gtx&sl=en&tl=ru&dt=t&q=' + - encodeURIComponent(text), - ) - .subscribe({ - // eslint-disable-next-line @typescript-eslint/no-explicit-any - next: (response: any) => { - console.log(response); - resolve(response[0][0][0]); - }, - error: (error) => { - console.error(error); - resolve(''); - }, - }); - }); + + private googleTranslate(text: string): Observable { + return this.http + .get( + 'https://translate.googleapis.com/translate_a/single?client=gtx&sl=en&tl=ru&dt=t&q=' + encodeURIComponent(text), + ) + .pipe( + map((response) => { + let result = ''; + response[0].forEach((el) => { + result += el[0] + ' '; + }); + return result; + }), + ); + } + translate(text: string, service: ETranslateService = ETranslateService.GOOGLE): Observable { + switch (service) { + case ETranslateService.GOOGLE: + return this.googleTranslate(text); + default: + return this.googleTranslate(text); + } } }