mirror of
https://github.com/hexastack/hexabot
synced 2025-05-05 13:24:37 +00:00
feat: add language crud
This commit is contained in:
parent
614766c246
commit
10f36c2d48
151
api/src/i18n/controllers/language.controller.ts
Normal file
151
api/src/i18n/controllers/language.controller.ts
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2024 Hexastack. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms:
|
||||||
|
* 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
|
||||||
|
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
|
||||||
|
* 3. SaaS Restriction: This software, or any derivative of it, may not be used to offer a competing product or service (SaaS) without prior written consent from Hexastack. Offering the software as a service or using it in a commercial cloud environment without express permission is strictly prohibited.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
BadRequestException,
|
||||||
|
Body,
|
||||||
|
Controller,
|
||||||
|
Delete,
|
||||||
|
Get,
|
||||||
|
HttpCode,
|
||||||
|
NotFoundException,
|
||||||
|
Param,
|
||||||
|
Patch,
|
||||||
|
Post,
|
||||||
|
Query,
|
||||||
|
UseInterceptors,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { CsrfCheck } from '@tekuconcept/nestjs-csrf';
|
||||||
|
import { TFilterQuery } from 'mongoose';
|
||||||
|
|
||||||
|
import { CsrfInterceptor } from '@/interceptors/csrf.interceptor';
|
||||||
|
import { LoggerService } from '@/logger/logger.service';
|
||||||
|
import { BaseController } from '@/utils/generics/base-controller';
|
||||||
|
import { DeleteResult } from '@/utils/generics/base-repository';
|
||||||
|
import { PageQueryDto } from '@/utils/pagination/pagination-query.dto';
|
||||||
|
import { PageQueryPipe } from '@/utils/pagination/pagination-query.pipe';
|
||||||
|
import { SearchFilterPipe } from '@/utils/pipes/search-filter.pipe';
|
||||||
|
|
||||||
|
import { LanguageCreateDto, LanguageUpdateDto } from '../dto/language.dto';
|
||||||
|
import { Language } from '../schemas/language.schema';
|
||||||
|
import { LanguageService } from '../services/language.service';
|
||||||
|
|
||||||
|
@UseInterceptors(CsrfInterceptor)
|
||||||
|
@Controller('language')
|
||||||
|
export class LanguageController extends BaseController<Language> {
|
||||||
|
constructor(
|
||||||
|
private readonly languageService: LanguageService,
|
||||||
|
private readonly logger: LoggerService,
|
||||||
|
) {
|
||||||
|
super(languageService);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves a paginated list of categories based on provided filters and pagination settings.
|
||||||
|
* @param pageQuery - The pagination settings.
|
||||||
|
* @param filters - The filters to apply to the language search.
|
||||||
|
* @returns A Promise that resolves to a paginated list of categories.
|
||||||
|
*/
|
||||||
|
@Get()
|
||||||
|
async findPage(
|
||||||
|
@Query(PageQueryPipe) pageQuery: PageQueryDto<Language>,
|
||||||
|
@Query(new SearchFilterPipe<Language>({ allowedFields: ['title', 'code'] }))
|
||||||
|
filters: TFilterQuery<Language>,
|
||||||
|
) {
|
||||||
|
return await this.languageService.findPage(filters, pageQuery);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Counts the filtered number of categories.
|
||||||
|
* @returns A promise that resolves to an object representing the filtered number of categories.
|
||||||
|
*/
|
||||||
|
@Get('count')
|
||||||
|
async filterCount(
|
||||||
|
@Query(
|
||||||
|
new SearchFilterPipe<Language>({
|
||||||
|
allowedFields: ['title', 'code'],
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
filters?: TFilterQuery<Language>,
|
||||||
|
) {
|
||||||
|
return await this.count(filters);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds a language by its ID.
|
||||||
|
* @param id - The ID of the language to find.
|
||||||
|
* @returns A Promise that resolves to the found language.
|
||||||
|
*/
|
||||||
|
@Get(':id')
|
||||||
|
async findOne(@Param('id') id: string): Promise<Language> {
|
||||||
|
const doc = await this.languageService.findOne(id);
|
||||||
|
if (!doc) {
|
||||||
|
this.logger.warn(`Unable to find Language by id ${id}`);
|
||||||
|
throw new NotFoundException(`Language with ID ${id} not found`);
|
||||||
|
}
|
||||||
|
return doc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new language.
|
||||||
|
* @param language - The data of the language to be created.
|
||||||
|
* @returns A Promise that resolves to the created language.
|
||||||
|
*/
|
||||||
|
@CsrfCheck(true)
|
||||||
|
@Post()
|
||||||
|
async create(@Body() language: LanguageCreateDto): Promise<Language> {
|
||||||
|
return await this.languageService.create(language);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates an existing language.
|
||||||
|
* @param id - The ID of the language to be updated.
|
||||||
|
* @param languageUpdate - The updated data for the language.
|
||||||
|
* @returns A Promise that resolves to the updated language.
|
||||||
|
*/
|
||||||
|
@CsrfCheck(true)
|
||||||
|
@Patch(':id')
|
||||||
|
async updateOne(
|
||||||
|
@Param('id') id: string,
|
||||||
|
@Body() languageUpdate: LanguageUpdateDto,
|
||||||
|
): Promise<Language> {
|
||||||
|
if ('default' in languageUpdate) {
|
||||||
|
if (languageUpdate.default) {
|
||||||
|
// A new default language is define, make sure that only one is marked as default
|
||||||
|
await this.languageService.updateMany({}, { default: false });
|
||||||
|
} else {
|
||||||
|
throw new BadRequestException('Should not be able to disable default');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await this.languageService.updateOne(id, languageUpdate);
|
||||||
|
if (!result) {
|
||||||
|
this.logger.warn(`Unable to update Language by id ${id}`);
|
||||||
|
throw new NotFoundException(`Language with ID ${id} not found`);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes a language by its ID.
|
||||||
|
* @param id - The ID of the language to be deleted.
|
||||||
|
* @returns A Promise that resolves to the deletion result.
|
||||||
|
*/
|
||||||
|
@CsrfCheck(true)
|
||||||
|
@Delete(':id')
|
||||||
|
@HttpCode(204)
|
||||||
|
async deleteOne(@Param('id') id: string): Promise<DeleteResult> {
|
||||||
|
const result = await this.languageService.deleteOne(id);
|
||||||
|
if (result.deletedCount === 0) {
|
||||||
|
this.logger.warn(`Unable to delete Language by id ${id}`);
|
||||||
|
throw new NotFoundException(`Language with ID ${id} not found`);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
31
api/src/i18n/dto/language.dto.ts
Normal file
31
api/src/i18n/dto/language.dto.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2024 Hexastack. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms:
|
||||||
|
* 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
|
||||||
|
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
|
||||||
|
* 3. SaaS Restriction: This software, or any derivative of it, may not be used to offer a competing product or service (SaaS) without prior written consent from Hexastack. Offering the software as a service or using it in a commercial cloud environment without express permission is strictly prohibited.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { PartialType } from '@nestjs/mapped-types';
|
||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
import { IsBoolean, IsNotEmpty, IsString } from 'class-validator';
|
||||||
|
|
||||||
|
export class LanguageCreateDto {
|
||||||
|
@ApiProperty({ description: 'Language Title', type: String })
|
||||||
|
@IsNotEmpty()
|
||||||
|
@IsString()
|
||||||
|
title: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: 'Language Code', type: String })
|
||||||
|
@IsNotEmpty()
|
||||||
|
@IsString()
|
||||||
|
code: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: 'Is Default Language ?', type: Boolean })
|
||||||
|
@IsNotEmpty()
|
||||||
|
@IsBoolean()
|
||||||
|
default: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class LanguageUpdateDto extends PartialType(LanguageCreateDto) {}
|
@ -7,23 +7,36 @@
|
|||||||
* 3. SaaS Restriction: This software, or any derivative of it, may not be used to offer a competing product or service (SaaS) without prior written consent from Hexastack. Offering the software as a service or using it in a commercial cloud environment without express permission is strictly prohibited.
|
* 3. SaaS Restriction: This software, or any derivative of it, may not be used to offer a competing product or service (SaaS) without prior written consent from Hexastack. Offering the software as a service or using it in a commercial cloud environment without express permission is strictly prohibited.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { DynamicModule, Global, Inject, Module } from '@nestjs/common';
|
import {
|
||||||
|
DynamicModule,
|
||||||
|
forwardRef,
|
||||||
|
Global,
|
||||||
|
Inject,
|
||||||
|
Module,
|
||||||
|
} from '@nestjs/common';
|
||||||
import { HttpAdapterHost } from '@nestjs/core';
|
import { HttpAdapterHost } from '@nestjs/core';
|
||||||
import { MongooseModule } from '@nestjs/mongoose';
|
import { MongooseModule } from '@nestjs/mongoose';
|
||||||
import {
|
import {
|
||||||
I18N_OPTIONS,
|
I18N_OPTIONS,
|
||||||
I18N_TRANSLATIONS,
|
I18N_TRANSLATIONS,
|
||||||
I18nModule as NativeI18nModule,
|
|
||||||
I18nOptions,
|
I18nOptions,
|
||||||
I18nTranslation,
|
I18nTranslation,
|
||||||
|
I18nModule as NativeI18nModule,
|
||||||
} from 'nestjs-i18n';
|
} from 'nestjs-i18n';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
|
import { ChatModule } from '@/chat/chat.module';
|
||||||
|
|
||||||
|
import { LanguageController } from './controllers/language.controller';
|
||||||
import { TranslationController } from './controllers/translation.controller';
|
import { TranslationController } from './controllers/translation.controller';
|
||||||
|
import { LanguageRepository } from './repositories/language.repository';
|
||||||
import { TranslationRepository } from './repositories/translation.repository';
|
import { TranslationRepository } from './repositories/translation.repository';
|
||||||
|
import { LanguageModel } from './schemas/language.schema';
|
||||||
import { TranslationModel } from './schemas/translation.schema';
|
import { TranslationModel } from './schemas/translation.schema';
|
||||||
|
import { LanguageSeeder } from './seeds/language.seed';
|
||||||
import { TranslationSeeder } from './seeds/translation.seed';
|
import { TranslationSeeder } from './seeds/translation.seed';
|
||||||
import { I18nService } from './services/i18n.service';
|
import { I18nService } from './services/i18n.service';
|
||||||
|
import { LanguageService } from './services/language.service';
|
||||||
import { TranslationService } from './services/translation.service';
|
import { TranslationService } from './services/translation.service';
|
||||||
|
|
||||||
@Global()
|
@Global()
|
||||||
@ -43,10 +56,19 @@ export class I18nModule extends NativeI18nModule {
|
|||||||
const { imports, providers, controllers, exports } = super.forRoot(options);
|
const { imports, providers, controllers, exports } = super.forRoot(options);
|
||||||
return {
|
return {
|
||||||
module: I18nModule,
|
module: I18nModule,
|
||||||
imports: imports.concat([MongooseModule.forFeature([TranslationModel])]),
|
imports: (imports || []).concat([
|
||||||
controllers: controllers.concat([TranslationController]),
|
MongooseModule.forFeature([LanguageModel, TranslationModel]),
|
||||||
|
forwardRef(() => ChatModule),
|
||||||
|
]),
|
||||||
|
controllers: (controllers || []).concat([
|
||||||
|
LanguageController,
|
||||||
|
TranslationController,
|
||||||
|
]),
|
||||||
providers: providers.concat([
|
providers: providers.concat([
|
||||||
I18nService,
|
I18nService,
|
||||||
|
LanguageRepository,
|
||||||
|
LanguageService,
|
||||||
|
LanguageSeeder,
|
||||||
TranslationRepository,
|
TranslationRepository,
|
||||||
TranslationService,
|
TranslationService,
|
||||||
TranslationSeeder,
|
TranslationSeeder,
|
||||||
|
23
api/src/i18n/repositories/language.repository.ts
Normal file
23
api/src/i18n/repositories/language.repository.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2024 Hexastack. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms:
|
||||||
|
* 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
|
||||||
|
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
|
||||||
|
* 3. SaaS Restriction: This software, or any derivative of it, may not be used to offer a competing product or service (SaaS) without prior written consent from Hexastack. Offering the software as a service or using it in a commercial cloud environment without express permission is strictly prohibited.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { InjectModel } from '@nestjs/mongoose';
|
||||||
|
import { Model } from 'mongoose';
|
||||||
|
|
||||||
|
import { BaseRepository } from '@/utils/generics/base-repository';
|
||||||
|
|
||||||
|
import { Language } from '../schemas/language.schema';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class LanguageRepository extends BaseRepository<Language> {
|
||||||
|
constructor(@InjectModel(Language.name) readonly model: Model<Language>) {
|
||||||
|
super(model, Language);
|
||||||
|
}
|
||||||
|
}
|
44
api/src/i18n/schemas/language.schema.ts
Normal file
44
api/src/i18n/schemas/language.schema.ts
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2024 Hexastack. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms:
|
||||||
|
* 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
|
||||||
|
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
|
||||||
|
* 3. SaaS Restriction: This software, or any derivative of it, may not be used to offer a competing product or service (SaaS) without prior written consent from Hexastack. Offering the software as a service or using it in a commercial cloud environment without express permission is strictly prohibited.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Prop, Schema, SchemaFactory, ModelDefinition } from '@nestjs/mongoose';
|
||||||
|
import { THydratedDocument } from 'mongoose';
|
||||||
|
|
||||||
|
import { BaseSchema } from '@/utils/generics/base-schema';
|
||||||
|
|
||||||
|
@Schema({ timestamps: true })
|
||||||
|
export class Language extends BaseSchema {
|
||||||
|
@Prop({
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
unique: true,
|
||||||
|
})
|
||||||
|
title: string;
|
||||||
|
|
||||||
|
@Prop({
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
unique: true,
|
||||||
|
})
|
||||||
|
code: string;
|
||||||
|
|
||||||
|
@Prop({
|
||||||
|
type: Boolean,
|
||||||
|
})
|
||||||
|
default: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const LanguageModel: ModelDefinition = {
|
||||||
|
name: Language.name,
|
||||||
|
schema: SchemaFactory.createForClass(Language),
|
||||||
|
};
|
||||||
|
|
||||||
|
export type LanguageDocument = THydratedDocument<Language>;
|
||||||
|
|
||||||
|
export default LanguageModel.schema;
|
23
api/src/i18n/seeds/language.seed-model.ts
Normal file
23
api/src/i18n/seeds/language.seed-model.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2024 Hexastack. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms:
|
||||||
|
* 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
|
||||||
|
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
|
||||||
|
* 3. SaaS Restriction: This software, or any derivative of it, may not be used to offer a competing product or service (SaaS) without prior written consent from Hexastack. Offering the software as a service or using it in a commercial cloud environment without express permission is strictly prohibited.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { LanguageCreateDto } from '../dto/language.dto';
|
||||||
|
|
||||||
|
export const languageModels: LanguageCreateDto[] = [
|
||||||
|
{
|
||||||
|
title: 'English',
|
||||||
|
code: 'en',
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Français',
|
||||||
|
code: 'fr',
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
];
|
22
api/src/i18n/seeds/language.seed.ts
Normal file
22
api/src/i18n/seeds/language.seed.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2024 Hexastack. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms:
|
||||||
|
* 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
|
||||||
|
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
|
||||||
|
* 3. SaaS Restriction: This software, or any derivative of it, may not be used to offer a competing product or service (SaaS) without prior written consent from Hexastack. Offering the software as a service or using it in a commercial cloud environment without express permission is strictly prohibited.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { BaseSeeder } from '@/utils/generics/base-seeder';
|
||||||
|
|
||||||
|
import { LanguageRepository } from '../repositories/language.repository';
|
||||||
|
import { Language } from '../schemas/language.schema';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class LanguageSeeder extends BaseSeeder<Language> {
|
||||||
|
constructor(private readonly languageRepository: LanguageRepository) {
|
||||||
|
super(languageRepository);
|
||||||
|
}
|
||||||
|
}
|
22
api/src/i18n/services/language.service.ts
Normal file
22
api/src/i18n/services/language.service.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2024 Hexastack. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms:
|
||||||
|
* 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
|
||||||
|
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
|
||||||
|
* 3. SaaS Restriction: This software, or any derivative of it, may not be used to offer a competing product or service (SaaS) without prior written consent from Hexastack. Offering the software as a service or using it in a commercial cloud environment without express permission is strictly prohibited.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { BaseService } from '@/utils/generics/base-service';
|
||||||
|
|
||||||
|
import { LanguageRepository } from '../repositories/language.repository';
|
||||||
|
import { Language } from '../schemas/language.schema';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class LanguageService extends BaseService<Language> {
|
||||||
|
constructor(readonly repository: LanguageRepository) {
|
||||||
|
super(repository);
|
||||||
|
}
|
||||||
|
}
|
@ -13,6 +13,8 @@ import { CategorySeeder } from './chat/seeds/category.seed';
|
|||||||
import { categoryModels } from './chat/seeds/category.seed-model';
|
import { categoryModels } from './chat/seeds/category.seed-model';
|
||||||
import { ContextVarSeeder } from './chat/seeds/context-var.seed';
|
import { ContextVarSeeder } from './chat/seeds/context-var.seed';
|
||||||
import { contextVarModels } from './chat/seeds/context-var.seed-model';
|
import { contextVarModels } from './chat/seeds/context-var.seed-model';
|
||||||
|
import { LanguageSeeder } from './i18n/seeds/language.seed';
|
||||||
|
import { languageModels } from './i18n/seeds/language.seed-model';
|
||||||
import { TranslationSeeder } from './i18n/seeds/translation.seed';
|
import { TranslationSeeder } from './i18n/seeds/translation.seed';
|
||||||
import { translationModels } from './i18n/seeds/translation.seed-model';
|
import { translationModels } from './i18n/seeds/translation.seed-model';
|
||||||
import { LoggerService } from './logger/logger.service';
|
import { LoggerService } from './logger/logger.service';
|
||||||
@ -40,6 +42,7 @@ export async function seedDatabase(app: INestApplicationContext) {
|
|||||||
const settingSeeder = app.get(SettingSeeder);
|
const settingSeeder = app.get(SettingSeeder);
|
||||||
const permissionSeeder = app.get(PermissionSeeder);
|
const permissionSeeder = app.get(PermissionSeeder);
|
||||||
const userSeeder = app.get(UserSeeder);
|
const userSeeder = app.get(UserSeeder);
|
||||||
|
const languageSeeder = app.get(LanguageSeeder);
|
||||||
const translationSeeder = app.get(TranslationSeeder);
|
const translationSeeder = app.get(TranslationSeeder);
|
||||||
const nlpEntitySeeder = app.get(NlpEntitySeeder);
|
const nlpEntitySeeder = app.get(NlpEntitySeeder);
|
||||||
const nlpValueSeeder = app.get(NlpValueSeeder);
|
const nlpValueSeeder = app.get(NlpValueSeeder);
|
||||||
@ -127,6 +130,14 @@ export async function seedDatabase(app: INestApplicationContext) {
|
|||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Seed languages
|
||||||
|
try {
|
||||||
|
await languageSeeder.seed(languageModels);
|
||||||
|
} catch (e) {
|
||||||
|
logger.error('Unable to seed the database with languages!');
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
|
||||||
// Seed translations
|
// Seed translations
|
||||||
try {
|
try {
|
||||||
await translationSeeder.seed(translationModels);
|
await translationSeeder.seed(translationModels);
|
||||||
|
@ -100,6 +100,11 @@ export const modelModels: ModelCreateDto[] = [
|
|||||||
identity: 'subscriber',
|
identity: 'subscriber',
|
||||||
attributes: {},
|
attributes: {},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'Language',
|
||||||
|
identity: 'language',
|
||||||
|
attributes: {},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'Translation',
|
name: 'Translation',
|
||||||
identity: 'translation',
|
identity: 'translation',
|
||||||
|
@ -26,6 +26,7 @@ export type TModel =
|
|||||||
| 'conversation'
|
| 'conversation'
|
||||||
| 'message'
|
| 'message'
|
||||||
| 'subscriber'
|
| 'subscriber'
|
||||||
|
| 'language'
|
||||||
| 'translation'
|
| 'translation'
|
||||||
| 'botstats'
|
| 'botstats'
|
||||||
| 'menu'
|
| 'menu'
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
* 3. SaaS Restriction: This software, or any derivative of it, may not be used to offer a competing product or service (SaaS) without prior written consent from Hexastack. Offering the software as a service or using it in a commercial cloud environment without express permission is strictly prohibited.
|
* 3. SaaS Restriction: This software, or any derivative of it, may not be used to offer a competing product or service (SaaS) without prior written consent from Hexastack. Offering the software as a service or using it in a commercial cloud environment without express permission is strictly prohibited.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { CheckCircle } from "@mui/icons-material";
|
||||||
import AdminPanelSettingsIcon from "@mui/icons-material/AdminPanelSettingsOutlined";
|
import AdminPanelSettingsIcon from "@mui/icons-material/AdminPanelSettingsOutlined";
|
||||||
import DeleteIcon from "@mui/icons-material/DeleteOutlined";
|
import DeleteIcon from "@mui/icons-material/DeleteOutlined";
|
||||||
import EditIcon from "@mui/icons-material/EditOutlined";
|
import EditIcon from "@mui/icons-material/EditOutlined";
|
||||||
@ -39,12 +40,16 @@ export enum ActionColumnLabel {
|
|||||||
Content = "button.content",
|
Content = "button.content",
|
||||||
Fields = "button.fields",
|
Fields = "button.fields",
|
||||||
Manage_Labels = "title.manage_labels",
|
Manage_Labels = "title.manage_labels",
|
||||||
|
Toggle = "button.toggle",
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ActionColumn<T extends GridValidRowModel> {
|
export interface ActionColumn<T extends GridValidRowModel> {
|
||||||
label: ActionColumnLabel;
|
label: ActionColumnLabel;
|
||||||
action?: (row: T) => void;
|
action?: (row: T) => void;
|
||||||
requires?: PermissionAction[];
|
requires?: PermissionAction[];
|
||||||
|
getState?: (row: T) => boolean;
|
||||||
|
helperText?: string;
|
||||||
|
isDisabled?: (row: T) => boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const BUTTON_WIDTH = 60;
|
const BUTTON_WIDTH = 60;
|
||||||
@ -70,6 +75,8 @@ function getIcon(label: ActionColumnLabel) {
|
|||||||
return <TocOutlinedIcon />;
|
return <TocOutlinedIcon />;
|
||||||
case ActionColumnLabel.Manage_Labels:
|
case ActionColumnLabel.Manage_Labels:
|
||||||
return <LocalOfferIcon />;
|
return <LocalOfferIcon />;
|
||||||
|
case ActionColumnLabel.Toggle:
|
||||||
|
return <CheckCircle />;
|
||||||
default:
|
default:
|
||||||
return <></>;
|
return <></>;
|
||||||
}
|
}
|
||||||
@ -78,7 +85,7 @@ function getIcon(label: ActionColumnLabel) {
|
|||||||
function getColor(label: ActionColumnLabel) {
|
function getColor(label: ActionColumnLabel) {
|
||||||
switch (label) {
|
switch (label) {
|
||||||
case ActionColumnLabel.Edit:
|
case ActionColumnLabel.Edit:
|
||||||
return theme.palette.warning.main;
|
return theme.palette.grey[900];
|
||||||
case ActionColumnLabel.Delete:
|
case ActionColumnLabel.Delete:
|
||||||
return theme.palette.error.main;
|
return theme.palette.error.main;
|
||||||
default:
|
default:
|
||||||
@ -97,29 +104,46 @@ function StackComponent<T extends GridValidRowModel>({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack height="100%" alignItems="center" direction="row" spacing={0.5}>
|
<Stack height="100%" alignItems="center" direction="row" spacing={0.5}>
|
||||||
{actions.map(({ label, action, requires = [] }) => (
|
{actions.map(
|
||||||
|
({
|
||||||
|
label,
|
||||||
|
action,
|
||||||
|
requires = [],
|
||||||
|
getState,
|
||||||
|
helperText,
|
||||||
|
isDisabled,
|
||||||
|
}) => (
|
||||||
<GridActionsCellItem
|
<GridActionsCellItem
|
||||||
key={label}
|
key={label}
|
||||||
className="actionButton"
|
className="actionButton"
|
||||||
icon={<Tooltip title={t(label)}>{getIcon(label)}</Tooltip>}
|
icon={
|
||||||
label={t(label)}
|
<Tooltip title={helperText || t(label)}>{getIcon(label)}</Tooltip>
|
||||||
|
}
|
||||||
|
label={helperText || t(label)}
|
||||||
showInMenu={false}
|
showInMenu={false}
|
||||||
sx={{
|
sx={{
|
||||||
color: "grey",
|
color:
|
||||||
|
label === ActionColumnLabel.Toggle &&
|
||||||
|
getState &&
|
||||||
|
getState(params.row)
|
||||||
|
? getColor(label)
|
||||||
|
: theme.palette.grey[600],
|
||||||
"&:hover": {
|
"&:hover": {
|
||||||
color: getColor(label),
|
color: getColor(label),
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
disabled={
|
disabled={
|
||||||
params.row.builtin &&
|
(isDisabled && isDisabled(params.row)) ||
|
||||||
|
(params.row.builtin &&
|
||||||
(requires.includes(PermissionAction.UPDATE) ||
|
(requires.includes(PermissionAction.UPDATE) ||
|
||||||
requires.includes(PermissionAction.DELETE))
|
requires.includes(PermissionAction.DELETE)))
|
||||||
}
|
}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
action && action(params.row);
|
action && action(params.row);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
))}
|
),
|
||||||
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
130
frontend/src/components/languages/LanguageDialog.tsx
Normal file
130
frontend/src/components/languages/LanguageDialog.tsx
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2024 Hexastack. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms:
|
||||||
|
* 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
|
||||||
|
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
|
||||||
|
* 3. SaaS Restriction: This software, or any derivative of it, may not be used to offer a competing product or service (SaaS) without prior written consent from Hexastack. Offering the software as a service or using it in a commercial cloud environment without express permission is strictly prohibited.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Dialog, DialogActions, DialogContent } from "@mui/material";
|
||||||
|
import { FC, useEffect } from "react";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
import DialogButtons from "@/app-components/buttons/DialogButtons";
|
||||||
|
import { DialogTitle } from "@/app-components/dialogs/DialogTitle";
|
||||||
|
import { ContentContainer } from "@/app-components/dialogs/layouts/ContentContainer";
|
||||||
|
import { ContentItem } from "@/app-components/dialogs/layouts/ContentItem";
|
||||||
|
import { Input } from "@/app-components/inputs/Input";
|
||||||
|
import { useCreate } from "@/hooks/crud/useCreate";
|
||||||
|
import { useUpdate } from "@/hooks/crud/useUpdate";
|
||||||
|
import { DialogControlProps } from "@/hooks/useDialog";
|
||||||
|
import { useToast } from "@/hooks/useToast";
|
||||||
|
import { EntityType } from "@/services/types";
|
||||||
|
import { ILanguage, ILanguageAttributes } from "@/types/language.types";
|
||||||
|
|
||||||
|
export type LanguageDialogProps = DialogControlProps<ILanguage>;
|
||||||
|
export const LanguageDialog: FC<LanguageDialogProps> = ({
|
||||||
|
open,
|
||||||
|
data,
|
||||||
|
closeDialog,
|
||||||
|
...rest
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { toast } = useToast();
|
||||||
|
const { mutateAsync: createLanguage } = useCreate(EntityType.LANGUAGE, {
|
||||||
|
onError: () => {
|
||||||
|
toast.error(t("message.internal_server_error"));
|
||||||
|
},
|
||||||
|
onSuccess() {
|
||||||
|
closeDialog();
|
||||||
|
toast.success(t("message.success_save"));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const { mutateAsync: updateLanguage } = useUpdate(EntityType.LANGUAGE, {
|
||||||
|
onError: () => {
|
||||||
|
toast.error(t("message.internal_server_error"));
|
||||||
|
},
|
||||||
|
onSuccess() {
|
||||||
|
closeDialog();
|
||||||
|
toast.success(t("message.success_save"));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const {
|
||||||
|
reset,
|
||||||
|
register,
|
||||||
|
formState: { errors },
|
||||||
|
handleSubmit,
|
||||||
|
} = useForm<ILanguageAttributes>({
|
||||||
|
defaultValues: {
|
||||||
|
title: data?.title || "",
|
||||||
|
code: data?.code || "",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const validationRules = {
|
||||||
|
title: {
|
||||||
|
required: t("message.title_is_required"),
|
||||||
|
},
|
||||||
|
code: {
|
||||||
|
required: t("message.code_is_required"),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const onSubmitForm = async (params: ILanguageAttributes) => {
|
||||||
|
if (data) {
|
||||||
|
updateLanguage({ id: data.id, params });
|
||||||
|
} else {
|
||||||
|
createLanguage(params);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (open) reset();
|
||||||
|
}, [open, reset]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (data) {
|
||||||
|
reset({
|
||||||
|
title: data.title,
|
||||||
|
code: data.code,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
}, [data, reset]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={open} fullWidth onClose={closeDialog} {...rest}>
|
||||||
|
<form onSubmit={handleSubmit(onSubmitForm)}>
|
||||||
|
<DialogTitle onClose={closeDialog}>
|
||||||
|
{data ? t("title.edit_label") : t("title.new_label")}
|
||||||
|
</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
<ContentContainer>
|
||||||
|
<ContentItem>
|
||||||
|
<Input
|
||||||
|
label={t("label.title")}
|
||||||
|
error={!!errors.title}
|
||||||
|
{...register("title", validationRules.title)}
|
||||||
|
helperText={errors.title ? errors.title.message : null}
|
||||||
|
multiline={true}
|
||||||
|
/>
|
||||||
|
</ContentItem>
|
||||||
|
<ContentItem>
|
||||||
|
<Input
|
||||||
|
label={t("label.code")}
|
||||||
|
error={!!errors.code}
|
||||||
|
{...register("code", validationRules.code)}
|
||||||
|
helperText={errors.code ? errors.code.message : null}
|
||||||
|
multiline={true}
|
||||||
|
/>
|
||||||
|
</ContentItem>
|
||||||
|
</ContentContainer>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<DialogButtons closeDialog={closeDialog} />
|
||||||
|
</DialogActions>
|
||||||
|
</form>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
204
frontend/src/components/languages/index.tsx
Normal file
204
frontend/src/components/languages/index.tsx
Normal file
@ -0,0 +1,204 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2024 Hexastack. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms:
|
||||||
|
* 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
|
||||||
|
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
|
||||||
|
* 3. SaaS Restriction: This software, or any derivative of it, may not be used to offer a competing product or service (SaaS) without prior written consent from Hexastack. Offering the software as a service or using it in a commercial cloud environment without express permission is strictly prohibited.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Flag } from "@mui/icons-material";
|
||||||
|
import AddIcon from "@mui/icons-material/Add";
|
||||||
|
import { Button, Grid, Paper } from "@mui/material";
|
||||||
|
import { GridColDef } from "@mui/x-data-grid";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
import { DeleteDialog } from "@/app-components/dialogs/DeleteDialog";
|
||||||
|
import { FilterTextfield } from "@/app-components/inputs/FilterTextfield";
|
||||||
|
import {
|
||||||
|
ActionColumnLabel,
|
||||||
|
useActionColumns,
|
||||||
|
} from "@/app-components/tables/columns/getColumns";
|
||||||
|
import { renderHeader } from "@/app-components/tables/columns/renderHeader";
|
||||||
|
import { DataGrid } from "@/app-components/tables/DataGrid";
|
||||||
|
import { useDelete } from "@/hooks/crud/useDelete";
|
||||||
|
import { useFind } from "@/hooks/crud/useFind";
|
||||||
|
import { useUpdate } from "@/hooks/crud/useUpdate";
|
||||||
|
import { getDisplayDialogs, useDialog } from "@/hooks/useDialog";
|
||||||
|
import { useHasPermission } from "@/hooks/useHasPermission";
|
||||||
|
import { useSearch } from "@/hooks/useSearch";
|
||||||
|
import { useToast } from "@/hooks/useToast";
|
||||||
|
import { PageHeader } from "@/layout/content/PageHeader";
|
||||||
|
import { EntityType } from "@/services/types";
|
||||||
|
import { ILanguage } from "@/types/language.types";
|
||||||
|
import { PermissionAction } from "@/types/permission.types";
|
||||||
|
import { getDateTimeFormatter } from "@/utils/date";
|
||||||
|
|
||||||
|
import { LanguageDialog } from "./LanguageDialog";
|
||||||
|
|
||||||
|
export const Languages = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { toast } = useToast();
|
||||||
|
const addDialogCtl = useDialog<ILanguage>(false);
|
||||||
|
const editDialogCtl = useDialog<ILanguage>(false);
|
||||||
|
const deleteDialogCtl = useDialog<string>(false);
|
||||||
|
const hasPermission = useHasPermission();
|
||||||
|
const { onSearch, searchPayload } = useSearch<ILanguage>({
|
||||||
|
$or: ["title", "code"],
|
||||||
|
});
|
||||||
|
const { dataGridProps, refetch } = useFind(
|
||||||
|
{ entity: EntityType.LANGUAGE },
|
||||||
|
{
|
||||||
|
params: searchPayload,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
const { mutateAsync: updateLanguage } = useUpdate(EntityType.LANGUAGE, {
|
||||||
|
onError: () => {
|
||||||
|
toast.error(t("message.internal_server_error"));
|
||||||
|
},
|
||||||
|
onSuccess() {
|
||||||
|
refetch();
|
||||||
|
toast.success(t("message.success_save"));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const { mutateAsync: deleteLanguage } = useDelete(EntityType.LANGUAGE, {
|
||||||
|
onError: () => {
|
||||||
|
toast.error(t("message.internal_server_error"));
|
||||||
|
},
|
||||||
|
onSuccess() {
|
||||||
|
deleteDialogCtl.closeDialog();
|
||||||
|
toast.success(t("message.item_delete_success"));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const toggleDefault = (row: ILanguage) => {
|
||||||
|
if (!row.default) {
|
||||||
|
updateLanguage({
|
||||||
|
id: row.id,
|
||||||
|
params: {
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const actionColumns = useActionColumns<ILanguage>(
|
||||||
|
EntityType.LANGUAGE,
|
||||||
|
[
|
||||||
|
{
|
||||||
|
label: ActionColumnLabel.Toggle,
|
||||||
|
action: (row) => toggleDefault(row),
|
||||||
|
requires: [PermissionAction.UPDATE],
|
||||||
|
getState: (row) => row.default,
|
||||||
|
helperText: t("button.mark_as_default"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: ActionColumnLabel.Edit,
|
||||||
|
action: (row) => editDialogCtl.openDialog(row),
|
||||||
|
requires: [PermissionAction.UPDATE],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: ActionColumnLabel.Delete,
|
||||||
|
action: (row) => deleteDialogCtl.openDialog(row.id),
|
||||||
|
requires: [PermissionAction.DELETE],
|
||||||
|
isDisabled: (row) => row.default,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
t("label.operations"),
|
||||||
|
);
|
||||||
|
const columns: GridColDef<ILanguage>[] = [
|
||||||
|
{ field: "id", headerName: "ID" },
|
||||||
|
{
|
||||||
|
flex: 2,
|
||||||
|
field: "title",
|
||||||
|
headerName: t("label.title"),
|
||||||
|
disableColumnMenu: true,
|
||||||
|
renderHeader,
|
||||||
|
headerAlign: "left",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
flex: 1,
|
||||||
|
field: "code",
|
||||||
|
headerName: t("label.code"),
|
||||||
|
disableColumnMenu: true,
|
||||||
|
renderHeader,
|
||||||
|
headerAlign: "left",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
flex: 1,
|
||||||
|
field: "default",
|
||||||
|
headerName: t("label.default"),
|
||||||
|
disableColumnMenu: true,
|
||||||
|
renderHeader,
|
||||||
|
headerAlign: "left",
|
||||||
|
valueGetter: (value) => (value ? t("label.yes") : t("label.no")),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
minWidth: 140,
|
||||||
|
field: "createdAt",
|
||||||
|
headerName: t("label.createdAt"),
|
||||||
|
disableColumnMenu: true,
|
||||||
|
renderHeader,
|
||||||
|
resizable: false,
|
||||||
|
headerAlign: "left",
|
||||||
|
valueGetter: (params) =>
|
||||||
|
t("datetime.created_at", getDateTimeFormatter(params)),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
minWidth: 140,
|
||||||
|
field: "updatedAt",
|
||||||
|
headerName: t("label.updatedAt"),
|
||||||
|
disableColumnMenu: true,
|
||||||
|
renderHeader,
|
||||||
|
resizable: false,
|
||||||
|
headerAlign: "left",
|
||||||
|
valueGetter: (params) =>
|
||||||
|
t("datetime.updated_at", getDateTimeFormatter(params)),
|
||||||
|
},
|
||||||
|
actionColumns,
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Grid container gap={3} flexDirection="column">
|
||||||
|
<LanguageDialog {...getDisplayDialogs(addDialogCtl)} />
|
||||||
|
<LanguageDialog {...getDisplayDialogs(editDialogCtl)} />
|
||||||
|
<DeleteDialog
|
||||||
|
{...deleteDialogCtl}
|
||||||
|
callback={() => {
|
||||||
|
if (deleteDialogCtl?.data) deleteLanguage(deleteDialogCtl.data);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<PageHeader icon={Flag} title={t("title.languages")}>
|
||||||
|
<Grid
|
||||||
|
justifyContent="flex-end"
|
||||||
|
gap={1}
|
||||||
|
container
|
||||||
|
alignItems="center"
|
||||||
|
flexShrink={0}
|
||||||
|
width="max-content"
|
||||||
|
>
|
||||||
|
<Grid item>
|
||||||
|
<FilterTextfield onChange={onSearch} />
|
||||||
|
</Grid>
|
||||||
|
{hasPermission(EntityType.LANGUAGE, PermissionAction.CREATE) ? (
|
||||||
|
<Grid item>
|
||||||
|
<Button
|
||||||
|
startIcon={<AddIcon />}
|
||||||
|
variant="contained"
|
||||||
|
sx={{ float: "right" }}
|
||||||
|
onClick={() => addDialogCtl.openDialog()}
|
||||||
|
>
|
||||||
|
{t("button.add")}
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
|
) : null}
|
||||||
|
</Grid>
|
||||||
|
</PageHeader>
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<Paper sx={{ padding: 2 }}>
|
||||||
|
<Grid>
|
||||||
|
<DataGrid columns={columns} {...dataGridProps} />
|
||||||
|
</Grid>
|
||||||
|
</Paper>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
};
|
@ -122,6 +122,8 @@
|
|||||||
"cms": "CMS",
|
"cms": "CMS",
|
||||||
"nodes": "Content",
|
"nodes": "Content",
|
||||||
"entities": "Content types",
|
"entities": "Content types",
|
||||||
|
"languages": "Languages",
|
||||||
|
"manage_localization": "Manage Localization",
|
||||||
"translations": "Translations",
|
"translations": "Translations",
|
||||||
"import": "Bulk Import",
|
"import": "Bulk Import",
|
||||||
"media_library": "Media Library",
|
"media_library": "Media Library",
|
||||||
@ -183,6 +185,7 @@
|
|||||||
"edit_node": "Edit Content",
|
"edit_node": "Edit Content",
|
||||||
"import": "Bulk Import",
|
"import": "Bulk Import",
|
||||||
"media_library": "Media Library",
|
"media_library": "Media Library",
|
||||||
|
"languages": "Languages",
|
||||||
"translations": "Translations",
|
"translations": "Translations",
|
||||||
"update_translation": "Update Translation",
|
"update_translation": "Update Translation",
|
||||||
"broadcast": "Broadcast",
|
"broadcast": "Broadcast",
|
||||||
@ -544,7 +547,9 @@
|
|||||||
"total": "Total",
|
"total": "Total",
|
||||||
"general": "General",
|
"general": "General",
|
||||||
"other": "Other",
|
"other": "Other",
|
||||||
"no_data": "No data"
|
"no_data": "No data",
|
||||||
|
"code": "Code",
|
||||||
|
"default": "Default"
|
||||||
},
|
},
|
||||||
"placeholder": {
|
"placeholder": {
|
||||||
"your_username": "Your username",
|
"your_username": "Your username",
|
||||||
@ -642,7 +647,8 @@
|
|||||||
"media_library": "Media Library",
|
"media_library": "Media Library",
|
||||||
"manage_roles": "Manage Roles",
|
"manage_roles": "Manage Roles",
|
||||||
"connect_with_sso": "Connect with SSO",
|
"connect_with_sso": "Connect with SSO",
|
||||||
"add_pattern": "Add pattern"
|
"add_pattern": "Add pattern",
|
||||||
|
"mark_as_default": "Mark as Default"
|
||||||
},
|
},
|
||||||
"input": {
|
"input": {
|
||||||
"search": "Search"
|
"search": "Search"
|
||||||
|
@ -123,6 +123,8 @@
|
|||||||
"cms": "CMS",
|
"cms": "CMS",
|
||||||
"nodes": "Contenu",
|
"nodes": "Contenu",
|
||||||
"entities": "Types de contenu",
|
"entities": "Types de contenu",
|
||||||
|
"manage_localization": "Internationalisation",
|
||||||
|
"languages": "Langues",
|
||||||
"translations": "Traductions",
|
"translations": "Traductions",
|
||||||
"import": "Importation en masse",
|
"import": "Importation en masse",
|
||||||
"media_library": "Bibliothéque Media",
|
"media_library": "Bibliothéque Media",
|
||||||
@ -184,6 +186,7 @@
|
|||||||
"edit_node": "Modifier le contenu",
|
"edit_node": "Modifier le contenu",
|
||||||
"import": "Importation en masse",
|
"import": "Importation en masse",
|
||||||
"media_library": "Bibliothéque Media",
|
"media_library": "Bibliothéque Media",
|
||||||
|
"languages": "Langues",
|
||||||
"translations": "Traductions",
|
"translations": "Traductions",
|
||||||
"update_translation": "Mettre à jour la traduction",
|
"update_translation": "Mettre à jour la traduction",
|
||||||
"broadcast": "Diffusion",
|
"broadcast": "Diffusion",
|
||||||
@ -544,7 +547,9 @@
|
|||||||
"total": "Totale",
|
"total": "Totale",
|
||||||
"general": "Général",
|
"general": "Général",
|
||||||
"other": "Autre",
|
"other": "Autre",
|
||||||
"no_data": "Pas de données"
|
"no_data": "Pas de données",
|
||||||
|
"code": "Code",
|
||||||
|
"default": "Par Défaut"
|
||||||
},
|
},
|
||||||
"placeholder": {
|
"placeholder": {
|
||||||
"your_username": "Votre nom d'utilisateur",
|
"your_username": "Votre nom d'utilisateur",
|
||||||
@ -578,7 +583,8 @@
|
|||||||
"start_date": "Date de début",
|
"start_date": "Date de début",
|
||||||
"end_date": "Date de fin",
|
"end_date": "Date de fin",
|
||||||
"nlp_value": "Valeur",
|
"nlp_value": "Valeur",
|
||||||
"type_message_here": "Ecrivez quelque chose ici ...."
|
"type_message_here": "Ecrivez quelque chose ici ....",
|
||||||
|
"mark_as_default": "Par Défaut"
|
||||||
},
|
},
|
||||||
"button": {
|
"button": {
|
||||||
"login": "Se connecter",
|
"login": "Se connecter",
|
||||||
|
@ -21,6 +21,7 @@ import {
|
|||||||
faUsers,
|
faUsers,
|
||||||
IconDefinition,
|
IconDefinition,
|
||||||
} from "@fortawesome/free-solid-svg-icons";
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { Flag, Language } from "@mui/icons-material";
|
||||||
import AppsIcon from "@mui/icons-material/Apps";
|
import AppsIcon from "@mui/icons-material/Apps";
|
||||||
import ChevronLeftIcon from "@mui/icons-material/ChevronLeft";
|
import ChevronLeftIcon from "@mui/icons-material/ChevronLeft";
|
||||||
import DriveFolderUploadIcon from "@mui/icons-material/DriveFolderUpload";
|
import DriveFolderUploadIcon from "@mui/icons-material/DriveFolderUpload";
|
||||||
@ -175,14 +176,6 @@ const getMenuItems = (ssoEnabled: boolean): MenuItem[] => [
|
|||||||
[EntityType.CONTENT_TYPE]: [PermissionAction.READ],
|
[EntityType.CONTENT_TYPE]: [PermissionAction.READ],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
text: "menu.translations",
|
|
||||||
href: "/translations",
|
|
||||||
Icon: faLanguage,
|
|
||||||
requires: {
|
|
||||||
[EntityType.TRANSLATION]: [PermissionAction.READ],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
text: "menu.media_library",
|
text: "menu.media_library",
|
||||||
href: "/content/media-library",
|
href: "/content/media-library",
|
||||||
@ -249,6 +242,28 @@ const getMenuItems = (ssoEnabled: boolean): MenuItem[] => [
|
|||||||
: []),
|
: []),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
text: "menu.manage_localization",
|
||||||
|
Icon: Language,
|
||||||
|
submenuItems: [
|
||||||
|
{
|
||||||
|
text: "menu.languages",
|
||||||
|
href: "/localization/languages",
|
||||||
|
Icon: Flag,
|
||||||
|
requires: {
|
||||||
|
[EntityType.LANGUAGE]: [PermissionAction.READ],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "menu.translations",
|
||||||
|
href: "/localization/translations",
|
||||||
|
Icon: faLanguage,
|
||||||
|
requires: {
|
||||||
|
[EntityType.TRANSLATION]: [PermissionAction.READ],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
text: "menu.settings",
|
text: "menu.settings",
|
||||||
href: "/settings",
|
href: "/settings",
|
||||||
|
23
frontend/src/pages/localization/languages.tsx
Normal file
23
frontend/src/pages/localization/languages.tsx
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2024 Hexastack. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms:
|
||||||
|
* 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
|
||||||
|
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
|
||||||
|
* 3. SaaS Restriction: This software, or any derivative of it, may not be used to offer a competing product or service (SaaS) without prior written consent from Hexastack. Offering the software as a service or using it in a commercial cloud environment without express permission is strictly prohibited.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { ReactElement } from "react";
|
||||||
|
|
||||||
|
import { Languages } from "@/components/languages";
|
||||||
|
import { Layout } from "@/layout";
|
||||||
|
|
||||||
|
const LanguagesPage = () => {
|
||||||
|
return <Languages />;
|
||||||
|
};
|
||||||
|
|
||||||
|
LanguagesPage.getLayout = function getLayout(page: ReactElement) {
|
||||||
|
return <Layout>{page}</Layout>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LanguagesPage;
|
@ -59,6 +59,7 @@ export const ROUTES = {
|
|||||||
[EntityType.NLP_VALUE]: "/nlpvalue",
|
[EntityType.NLP_VALUE]: "/nlpvalue",
|
||||||
[EntityType.NLP_SAMPLE_ENTITY]: "",
|
[EntityType.NLP_SAMPLE_ENTITY]: "",
|
||||||
[EntityType.MESSAGE]: "/message",
|
[EntityType.MESSAGE]: "/message",
|
||||||
|
[EntityType.LANGUAGE]: "/language",
|
||||||
[EntityType.TRANSLATION]: "/translation",
|
[EntityType.TRANSLATION]: "/translation",
|
||||||
[EntityType.ATTACHMENT]: "/attachment",
|
[EntityType.ATTACHMENT]: "/attachment",
|
||||||
[EntityType.CHANNEL]: "/channel",
|
[EntityType.CHANNEL]: "/channel",
|
||||||
|
@ -210,6 +210,15 @@ export const NlpSampleEntityEntity = new schema.Entity(
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const LanguageEntity = new schema.Entity(
|
||||||
|
EntityType.LANGUAGE,
|
||||||
|
undefined,
|
||||||
|
{
|
||||||
|
idAttribute: ({ id }) => id,
|
||||||
|
processStrategy: processCommonStrategy,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
export const TranslationEntity = new schema.Entity(
|
export const TranslationEntity = new schema.Entity(
|
||||||
EntityType.TRANSLATION,
|
EntityType.TRANSLATION,
|
||||||
undefined,
|
undefined,
|
||||||
@ -280,6 +289,7 @@ export const ENTITY_MAP = {
|
|||||||
[EntityType.NLP_ENTITY]: NlpEntityEntity,
|
[EntityType.NLP_ENTITY]: NlpEntityEntity,
|
||||||
[EntityType.NLP_SAMPLE_ENTITY]: NlpSampleEntityEntity,
|
[EntityType.NLP_SAMPLE_ENTITY]: NlpSampleEntityEntity,
|
||||||
[EntityType.NLP_VALUE]: NlpValueEntity,
|
[EntityType.NLP_VALUE]: NlpValueEntity,
|
||||||
|
[EntityType.LANGUAGE]: LanguageEntity,
|
||||||
[EntityType.TRANSLATION]: TranslationEntity,
|
[EntityType.TRANSLATION]: TranslationEntity,
|
||||||
[EntityType.ATTACHMENT]: AttachmentEntity,
|
[EntityType.ATTACHMENT]: AttachmentEntity,
|
||||||
[EntityType.BLOCK]: BlockEntity,
|
[EntityType.BLOCK]: BlockEntity,
|
||||||
|
@ -32,6 +32,7 @@ export enum EntityType {
|
|||||||
NLP_VALUE = "NlpValue",
|
NLP_VALUE = "NlpValue",
|
||||||
MESSAGE = "Message",
|
MESSAGE = "Message",
|
||||||
MENU = "Menu",
|
MENU = "Menu",
|
||||||
|
LANGUAGE = "Language",
|
||||||
TRANSLATION = "Translation",
|
TRANSLATION = "Translation",
|
||||||
ATTACHMENT = "Attachment",
|
ATTACHMENT = "Attachment",
|
||||||
CHANNEL = "Channel",
|
CHANNEL = "Channel",
|
||||||
|
@ -24,6 +24,7 @@ import { IContentType, IContentTypeAttributes } from "./content-type.types";
|
|||||||
import { IContent, IContentAttributes, IContentFull } from "./content.types";
|
import { IContent, IContentAttributes, IContentFull } from "./content.types";
|
||||||
import { IContextVar, IContextVarAttributes } from "./context-var.types";
|
import { IContextVar, IContextVarAttributes } from "./context-var.types";
|
||||||
import { ILabel, ILabelAttributes, ILabelFull } from "./label.types";
|
import { ILabel, ILabelAttributes, ILabelFull } from "./label.types";
|
||||||
|
import { ILanguage, ILanguageAttributes } from "./language.types";
|
||||||
import {
|
import {
|
||||||
IMenuNode,
|
IMenuNode,
|
||||||
IMenuNodeAttributes,
|
IMenuNodeAttributes,
|
||||||
@ -106,6 +107,7 @@ export const POPULATE_BY_TYPE = {
|
|||||||
[EntityType.MESSAGE]: ["sender", "recipient", "sentBy"],
|
[EntityType.MESSAGE]: ["sender", "recipient", "sentBy"],
|
||||||
[EntityType.MENU]: ["parent"],
|
[EntityType.MENU]: ["parent"],
|
||||||
[EntityType.MENUTREE]: [],
|
[EntityType.MENUTREE]: [],
|
||||||
|
[EntityType.LANGUAGE]: [],
|
||||||
[EntityType.TRANSLATION]: [],
|
[EntityType.TRANSLATION]: [],
|
||||||
[EntityType.ATTACHMENT]: [],
|
[EntityType.ATTACHMENT]: [],
|
||||||
[EntityType.CUSTOM_BLOCK]: [],
|
[EntityType.CUSTOM_BLOCK]: [],
|
||||||
@ -189,6 +191,7 @@ export interface IEntityMapTypes {
|
|||||||
ISubscriber,
|
ISubscriber,
|
||||||
ISubscriberFull
|
ISubscriberFull
|
||||||
>;
|
>;
|
||||||
|
[EntityType.LANGUAGE]: IEntityTypes<ILanguageAttributes, ILanguage>;
|
||||||
[EntityType.TRANSLATION]: IEntityTypes<ITranslationAttributes, ITranslation>;
|
[EntityType.TRANSLATION]: IEntityTypes<ITranslationAttributes, ITranslation>;
|
||||||
[EntityType.USER]: IEntityTypes<IUserAttributes, IUser, IUserFull>;
|
[EntityType.USER]: IEntityTypes<IUserAttributes, IUser, IUserFull>;
|
||||||
[EntityType.ATTACHMENT]: IEntityTypes<IAttachmentAttributes, IAttachment>;
|
[EntityType.ATTACHMENT]: IEntityTypes<IAttachmentAttributes, IAttachment>;
|
||||||
|
26
frontend/src/types/language.types.ts
Normal file
26
frontend/src/types/language.types.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2024 Hexastack. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms:
|
||||||
|
* 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
|
||||||
|
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
|
||||||
|
* 3. SaaS Restriction: This software, or any derivative of it, may not be used to offer a competing product or service (SaaS) without prior written consent from Hexastack. Offering the software as a service or using it in a commercial cloud environment without express permission is strictly prohibited.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { EntityType, Format } from "@/services/types";
|
||||||
|
|
||||||
|
import { IBaseSchema, IFormat, OmitPopulate } from "./base.types";
|
||||||
|
|
||||||
|
export type ILanguages = Record<string, string>;
|
||||||
|
|
||||||
|
export interface ILanguageAttributes {
|
||||||
|
title: string;
|
||||||
|
code: string;
|
||||||
|
default: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ILanguageStub
|
||||||
|
extends IBaseSchema,
|
||||||
|
OmitPopulate<ILanguageAttributes, EntityType.TRANSLATION> {}
|
||||||
|
|
||||||
|
export interface ILanguage extends ILanguageStub, IFormat<Format.BASIC> {}
|
@ -7,9 +7,9 @@
|
|||||||
* 3. SaaS Restriction: This software, or any derivative of it, may not be used to offer a competing product or service (SaaS) without prior written consent from Hexastack. Offering the software as a service or using it in a commercial cloud environment without express permission is strictly prohibited.
|
* 3. SaaS Restriction: This software, or any derivative of it, may not be used to offer a competing product or service (SaaS) without prior written consent from Hexastack. Offering the software as a service or using it in a commercial cloud environment without express permission is strictly prohibited.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Format } from "@/services/types";
|
import { EntityType, Format } from "@/services/types";
|
||||||
|
|
||||||
import { IBaseSchema, IFormat } from "./base.types";
|
import { IBaseSchema, IFormat, OmitPopulate } from "./base.types";
|
||||||
|
|
||||||
export type ITranslations = Record<string, string>;
|
export type ITranslations = Record<string, string>;
|
||||||
|
|
||||||
@ -19,11 +19,8 @@ export interface ITranslationAttributes {
|
|||||||
translated: number;
|
translated: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ITranslationStub extends IBaseSchema {
|
export interface ITranslationStub
|
||||||
str: string;
|
extends IBaseSchema,
|
||||||
translations: ITranslations;
|
OmitPopulate<ITranslationAttributes, EntityType.TRANSLATION> {}
|
||||||
translated: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ITranslation extends ITranslationStub, IFormat<Format.BASIC> {}
|
export interface ITranslation extends ITranslationStub, IFormat<Format.BASIC> {}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user