import TUIChatEngine, { IMessageModel, TUIChatService, TUIStore, TUITranslateService, TUIUserService, } from '@tencentcloud/chat-uikit-engine'; import { IChatResponese, IUserProfile } from '../../../interface'; /** * three type for origin text to be translated * 1. image: small face text * 2. text: plain text * 3. mention: mention '@' */ interface ITextFace { type: 'face'; value: string; } interface ITextPlain { type: 'text'; value: string; } interface ITextAt { type: 'mention'; value: string; } export type TranslationTextType = ITextFace | ITextPlain | ITextAt; class Translator { public isUseCache = true; private translationCache = new Map(); private static instance: Translator | undefined = undefined; private constructor() {} static getInstance() { if (!Translator.instance) { Translator.instance = new Translator(); } return Translator.instance; } async get(message: IMessageModel): Promise { // step1: check in cache if translation exist if (this.isUseCache) { const cache = this.translationCache.get(message.ID); if (cache !== undefined) { return cache; } } // step2: get message model with prototype methods const currentMessage: IMessageModel = TUIStore.getMessageModel(message.ID); if (!currentMessage) { return []; } const { text } = currentMessage.getMessageContent() || {}; const textList: TranslationTextType[] = []; const splittingList = await this.getNickList(currentMessage); // step3: Categorize origin messages to 'plain text', 'face', 'mention' for (let i = 0; i < text.length; ++i) { const item = text[i]; if (item.name === 'img') { textList.push({ type: 'face', value: item.src }); continue; } const { transSplitingList, atNickList } = this.getSplitResult(item.text, splittingList); for (let j = 0; j < transSplitingList.length; ++j) { textList.push({ type: 'text', value: transSplitingList[j] }); if (j < atNickList.length) { textList.push({ type: 'mention', value: atNickList[j] }); } } } // step4: filter plain text to be translated const needTranslateTextIndex: number[] = []; const needTranslateText = textList.filter((item, index) => { if (item.type === 'text' && item.value.trim() !== '') { needTranslateTextIndex.push(index); return true; } return false; }).map(item => item.value); if (needTranslateText.length === 0) { this.translationCache.set(currentMessage.ID, textList); return textList; } // step5: get final translation result const translationResult = await this.getTranslationStandard(needTranslateText) as string[]; translationResult.forEach((item, index) => { textList[needTranslateTextIndex[index]].value = item; }); // step6: cache translation result this.translationCache.set(currentMessage.ID, textList); return textList; } /** * Clears the translation cache. */ clear() { this.translationCache.clear(); } disableCache() { this.isUseCache = false; } enableCache() { this.isUseCache = true; } private getTranslationStandard(originTextList: string[]): Promise { return new Promise((resolve, reject) => { TUIChatService.translateText({ sourceTextList: originTextList, sourceLanguage: 'auto', }) .then((response: IChatResponese<{ translatedTextList: string[] }>) => { const { data: { translatedTextList }, } = response; resolve(translatedTextList); }) .catch((error) => { reject(error); }); }); } /** * the nick list is used to split the text by @ + {nick or userID} * @param message * @returns e.g. ['@james', '@john'] */ private async getNickList(message: IMessageModel): Promise { const splittingList: string[] = []; const { atUserList = [] } = message; const atAllID: string = TUIChatEngine.TYPES.MSG_AT_ALL; if (atUserList.includes(atAllID)) { splittingList.push(`@${TUITranslateService.t('TUIChat.所有人')}`); } if (atUserList.length > 0) { const { data: userProfileList } = await TUIUserService.getUserProfile({ userIDList: atUserList }) as IChatResponese; userProfileList.forEach((user) => { const atNick = `@${user.nick || user.userID}`; splittingList.push(atNick); }); } return [...new Set(splittingList)]; } /** * Splits the given text into substrings based on the provided splitString array. * * @param {string} text - The text to be split. * @param {string[]} splitString - The array of strings to split the text by. * @return {{ transSplitingList: string[]; atNickList: string[] }} - An object containing two arrays: * - transSplitingList: An array of substrings extracted from the text. * - atNickList: An array of split strings that were found in the text. */ private getSplitResult(text: string, splitString: string[]): { transSplitingList: string[]; atNickList: string[] } { let searchStartPos = 0; const transSplitingList: string[] = []; const atNickList: string[] = []; while (searchStartPos < text.length) { const nextAtCharPos = text.indexOf('@', searchStartPos); if (nextAtCharPos === -1) { transSplitingList.push(text.substring(searchStartPos)); break; } let found = false; for (let i = 0; i < splitString.length; ++i) { const pos = text.indexOf(splitString[i], nextAtCharPos); if (pos !== -1 && pos === nextAtCharPos) { transSplitingList.push(text.substring(searchStartPos, pos)); atNickList.push(splitString[i]); searchStartPos = pos + splitString[i].length; found = true; break; } } if (!found) { transSplitingList.push(text.substring(searchStartPos)); break; } } return { transSplitingList, atNickList, }; } } export const translator = Translator.getInstance();