From 90582949d5e0197150c136a1ce58d5477f4f1b53 Mon Sep 17 00:00:00 2001 From: Sergey Elpashev Date: Sun, 10 Mar 2024 00:14:00 +0300 Subject: [PATCH] Added i18n translations --- src/app/app.component.ts | 3 +- src/app/app.config.ts | 24 ++++++++++-- src/app/modules/dock/dock.component.ts | 6 +-- src/app/modules/link/link.component.html | 2 +- src/app/modules/link/link.component.ts | 3 +- src/app/modules/panel/panel.component.html | 14 ++++++- src/app/modules/panel/panel.component.less | 40 ++++++++++++++++++++ src/app/modules/panel/panel.component.ts | 38 +++++++++++++++++-- src/app/modules/window/window.component.html | 4 +- src/app/modules/window/window.component.ts | 9 +++-- src/app/pipes/translation.pipe.ts | 14 +++++++ src/app/services/translate.service.ts | 30 +++++++++++++++ src/assets/i18n/en.json | 8 ++++ src/assets/i18n/ja.json | 8 ++++ src/assets/i18n/ru.json | 8 ++++ 15 files changed, 191 insertions(+), 20 deletions(-) create mode 100644 src/app/pipes/translation.pipe.ts create mode 100644 src/app/services/translate.service.ts create mode 100644 src/assets/i18n/en.json create mode 100644 src/assets/i18n/ja.json create mode 100644 src/assets/i18n/ru.json diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 44b6132..876f596 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,5 +1,4 @@ import { Component } from "@angular/core"; -import { RouterOutlet } from "@angular/router"; import { DockComponent } from "./modules/dock/dock.component"; import { ModalComponent } from "./modules/modal/modal.component"; import { PanelComponent } from "./modules/panel/panel.component"; @@ -7,7 +6,7 @@ import { PanelComponent } from "./modules/panel/panel.component"; @Component({ selector: "app-root", standalone: true, - imports: [RouterOutlet, PanelComponent, DockComponent, ModalComponent], + imports: [PanelComponent, DockComponent, ModalComponent], templateUrl: "./app.component.html", styleUrl: "./app.component.less", }) diff --git a/src/app/app.config.ts b/src/app/app.config.ts index 6c6ef60..2af3ac9 100644 --- a/src/app/app.config.ts +++ b/src/app/app.config.ts @@ -1,8 +1,24 @@ -import { ApplicationConfig } from '@angular/core'; -import { provideRouter } from '@angular/router'; +import { APP_INITIALIZER, ApplicationConfig } from "@angular/core"; +import { provideRouter } from "@angular/router"; -import { routes } from './app.routes'; +import { provideHttpClient } from "@angular/common/http"; +import { routes } from "./app.routes"; +import { TranslateService } from "./services/translate.service"; + +export function setupTranslateServiceFactory(service: TranslateService) { + return () => service.use("en"); +} export const appConfig: ApplicationConfig = { - providers: [provideRouter(routes)] + providers: [ + provideRouter(routes), + provideHttpClient(), + TranslateService, + { + provide: APP_INITIALIZER, + useFactory: setupTranslateServiceFactory, + deps: [TranslateService], + multi: true, + }, + ], }; diff --git a/src/app/modules/dock/dock.component.ts b/src/app/modules/dock/dock.component.ts index 24a7212..a1a7a3b 100644 --- a/src/app/modules/dock/dock.component.ts +++ b/src/app/modules/dock/dock.component.ts @@ -16,19 +16,19 @@ export class DockComponent { id: 0, svg: "../../../assets/svg/logo-telegram.svg", url: "https://t.me/neur0w0men", - text: "Telegram channel", + text: "TELEGRAM_LABEL", }, { id: 1, svg: "../../../assets/svg/logo-github.svg", url: "https://github.com/MrSedan", - text: "Admin's GitHub", + text: "GITHUB_LABEL", }, { id: 2, svg: "../../../assets/svg/logo-gitea.svg", url: "https://git.nwaifu.su", - text: "Gitea", + text: "GITEA_LABEL", }, ]; } diff --git a/src/app/modules/link/link.component.html b/src/app/modules/link/link.component.html index 522d164..580ecaa 100644 --- a/src/app/modules/link/link.component.html +++ b/src/app/modules/link/link.component.html @@ -1,4 +1,4 @@ - {{ text }} + {{ text | translate }} diff --git a/src/app/modules/link/link.component.ts b/src/app/modules/link/link.component.ts index 31036bc..fe786e9 100644 --- a/src/app/modules/link/link.component.ts +++ b/src/app/modules/link/link.component.ts @@ -1,10 +1,11 @@ import { CommonModule } from "@angular/common"; import { Component, Input } from "@angular/core"; +import { TranslationPipe } from "../../pipes/translation.pipe"; @Component({ standalone: true, selector: "app-link", - imports: [CommonModule], + imports: [CommonModule, TranslationPipe], templateUrl: "./link.component.html", styleUrls: ["./link.component.less"], }) diff --git a/src/app/modules/panel/panel.component.html b/src/app/modules/panel/panel.component.html index 0da7a76..0350ee5 100644 --- a/src/app/modules/panel/panel.component.html +++ b/src/app/modules/panel/panel.component.html @@ -7,7 +7,14 @@ {{ time }}
- en + {{ getLang() }} @@ -15,3 +22,8 @@
+
+

en - English

+

ru - Русский

+

ja - 日本語

+
diff --git a/src/app/modules/panel/panel.component.less b/src/app/modules/panel/panel.component.less index 6eafcac..94daf1d 100644 --- a/src/app/modules/panel/panel.component.less +++ b/src/app/modules/panel/panel.component.less @@ -63,3 +63,43 @@ cursor: pointer; } } +.lang-choose { + position: absolute; + display: none; + &.active { + display: flex; + } + top: 0; + flex-direction: column; + justify-content: space-around; + gap: 0; + background-color: var(--black); + margin-block-start: 3rem; + z-index: 9999; + margin-inline-end: 1rem; + color: var(--white); + padding: 1rem 0; + border-radius: 15px; + &::before { + content: ""; + position: absolute; + left: 50%; + transform: translateX(-50%) rotate(45deg); + vertical-align: middle; + top: -0.5rem; + width: 0; + height: 0; + border: 10px solid var(--black); + } + p { + line-height: 2rem; + width: 100%; + padding: 0.5rem 1rem; + user-select: none; + -webkit-user-select: none; + cursor: pointer; + &:hover { + background-color: #000; + } + } +} diff --git a/src/app/modules/panel/panel.component.ts b/src/app/modules/panel/panel.component.ts index 50737c4..09f5f0d 100644 --- a/src/app/modules/panel/panel.component.ts +++ b/src/app/modules/panel/panel.component.ts @@ -1,9 +1,10 @@ import { CommonModule } from "@angular/common"; -import { Component } from "@angular/core"; +import { Component, ElementRef, HostListener, ViewChild } from "@angular/core"; import { FontAwesomeModule } from "@fortawesome/angular-fontawesome"; import { faGithub } from "@fortawesome/free-brands-svg-icons"; import { faPowerOff, faVolumeHigh } from "@fortawesome/free-solid-svg-icons"; import { PanelSevice } from "../../services/panel.service"; +import { TranslateService } from "../../services/translate.service"; @Component({ standalone: true, @@ -17,7 +18,11 @@ export class PanelComponent { faGithub = faGithub; faVolume = faVolumeHigh; faPower = faPowerOff; - constructor(private panelService: PanelSevice) { + showLangModalBool = false; + @ViewChild("lang_modal") langModal: ElementRef | null = null; + @ViewChild("lang_btn") langBtn: ElementRef | null = null; + + constructor(private panelService: PanelSevice, private translateService: TranslateService) { this.time = this.getTime(); setInterval(() => { this.time = this.getTime(); @@ -26,7 +31,7 @@ export class PanelComponent { private getTime() { const time = this.panelService.getTime(); - return time.toLocaleDateString("en-US", { + return time.toLocaleDateString(this.translateService.translate("TIME_SCHEMA"), { month: "short", day: "numeric", hour: "numeric", @@ -37,4 +42,31 @@ export class PanelComponent { goToSource() { window.open("https://git.nwaifu.su/neuro_llc/NwaifuWeb", "_blank"); } + + @HostListener("window:resize") + private moveLangModal() { + if (!this.langModal || !this.langBtn) { + return; + } + + const x = this.langBtn.nativeElement.getBoundingClientRect().x; + this.langModal.nativeElement.style.left = `calc(${x}px - 3.5rem)`; + } + + toggleModal() { + if (this.langModal) { + this.langModal.nativeElement.classList.toggle("active"); + if (this.langModal.nativeElement.classList.contains("active")) { + this.moveLangModal(); + } + } + } + + useLang(lang: string) { + this.translateService.use(lang); + } + + getLang() { + return this.translateService.lang; + } } diff --git a/src/app/modules/window/window.component.html b/src/app/modules/window/window.component.html index 1239aa1..bd377db 100644 --- a/src/app/modules/window/window.component.html +++ b/src/app/modules/window/window.component.html @@ -1,10 +1,10 @@
- Info + {{ modal_title | translate }}
-

Hello

+

{{ modal_text | translate }}

diff --git a/src/app/modules/window/window.component.ts b/src/app/modules/window/window.component.ts index df6ec48..db5642a 100644 --- a/src/app/modules/window/window.component.ts +++ b/src/app/modules/window/window.component.ts @@ -1,18 +1,21 @@ import { CommonModule } from "@angular/common"; -import { ChangeDetectionStrategy, Component } from "@angular/core"; +import { Component } from "@angular/core"; import { FontAwesomeModule } from "@fortawesome/angular-fontawesome"; import { faBars, faXmark } from "@fortawesome/free-solid-svg-icons"; import { DialogRef } from "@ngneat/dialog"; +import { TranslationPipe } from "../../pipes/translation.pipe"; @Component({ selector: "app-window", standalone: true, - imports: [CommonModule, FontAwesomeModule], + imports: [CommonModule, FontAwesomeModule, TranslationPipe], templateUrl: "./window.component.html", styleUrls: ["./window.component.less"], - changeDetection: ChangeDetectionStrategy.OnPush, + // changeDetection: ChangeDetectionStrategy.OnPush, }) export class WindowComponent { + modal_text = "MODAL_TEXT"; + modal_title = "MODAL_TITLE"; faClose = faXmark; faMenu = faBars; constructor(public ref: DialogRef) {} diff --git a/src/app/pipes/translation.pipe.ts b/src/app/pipes/translation.pipe.ts new file mode 100644 index 0000000..de13ada --- /dev/null +++ b/src/app/pipes/translation.pipe.ts @@ -0,0 +1,14 @@ +import { Pipe, PipeTransform } from "@angular/core"; +import { TranslateService } from "../services/translate.service"; + +@Pipe({ + name: "translate", + standalone: true, + pure: false, +}) +export class TranslationPipe implements PipeTransform { + constructor(private translateService: TranslateService) {} + transform(key: string) { + return this.translateService.data[key] ?? key; + } +} diff --git a/src/app/services/translate.service.ts b/src/app/services/translate.service.ts new file mode 100644 index 0000000..94c5502 --- /dev/null +++ b/src/app/services/translate.service.ts @@ -0,0 +1,30 @@ +import { HttpClient } from "@angular/common/http"; +import { Injectable } from "@angular/core"; + +@Injectable({ providedIn: "root" }) +export class TranslateService { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + data: Record = {}; + lang = "en"; + constructor(private http: HttpClient) {} + use(lang: string): Promise { + this.lang = lang ?? "en"; + return new Promise((resolve) => { + const langPath = `assets/i18n/${lang ?? "en"}.json`; + this.http.get(langPath).subscribe({ + next: (response) => { + this.data = response ?? {}; + resolve(this.data); + }, + error: () => { + this.data = {}; + resolve(this.data); + }, + }); + }); + } + + translate(key: string) { + return this.data[key] ?? key; + } +} diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json new file mode 100644 index 0000000..6ccb062 --- /dev/null +++ b/src/assets/i18n/en.json @@ -0,0 +1,8 @@ +{ + "MODAL_TITLE": "Info", + "TIME_SCHEMA": "en-US", + "MODAL_TEXT": "Hello, World!", + "TELEGRAM_LABEL": "Telegram channel", + "GITHUB_LABEL": "Admin's Github", + "GITEA_LABEL": "Neuro LLC Gitea" +} diff --git a/src/assets/i18n/ja.json b/src/assets/i18n/ja.json new file mode 100644 index 0000000..94ee720 --- /dev/null +++ b/src/assets/i18n/ja.json @@ -0,0 +1,8 @@ +{ + "MODAL_TITLE": "情報", + "TIME_SCHEMA": "ja-JP", + "MODAL_TEXT": "こんにちは、世界!", + "TELEGRAM_LABEL": "Telegramチャンネル", + "GITHUB_LABEL": "管理者Github", + "GITEA_LABEL": "Neuro LLC Gitea" +} diff --git a/src/assets/i18n/ru.json b/src/assets/i18n/ru.json new file mode 100644 index 0000000..1c67830 --- /dev/null +++ b/src/assets/i18n/ru.json @@ -0,0 +1,8 @@ +{ + "MODAL_TITLE": "Информация", + "TIME_SCHEMA": "ru-RU", + "MODAL_TEXT": "Здравствуйте, мир!", + "TELEGRAM_LABEL": "Телеграм канал", + "GITHUB_LABEL": "Github админа", + "GITEA_LABEL": "Neuro LLC Gitea" +}