Skip to content

Commit f9a9abf

Browse files
committed
Added option to use offline translation.
1 parent c62a0a5 commit f9a9abf

22 files changed

Lines changed: 406 additions & 5 deletions

File tree

‎TMessagesProj/build.gradle‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,10 @@ android {
121121
}
122122
}
123123

124+
buildFeatures {
125+
aidl true
126+
}
127+
124128
lintOptions {
125129
disable 'MissingTranslation'
126130
disable 'ExtraTranslation'

‎TMessagesProj/src/main/AndroidManifest.xml‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,9 @@
9090
<action android:name="android.intent.action.VIEW"/>
9191
<data android:scheme="ton"/>
9292
</intent>
93+
<intent>
94+
<action android:name="dev.davidv.translator.ITranslationService"/>
95+
</intent>
9396
</queries>
9497

9598
<application
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package dev.davidv.translator;
2+
3+
enum ErrorType {
4+
COULD_NOT_DETECT_LANGUAGE,
5+
DETECTED_BUT_UNAVAILABLE,
6+
UNEXPECTED,
7+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package dev.davidv.translator;
2+
3+
import dev.davidv.translator.TranslationError;
4+
5+
oneway interface ITranslationCallback {
6+
void onTranslationResult(String translatedText);
7+
void onTranslationError(in TranslationError error);
8+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package dev.davidv.translator;
2+
3+
import dev.davidv.translator.ITranslationCallback;
4+
5+
interface ITranslationService {
6+
void translate(String textToTranslate, String fromLanguage, String toLanguage, ITranslationCallback callback);
7+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package dev.davidv.translator;
2+
3+
import dev.davidv.translator.ErrorType;
4+
5+
parcelable TranslationError {
6+
ErrorType type;
7+
@nullable String language;
8+
@nullable String message;
9+
}

‎TMessagesProj/src/main/java/org/telegram/messenger/TranslateController.java‎

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import org.telegram.ui.ActionBar.BaseFragment;
3535
import org.telegram.ui.Components.Bulletin;
3636
import org.telegram.ui.Components.BulletinFactory;
37+
import org.telegram.messenger.forkgram.ForkOfflineTranslate;
3738
import org.telegram.ui.Components.TranslateAlert2;
3839
import org.telegram.ui.LaunchActivity;
3940
import org.telegram.ui.PremiumPreviewFragment;
@@ -1092,7 +1093,29 @@ private void pushToTranslate(
10921093
}
10931094
}
10941095

1095-
final String method = getMessagesController().translationsAutoEnabled;
1096+
final String method = ForkOfflineTranslate.method(getMessagesController().translationsAutoEnabled);
1097+
if ("offline".equals(method)) {
1098+
final String toLanguage = pendingTranslation1.language;
1099+
for (int i = 0; i < pendingTranslation1.messageIds.size(); ++i) {
1100+
final int id = pendingTranslation1.messageIds.get(i);
1101+
final Utilities.Callback4<Boolean, Integer, TLRPC.TL_textWithEntities, String> _callback = pendingTranslation1.callbacks.get(i);
1102+
final String _text = pendingTranslation1.messageTexts.get(i).text;
1103+
TranslateAlert2.offlineTranslate(_text, null, toLanguage, (result, rateLimit) -> {
1104+
if (result != null) {
1105+
final TLRPC.TL_textWithEntities resultWithEntities = new TLRPC.TL_textWithEntities();
1106+
resultWithEntities.text = result;
1107+
_callback.run(isTranscription, id, resultWithEntities, toLanguage);
1108+
} else {
1109+
toggleTranslatingDialog(dialogId, false);
1110+
NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.showBulletin, Bulletin.TYPE_ERROR, getString(rateLimit ? R.string.TranslationFailedAlert1 : R.string.TranslationFailedAlert2));
1111+
}
1112+
synchronized (TranslateController.this) {
1113+
loadingTranslations.remove((Integer) id);
1114+
}
1115+
});
1116+
}
1117+
return;
1118+
}
10961119
if ("alternative".equals(method) || "system".equals(method)) {
10971120
final String toLanguage = pendingTranslation1.language;
10981121
for (int i = 0; i < pendingTranslation1.messageIds.size(); ++i) {
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
package org.telegram.messenger.forkgram
2+
3+
import android.content.ComponentName
4+
import android.content.Context
5+
import android.content.Intent
6+
import android.content.ServiceConnection
7+
import android.os.IBinder
8+
import android.os.Looper
9+
import dev.davidv.translator.ITranslationCallback
10+
import dev.davidv.translator.ITranslationService
11+
import dev.davidv.translator.TranslationError
12+
import org.telegram.messenger.ApplicationLoader
13+
import org.telegram.messenger.MessagesController
14+
import java.util.concurrent.CountDownLatch
15+
import java.util.concurrent.TimeUnit
16+
import java.util.concurrent.atomic.AtomicReference
17+
18+
object ForkOfflineTranslate {
19+
20+
const val PACKAGE = "dev.davidv.translator"
21+
const val FDROID_URL = "https://f-droid.org/packages/$PACKAGE/"
22+
private const val ACTION = "dev.davidv.translator.ITranslationService"
23+
private const val BIND_TIMEOUT_MS = 5000L
24+
private const val CALL_TIMEOUT_MS = 60000L
25+
26+
const val PROVIDER_DEFAULT = 0
27+
const val PROVIDER_ALTERNATIVE = 1
28+
const val PROVIDER_OFFLINE = 2
29+
30+
@JvmStatic
31+
fun provider(): Int =
32+
MessagesController.getGlobalMainSettings().getInt("translationProvider", PROVIDER_DEFAULT)
33+
34+
@JvmStatic
35+
fun method(serverMethod: String?): String? = when (provider()) {
36+
PROVIDER_ALTERNATIVE -> "alternative"
37+
PROVIDER_OFFLINE -> "offline"
38+
else -> serverMethod
39+
}
40+
41+
private val bindLock = Any()
42+
@Volatile private var service: ITranslationService? = null
43+
private var connection: ServiceConnection? = null
44+
private var bindLatch: CountDownLatch? = null
45+
46+
@JvmStatic
47+
fun isAvailable(): Boolean {
48+
val context = ApplicationLoader.applicationContext ?: return false
49+
val intent = Intent(ACTION).setPackage(PACKAGE)
50+
return context.packageManager.resolveService(intent, 0) != null
51+
}
52+
53+
@JvmStatic
54+
fun translate(text: String, fromLanguage: String?, toLanguage: String): String? {
55+
if (Looper.myLooper() == Looper.getMainLooper()) {
56+
return null
57+
}
58+
val bound = ensureService() ?: return null
59+
val resultRef = AtomicReference<String?>(null)
60+
val latch = CountDownLatch(1)
61+
val callback = object : ITranslationCallback.Stub() {
62+
override fun onTranslationResult(translatedText: String?) {
63+
resultRef.set(translatedText)
64+
latch.countDown()
65+
}
66+
67+
override fun onTranslationError(error: TranslationError?) {
68+
latch.countDown()
69+
}
70+
}
71+
try {
72+
bound.translate(text, fromLanguage ?: "", toLanguage, callback)
73+
} catch (e: Exception) {
74+
service = null
75+
return null
76+
}
77+
latch.await(CALL_TIMEOUT_MS, TimeUnit.MILLISECONDS)
78+
return resultRef.get()
79+
}
80+
81+
private fun ensureService(): ITranslationService? {
82+
service?.let { return it }
83+
val context = ApplicationLoader.applicationContext ?: return null
84+
synchronized(bindLock) {
85+
service?.let { return it }
86+
var latch = bindLatch
87+
if (connection == null) {
88+
latch = CountDownLatch(1)
89+
val awaiting = latch
90+
val conn = object : ServiceConnection {
91+
override fun onServiceConnected(name: ComponentName?, binder: IBinder?) {
92+
service = ITranslationService.Stub.asInterface(binder)
93+
awaiting.countDown()
94+
}
95+
96+
override fun onServiceDisconnected(name: ComponentName?) {
97+
service = null
98+
}
99+
}
100+
val intent = Intent(ACTION).setPackage(PACKAGE)
101+
val ok = try {
102+
context.bindService(intent, conn, Context.BIND_AUTO_CREATE)
103+
} catch (e: Exception) {
104+
false
105+
}
106+
if (!ok) {
107+
try {
108+
context.unbindService(conn)
109+
} catch (ignore: Exception) {
110+
}
111+
return null
112+
}
113+
connection = conn
114+
bindLatch = latch
115+
}
116+
latch?.await(BIND_TIMEOUT_MS, TimeUnit.MILLISECONDS)
117+
return service
118+
}
119+
}
120+
}

‎TMessagesProj/src/main/java/org/telegram/ui/Components/TranslateAlert2.java‎

Lines changed: 74 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@
6464
import org.telegram.messenger.TranslateController;
6565
import org.telegram.messenger.Utilities;
6666
import org.telegram.messenger.XiaomiUtilities;
67+
import org.telegram.messenger.forkgram.ForkOfflineTranslate;
6768
import org.telegram.messenger.forkgram.ForkTranslate;
6869
import org.telegram.tgnet.ConnectionsManager;
6970
import org.telegram.tgnet.TLRPC;
@@ -314,10 +315,16 @@ public void translate() {
314315
reqId = null;
315316
}
316317

317-
final String method = MessagesController.getInstance(currentAccount).translationsManualEnabled;
318-
if ("alternative".equalsIgnoreCase(method)) {
319-
translateAlt();
320-
return;
318+
final String method = ForkOfflineTranslate.method(MessagesController.getInstance(currentAccount).translationsManualEnabled);
319+
if (!reqSum) {
320+
if ("offline".equalsIgnoreCase(method)) {
321+
translateOffline();
322+
return;
323+
}
324+
if ("alternative".equalsIgnoreCase(method)) {
325+
translateAlt();
326+
return;
327+
}
321328
}/* else if ("system".equalsIgnoreCase(method)) {
322329
translateSystem();
323330
return;
@@ -491,6 +498,44 @@ private void translateAlt() {
491498
});
492499
}
493500

501+
private void translateOffline() {
502+
final String text = reqText == null ? "" : reqText.toString();
503+
String _fromLng = fromLanguage;
504+
if (_fromLng != null) {
505+
_fromLng = _fromLng.split("_")[0];
506+
}
507+
if ("nb".equals(_fromLng)) {
508+
_fromLng = "no";
509+
}
510+
final String fromLng = _fromLng;
511+
String _toLng = toLanguage;
512+
if (_toLng != null) {
513+
_toLng = _toLng.split("_")[0];
514+
}
515+
if ("nb".equals(_toLng)) {
516+
_toLng = "no";
517+
}
518+
final String toLng = _toLng;
519+
520+
offlineTranslate(text, fromLng, toLng, (res, rateLimit) -> {
521+
if (res != null) {
522+
firstTranslation = false;
523+
textView.setText(preprocessText(res));
524+
adapter.updateMainView(textViewContainer);
525+
} else {
526+
if (isDismissed()) return;
527+
if (firstTranslation) {
528+
dismiss();
529+
NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.showBulletin, Bulletin.TYPE_ERROR, LocaleController.getString(rateLimit ? R.string.TranslationFailedAlert1 : R.string.TranslationFailedAlert2));
530+
} else {
531+
BulletinFactory.of((FrameLayout) containerView, resourcesProvider).createErrorBulletin(LocaleController.getString(rateLimit ? R.string.TranslationFailedAlert1 : R.string.TranslationFailedAlert2)).show();
532+
headerView.toLanguageTextView.setText(languageName(toLanguage = prevToLanguage));
533+
adapter.updateMainView(textViewContainer);
534+
}
535+
}
536+
});
537+
}
538+
494539
private static int lastIndexOfSafe(String text, String target, int start, int end) {
495540
int idx = text.lastIndexOf(target, end - 1);
496541
return (idx >= start) ? idx : -1;
@@ -519,6 +564,31 @@ public static ArrayList<String> cut(String encodedText, int maxLength) {
519564
return result;
520565
}
521566

567+
public static void offlineTranslate(String text, String fromLng, String toLng, Utilities.Callback2<String, Boolean> done) {
568+
if (done == null) return;
569+
new Thread() {
570+
@Override
571+
public void run() {
572+
String result = null;
573+
try {
574+
result = ForkOfflineTranslate.translate(text, fromLng, toLng);
575+
} catch (Throwable t) {
576+
result = null;
577+
}
578+
if (result != null) {
579+
String out = result;
580+
if (text.length() > 0 && text.charAt(0) == '\n') {
581+
out = "\n" + out;
582+
}
583+
final String finalResult = out;
584+
AndroidUtilities.runOnUIThread(() -> done.run(finalResult, false));
585+
} else {
586+
alternativeTranslate(text, fromLng, toLng, done);
587+
}
588+
}
589+
}.start();
590+
}
591+
522592
public static void alternativeTranslate(String text, String fromLng, String toLng, Utilities.Callback2<String, Boolean> done) {
523593
if (done == null) return;
524594
if (fromLng == null) {

0 commit comments

Comments
 (0)