FairEmail/app/src/main/java/eu/faircode/email/DeepL.java

538 lines
22 KiB
Java
Raw Normal View History

2021-05-19 05:22:36 +00:00
package eu.faircode.email;
/*
This file is part of FairEmail.
FairEmail is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
FairEmail is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with FairEmail. If not, see <http://www.gnu.org/licenses/>.
2024-01-01 07:50:49 +00:00
Copyright 2018-2024 by Marcel Bokhorst (M66B)
2021-05-19 05:22:36 +00:00
*/
2021-05-24 06:51:13 +00:00
import android.app.Dialog;
2021-05-19 05:22:36 +00:00
import android.content.Context;
2021-05-24 06:51:13 +00:00
import android.content.DialogInterface;
2021-05-19 05:22:36 +00:00
import android.content.SharedPreferences;
2021-06-27 15:26:53 +00:00
import android.content.res.Resources;
2021-06-29 15:47:57 +00:00
import android.graphics.Paint;
import android.net.Uri;
2021-05-24 06:51:13 +00:00
import android.os.Bundle;
2022-04-06 17:35:53 +00:00
import android.text.SpannableStringBuilder;
2021-05-23 06:26:31 +00:00
import android.text.TextUtils;
2022-04-06 17:35:53 +00:00
import android.text.style.RelativeSizeSpan;
2021-05-24 06:51:13 +00:00
import android.view.LayoutInflater;
import android.view.View;
import android.widget.CheckBox;
import android.widget.CompoundButton;
2021-05-24 06:51:13 +00:00
import android.widget.ImageButton;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
2021-05-19 05:22:36 +00:00
import androidx.preference.PreferenceManager;
import com.google.android.material.textfield.TextInputLayout;
2021-05-19 05:22:36 +00:00
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
2022-04-06 17:35:53 +00:00
import org.jsoup.nodes.Document;
2022-04-07 10:57:18 +00:00
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
2021-05-19 05:22:36 +00:00
import java.io.IOException;
2021-05-21 09:45:39 +00:00
import java.io.InputStream;
2021-05-19 05:22:36 +00:00
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
2021-05-21 09:45:39 +00:00
import java.text.Collator;
import java.util.ArrayList;
2022-02-03 15:51:49 +00:00
import java.util.Calendar;
2021-05-21 09:45:39 +00:00
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
2022-02-03 11:47:52 +00:00
import java.util.Objects;
2021-05-19 05:22:36 +00:00
import javax.net.ssl.HttpsURLConnection;
public class DeepL {
2021-05-19 17:29:50 +00:00
// https://www.deepl.com/docs-api/
2022-12-14 21:37:19 +00:00
// https://github.com/DeepLcom/deepl-java
2021-06-27 12:50:54 +00:00
private static JSONArray jlanguages = null;
2021-05-19 05:22:36 +00:00
private static final int DEEPL_TIMEOUT = 20; // seconds
2021-07-28 11:05:10 +00:00
private static final String PLAN_URI = "https://www.deepl.com/pro-account/plan";
2022-07-22 07:58:00 +00:00
static final String PRIVACY_URI = "https://www.deepl.com/privacy/";
2021-05-19 05:22:36 +00:00
2021-05-19 17:29:50 +00:00
// curl https://api-free.deepl.com/v2/languages \
2021-06-27 12:50:54 +00:00
// -d auth_key=... \
2021-05-19 17:29:50 +00:00
// -d type=target
2021-05-24 06:51:13 +00:00
public static boolean isAvailable(Context context) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
2021-06-29 15:12:22 +00:00
return prefs.getBoolean("deepl_enabled", false);
2021-05-24 06:51:13 +00:00
}
public static boolean canTranslate(Context context) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
String deepl_key = prefs.getString("deepl_key", null);
return !TextUtils.isEmpty(deepl_key);
}
public static List<Language> getTargetLanguages(Context context, boolean favorites) {
2021-06-27 12:50:54 +00:00
try {
ensureLanguages(context);
2021-05-21 09:45:39 +00:00
2021-05-22 17:14:18 +00:00
String pkg = context.getPackageName();
2021-06-27 15:26:53 +00:00
Resources res = context.getResources();
2021-05-21 09:45:39 +00:00
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
2021-05-22 17:14:18 +00:00
List<Language> languages = new ArrayList<>();
2021-05-21 09:45:39 +00:00
Map<String, Integer> frequencies = new HashMap<>();
2021-06-27 12:50:54 +00:00
for (int i = 0; i < jlanguages.length(); i++) {
JSONObject jlanguage = jlanguages.getJSONObject(i);
2021-05-21 09:45:39 +00:00
String name = jlanguage.getString("name");
String target = jlanguage.getString("language");
2022-02-03 11:47:52 +00:00
boolean formality = jlanguage.optBoolean("supports_formality");
2021-05-21 09:45:39 +00:00
Locale locale = Locale.forLanguageTag(target);
if (locale != null)
name = locale.getDisplayName();
int frequency = prefs.getInt("translated_" + target, 0);
2023-07-11 11:08:54 +00:00
String flag;
if ("CS".equals(target))
flag = "CZ";
else if ("DA".equals(target))
flag = "DK";
else if ("EL".equals(target))
flag = "GR";
else if ("ET".equals(target))
flag = "EE";
else if ("JA".equals(target))
flag = "JP";
else if ("KO".equals(target))
flag = "KR";
else if ("NB".equals(target))
flag = "NO";
else if ("SL".equals(target))
flag = "SI";
else if ("SV".equals(target))
flag = "SE";
else if ("UK".equals(target))
flag = "UA";
else if ("ZH".equals(target))
flag = "CN";
else {
String[] t = target.split("-");
flag = t[t.length - 1];
}
String resname = "flag_" + flag.toLowerCase();
2021-06-27 15:26:53 +00:00
int resid = res.getIdentifier(resname, "drawable", pkg);
2021-05-22 17:14:18 +00:00
2022-02-03 11:47:52 +00:00
languages.add(new Language(name, target, formality,
resid == 0 ? null : resid,
2022-02-24 09:20:53 +00:00
favorites && frequency > 0, frequency));
2021-05-21 09:45:39 +00:00
frequencies.put(target, frequency);
}
Collator collator = Collator.getInstance(Locale.getDefault());
collator.setStrength(Collator.SECONDARY); // Case insensitive, process accents etc
2021-05-22 17:14:18 +00:00
Collections.sort(languages, new Comparator<Language>() {
2021-05-21 09:45:39 +00:00
@Override
2021-05-22 17:14:18 +00:00
public int compare(Language l1, Language l2) {
int freq1 = frequencies.get(l1.target);
int freq2 = frequencies.get(l2.target);
2021-05-21 09:45:39 +00:00
if (freq1 == freq2 || !favorites)
2021-05-22 17:14:18 +00:00
return collator.compare(l1.name, l2.name);
2021-05-21 09:45:39 +00:00
else
return -Integer.compare(freq1, freq2);
}
});
if (BuildConfig.DEBUG && TextHelper.canTransliterate())
languages.add(0, new Language(context.getString(R.string.title_advanced_notify_transliterate),
"transliterate", false, null, true, 0));
2021-05-21 09:45:39 +00:00
return languages;
} catch (Throwable ex) {
Log.e(ex);
return null;
}
}
2021-06-27 12:50:54 +00:00
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);
}
}
2022-04-06 17:35:53 +00:00
public static Translation translate(CharSequence text, boolean html, String target, Context context) throws IOException, JSONException {
2022-02-03 11:47:52 +00:00
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
2022-04-06 17:35:53 +00:00
boolean deepl_formal = prefs.getBoolean("deepl_formal", true);
boolean deepl_html = prefs.getBoolean("deepl_html", false);
return translate(text, html && deepl_html, target, deepl_formal, context);
2022-02-03 11:47:52 +00:00
}
2022-04-06 17:35:53 +00:00
public static Translation translate(CharSequence text, boolean html, String target, boolean formality, Context context) throws IOException, JSONException {
if ("transliterate".equals(target)) {
Locale detected = TextHelper.detectLanguage(context, text.toString());
String transliterated = TextHelper.transliterate(context, text.toString());
String language = Locale.getDefault().toLanguageTag();
return new Translation(detected == null ? language : detected.toLanguageTag(), language, transliterated);
}
2022-03-21 14:38:10 +00:00
if (!ConnectionHelper.getNetworkState(context).isConnected())
throw new IllegalArgumentException(context.getString(R.string.title_no_internet));
2022-04-06 17:35:53 +00:00
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
boolean small = prefs.getBoolean("deepl_small", false);
String key = prefs.getString("deepl_key", null);
String input;
if (html) {
SpannableStringBuilder ssb = new SpannableStringBuilderEx(text);
if (small)
for (RelativeSizeSpan span : ssb.getSpans(0, ssb.length(), RelativeSizeSpan.class))
if (span.getSizeChange() == HtmlHelper.FONT_SMALL)
ssb.removeSpan(span);
2022-04-07 10:57:18 +00:00
String h = HtmlHelper.toHtml(ssb, context);
Elements content = JsoupEx.parse(h).body().children();
Element last = (content.size() == 0 ? null : content.get(content.size() - 1));
if (last != null && "br".equals(last.tagName()))
content.remove(last);
input = content.outerHtml();
2022-04-06 17:35:53 +00:00
} else
input = text.toString();
2022-04-07 10:57:18 +00:00
Log.i("DeepL input=" + input.replaceAll("\\r?\\n", "|"));
2021-07-28 11:01:40 +00:00
// https://www.deepl.com/docs-api/translating-text/request/
2021-05-19 05:22:36 +00:00
String request =
2022-04-06 17:35:53 +00:00
"text=" + URLEncoder.encode(input, StandardCharsets.UTF_8.name()) +
2021-05-19 05:22:36 +00:00
"&target_lang=" + URLEncoder.encode(target, StandardCharsets.UTF_8.name());
2022-04-06 17:35:53 +00:00
// https://www.deepl.com/docs-api/handling-html-(beta)/
if (html)
request += "&tag_handling=html";
2022-02-03 11:47:52 +00:00
ensureLanguages(context);
for (int i = 0; i < jlanguages.length(); i++) {
JSONObject jlanguage = jlanguages.getJSONObject(i);
if (Objects.equals(target, jlanguage.getString("language"))) {
boolean supports_formality = jlanguage.optBoolean("supports_formality");
if (supports_formality)
request += "&formality=" + (formality ? "more" : "less");
break;
}
}
2022-12-12 18:45:16 +00:00
URL url = new URL(getBaseUri(key) + "translate");
2021-05-19 05:22:36 +00:00
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
connection.setRequestMethod("POST");
connection.setDoOutput(true);
connection.setReadTimeout(DEEPL_TIMEOUT * 1000);
connection.setConnectTimeout(DEEPL_TIMEOUT * 1000);
ConnectionHelper.setUserAgent(context, connection);
2022-12-12 18:45:16 +00:00
connection.setRequestProperty("Authorization", "DeepL-Auth-Key " + key);
2021-05-19 05:22:36 +00:00
connection.setRequestProperty("Accept", "*/*");
connection.setRequestProperty("Content-Length", Integer.toString(request.length()));
connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
connection.connect();
try {
connection.getOutputStream().write(request.getBytes());
int status = connection.getResponseCode();
if (status != HttpsURLConnection.HTTP_OK) {
2021-05-19 14:34:52 +00:00
String error = "Error " + status + ": " + connection.getResponseMessage();
2021-05-19 05:22:36 +00:00
try {
2021-08-14 07:23:01 +00:00
InputStream is = connection.getErrorStream();
if (is != null)
error += "\n" + Helper.readStream(is);
2021-05-19 05:22:36 +00:00
} catch (Throwable ex) {
Log.w(ex);
}
throw new IOException(error);
2021-05-19 05:22:36 +00:00
}
String response = Helper.readStream(connection.getInputStream());
JSONObject jroot = new JSONObject(response);
JSONArray jtranslations = jroot.getJSONArray("translations");
2021-05-19 15:58:09 +00:00
if (jtranslations.length() == 0)
2022-12-12 18:48:11 +00:00
throw new IOException();
2021-05-19 05:22:36 +00:00
JSONObject jtranslation = (JSONObject) jtranslations.get(0);
2021-06-27 12:50:54 +00:00
Translation result = new Translation();
result.target_language = target;
result.detected_language = jtranslation.getString("detected_source_language");
2022-04-06 17:35:53 +00:00
String output = jtranslation.getString("text");
2022-04-07 10:57:18 +00:00
Log.i("DeepL output=" + output.replaceAll("\\r?\\n", "|"));
2022-04-06 17:35:53 +00:00
if (html) {
2022-04-07 10:57:18 +00:00
Document document = JsoupEx.parse(output);
2022-04-06 17:35:53 +00:00
result.translated_text = HtmlHelper.fromDocument(context, document, null, null);
} else
result.translated_text = output;
2022-04-07 10:57:18 +00:00
Log.i("DeepL result=" + result.translated_text.toString().replaceAll("\\r?\\n", "|"));
2021-06-27 12:50:54 +00:00
return result;
2021-05-19 15:58:09 +00:00
} finally {
connection.disconnect();
}
}
public static Integer[] getUsage(Context context) throws IOException, JSONException {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
2021-05-19 16:15:48 +00:00
String key = prefs.getString("deepl_key", null);
2021-05-19 15:58:09 +00:00
2021-07-28 11:01:40 +00:00
// https://www.deepl.com/docs-api/other-functions/monitoring-usage/
2022-12-12 18:45:16 +00:00
URL url = new URL(getBaseUri(key) + "usage");
2021-05-19 15:58:09 +00:00
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
connection.setReadTimeout(DEEPL_TIMEOUT * 1000);
connection.setConnectTimeout(DEEPL_TIMEOUT * 1000);
ConnectionHelper.setUserAgent(context, connection);
2022-12-12 18:45:16 +00:00
connection.setRequestProperty("Authorization", "DeepL-Auth-Key " + key);
2021-05-19 15:58:09 +00:00
connection.connect();
try {
int status = connection.getResponseCode();
if (status != HttpsURLConnection.HTTP_OK) {
String error = "Error " + status + ": " + connection.getResponseMessage();
try {
2021-08-14 07:23:01 +00:00
InputStream is = connection.getErrorStream();
if (is != null)
error += "\n" + Helper.readStream(is);
2021-05-19 15:58:09 +00:00
} catch (Throwable ex) {
Log.w(ex);
}
2022-12-12 18:48:11 +00:00
throw new IOException(error);
2021-05-19 15:58:09 +00:00
}
String response = Helper.readStream(connection.getInputStream());
JSONObject jroot = new JSONObject(response);
int count = jroot.getInt("character_count");
int limit = jroot.getInt("character_limit");
return new Integer[]{count, limit};
2021-05-19 05:22:36 +00:00
} finally {
connection.disconnect();
}
}
2021-05-19 16:15:48 +00:00
2022-12-12 18:40:29 +00:00
private static String getBaseUri(String key) {
2023-11-10 08:30:11 +00:00
String domain = (key != null && key.endsWith(":fx") ? "api-free.deepl.com" : "api.deepl.com");
2021-05-19 16:15:48 +00:00
return "https://" + domain + "/v2/";
}
2021-05-22 17:14:18 +00:00
public static class Language {
public String name;
public String target;
2022-02-03 11:47:52 +00:00
public boolean formality;
2021-05-22 17:14:18 +00:00
public Integer icon;
public boolean favorite;
2022-02-24 09:20:53 +00:00
public int frequency;
2021-05-22 17:14:18 +00:00
2022-02-24 09:20:53 +00:00
private Language(String name, String target, boolean formality, Integer icon, boolean favorite, int frequency) {
2021-05-22 17:14:18 +00:00
this.name = name;
this.target = target;
2022-02-03 11:47:52 +00:00
this.formality = formality;
2021-05-22 17:14:18 +00:00
this.icon = icon;
2022-02-24 09:20:53 +00:00
this.favorite = favorite;
this.frequency = frequency;
2021-05-22 17:14:18 +00:00
}
2021-06-29 14:09:28 +00:00
@Override
public String toString() {
return name;
}
2021-05-22 17:14:18 +00:00
}
2021-05-24 06:51:13 +00:00
2021-06-27 12:50:54 +00:00
public static class Translation {
public String detected_language;
public String target_language;
2022-04-06 17:35:53 +00:00
public CharSequence translated_text;
Translation() {
}
Translation(String detected, String target, CharSequence text) {
this.detected_language = detected;
this.target_language = target;
this.translated_text = text;
}
2021-06-27 12:50:54 +00:00
}
2021-05-24 06:51:13 +00:00
public static class FragmentDialogDeepL extends FragmentDialogBase {
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
final Context context = getContext();
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
String key = prefs.getString("deepl_key", null);
2022-02-03 11:47:52 +00:00
boolean formal = prefs.getBoolean("deepl_formal", true);
2021-05-24 06:51:13 +00:00
boolean small = prefs.getBoolean("deepl_small", false);
boolean replace = prefs.getBoolean("deepl_replace", false);
boolean highlight = prefs.getBoolean("deepl_highlight", true);
2022-04-06 17:35:53 +00:00
boolean html = prefs.getBoolean("deepl_html", false);
2022-02-03 15:51:49 +00:00
int subscription = prefs.getInt("deepl_subscription", BuildConfig.DEBUG ? 17 : 0);
2021-05-24 06:51:13 +00:00
View view = LayoutInflater.from(context).inflate(R.layout.dialog_deepl, null);
final ImageButton ibInfo = view.findViewById(R.id.ibInfo);
final TextInputLayout tilKey = view.findViewById(R.id.tilKey);
2022-02-03 11:47:52 +00:00
final CheckBox cbFormal = view.findViewById(R.id.cbFormal);
final TextView tvFormal = view.findViewById(R.id.tvFormal);
2021-05-24 06:51:13 +00:00
final CheckBox cbSmall = view.findViewById(R.id.cbSmall);
final CheckBox cbReplace = view.findViewById(R.id.cbReplace);
final CheckBox cbHighlight = view.findViewById(R.id.cbHighlight);
2022-04-06 17:35:53 +00:00
final CheckBox cbHtml = view.findViewById(R.id.cbHtml);
2021-05-24 06:51:13 +00:00
final TextView tvUsage = view.findViewById(R.id.tvUsage);
2021-06-29 15:47:57 +00:00
final TextView tvPrivacy = view.findViewById(R.id.tvPrivacy);
2021-05-24 06:51:13 +00:00
ibInfo.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
2023-12-26 11:04:41 +00:00
Helper.viewFAQ(v.getContext(), 167);
2021-05-24 06:51:13 +00:00
}
});
cbSmall.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
cbReplace.setEnabled(!isChecked);
}
});
2021-07-28 11:05:10 +00:00
tvUsage.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Helper.view(view.getContext(), Uri.parse(PLAN_URI), true);
}
});
2021-06-29 15:47:57 +00:00
tvPrivacy.setPaintFlags(tvPrivacy.getPaintFlags() | Paint.UNDERLINE_TEXT_FLAG);
tvPrivacy.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Helper.view(v.getContext(), Uri.parse(PRIVACY_URI), false);
}
});
tilKey.getEditText().setText(key);
2022-02-03 11:47:52 +00:00
cbFormal.setChecked(formal);
try {
List<String> formals = new ArrayList<>();
for (Language lang : getTargetLanguages(context, false))
if (lang.formality)
formals.add(lang.name);
tvFormal.setText(TextUtils.join(", ", formals));
} catch (Throwable ex) {
tvFormal.setText(Log.formatThrowable(ex, false));
}
2021-05-24 06:51:13 +00:00
cbSmall.setChecked(small);
cbReplace.setChecked(replace);
cbReplace.setEnabled(!small);
cbHighlight.setChecked(highlight);
2022-04-06 17:35:53 +00:00
cbHtml.setChecked(html);
2021-05-24 06:51:13 +00:00
tvUsage.setVisibility(View.GONE);
2021-06-13 16:28:44 +00:00
if (!TextUtils.isEmpty(key)) {
2021-05-24 06:51:13 +00:00
Bundle args = new Bundle();
args.putString("key", key);
new SimpleTask<Integer[]>() {
@Override
protected Integer[] onExecute(Context context, Bundle args) throws Throwable {
return DeepL.getUsage(context);
}
@Override
protected void onExecuted(Bundle args, Integer[] usage) {
2022-02-03 15:51:49 +00:00
String used = getString(R.string.title_translate_usage,
2021-05-24 06:51:13 +00:00
Helper.humanReadableByteCount(usage[0]),
Helper.humanReadableByteCount(usage[1]),
2022-02-03 15:51:49 +00:00
Math.round(100f * usage[0] / usage[1]));
if (subscription > 0) {
Calendar next = Calendar.getInstance();
next.set(Calendar.MILLISECOND, 0);
next.set(Calendar.SECOND, 0);
next.set(Calendar.MINUTE, 0);
2022-12-23 13:10:14 +00:00
next.set(Calendar.HOUR_OF_DAY, 0);
2022-02-03 15:51:49 +00:00
long today = next.getTimeInMillis();
if (next.get(Calendar.DATE) > subscription)
next.add(Calendar.MONTH, 1);
next.set(Calendar.DATE, subscription);
int remaining = (int) ((next.getTimeInMillis() - today) / (24 * 3600 * 1000L));
if (remaining > 0)
used += " +" + remaining;
}
tvUsage.setText(used);
2021-05-24 06:51:13 +00:00
tvUsage.setVisibility(View.VISIBLE);
}
@Override
protected void onException(Bundle args, Throwable ex) {
2021-06-27 12:50:54 +00:00
tvUsage.setText(Log.formatThrowable(ex, false));
tvUsage.setVisibility(View.VISIBLE);
2021-05-24 06:51:13 +00:00
}
}.execute(this, new Bundle(), "deepl:usage");
}
return new AlertDialog.Builder(context)
.setView(view)
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
String key = tilKey.getEditText().getText().toString().trim();
2021-05-24 06:51:13 +00:00
SharedPreferences.Editor editor = prefs.edit();
if (TextUtils.isEmpty(key))
2021-06-13 16:28:44 +00:00
editor.remove("deepl_key");
else
2021-05-24 06:51:13 +00:00
editor.putString("deepl_key", key);
2022-02-03 11:47:52 +00:00
editor.putBoolean("deepl_formal", cbFormal.isChecked());
2021-05-24 06:51:13 +00:00
editor.putBoolean("deepl_small", cbSmall.isChecked());
editor.putBoolean("deepl_replace", cbReplace.isChecked());
editor.putBoolean("deepl_highlight", cbHighlight.isChecked());
2022-04-06 17:35:53 +00:00
editor.putBoolean("deepl_html", cbHtml.isChecked());
2021-05-24 06:51:13 +00:00
editor.apply();
}
})
.setNegativeButton(android.R.string.cancel, null)
.create();
}
}
2021-05-19 05:22:36 +00:00
}