package org.telegram.messenger; import android.content.Context; import android.content.res.Resources; import android.icu.text.Collator; import android.text.TextUtils; import android.view.inputmethod.InputMethodInfo; import android.view.inputmethod.InputMethodManager; import android.view.inputmethod.InputMethodSubtype; import androidx.annotation.Nullable; import org.telegram.tgnet.TLRPC; import org.telegram.ui.Components.Bulletin; import org.telegram.ui.Components.TranslateAlert2; import org.telegram.ui.RestrictedLanguagesSelectActivity; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Locale; import java.util.Objects; import java.util.Set; public class TranslateController extends BaseController { public static final String UNKNOWN_LANGUAGE = "und"; private static final int REQUIRED_TOTAL_MESSAGES_CHECKED = 8; private static final float REQUIRED_PERCENTAGE_MESSAGES_TRANSLATABLE = .60F; private static final float REQUIRED_MIN_PERCENTAGE_MESSAGES_UNKNOWN = .65F; private static final int MAX_SYMBOLS_PER_REQUEST = 25000; private static final int MAX_MESSAGES_PER_REQUEST = 20; private static final int GROUPING_TRANSLATIONS_TIMEOUT = 200; private final Set translatingDialogs = new HashSet<>(); private final Set translatableDialogs = new HashSet<>(); private final HashMap translatableDialogMessages = new HashMap<>(); private final HashMap translateDialogLanguage = new HashMap<>(); private final HashMap detectedDialogLanguage = new HashMap<>(); private final HashMap> keptReplyMessageObjects = new HashMap<>(); private final Set hideTranslateDialogs = new HashSet<>(); class TranslatableDecision { Set certainlyTranslatable = new HashSet<>(); Set unknown = new HashSet<>(); Set certainlyNotTranslatable = new HashSet<>(); } private MessagesController messagesController; public TranslateController(MessagesController messagesController) { super(messagesController.currentAccount); this.messagesController = messagesController; AndroidUtilities.runOnUIThread(this::loadTranslatingDialogsCached, 150); } public boolean isFeatureAvailable() { return UserConfig.getInstance(currentAccount).isPremium() && isChatTranslateEnabled(); } public boolean isChatTranslateEnabled() { return MessagesController.getMainSettings(currentAccount).getBoolean("translate_chat_button", true); } public boolean isContextTranslateEnabled() { return MessagesController.getMainSettings(currentAccount).getBoolean("translate_button", MessagesController.getGlobalMainSettings().getBoolean("translate_button", false)); } public void setContextTranslateEnabled(boolean enable) { MessagesController.getMainSettings(currentAccount).edit().putBoolean("translate_button", enable).apply(); } public static boolean isTranslatable(MessageObject messageObject) { return ( messageObject != null && messageObject.messageOwner != null && !messageObject.isOutOwner() && ( messageObject.type == MessageObject.TYPE_TEXT || messageObject.type == MessageObject.TYPE_VIDEO || messageObject.type == MessageObject.TYPE_PHOTO || messageObject.type == MessageObject.TYPE_VOICE || messageObject.type == MessageObject.TYPE_FILE || messageObject.type == MessageObject.TYPE_MUSIC ) && !TextUtils.isEmpty(messageObject.messageOwner.message) ); } public boolean isDialogTranslatable(long dialogId) { return ( isFeatureAvailable() && !DialogObject.isEncryptedDialog(dialogId) && getUserConfig().getClientUserId() != dialogId && /* DialogObject.isChatDialog(dialogId) &&*/ translatableDialogs.contains(dialogId) ); } public boolean isTranslateDialogHidden(long dialogId) { if (hideTranslateDialogs.contains(dialogId)) { return true; } TLRPC.ChatFull chatFull = getMessagesController().getChatFull(-dialogId); if (chatFull != null) { return chatFull.translations_disabled; } TLRPC.UserFull userFull = getMessagesController().getUserFull(dialogId); if (userFull != null) { return userFull.translations_disabled; } return false; } public boolean isTranslatingDialog(long dialogId) { return isFeatureAvailable() && translatingDialogs.contains(dialogId); } public void toggleTranslatingDialog(long dialogId) { toggleTranslatingDialog(dialogId, !isTranslatingDialog(dialogId)); } public boolean toggleTranslatingDialog(long dialogId, boolean value) { boolean currentValue = isTranslatingDialog(dialogId), notified = false; if (value && !currentValue) { translatingDialogs.add(dialogId); NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.dialogTranslate, dialogId, true); notified = true; } else if (!value && currentValue) { translatingDialogs.remove((Long) dialogId); NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.dialogTranslate, dialogId, false); cancelTranslations(dialogId); notified = true; } saveTranslatingDialogsCache(); return notified; } private int hash(MessageObject messageObject) { if (messageObject == null) { return 0; } return Objects.hash(messageObject.getDialogId(), messageObject.getId()); } private String currentLanguage() { String lang = LocaleController.getInstance().getCurrentLocaleInfo().pluralLangCode; if (lang != null) { lang = lang.split("_")[0]; } return lang; } public String getDialogTranslateTo(long dialogId) { String lang = translateDialogLanguage.get(dialogId); if (lang == null) { lang = TranslateAlert2.getToLanguage(); if (lang == null || lang.equals(getDialogDetectedLanguage(dialogId))) { lang = currentLanguage(); } } if ("nb".equals(lang)) { lang = "no"; } return lang; } public void setDialogTranslateTo(long dialogId, String language) { if (TextUtils.equals(getDialogTranslateTo(dialogId), language)) { return; } boolean wasTranslating = isTranslatingDialog(dialogId); if (wasTranslating) { AndroidUtilities.runOnUIThread(() -> { synchronized (TranslateController.this) { translateDialogLanguage.put(dialogId, language); translatingDialogs.add(dialogId); saveTranslatingDialogsCache(); } NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.dialogTranslate, dialogId, true); }, 150); } else { synchronized (TranslateController.this) { translateDialogLanguage.put(dialogId, language); } } cancelTranslations(dialogId); synchronized (this) { translatingDialogs.remove(dialogId); } NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.dialogTranslate, dialogId, false); TranslateAlert2.setToLanguage(language); } public void updateDialogFull(long dialogId) { if (!isFeatureAvailable() || !isDialogTranslatable(dialogId)) { return; } final boolean wasHidden = hideTranslateDialogs.contains(dialogId); boolean hidden = false; TLRPC.ChatFull chatFull = getMessagesController().getChatFull(-dialogId); if (chatFull != null) { hidden = chatFull.translations_disabled; } else { TLRPC.UserFull userFull = getMessagesController().getUserFull(dialogId); if (userFull != null) { hidden = userFull.translations_disabled; } } synchronized (this) { if (hidden) { hideTranslateDialogs.add(dialogId); } else { hideTranslateDialogs.remove(dialogId); } } if (wasHidden != hidden) { saveTranslatingDialogsCache(); NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.dialogTranslate, dialogId, isTranslatingDialog(dialogId)); } } public void setHideTranslateDialog(long dialogId, boolean hide) { setHideTranslateDialog(dialogId, hide, false); } public void setHideTranslateDialog(long dialogId, boolean hide, boolean doNotNotify) { TLRPC.TL_messages_togglePeerTranslations req = new TLRPC.TL_messages_togglePeerTranslations(); req.peer = getMessagesController().getInputPeer(dialogId); req.disabled = hide; getConnectionsManager().sendRequest(req, null); TLRPC.ChatFull chatFull = getMessagesController().getChatFull(-dialogId); if (chatFull != null) { chatFull.translations_disabled = hide; getMessagesStorage().updateChatInfo(chatFull, true); } TLRPC.UserFull userFull = getMessagesController().getUserFull(dialogId); if (userFull != null) { userFull.translations_disabled = hide; getMessagesStorage().updateUserInfo(userFull, true); } synchronized (this) { if (hide) { hideTranslateDialogs.add(dialogId); } else { hideTranslateDialogs.remove(dialogId); } } saveTranslatingDialogsCache(); if (!doNotNotify) { NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.dialogTranslate, dialogId, isTranslatingDialog(dialogId)); } } private static List languagesOrder = Arrays.asList( "en", "ar", "zh", "fr", "de", "it", "ja", "ko", "pt", "ru", "es", "uk" ); private static List allLanguages = Arrays.asList( "af", "sq", "am", "ar", "hy", "az", "eu", "be", "bn", "bs", "bg", "ca", "ceb", "zh", "co", "hr", "cs", "da", "nl", "en", "eo", "et", "fi", "fr", "fy", "gl", "ka", "de", "el", "gu", "ht", "ha", "haw", "he", "iw", "hi", "hmn", "hu", "is", "ig", "id", "ga", "it", "ja", "jv", "kn", "kk", "km", "rw", "ko", "ku", "ky", "lo", "la", "lv", "lt", "lb", "mk", "mg", "ms", "ml", "mt", "mi", "mr", "mn", "my", "ne", "no", "ny", "or", "ps", "fa", "pl", "pt", "pa", "ro", "ru", "sm", "gd", "sr", "st", "sn", "sd", "si", "sk", "sl", "so", "es", "su", "sw", "sv", "tl", "tg", "ta", "tt", "te", "th", "tr", "tk", "uk", "ur", "ug", "uz", "vi", "cy", "xh", "yi", "yo", "zu" ); public static class Language { public String code; public String displayName; } public static ArrayList getLanguages() { ArrayList result = new ArrayList<>(); for (int i = 0; i < allLanguages.size(); ++i) { Language language = new Language(); language.code = allLanguages.get(i); language.displayName = TranslateAlert2.capitalFirst(TranslateAlert2.languageName(language.code)); if (language.displayName == null) { continue; } result.add(language); } if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) { Collator collator = Collator.getInstance(Locale.getDefault()); Collections.sort(result, (lng1, lng2) -> collator.compare(lng1.displayName, lng2.displayName)); } else { Collections.sort(result, Comparator.comparing(lng -> lng.displayName)); } return result; } private static LinkedHashSet suggestedLanguageCodes = null; public static void invalidateSuggestedLanguageCodes() { suggestedLanguageCodes = null; } public static void analyzeSuggestedLanguageCodes() { LinkedHashSet langs = new LinkedHashSet<>(); try { langs.add(LocaleController.getInstance().getCurrentLocaleInfo().pluralLangCode); } catch (Exception e1) { FileLog.e(e1); } try { langs.add(Resources.getSystem().getConfiguration().locale.getLanguage()); } catch (Exception e2) { FileLog.e(e2); } try { langs.addAll(RestrictedLanguagesSelectActivity.getRestrictedLanguages()); } catch (Exception e3) { FileLog.e(e3); } try { InputMethodManager imm = (InputMethodManager) ApplicationLoader.applicationContext.getSystemService(Context.INPUT_METHOD_SERVICE); List ims = imm.getEnabledInputMethodList(); for (InputMethodInfo method : ims) { List submethods = imm.getEnabledInputMethodSubtypeList(method, true); for (InputMethodSubtype submethod : submethods) { if ("keyboard".equals(submethod.getMode())) { String currentLocale = submethod.getLocale(); if (currentLocale != null && currentLocale.contains("_")) { currentLocale = currentLocale.split("_")[0]; } if (TranslateAlert2.languageName(currentLocale) != null) { langs.add(currentLocale); } } } } } catch (Exception e4) { FileLog.e(e4); } suggestedLanguageCodes = langs; } public static ArrayList getSuggestedLanguages(String except) { ArrayList result = new ArrayList<>(); if (suggestedLanguageCodes == null) { analyzeSuggestedLanguageCodes(); if (suggestedLanguageCodes == null) { return result; } } Iterator i = suggestedLanguageCodes.iterator(); while (i.hasNext()) { final String code = i.next(); if (TextUtils.equals(code, except) || "no".equals(except) && "nb".equals(code) || "nb".equals(except) && "no".equals(code)) { continue; } Language language = new Language(); language.code = code; language.displayName = TranslateAlert2.capitalFirst(TranslateAlert2.languageName(language.code)); if (language.displayName == null) { continue; } result.add(language); } return result; } public static ArrayList getLocales() { HashMap languages = LocaleController.getInstance().languagesDict; ArrayList locales = new ArrayList<>(languages.values()); for (int i = 0; i < locales.size(); ++i) { LocaleController.LocaleInfo locale = locales.get(i); if (locale == null || locale.shortName != null && locale.shortName.endsWith("_raw") || !"remote".equals(locale.pathToFile)) { locales.remove(i); i--; } } final LocaleController.LocaleInfo currentLocale = LocaleController.getInstance().getCurrentLocaleInfo(); Comparator comparator = (o, o2) -> { if (o == currentLocale) { return -1; } else if (o2 == currentLocale) { return 1; } final int index1 = languagesOrder.indexOf(o.pluralLangCode); final int index2 = languagesOrder.indexOf(o2.pluralLangCode); if (index1 >= 0 && index2 >= 0) { return index1 - index2; } else if (index1 >= 0) { return -1; } else if (index2 >= 0) { return 1; } if (o.serverIndex == o2.serverIndex) { return o.name.compareTo(o2.name); } if (o.serverIndex > o2.serverIndex) { return 1; } else if (o.serverIndex < o2.serverIndex) { return -1; } return 0; }; Collections.sort(locales, comparator); return locales; } public void checkRestrictedLanguagesUpdate() { synchronized (this) { translatableDialogMessages.clear(); ArrayList toNotify = new ArrayList<>(); HashSet languages = RestrictedLanguagesSelectActivity.getRestrictedLanguages(); for (long dialogId : translatableDialogs) { String language = detectedDialogLanguage.get(dialogId); if (language != null && languages.contains(language)) { cancelTranslations(dialogId); translatingDialogs.remove(dialogId); toNotify.add(dialogId); } } translatableDialogs.clear(); saveTranslatingDialogsCache(); for (long dialogId : toNotify) { NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.dialogTranslate, dialogId, false); } } } @Nullable public String getDialogDetectedLanguage(long dialogId) { return detectedDialogLanguage.get(dialogId); } public void checkTranslation(MessageObject messageObject, boolean onScreen) { checkTranslation(messageObject, onScreen, false); } private void checkTranslation(MessageObject messageObject, boolean onScreen, boolean keepReply) { if (!isFeatureAvailable()) { return; } if (messageObject == null || messageObject.messageOwner == null) { return; } long dialogId = messageObject.getDialogId(); if (!keepReply && messageObject.replyMessageObject != null) { checkTranslation(messageObject.replyMessageObject, onScreen, true); } if (!isTranslatable(messageObject)) { return; } if (!isTranslatingDialog(dialogId)) { checkLanguage(messageObject); return; } final String language = getDialogTranslateTo(dialogId); MessageObject potentialReplyMessageObject; if (!keepReply && (messageObject.messageOwner.translatedText == null || !language.equals(messageObject.messageOwner.translatedToLanguage)) && (potentialReplyMessageObject = findReplyMessageObject(dialogId, messageObject.getId())) != null) { messageObject.messageOwner.translatedToLanguage = potentialReplyMessageObject.messageOwner.translatedToLanguage; messageObject.messageOwner.translatedText = potentialReplyMessageObject.messageOwner.translatedText; messageObject = potentialReplyMessageObject; } if (onScreen && isTranslatingDialog(dialogId)) { final MessageObject finalMessageObject = messageObject; if (finalMessageObject.messageOwner.translatedText == null || !language.equals(finalMessageObject.messageOwner.translatedToLanguage)) { NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.messageTranslating, finalMessageObject); pushToTranslate(finalMessageObject, language, (text, lang) -> { finalMessageObject.messageOwner.translatedToLanguage = lang; finalMessageObject.messageOwner.translatedText = text; if (keepReply) { keepReplyMessage(finalMessageObject); } getMessagesStorage().updateMessageCustomParams(dialogId, finalMessageObject.messageOwner); NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.messageTranslated, finalMessageObject); ArrayList dialogMessages = messagesController.dialogMessage.get(dialogId); if (dialogMessages != null) { for (int i = 0; i < dialogMessages.size(); ++i) { MessageObject dialogMessage = dialogMessages.get(i); if (dialogMessage != null && dialogMessage.getId() == finalMessageObject.getId()) { dialogMessage.messageOwner.translatedToLanguage = lang; dialogMessage.messageOwner.translatedText = text; if (dialogMessage.updateTranslation()) { NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.updateInterfaces, 0); } break; } } } }); } else if (keepReply) { keepReplyMessage(messageObject); } } } public void invalidateTranslation(MessageObject messageObject) { if (!isFeatureAvailable()) { return; } if (messageObject == null || messageObject.messageOwner == null) { return; } final long dialogId = messageObject.getDialogId(); messageObject.messageOwner.translatedToLanguage = null; messageObject.messageOwner.translatedText = null; getMessagesStorage().updateMessageCustomParams(dialogId, messageObject.messageOwner); AndroidUtilities.runOnUIThread(() -> { NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.messageTranslated, messageObject, isTranslatingDialog(dialogId)); }); } public void checkDialogMessages(long dialogId) { if (!isFeatureAvailable()) { return; } getMessagesStorage().getStorageQueue().postRunnable(() -> { final ArrayList dialogMessages = messagesController.dialogMessage.get(dialogId); if (dialogMessages == null) { return; } ArrayList customProps = new ArrayList<>(); for (int i = 0; i < dialogMessages.size(); ++i) { MessageObject dialogMessage = dialogMessages.get(i); if (dialogMessage == null || dialogMessage.messageOwner == null) { customProps.add(null); continue; } customProps.add(getMessagesStorage().getMessageWithCustomParamsOnlyInternal(dialogMessage.getId(), dialogMessage.getDialogId())); } AndroidUtilities.runOnUIThread(() -> { boolean updated = false; for (int i = 0; i < Math.min(customProps.size(), dialogMessages.size()); ++i) { MessageObject dialogMessage = dialogMessages.get(i); TLRPC.Message props = customProps.get(i); if (dialogMessage == null || dialogMessage.messageOwner == null || props == null) { continue; } dialogMessage.messageOwner.translatedText = props.translatedText; dialogMessage.messageOwner.translatedToLanguage = props.translatedToLanguage; if (dialogMessage.updateTranslation(false)) { updated = true; } } if (updated) { NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.updateInterfaces, 0); } }); }); } public void cleanup() { cancelAllTranslations(); resetTranslatingDialogsCache(); translatingDialogs.clear(); translatableDialogs.clear(); translatableDialogMessages.clear(); translateDialogLanguage.clear(); detectedDialogLanguage.clear(); keptReplyMessageObjects.clear(); hideTranslateDialogs.clear(); loadingTranslations.clear(); } private ArrayList pendingLanguageChecks = new ArrayList<>(); private void checkLanguage(MessageObject messageObject) { if (!LanguageDetector.hasSupport()) { return; } if (!isTranslatable(messageObject) || messageObject.messageOwner == null || TextUtils.isEmpty(messageObject.messageOwner.message)) { return; } if (messageObject.messageOwner.originalLanguage != null) { checkDialogTranslatable(messageObject); return; } final long dialogId = messageObject.getDialogId(); final int hash = hash(messageObject); if (isDialogTranslatable(dialogId)) { return; } if (pendingLanguageChecks.contains(hash)) { return; } pendingLanguageChecks.add(hash); LanguageDetector.detectLanguage(messageObject.messageOwner.message, lng -> AndroidUtilities.runOnUIThread(() -> { String detectedLanguage = lng; if (detectedLanguage == null) { detectedLanguage = UNKNOWN_LANGUAGE; } messageObject.messageOwner.originalLanguage = detectedLanguage; getMessagesStorage().updateMessageCustomParams(dialogId, messageObject.messageOwner); pendingLanguageChecks.remove((Integer) hash); checkDialogTranslatable(messageObject); }), err -> AndroidUtilities.runOnUIThread(() -> { messageObject.messageOwner.originalLanguage = UNKNOWN_LANGUAGE; getMessagesStorage().updateMessageCustomParams(dialogId, messageObject.messageOwner); pendingLanguageChecks.remove((Integer) hash); })); } private void checkDialogTranslatable(MessageObject messageObject) { if (messageObject == null || messageObject.messageOwner == null) { return; } final long dialogId = messageObject.getDialogId(); TranslatableDecision translatableMessages = translatableDialogMessages.get(dialogId); if (translatableMessages == null) { translatableDialogMessages.put(dialogId, translatableMessages = new TranslatableDecision()); } final boolean isUnknown = isTranslatable(messageObject) && ( messageObject.messageOwner.originalLanguage == null || UNKNOWN_LANGUAGE.equals(messageObject.messageOwner.originalLanguage) ); final boolean translatable = ( isTranslatable(messageObject) && messageObject.messageOwner.originalLanguage != null && !UNKNOWN_LANGUAGE.equals(messageObject.messageOwner.originalLanguage) && !RestrictedLanguagesSelectActivity.getRestrictedLanguages().contains(messageObject.messageOwner.originalLanguage) && !TextUtils.equals(getDialogTranslateTo(dialogId), messageObject.messageOwner.originalLanguage) ); if (isUnknown) { translatableMessages.unknown.add(messageObject.getId()); } else { (translatable ? translatableMessages.certainlyTranslatable : translatableMessages.certainlyNotTranslatable).add(messageObject.getId()); } if (!isUnknown) { detectedDialogLanguage.put(dialogId, messageObject.messageOwner.originalLanguage); } final int translatableCount = translatableMessages.certainlyTranslatable.size(); final int unknownCount = translatableMessages.unknown.size(); final int notTranslatableCount = translatableMessages.certainlyNotTranslatable.size(); final int totalCount = translatableCount + unknownCount + notTranslatableCount; if ( totalCount >= REQUIRED_TOTAL_MESSAGES_CHECKED && (translatableCount / (float) (translatableCount + notTranslatableCount)) >= REQUIRED_PERCENTAGE_MESSAGES_TRANSLATABLE && (unknownCount / (float) totalCount) < REQUIRED_MIN_PERCENTAGE_MESSAGES_UNKNOWN ) { translatableDialogs.add(dialogId); translatableDialogMessages.remove((Long) dialogId); AndroidUtilities.runOnUIThread(() -> { NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.dialogIsTranslatable, dialogId); }, 450); } } private final Set loadingTranslations = new HashSet<>(); private final HashMap> pendingTranslations = new HashMap<>(); private static class PendingTranslation { Runnable runnable; ArrayList messageIds = new ArrayList<>(); ArrayList messageTexts = new ArrayList<>(); ArrayList> callbacks = new ArrayList<>(); String language; int symbolsCount; int reqId = -1; } private void pushToTranslate( MessageObject message, String language, Utilities.Callback2 callback ) { if (message == null || callback == null) { return; } long dialogId = message.getDialogId(); PendingTranslation pendingTranslation; synchronized (this) { ArrayList dialogPendingTranslations = pendingTranslations.get(dialogId); if (dialogPendingTranslations == null) { pendingTranslations.put(dialogId, dialogPendingTranslations = new ArrayList<>()); } if (dialogPendingTranslations.isEmpty()) { dialogPendingTranslations.add(pendingTranslation = new PendingTranslation()); } else { pendingTranslation = dialogPendingTranslations.get(dialogPendingTranslations.size() - 1); } if (pendingTranslation.messageIds.contains(message.getId())) { return; } int messageSymbolsCount = 0; if (message.messageOwner != null && message.messageOwner.message != null) { messageSymbolsCount = message.messageOwner.message.length(); } else if (message.caption != null) { messageSymbolsCount = message.caption.length(); } else if (message.messageText != null) { messageSymbolsCount = message.messageText.length(); } if (pendingTranslation.symbolsCount + messageSymbolsCount >= MAX_SYMBOLS_PER_REQUEST || pendingTranslation.messageIds.size() + 1 >= MAX_MESSAGES_PER_REQUEST) { dialogPendingTranslations.add(pendingTranslation = new PendingTranslation()); } if (pendingTranslation.runnable != null) { AndroidUtilities.cancelRunOnUIThread(pendingTranslation.runnable); } loadingTranslations.add(message.getId()); pendingTranslation.messageIds.add(message.getId()); TLRPC.TL_textWithEntities source = null; if (message.messageOwner != null) { source = new TLRPC.TL_textWithEntities(); source.text = message.messageOwner.message; source.entities = message.messageOwner.entities; } pendingTranslation.messageTexts.add(source); pendingTranslation.callbacks.add(callback); pendingTranslation.language = language; pendingTranslation.symbolsCount += messageSymbolsCount; final PendingTranslation pendingTranslation1 = pendingTranslation; pendingTranslation.runnable = () -> { synchronized (TranslateController.this) { ArrayList dialogPendingTranslations1 = pendingTranslations.get(dialogId); if (dialogPendingTranslations1 != null) { dialogPendingTranslations1.remove(pendingTranslation1); if (dialogPendingTranslations1.isEmpty()) { pendingTranslations.remove(dialogId); } } } TLRPC.TL_messages_translateText req = new TLRPC.TL_messages_translateText(); req.flags |= 1; req.peer = getMessagesController().getInputPeer(dialogId); req.id = pendingTranslation1.messageIds; req.to_lang = pendingTranslation1.language; final int reqId = getConnectionsManager().sendRequest(req, (res, err) -> AndroidUtilities.runOnUIThread(() -> { final ArrayList ids; final ArrayList> callbacks; final ArrayList texts; synchronized (TranslateController.this) { ids = pendingTranslation1.messageIds; callbacks = pendingTranslation1.callbacks; texts = pendingTranslation1.messageTexts; } if (res instanceof TLRPC.TL_messages_translateResult) { ArrayList translated = ((TLRPC.TL_messages_translateResult) res).result; final int count = Math.min(callbacks.size(), translated.size()); for (int i = 0; i < count; ++i) { callbacks.get(i).run(TranslateAlert2.preprocess(texts.get(i), translated.get(i)), pendingTranslation1.language); } } else if (err != null && "TO_LANG_INVALID".equals(err.text)) { toggleTranslatingDialog(dialogId, false); NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.showBulletin, Bulletin.TYPE_ERROR, LocaleController.getString("TranslationFailedAlert2", R.string.TranslationFailedAlert2)); } else { for (int i = 0; i < callbacks.size(); ++i) { callbacks.get(i).run(null, pendingTranslation1.language); } } synchronized (TranslateController.this) { for (int i = 0; i < ids.size(); ++i) { loadingTranslations.remove(ids.get(i)); } } })); synchronized (TranslateController.this) { pendingTranslation1.reqId = reqId; } }; AndroidUtilities.runOnUIThread(pendingTranslation.runnable, GROUPING_TRANSLATIONS_TIMEOUT); } } public boolean isTranslating(MessageObject messageObject) { synchronized (this) { return messageObject != null && isTranslatingDialog(messageObject.getDialogId()) && loadingTranslations.contains(messageObject.getId()); } } public boolean isTranslating(MessageObject messageObject, MessageObject.GroupedMessages group) { if (messageObject == null) { return false; } if (!isTranslatingDialog(messageObject.getDialogId())) { return false; } synchronized (this) { if (loadingTranslations.contains(messageObject.getId())) { return true; } if (group != null) { for (MessageObject message : group.messages) { if (loadingTranslations.contains(message.getId())) { return true; } } } } return false; } public void cancelAllTranslations() { synchronized (this) { for (ArrayList translations : pendingTranslations.values()) { if (translations != null) { for (PendingTranslation pendingTranslation : translations) { AndroidUtilities.cancelRunOnUIThread(pendingTranslation.runnable); if (pendingTranslation.reqId != -1) { getConnectionsManager().cancelRequest(pendingTranslation.reqId, true); for (Integer messageId : pendingTranslation.messageIds) { loadingTranslations.remove(messageId); } } } } } } } public void cancelTranslations(long dialogId) { synchronized (this) { ArrayList translations = pendingTranslations.get(dialogId); if (translations != null) { for (PendingTranslation pendingTranslation : translations) { AndroidUtilities.cancelRunOnUIThread(pendingTranslation.runnable); if (pendingTranslation.reqId != -1) { getConnectionsManager().cancelRequest(pendingTranslation.reqId, true); for (Integer messageId : pendingTranslation.messageIds) { loadingTranslations.remove(messageId); } } } pendingTranslations.remove((Long) dialogId); } } } private void keepReplyMessage(MessageObject messageObject) { if (messageObject == null) { return; } HashMap map = keptReplyMessageObjects.get(messageObject.getDialogId()); if (map == null) { keptReplyMessageObjects.put(messageObject.getDialogId(), map = new HashMap<>()); } map.put(messageObject.getId(), messageObject); } public MessageObject findReplyMessageObject(long dialogId, int messageId) { HashMap map = keptReplyMessageObjects.get(dialogId); if (map == null) { return null; } return map.get(messageId); } private void clearAllKeptReplyMessages(long dialogId) { keptReplyMessageObjects.remove(dialogId); } private void loadTranslatingDialogsCached() { if (!isFeatureAvailable()) { return; } String translatingDialogsCache = messagesController.getMainSettings().getString("translating_dialog_languages2", null); if (translatingDialogsCache == null) { return; } String[] dialogs = translatingDialogsCache.split(";"); HashSet restricted = RestrictedLanguagesSelectActivity.getRestrictedLanguages(); for (int i = 0; i < dialogs.length; ++i) { String[] keyval = dialogs[i].split("="); if (keyval.length < 2) { continue; } long did = Long.parseLong(keyval[0]); String[] langs = keyval[1].split(">"); if (langs.length != 2) { continue; } String from = langs[0], to = langs[1]; if ("null".equals(from)) from = null; if ("null".equals(to)) to = null; if (from != null) { detectedDialogLanguage.put(did, from); if (!restricted.contains(from)) { translatingDialogs.add(did); translatableDialogs.add(did); } if (to != null) { translateDialogLanguage.put(did, to); } } } Set hidden = messagesController.getMainSettings().getStringSet("hidden_translation_at", null); if (hidden != null) { Iterator i = hidden.iterator(); while (i.hasNext()) { try { hideTranslateDialogs.add(Long.parseLong(i.next())); } catch (Exception e) { FileLog.e(e); } } } } private void saveTranslatingDialogsCache() { StringBuilder langset = new StringBuilder(); Iterator i = translatingDialogs.iterator(); boolean first = true; while (i.hasNext()) { try { long did = i.next(); if (!first) { langset.append(";"); } if (first) { first = false; } String lang = detectedDialogLanguage.get(did); if (lang == null) { lang = "null"; } String tolang = getDialogTranslateTo(did); if (tolang == null) { tolang = "null"; } langset.append(did).append("=").append(lang).append(">").append(tolang); } catch (Exception e) {} } Set hidden = new HashSet<>(); i = hideTranslateDialogs.iterator(); while (i.hasNext()) { try { hidden.add("" + i.next()); } catch (Exception e) { FileLog.e(e); } } MessagesController.getMainSettings(currentAccount).edit().putString("translating_dialog_languages2", langset.toString()).putStringSet("hidden_translation_at", hidden).apply(); } private void resetTranslatingDialogsCache() { MessagesController.getMainSettings(currentAccount).edit().remove("translating_dialog_languages2").remove("hidden_translation_at").apply(); } }