Adds the i18n manager files.

parent 28e6d8c5
......@@ -167,19 +167,21 @@ const getUnusedKeys = (req) => {
const i18nFileContent = JSON.parse(fs.readFileSync(path.join(req.i18nFileBaseLocation, `${langRefs[i]}.json`)).toString('UTF-8'));
const objectPaths = objectPath(i18nFileContent);
objectPaths.forEach((i18nPath => {
let matched = false;
fileNames.forEach(filename => {
if (matched) {
return;
objectPaths.forEach((
i18nPath => {
let matched = false;
fileNames.forEach(filename => {
if (matched) {
return;
}
const fileContent = fs.readFileSync(filename).toString('UTF-8');
matched = fileContent.indexOf(i18nPath) > -1;
});
if (!matched) {
result[langRefs[i]].push(i18nPath);
}
const fileContent = fs.readFileSync(filename).toString('UTF-8');
matched = fileContent.indexOf(i18nPath) > -1;
});
if (!matched) {
result[langRefs[i]].push(i18nPath);
}
}));
));
}
return result;
......@@ -192,8 +194,7 @@ const getBranch = (req) => {
};
app.engine('html', ngExpressEngine({
bootstrap: RootServerModuleNgFactory,
providers: [
bootstrap: RootServerModuleNgFactory, providers: [
provideModuleMap(LAZY_MODULE_MAP),
],
}));
......@@ -209,10 +210,7 @@ app.get('/api/v1/plugin/i18nator/:project/langFile', async (req, res) => {
const langData = [];
availableLangs.forEach((langRef, index) => {
buildKeys({
root: '',
dataNode: JSON.parse(fs.readFileSync(path.join(req.i18nFileBaseLocation, `${langRef}.json`)).toString('UTF-8')),
langRef,
langData,
root: '', dataNode: JSON.parse(fs.readFileSync(path.join(req.i18nFileBaseLocation, `${langRef}.json`)).toString('UTF-8')), langRef, langData,
});
});
cache[req.projectCache].langData = langData;
......@@ -292,9 +290,7 @@ const buildCache = () => {
console.log(`* Fetching unused keys`);
const unusedKeysStart = new Date().getTime();
cache[projectName].unused = getUnusedKeys({
params: {},
projectAppLocation: projectAppLocation[projectName],
i18nFileBaseLocation: i18nFileBaseLocation[projectName],
params: {}, projectAppLocation: projectAppLocation[projectName], i18nFileBaseLocation: i18nFileBaseLocation[projectName],
});
const unusedKeysEnd = new Date().getTime();
console.log(`-- Done. Took ${unusedKeysEnd - unusedKeysStart}ms`);
......@@ -316,10 +312,7 @@ const buildImages = () => {
console.log(``);
console.log(`------- Building preview screenshots and logo derivates -------`);
const params = [
'--experimental-modules',
'GenerateImages.mjs',
'--command=all',
`--host=http://localhost:${PORT}`,
'--experimental-modules', 'GenerateImages.mjs', '--command=all', `--host=http://localhost:${PORT}`,
];
const instance = child_process.spawn(`node`, params, { cwd: JOBS_FOLDER });
instance.stdout.on('data', (data) => {
......
import { I18nManagerModule } from './i18n-manager.module';
describe('I18nManagerModule', () => {
let i18nManagerModule: I18nManagerModule;
beforeEach(() => {
i18nManagerModule = new I18nManagerModule();
});
it('should create an instance', () => {
expect(i18nManagerModule).toBeTruthy();
});
});
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { PipesModule } from '../pipes/pipes.module';
import { LanguageLoaderService } from '../service/language-loader/language-loader.service';
import { ModalOrganizerService } from '../service/modal-organizer/modal-organizer.service';
import { ProjectLoaderService } from '../service/project-loader/project-loader.service';
import { SharedModule } from '../shared/shared.module';
import { I18nManagerComponent } from './i18n-manager/i18n-manager.component';
import { KeyOutputComponent } from './key-output/key-output.component';
const i18nManagerRoutes: Routes = [
{
path: 'i18n-manager',
component: I18nManagerComponent,
},
];
@NgModule({
imports: [
SharedModule, PipesModule, RouterModule.forChild(i18nManagerRoutes),
],
declarations: [I18nManagerComponent, KeyOutputComponent],
providers: [
LanguageLoaderService, ProjectLoaderService, ModalOrganizerService,
],
})
export class I18nManagerModule {
}
<div class="row d-flex flex-wrap mb-2 text-light">
<div class="d-flex col-12">
<span class="d-inline-flex mx-2 align-items-center">Current Project:</span>
<select class="border-0 bg-transparent text-light"
(change)="setProject($event.target.value)">
<option [value]="projectLoaderService.projects.FRONTEND">Frontend</option>
<option [value]="projectLoaderService.projects.BACKEND">Backend</option>
</select>
<span class="d-inline-flex mx-2 align-items-center"
[class.text-success]="this.projectLoaderService.connected"
[class.text-danger]="!this.projectLoaderService.connected">
{{!this.projectLoaderService.connected ? 'Not ' : ''}} Loaded
</span>
<div *ngIf="this.projectLoaderService.connected"
class="btn-group ml-auto">
<button class="btn btn-secondary pointer"
[disabled]="!changedData"
(click)="updateData()"><span>Save Changes</span>
</button>
<button class="btn btn-secondary pointer"
(click)="modalOrganizerService.addKey(languageLoaderService.parsedLangData)">
<span>Add Key</span>
</button>
</div>
</div>
</div>
<div *ngIf="this.projectLoaderService.connected;else loading"
class="row mh-100 text-light">
<div class="d-flex sticky-top col-12 mb-2">
<div class="bg-white d-flex w-100 py-1">
<select class="border-0 bg-transparent mr-2"
(change)="changeFilter($event.target.value)">
<option [value]="filters.NONE"
[selected]="filter === filters.NONE">No Filter
</option>
<option [value]="filters.UNUSED"
[selected]="filter === filters.UNUSED">Unused Keys
</option>
<option [value]="filters.INVALID_KEYS"
[selected]="filter === filters.INVALID_KEYS">Empty Keys
</option>
<option [value]="filters.INVALID_DE"
[selected]="filter === filters.INVALID_DE">Empty DE Keys
</option>
<option [value]="filters.INVALID_EN"
[selected]="filter === filters.INVALID_EN">Empty EN Keys
</option>
<option [value]="filters.INVALID_ES"
[selected]="filter === filters.INVALID_ES">Empty ES Keys
</option>
<option [value]="filters.INVALID_FR"
[selected]="filter === filters.INVALID_FR">Empty FR Keys
</option>
<option [value]="filters.INVALID_IT"
[selected]="filter === filters.INVALID_IT">Empty IT Keys
</option>
</select>
<input type="text"
class="d-flex flex-grow-1 border-0"
#searchBox
[value]="searchFilter"
(keyup)="searchFilter = (searchBox.value)"
placeholder="Search..."/>
</div>
</div>
<div class="col-12 key-list overflow-auto"
[class.col-sm-6]="!!selectedKey">
<app-key-output [filter]="filter"
[searchFilter]="searchFilter"
[changedData]="changedData"
(changeEmitter)="dataChanged($event)"></app-key-output>
</div>
<div class="col-12 col-sm-6"
*ngIf="!!selectedKey">
<div *ngFor="let langKey of getKeys(languageLoaderService.languages)">
<p class="mb-0">{{langKey.toUpperCase()}}</p>
<textarea class="w-100 key-textarea"
(keyup)="updateKey($event, langKey, selectedKey)"
[value]="selectedKey?.value[langKey] || ''"></textarea>
</div>
</div>
<div class="col-12 d-flex align-items-end justify-content-end"
style="height: 40px;">
<span>Currently on branch: {{projectLoaderService.currentBranch}}</span>
</div>
</div>
<ng-template #loading><h4 class="text-light text-center mt-5">Loading...</h4></ng-template>
.key-textarea {
min-height: 60px;
}
\ No newline at end of file
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { I18nManagerComponent } from './i18n-manager.component';
describe('FeTranslationComponent', () => {
let component: I18nManagerComponent;
let fixture: ComponentFixture<I18nManagerComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [I18nManagerComponent],
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(I18nManagerComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should contain a TYPE reference', () => {
expect(I18nManagerComponent.TYPE).toEqual('I18nManagerComponent');
});
});
import { Component, OnDestroy, OnInit } from '@angular/core';
import { Observable, of } from 'rxjs/index';
import { FooterBarService } from '../../service/footer-bar/footer-bar.service';
import { HeaderLabelService } from '../../service/header-label/header-label.service';
import { LanguageLoaderService } from '../../service/language-loader/language-loader.service';
import { ModalOrganizerService } from '../../service/modal-organizer/modal-organizer.service';
import { ProjectLoaderService } from '../../service/project-loader/project-loader.service';
import { FILTER, PROJECT } from '../../shared/enums';
@Component({
selector: 'app-fe-translation',
templateUrl: './i18n-manager.component.html',
styleUrls: ['./i18n-manager.component.scss'],
})
export class I18nManagerComponent implements OnInit, OnDestroy {
public static readonly TYPE = 'I18nManagerComponent';
public readonly filters = FILTER;
private _langRef = ['en', 'de', 'fr', 'it', 'es'];
get langRef(): Array<string> {
return this._langRef;
}
private _selectedKey: { key: string, value: string };
get selectedKey(): { key: string; value: string } {
return this._selectedKey;
}
private _changedData = false;
get changedData(): boolean {
return this._changedData;
}
private _searchFilter = '';
get searchFilter(): string {
return this._searchFilter;
}
set searchFilter(value: string) {
this._searchFilter = value;
}
private _filter = FILTER.NONE;
get filter(): FILTER {
return this._filter;
}
set filter(value: FILTER) {
this.hasAnyMatches = of(false);
switch (parseInt(String(value), 10)) {
case 0:
this._filter = FILTER.NONE;
return;
case 1:
this._filter = FILTER.UNUSED;
return;
case 2:
this._filter = FILTER.INVALID_KEYS;
return;
case 3:
this._filter = FILTER.INVALID_DE;
return;
case 4:
this._filter = FILTER.INVALID_EN;
return;
case 5:
this._filter = FILTER.INVALID_ES;
return;
case 6:
this._filter = FILTER.INVALID_FR;
return;
case 7:
this._filter = FILTER.INVALID_IT;
return;
default:
throw Error(`Unknown filter set: ${value}`);
}
}
private _hasAnyMatches = of(false);
get hasAnyMatches(): Observable<boolean> {
return this._hasAnyMatches;
}
set hasAnyMatches(value: Observable<boolean>) {
this._hasAnyMatches = value;
}
constructor(private footerBarService: FooterBarService, private headerLabelService: HeaderLabelService,
public modalOrganizerService: ModalOrganizerService, public projectLoaderService: ProjectLoaderService,
private languageLoaderService: LanguageLoaderService,
) {
this.headerLabelService.headerLabel = 'I18Nator';
this.footerBarService.replaceFooterElements([]);
}
public ngOnInit(): void {
this.setProject(PROJECT.FRONTEND);
document.getElementById('content-container').classList.remove('container');
document.getElementById('content-container').classList.add('container-fluid');
}
public ngOnDestroy(): void {
document.getElementById('content-container').classList.add('container');
document.getElementById('content-container').classList.remove('container-fluid');
}
public updateData(): void {
this.languageLoaderService.updateProject();
this._changedData = false;
}
public changeFilter(filter: number): void {
this.filter = filter;
this._selectedKey = null;
}
public setProject(value: PROJECT): void {
this._selectedKey = undefined;
this.languageLoaderService.reset();
this.projectLoaderService.currentProject = value;
this.reloadLanguageData();
}
public dataChanged(key): void {
this._selectedKey = key;
}
public getKeys(dataNode: Array<string>): Array<string> {
if (!dataNode) {
return [];
}
return Object.keys(dataNode).sort();
}
public updateKey(event, langRef, key): void {
const value = event.target.value;
this._changedData = true;
if (!value.length) {
delete key.value[langRef];
} else {
key.value[langRef] = value;
}
}
private reloadLanguageData(): void {
this.languageLoaderService.getLangData();
}
}
<div>
<div (scroll)="scrollHandler($event)"
class="overflow-auto"
style="height: calc(100vh - 200px);">
<div [style.height.px]="Math.max(0, Math.max(0, scrollPos - 10) * 40)"></div>
<div #langDataElement
style="height: calc(100vh - 200px);">
<div *ngFor="let elem of (languageLoaderService.parsedLangData | filterKeys:filter | searchFilter:searchFilter | sort) | justafew:scrollPos; let i of index"
class="px-2 py-2 pointer d-flex"
[class.bg-warning]="selectedIndex === i"
(keydown.ArrowUp)="selectKey(i - 1)"
(keydown.ArrowDown)="selectKey(i + 1)"
(click)="selectKey(i)">
<span class="text-danger mr-2"
*ngIf="hasEmptyKeys(elem)">
<i class="fas fa-exclamation-triangle"></i>
</span>
<span class="text-truncate"
[title]="elem.key">{{elem.key}}</span>
<span class="ml-auto pointer"
*ngIf="selectedIndex === i"
(click)="removeKey(i)">
<i class="fas fa-trash"></i>
</span>
</div>
</div>
<h5 class="mt-5 text-danger text-center w-100 position-absolute"
style="top: 0;"
*ngIf="langDataElement.children.length === 0">
No data could be mapped to the specified filter.
</h5>
</div>
</div>
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { KeyOutputComponent } from './key-output.component';
describe('KeyOutputComponent', () => {
let component: KeyOutputComponent;
let fixture: ComponentFixture<KeyOutputComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [KeyOutputComponent],
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(KeyOutputComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should contain a TYPE reference', () => {
expect(KeyOutputComponent.TYPE).toEqual('KeyOutputComponent');
});
});
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
import { LanguageLoaderService } from '../../service/language-loader/language-loader.service';
import { ProjectLoaderService } from '../../service/project-loader/project-loader.service';
import { FILTER } from '../../shared/enums';
@Component({
selector: 'app-key-output',
templateUrl: './key-output.component.html',
styleUrls: ['./key-output.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class KeyOutputComponent {
public static readonly TYPE = 'KeyOutputComponent';
public readonly filters = FILTER;
@Input() public changedData;
public scrollPos = 0;
public readonly Math = Math;
@Input() public filter = FILTER.NONE;
@Input() public searchFilter = '';
private _selectedIndex: number;
get selectedIndex(): number {
return this._selectedIndex;
}
set selectedIndex(value: number) {
this._selectedIndex = value;
this.changeEmitter.emit(value);
}
@Output() private changeEmitter = new EventEmitter<Object>();
@Output() private changeLangEmitter = new EventEmitter<string>();
constructor(public projectLoaderService: ProjectLoaderService, public languageLoaderService: LanguageLoaderService) {
}
public scrollHandler(event: Event): void {
const pos = (
<HTMLElement>event.target
).scrollTop;
if (this.scrollPos !== Math.floor(pos / 40)) {
this.scrollPos = Math.floor(pos / 40);
}
}
public selectKey(index: number): void {
if (this.selectedIndex === index) {
this.selectedIndex = undefined;
} else {
this.selectedIndex = index;
}
}
public hasEmptyKeys(elem): boolean {
return this.getKeys(elem.value).length < this.getKeys(this.languageLoaderService.languages).length;
}
public removeKey(target: any): void {
this.languageLoaderService.parsedLangData.splice(this.languageLoaderService.parsedLangData.findIndex(elem => elem === target), 1);
}
public getKeys(dataNode: object): Array<string> {
if (!dataNode) {
return [];
}
return Object.keys(dataNode).sort();
}
}
<div class="modal-header">
<h5 class="modal-title">Add key</h5>
<button type="button"
class="close"
data-dismiss="modal"
aria-label="Close"
(click)="dismiss()">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<input type="text"
class="w-100"
id="add-key-input"
[value]="key"
(keyup)="key = $event.target.value"
placeholder="Enter the Key here"/>
<div *ngFor="let langKey of getKeys(languageLoaderService.languages)">
<p class="mb-0">{{langKey.toUpperCase()}}</p>
<textarea class="w-100 key-textarea"
(keyup)="updateKey($event, langKey)"></textarea>
</div>
</div>
<div class="modal-footer">
<button type="button"
class="btn btn-warning cursor-pointer"
data-dismiss="modal"
(click)="dismiss()">Abort
</button>
<button type="button"
class="btn btn-success cursor-pointer"
data-dismiss="modal"
(click)="save()">Save
</button>
</div>
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { AddModeComponent } from './add-mode.component';
describe('AddModeComponent', () => {
let component: AddModeComponent;
let fixture: ComponentFixture<AddModeComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [AddModeComponent],
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(AddModeComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should have a TYPE reference', () => {
expect(AddModeComponent.TYPE).toEqual('AddModeComponent');
});
});
import { Component } from '@angular/core';
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
import { LanguageLoaderService } from '../../service/language-loader/language-loader.service';
@Component({
selector: 'app-add-mode', templateUrl: './add-mode.component.html', styleUrls: ['./add-mode.component.scss'],
})
export class AddModeComponent {
public static readonly TYPE = 'AddModeComponent';
public dataMap;
public key = '';
public value = {};
private scrollY = window.scrollY;
constructor(private activeModal: NgbActiveModal, public languageLoaderService: LanguageLoaderService) {
}
public dismiss(result?): void {
window.scroll(0, this.scrollY);
this.activeModal.dismiss(result);
}
public updateKey(event, langRef): void {
this.value[langRef] = event.target.value;
}
public getKeys(dataNode: object): Array<string> {
if (!dataNode) {
return [];
}
return Object.keys(dataNode).sort();
}
public save(): void {
if (!this.key.length) {
return;
}
this.dataMap.push({ key: this.key, value: this.value });
window.scroll(0, this.scrollY);
this.activeModal.close({ dataMap: this.dataMap });
}
}
import { NgModule } from '@angular/core';
import { SharedModule } from '../shared/shared.module';
import { AddModeComponent } from './add-mode/add-mode.component';
import { AvailableQuizzesComponent } from './available-quizzes/available-quizzes.component';