diff --git a/.vscode/settings.json b/.vscode/settings.json index b3b9243..a1acd47 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,5 @@ { "editor.defaultFormatter": "esbenp.prettier-vscode", "editor.formatOnSave": true, - "prettier.tabWidth": 2 + "editor.tabSize": 2 } \ No newline at end of file diff --git a/src/app/app.component.html b/src/app/app.component.html index 2c3d4eb..990c5d5 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -1,3 +1,6 @@ - +
+ + +
diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 48ba98b..accfed6 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,12 +1,13 @@ import { Component } from '@angular/core'; import { RouterModule } from '@angular/router'; +import { ClearBtnComponent } from './components/clear_btn/clear_btn.component'; import { TextListComponent } from './components/text_list/text_list.component'; import { UploadBtnComponent } from './components/upload_btn/upload_btn.component'; import { parse } from './lib/parser'; @Component({ standalone: true, - imports: [UploadBtnComponent, RouterModule, TextListComponent], + imports: [UploadBtnComponent, RouterModule, TextListComponent, ClearBtnComponent], selector: 'app-root', templateUrl: './app.component.html', styleUrl: './app.component.scss', @@ -18,9 +19,9 @@ export class AppComponent { onFileLoaded(text: string) { const parsed = parse(text); this.elements = parsed; - console.log(parsed); + } - // const lines = text.split('\n'); - // this.elements.push(...lines); + onClearClicked() { + this.elements = []; } } diff --git a/src/app/app.config.ts b/src/app/app.config.ts index 9e677fd..98b4000 100644 --- a/src/app/app.config.ts +++ b/src/app/app.config.ts @@ -1,8 +1,9 @@ import { provideHttpClient } from '@angular/common/http'; -import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core'; +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(), provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(appRoutes)], + providers: [provideHttpClient(), provideRouter(appRoutes), TranslateService], }; diff --git a/src/app/components/clear_btn/clear_btn.component.html b/src/app/components/clear_btn/clear_btn.component.html new file mode 100644 index 0000000..ec0f6e9 --- /dev/null +++ b/src/app/components/clear_btn/clear_btn.component.html @@ -0,0 +1,3 @@ + diff --git a/src/app/components/clear_btn/clear_btn.component.scss b/src/app/components/clear_btn/clear_btn.component.scss new file mode 100644 index 0000000..d68a12c --- /dev/null +++ b/src/app/components/clear_btn/clear_btn.component.scss @@ -0,0 +1,14 @@ +button { + outline: none; + border: none; + cursor: pointer; + background-color: #5b3f45; + padding: 1em 1.5em; + color: #f5f6fa; + border-radius: 15px; + transition: ease-in-out 0.2s; + margin: 2rem; + &:hover { + transform: scale(1.2); + } +} diff --git a/src/app/components/clear_btn/clear_btn.component.ts b/src/app/components/clear_btn/clear_btn.component.ts new file mode 100644 index 0000000..9e639c8 --- /dev/null +++ b/src/app/components/clear_btn/clear_btn.component.ts @@ -0,0 +1,17 @@ +import { CommonModule } from '@angular/common'; +import { Component, EventEmitter, Output } from '@angular/core'; + +@Component({ + selector: 'app-clear-btn', + templateUrl: './clear_btn.component.html', + styleUrls: ['./clear_btn.component.scss'], + standalone: true, + imports: [CommonModule], +}) +export class ClearBtnComponent { + @Output() clear = new EventEmitter(); + clear_func() { + localStorage.setItem('translations', '[]'); + this.clear.emit(); + } +} diff --git a/src/app/components/text_list/guide/http b/src/app/components/text_list/guide/http deleted file mode 100644 index e69de29..0000000 diff --git a/src/app/components/text_list/text_list.component.html b/src/app/components/text_list/text_list.component.html index 8a6dc89..af033f4 100644 --- a/src/app/components/text_list/text_list.component.html +++ b/src/app/components/text_list/text_list.component.html @@ -1,13 +1,21 @@ -

Всего: {{ elements.length }}

+

Всего: {{ elements_data.length }}

- @for(item of elements; track $index) { -
+ @for(item of elements_data; track $index) { + + }
diff --git a/src/app/components/text_list/text_list.component.scss b/src/app/components/text_list/text_list.component.scss index cb41263..4ec5585 100644 --- a/src/app/components/text_list/text_list.component.scss +++ b/src/app/components/text_list/text_list.component.scss @@ -3,85 +3,17 @@ flex-direction: column; gap: 3rem; align-items: center; - textarea { - height: fit-content; - min-height: 3rem; - line-height: 3rem; - border-radius: 8px; - color: #efdee0; - outline: none; - padding-inline: 2rem 1rem; - background-color: transparent; - border: 2px solid #e4bdc3; - word-wrap: break-word; - overflow-x: hidden; - resize: none; - overflow-y: hidden; - &:focus { - border: 2px solid #efdee0; - } - font-size: 1rem; - font-weight: inherit; - } margin-inline: 2rem; } -.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; - padding: 3rem 5rem; - border-radius: 30px; - flex-direction: row; - width: 100%; - max-width: 1200px; - align-items: center; - gap: 2rem; - justify-content: space-between; - h2 { - color: #efdee0; - } - .fields { - display: flex; - flex-direction: column; - gap: 1rem; - width: 100%; - max-width: 1200px; - } -} - -@media (max-width: 768px) { - .element { - flex-direction: column; - } -} - h1 { color: #efdee0; text-align: center; margin: 1rem 2rem; } - -.send-translate-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; - margin: 2rem; - &:hover { - transform: scale(1.2); - } +app-translate-block { + width: 100%; + display: flex; + flex-direction: column; + align-items: center; } diff --git a/src/app/components/text_list/text_list.component.ts b/src/app/components/text_list/text_list.component.ts index 53c7cd3..2cf02c4 100644 --- a/src/app/components/text_list/text_list.component.ts +++ b/src/app/components/text_list/text_list.component.ts @@ -1,38 +1,67 @@ import { CommonModule } from '@angular/common'; import { HttpClient } from '@angular/common/http'; -import { Component, Input } from '@angular/core'; +import { ChangeDetectorRef, Component, Input } from '@angular/core'; +import { TranslateBlockComponent } from '../translate_block/translate_block.component'; +import { TranslateData } from './text_list.dto'; @Component({ selector: 'app-text-list', templateUrl: './text_list.component.html', styleUrls: ['./text_list.component.scss'], standalone: true, - imports: [CommonModule], + imports: [CommonModule, TranslateBlockComponent], }) export class TextListComponent { - constructor(private http: HttpClient) {} - private _elements: string[] = []; - translated_text: string = ''; + constructor(private http: HttpClient, private changeDetectionRef: ChangeDetectorRef) { + const data = localStorage.getItem('translations'); + if (data) { + this.elements_data = JSON.parse(data); + } + } + elements_data: TranslateData[] = []; @Input() set elements(el: string[]) { - this._elements = el; + this.elements_data = el.map((item) => ({ english_text: item, translated_text: '' })); + localStorage.setItem('translations', JSON.stringify(this.elements_data)); } get elements() { - return this._elements; - } - autoScroll($event: Event) { - const target = $event.target as HTMLTextAreaElement; - target.style.height = '3rem'; - target.style.height = target.scrollHeight + 'px'; + return this.elements_data.map((item) => item.english_text); } - sendToTranslate($text: string) { + sendToTranslate($text: string, $index: number) { + if (!$text) { + return; + } + if (this.elements_data[$index].translated_text && !confirm('Are you sure you want to override the translation?')) { + return; + } + console.log('translated'); $text = encodeURIComponent($text); - console.log(this.elements); - //TODO: говно какое-та не хочет отправлять целиком this.http .get(`https://translate.googleapis.com/translate_a/single?client=gtx&sl=en&tl=ru&dt=t&q=` + $text) - .subscribe({ next: (data: any) => (this.translated_text = data[0][0][0]) }); + .subscribe({ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + next: (data: any) => { + this.elements_data[$index].translated_text = data[0][0][0]; + localStorage.setItem('translations', JSON.stringify(this.elements_data)); + this.changeDetectionRef.detectChanges(); + }, + error: (error) => { + alert(error); + console.error(error); + }, + }); + } + + typeTranslation($event: Event, $index: number) { + const target = $event.target as HTMLDivElement; + if (target) { + this.elements_data[$index].translated_text = target.textContent || '[]'; + localStorage.setItem('translations', JSON.stringify(this.elements_data)); + target.style.height = '3rem'; + target.style.height = target.scrollHeight + 'px'; + // this.changeDetectionRef.detectChanges(); + } } } diff --git a/src/app/components/text_list/text_list.dto.ts b/src/app/components/text_list/text_list.dto.ts new file mode 100644 index 0000000..588f7cb --- /dev/null +++ b/src/app/components/text_list/text_list.dto.ts @@ -0,0 +1,4 @@ +export interface TranslateData { + english_text: string; + translated_text: string; +} diff --git a/src/app/components/translate_block/translate_block.component.html b/src/app/components/translate_block/translate_block.component.html new file mode 100644 index 0000000..64db149 --- /dev/null +++ b/src/app/components/translate_block/translate_block.component.html @@ -0,0 +1,16 @@ +
+

{{ index + 1 }}

+
+
{{ item.english_text }}
+
+ +
+
+ +
diff --git a/src/app/components/translate_block/translate_block.component.scss b/src/app/components/translate_block/translate_block.component.scss new file mode 100644 index 0000000..8a402d4 --- /dev/null +++ b/src/app/components/translate_block/translate_block.component.scss @@ -0,0 +1,82 @@ +.translated-text { + border-radius: 8px; + height: fit-content; + color: #efdee0; + outline: none; + padding-inline: 2rem 1rem; + background-color: transparent; + border: 2px solid #e4bdc3; + font-size: 1rem; + font-weight: inherit; + &:focus { + border: 2px solid #efdee0; + } + textarea { + resize: none; + width: 100%; + height: fit-content; + background-color: transparent; + border: none; + outline: none; + color: #efdee0; + font-size: 1rem; + font-weight: inherit; + line-height: 3rem; + + 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; + padding: 3rem 5rem; + border-radius: 30px; + flex-direction: row; + width: 100%; + max-width: 1200px; + align-items: center; + gap: 2rem; + justify-content: space-between; + h2 { + color: #efdee0; + } + .fields { + display: flex; + flex-direction: column; + gap: 1rem; + width: 100%; + max-width: 1200px; + } +} + +@media (max-width: 768px) { + .element { + flex-direction: column; + } +} + +.send-translate-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; + margin: 2rem; + &:hover { + transform: scale(1.2); + } +} diff --git a/src/app/components/translate_block/translate_block.component.ts b/src/app/components/translate_block/translate_block.component.ts new file mode 100644 index 0000000..78eab94 --- /dev/null +++ b/src/app/components/translate_block/translate_block.component.ts @@ -0,0 +1,31 @@ +import { CommonModule } from '@angular/common'; +import { Component, ElementRef, Input, ViewChild } from '@angular/core'; +import { TranslatePipe } from 'src/app/pipes/translate.pipe'; +import { TranslateData } from '../text_list/text_list.dto'; + +@Component({ + selector: 'app-translate-block', + templateUrl: './translate_block.component.html', + styleUrls: ['./translate_block.component.scss'], + standalone: true, + imports: [CommonModule], + providers: [TranslatePipe], +}) +export class TranslateBlockComponent { + @ViewChild('translatedText') translatedText: ElementRef | null = null; + @Input() item: TranslateData = { english_text: '', translated_text: '' }; + @Input() index = 0; + + constructor(private translatePipe: TranslatePipe) {} + async sendToTranslate() { + const text = await this.translatePipe.transform(this.item.english_text); + this.item.translated_text = text; + } + typeTranslation() { + if (this.translatedText) { + const element = this.translatedText.nativeElement; + element.setAttribute('style', 'height: 3px;'); + element.setAttribute('style', 'height:' + element.scrollHeight + 'px; overflow-y:hidden;'); + } + } +} diff --git a/src/app/components/upload_btn/upload_btn.component.ts b/src/app/components/upload_btn/upload_btn.component.ts index e0fa549..8af4024 100644 --- a/src/app/components/upload_btn/upload_btn.component.ts +++ b/src/app/components/upload_btn/upload_btn.component.ts @@ -25,6 +25,7 @@ export class UploadBtnComponent { const reader = new FileReader(); reader.onload = () => { this.fileLoaded.emit(reader.result as string); + target.value = ''; }; reader.readAsText(file); } diff --git a/src/app/lib/translater.ts b/src/app/lib/translater.ts new file mode 100644 index 0000000..30a8184 --- /dev/null +++ b/src/app/lib/translater.ts @@ -0,0 +1,17 @@ +import { HttpClient } from '@angular/common/http'; +import { firstValueFrom } from 'rxjs'; +import { map } from 'rxjs/operators'; + +export async function translate(text: string, http: HttpClient): Promise { + const response = await firstValueFrom( + http + .get( + `https://translate.googleapis.com/translate_a/single?client=gtx&sl=en&tl=ru&dt=t&q=` + encodeURIComponent(text), + ) + .pipe( + map((data: any) => data[0][0][0]), // Assuming the response structure from the API + ), + ); + + return response; +} diff --git a/src/app/pipes/translate.pipe.ts b/src/app/pipes/translate.pipe.ts new file mode 100644 index 0000000..e4400b3 --- /dev/null +++ b/src/app/pipes/translate.pipe.ts @@ -0,0 +1,10 @@ +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.service.ts b/src/app/services/translate.service.ts new file mode 100644 index 0000000..95d0dd8 --- /dev/null +++ b/src/app/services/translate.service.ts @@ -0,0 +1,27 @@ +import { HttpClient } from '@angular/common/http'; +import { Injectable } from '@angular/core'; + +@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(''); + }, + }); + }); + } +}