forked from RocketChat/Rocket.Chat
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[NEW] Translation via MS translate (RocketChat#16363)
* Translation: Microsoft translate as new provider * Translation: Display translation provider * Linting * Add missing translation * Don't expose translation API keys to client * Refactor MS translate: remove redundant code * More precision in MS translate API key setting * Apply suggestions from code review Co-Authored-By: Rodrigo Nascimento <[email protected]> * reset package-lock to upstream Co-authored-by: Rodrigo Nascimento <[email protected]>
- Loading branch information
Showing
8 changed files
with
204 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import { Logger } from '../../logger'; | ||
|
||
export const logger = new Logger('AutoTranslate', { | ||
sections: { | ||
google: 'Google', | ||
deepl: 'DeepL', | ||
microsoft: 'Microsoft', | ||
}, | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,164 @@ | ||
/** | ||
* @author Vigneshwaran Odayappan <[email protected]> | ||
*/ | ||
|
||
import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; | ||
import { HTTP } from 'meteor/http'; | ||
import _ from 'underscore'; | ||
|
||
import { TranslationProviderRegistry, AutoTranslate } from './autotranslate'; | ||
import { logger } from './logger'; | ||
import { settings } from '../../settings'; | ||
|
||
/** | ||
* Microsoft translation service provider class representation. | ||
* Encapsulates the service provider settings and information. | ||
* Provides languages supported by the service provider. | ||
* Resolves API call to service provider to resolve the translation request. | ||
* @class | ||
* @augments AutoTranslate | ||
*/ | ||
class MsAutoTranslate extends AutoTranslate { | ||
/** | ||
* setup api reference to Microsoft translate to be used as message translation provider. | ||
* @constructor | ||
*/ | ||
constructor() { | ||
super(); | ||
this.name = 'microsoft-translate'; | ||
this.apiEndPointUrl = 'https://api.cognitive.microsofttranslator.com/translate?api-version=3.0'; | ||
this.apiDetectText = 'https://api.cognitive.microsofttranslator.com/detect?api-version=3.0'; | ||
this.apiGetLanguages = 'https://api.cognitive.microsofttranslator.com/languages?api-version=3.0'; | ||
this.breakSentence = 'https://api.cognitive.microsofttranslator.com/breaksentence?api-version=3.0'; | ||
// Get the service provide API key. | ||
settings.get('AutoTranslate_MicrosoftAPIKey', (key, value) => { | ||
this.apiKey = value; | ||
}); | ||
} | ||
|
||
/** | ||
* Returns metadata information about the service provide | ||
* @private implements super abstract method. | ||
* @return {object} | ||
*/ | ||
_getProviderMetadata() { | ||
return { | ||
name: this.name, | ||
displayName: TAPi18n.__('AutoTranslate_Microsoft'), | ||
settings: this._getSettings(), | ||
}; | ||
} | ||
|
||
/** | ||
* Returns necessary settings information about the translation service provider. | ||
* @private implements super abstract method. | ||
* @return {object} | ||
*/ | ||
_getSettings() { | ||
return { | ||
apiKey: this.apiKey, | ||
apiEndPointUrl: this.apiEndPointUrl, | ||
}; | ||
} | ||
|
||
/** | ||
* Returns supported languages for translation by the active service provider. | ||
* Microsoft does not provide an endpoint yet to retrieve the supported languages. | ||
* So each supported languages are explicitly maintained. | ||
* @private implements super abstract method. | ||
* @param {string} target | ||
* @returns {object} code : value pair | ||
*/ | ||
getSupportedLanguages(target) { | ||
if (this.autoTranslateEnabled && this.apiKey) { | ||
if (this.supportedLanguages[target]) { | ||
return this.supportedLanguages[target]; | ||
} | ||
const languages = HTTP.get(this.apiGetLanguages); | ||
this.supportedLanguages[target] = Object.keys(languages.data.translation).map((language) => ({ | ||
language, | ||
name: languages.data.translation[language].name, | ||
})); | ||
return this.supportedLanguages[target || 'en']; | ||
} | ||
} | ||
|
||
/** | ||
* Re-use method for REST API consumption of MS translate. | ||
* @private | ||
* @param {object} message | ||
* @param {object} targetLanguages | ||
* @throws Communication Errors | ||
* @returns {object} translations: Translated messages for each language | ||
*/ | ||
_translate(data, targetLanguages) { | ||
let translations = {}; | ||
const supportedLanguages = this.getSupportedLanguages('en'); | ||
targetLanguages = targetLanguages.map((language) => { | ||
if (language.indexOf('-') !== -1 && !_.findWhere(supportedLanguages, { language })) { | ||
language = language.substr(0, 2); | ||
} | ||
return language; | ||
}); | ||
const url = `${ this.apiEndPointUrl }&to=${ targetLanguages.join('&to=') }`; | ||
const result = HTTP.post(url, { | ||
headers: { | ||
'Ocp-Apim-Subscription-Key': this.apiKey, | ||
'Content-Type': 'application/json; charset=UTF-8', | ||
}, | ||
data, | ||
}); | ||
|
||
if (result.statusCode === 200 && result.data && result.data.length > 0) { | ||
// store translation only when the source and target language are different. | ||
translations = Object.assign({}, ...targetLanguages.map((language) => | ||
({ | ||
[language]: result.data.map((line) => line.translations.find((translation) => translation.to === language).text).join('\n'), | ||
}), | ||
)); | ||
} | ||
|
||
return translations; | ||
} | ||
|
||
/** | ||
* Returns translated message for each target language. | ||
* @private | ||
* @param {object} message | ||
* @param {object} targetLanguages | ||
* @returns {object} translations: Translated messages for each language | ||
*/ | ||
_translateMessage(message, targetLanguages) { | ||
// There are multi-sentence-messages where multiple sentences come from different languages | ||
// This is a problem for translation services since the language detection fails. | ||
// Thus, we'll split the message in sentences, get them translated, and join them again after translation | ||
const msgs = message.msg.split('\n').map((msg) => ({ Text: msg })); | ||
try { | ||
return this._translate(msgs, targetLanguages); | ||
} catch (e) { | ||
logger.microsoft.error('Error translating message', e); | ||
} | ||
return {}; | ||
} | ||
|
||
/** | ||
* Returns translated message attachment description in target languages. | ||
* @private | ||
* @param {object} attachment | ||
* @param {object} targetLanguages | ||
* @returns {object} translated messages for each target language | ||
*/ | ||
_translateAttachmentDescriptions(attachment, targetLanguages) { | ||
try { | ||
return this._translate([{ | ||
Text: attachment.description || attachment.text, | ||
}], targetLanguages); | ||
} catch (e) { | ||
logger.microsoft.error('Error translating message attachment', e); | ||
} | ||
return {}; | ||
} | ||
} | ||
|
||
// Register Microsoft translation provider to the registry. | ||
TranslationProviderRegistry.registerProvider(new MsAutoTranslate()); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters