import type { IMessage, IRoom, MessageAttachment, IProviderMetadata, ISupportedLanguage, ITranslationResult, } from '@rocket.chat/core-typings'; import { Logger } from '@rocket.chat/logger'; import { Messages, Subscriptions } from '@rocket.chat/models'; import { escapeHTML, isTruthy } from '@rocket.chat/tools'; import { Meteor } from 'meteor/meteor'; import _ from 'underscore'; import { settings } from '../../settings'; import { callbacks } from '../callbacks'; import { Markdown } from '../messaging/markdown'; import { notifyOnMessageChange } from '../notifyListener'; const translationLogger = new Logger('AutoTranslate'); // Constant tokenizer patterns, compiled once instead of on every message. // Safe to share with /g flags because String.prototype.replace resets // lastIndex on each call. const urlSchemes = 'http,https'; const markdownLinkRegex = new RegExp(`(!?\\[)([^\\]]+)(\\]\\((?:${urlSchemes}):\\/\\/[^\\)]+\\))`, 'gm'); const pipedLinkRegex = new RegExp(`((?:<|<)(?:${urlSchemes}):\\/\\/[^\\|]+\\|)(.+?)(?=>|>)((?:>|>))`, 'gm'); const wrappedParagraphRegex = new RegExp('^\\s*
|
\\s*$', 'gm'); const Providers = Symbol('Providers'); const Provider = Symbol('Provider'); /** * This class allows translation providers to * register,load and also returns the active provider. */ export class TranslationProviderRegistry { static [Providers]: { [k: string]: AutoTranslate } = {}; static enabled = false; static [Provider]: string | null = null; /** * Registers the translation provider into the registry. * @param {*} provider */ static registerProvider(provider: AutoTranslate): void { // get provider information const metadata = provider._getProviderMetadata(); if (!metadata) { translationLogger.error('Provider metadata is not defined'); return; } TranslationProviderRegistry[Providers][metadata.name] = provider; } /** * Return the active Translation provider */ static getActiveProvider(): AutoTranslate | null { if (!TranslationProviderRegistry.enabled) { return null; } const provider = TranslationProviderRegistry[Provider]; if (!provider) { return null; } return TranslationProviderRegistry[Providers][provider]; } static async getSupportedLanguages(target: string): Promise - this is unnecessary and should be ignored with respect to translations
message.msg = message.msg.replace(wrappedParagraphRegex, '');
for (const [tokenIndex, value] of message.tokens?.entries() ?? []) {
const { token } = value;
if (token.indexOf('notranslate') === -1) {
const newToken = `{${count++}}`;
message.msg = message.msg.replace(token, newToken);
message.tokens ? (message.tokens[tokenIndex].token = newToken) : undefined;
}
}
return message;
}
tokenizeMentions(message: IMessage): IMessage {
let count = message.tokens?.length || 0;
if (message.mentions && message.mentions.length > 0) {
message.mentions.forEach((mention) => {
message.msg = message.msg.replace(new RegExp(`(@${mention.username})`, 'gm'), (match) => {
const token = `{${count++}}`;
message.tokens?.push({
token,
text: match,
});
return token;
});
});
}
if (message.channels && message.channels.length > 0) {
message.channels.forEach((channel) => {
message.msg = message.msg.replace(new RegExp(`(#${channel.name})`, 'gm'), (match) => {
const token = `{${count++}}`;
message.tokens?.push({
token,
text: match,
});
return token;
});
});
}
return message;
}
deTokenize(message: IMessage): string {
if (message.tokens && message.tokens?.length > 0) {
for (const { token, text, noHtml } of message.tokens) {
message.msg = message.msg.replace(token, () => noHtml || text);
}
}
return message.msg;
}
/**
* Triggers the translation of the prepared (tokenized) message
* and persists the result
* @public
* @param {object} message
* @param {object} room
* @param {object} targetLanguage
* @returns {object} unmodified message object.
*/
async translateMessage(message: IMessage, { room, targetLanguage }: { room: IRoom; targetLanguage?: string }): Promise