diff --git a/app/build.gradle b/app/build.gradle index 64655dd812..ee7b9ea17d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -50,14 +50,17 @@ android { } sourceSets { - github { - java.srcDirs = ['src/main/java', 'src/iab/java'] + githubDebug { + java.srcDirs = ['src/main/java', 'src/iab/java', 'src/xlat/java'] + } + githubRelease { + java.srcDirs = ['src/main/java', 'src/iab/java', 'src/stub/java'] } fdroid { - java.srcDirs = ['src/main/java', 'src/fdroid/java'] + java.srcDirs = ['src/main/java', 'src/fdroid/java', 'src/stub/java'] } play { - java.srcDirs = ['src/main/java', 'src/iab/java'] + java.srcDirs = ['src/main/java', 'src/iab/java', 'src/stub/java'] } } } @@ -247,6 +250,7 @@ dependencies { def exif_version = "1.3.0-alpha01" def biometric_version = "1.0.1" // https://issuetracker.google.com/issues/159983244 def textclassifier_version = "1.0.0-alpha03" + def mlkit_translate = "16.0.0" def billingclient_version = "3.0.0" def javamail_version = "1.6.5" def jsoup_version = "1.13.1" @@ -340,6 +344,9 @@ dependencies { // https://developer.android.com/jetpack/androidx/releases/textclassifier //implementation "androidx.textclassifier:textclassifier:$textclassifier_version" + // https://developers.google.com/ml-kit/language/translation/android + debugImplementation "com.google.mlkit:translate:$mlkit_translate" + // https://developer.android.com/google/play/billing/billing_library_releases_notes // https://android-developers.googleblog.com/2020/06/meet-google-play-billing-library.html githubImplementation "com.android.billingclient:billing:$billingclient_version" diff --git a/app/src/main/java/eu/faircode/email/FragmentCompose.java b/app/src/main/java/eu/faircode/email/FragmentCompose.java index a7bf8ded0f..4cedaaf512 100644 --- a/app/src/main/java/eu/faircode/email/FragmentCompose.java +++ b/app/src/main/java/eu/faircode/email/FragmentCompose.java @@ -278,9 +278,10 @@ public class FragmentCompose extends FragmentBase { private static final int REQUEST_COLOR = 10; private static final int REQUEST_CONTACT_GROUP = 11; private static final int REQUEST_ANSWER = 12; - private static final int REQUEST_LINK = 13; - private static final int REQUEST_DISCARD = 14; - private static final int REQUEST_SEND = 15; + private static final int REQUEST_TRANSLATE = 13; + private static final int REQUEST_LINK = 14; + private static final int REQUEST_DISCARD = 15; + private static final int REQUEST_SEND = 16; @Override public void onCreate(Bundle savedInstanceState) { @@ -1213,8 +1214,11 @@ public class FragmentCompose extends FragmentBase { menu.findItem(R.id.menu_contact_group).setEnabled( state == State.LOADED && hasPermission(Manifest.permission.READ_CONTACTS)); menu.findItem(R.id.menu_answer).setEnabled(state == State.LOADED); + menu.findItem(R.id.menu_translate).setEnabled(state == State.LOADED); menu.findItem(R.id.menu_clear).setEnabled(state == State.LOADED); + menu.findItem(R.id.menu_translate).setVisible(BuildConfig.DEBUG); + int colorEncrypt = Helper.resolveColor(getContext(), R.attr.colorEncrypt); ImageButton ib = (ImageButton) menu.findItem(R.id.menu_encrypt).getActionView(); ib.setEnabled(state == State.LOADED); @@ -1275,6 +1279,9 @@ public class FragmentCompose extends FragmentBase { case R.id.menu_answer: onMenuAnswer(); return true; + case R.id.menu_translate: + onMenuTranslate(); + return true; case R.id.menu_clear: StyleHelper.apply(R.id.menu_clear, etBody); return true; @@ -1444,6 +1451,20 @@ public class FragmentCompose extends FragmentBase { fragment.show(getParentFragmentManager(), "compose:answer"); } + private void onMenuTranslate() { + Bundle args = new Bundle(); + + CharSequence seq = (etBody.hasSelection() + ? etBody.getText().subSequence(etBody.getSelectionStart(), etBody.getSelectionEnd()) + : etBody.getText()); + args.putString("text", seq.toString()); + + FragmentDialogTranslate fragment = new FragmentDialogTranslate(); + fragment.setArguments(args); + fragment.setTargetFragment(this, REQUEST_TRANSLATE); + fragment.show(getParentFragmentManager(), "compose:answer"); + } + private boolean onActionStyle(int action) { Log.i("Style action=" + action); @@ -1752,6 +1773,10 @@ public class FragmentCompose extends FragmentBase { if (resultCode == RESULT_OK && data != null) onAnswerSelected(data.getBundleExtra("args")); break; + case REQUEST_TRANSLATE: + if (resultCode == RESULT_OK && data != null) + onTranslated(data.getBundleExtra("args")); + break; case REQUEST_COLOR: if (resultCode == RESULT_OK && data != null) onColorSelected(data.getBundleExtra("args")); @@ -2732,6 +2757,15 @@ public class FragmentCompose extends FragmentBase { etBody.getText().insert(etBody.getSelectionStart(), spanned); } + private void onTranslated(Bundle args) { + String translated = "\n" + args.getString("translated"); + + if (etBody.hasSelection()) + etBody.getEditableText().insert(etBody.getSelectionEnd(), translated); + else + etBody.getEditableText().append(translated); + } + private void onColorSelected(Bundle args) { int color = args.getInt("color"); int start = args.getInt("start"); diff --git a/app/src/main/res/menu/menu_compose.xml b/app/src/main/res/menu/menu_compose.xml index 227f6fb0f3..9113ffddff 100644 --- a/app/src/main/res/menu/menu_compose.xml +++ b/app/src/main/res/menu/menu_compose.xml @@ -50,6 +50,11 @@ android:title="@string/title_insert_template" app:showAsAction="never" /> + + Více Nevyžádané Přesunout - Kopírovat + Kopírovat Odhlásit odběr Vytvořit pravidlo Není spam diff --git a/app/src/main/res/values-da-rDK/strings.xml b/app/src/main/res/values-da-rDK/strings.xml index 335a725124..462b8032ce 100644 --- a/app/src/main/res/values-da-rDK/strings.xml +++ b/app/src/main/res/values-da-rDK/strings.xml @@ -663,7 +663,7 @@ Mere Spam Flyt - Kopiér + Kopiér Afmeld Opret regel Ikke spam diff --git a/app/src/main/res/values-de-rDE/strings.xml b/app/src/main/res/values-de-rDE/strings.xml index 61ea20b628..33dda46ff7 100644 --- a/app/src/main/res/values-de-rDE/strings.xml +++ b/app/src/main/res/values-de-rDE/strings.xml @@ -663,7 +663,7 @@ Protokolle, die dem Standard nicht entsprechen, wie „Microsoft Exchange Web Se Mehr Spam Verschieben - Kopieren + Kopieren Aus Liste abmelden Regel erstellen Kein Spam @@ -780,6 +780,7 @@ Protokolle, die dem Standard nicht entsprechen, wie „Microsoft Exchange Web Se Mediensymbolleiste Kontaktgruppe einfügen Vorlage einfügen + Übersetzen Als einfachen Text bearbeiten Als neu formatierten Text bearbeiten Öffentlichen Schlüssel auswählen diff --git a/app/src/main/res/values-es-rES/strings.xml b/app/src/main/res/values-es-rES/strings.xml index 44858d7ef4..35bdc9dbb4 100644 --- a/app/src/main/res/values-es-rES/strings.xml +++ b/app/src/main/res/values-es-rES/strings.xml @@ -664,7 +664,7 @@ Más Spam Mover - Copiar + Copiar Darse de baja Crear regla No es Spam diff --git a/app/src/main/res/values-fi-rFI/strings.xml b/app/src/main/res/values-fi-rFI/strings.xml index 72ca78a034..4f64098b93 100644 --- a/app/src/main/res/values-fi-rFI/strings.xml +++ b/app/src/main/res/values-fi-rFI/strings.xml @@ -6,6 +6,7 @@ Esimerkiksi viestit muotoillaan aina uudelleen vaarallisten elementtien poistamiseksi ja luettavuuden parantamiseksi, ja linkkien avaaminen pitää turvallisuuden takia erikseen vahvistaa. FairEmailia ei tueta tässä laitteessa, koska bugit Androidissa aiheuttavat kaatumisia Mene \'takaisin\' uudelleen poistuaksesi + Vastaanottaminen Lähettäminen Ilmoitukset Päivitykset @@ -75,6 +76,7 @@ Toimeton \'%1$s\' epäonnistui Palvelimen \'%1$s\' hälytys + Lähettäminen vastaanottajalle %1$s epäonnistui %1$s/%2$s %1$s (%2$s) %1$s +%2$d @@ -620,6 +622,7 @@ Tee viestistä kopio kansioon %1$s Re: %1$s Fwd: %1$s + Painikkeet Merkitse luetuksi Merkitse lukemattomaksi Piilota @@ -658,6 +661,9 @@ Lisää Siirrä roskapostikansioon Siirrä + Kopioi + Peru tilaus + Luo sääntö Ei roskaposti Siirrä kansioon … Siirrä kansioon … @@ -772,6 +778,7 @@ Mediatyökalupalkki Lisää yhteystietoryhmä Lisää viestipohja + Käännä Muokkaa pelkkänä tekstinä Muokkaa uudelleenmuotoiltuna tekstinä Valitse julkinen avain diff --git a/app/src/main/res/values-fr-rCA/strings.xml b/app/src/main/res/values-fr-rCA/strings.xml index 18d66ed360..96f90497cf 100644 --- a/app/src/main/res/values-fr-rCA/strings.xml +++ b/app/src/main/res/values-fr-rCA/strings.xml @@ -663,7 +663,7 @@ Plus Indésirables Déplacer - Copier + Copier Se désabonner Créer une règle Non indésirable @@ -780,6 +780,7 @@ Barre d’outils média Insérer un groupe de contacts Insérer un modèle + Traduire Modifier comme texte brut Modifier comme texte reformaté Sélectionner la clé publique diff --git a/app/src/main/res/values-fr-rFR/strings.xml b/app/src/main/res/values-fr-rFR/strings.xml index 3932eec86c..3adb225b38 100644 --- a/app/src/main/res/values-fr-rFR/strings.xml +++ b/app/src/main/res/values-fr-rFR/strings.xml @@ -663,7 +663,7 @@ Plus Spam Déplacer - Copier + Copier Se désabonner Créer une règle Non spam @@ -780,6 +780,7 @@ Barre d\'outils média Insérer un groupe de contacts Insérer un modèle + Traduire Modifier comme texte brut Modifier comme texte reformaté Sélectionner la clé publique diff --git a/app/src/main/res/values-it-rIT/strings.xml b/app/src/main/res/values-it-rIT/strings.xml index 2a8eba86ad..508f9a0beb 100644 --- a/app/src/main/res/values-it-rIT/strings.xml +++ b/app/src/main/res/values-it-rIT/strings.xml @@ -663,7 +663,7 @@ Altro Posta indesiderata Sposta - Copia + Copia Disiscriviti Crea una regola Non spam diff --git a/app/src/main/res/values-nl-rNL/strings.xml b/app/src/main/res/values-nl-rNL/strings.xml index b478674786..3482bbd231 100644 --- a/app/src/main/res/values-nl-rNL/strings.xml +++ b/app/src/main/res/values-nl-rNL/strings.xml @@ -662,7 +662,7 @@ Meer Spam Verplaatsen - Kopiëren + Kopiëren Uitschrijven Maak regel Geen spam @@ -779,6 +779,7 @@ Media werkbalk Contactgroep toevoegen Sjabloon invoegen + Vertalen Bewerk als platte tekst Bewerk als geformatteerde tekst Selecteer publieke sleutel diff --git a/app/src/main/res/values-pl-rPL/strings.xml b/app/src/main/res/values-pl-rPL/strings.xml index 8807371c51..faf96ab8c5 100644 --- a/app/src/main/res/values-pl-rPL/strings.xml +++ b/app/src/main/res/values-pl-rPL/strings.xml @@ -684,7 +684,7 @@ Więcej Spam Przenieś - Kopiuj + Kopiuj Anuluj subskrypcję Utwórz regułę To nie jest spam diff --git a/app/src/main/res/values-ru-rRU/strings.xml b/app/src/main/res/values-ru-rRU/strings.xml index f5ff9faab4..f2e4197343 100644 --- a/app/src/main/res/values-ru-rRU/strings.xml +++ b/app/src/main/res/values-ru-rRU/strings.xml @@ -685,7 +685,7 @@ Ещё Спам Переместить - Копировать + Копировать Отписаться Создать правило Не спам @@ -802,6 +802,7 @@ Панель медиа Добавить группу контактов Вставить шаблон + Перевод Редактировать как простой текст Редактировать как исходный текст Выбор публичного ключа diff --git a/app/src/main/res/values-sv-rSE/strings.xml b/app/src/main/res/values-sv-rSE/strings.xml index 16465ddab8..40c7e52ac0 100644 --- a/app/src/main/res/values-sv-rSE/strings.xml +++ b/app/src/main/res/values-sv-rSE/strings.xml @@ -662,7 +662,7 @@ Mer Skräppost Flytta - Kopiera + Kopiera Avbryt prenumeration Skapa regel Inte skräppost @@ -779,6 +779,7 @@ Verktygsfält för media Infoga kontaktgrupp Infoga mall + Översätt Redigera som oformaterad text Redigera som formaterad text Välj publik nyckel diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index b79837bfc2..009f541cca 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -651,7 +651,7 @@ 更多 垃圾邮件 移动 - 复制 + 复制 取消订阅 创建规则 不是垃圾邮件 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 61a2e4f5c6..4aea0d965d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -887,6 +887,7 @@ Media toolbar Insert contact group Insert template + Translate Edit as plain text Edit as reformatted text Select public key diff --git a/app/src/stub/java/eu/faircode/email/FragmentDialogTranslate.java b/app/src/stub/java/eu/faircode/email/FragmentDialogTranslate.java new file mode 100644 index 0000000000..1220748061 --- /dev/null +++ b/app/src/stub/java/eu/faircode/email/FragmentDialogTranslate.java @@ -0,0 +1,18 @@ +package eu.faircode.email; + +import android.app.AlertDialog; +import android.app.Dialog; +import android.os.Bundle; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +public class FragmentDialogTranslate extends FragmentDialogBase { + @NonNull + @Override + public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { + return new AlertDialog.Builder(getContext()) + .setTitle(R.string.title_translate) + .create(); + } +} diff --git a/app/src/xlat/java/eu/faircode/email/FragmentDialogTranslate.java b/app/src/xlat/java/eu/faircode/email/FragmentDialogTranslate.java new file mode 100644 index 0000000000..2afe0f4262 --- /dev/null +++ b/app/src/xlat/java/eu/faircode/email/FragmentDialogTranslate.java @@ -0,0 +1,90 @@ +package eu.faircode.email; + +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.DialogInterface; +import android.os.Bundle; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.google.android.gms.tasks.OnFailureListener; +import com.google.android.gms.tasks.OnSuccessListener; +import com.google.mlkit.common.model.DownloadConditions; +import com.google.mlkit.nl.translate.TranslateLanguage; +import com.google.mlkit.nl.translate.Translation; +import com.google.mlkit.nl.translate.Translator; +import com.google.mlkit.nl.translate.TranslatorOptions; + +import java.util.Locale; +import java.util.Map; +import java.util.TreeMap; + +import static android.app.Activity.RESULT_OK; + +public class FragmentDialogTranslate extends FragmentDialogBase { + @NonNull + @Override + public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { + final String text = getArguments().getString("text"); + + Map map = new TreeMap<>(); + for (String lc : TranslateLanguage.getAllLanguages()) + map.put(new Locale(lc).getDisplayLanguage(), lc); + + String[] items = map.keySet().toArray(new String[0]); + + return new AlertDialog.Builder(getContext()) + .setTitle(R.string.title_translate) + .setItems(items, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + String language = map.get(items[which]); + + TranslatorOptions options = new TranslatorOptions.Builder() + .setSourceLanguage(TranslateLanguage.ENGLISH) + .setTargetLanguage(language) + .build(); + Translator translator = Translation.getClient(options); + DownloadConditions conditions = new DownloadConditions.Builder() + .requireWifi() + .build(); + translator.downloadModelIfNeeded(conditions) + .addOnSuccessListener( + new OnSuccessListener() { + @Override + public void onSuccess(Void v) { + translator.translate(text) + .addOnSuccessListener( + new OnSuccessListener() { + @Override + public void onSuccess(@NonNull String translatedText) { + getArguments().putString("translated", translatedText); + sendResult(RESULT_OK); + } + }) + .addOnFailureListener( + new OnFailureListener() { + @Override + public void onFailure(@NonNull Exception ex) { + Log.unexpectedError(getParentFragmentManager(), ex); + } + }); + } + }) + .addOnFailureListener( + new OnFailureListener() { + @Override + public void onFailure(@NonNull Exception ex) { + Log.unexpectedError(getParentFragmentManager(), ex); + } + }); + } + }) + .create(); + } + + @Override + public void onDismiss(@NonNull DialogInterface dialog) { + } +}