🔀 Commit

improved translation picker
Commit35883b2d43fb275f85e35a95486d463f65045902
AuthorJabJab <noreply@xmpp.tel>
Date2026-07-17
Parent61f19a0a
commit 35883b2d43fb275f85e35a95486d463f65045902
Author: JabJab <noreply@xmpp.tel>
Date:   Fri Jul 17 12:25:49 2026 +0300

    improved translation picker
---
 build.gradle                                       |   2 +-
 .../ui/util/OutgoingTranslateLanguagePicker.java   | 165 ++++++++++++++++-----
 .../xmpp/jabjab/ui/util/TranslationSettings.java   |  31 ++++
 src/main/res/layout/dialog_language_search.xml     |  25 ++++
 src/main/res/values-ar/strings.xml                 |   9 +-
 src/main/res/values-cs/strings.xml                 |   9 +-
 src/main/res/values-da-rDK/strings.xml             |   9 +-
 src/main/res/values-de/strings.xml                 |   9 +-
 src/main/res/values-es/strings.xml                 |   9 +-
 src/main/res/values-et/strings.xml                 |   9 +-
 src/main/res/values-fi/strings.xml                 |   9 +-
 src/main/res/values-fr/strings.xml                 |   9 +-
 src/main/res/values-it/strings.xml                 |   9 +-
 src/main/res/values-ja/strings.xml                 |   9 +-
 src/main/res/values-nl/strings.xml                 |   9 +-
 src/main/res/values-pl/strings.xml                 |   9 +-
 src/main/res/values-pt-rBR/strings.xml             |   9 +-
 src/main/res/values-pt/strings.xml                 |   9 +-
 src/main/res/values-ro-rRO/strings.xml             |   9 +-
 src/main/res/values-ru/strings.xml                 |   9 +-
 src/main/res/values-sr/strings.xml                 |   9 +-
 src/main/res/values-sv/strings.xml                 |   9 +-
 src/main/res/values-zh-rCN/strings.xml             |   9 +-
 src/main/res/values-zh-rTW/strings.xml             |   9 +-
 24 files changed, 346 insertions(+), 57 deletions(-)

diff --git a/build.gradle b/build.gradle
index 2a2474e..a42434d 100644
--- a/build.gradle
+++ b/build.gradle
@@ -113,7 +113,7 @@ android {
 
     defaultConfig {
         minSdkVersion 23
-        versionCode 42292
+        versionCode 42294
         versionName "1.0.1"
         applicationId "tel.xmpp.jabjab"
         resValue "string", "applicationId", applicationId
diff --git a/src/main/java/tel/xmpp/jabjab/ui/util/OutgoingTranslateLanguagePicker.java b/src/main/java/tel/xmpp/jabjab/ui/util/OutgoingTranslateLanguagePicker.java
index cda4961..1b6c386 100644
--- a/src/main/java/tel/xmpp/jabjab/ui/util/OutgoingTranslateLanguagePicker.java
+++ b/src/main/java/tel/xmpp/jabjab/ui/util/OutgoingTranslateLanguagePicker.java
@@ -1,17 +1,25 @@
 package tel.xmpp.jabjab.ui.util;
 
 import android.app.Activity;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.view.View;
+import android.widget.ArrayAdapter;
+import android.widget.EditText;
+import android.widget.ListView;
 import com.google.android.material.dialog.MaterialAlertDialogBuilder;
 import java.util.ArrayList;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Locale;
+import java.util.Map;
 import java.util.concurrent.atomic.AtomicInteger;
 import tel.xmpp.jabjab.R;
 
 /**
  * Target-language picker for the compose-bar outgoing-translate icon. Shows the
  * confirmed-good-quality languages up front, with a "More…" row that expands into
- * the full ISO-639-1 language list rather than a hand-maintained one.
+ * a searchable full ISO-639-1 language list rather than a hand-maintained one.
  */
 public final class OutgoingTranslateLanguagePicker {
 
@@ -22,13 +30,7 @@ public final class OutgoingTranslateLanguagePicker {
                     new LangEntry("en", "English"),
                     new LangEntry("de", "Deutsch"),
                     new LangEntry("tr", "Türkçe"),
-                    new LangEntry("ar", "العربية"),
-                    new LangEntry("ru", "Русский"),
-                    new LangEntry("ja", "日本語"),
-                    new LangEntry("ko", "한국어"),
-                    new LangEntry("zh", "中文"),
-                    new LangEntry("nl", "Nederlands"),
-                    new LangEntry("fr", "Français"));
+                    new LangEntry("ar", "العربية"));
 
     public interface OnLanguageSelected {
         void onLanguageSelected(String langCode);
@@ -37,28 +39,80 @@ public final class OutgoingTranslateLanguagePicker {
     private OutgoingTranslateLanguagePicker() {}
 
     public static void show(final Activity activity, final OnLanguageSelected callback) {
-        final String[] names = new String[PRIMARY.size() + 1];
-        for (int i = 0; i < PRIMARY.size(); i++) {
-            names[i] = PRIMARY.get(i).displayName();
+        final List<LangEntry> shown = buildShownList(activity);
+        final String[] names = new String[shown.size() + 1];
+        for (int i = 0; i < shown.size(); i++) {
+            names[i] = shown.get(i).displayName();
         }
-        names[PRIMARY.size()] = activity.getString(R.string.translate_more_languages);
+        names[shown.size()] = activity.getString(R.string.translate_more_languages);
         final AtomicInteger selected = new AtomicInteger(0);
-        new MaterialAlertDialogBuilder(activity)
-                .setTitle(R.string.translate_choose_language)
-                .setSingleChoiceItems(names, 0, (dialog, which) -> selected.set(which))
-                .setNegativeButton(R.string.cancel, null)
-                .setPositiveButton(
-                        R.string.ok,
-                        (dialog, which) -> {
-                            final int idx = selected.get();
-                            if (idx < PRIMARY.size()) {
-                                callback.onLanguageSelected(PRIMARY.get(idx).code());
+        final var dialog =
+                new MaterialAlertDialogBuilder(activity)
+                        .setTitle(R.string.translate_choose_language)
+                        .setSingleChoiceItems(names, 0, (d, which) -> selected.set(which))
+                        .setNegativeButton(R.string.cancel, null)
+                        .setPositiveButton(
+                                R.string.ok,
+                                (d, which) -> {
+                                    final int idx = selected.get();
+                                    if (idx < shown.size()) {
+                                        callback.onLanguageSelected(shown.get(idx).code());
+                                    } else {
+                                        showExpandedPicker(activity, callback);
+                                    }
+                                })
+                        .create();
+        dialog.show();
+        // Long-press a primary-list row (not "More…") to remove it: the pinned custom
+        // entry gets unpinned, a hard-coded primary gets hidden going forward. Only
+        // reachable after .show() — the ListView doesn't exist until the dialog is shown.
+        dialog.getListView()
+                .setOnItemLongClickListener(
+                        (parent, view, position, id) -> {
+                            if (position >= shown.size()) return false; // "More…" itself
+                            final String code = shown.get(position).code();
+                            final var settings = TranslationSettings.getInstance(activity);
+                            if (code.equals(settings.getLastCustomLanguage())) {
+                                settings.setLastCustomLanguage(null);
                             } else {
-                                showExpandedPicker(activity, callback);
+                                settings.hidePrimaryLanguage(code);
                             }
-                        })
-                .create()
-                .show();
+                            dialog.dismiss();
+                            show(activity, callback); // reopen with the updated list
+                            return true;
+                        });
+    }
+
+    // Pins the last language picked from "More…" to the front, unless it's already
+    // one of the primaries (already visible, nothing to pin). Display name is
+    // resolved live rather than stored, so it stays correct across locale changes.
+    // Hidden primaries (long-pressed away) are filtered out here too.
+    private static List<LangEntry> buildShownList(final Activity activity) {
+        final var settings = TranslationSettings.getInstance(activity);
+        final String pinnedCode = settings.getLastCustomLanguage();
+        final var hidden = settings.getHiddenPrimaryLanguages();
+
+        final List<LangEntry> visiblePrimary = new ArrayList<>(PRIMARY.size());
+        for (final LangEntry entry : PRIMARY) {
+            if (!hidden.contains(entry.code())) visiblePrimary.add(entry);
+        }
+
+        if (pinnedCode == null || isPrimary(pinnedCode)) {
+            return visiblePrimary;
+        }
+        final String displayName =
+                new Locale(pinnedCode).getDisplayLanguage(Locale.getDefault());
+        final List<LangEntry> shown = new ArrayList<>(visiblePrimary.size() + 1);
+        shown.add(new LangEntry(pinnedCode, displayName));
+        shown.addAll(visiblePrimary);
+        return shown;
+    }
+
+    private static boolean isPrimary(final String code) {
+        for (final LangEntry entry : PRIMARY) {
+            if (entry.code().equals(code)) return true;
+        }
+        return false;
     }
 
     private static void showExpandedPicker(final Activity activity, final OnLanguageSelected callback) {
@@ -71,19 +125,58 @@ public final class OutgoingTranslateLanguagePicker {
             }
         }
         all.sort((a, b) -> a.displayName().compareToIgnoreCase(b.displayName()));
+
         final String[] names = new String[all.size()];
+        // LinkedHashMap: display names are effectively unique in practice, but even in the
+        // rare collision case this just keeps the first (alphabetically earliest) code —
+        // never crashes, never picks nothing.
+        final Map<String, String> nameToCode = new LinkedHashMap<>();
         for (int i = 0; i < all.size(); i++) {
             names[i] = all.get(i).displayName();
+            nameToCode.putIfAbsent(all.get(i).displayName(), all.get(i).code());
         }
-        final AtomicInteger selected = new AtomicInteger(0);
-        new MaterialAlertDialogBuilder(activity)
-                .setTitle(R.string.translate_choose_language)
-                .setSingleChoiceItems(names, 0, (dialog, which) -> selected.set(which))
-                .setNegativeButton(R.string.cancel, null)
-                .setPositiveButton(
-                        R.string.ok,
-                        (dialog, which) -> callback.onLanguageSelected(all.get(selected.get()).code()))
-                .create()
-                .show();
+
+        final View view =
+                activity.getLayoutInflater().inflate(R.layout.dialog_language_search, null, false);
+        final EditText searchInput = view.findViewById(R.id.search_input);
+        final ListView listView = view.findViewById(R.id.language_list);
+        final ArrayAdapter<String> adapter =
+                new ArrayAdapter<>(activity, android.R.layout.simple_list_item_1, names);
+        listView.setAdapter(adapter);
+
+        final var dialog =
+                new MaterialAlertDialogBuilder(activity)
+                        .setTitle(R.string.translate_choose_language)
+                        .setView(view)
+                        .setNegativeButton(R.string.cancel, null)
+                        .create();
+
+        listView.setOnItemClickListener(
+                (parent, itemView, position, id) -> {
+                    final String pickedName = (String) parent.getItemAtPosition(position);
+                    final String code = nameToCode.get(pickedName);
+                    if (code == null) return; // defensive; adapter/map always stay in sync
+                    if (!isPrimary(code)) {
+                        TranslationSettings.getInstance(activity).setLastCustomLanguage(code);
+                    }
+                    dialog.dismiss();
+                    callback.onLanguageSelected(code);
+                });
+
+        searchInput.addTextChangedListener(
+                new TextWatcher() {
+                    @Override
+                    public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
+
+                    @Override
+                    public void onTextChanged(CharSequence s, int start, int before, int count) {
+                        adapter.getFilter().filter(s);
+                    }
+
+                    @Override
+                    public void afterTextChanged(Editable s) {}
+                });
+
+        dialog.show();
     }
 }
diff --git a/src/main/java/tel/xmpp/jabjab/ui/util/TranslationSettings.java b/src/main/java/tel/xmpp/jabjab/ui/util/TranslationSettings.java
index 0273f9f..6c9090b 100644
--- a/src/main/java/tel/xmpp/jabjab/ui/util/TranslationSettings.java
+++ b/src/main/java/tel/xmpp/jabjab/ui/util/TranslationSettings.java
@@ -2,6 +2,10 @@ package tel.xmpp.jabjab.ui.util;
 
 import android.content.Context;
 import android.content.SharedPreferences;
+import androidx.annotation.Nullable;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
 
 /**
  * Shared by both the incoming (long-press) and outgoing (compose bar) translate
@@ -12,6 +16,8 @@ public class TranslationSettings {
 
     private static final String PREFS_NAME = "jabjab_translation";
     private static final String KEY_AUTO_FALLBACK_GOOGLE = "auto_fallback_google";
+    private static final String KEY_LAST_CUSTOM_LANGUAGE = "last_custom_language";
+    private static final String KEY_HIDDEN_PRIMARY_LANGUAGES = "hidden_primary_languages";
     private static TranslationSettings instance;
     private final SharedPreferences prefs;
 
@@ -35,4 +41,29 @@ public class TranslationSettings {
     public void setAutoFallbackToGoogleEnabled(final boolean enabled) {
         prefs.edit().putBoolean(KEY_AUTO_FALLBACK_GOOGLE, enabled).apply();
     }
+
+    /** Single slot (not a history): the last language picked from the outgoing-translate
+     * picker's "More…" list, pinned to the front of the primary screen next time. */
+    @Nullable
+    public String getLastCustomLanguage() {
+        return prefs.getString(KEY_LAST_CUSTOM_LANGUAGE, null);
+    }
+
+    public void setLastCustomLanguage(@Nullable final String langCode) {
+        prefs.edit().putString(KEY_LAST_CUSTOM_LANGUAGE, langCode).apply();
+    }
+
+    /** Codes long-pressed off the primary language-picker screen. Only ever grows via
+     * explicit user action; there's no UI to un-hide short of clearing app data, which
+     * matches "remove it" being a one-way action from the user's own framing. */
+    public Set<String> getHiddenPrimaryLanguages() {
+        return new HashSet<>(
+                prefs.getStringSet(KEY_HIDDEN_PRIMARY_LANGUAGES, Collections.emptySet()));
+    }
+
+    public void hidePrimaryLanguage(final String langCode) {
+        final Set<String> hidden = getHiddenPrimaryLanguages();
+        hidden.add(langCode);
+        prefs.edit().putStringSet(KEY_HIDDEN_PRIMARY_LANGUAGES, hidden).apply();
+    }
 }
diff --git a/src/main/res/layout/dialog_language_search.xml b/src/main/res/layout/dialog_language_search.xml
new file mode 100644
index 0000000..47bd1b5
--- /dev/null
+++ b/src/main/res/layout/dialog_language_search.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    android:paddingHorizontal="?dialogPreferredPadding"
+    android:paddingTop="8dp">
+
+    <EditText
+        android:id="@+id/search_input"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:hint="@string/search"
+        android:inputType="text"
+        android:imeOptions="flagNoExtractUi"
+        android:maxLines="1"
+        android:singleLine="true" />
+
+    <ListView
+        android:id="@+id/language_list"
+        android:layout_width="match_parent"
+        android:layout_height="400dp"
+        android:layout_marginTop="8dp" />
+
+</LinearLayout>
diff --git a/src/main/res/values-ar/strings.xml b/src/main/res/values-ar/strings.xml
index 6c4db96..5d71248 100644
--- a/src/main/res/values-ar/strings.xml
+++ b/src/main/res/values-ar/strings.xml
@@ -728,7 +728,14 @@
     <string name="story_video_too_long">الفيديو طويل جدًا للقصة (الحد الأقصى %1$d ثانية)</string>
     <string name="tap_to_open_in_telegram">اضغط للاستيراد أو الفتح في تيليجرام</string>
     <string name="translate_message">ترجمة</string>
-    <string name="translation_disclaimer">ترجمة آلية عبر ترجمة جوجل (مؤقتًا، حتى يصبح واجهة برمجة الترجمة الخاصة بـ xmpp.tel جاهزة)</string>
+    <string name="translation_disclaimer_private">عبر ترجمة Qwen3.6 الخاصة</string>
+    <string name="translation_disclaimer_google">عبر ترجمة جوجل العامة</string>
+    <string name="translation_fallback_title">استخدام ترجمة جوجل؟</string>
+    <string name="translation_fallback_explained">تعذّر الوصول إلى خدمة الترجمة الخاصة. سيؤدي اللجوء إلى ترجمة جوجل إلى إرسال نص هذه الرسالة إلى جوجل.</string>
+    <string name="translation_fallback_allow">السماح هذه المرة</string>
+    <string name="translation_do_not_ask_again">السماح دائمًا باستخدام ترجمة جوجل كبديل</string>
+    <string name="translate_choose_language">ترجمة إلى…</string>
+    <string name="translate_more_languages">المزيد…</string>
     <string name="translation_failed">فشلت الترجمة</string>
     <string name="translation_in_progress">جارٍ الترجمة…</string>
     <string name="translation_title">الترجمة</string>
diff --git a/src/main/res/values-cs/strings.xml b/src/main/res/values-cs/strings.xml
index 6fbf1f8..966f5f1 100644
--- a/src/main/res/values-cs/strings.xml
+++ b/src/main/res/values-cs/strings.xml
@@ -1429,7 +1429,14 @@
     <string name="translation_title">Překlad</string>
     <string name="translation_failed">Překlad se nezdařil</string>
     <string name="translation_in_progress">Překládání…</string>
-    <string name="translation_disclaimer">Strojově přeloženo přes Google Translate (dočasně, dokud nebude připraveno vlastní překladové API xmpp.tel)</string>
+    <string name="translation_disclaimer_private">prostřednictvím soukromého překladu Qwen3.6</string>
+    <string name="translation_disclaimer_google">prostřednictvím veřejného Překladače Google</string>
+    <string name="translation_fallback_title">Použít Překladač Google?</string>
+    <string name="translation_fallback_explained">Soukromou překladatelskou službu se nepodařilo kontaktovat. Použití Překladače Google jako zálohy odešle text této zprávy společnosti Google.</string>
+    <string name="translation_fallback_allow">Povolit tentokrát</string>
+    <string name="translation_do_not_ask_again">Vždy povolit záložní Překladač Google</string>
+    <string name="translate_choose_language">Přeložit do…</string>
+    <string name="translate_more_languages">Více…</string>
     <string name="story_caption_hint">Přidat popisek…</string>
     <string name="story_post_button">Publikovat</string>
 </resources>
diff --git a/src/main/res/values-da-rDK/strings.xml b/src/main/res/values-da-rDK/strings.xml
index 2e32a8a..59043c8 100644
--- a/src/main/res/values-da-rDK/strings.xml
+++ b/src/main/res/values-da-rDK/strings.xml
@@ -1398,7 +1398,14 @@
     <string name="translation_title">Oversættelse</string>
     <string name="translation_failed">Oversættelse mislykkedes</string>
     <string name="translation_in_progress">Oversætter…</string>
-    <string name="translation_disclaimer">Maskinoversat via Google Translate (midlertidigt, indtil xmpp.tel\'s egen oversættelses-API er klar)</string>
+    <string name="translation_disclaimer_private">via privat Qwen3.6-oversættelse</string>
+    <string name="translation_disclaimer_google">via offentlig Google Oversæt</string>
+    <string name="translation_fallback_title">Bruge Google Oversæt?</string>
+    <string name="translation_fallback_explained">Den private oversættelsestjeneste kunne ikke nås. Hvis du falder tilbage på Google Oversæt, sendes denne beskedtekst til Google.</string>
+    <string name="translation_fallback_allow">Tillad denne gang</string>
+    <string name="translation_do_not_ask_again">Tillad altid tilbagefald til Google Oversæt</string>
+    <string name="translate_choose_language">Oversæt til…</string>
+    <string name="translate_more_languages">Mere…</string>
     <string name="story_caption_hint">Tilføj en billedtekst…</string>
     <string name="story_post_button">Udgiv</string>
 </resources>
diff --git a/src/main/res/values-de/strings.xml b/src/main/res/values-de/strings.xml
index 2cd19a3..18f5616 100644
--- a/src/main/res/values-de/strings.xml
+++ b/src/main/res/values-de/strings.xml
@@ -1302,7 +1302,14 @@
     <string name="story_video_too_long">Video ist zu lang für eine Story (max. %1$d Sekunden)</string>
     <string name="tap_to_open_in_telegram">Tippen, um in Telegram zu importieren oder zu öffnen</string>
     <string name="translate_message">Übersetzen</string>
-    <string name="translation_disclaimer">Maschinell übersetzt über Google Übersetzer (vorübergehend, bis die eigene Übersetzungs-API von xmpp.tel bereit ist)</string>
+    <string name="translation_disclaimer_private">via privater Qwen3.6-Übersetzung</string>
+    <string name="translation_disclaimer_google">via öffentlichem Google Übersetzer</string>
+    <string name="translation_fallback_title">Google Übersetzer verwenden?</string>
+    <string name="translation_fallback_explained">Der private Übersetzungsdienst konnte nicht erreicht werden. Der Rückgriff auf Google Übersetzer sendet diesen Nachrichtentext an Google.</string>
+    <string name="translation_fallback_allow">Diesmal erlauben</string>
+    <string name="translation_do_not_ask_again">Rückgriff auf Google Übersetzer immer erlauben</string>
+    <string name="translate_choose_language">Übersetzen nach…</string>
+    <string name="translate_more_languages">Mehr…</string>
     <string name="translation_failed">Übersetzung fehlgeschlagen</string>
     <string name="translation_in_progress">Wird übersetzt…</string>
     <string name="translation_title">Übersetzung</string>
diff --git a/src/main/res/values-es/strings.xml b/src/main/res/values-es/strings.xml
index ef96696..9816d5d 100644
--- a/src/main/res/values-es/strings.xml
+++ b/src/main/res/values-es/strings.xml
@@ -1439,7 +1439,14 @@
     <string name="translation_title">Traducción</string>
     <string name="translation_failed">Error en la traducción</string>
     <string name="translation_in_progress">Traduciendo…</string>
-    <string name="translation_disclaimer">Traducido automáticamente con Google Translate (temporal, hasta que la API de traducción propia de xmpp.tel esté lista)</string>
+    <string name="translation_disclaimer_private">vía traducción privada de Qwen3.6</string>
+    <string name="translation_disclaimer_google">vía Google Translate (público)</string>
+    <string name="translation_fallback_title">¿Usar Google Translate?</string>
+    <string name="translation_fallback_explained">No se pudo contactar con el servicio de traducción privado. Recurrir a Google Translate enviará este texto del mensaje a Google.</string>
+    <string name="translation_fallback_allow">Permitir esta vez</string>
+    <string name="translation_do_not_ask_again">Permitir siempre el uso de Google Translate</string>
+    <string name="translate_choose_language">Traducir a…</string>
+    <string name="translate_more_languages">Más…</string>
     <string name="story_caption_hint">Añade un pie de foto…</string>
     <string name="story_post_button">Publicar</string>
 </resources>
diff --git a/src/main/res/values-et/strings.xml b/src/main/res/values-et/strings.xml
index d1da2aa..5656cd8 100644
--- a/src/main/res/values-et/strings.xml
+++ b/src/main/res/values-et/strings.xml
@@ -1440,7 +1440,14 @@
     <string name="translation_title">Tõlge</string>
     <string name="translation_failed">Tõlkimine nurjus</string>
     <string name="translation_in_progress">Tõlkimine…</string>
-    <string name="translation_disclaimer">Masintõlge Google Translate\'i kaudu (ajutine, kuni xmpp.tel oma tõlke-API on valmis)</string>
+    <string name="translation_disclaimer_private">privaatse Qwen3.6 tõlke kaudu</string>
+    <string name="translation_disclaimer_google">avaliku Google Tõlke kaudu</string>
+    <string name="translation_fallback_title">Kas kasutada Google Tõlget?</string>
+    <string name="translation_fallback_explained">Privaatse tõlketeenusega ei õnnestunud ühendust luua. Google Tõlke kasutamine varuvariandina saadab selle sõnumi teksti Google\'ile.</string>
+    <string name="translation_fallback_allow">Luba seekord</string>
+    <string name="translation_do_not_ask_again">Luba alati varuvariandina Google Tõlge</string>
+    <string name="translate_choose_language">Tõlgi keelde…</string>
+    <string name="translate_more_languages">Rohkem…</string>
     <string name="story_caption_hint">Lisa pealdis…</string>
     <string name="story_post_button">Avalda</string>
 </resources>
diff --git a/src/main/res/values-fi/strings.xml b/src/main/res/values-fi/strings.xml
index d2b71b8..9468b1c 100644
--- a/src/main/res/values-fi/strings.xml
+++ b/src/main/res/values-fi/strings.xml
@@ -1398,7 +1398,14 @@
     <string name="translation_title">Käännös</string>
     <string name="translation_failed">Kääntäminen epäonnistui</string>
     <string name="translation_in_progress">Käännetään…</string>
-    <string name="translation_disclaimer">Konekäännetty Google Translatella (väliaikaisesti, kunnes xmpp.telin oma käännös-API on valmis)</string>
+    <string name="translation_disclaimer_private">yksityisen Qwen3.6-käännöksen kautta</string>
+    <string name="translation_disclaimer_google">julkisen Google Kääntäjän kautta</string>
+    <string name="translation_fallback_title">Käytetäänkö Google Kääntäjää?</string>
+    <string name="translation_fallback_explained">Yksityiseen käännöspalveluun ei saatu yhteyttä. Google Kääntäjän käyttäminen varavaihtoehtona lähettää tämän viestin tekstin Googlelle.</string>
+    <string name="translation_fallback_allow">Salli tällä kertaa</string>
+    <string name="translation_do_not_ask_again">Salli aina varautuminen Google Kääntäjään</string>
+    <string name="translate_choose_language">Käännä kielelle…</string>
+    <string name="translate_more_languages">Lisää…</string>
     <string name="story_caption_hint">Lisää kuvateksti…</string>
     <string name="story_post_button">Julkaise</string>
 </resources>
diff --git a/src/main/res/values-fr/strings.xml b/src/main/res/values-fr/strings.xml
index a69a34d..e64e176 100644
--- a/src/main/res/values-fr/strings.xml
+++ b/src/main/res/values-fr/strings.xml
@@ -1431,7 +1431,14 @@
     <string name="translation_title">Traduction</string>
     <string name="translation_failed">Échec de la traduction</string>
     <string name="translation_in_progress">Traduction…</string>
-    <string name="translation_disclaimer">Traduit automatiquement via Google Translate (temporaire, jusqu\'à ce que l\'API de traduction propre à xmpp.tel soit prête)</string>
+    <string name="translation_disclaimer_private">via traduction privée Qwen3.6</string>
+    <string name="translation_disclaimer_google">via Google Traduction (public)</string>
+    <string name="translation_fallback_title">Utiliser Google Traduction ?</string>
+    <string name="translation_fallback_explained">Le service de traduction privé n\'a pas pu être contacté. Le recours à Google Traduction enverra ce texte de message à Google.</string>
+    <string name="translation_fallback_allow">Autoriser cette fois</string>
+    <string name="translation_do_not_ask_again">Toujours autoriser le recours à Google Traduction</string>
+    <string name="translate_choose_language">Traduire en…</string>
+    <string name="translate_more_languages">Plus…</string>
     <string name="story_caption_hint">Ajouter une légende…</string>
     <string name="story_post_button">Publier</string>
 </resources>
diff --git a/src/main/res/values-it/strings.xml b/src/main/res/values-it/strings.xml
index 7f11b9c..df1cb3d 100644
--- a/src/main/res/values-it/strings.xml
+++ b/src/main/res/values-it/strings.xml
@@ -1441,7 +1441,14 @@
     <string name="translation_title">Traduzione</string>
     <string name="translation_failed">Traduzione non riuscita</string>
     <string name="translation_in_progress">Traduzione in corso…</string>
-    <string name="translation_disclaimer">Tradotto automaticamente tramite Google Translate (temporaneo, finché l\'API di traduzione di xmpp.tel non sarà pronta)</string>
+    <string name="translation_disclaimer_private">tramite traduzione privata Qwen3.6</string>
+    <string name="translation_disclaimer_google">tramite Google Traduttore (pubblico)</string>
+    <string name="translation_fallback_title">Usare Google Traduttore?</string>
+    <string name="translation_fallback_explained">Il servizio di traduzione privato non è raggiungibile. Il ricorso a Google Traduttore invierà il testo di questo messaggio a Google.</string>
+    <string name="translation_fallback_allow">Consenti questa volta</string>
+    <string name="translation_do_not_ask_again">Consenti sempre il ricorso a Google Traduttore</string>
+    <string name="translate_choose_language">Traduci in…</string>
+    <string name="translate_more_languages">Altro…</string>
     <string name="story_caption_hint">Aggiungi una didascalia…</string>
     <string name="story_post_button">Pubblica</string>
 </resources>
diff --git a/src/main/res/values-ja/strings.xml b/src/main/res/values-ja/strings.xml
index e08cad3..161f1d9 100644
--- a/src/main/res/values-ja/strings.xml
+++ b/src/main/res/values-ja/strings.xml
@@ -1396,7 +1396,14 @@
     <string name="translation_title">翻訳</string>
     <string name="translation_failed">翻訳に失敗しました</string>
     <string name="translation_in_progress">翻訳中…</string>
-    <string name="translation_disclaimer">Google 翻訳による機械翻訳(xmpp.tel 独自の翻訳 API が準備できるまでの暫定対応)</string>
+    <string name="translation_disclaimer_private">プライベートQwen3.6翻訳経由</string>
+    <string name="translation_disclaimer_google">パブリックGoogle翻訳経由</string>
+    <string name="translation_fallback_title">Google翻訳を使用しますか?</string>
+    <string name="translation_fallback_explained">プライベート翻訳サービスに接続できませんでした。Google翻訳にフォールバックすると、このメッセージのテキストがGoogleに送信されます。</string>
+    <string name="translation_fallback_allow">今回のみ許可</string>
+    <string name="translation_do_not_ask_again">常にGoogle翻訳へのフォールバックを許可</string>
+    <string name="translate_choose_language">翻訳先…</string>
+    <string name="translate_more_languages">その他…</string>
     <string name="story_caption_hint">キャプションを追加…</string>
     <string name="story_post_button">投稿</string>
 </resources>
diff --git a/src/main/res/values-nl/strings.xml b/src/main/res/values-nl/strings.xml
index d9f37ac..cf92ce8 100644
--- a/src/main/res/values-nl/strings.xml
+++ b/src/main/res/values-nl/strings.xml
@@ -1416,7 +1416,14 @@
     <string name="translation_title">Vertaling</string>
     <string name="translation_failed">Vertaling mislukt</string>
     <string name="translation_in_progress">Vertalen…</string>
-    <string name="translation_disclaimer">Machinaal vertaald via Google Translate (tijdelijk, totdat de eigen vertaal-API van xmpp.tel klaar is)</string>
+    <string name="translation_disclaimer_private">via privé Qwen3.6-vertaling</string>
+    <string name="translation_disclaimer_google">via openbare Google Translate</string>
+    <string name="translation_fallback_title">Google Translate gebruiken?</string>
+    <string name="translation_fallback_explained">De privévertaalservice kon niet worden bereikt. Terugvallen op Google Translate stuurt deze berichttekst naar Google.</string>
+    <string name="translation_fallback_allow">Deze keer toestaan</string>
+    <string name="translation_do_not_ask_again">Terugvallen op Google Translate altijd toestaan</string>
+    <string name="translate_choose_language">Vertalen naar…</string>
+    <string name="translate_more_languages">Meer…</string>
     <string name="story_caption_hint">Bijschrift toevoegen…</string>
     <string name="story_post_button">Plaatsen</string>
 </resources>
diff --git a/src/main/res/values-pl/strings.xml b/src/main/res/values-pl/strings.xml
index d50ef90..91b348d 100644
--- a/src/main/res/values-pl/strings.xml
+++ b/src/main/res/values-pl/strings.xml
@@ -1454,7 +1454,14 @@
     <string name="translation_title">Tłumaczenie</string>
     <string name="translation_failed">Tłumaczenie nie powiodło się</string>
     <string name="translation_in_progress">Tłumaczenie…</string>
-    <string name="translation_disclaimer">Przetłumaczone maszynowo przez Tłumacz Google (tymczasowo, do czasu udostępnienia własnego API tłumaczeń xmpp.tel)</string>
+    <string name="translation_disclaimer_private">przez prywatne tłumaczenie Qwen3.6</string>
+    <string name="translation_disclaimer_google">przez publiczny Tłumacz Google</string>
+    <string name="translation_fallback_title">Użyć Tłumacza Google?</string>
+    <string name="translation_fallback_explained">Nie udało się połączyć z prywatną usługą tłumaczenia. Skorzystanie z Tłumacza Google wyśle tekst tej wiadomości do Google.</string>
+    <string name="translation_fallback_allow">Zezwól tym razem</string>
+    <string name="translation_do_not_ask_again">Zawsze zezwalaj na korzystanie z Tłumacza Google</string>
+    <string name="translate_choose_language">Przetłumacz na…</string>
+    <string name="translate_more_languages">Więcej…</string>
     <string name="story_caption_hint">Dodaj podpis…</string>
     <string name="story_post_button">Opublikuj</string>
 </resources>
diff --git a/src/main/res/values-pt-rBR/strings.xml b/src/main/res/values-pt-rBR/strings.xml
index 2a9fbd3..ee61ad8 100644
--- a/src/main/res/values-pt-rBR/strings.xml
+++ b/src/main/res/values-pt-rBR/strings.xml
@@ -1431,7 +1431,14 @@
     <string name="translation_title">Tradução</string>
     <string name="translation_failed">Falha na tradução</string>
     <string name="translation_in_progress">Traduzindo…</string>
-    <string name="translation_disclaimer">Traduzido automaticamente via Google Tradutor (temporário, até a API de tradução própria do xmpp.tel estar pronta)</string>
+    <string name="translation_disclaimer_private">via tradução privada Qwen3.6</string>
+    <string name="translation_disclaimer_google">via Google Tradutor (público)</string>
+    <string name="translation_fallback_title">Usar o Google Tradutor?</string>
+    <string name="translation_fallback_explained">Não foi possível contatar o serviço de tradução privado. Recorrer ao Google Tradutor enviará este texto da mensagem para o Google.</string>
+    <string name="translation_fallback_allow">Permitir desta vez</string>
+    <string name="translation_do_not_ask_again">Permitir sempre o recurso ao Google Tradutor</string>
+    <string name="translate_choose_language">Traduzir para…</string>
+    <string name="translate_more_languages">Mais…</string>
     <string name="story_caption_hint">Adicionar uma legenda…</string>
     <string name="story_post_button">Publicar</string>
 </resources>
diff --git a/src/main/res/values-pt/strings.xml b/src/main/res/values-pt/strings.xml
index 9291b39..1c14e3b 100644
--- a/src/main/res/values-pt/strings.xml
+++ b/src/main/res/values-pt/strings.xml
@@ -1431,7 +1431,14 @@
     <string name="translation_title">Tradução</string>
     <string name="translation_failed">Falha na tradução</string>
     <string name="translation_in_progress">A traduzir…</string>
-    <string name="translation_disclaimer">Traduzido automaticamente através do Google Tradutor (temporário, até a API de tradução do xmpp.tel estar pronta)</string>
+    <string name="translation_disclaimer_private">via tradução privada Qwen3.6</string>
+    <string name="translation_disclaimer_google">via Google Tradutor (público)</string>
+    <string name="translation_fallback_title">Usar o Google Tradutor?</string>
+    <string name="translation_fallback_explained">Não foi possível contactar o serviço de tradução privado. Recorrer ao Google Tradutor enviará este texto da mensagem para a Google.</string>
+    <string name="translation_fallback_allow">Permitir desta vez</string>
+    <string name="translation_do_not_ask_again">Permitir sempre o recurso ao Google Tradutor</string>
+    <string name="translate_choose_language">Traduzir para…</string>
+    <string name="translate_more_languages">Mais…</string>
     <string name="story_caption_hint">Adicionar uma legenda…</string>
     <string name="story_post_button">Publicar</string>
 </resources>
diff --git a/src/main/res/values-ro-rRO/strings.xml b/src/main/res/values-ro-rRO/strings.xml
index 29e8303..ebffc34 100644
--- a/src/main/res/values-ro-rRO/strings.xml
+++ b/src/main/res/values-ro-rRO/strings.xml
@@ -1442,7 +1442,14 @@
     <string name="translation_title">Traducere</string>
     <string name="translation_failed">Traducerea a eșuat</string>
     <string name="translation_in_progress">Se traduce…</string>
-    <string name="translation_disclaimer">Tradus automat prin Google Translate (temporar, până când API-ul propriu de traducere al xmpp.tel este gata)</string>
+    <string name="translation_disclaimer_private">prin traducere privată Qwen3.6</string>
+    <string name="translation_disclaimer_google">prin Google Traducere (public)</string>
+    <string name="translation_fallback_title">Se utilizează Google Traducere?</string>
+    <string name="translation_fallback_explained">Serviciul de traducere privat nu a putut fi contactat. Revenirea la Google Traducere va trimite textul acestui mesaj către Google.</string>
+    <string name="translation_fallback_allow">Permite de data aceasta</string>
+    <string name="translation_do_not_ask_again">Permite întotdeauna revenirea la Google Traducere</string>
+    <string name="translate_choose_language">Traduceți în…</string>
+    <string name="translate_more_languages">Mai mult…</string>
     <string name="story_caption_hint">Adaugă o legendă…</string>
     <string name="story_post_button">Publică</string>
 </resources>
diff --git a/src/main/res/values-ru/strings.xml b/src/main/res/values-ru/strings.xml
index 3f69b8e..e7bd972 100644
--- a/src/main/res/values-ru/strings.xml
+++ b/src/main/res/values-ru/strings.xml
@@ -1477,7 +1477,14 @@
     <string name="translation_title">Перевод</string>
     <string name="translation_failed">Не удалось перевести</string>
     <string name="translation_in_progress">Перевод…</string>
-    <string name="translation_disclaimer">Машинный перевод через Google Переводчик (временно, пока не готов собственный API перевода xmpp.tel)</string>
+    <string name="translation_disclaimer_private">через частный перевод Qwen3.6</string>
+    <string name="translation_disclaimer_google">через публичный Google Переводчик</string>
+    <string name="translation_fallback_title">Использовать Google Переводчик?</string>
+    <string name="translation_fallback_explained">Не удалось подключиться к частному сервису перевода. Использование Google Переводчика отправит текст этого сообщения в Google.</string>
+    <string name="translation_fallback_allow">Разрешить в этот раз</string>
+    <string name="translation_do_not_ask_again">Всегда разрешать резервный Google Переводчик</string>
+    <string name="translate_choose_language">Перевести на…</string>
+    <string name="translate_more_languages">Ещё…</string>
     <string name="story_caption_hint">Добавить подпись…</string>
     <string name="story_post_button">Опубликовать</string>
 </resources>
diff --git a/src/main/res/values-sr/strings.xml b/src/main/res/values-sr/strings.xml
index 4fc7912..42ce8c1 100644
--- a/src/main/res/values-sr/strings.xml
+++ b/src/main/res/values-sr/strings.xml
@@ -1459,7 +1459,14 @@
     <string name="translation_title">Prevod</string>
     <string name="translation_failed">Prevođenje nije uspelo</string>
     <string name="translation_in_progress">Prevođenje…</string>
-    <string name="translation_disclaimer">Mašinski prevedeno putem Google prevodioca (privremeno, dok xmpp.tel ne pripremi sopstveni API za prevođenje)</string>
+    <string name="translation_disclaimer_private">путем приватног Qwen3.6 превода</string>
+    <string name="translation_disclaimer_google">путем јавног Google преводиоца</string>
+    <string name="translation_fallback_title">Користити Google преводилац?</string>
+    <string name="translation_fallback_explained">Приватна услуга превођења није доступна. Коришћење Google преводиоца ће послати текст ове поруке ка Google-у.</string>
+    <string name="translation_fallback_allow">Дозволи овог пута</string>
+    <string name="translation_do_not_ask_again">Увек дозволи резервни Google преводилац</string>
+    <string name="translate_choose_language">Преведи на…</string>
+    <string name="translate_more_languages">Више…</string>
     <string name="story_caption_hint">Dodaj natpis…</string>
     <string name="story_post_button">Objavi</string>
 </resources>
diff --git a/src/main/res/values-sv/strings.xml b/src/main/res/values-sv/strings.xml
index bb7491b..91cf82a 100644
--- a/src/main/res/values-sv/strings.xml
+++ b/src/main/res/values-sv/strings.xml
@@ -1435,7 +1435,14 @@
     <string name="translation_title">Översättning</string>
     <string name="translation_failed">Översättningen misslyckades</string>
     <string name="translation_in_progress">Översätter…</string>
-    <string name="translation_disclaimer">Maskinöversatt via Google Översätt (tillfälligt, tills xmpp.tel:s egen översättnings-API är klar)</string>
+    <string name="translation_disclaimer_private">via privat Qwen3.6-översättning</string>
+    <string name="translation_disclaimer_google">via offentlig Google Translate</string>
+    <string name="translation_fallback_title">Använda Google Translate?</string>
+    <string name="translation_fallback_explained">Den privata översättningstjänsten kunde inte nås. Om du faller tillbaka på Google Translate skickas meddelandetexten till Google.</string>
+    <string name="translation_fallback_allow">Tillåt den här gången</string>
+    <string name="translation_do_not_ask_again">Tillåt alltid reserv till Google Translate</string>
+    <string name="translate_choose_language">Översätt till…</string>
+    <string name="translate_more_languages">Mer…</string>
     <string name="story_caption_hint">Lägg till en bildtext…</string>
     <string name="story_post_button">Publicera</string>
 </resources>
diff --git a/src/main/res/values-zh-rCN/strings.xml b/src/main/res/values-zh-rCN/strings.xml
index e4920e5..40b62f7 100644
--- a/src/main/res/values-zh-rCN/strings.xml
+++ b/src/main/res/values-zh-rCN/strings.xml
@@ -1395,7 +1395,14 @@
     <string name="translation_title">翻译</string>
     <string name="translation_failed">翻译失败</string>
     <string name="translation_in_progress">正在翻译…</string>
-    <string name="translation_disclaimer">通过 Google 翻译进行机器翻译(临时方案,直到 xmpp.tel 自有的翻译 API 就绪)</string>
+    <string name="translation_disclaimer_private">通过私有 Qwen3.6 翻译</string>
+    <string name="translation_disclaimer_google">通过公共 Google 翻译</string>
+    <string name="translation_fallback_title">使用 Google 翻译?</string>
+    <string name="translation_fallback_explained">无法连接到私有翻译服务。回退到 Google 翻译会将此消息文本发送给 Google。</string>
+    <string name="translation_fallback_allow">本次允许</string>
+    <string name="translation_do_not_ask_again">始终允许回退到 Google 翻译</string>
+    <string name="translate_choose_language">翻译为…</string>
+    <string name="translate_more_languages">更多…</string>
     <string name="story_caption_hint">添加说明文字…</string>
     <string name="story_post_button">发布</string>
 </resources>
diff --git a/src/main/res/values-zh-rTW/strings.xml b/src/main/res/values-zh-rTW/strings.xml
index 1577685..a40d2c8 100644
--- a/src/main/res/values-zh-rTW/strings.xml
+++ b/src/main/res/values-zh-rTW/strings.xml
@@ -1405,7 +1405,14 @@
     <string name="translation_title">翻譯</string>
     <string name="translation_failed">翻譯失敗</string>
     <string name="translation_in_progress">正在翻譯…</string>
-    <string name="translation_disclaimer">透過 Google 翻譯進行機器翻譯(暫時方案,直到 xmpp.tel 自有的翻譯 API 就緒)</string>
+    <string name="translation_disclaimer_private">透過私人 Qwen3.6 翻譯</string>
+    <string name="translation_disclaimer_google">透過公開 Google 翻譯</string>
+    <string name="translation_fallback_title">使用 Google 翻譯?</string>
+    <string name="translation_fallback_explained">無法連線至私人翻譯服務。回退至 Google 翻譯將會把此訊息文字傳送給 Google。</string>
+    <string name="translation_fallback_allow">本次允許</string>
+    <string name="translation_do_not_ask_again">永遠允許回退至 Google 翻譯</string>
+    <string name="translate_choose_language">翻譯為…</string>
+    <string name="translate_more_languages">更多…</string>
     <string name="story_caption_hint">新增說明文字…</string>
     <string name="story_post_button">發佈</string>
 </resources>

Built with passion for open-source software, XMPP, privacy, and security —
the belief that people deserve communication tools that serve them, not surveil them.

Dedicated to every brave man and woman who stands up against tyranny and oppression across the world.
And in memory of those who were silenced before they could speak freely.