🔀 Commit

fixed badges, translate blog posts, multi-select messages to forward
Commit2ad7ec65ee42ee7d7ddeb245d030dcded807eecc
AuthorJabJab <noreply@xmpp.tel>
Date2026-07-06
Parentb28f900b
commit 2ad7ec65ee42ee7d7ddeb245d030dcded807eecc
Author: JabJab <noreply@xmpp.tel>
Date:   Mon Jul 6 09:27:16 2026 +0300

    fixed badges, translate blog posts, multi-select messages to forward
---
 build.gradle                                       |   2 +-
 .../java/tel/xmpp/jabjab/ui/BlogPostActivity.java  |  69 +++++++
 .../tel/xmpp/jabjab/ui/ConversationFragment.java   | 116 +++++++++++
 .../tel/xmpp/jabjab/ui/MultiShareActivity.java     | 223 +++++++++++++++------
 .../tel/xmpp/jabjab/ui/adapter/MessageAdapter.java |  17 ++
 .../tel/xmpp/jabjab/xmpp/manager/BlogManager.java  |  27 ++-
 .../xmpp/jabjab/xmpp/manager/StoriesManager.java   |  37 ++--
 src/main/res/layout/fragment_conversation.xml      |  13 ++
 src/main/res/layout/item_message_channel_card.xml  |  22 +-
 src/main/res/menu/message_context.xml              |   5 +
 src/main/res/menu/message_selection_action.xml     |  11 +
 src/main/res/values-ar/strings.xml                 | 176 ++++++++++++++++
 src/main/res/values-de/strings.xml                 |  91 +++++++++
 src/main/res/values-tr/strings.xml                 | 139 +++++++++++++
 src/main/res/values/strings.xml                    |   3 +
 15 files changed, 857 insertions(+), 94 deletions(-)

diff --git a/build.gradle b/build.gradle
index 2d64a09..b4ecaf1 100644
--- a/build.gradle
+++ b/build.gradle
@@ -113,7 +113,7 @@ android {
 
     defaultConfig {
         minSdkVersion 23
-        versionCode 42288
+        versionCode 42289
         versionName "1.0.1"
         applicationId "tel.xmpp.jabjab"
         resValue "string", "applicationId", applicationId
diff --git a/src/main/java/tel/xmpp/jabjab/ui/BlogPostActivity.java b/src/main/java/tel/xmpp/jabjab/ui/BlogPostActivity.java
index a40a614..e199eb7 100644
--- a/src/main/java/tel/xmpp/jabjab/ui/BlogPostActivity.java
+++ b/src/main/java/tel/xmpp/jabjab/ui/BlogPostActivity.java
@@ -1,6 +1,9 @@
 package tel.xmpp.jabjab.ui;
 
 import android.app.AlertDialog;
+import android.content.ClipData;
+import android.util.Log;
+import android.content.ClipboardManager;
 import android.content.Intent;
 import android.net.Uri;
 import android.os.Bundle;
@@ -315,6 +318,9 @@ public class BlogPostActivity extends XmppActivity {
         bodyView.setText(Markdown.render(pendingBody != null ? pendingBody : ""));
         bodyView.setMovementMethod(LinkMovementMethod.getInstance());
         bodyView.setVisibility(View.VISIBLE);
+        // Long-press on body or title → copy / translate menu
+        bodyView.setOnLongClickListener(v -> { showCopyTranslateMenu(); return true; });
+        titleView.setOnLongClickListener(v -> { showCopyTranslateMenu(); return true; });
         final String headerUrl = pendingHeaderUrl != null
                 ? pendingHeaderUrl : getIntent().getStringExtra(EXTRA_HEADER_IMAGE);
         if (headerUrl != null) {
@@ -324,6 +330,69 @@ public class BlogPostActivity extends XmppActivity {
         invalidateOptionsMenu();
     }
 
+    private void showCopyTranslateMenu() {
+        final String text = (pendingTitle != null && !pendingTitle.isEmpty()
+                ? pendingTitle + "\n\n" : "")
+                + (pendingBody != null ? pendingBody : "");
+        new AlertDialog.Builder(this)
+                .setItems(new CharSequence[]{
+                        getString(R.string.copy_to_clipboard),
+                        getString(R.string.translate_message)
+                }, (d, which) -> {
+                    if (which == 0) {
+                        final ClipboardManager cm =
+                                (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
+                        cm.setPrimaryClip(ClipData.newPlainText("blog post", text));
+                        Toast.makeText(this, R.string.message_copied_to_clipboard,
+                                Toast.LENGTH_SHORT).show();
+                    } else {
+                        translatePost(text);
+                    }
+                })
+                .show();
+    }
+
+    private void translatePost(final String text) {
+        final android.app.AlertDialog progress = new AlertDialog.Builder(this)
+                .setTitle(R.string.translation_title)
+                .setMessage(R.string.translation_in_progress)
+                .setCancelable(true)
+                .show();
+        final String targetLang = java.util.Locale.getDefault().getLanguage();
+        Log.d("BlogPostActivity", "translating post, length=" + text.length() + " targetLang=" + targetLang);
+        tel.xmpp.jabjab.ui.util.TranslationService.translate(
+                this,
+                text,
+                targetLang,
+                new tel.xmpp.jabjab.ui.util.TranslationService.Callback() {
+                    @Override
+                    public void onResult(final String translatedText,
+                            @androidx.annotation.Nullable final String detectedSourceLang) {
+                        progress.dismiss();
+                        new AlertDialog.Builder(BlogPostActivity.this)
+                                .setTitle(R.string.translation_title)
+                                .setMessage(translatedText + "\n\n"
+                                        + getString(R.string.translation_disclaimer))
+                                .setPositiveButton(R.string.close, null)
+                                .setNeutralButton(R.string.copy_to_clipboard, (d2, w) -> {
+                                    final ClipboardManager cm =
+                                            (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
+                                    cm.setPrimaryClip(ClipData.newPlainText(
+                                            translatedText, translatedText));
+                                })
+                                .show();
+                    }
+
+                    @Override
+                    public void onError(final Exception e) {
+                        progress.dismiss();
+                        Log.w("BlogPostActivity", "translation failed", e);
+                        Toast.makeText(BlogPostActivity.this,
+                                R.string.translation_failed, Toast.LENGTH_SHORT).show();
+                    }
+                });
+    }
+
     @Override
     public boolean onSupportNavigateUp() {
         finish();
diff --git a/src/main/java/tel/xmpp/jabjab/ui/ConversationFragment.java b/src/main/java/tel/xmpp/jabjab/ui/ConversationFragment.java
index b0480d0..149161c 100644
--- a/src/main/java/tel/xmpp/jabjab/ui/ConversationFragment.java
+++ b/src/main/java/tel/xmpp/jabjab/ui/ConversationFragment.java
@@ -34,6 +34,7 @@ import android.view.ContextMenu;
 import android.view.ContextMenu.ContextMenuInfo;
 import android.view.Gravity;
 import android.view.LayoutInflater;
+import android.view.ActionMode;
 import android.view.Menu;
 import android.view.MenuInflater;
 import android.view.MenuItem;
@@ -545,6 +546,38 @@ public class ConversationFragment extends XmppFragment
                 return true;
             };
     private Message selectedMessage;
+
+    // Multi-select state
+    private final java.util.LinkedHashSet<String> selectedUuids = new java.util.LinkedHashSet<>();
+    private ActionMode selectionActionMode;
+
+    private final ActionMode.Callback selectionModeCallback = new ActionMode.Callback() {
+        @Override
+        public boolean onCreateActionMode(final ActionMode mode, final Menu menu) {
+            requireActivity().getMenuInflater().inflate(R.menu.message_selection_action, menu);
+            return true;
+        }
+        @Override
+        public boolean onPrepareActionMode(final ActionMode mode, final Menu menu) {
+            updateSelectionTitle(mode);
+            return true;
+        }
+        @Override
+        public boolean onActionItemClicked(final ActionMode mode, final MenuItem item) {
+            if (item.getItemId() == R.id.action_forward_selected) {
+                forwardSelectedMessages();
+                return true;
+            }
+            return false;
+        }
+        @Override
+        public void onDestroyActionMode(final ActionMode mode) {
+            // Clears highlight and hides overlay without re-triggering finish().
+            selectionActionMode = null;
+            exitSelectionMode();
+        }
+    };
+
     private final OnClickListener mEnableAccountListener =
             new OnClickListener() {
                 @Override
@@ -1648,6 +1681,7 @@ public class ConversationFragment extends XmppFragment
             final MenuItem retryDecryption = menu.findItem(R.id.retry_decryption);
             final MenuItem correctMessage = menu.findItem(R.id.correct_message);
             final MenuItem retractMessage = menu.findItem(R.id.retract_message);
+            final MenuItem selectMessage = menu.findItem(R.id.action_select_message);
             final MenuItem shareWith = menu.findItem(R.id.share_with);
             final MenuItem forwardToContacts = menu.findItem(R.id.forward_to_contacts);
             final MenuItem sendAgain = menu.findItem(R.id.send_again);
@@ -1772,6 +1806,12 @@ public class ConversationFragment extends XmppFragment
                 // "Forward to multiple contacts" — same conditions as share
                 forwardToContacts.setVisible(true);
             }
+            // "Select messages" is always available for forwardable message types.
+            if (!deleted && !encrypted && !receiving
+                    && m.getType() == Message.TYPE_TEXT
+                    || (m.isFileOrImage() && m.hasFileOnRemoteHost())) {
+                selectMessage.setVisible(true);
+            }
             if (m.getStatus() == Message.STATUS_SEND_FAILED) {
                 sendAgain.setVisible(true);
                 final var httpUploadAvailable =
@@ -1949,6 +1989,10 @@ public class ConversationFragment extends XmppFragment
                 requireXmppActivity().xmppConnectionService.retractMessage(selectedMessage);
                 yield true;
             }
+            case R.id.action_select_message -> {
+                enterSelectionMode(selectedMessage);
+                yield true;
+            }
             case R.id.forward_to_contacts -> {
                 forwardToMultipleContacts(selectedMessage);
                 yield true;
@@ -1972,6 +2016,78 @@ public class ConversationFragment extends XmppFragment
         startActivity(intent);
     }
 
+    private void enterSelectionMode(final Message first) {
+        selectedUuids.clear();
+        selectedUuids.add(first.getUuid());
+        messageListAdapter.setSelectedUuids(selectedUuids);
+        selectionActionMode = requireActivity().startActionMode(selectionModeCallback);
+        final var overlay = binding.selectionOverlay;
+        overlay.setVisibility(View.VISIBLE);
+        overlay.setOnTouchListener((v, event) -> {
+            if (event.getAction() == MotionEvent.ACTION_UP) {
+                final int pos = binding.messagesView.pointToPosition(
+                        (int) event.getX(), (int) event.getY());
+                if (pos >= 0) {
+                    final Message msg;
+                    synchronized (messageList) {
+                        if (pos >= messageList.size()) return true;
+                        msg = messageList.get(pos);
+                    }
+                    // Skip non-content items
+                    if (msg.getType() == Message.TYPE_STATUS
+                            || msg.getType() == Message.TYPE_RTP_SESSION) return true;
+                    final String uuid = msg.getUuid();
+                    if (!selectedUuids.remove(uuid)) selectedUuids.add(uuid);
+                    messageListAdapter.setSelectedUuids(selectedUuids);
+                    if (selectionActionMode != null) updateSelectionTitle(selectionActionMode);
+                    // Exit if everything was deselected
+                    if (selectedUuids.isEmpty()) exitSelectionMode();
+                }
+            }
+            return true;
+        });
+    }
+
+    private void exitSelectionMode() {
+        selectedUuids.clear();
+        messageListAdapter.setSelectedUuids(selectedUuids);
+        binding.selectionOverlay.setVisibility(View.GONE);
+        binding.selectionOverlay.setOnTouchListener(null);
+        if (selectionActionMode != null) {
+            final ActionMode mode = selectionActionMode;
+            selectionActionMode = null;
+            mode.finish();
+        }
+    }
+
+    private void updateSelectionTitle(final ActionMode mode) {
+        final int n = selectedUuids.size();
+        mode.setTitle(String.valueOf(n));
+        mode.setSubtitle(getString(R.string.messages_selected));
+    }
+
+    private void forwardSelectedMessages() {
+        final java.util.ArrayList<String> items = new java.util.ArrayList<>();
+        synchronized (messageList) {
+            // Preserve chronological order by iterating messageList (not selectedUuids order).
+            for (final Message m : messageList) {
+                if (!selectedUuids.contains(m.getUuid())) continue;
+                if (m.isFileOrImage() && m.hasFileOnRemoteHost()) {
+                    final String url = m.getFileParams().url;
+                    final String mime = m.getMimeType() != null ? m.getMimeType() : "";
+                    items.add("MEDIA:" + url + "\t" + mime);
+                } else if (m.getType() == Message.TYPE_TEXT && m.getBody() != null) {
+                    items.add("TEXT:" + m.getBody());
+                }
+            }
+        }
+        exitSelectionMode();
+        if (items.isEmpty()) return;
+        final Intent intent = new Intent(requireContext(), MultiShareActivity.class);
+        intent.putStringArrayListExtra(MultiShareActivity.EXTRA_FORWARD_ITEMS, items);
+        startActivity(intent);
+    }
+
     private void startSearch() {
         final Intent intent = new Intent(getActivity(), SearchActivity.class);
         intent.putExtra(SearchActivity.EXTRA_CONVERSATION_UUID, conversation.getUuid());
diff --git a/src/main/java/tel/xmpp/jabjab/ui/MultiShareActivity.java b/src/main/java/tel/xmpp/jabjab/ui/MultiShareActivity.java
index 2946a8e..15eeb56 100644
--- a/src/main/java/tel/xmpp/jabjab/ui/MultiShareActivity.java
+++ b/src/main/java/tel/xmpp/jabjab/ui/MultiShareActivity.java
@@ -15,6 +15,7 @@ import android.widget.ImageView;
 import android.widget.TextView;
 import android.widget.Toast;
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.recyclerview.widget.LinearLayoutManager;
 import androidx.recyclerview.widget.RecyclerView;
 import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton;
@@ -27,6 +28,7 @@ import tel.xmpp.jabjab.entities.Account;
 import tel.xmpp.jabjab.entities.Contact;
 import tel.xmpp.jabjab.entities.Conversation;
 import tel.xmpp.jabjab.ui.util.AvatarWorkerTask;
+import tel.xmpp.jabjab.xmpp.Jid;
 import java.util.ArrayList;
 import java.util.LinkedHashSet;
 import java.util.List;
@@ -36,22 +38,60 @@ import java.util.Locale;
  * Share a file or text to multiple contacts at once.
  * Each recipient gets a separate message (and separate upload for OMEMO).
  * Entry point: registered alongside ShareWithActivity for ACTION_SEND / ACTION_SEND_MULTIPLE.
- * Also reachable from ConversationFragment via "Forward to multiple contacts".
+ * Also reachable from ConversationFragment via "Forward to multiple contacts" or multi-select.
  */
 public class MultiShareActivity extends XmppActivity {
 
     public static final String EXTRA_TEXT = "extra_text";
+    /** Ordered list of messages to forward. Each entry is "TEXT:<body>" or "MEDIA:<url>\t<mime>". */
+    public static final String EXTRA_FORWARD_ITEMS = "extra_forward_items";
 
-    // Share payload
+    /**
+     * A forward target is either a roster contact or one of our own accounts (self-send).
+     * Using a unified type lets the contact list show both without separate adapter paths.
+     */
+    static final class ForwardTarget {
+        final Account account;      // account that will send the message
+        final Jid jid;             // recipient JID (may equal account.getJid() for self-send)
+        final String displayName;
+        @Nullable final Contact contact; // null for self entries
+        @Nullable final Account selfAccount; // non-null only for self entries (for avatar loading)
+
+        private ForwardTarget(final Account account, final Jid jid, final String displayName,
+                              @Nullable final Contact contact,
+                              @Nullable final Account selfAccount) {
+            this.account     = account;
+            this.jid         = jid;
+            this.displayName = displayName;
+            this.contact     = contact;
+            this.selfAccount = selfAccount;
+        }
+
+        static ForwardTarget forContact(final Contact c) {
+            return new ForwardTarget(c.getAccount(), c.getAddress(),
+                    c.getDisplayName(), c, null);
+        }
+
+        static ForwardTarget forSelf(final Account a) {
+            return new ForwardTarget(a, a.getJid().asBareJid(),
+                    a.getJid().asBareJid().toString(), null, a);
+        }
+
+        /** Key used for deduplication: prefer contact JID; own accounts use their JID. */
+        String key() { return jid.asBareJid().toString(); }
+    }
+
+    // Share payload — one of: forward items list, text, or file URIs
+    private ArrayList<String> forwardItems; // non-null when coming from multi-select
     private ArrayList<Uri> uris = new ArrayList<>();
     private String mime;
     private String text;
 
-    private final List<Contact> allContacts  = new ArrayList<>();
-    private final List<Contact> filteredList = new ArrayList<>();
-    private final LinkedHashSet<Contact> selected = new LinkedHashSet<>();
+    private final List<ForwardTarget> allTargets    = new ArrayList<>();
+    private final List<ForwardTarget> filteredTargets = new ArrayList<>();
+    private final LinkedHashSet<ForwardTarget> selected = new LinkedHashSet<>();
 
-    private ContactCheckAdapter adapter;
+    private TargetAdapter adapter;
     private ExtendedFloatingActionButton sendFab;
     // Account chosen by the user (or auto-selected) to perform the HTTP upload
     private Account uploadAccount;
@@ -71,7 +111,7 @@ public class MultiShareActivity extends XmppActivity {
 
         populateFromIntent(getIntent());
 
-        adapter = new ContactCheckAdapter();
+        adapter = new TargetAdapter();
         final RecyclerView rv = findViewById(R.id.contact_list);
         rv.setLayoutManager(new LinearLayoutManager(this));
         rv.setAdapter(adapter);
@@ -97,6 +137,12 @@ public class MultiShareActivity extends XmppActivity {
 
     private void populateFromIntent(final Intent intent) {
         if (intent == null) return;
+        // Multi-select forward takes priority
+        final ArrayList<String> fwdItems = intent.getStringArrayListExtra(EXTRA_FORWARD_ITEMS);
+        if (fwdItems != null && !fwdItems.isEmpty()) {
+            forwardItems = fwdItems;
+            return;
+        }
         final String action = intent.getAction();
         if (Intent.ACTION_SEND.equals(action)) {
             final Uri uri = intent.getParcelableExtra(Intent.EXTRA_STREAM);
@@ -133,43 +179,61 @@ public class MultiShareActivity extends XmppActivity {
         }
 
         // For file shares that need uploading, pick the upload account first.
-        final boolean needsUpload = !uris.isEmpty() && uris.stream().anyMatch(u -> {
-            final String s = u.getScheme();
-            return !"aesgcm".equals(s) && !"https".equals(s) && !"http".equals(s);
-        });
+        final boolean needsUpload = forwardItems == null && !uris.isEmpty()
+                && uris.stream().anyMatch(u -> {
+                    final String s = u.getScheme();
+                    return !"aesgcm".equals(s) && !"https".equals(s) && !"http".equals(s);
+                });
         if (needsUpload && onlineAccounts.size() > 1 && uploadAccount == null) {
-            showUploadAccountPicker(onlineAccounts, () -> populateContacts(onlineAccounts));
+            showUploadAccountPicker(onlineAccounts, () -> populateTargets(onlineAccounts));
             return;
         }
         if (needsUpload && uploadAccount == null && !onlineAccounts.isEmpty()) {
             uploadAccount = onlineAccounts.get(0);
         }
-        populateContacts(onlineAccounts);
+        populateTargets(onlineAccounts);
     }
 
-    private void populateContacts(final List<Account> onlineAccounts) {
-        allContacts.clear();
+    private void populateTargets(final List<Account> onlineAccounts) {
+        allTargets.clear();
         final var seen = new java.util.HashSet<String>();
+
+        // Own accounts first — "Me (jid)" entries for self-send
+        for (final Account account : onlineAccounts) {
+            final ForwardTarget self = ForwardTarget.forSelf(account);
+            if (seen.add(self.key())) allTargets.add(self);
+        }
+
+        // Roster contacts
         for (final Account account : onlineAccounts) {
             for (final Contact c : account.getRoster().getContacts()) {
                 if (!c.getOption(Contact.Options.TO)) continue;
                 final String key = c.getAddress().asBareJid().toString();
-                if (seen.add(key)) allContacts.add(c);
+                if (seen.add(key)) allTargets.add(ForwardTarget.forContact(c));
             }
         }
-        allContacts.sort((a, b) ->
-                a.getDisplayName().compareToIgnoreCase(b.getDisplayName()));
+
+        // Sort roster contacts alphabetically; keep self entries at the top.
+        final int selfCount = (int) allTargets.stream()
+                .filter(t -> t.selfAccount != null).count();
+        final List<ForwardTarget> selfEntries = new ArrayList<>(allTargets.subList(0, selfCount));
+        final List<ForwardTarget> contactEntries = new ArrayList<>(allTargets.subList(selfCount, allTargets.size()));
+        contactEntries.sort((a, b) -> a.displayName.compareToIgnoreCase(b.displayName));
+        allTargets.clear();
+        allTargets.addAll(selfEntries);
+        allTargets.addAll(contactEntries);
+
         applyFilter("");
     }
 
     private void applyFilter(final String query) {
-        filteredList.clear();
+        filteredTargets.clear();
         final String q = query.trim().toLowerCase(Locale.getDefault());
-        for (final Contact c : allContacts) {
+        for (final ForwardTarget t : allTargets) {
             if (q.isEmpty()
-                    || c.getDisplayName().toLowerCase(Locale.getDefault()).contains(q)
-                    || c.getAddress().toString().toLowerCase(Locale.getDefault()).contains(q)) {
-                filteredList.add(c);
+                    || t.displayName.toLowerCase(Locale.getDefault()).contains(q)
+                    || t.jid.toString().toLowerCase(Locale.getDefault()).contains(q)) {
+                filteredTargets.add(t);
             }
         }
         if (adapter != null) adapter.notifyDataSetChanged();
@@ -189,22 +253,20 @@ public class MultiShareActivity extends XmppActivity {
 
     private void sendToSelected() {
         if (selected.isEmpty()) return;
+        final List<ForwardTarget> targets = new ArrayList<>(selected);
+        sendFab.setEnabled(false);
+        sendFab.setText(getString(R.string.multi_share_sending, targets.size()));
 
-        final List<Contact> targets = new ArrayList<>(selected);
-
-        if (!uris.isEmpty()) {
-            sendFab.setEnabled(false);
-            sendFab.setText(getString(R.string.multi_share_sending, targets.size()));
+        if (forwardItems != null) {
+            sendForwardItems(targets);
+        } else if (!uris.isEmpty()) {
             sendFilesToSelected(targets);
         } else if (text != null && !text.isEmpty()) {
-            sendFab.setEnabled(false);
-            sendFab.setText(getString(R.string.multi_share_sending, targets.size()));
-            // Text share: just send the same text to everyone, no upload needed.
             final int total = targets.size();
             final int[] done = {0};
-            for (final Contact contact : targets) {
+            for (final ForwardTarget target : targets) {
                 final Conversation conv = xmppConnectionService.findOrCreateConversation(
-                        contact.getAccount(), contact.getAddress(), false, true);
+                        target.account, target.jid, false, true);
                 final tel.xmpp.jabjab.entities.Message msg =
                         new tel.xmpp.jabjab.entities.Message(conv, text, conv.getNextEncryption());
                 xmppConnectionService.sendMessage(msg);
@@ -215,6 +277,37 @@ public class MultiShareActivity extends XmppActivity {
         }
     }
 
+    /**
+     * Sends each forward item (in order) to every selected target.
+     * Text items go immediately; media items re-use the already-uploaded URL.
+     */
+    private void sendForwardItems(final List<ForwardTarget> targets) {
+        final int total = forwardItems.size() * targets.size();
+        final int[] done = {0};
+        for (final String item : forwardItems) {
+            for (final ForwardTarget target : targets) {
+                final Conversation conv = xmppConnectionService.findOrCreateConversation(
+                        target.account, target.jid, false, true);
+                final tel.xmpp.jabjab.entities.Message msg;
+                if (item.startsWith("TEXT:")) {
+                    final String body = item.substring(5);
+                    msg = new tel.xmpp.jabjab.entities.Message(conv, body, conv.getNextEncryption());
+                } else if (item.startsWith("MEDIA:")) {
+                    // "MEDIA:<url>\t<mime>" — re-send the remote URL directly, no re-upload.
+                    final String payload = item.substring(6);
+                    final int tab = payload.indexOf('\t');
+                    final String url  = tab > 0 ? payload.substring(0, tab) : payload;
+                    msg = new tel.xmpp.jabjab.entities.Message(conv, url, conv.getNextEncryption());
+                } else {
+                    checkDone(done, total);
+                    continue;
+                }
+                xmppConnectionService.sendMessage(msg);
+                checkDone(done, total);
+            }
+        }
+    }
+
     private void showUploadAccountPicker(final List<Account> accounts, final Runnable onPicked) {
         final String[] labels = new String[accounts.size()];
         for (int i = 0; i < accounts.size(); i++) {
@@ -238,7 +331,7 @@ public class MultiShareActivity extends XmppActivity {
      * an already-received file from the context menu — the upload step is skipped entirely
      * and the URL is sent directly to all contacts.
      */
-    private void sendFilesToSelected(final List<Contact> targets) {
+    private void sendFilesToSelected(final List<ForwardTarget> targets) {
         final int totalOps = uris.size() * targets.size();
         final int[] done = {0};
 
@@ -248,18 +341,15 @@ public class MultiShareActivity extends XmppActivity {
                 // Already a remote URL — forward directly to all contacts without re-uploading.
                 final String urlOnly = uri.toString();
                 Log.d(Config.LOGTAG, "multi-share: skipping upload, URL already remote: " + urlOnly);
-                forwardFileBodyToContacts(urlOnly, targets, done, totalOps);
+                forwardFileBodyToTargets(urlOnly, targets, done, totalOps);
             } else {
                 // Local file — upload once via the chosen upload account, then forward URL to all.
-                // uploadAccount is set by the account picker (or auto-selected if only one online).
                 final Account uploader = uploadAccount != null ? uploadAccount
-                        : targets.get(0).getAccount();
-                // Use a dummy conversation with the first contact just to get a valid Conversation
-                // object for the upload — the message is sent to all contacts via forwardFileBody.
-                final Contact first = targets.get(0);
-                final List<Contact> rest = new ArrayList<>(targets.subList(1, targets.size()));
+                        : targets.get(0).account;
+                final ForwardTarget first = targets.get(0);
+                final List<ForwardTarget> rest = new ArrayList<>(targets.subList(1, targets.size()));
                 final Conversation firstConv = xmppConnectionService.findOrCreateConversation(
-                        uploader, first.getAddress(), false, true);
+                        uploader, first.jid, false, true);
                 final String finalMime = mime != null ? mime : getContentResolver().getType(uri);
 
                 Futures.addCallback(
@@ -269,10 +359,9 @@ public class MultiShareActivity extends XmppActivity {
                             public void onSuccess(final String fileBody) {
                                 checkDone(done, totalOps);
                                 if (fileBody != null && !fileBody.isEmpty()) {
-                                    // Strip |size|width|height — only the URL is needed
                                     final int pipe = fileBody.indexOf('|');
                                     final String urlOnly = pipe > 0 ? fileBody.substring(0, pipe) : fileBody;
-                                    forwardFileBodyToContacts(urlOnly, rest, done, totalOps);
+                                    forwardFileBodyToTargets(urlOnly, rest, done, totalOps);
                                 } else {
                                     for (int i = 0; i < rest.size(); i++) checkDone(done, totalOps);
                                 }
@@ -288,21 +377,16 @@ public class MultiShareActivity extends XmppActivity {
         }
     }
 
-    /** Sends a previously-uploaded file body string to a list of contacts without re-uploading. */
-    private void forwardFileBodyToContacts(
+    private void forwardFileBodyToTargets(
             final String fileBody,
-            final List<Contact> contacts,
+            final List<ForwardTarget> targets,
             final int[] done,
             final int total) {
-        for (final Contact contact : contacts) {
+        for (final ForwardTarget target : targets) {
             final Conversation conv = xmppConnectionService.findOrCreateConversation(
-                    contact.getAccount(), contact.getAddress(), false, true);
-            // Forward the aesgcm:// URL as a plain message — receivers render it as media.
-            // Do NOT set TYPE_FILE: that would make needsUploading()=true and trigger a
-            // redundant local-file open which fails with ENOENT (file already uploaded).
+                    target.account, target.jid, false, true);
             final tel.xmpp.jabjab.entities.Message fwd =
-                    new tel.xmpp.jabjab.entities.Message(
-                            conv, fileBody, conv.getNextEncryption());
+                    new tel.xmpp.jabjab.entities.Message(conv, fileBody, conv.getNextEncryption());
             xmppConnectionService.sendMessage(fwd);
             checkDone(done, total);
         }
@@ -321,7 +405,7 @@ public class MultiShareActivity extends XmppActivity {
 
     // ————————————————————————————————————————————————————————————————————
 
-    private class ContactCheckAdapter extends RecyclerView.Adapter<ContactCheckAdapter.VH> {
+    private class TargetAdapter extends RecyclerView.Adapter<TargetAdapter.VH> {
 
         @NonNull
         @Override
@@ -332,24 +416,33 @@ public class MultiShareActivity extends XmppActivity {
 
         @Override
         public void onBindViewHolder(@NonNull final VH h, final int position) {
-            final Contact c = filteredList.get(position);
-            h.name.setText(c.getDisplayName());
-            h.jid.setText(c.getAddress().asBareJid().toString());
-            h.check.setChecked(selected.contains(c));
-            AvatarWorkerTask.loadAvatar(c, h.avatar, R.dimen.avatar);
+            final ForwardTarget t = filteredTargets.get(position);
+            if (t.selfAccount != null) {
+                // Self entry — show "Me (jid)" with own account avatar
+                h.name.setText(getString(R.string.multi_share_self,
+                        t.jid.asBareJid().toString()));
+                h.jid.setVisibility(View.GONE);
+                AvatarWorkerTask.loadAvatar(t.selfAccount, h.avatar, R.dimen.avatar);
+            } else {
+                h.name.setText(t.displayName);
+                h.jid.setVisibility(View.VISIBLE);
+                h.jid.setText(t.jid.asBareJid().toString());
+                AvatarWorkerTask.loadAvatar(t.contact, h.avatar, R.dimen.avatar);
+            }
+            h.check.setChecked(selected.contains(t));
             h.itemView.setOnClickListener(v -> {
-                if (selected.contains(c)) {
-                    selected.remove(c);
+                if (selected.contains(t)) {
+                    selected.remove(t);
                 } else {
-                    selected.add(c);
+                    selected.add(t);
                 }
-                h.check.setChecked(selected.contains(c));
+                h.check.setChecked(selected.contains(t));
                 updateFab();
             });
         }
 
         @Override
-        public int getItemCount() { return filteredList.size(); }
+        public int getItemCount() { return filteredTargets.size(); }
 
         class VH extends RecyclerView.ViewHolder {
             final ImageView avatar;
diff --git a/src/main/java/tel/xmpp/jabjab/ui/adapter/MessageAdapter.java b/src/main/java/tel/xmpp/jabjab/ui/adapter/MessageAdapter.java
index 4818c36..939cb03 100644
--- a/src/main/java/tel/xmpp/jabjab/ui/adapter/MessageAdapter.java
+++ b/src/main/java/tel/xmpp/jabjab/ui/adapter/MessageAdapter.java
@@ -165,6 +165,14 @@ public class MessageAdapter extends ArrayAdapter<Message> {
         this.mOnContactPictureClickedListener = listener;
     }
 
+    private java.util.Set<String> selectedUuids = java.util.Collections.emptySet();
+
+    /** Updates the set of selected message UUIDs and redraws the list. */
+    public void setSelectedUuids(final java.util.Set<String> uuids) {
+        this.selectedUuids = uuids;
+        notifyDataSetChanged();
+    }
+
     public Activity getActivity() {
         return activity;
     }
@@ -1843,6 +1851,15 @@ public class MessageAdapter extends ArrayAdapter<Message> {
         }
 
         displayStatus(viewHolder, message, bubbleColor);
+        // Highlight selected messages with a translucent tint (20% colorPrimary overlay).
+        if (selectedUuids.contains(message.getUuid())) {
+            final int primary = MaterialColors.getColor(viewHolder.root(),
+                    android.R.attr.colorPrimary, 0xFF00BCD4);
+            viewHolder.root().setForeground(
+                    new android.graphics.drawable.ColorDrawable(0x33000000 | (primary & 0x00FFFFFF)));
+        } else {
+            viewHolder.root().setForeground(null);
+        }
         return viewHolder.root();
     }
 
diff --git a/src/main/java/tel/xmpp/jabjab/xmpp/manager/BlogManager.java b/src/main/java/tel/xmpp/jabjab/xmpp/manager/BlogManager.java
index d81eb09..7efd001 100644
--- a/src/main/java/tel/xmpp/jabjab/xmpp/manager/BlogManager.java
+++ b/src/main/java/tel/xmpp/jabjab/xmpp/manager/BlogManager.java
@@ -42,11 +42,28 @@ public class BlogManager extends AbstractManager {
         final Map<String, Entry> entryMap = items.getItemMap(Entry.class);
         Log.d(Config.LOGTAG, "BLOG push from " + from + ": " + entryMap.size() + " items");
         if (!entryMap.isEmpty()) {
-            service.getApplicationContext()
-                    .getSharedPreferences(BADGE_PREFS, Context.MODE_PRIVATE)
-                    .edit()
-                    .putLong(KEY_BADGE_TS, System.currentTimeMillis())
-                    .apply();
+            bumpBadgeIfNew(entryMap);
+        }
+    }
+
+    /** Bumps badge_ts only when at least one entry was published after the user's last read. */
+    private void bumpBadgeIfNew(final Map<String, Entry> entryMap) {
+        final var prefs = service.getApplicationContext()
+                .getSharedPreferences(BADGE_PREFS, Context.MODE_PRIVATE);
+        final long lastReadTs = prefs.getLong("last_read_ts", 0L);
+        long newestPublishedMs = 0L;
+        for (final Entry e : entryMap.values()) {
+            final String raw = e.getPublished();
+            if (raw == null) continue;
+            try {
+                final long ms = Instant.parse(raw).toEpochMilli();
+                if (ms > newestPublishedMs) newestPublishedMs = ms;
+            } catch (final Exception ignored) {}
+        }
+        Log.d(Config.LOGTAG, "BLOG bumpBadgeIfNew: newestPublishedMs=" + newestPublishedMs
+                + " lastReadTs=" + lastReadTs);
+        if (newestPublishedMs > lastReadTs) {
+            prefs.edit().putLong(KEY_BADGE_TS, newestPublishedMs).apply();
         }
     }
 
diff --git a/src/main/java/tel/xmpp/jabjab/xmpp/manager/StoriesManager.java b/src/main/java/tel/xmpp/jabjab/xmpp/manager/StoriesManager.java
index 8b06fdd..e8b530b 100644
--- a/src/main/java/tel/xmpp/jabjab/xmpp/manager/StoriesManager.java
+++ b/src/main/java/tel/xmpp/jabjab/xmpp/manager/StoriesManager.java
@@ -74,15 +74,32 @@ public class StoriesManager extends AbstractManager {
                     existing.getOrDefault(contactJid, new LinkedHashMap<>());
             merged.putAll(stories);
             db.replaceStoryCacheForContact(accountJid, contactJid, merged);
-            service.getApplicationContext()
-                    .getSharedPreferences(BADGE_PREFS, Context.MODE_PRIVATE)
-                    .edit()
-                    .putLong(KEY_BADGE_TS, System.currentTimeMillis())
-                    .apply();
+            bumpBadgeIfNew(stories);
             service.updateStoriesUi();
         }
     }
 
+    /** Bumps badge_ts only when at least one story was published after the user's last read. */
+    private void bumpBadgeIfNew(final Map<String, Story> stories) {
+        final var prefs = service.getApplicationContext()
+                .getSharedPreferences(BADGE_PREFS, Context.MODE_PRIVATE);
+        final long lastReadTs = prefs.getLong("last_read_ts", 0L);
+        long newestPublishedMs = 0L;
+        for (final Story s : stories.values()) {
+            final String raw = s.getPublishedRaw();
+            if (raw == null) continue;
+            try {
+                final long ms = Instant.parse(raw).toEpochMilli();
+                if (ms > newestPublishedMs) newestPublishedMs = ms;
+            } catch (final Exception ignored) {}
+        }
+        Log.d(Config.LOGTAG, "STORIES bumpBadgeIfNew: newestPublishedMs=" + newestPublishedMs
+                + " lastReadTs=" + lastReadTs);
+        if (newestPublishedMs > lastReadTs) {
+            prefs.edit().putLong(KEY_BADGE_TS, newestPublishedMs).apply();
+        }
+    }
+
     public ListenableFuture<Void> publishStory(
             final String url, final String mime, final String caption) {
         final var itemId = UUID.randomUUID().toString();
@@ -205,14 +222,8 @@ public class StoriesManager extends AbstractManager {
                                 + contactJid + " → " + stories.size() + " stories");
                         final DatabaseBackend db = DatabaseBackend.getInstance(context);
                         db.replaceStoryCacheForContact(accountJid, contactJid, stories);
-                        // Bump badge timestamp if there are live stories
-                        if (!stories.isEmpty()) {
-                            service.getApplicationContext()
-                                    .getSharedPreferences(BADGE_PREFS, Context.MODE_PRIVATE)
-                                    .edit()
-                                    .putLong(KEY_BADGE_TS, System.currentTimeMillis())
-                                    .apply();
-                        }
+                        // Bump badge only if a story is newer than the user's last read time
+                        bumpBadgeIfNew(stories);
                         service.updateStoriesUi();
                     }
                     @Override
diff --git a/src/main/res/layout/fragment_conversation.xml b/src/main/res/layout/fragment_conversation.xml
index 0c60ae0..6f15d12 100644
--- a/src/main/res/layout/fragment_conversation.xml
+++ b/src/main/res/layout/fragment_conversation.xml
@@ -41,6 +41,19 @@
                 android:transcriptMode="normal"
                 tools:listitem="@layout/item_message_end" />
 
+            <!-- Transparent overlay that intercepts taps during multi-select mode -->
+            <FrameLayout
+                android:id="@+id/selection_overlay"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                android:layout_above="@+id/snackbar"
+                android:layout_alignParentStart="true"
+                android:layout_alignParentTop="true"
+                android:background="@android:color/transparent"
+                android:clickable="true"
+                android:focusable="true"
+                android:visibility="gone" />
+
             <com.google.android.material.floatingactionbutton.FloatingActionButton
                 android:id="@+id/scroll_to_bottom_button"
                 style="?attr/floatingActionButtonSmallSurfaceStyle"
diff --git a/src/main/res/layout/item_message_channel_card.xml b/src/main/res/layout/item_message_channel_card.xml
index c6a9a60..e5787f9 100644
--- a/src/main/res/layout/item_message_channel_card.xml
+++ b/src/main/res/layout/item_message_channel_card.xml
@@ -56,9 +56,20 @@
                     android:layout_height="wrap_content"
                     android:textAppearance="?attr/textAppearanceLabelSmall"
                     android:alpha="0.55"
-                    android:paddingBottom="6dp"
+                    android:paddingBottom="2dp"
                     android:visibility="gone" />
 
+                <!-- Timestamp — sits right after the channel label (or right under the
+                     thumbnail when there is no label), left-aligned for LTR -->
+                <TextView
+                    android:id="@+id/channel_card_time"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_gravity="start"
+                    android:textAppearance="?attr/textAppearanceLabelSmall"
+                    android:alpha="0.45"
+                    android:paddingBottom="6dp" />
+
                 <!-- Rendered title + body -->
                 <TextView
                     android:id="@+id/channel_card_text"
@@ -109,15 +120,6 @@
                     app:icon="@drawable/ic_location_pin_24dp"
                     app:iconGravity="textStart" />
 
-                <!-- Timestamp -->
-                <TextView
-                    android:id="@+id/channel_card_time"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:layout_gravity="end"
-                    android:textAppearance="?attr/textAppearanceLabelSmall"
-                    android:alpha="0.45"
-                    android:paddingTop="4dp" />
 
             </LinearLayout>
         </LinearLayout>
diff --git a/src/main/res/menu/message_context.xml b/src/main/res/menu/message_context.xml
index 5f36191..0a07171 100644
--- a/src/main/res/menu/message_context.xml
+++ b/src/main/res/menu/message_context.xml
@@ -1,6 +1,11 @@
 <?xml version="1.0" encoding="utf-8"?>
 <menu xmlns:android="http://schemas.android.com/apk/res/android">
 
+    <item
+        android:id="@+id/action_select_message"
+        android:title="@string/select_messages"
+        android:visible="false" />
+
     <item
         android:id="@+id/action_add_reaction"
         android:title="@string/add_reaction"
diff --git a/src/main/res/menu/message_selection_action.xml b/src/main/res/menu/message_selection_action.xml
new file mode 100644
index 0000000..0a36627
--- /dev/null
+++ b/src/main/res/menu/message_selection_action.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+
+    <item
+        android:id="@+id/action_forward_selected"
+        android:icon="@drawable/ic_share_24dp"
+        android:title="@string/forward_to_contacts"
+        app:showAsAction="always" />
+
+</menu>
diff --git a/src/main/res/values-ar/strings.xml b/src/main/res/values-ar/strings.xml
index 288a844..6c4db96 100644
--- a/src/main/res/values-ar/strings.xml
+++ b/src/main/res/values-ar/strings.xml
@@ -1397,4 +1397,180 @@
     <string name="pref_incoming_call_notification_settings">إعدادات إشعارات المكالمات الواردة</string>
     <string name="pref_message_notification_settings">إعدادات إشعارات الرسائل</string>
     <string name="presence_offline">غير متصل</string>
+    <string name="select_messages">تحديد الرسائل</string>
+    <string name="messages_selected">محدد</string>
+    <string name="multi_share_self">أنا (%s)</string>
+    <string name="action_set_ephemeral_timer">رسائل مختفية</string>
+    <string name="blog">مدوّنة</string>
+    <string name="days_ago">منذ %d أيام</string>
+    <string name="hours_ago">منذ %d ساعات</string>
+    <string name="debug_blogs_deleted">تم حذف جميع مقالات المدوّنة</string>
+    <string name="debug_category_info">التشخيصات</string>
+    <string name="debug_category_pubsub">بيانات PubSub</string>
+    <string name="debug_copied">تم النسخ إلى الحافظة</string>
+    <string name="debug_copy_build_info_summary">نسخ تفاصيل الإصدار والجهاز والبناء إلى الحافظة</string>
+    <string name="debug_copy_build_info_title">نسخ معلومات البناء</string>
+    <string name="debug_delete_blogs_confirm">سيؤدي هذا إلى حذف جميع مقالاتك في المدوّنة نهائياً من الخادم. هل تريد المتابعة؟</string>
+    <string name="debug_delete_blogs_summary">سحب جميع مقالات المدوّنة من عقدة PubSub الخاصة بك على جميع الحسابات</string>
+    <string name="debug_delete_blogs_title">حذف جميع مقالات مدوّنتي</string>
+    <string name="debug_delete_stories_confirm">سيؤدي هذا إلى حذف جميع قصصك نهائياً من الخادم. لن يتمكن المستخدمون الآخرون من رؤيتها. هل تريد المتابعة؟</string>
+    <string name="debug_delete_stories_summary">سحب جميع عناصر القصص من عقدة PubSub الخاصة بك على جميع الحسابات</string>
+    <string name="debug_delete_stories_title">حذف جميع قصصي</string>
+    <string name="debug_fetch_failed">فشل جلب العناصر من الخادم</string>
+    <string name="debug_log_accounts_summary">تفريغ حالة اتصال الحساب إلى logcat (الوسم: jabjab)</string>
+    <string name="debug_log_accounts_title">تسجيل حالة الحساب</string>
+    <string name="debug_menu_summary">أدوات إعادة ضبط البيانات الخطرة</string>
+    <string name="debug_menu_title">خيارات المطوّر</string>
+    <string name="debug_no_accounts">لا توجد حسابات متاحة</string>
+    <string name="debug_nothing_to_delete">لا يوجد شيء لحذفه</string>
+    <string name="debug_refetch_summary">فرض إعادة جلب القصص من جميع جهات الاتصال (افتح شاشة القصص للتشغيل)</string>
+    <string name="debug_refetch_title">إعادة جلب القصص</string>
+    <string name="debug_stories_deleted">تم حذف جميع القصص</string>
+    <string name="debug_working">جارٍ العمل…</string>
+    <string name="ephemeral_timer_15m">١٥ دقيقة</string>
+    <string name="ephemeral_timer_1d">يوم واحد</string>
+    <string name="ephemeral_timer_1h">ساعة واحدة</string>
+    <string name="ephemeral_timer_1m">دقيقة واحدة</string>
+    <string name="ephemeral_timer_1w">أسبوع واحد</string>
+    <string name="ephemeral_timer_30m">٣٠ دقيقة</string>
+    <string name="ephemeral_timer_30s">٣٠ ثانية</string>
+    <string name="ephemeral_timer_3h">٣ ساعات</string>
+    <string name="ephemeral_timer_5m">٥ دقائق</string>
+    <string name="ephemeral_timer_current">الحالي: %s</string>
+    <string name="ephemeral_timer_disabled">تم إيقاف تشغيل الرسائل المختفية</string>
+    <string name="ephemeral_timer_off">إيقاف</string>
+    <string name="ephemeral_timer_set">تم ضبط الرسائل المختفية على %s</string>
+    <string name="forward_to_contacts">إعادة توجيه إلى جهات اتصال متعددة…</string>
+    <string name="multi_share_done">تم الإرسال إلى %1$d جهة اتصال</string>
+    <string name="multi_share_no_contacts">لم يتم العثور على جهات اتصال</string>
+    <string name="multi_share_pick_account">إرسال من الحساب</string>
+    <string name="multi_share_search_hint">البحث في جهات الاتصال…</string>
+    <string name="multi_share_send">إرسال</string>
+    <string name="multi_share_sending">جارٍ الإرسال إلى %1$d جهة اتصال…</string>
+    <string name="multi_share_send_to">إرسال إلى %1$d جهة اتصال</string>
+    <string name="multi_share_title">مشاركة مع جهات الاتصال</string>
+    <string name="notification_ephemeral_message">🔥 رسالة مختفية</string>
+    <string name="notification_new_message">رسالة جديدة</string>
+    <string name="pref_category_encryption_options">خيارات التشفير</string>
+    <string name="pref_category_notifications">الإشعارات</string>
+    <string name="pref_hide_notification_content">إخفاء محتوى الرسالة في الإشعارات</string>
+    <string name="pref_hide_notification_content_summary">عرض \"رسالة جديدة\" بدلاً من النص الفعلي. يتم التفعيل تلقائياً عند تشغيل قفل التطبيق.</string>
+    <string name="pref_hide_openpgp">إخفاء خيار OpenPGP</string>
+    <string name="pref_hide_openpgp_summary">إزالة خيار تشفير OpenPGP من المحادثات. يحتاج معظم المستخدمين إلى OMEMO فقط.</string>
+    <string name="restore_minimize_hint">جارٍ استعادة نسختك الاحتياطية. قد يستغرق هذا بضع دقائق للحسابات الكبيرة. يمكنك تصغير هذه الشاشة ومراقبة شريط الإشعارات. سيخطرك JabJab عند الانتهاء.</string>
+    <string name="retract_message">سحب</string>
+    <string name="see_all_posts">عرض جميع المقالات</string>
+    <string name="stories">القصص</string>
+    <string name="story_phase_compressing">جارٍ ضغط الفيديو…</string>
+    <string name="story_phase_preparing">جارٍ التحضير…</string>
+    <string name="story_phase_publishing">جارٍ النشر…</string>
+    <string name="story_phase_trimming">جارٍ قص الفيديو…</string>
+    <string name="story_phase_uploading">جارٍ الرفع…</string>
+    <string name="story_phase_uploading_video">جارٍ رفع الفيديو</string>
+    <string name="story_video_will_compress">حجم الفيديو %1$s — سيتم ضغطه قبل الرفع</string>
+    <string name="update_available_with_code">التحديث %1$s (%2$d) متاح</string>
+    <string name="update_cancel">إلغاء</string>
+    <string name="update_downloading_progress">جارٍ التنزيل… %d%%</string>
+    <string name="wizard_privacy_disappearing_desc">اضبط مؤقتاً على أي محادثة لحذف الرسائل تلقائياً بعد قراءتها. فعّله عبر أيقونة الساعة في شريط أدوات الدردشة.</string>
+    <string name="wizard_privacy_disappearing_title">الرسائل المختفية</string>
+    <string name="wizard_privacy_retraction_desc">اضغط مطولاً على أي رسالة أرسلتها لسحبها. تتم إزالة الرسالة من كلا طرفي المحادثة.</string>
+    <string name="wizard_privacy_retraction_title">سحب الرسائل</string>
+    <string name="wizard_privacy_translation_desc">اضغط مطولاً على أي رسالة مستلمة واضغط ترجمة لقراءتها بلغتك. تُرسل الترجمات إلى Google Translate — يغادر نص الرسالة جهازك.</string>
+    <string name="wizard_privacy_translation_title">ترجمة الرسائل</string>
+    <string name="wizard_security_omemo_summary">ستبدأ المحادثات الجديدة بتشفير OMEMO مفعّل. يمكنك إيقافه لكل دردشة على حدة.</string>
+    <string name="wizard_security_subtitle">إعداد قفل التطبيق وتكوين إعدادات التشفير الافتراضية.</string>
+    <string name="wizard_security_title">الأمان</string>
+    <plurals name="attachments_saved">
+        <item quantity="zero">لم يتم حفظ أي ملف وسائط</item>
+        <item quantity="one">تم حفظ ملف وسائط واحد</item>
+        <item quantity="two">تم حفظ ملفَي وسائط</item>
+        <item quantity="few">تم حفظ %d ملفات وسائط</item>
+        <item quantity="many">تم حفظ %d ملف وسائط</item>
+        <item quantity="other">تم حفظ %d ملف وسائط</item>
+    </plurals>
+    <plurals name="delete_files_dialog">
+        <item quantity="zero">حذف الملفات</item>
+        <item quantity="one">حذف الملف</item>
+        <item quantity="two">حذف ملفَين</item>
+        <item quantity="few">حذف %d ملفات</item>
+        <item quantity="many">حذف %d ملفاً</item>
+        <item quantity="other">حذف %d ملف</item>
+    </plurals>
+    <plurals name="delete_files_dialog_msg">
+        <item quantity="one">هل أنت متأكد أنك تريد حذف هذا الملف؟</item>
+        <item quantity="other">هل أنت متأكد أنك تريد حذف هذه الملفات؟</item>
+    </plurals>
+    <plurals name="delete_n_messages">
+        <item quantity="zero">حذف الرسائل</item>
+        <item quantity="one">حذف رسالة واحدة</item>
+        <item quantity="two">حذف رسالتين</item>
+        <item quantity="few">حذف %d رسائل</item>
+        <item quantity="many">حذف %d رسالة</item>
+        <item quantity="other">حذف %d رسالة</item>
+    </plurals>
+    <plurals name="n_missed_calls">
+        <item quantity="zero">لا توجد مكالمات فائتة</item>
+        <item quantity="one">مكالمة فائتة واحدة</item>
+        <item quantity="two">مكالمتان فائتتان</item>
+        <item quantity="few">%d مكالمات فائتة</item>
+        <item quantity="many">%d مكالمة فائتة</item>
+        <item quantity="other">%d مكالمة فائتة</item>
+    </plurals>
+    <plurals name="n_missed_calls_from_m_contacts">
+        <item quantity="few">%1$d مكالمات فائتة من %2$d جهات اتصال</item>
+        <item quantity="many">%1$d مكالمة فائتة من %2$d جهة اتصال</item>
+        <item quantity="other">%1$d مكالمة فائتة من %2$d جهة اتصال</item>
+    </plurals>
+    <plurals name="n_missed_calls_from_x">
+        <item quantity="one">مكالمة فائتة واحدة من %2$s</item>
+        <item quantity="other">%1$d مكالمة فائتة من %2$s</item>
+    </plurals>
+    <plurals name="some_messages_could_not_be_delivered">
+        <item quantity="one">تعذّر تسليم رسالة</item>
+        <item quantity="other">تعذّر تسليم بعض الرسائل</item>
+    </plurals>
+    <plurals name="stickers_count">
+        <item quantity="zero">لا يوجد ملصقات</item>
+        <item quantity="one">ملصق واحد</item>
+        <item quantity="two">ملصقان</item>
+        <item quantity="few">%d ملصقات</item>
+        <item quantity="many">%d ملصقاً</item>
+        <item quantity="other">%d ملصق</item>
+    </plurals>
+    <plurals name="stickers_imported">
+        <item quantity="zero">لم يتم استيراد أي ملصقات</item>
+        <item quantity="one">تم استيراد ملصق واحد</item>
+        <item quantity="two">تم استيراد ملصقَين</item>
+        <item quantity="few">تم استيراد %d ملصقات</item>
+        <item quantity="many">تم استيراد %d ملصقاً</item>
+        <item quantity="other">تم استيراد %d ملصق</item>
+    </plurals>
+    <plurals name="toast_delete_certificates">
+        <item quantity="one">تم حذف %d شهادة</item>
+        <item quantity="other">تم حذف %d شهادات</item>
+    </plurals>
+    <plurals name="view_users">
+        <item quantity="zero">عرض المشاركين</item>
+        <item quantity="one">عرض مشارك واحد</item>
+        <item quantity="two">عرض مشاركَين</item>
+        <item quantity="few">عرض %1$d مشاركين</item>
+        <item quantity="many">عرض %1$d مشاركاً</item>
+        <item quantity="other">عرض %1$d مشارك</item>
+    </plurals>
+    <plurals name="x_participants">
+        <item quantity="zero">لا يوجد مشاركون</item>
+        <item quantity="one">مشارك واحد</item>
+        <item quantity="two">مشاركان</item>
+        <item quantity="few">%1$d مشاركون</item>
+        <item quantity="many">%1$d مشاركاً</item>
+        <item quantity="other">%1$d مشارك</item>
+    </plurals>
+    <plurals name="x_unread_conversations">
+        <item quantity="zero">لا توجد محادثات غير مقروءة</item>
+        <item quantity="one">محادثة غير مقروءة واحدة</item>
+        <item quantity="two">محادثتان غير مقروءتان</item>
+        <item quantity="few">%d محادثات غير مقروءة</item>
+        <item quantity="many">%d محادثة غير مقروءة</item>
+        <item quantity="other">%d محادثة غير مقروءة</item>
+    </plurals>
 </resources>
diff --git a/src/main/res/values-de/strings.xml b/src/main/res/values-de/strings.xml
index 37ff6d7..2cd19a3 100644
--- a/src/main/res/values-de/strings.xml
+++ b/src/main/res/values-de/strings.xml
@@ -1426,4 +1426,95 @@
     <string name="blog_draft_untitled">(unbenannter Entwurf)</string>
     <string name="blog_char_counter">%1$d / %2$d</string>
     <string name="story_requires_connection">Du bist offline — für Storys ist eine Internetverbindung erforderlich</string>
+    <string name="select_messages">Nachrichten auswählen</string>
+    <string name="messages_selected">ausgewählt</string>
+    <string name="multi_share_self">Ich (%s)</string>
+    <string name="action_set_ephemeral_timer">Verschwindende Nachrichten</string>
+    <string name="blog">Blog</string>
+    <string name="days_ago">Vor %d Tagen</string>
+    <string name="hours_ago">Vor %d Stunden</string>
+    <string name="debug_blogs_deleted">Alle Blog-Beiträge gelöscht</string>
+    <string name="debug_category_info">Diagnose</string>
+    <string name="debug_category_pubsub">PubSub-Daten</string>
+    <string name="debug_copied">In Zwischenablage kopiert</string>
+    <string name="debug_copy_build_info_summary">Version, Gerät und Build-Details in die Zwischenablage kopieren</string>
+    <string name="debug_copy_build_info_title">Build-Info kopieren</string>
+    <string name="debug_delete_blogs_confirm">Alle deine Blog-Beiträge werden dauerhaft vom Server gelöscht. Fortfahren?</string>
+    <string name="debug_delete_blogs_summary">Alle Blog-Einträge von deinem PubSub-Knoten auf allen Konten zurückziehen</string>
+    <string name="debug_delete_blogs_title">Alle meine Blog-Beiträge löschen</string>
+    <string name="debug_delete_stories_confirm">Alle deine Story-Beiträge werden dauerhaft vom Server gelöscht. Andere Nutzer können sie nicht mehr sehen. Fortfahren?</string>
+    <string name="debug_delete_stories_summary">Alle Story-Elemente von deinem PubSub-Knoten auf allen Konten zurückziehen</string>
+    <string name="debug_delete_stories_title">Alle meine Storys löschen</string>
+    <string name="debug_fetch_failed">Elemente vom Server konnten nicht abgerufen werden</string>
+    <string name="debug_log_accounts_summary">Kontoverbindungsstatus in logcat ausgeben (Tag: jabjab)</string>
+    <string name="debug_log_accounts_title">Kontostatus protokollieren</string>
+    <string name="debug_menu_summary">Gefährliche Datenrücksetz-Werkzeuge</string>
+    <string name="debug_menu_title">Entwickleroptionen</string>
+    <string name="debug_no_accounts">Keine Konten verfügbar</string>
+    <string name="debug_nothing_to_delete">Nichts zu löschen</string>
+    <string name="debug_refetch_summary">Erneutes Abrufen von Storys aller Kontakte erzwingen (Story-Bildschirm öffnen, um auszulösen)</string>
+    <string name="debug_refetch_title">Storys neu laden</string>
+    <string name="debug_stories_deleted">Alle Storys gelöscht</string>
+    <string name="debug_working">Wird bearbeitet…</string>
+    <string name="ephemeral_timer_15m">15 Minuten</string>
+    <string name="ephemeral_timer_1d">1 Tag</string>
+    <string name="ephemeral_timer_1h">1 Stunde</string>
+    <string name="ephemeral_timer_1m">1 Minute</string>
+    <string name="ephemeral_timer_1w">1 Woche</string>
+    <string name="ephemeral_timer_30m">30 Minuten</string>
+    <string name="ephemeral_timer_30s">30 Sekunden</string>
+    <string name="ephemeral_timer_3h">3 Stunden</string>
+    <string name="ephemeral_timer_5m">5 Minuten</string>
+    <string name="ephemeral_timer_current">Aktuell: %s</string>
+    <string name="ephemeral_timer_disabled">Verschwindende Nachrichten deaktiviert</string>
+    <string name="ephemeral_timer_off">Aus</string>
+    <string name="ephemeral_timer_set">Verschwindende Nachrichten auf %s eingestellt</string>
+    <string name="forward_to_contacts">An mehrere Kontakte weiterleiten…</string>
+    <string name="multi_share_done">An %1$d Kontakte gesendet</string>
+    <string name="multi_share_no_contacts">Keine Kontakte gefunden</string>
+    <string name="multi_share_pick_account">Von Konto senden</string>
+    <string name="multi_share_search_hint">Kontakte suchen…</string>
+    <string name="multi_share_send">Senden</string>
+    <string name="multi_share_sending">Wird an %1$d Kontakte gesendet…</string>
+    <string name="multi_share_send_to">An %1$d Kontakte senden</string>
+    <string name="multi_share_title">Mit Kontakten teilen</string>
+    <string name="notification_ephemeral_message">🔥 Verschwindende Nachricht</string>
+    <string name="notification_new_message">Neue Nachricht</string>
+    <string name="pref_category_encryption_options">Verschlüsselungsoptionen</string>
+    <string name="pref_category_notifications">Benachrichtigungen</string>
+    <string name="pref_hide_notification_content">Nachrichteninhalt in Benachrichtigungen ausblenden</string>
+    <string name="pref_hide_notification_content_summary">&#8222;Neue Nachricht&#8220; statt dem tatsächlichen Text anzeigen. Wird automatisch aktiviert, wenn die App-Sperre eingeschaltet ist.</string>
+    <string name="pref_hide_openpgp">OpenPGP-Option ausblenden</string>
+    <string name="pref_hide_openpgp_summary">OpenPGP-Verschlüsselungsoption aus Unterhaltungen entfernen. Die meisten Nutzer benötigen nur OMEMO.</string>
+    <string name="restore_minimize_hint">Dein Backup wird wiederhergestellt. Das kann bei großen Konten einige Minuten dauern. Du kannst diesen Bildschirm minimieren und die Benachrichtigungsleiste beobachten. JabJab benachrichtigt dich, wenn es fertig ist.</string>
+    <string name="retract_message">Widerrufen</string>
+    <string name="see_all_posts">Alle Beiträge anzeigen</string>
+    <string name="stories">Storys</string>
+    <string name="story_phase_compressing">Video wird komprimiert…</string>
+    <string name="story_phase_preparing">Wird vorbereitet…</string>
+    <string name="story_phase_publishing">Wird veröffentlicht…</string>
+    <string name="story_phase_trimming">Video wird geschnitten…</string>
+    <string name="story_phase_uploading">Wird hochgeladen…</string>
+    <string name="story_phase_uploading_video">Video wird hochgeladen</string>
+    <string name="story_video_will_compress">Video ist %1$s — wird vor dem Hochladen komprimiert</string>
+    <string name="update_available_with_code">Update %1$s (%2$d) verfügbar</string>
+    <string name="update_cancel">Abbrechen</string>
+    <string name="update_downloading_progress">Wird heruntergeladen… %d%%</string>
+    <string name="wizard_privacy_disappearing_desc">Stelle einen Timer für jede Unterhaltung ein, damit Nachrichten nach dem Lesen automatisch gelöscht werden. Aktiviere ihn über das Uhr-Symbol in der Chat-Symbolleiste.</string>
+    <string name="wizard_privacy_disappearing_title">Verschwindende Nachrichten</string>
+    <string name="wizard_privacy_retraction_desc">Halte eine gesendete Nachricht gedrückt, um sie zu widerrufen. Die Nachricht wird auf beiden Seiten der Unterhaltung entfernt.</string>
+    <string name="wizard_privacy_retraction_title">Nachrichtenwiderruf</string>
+    <string name="wizard_privacy_translation_desc">Halte eine empfangene Nachricht gedrückt und tippe auf Übersetzen, um sie in deiner Sprache zu lesen. Übersetzungen werden an Google Translate gesendet — der Nachrichtentext verlässt dein Gerät.</string>
+    <string name="wizard_privacy_translation_title">Nachrichtenübersetzung</string>
+    <string name="wizard_security_omemo_summary">Neue Unterhaltungen starten mit aktivierter OMEMO-Verschlüsselung. Du kannst sie für einzelne Chats deaktivieren.</string>
+    <string name="wizard_security_subtitle">App-Sperre einrichten und Verschlüsselungsstandards konfigurieren.</string>
+    <string name="wizard_security_title">Sicherheit</string>
+    <plurals name="stickers_count">
+        <item quantity="one">1 Sticker</item>
+        <item quantity="other">%d Sticker</item>
+    </plurals>
+    <plurals name="stickers_imported">
+        <item quantity="one">1 Sticker importiert</item>
+        <item quantity="other">%d Sticker importiert</item>
+    </plurals>
 </resources>
diff --git a/src/main/res/values-tr/strings.xml b/src/main/res/values-tr/strings.xml
index 9246f06..d912495 100644
--- a/src/main/res/values-tr/strings.xml
+++ b/src/main/res/values-tr/strings.xml
@@ -49,4 +49,143 @@
     <string name="jabjab_download_already_latest">Zaten en son sürümdesiniz</string>
     <string name="update_check_failed">Güncellemeler kontrol edilemedi</string>
     <string name="blog_search_hint">Yazı ara…</string>
+    <string name="action_set_ephemeral_timer">Kaybolan mesajlar</string>
+    <string name="blog">Blog</string>
+    <string name="days_ago">%d gün önce</string>
+    <string name="hours_ago">%d saat önce</string>
+    <string name="debug_blogs_deleted">Tüm blog gönderileri silindi</string>
+    <string name="debug_category_info">Tanılama</string>
+    <string name="debug_category_pubsub">PubSub verileri</string>
+    <string name="debug_copied">Panoya kopyalandı</string>
+    <string name="debug_copy_build_info_summary">Sürüm, cihaz ve derleme ayrıntılarını panoya kopyala</string>
+    <string name="debug_copy_build_info_title">Derleme bilgisini kopyala</string>
+    <string name="debug_delete_blogs_confirm">Bu işlem tüm blog gönderilerinizi sunucudan kalıcı olarak silecektir. Devam etmek istiyor musunuz?</string>
+    <string name="debug_delete_blogs_summary">Tüm hesaplardaki PubSub düğümünüzdeki tüm blog girdilerini geri çekin</string>
+    <string name="debug_delete_blogs_title">Tüm blog gönderilerimi sil</string>
+    <string name="debug_delete_stories_confirm">Bu işlem tüm hikaye gönderilerinizi sunucudan kalıcı olarak silecektir. Diğer kullanıcılar artık bunları göremez. Devam etmek istiyor musunuz?</string>
+    <string name="debug_delete_stories_summary">Tüm hesaplardaki PubSub düğümünüzdeki tüm hikaye öğelerini geri çekin</string>
+    <string name="debug_delete_stories_title">Tüm hikayelerimi sil</string>
+    <string name="debug_fetch_failed">Sunucudan öğeler alınamadı</string>
+    <string name="debug_log_accounts_summary">Hesap bağlantı durumunu logcat\'e aktar (etiket: jabjab)</string>
+    <string name="debug_log_accounts_title">Hesap durumunu kaydet</string>
+    <string name="debug_menu_summary">Tehlikeli veri sıfırlama araçları</string>
+    <string name="debug_menu_title">Geliştirici seçenekleri</string>
+    <string name="debug_no_accounts">Kullanılabilir hesap yok</string>
+    <string name="debug_nothing_to_delete">Silinecek bir şey yok</string>
+    <string name="debug_refetch_summary">Tüm kişilerin hikayelerini yeniden almaya zorla (tetiklemek için Hikayeler ekranını açın)</string>
+    <string name="debug_refetch_title">Hikayeleri yeniden getir</string>
+    <string name="debug_stories_deleted">Tüm hikayeler silindi</string>
+    <string name="debug_working">İşleniyor…</string>
+    <string name="ephemeral_timer_15m">15 dakika</string>
+    <string name="ephemeral_timer_1d">1 gün</string>
+    <string name="ephemeral_timer_1h">1 saat</string>
+    <string name="ephemeral_timer_1m">1 dakika</string>
+    <string name="ephemeral_timer_1w">1 hafta</string>
+    <string name="ephemeral_timer_30m">30 dakika</string>
+    <string name="ephemeral_timer_30s">30 saniye</string>
+    <string name="ephemeral_timer_3h">3 saat</string>
+    <string name="ephemeral_timer_5m">5 dakika</string>
+    <string name="ephemeral_timer_current">Şu an: %s</string>
+    <string name="ephemeral_timer_disabled">Kaybolan mesajlar kapatıldı</string>
+    <string name="ephemeral_timer_off">Kapalı</string>
+    <string name="ephemeral_timer_set">Kaybolan mesajlar %s olarak ayarlandı</string>
+    <string name="forward_to_contacts">Birden fazla kişiye ilet…</string>
+    <string name="select_messages">Mesaj seç</string>
+    <string name="messages_selected">seçildi</string>
+    <string name="multi_share_self">Ben (%s)</string>
+    <string name="multi_share_done">%1$d kişiye gönderildi</string>
+    <string name="multi_share_no_contacts">Kişi bulunamadı</string>
+    <string name="multi_share_pick_account">Hesaptan gönder</string>
+    <string name="multi_share_search_hint">Kişileri ara…</string>
+    <string name="multi_share_send">Gönder</string>
+    <string name="multi_share_sending">%1$d kişiye gönderiliyor…</string>
+    <string name="multi_share_send_to">%1$d kişiye gönder</string>
+    <string name="multi_share_title">Kişilerle paylaş</string>
+    <string name="notification_ephemeral_message">🔥 Kaybolan mesaj</string>
+    <string name="notification_new_message">Yeni mesaj</string>
+    <string name="pref_category_encryption_options">Şifreleme seçenekleri</string>
+    <string name="pref_category_notifications">Bildirimler</string>
+    <string name="pref_hide_notification_content">Bildirimlerde mesaj içeriğini gizle</string>
+    <string name="pref_hide_notification_content_summary">Gerçek metin yerine \"Yeni mesaj\" göster. Uygulama kilidi açıkken otomatik olarak etkinleşir.</string>
+    <string name="pref_hide_openpgp">OpenPGP seçeneğini gizle</string>
+    <string name="pref_hide_openpgp_summary">OpenPGP şifreleme seçeneğini konuşmalardan kaldır. Çoğu kullanıcının yalnızca OMEMO\'ya ihtiyacı vardır.</string>
+    <string name="restore_minimize_hint">Yedeğiniz geri yükleniyor. Büyük hesaplar için bu birkaç dakika sürebilir. Bu ekranı küçültebilir ve bildirim çubuğunu izleyebilirsiniz. JabJab işlem tamamlandığında sizi bilgilendirecek.</string>
+    <string name="retract_message">Geri çek</string>
+    <string name="see_all_posts">Tüm gönderileri gör</string>
+    <string name="stories">Hikayeler</string>
+    <string name="story_phase_compressing">Video sıkıştırılıyor…</string>
+    <string name="story_phase_preparing">Hazırlanıyor…</string>
+    <string name="story_phase_publishing">Yayınlanıyor…</string>
+    <string name="story_phase_trimming">Video kırpılıyor…</string>
+    <string name="story_phase_uploading">Yükleniyor…</string>
+    <string name="story_phase_uploading_video">Video yükleniyor</string>
+    <string name="story_video_will_compress">Video %1$s — yüklemeden önce sıkıştırılacak</string>
+    <string name="update_available_with_code">%1$s (%2$d) güncellemesi mevcut</string>
+    <string name="update_cancel">İptal</string>
+    <string name="update_downloading_progress">İndiriliyor… %d%%</string>
+    <string name="wizard_privacy_disappearing_desc">Okunurken otomatik silinmesi için herhangi bir konuşmaya zamanlayıcı ayarlayın. Sohbet araç çubuğundaki saat simgesi aracılığıyla etkinleştirin.</string>
+    <string name="wizard_privacy_disappearing_title">Kaybolan mesajlar</string>
+    <string name="wizard_privacy_retraction_desc">Gönderdiğiniz herhangi bir mesaja uzun basın. Mesaj konuşmanın her iki tarafından da kaldırılır.</string>
+    <string name="wizard_privacy_retraction_title">Mesaj geri çekme</string>
+    <string name="wizard_privacy_translation_desc">Alınan herhangi bir mesaja uzun basın ve kendi dilinizde okumak için Çevir\'e dokunun. Çeviriler Google Translate\'e gönderilir — mesaj metni cihazınızdan ayrılır.</string>
+    <string name="wizard_privacy_translation_title">Mesaj çevirisi</string>
+    <string name="wizard_security_omemo_summary">Yeni konuşmalar OMEMO şifrelemesi açık olarak başlayacak. Her sohbet için kapatabilirsiniz.</string>
+    <string name="wizard_security_subtitle">Uygulama kilidini ayarlayın ve şifreleme varsayılanlarını yapılandırın.</string>
+    <string name="wizard_security_title">Güvenlik</string>
+    <plurals name="attachments_saved">
+        <item quantity="one">%d medya dosyası kaydedildi</item>
+        <item quantity="other">%d medya dosyası kaydedildi</item>
+    </plurals>
+    <plurals name="delete_files_dialog">
+        <item quantity="one">Dosyayı sil</item>
+        <item quantity="other">%d dosyayı sil</item>
+    </plurals>
+    <plurals name="delete_files_dialog_msg">
+        <item quantity="one">Bu dosyayı silmek istediğinizden emin misiniz?</item>
+        <item quantity="other">Bu dosyaları silmek istediğinizden emin misiniz?</item>
+    </plurals>
+    <plurals name="delete_n_messages">
+        <item quantity="one">%d mesajı sil</item>
+        <item quantity="other">%d mesajı sil</item>
+    </plurals>
+    <plurals name="n_missed_calls">
+        <item quantity="one">%d cevapsız arama</item>
+        <item quantity="other">%d cevapsız arama</item>
+    </plurals>
+    <plurals name="n_missed_calls_from_m_contacts">
+        <item quantity="one">%2$d kişiden %1$d cevapsız arama</item>
+        <item quantity="other">%2$d kişiden %1$d cevapsız arama</item>
+    </plurals>
+    <plurals name="n_missed_calls_from_x">
+        <item quantity="one">%2$s\'den %1$d cevapsız arama</item>
+        <item quantity="other">%2$s\'den %1$d cevapsız arama</item>
+    </plurals>
+    <plurals name="some_messages_could_not_be_delivered">
+        <item quantity="one">Bir mesaj teslim edilemedi</item>
+        <item quantity="other">Bazı mesajlar teslim edilemedi</item>
+    </plurals>
+    <plurals name="stickers_count">
+        <item quantity="one">1 çıkartma</item>
+        <item quantity="other">%d çıkartma</item>
+    </plurals>
+    <plurals name="stickers_imported">
+        <item quantity="one">1 çıkartma içe aktarıldı</item>
+        <item quantity="other">%d çıkartma içe aktarıldı</item>
+    </plurals>
+    <plurals name="toast_delete_certificates">
+        <item quantity="one">%d sertifika silindi</item>
+        <item quantity="other">%d sertifika silindi</item>
+    </plurals>
+    <plurals name="view_users">
+        <item quantity="one">%1$d katılımcıyı görüntüle</item>
+        <item quantity="other">%1$d katılımcıyı görüntüle</item>
+    </plurals>
+    <plurals name="x_participants">
+        <item quantity="one">%1$d katılımcı</item>
+        <item quantity="other">%1$d katılımcı</item>
+    </plurals>
+    <plurals name="x_unread_conversations">
+        <item quantity="one">%d okunmamış sohbet</item>
+        <item quantity="other">%d okunmamış sohbet</item>
+    </plurals>
 </resources>
diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml
index 2e9e38f..0ea692b 100644
--- a/src/main/res/values/strings.xml
+++ b/src/main/res/values/strings.xml
@@ -40,6 +40,9 @@
     <string name="title_activity_share_with">Share with…</string>
     <string name="multi_share_title">Share with contacts</string>
     <string name="forward_to_contacts">Forward to multiple contacts…</string>
+    <string name="select_messages">Select messages</string>
+    <string name="messages_selected">selected</string>
+    <string name="multi_share_self">Me (%s)</string>
     <string name="multi_share_search_hint">Search contacts…</string>
     <string name="multi_share_send">Send</string>
     <string name="multi_share_send_to">Send to %1$d contacts</string>

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.