From c908bca9dc7c1e8fb4a49eb4dc53c05add893293 Mon Sep 17 00:00:00 2001 From: M66B Date: Sun, 27 Jun 2021 14:50:54 +0200 Subject: [PATCH] Added translation of received messages --- .../eu/faircode/email/AdapterMessage.java | 95 +++++++++++++++++++ .../main/java/eu/faircode/email/DeepL.java | 64 ++++++++++--- .../eu/faircode/email/FragmentCompose.java | 10 +- app/src/main/res/layout/dialog_buttons.xml | 15 ++- app/src/main/res/layout/dialog_translate.xml | 58 +++++++++++ .../res/layout/include_message_navigation.xml | 14 ++- 6 files changed, 237 insertions(+), 19 deletions(-) create mode 100644 app/src/main/res/layout/dialog_translate.xml diff --git a/app/src/main/java/eu/faircode/email/AdapterMessage.java b/app/src/main/java/eu/faircode/email/AdapterMessage.java index c863a541b3..ff54973d08 100644 --- a/app/src/main/java/eu/faircode/email/AdapterMessage.java +++ b/app/src/main/java/eu/faircode/email/AdapterMessage.java @@ -304,6 +304,7 @@ public class AdapterMessage extends RecyclerView.Adapter IMAP_KEYWORDS_BLACKLIST = Collections.unmodifiableList(Arrays.asList( @@ -443,6 +444,7 @@ public class AdapterMessage extends RecyclerView.Adapter 0 || tos > 0) && !outbox ? View.VISIBLE : View.GONE); + ibTranslate.setVisibility(tools && button_translate && DeepL.isAvailable(context) && message.content ? View.VISIBLE : View.GONE); ibHide.setVisibility(tools && button_hide && !outbox ? View.VISIBLE : View.GONE); ibSeen.setVisibility(tools && button_seen && !outbox && seen ? View.VISIBLE : View.GONE); ibAnswer.setVisibility(!tools || outbox || (!expand_all && expand_one) ? View.GONE : View.VISIBLE); @@ -3187,6 +3198,13 @@ public class AdapterMessage extends RecyclerView.Adapter() { + @Override + protected void onPreExecute(Bundle args) { + pbWait.setVisibility(View.VISIBLE); + } + + @Override + protected void onPostExecute(Bundle args) { + pbWait.setVisibility(View.GONE); + } + + @Override + protected DeepL.Translation onExecute(Context context, Bundle args) throws Throwable { + long id = args.getLong("id"); + + File file = EntityMessage.getFile(context, id); + Document d = JsoupEx.parse(file); + HtmlHelper.truncate(d, MAX_TRANSLATE); + + String text = d.text(); + String language = DeepL.getCurrentLanguage(context); + return DeepL.translate(text, language, context); + } + + @Override + protected void onExecuted(Bundle args, DeepL.Translation translation) { + tvTranslated.setText(translation.translated_text); + tvLanguage.setText(getString(R.string.title_from_to, + translation.detected_language, translation.target_language)); + } + + @Override + protected void onException(Bundle args, Throwable ex) { + tvTranslated.setText(ex.toString()); + } + }.execute(this, getArguments(), "message:translate"); + + AlertDialog.Builder builder = new AlertDialog.Builder(context) + .setView(view) + .setPositiveButton(android.R.string.ok, null); + + return builder.create(); + } + } + public static class FragmentDialogKeywordManage extends FragmentDialogBase { @NonNull @Override @@ -6931,6 +7020,7 @@ public class AdapterMessage extends RecyclerView.Adapter getTargetLanguages(Context context) { - try (InputStream is = context.getAssets().open("deepl.json")) { - String json = Helper.readStream(is); - JSONArray jarray = new JSONArray(json); + try { + ensureLanguages(context); String pkg = context.getPackageName(); SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); List languages = new ArrayList<>(); Map frequencies = new HashMap<>(); - for (int i = 0; i < jarray.length(); i++) { - JSONObject jlanguage = jarray.getJSONObject(i); + for (int i = 0; i < jlanguages.length(); i++) { + JSONObject jlanguage = jlanguages.getJSONObject(i); String name = jlanguage.getString("name"); String target = jlanguage.getString("language"); @@ -132,6 +133,36 @@ public class DeepL { } } + public static String getCurrentLanguage(Context context) { + try { + ensureLanguages(context); + + Locale locale = Locale.getDefault(); + for (int i = 0; i < jlanguages.length(); i++) { + JSONObject jlanguage = jlanguages.getJSONObject(i); + String language = jlanguage.getString("language"); + if (language.equalsIgnoreCase(locale.toLanguageTag()) || + language.equalsIgnoreCase(locale.getLanguage())) { + return language; + } + } + } catch (Throwable ex) { + Log.e(ex); + } + + return null; + } + + private static void ensureLanguages(Context context) throws IOException, JSONException { + if (jlanguages != null) + return; + + try (InputStream is = context.getAssets().open("deepl.json")) { + String json = Helper.readStream(is); + jlanguages = new JSONArray(json); + } + } + public static Pair getParagraph(EditText etBody) { int start = etBody.getSelectionStart(); int end = etBody.getSelectionEnd(); @@ -171,7 +202,7 @@ public class DeepL { return null; } - public static String translate(String text, String target, Context context) throws IOException, JSONException { + public static Translation translate(String text, String target, Context context) throws IOException, JSONException { String request = "text=" + URLEncoder.encode(text, StandardCharsets.UTF_8.name()) + "&target_lang=" + URLEncoder.encode(target, StandardCharsets.UTF_8.name()); @@ -212,9 +243,12 @@ public class DeepL { if (jtranslations.length() == 0) throw new FileNotFoundException(); JSONObject jtranslation = (JSONObject) jtranslations.get(0); - String detected = jtranslation.getString("detected_source_language"); - String translated = jtranslation.getString("text"); - return translated; + + Translation result = new Translation(); + result.target_language = target; + result.detected_language = jtranslation.getString("detected_source_language"); + result.translated_text = jtranslation.getString("text"); + return result; } finally { connection.disconnect(); } @@ -273,6 +307,12 @@ public class DeepL { } } + public static class Translation { + public String detected_language; + public String target_language; + public String translated_text; + } + public static class FragmentDialogDeepL extends FragmentDialogBase { @NonNull @Override @@ -324,8 +364,8 @@ public class DeepL { @Override protected void onException(Bundle args, Throwable ex) { - if (BuildConfig.DEBUG) - Log.unexpectedError(getParentFragmentManager(), ex); + tvUsage.setText(Log.formatThrowable(ex, false)); + tvUsage.setVisibility(View.VISIBLE); } }.execute(this, new Bundle(), "deepl:usage"); } diff --git a/app/src/main/java/eu/faircode/email/FragmentCompose.java b/app/src/main/java/eu/faircode/email/FragmentCompose.java index 981741daf7..fd4e2c8a18 100644 --- a/app/src/main/java/eu/faircode/email/FragmentCompose.java +++ b/app/src/main/java/eu/faircode/email/FragmentCompose.java @@ -755,21 +755,21 @@ public class FragmentCompose extends FragmentBase { args.putString("target", target); args.putString("text", text); - new SimpleTask() { + new SimpleTask() { @Override protected void onPreExecute(Bundle args) { ToastEx.makeText(getContext(), R.string.title_translating, Toast.LENGTH_SHORT).show(); } @Override - protected String onExecute(Context context, Bundle args) throws Throwable { + protected DeepL.Translation onExecute(Context context, Bundle args) throws Throwable { String target = args.getString("target"); String text = args.getString("text"); return DeepL.translate(text, target, context); } @Override - protected void onExecuted(Bundle args, String translated) { + protected void onExecuted(Bundle args, DeepL.Translation translation) { if (paragraph.second > edit.length()) return; @@ -781,8 +781,8 @@ public class FragmentCompose extends FragmentBase { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); // Insert translated text - edit.insert(paragraph.second, "\n\n" + translated); - etBody.setSelection(paragraph.second + 2 + translated.length()); + edit.insert(paragraph.second, "\n\n" + translation.translated_text); + etBody.setSelection(paragraph.second + 2 + translation.translated_text.length()); boolean small = prefs.getBoolean("deepl_small", false); if (small) { diff --git a/app/src/main/res/layout/dialog_buttons.xml b/app/src/main/res/layout/dialog_buttons.xml index 84eab5ad53..a3d0f54fb6 100644 --- a/app/src/main/res/layout/dialog_buttons.xml +++ b/app/src/main/res/layout/dialog_buttons.xml @@ -174,6 +174,19 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/cbSearch" /> + + + app:layout_constraintTop_toBottomOf="@id/cbTranslate" /> + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/include_message_navigation.xml b/app/src/main/res/layout/include_message_navigation.xml index ec16e5bec6..196a3b35a1 100644 --- a/app/src/main/res/layout/include_message_navigation.xml +++ b/app/src/main/res/layout/include_message_navigation.xml @@ -42,7 +42,7 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginEnd="3dp" - app:constraint_referenced_ids="ibMore,ibInbox,ibJunk,ibTrash,ibArchive,ibMove,ibCopy,ibKeywords,ibLabels,ibNotes,ibAnswer,ibSeen,ibHide,ibSearch,ibSearchText,ibEvent,ibShare,ibPin,ibPrint,ibHeaders,ibUnsubscribe,ibRule,ibUndo" + app:constraint_referenced_ids="ibMore,ibInbox,ibJunk,ibTrash,ibArchive,ibMove,ibCopy,ibKeywords,ibLabels,ibNotes,ibAnswer,ibSeen,ibHide,ibTranslate,ibSearch,ibSearchText,ibEvent,ibShare,ibPin,ibPrint,ibHeaders,ibUnsubscribe,ibRule,ibUndo" app:flow_horizontalBias="0" app:flow_horizontalGap="3dp" app:flow_horizontalStyle="packed" @@ -201,6 +201,18 @@ app:srcCompat="@drawable/twotone_visibility_off_24" tools:ignore="MissingConstraints" /> + +