Merge branch 'main' into 48-request-context-vars-permanent-option

This commit is contained in:
medtaher
2024-09-28 15:01:29 +01:00
131 changed files with 3087 additions and 989 deletions

View File

@@ -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.
*/
import { CACHE_MANAGER } from '@nestjs/cache-manager';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { MongooseModule } from '@nestjs/mongoose';
import { Test } from '@nestjs/testing';
@@ -24,11 +25,14 @@ import { ContentTypeModel } from '@/cms/schemas/content-type.schema';
import { Content, ContentModel } from '@/cms/schemas/content.schema';
import { ContentTypeService } from '@/cms/services/content-type.service';
import { ContentService } from '@/cms/services/content.service';
import { ExtendedI18nService } from '@/extended-i18n.service';
import OfflineHandler from '@/extensions/channels/offline/index.channel';
import { OFFLINE_CHANNEL_NAME } from '@/extensions/channels/offline/settings';
import { Offline } from '@/extensions/channels/offline/types';
import OfflineEventWrapper from '@/extensions/channels/offline/wrapper';
import { LanguageRepository } from '@/i18n/repositories/language.repository';
import { LanguageModel } from '@/i18n/schemas/language.schema';
import { I18nService } from '@/i18n/services/i18n.service';
import { LanguageService } from '@/i18n/services/language.service';
import { LoggerService } from '@/logger/logger.service';
import { PluginService } from '@/plugins/plugins.service';
import { Settings } from '@/setting/schemas/types';
@@ -94,6 +98,7 @@ describe('BlockService', () => {
ContentModel,
AttachmentModel,
LabelModel,
LanguageModel,
]),
],
providers: [
@@ -102,18 +107,20 @@ describe('BlockService', () => {
ContentTypeRepository,
ContentRepository,
AttachmentRepository,
LanguageRepository,
BlockService,
CategoryService,
ContentTypeService,
ContentService,
AttachmentService,
LanguageService,
{
provide: PluginService,
useValue: {},
},
LoggerService,
{
provide: ExtendedI18nService,
provide: I18nService,
useValue: {
t: jest.fn().mockImplementation((t) => {
return t === 'Welcome' ? 'Bienvenue' : t;
@@ -132,6 +139,14 @@ describe('BlockService', () => {
},
},
EventEmitter2,
{
provide: CACHE_MANAGER,
useValue: {
del: jest.fn(),
get: jest.fn(),
set: jest.fn(),
},
},
],
}).compile();
blockService = module.get<BlockService>(BlockService);

View File

@@ -13,7 +13,8 @@ import { Attachment } from '@/attachment/schemas/attachment.schema';
import { AttachmentService } from '@/attachment/services/attachment.service';
import EventWrapper from '@/channel/lib/EventWrapper';
import { ContentService } from '@/cms/services/content.service';
import { ExtendedI18nService } from '@/extended-i18n.service';
import { I18nService } from '@/i18n/services/i18n.service';
import { LanguageService } from '@/i18n/services/language.service';
import { LoggerService } from '@/logger/logger.service';
import { Nlp } from '@/nlp/lib/types';
import { PluginService } from '@/plugins/plugins.service';
@@ -44,7 +45,8 @@ export class BlockService extends BaseService<Block, BlockPopulate, BlockFull> {
private readonly settingService: SettingService,
private readonly pluginService: PluginService,
private readonly logger: LoggerService,
protected readonly i18n: ExtendedI18nService,
protected readonly i18n: I18nService,
protected readonly languageService: LanguageService,
) {
super(repository);
}
@@ -109,12 +111,9 @@ export class BlockService extends BaseService<Block, BlockPopulate, BlockFull> {
// Check & catch user language through NLP
const nlp = event.getNLP();
if (nlp) {
const settings = await this.settingService.getSettings();
const languages = await this.languageService.getLanguages();
const lang = nlp.entities.find((e) => e.entity === 'language');
if (
lang &&
settings.nlp_settings.languages.indexOf(lang.value) !== -1
) {
if (lang && Object.keys(languages).indexOf(lang.value) !== -1) {
const profile = event.getSender();
profile.language = lang.value;
event.setSender(profile);
@@ -372,12 +371,11 @@ export class BlockService extends BaseService<Block, BlockPopulate, BlockFull> {
subscriberContext: SubscriberContext,
settings: Settings,
): string {
const lang =
context && context.user && context.user.language
? context.user.language
: settings.nlp_settings.default_lang;
// Translate
text = this.i18n.t(text, { lang, defaultValue: text });
text = this.i18n.t(text, {
lang: context.user.language,
defaultValue: text,
});
// Replace context tokens
text = this.processTokenReplacements(
text,

View File

@@ -25,10 +25,13 @@ import { MenuModel } from '@/cms/schemas/menu.schema';
import { ContentTypeService } from '@/cms/services/content-type.service';
import { ContentService } from '@/cms/services/content.service';
import { MenuService } from '@/cms/services/menu.service';
import { ExtendedI18nService } from '@/extended-i18n.service';
import { offlineEventText } from '@/extensions/channels/offline/__test__/events.mock';
import OfflineHandler from '@/extensions/channels/offline/index.channel';
import OfflineEventWrapper from '@/extensions/channels/offline/wrapper';
import { LanguageRepository } from '@/i18n/repositories/language.repository';
import { LanguageModel } from '@/i18n/schemas/language.schema';
import { I18nService } from '@/i18n/services/i18n.service';
import { LanguageService } from '@/i18n/services/language.service';
import { LoggerService } from '@/logger/logger.service';
import { NlpEntityRepository } from '@/nlp/repositories/nlp-entity.repository';
import { NlpSampleEntityRepository } from '@/nlp/repositories/nlp-sample-entity.repository';
@@ -111,6 +114,7 @@ describe('BlockService', () => {
NlpSampleEntityModel,
NlpSampleModel,
ContextVarModel,
LanguageModel,
]),
],
providers: [
@@ -130,6 +134,7 @@ describe('BlockService', () => {
NlpEntityRepository,
NlpSampleEntityRepository,
NlpSampleRepository,
LanguageRepository,
BlockService,
CategoryService,
ContentTypeService,
@@ -149,13 +154,14 @@ describe('BlockService', () => {
NlpService,
ContextVarService,
ContextVarRepository,
LanguageService,
{
provide: PluginService,
useValue: {},
},
LoggerService,
{
provide: ExtendedI18nService,
provide: I18nService,
useValue: {
t: jest.fn().mockImplementation((t) => t),
},

View File

@@ -1,140 +0,0 @@
/*
* 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 { OnEvent } from '@nestjs/event-emitter';
import { ExtendedI18nService } from '@/extended-i18n.service';
import { SettingService } from '@/setting/services/setting.service';
import { BaseService } from '@/utils/generics/base-service';
import { BlockService } from './block.service';
import { TranslationRepository } from '../repositories/translation.repository';
import { Block } from '../schemas/block.schema';
import { Translation } from '../schemas/translation.schema';
@Injectable()
export class TranslationService extends BaseService<Translation> {
constructor(
readonly repository: TranslationRepository,
private readonly blockService: BlockService,
private readonly settingService: SettingService,
private readonly i18n: ExtendedI18nService,
) {
super(repository);
this.resetI18nTranslations();
}
public async resetI18nTranslations() {
const translations = await this.findAll();
this.i18n.initDynamicTranslations(translations);
}
/**
* Return any available string inside a given block (message, button titles, fallback messages, ...)
*
* @param block - The block to parse
*
* @returns An array of strings
*/
getBlockStrings(block: Block): string[] {
let strings: string[] = [];
if (Array.isArray(block.message)) {
// Text Messages
strings = strings.concat(block.message);
} else if (typeof block.message === 'object') {
if ('plugin' in block.message) {
// plugin
Object.values(block.message.args).forEach((arg) => {
if (Array.isArray(arg)) {
// array of text
strings = strings.concat(arg);
} else if (typeof arg === 'string') {
// text
strings.push(arg);
}
});
} else if ('text' in block.message && Array.isArray(block.message.text)) {
// array of text
strings = strings.concat(block.message.text);
} else if (
'text' in block.message &&
typeof block.message.text === 'string'
) {
// text
strings.push(block.message.text);
}
if (
'quickReplies' in block.message &&
Array.isArray(block.message.quickReplies) &&
block.message.quickReplies.length > 0
) {
// Quick replies
strings = strings.concat(
block.message.quickReplies.map((qr) => qr.title),
);
} else if (
'buttons' in block.message &&
Array.isArray(block.message.buttons) &&
block.message.buttons.length > 0
) {
// Buttons
strings = strings.concat(block.message.buttons.map((btn) => btn.title));
}
}
// Add fallback messages
if (
'fallback' in block.options &&
block.options.fallback &&
'message' in block.options.fallback &&
Array.isArray(block.options.fallback.message)
) {
strings = strings.concat(block.options.fallback.message);
}
return strings;
}
/**
* Return any available string inside a block (message, button titles, fallback messages, ...)
*
* @returns A promise of all strings available in a array
*/
async getAllBlockStrings(): Promise<string[]> {
const blocks = await this.blockService.find({});
if (blocks.length === 0) {
return [];
}
return blocks.reduce((acc, block) => {
const strings = this.getBlockStrings(block);
return acc.concat(strings);
}, [] as string[]);
}
/**
* Return any available strings in settings
*
* @returns A promise of all strings available in a array
*/
async getSettingStrings(): Promise<string[]> {
let strings: string[] = [];
const settings = await this.settingService.getSettings();
if (settings.chatbot_settings.global_fallback) {
strings = strings.concat(settings.chatbot_settings.fallback_message);
}
return strings;
}
/**
* Updates the in-memory translations
*/
@OnEvent('hook:translation:*')
handleTranslationsUpdate() {
this.resetI18nTranslations();
}
}