diff --git a/bitchat/Localizable.xcstrings b/bitchat/Localizable.xcstrings index f869ab85..a3a89655 100644 --- a/bitchat/Localizable.xcstrings +++ b/bitchat/Localizable.xcstrings @@ -2083,6 +2083,754 @@ "zh-Hant" : { "stringUnit" : { "state" : "needs_review", "value" : "部分媒體無法刪除。請先嘗試刪除較舊的媒體。" } } } }, + "media.system.voice_mesh_only" : { + "comment" : "System message when the user tries to send a voice note outside a mesh chat", + "extractionState" : "manual", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "Voice notes are only available in mesh chats." + } + }, + "bn" : { + "stringUnit" : { + "state" : "translated", + "value" : "Voice notes are only available in mesh chats." + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sprachnachrichten sind nur in Mesh-Chats verfügbar." + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Voice notes are only available in mesh chats." + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Las notas de voz solo están disponibles en chats mesh." + } + }, + "fa" : { + "stringUnit" : { + "state" : "translated", + "value" : "Voice notes are only available in mesh chats." + } + }, + "fil" : { + "stringUnit" : { + "state" : "translated", + "value" : "Voice notes are only available in mesh chats." + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Les notes vocales ne sont disponibles que dans les chats mesh." + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "Voice notes are only available in mesh chats." + } + }, + "hi" : { + "stringUnit" : { + "state" : "translated", + "value" : "वॉइस नोट केवल mesh चैट में उपलब्ध हैं।" + } + }, + "id" : { + "stringUnit" : { + "state" : "translated", + "value" : "Voice notes are only available in mesh chats." + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Le note vocali sono disponibili solo nelle chat mesh." + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "ボイスノートはメッシュチャットでのみ利用できます。" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "음성 메모는 메시 채팅에서만 사용할 수 있습니다." + } + }, + "ms" : { + "stringUnit" : { + "state" : "translated", + "value" : "Voice notes are only available in mesh chats." + } + }, + "ne" : { + "stringUnit" : { + "state" : "translated", + "value" : "Voice notes are only available in mesh chats." + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Spraakberichten zijn alleen beschikbaar in mesh-chats." + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Notatki głosowe są dostępne tylko w czatach mesh." + } + }, + "pt" : { + "stringUnit" : { + "state" : "translated", + "value" : "Voice notes are only available in mesh chats." + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Notas de voz só estão disponíveis em chats mesh." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Голосовые заметки доступны только в mesh-чатах." + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Röstanteckningar är bara tillgängliga i mesh-chattar." + } + }, + "ta" : { + "stringUnit" : { + "state" : "translated", + "value" : "Voice notes are only available in mesh chats." + } + }, + "th" : { + "stringUnit" : { + "state" : "translated", + "value" : "Voice notes are only available in mesh chats." + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ses notları yalnızca mesh sohbetlerinde kullanılabilir." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Voice notes are only available in mesh chats." + } + }, + "ur" : { + "stringUnit" : { + "state" : "translated", + "value" : "Voice notes are only available in mesh chats." + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Voice notes are only available in mesh chats." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "语音便笺仅在 mesh 聊天中可用。" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "語音備註僅在 mesh 聊天中可用。" + } + } + } + }, + + "media.system.image_mesh_only" : { + "comment" : "System message when the user tries to send an image outside a mesh chat", + "extractionState" : "manual", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "Images are only available in mesh chats." + } + }, + "bn" : { + "stringUnit" : { + "state" : "translated", + "value" : "Images are only available in mesh chats." + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bilder sind nur in Mesh-Chats verfügbar." + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Images are only available in mesh chats." + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Las imágenes solo están disponibles en chats mesh." + } + }, + "fa" : { + "stringUnit" : { + "state" : "translated", + "value" : "Images are only available in mesh chats." + } + }, + "fil" : { + "stringUnit" : { + "state" : "translated", + "value" : "Images are only available in mesh chats." + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Les images ne sont disponibles que dans les chats mesh." + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "Images are only available in mesh chats." + } + }, + "hi" : { + "stringUnit" : { + "state" : "translated", + "value" : "छवियाँ केवल mesh चैट में उपलब्ध हैं।" + } + }, + "id" : { + "stringUnit" : { + "state" : "translated", + "value" : "Images are only available in mesh chats." + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Le immagini sono disponibili solo nelle chat mesh." + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "画像はメッシュチャットでのみ送信できます。" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "이미지는 메시 채팅에서만 사용할 수 있습니다." + } + }, + "ms" : { + "stringUnit" : { + "state" : "translated", + "value" : "Images are only available in mesh chats." + } + }, + "ne" : { + "stringUnit" : { + "state" : "translated", + "value" : "Images are only available in mesh chats." + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Afbeeldingen zijn alleen beschikbaar in mesh-chats." + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Obrazy są dostępne tylko w czatach mesh." + } + }, + "pt" : { + "stringUnit" : { + "state" : "translated", + "value" : "Images are only available in mesh chats." + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Imagens só estão disponíveis em chats mesh." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Изображения доступны только в mesh-чатах." + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bilder är bara tillgängliga i mesh-chattar." + } + }, + "ta" : { + "stringUnit" : { + "state" : "translated", + "value" : "Images are only available in mesh chats." + } + }, + "th" : { + "stringUnit" : { + "state" : "translated", + "value" : "Images are only available in mesh chats." + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Görseller yalnızca mesh sohbetlerinde kullanılabilir." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Images are only available in mesh chats." + } + }, + "ur" : { + "stringUnit" : { + "state" : "translated", + "value" : "Images are only available in mesh chats." + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Images are only available in mesh chats." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "图片仅在 mesh 聊天中可用。" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "圖片僅在 mesh 聊天中可用。" + } + } + } + }, + + "media.system.image_prepare_failed" : { + "comment" : "System message when an image cannot be prepared for sending", + "extractionState" : "manual", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "Failed to prepare image for sending." + } + }, + "bn" : { + "stringUnit" : { + "state" : "translated", + "value" : "Failed to prepare image for sending." + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bild konnte nicht zum Senden vorbereitet werden." + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Failed to prepare image for sending." + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "No se pudo preparar la imagen para enviarla." + } + }, + "fa" : { + "stringUnit" : { + "state" : "translated", + "value" : "Failed to prepare image for sending." + } + }, + "fil" : { + "stringUnit" : { + "state" : "translated", + "value" : "Failed to prepare image for sending." + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Échec de la préparation de l'image à l'envoi." + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "Failed to prepare image for sending." + } + }, + "hi" : { + "stringUnit" : { + "state" : "translated", + "value" : "भेजने के लिए छवि तैयार नहीं की जा सकी।" + } + }, + "id" : { + "stringUnit" : { + "state" : "translated", + "value" : "Failed to prepare image for sending." + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Preparazione dell'immagine per l'invio non riuscita." + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "送信用の画像の準備に失敗しました。" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "전송할 이미지를 준비하지 못했습니다." + } + }, + "ms" : { + "stringUnit" : { + "state" : "translated", + "value" : "Failed to prepare image for sending." + } + }, + "ne" : { + "stringUnit" : { + "state" : "translated", + "value" : "Failed to prepare image for sending." + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Afbeelding voorbereiden voor verzending mislukt." + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nie udało się przygotować obrazu do wysłania." + } + }, + "pt" : { + "stringUnit" : { + "state" : "translated", + "value" : "Failed to prepare image for sending." + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Falha ao preparar a imagem para envio." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Не удалось подготовить изображение к отправке." + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kunde inte förbereda bilden för sändning." + } + }, + "ta" : { + "stringUnit" : { + "state" : "translated", + "value" : "Failed to prepare image for sending." + } + }, + "th" : { + "stringUnit" : { + "state" : "translated", + "value" : "Failed to prepare image for sending." + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Görsel gönderim için hazırlanamadı." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Failed to prepare image for sending." + } + }, + "ur" : { + "stringUnit" : { + "state" : "translated", + "value" : "Failed to prepare image for sending." + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Failed to prepare image for sending." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "无法准备要发送的图片。" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "無法準備要傳送的圖片。" + } + } + } + }, + + "media.system.image_too_large" : { + "comment" : "System message when a selected image exceeds the send size limit", + "extractionState" : "manual", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "Image is too large to send." + } + }, + "bn" : { + "stringUnit" : { + "state" : "translated", + "value" : "Image is too large to send." + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bild ist zu groß zum Senden." + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Image is too large to send." + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "La imagen es demasiado grande para enviarla." + } + }, + "fa" : { + "stringUnit" : { + "state" : "translated", + "value" : "Image is too large to send." + } + }, + "fil" : { + "stringUnit" : { + "state" : "translated", + "value" : "Image is too large to send." + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "L'image est trop grande pour être envoyée." + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "Image is too large to send." + } + }, + "hi" : { + "stringUnit" : { + "state" : "translated", + "value" : "छवि भेजने के लिए बहुत बड़ी है।" + } + }, + "id" : { + "stringUnit" : { + "state" : "translated", + "value" : "Image is too large to send." + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "L'immagine è troppo grande per essere inviata." + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "画像が大きすぎて送信できません。" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "이미지가 너무 커서 보낼 수 없습니다." + } + }, + "ms" : { + "stringUnit" : { + "state" : "translated", + "value" : "Image is too large to send." + } + }, + "ne" : { + "stringUnit" : { + "state" : "translated", + "value" : "Image is too large to send." + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Afbeelding is te groot om te verzenden." + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Obraz jest za duży, by go wysłać." + } + }, + "pt" : { + "stringUnit" : { + "state" : "translated", + "value" : "Image is too large to send." + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "A imagem é grande demais para enviar." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Изображение слишком большое для отправки." + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bilden är för stor för att skicka." + } + }, + "ta" : { + "stringUnit" : { + "state" : "translated", + "value" : "Image is too large to send." + } + }, + "th" : { + "stringUnit" : { + "state" : "translated", + "value" : "Image is too large to send." + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Görsel gönderilemeyecek kadar büyük." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Image is too large to send." + } + }, + "ur" : { + "stringUnit" : { + "state" : "translated", + "value" : "Image is too large to send." + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Image is too large to send." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "图片太大,无法发送。" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "圖片太大,無法傳送。" + } + } + } + }, + "notification.action.wave" : { "comment" : "Title of the notification action button that sends a friendly wave back to a nearby person", "extractionState" : "manual", diff --git a/bitchat/ViewModels/ChatMediaTransferCoordinator.swift b/bitchat/ViewModels/ChatMediaTransferCoordinator.swift index fe314f62..d496fb41 100644 --- a/bitchat/ViewModels/ChatMediaTransferCoordinator.swift +++ b/bitchat/ViewModels/ChatMediaTransferCoordinator.swift @@ -551,7 +551,11 @@ final class ChatMediaTransferCoordinator { guard context.canSendMediaInCurrentContext else { SecureLogger.info("Voice note blocked outside mesh/private context", category: .session) try? FileManager.default.removeItem(at: url) - context.addSystemMessage("Voice notes are only available in mesh chats.") + context.addSystemMessage(String( + localized: "media.system.voice_mesh_only", + defaultValue: "Voice notes are only available in mesh chats.", + comment: "System message when the user tries to send a voice note outside a mesh chat" + )) return } @@ -694,7 +698,11 @@ final class ChatMediaTransferCoordinator { guard context.canSendMediaInCurrentContext else { SecureLogger.info("Image send blocked outside mesh/private context", category: .session) cleanup?() - context.addSystemMessage("Images are only available in mesh chats.") + context.addSystemMessage(String( + localized: "media.system.image_mesh_only", + defaultValue: "Images are only available in mesh chats.", + comment: "System message when the user tries to send an image outside a mesh chat" + )) return } @@ -705,7 +713,11 @@ final class ChatMediaTransferCoordinator { try ImageUtils.validateImageSource(at: sourceURL) } catch { SecureLogger.error("Image send preparation failed: \(error)", category: .session) - context.addSystemMessage("Failed to prepare image for sending.") + context.addSystemMessage(String( + localized: "media.system.image_prepare_failed", + defaultValue: "Failed to prepare image for sending.", + comment: "System message when an image cannot be prepared for sending" + )) return } @@ -764,7 +776,11 @@ final class ChatMediaTransferCoordinator { barrier.isCurrent(generation) else { return } - self.context.addSystemMessage("Image is too large to send.") + self.context.addSystemMessage(String( + localized: "media.system.image_too_large", + defaultValue: "Image is too large to send.", + comment: "System message when a selected image exceeds the send size limit" + )) } } catch { SecureLogger.error("Image send preparation failed: \(error)", category: .session) @@ -773,7 +789,11 @@ final class ChatMediaTransferCoordinator { barrier.isCurrent(generation) else { return } - self.context.addSystemMessage("Failed to prepare image for sending.") + self.context.addSystemMessage(String( + localized: "media.system.image_prepare_failed", + defaultValue: "Failed to prepare image for sending.", + comment: "System message when an image cannot be prepared for sending" + )) } } } diff --git a/bitchatTests/ChatMediaTransferCoordinatorContextTests.swift b/bitchatTests/ChatMediaTransferCoordinatorContextTests.swift index 1969602c..64bd7465 100644 --- a/bitchatTests/ChatMediaTransferCoordinatorContextTests.swift +++ b/bitchatTests/ChatMediaTransferCoordinatorContextTests.swift @@ -759,7 +759,11 @@ struct ChatMediaTransferCoordinatorContextTests { coordinator.sendVoiceNote(at: url) #expect(!FileManager.default.fileExists(atPath: url.path)) - #expect(context.systemMessages == ["Voice notes are only available in mesh chats."]) + #expect(context.systemMessages == [String( + localized: "media.system.voice_mesh_only", + defaultValue: "Voice notes are only available in mesh chats.", + comment: "System message when the user tries to send a voice note outside a mesh chat" + )]) #expect(context.privateChats.isEmpty) #expect(context.appendedPublicMessages.isEmpty) #expect(coordinator.transferIdToMessageIDs.isEmpty) diff --git a/bitchatTests/ChatViewModelExtensionsTests.swift b/bitchatTests/ChatViewModelExtensionsTests.swift index a1d12550..1faade3a 100644 --- a/bitchatTests/ChatViewModelExtensionsTests.swift +++ b/bitchatTests/ChatViewModelExtensionsTests.swift @@ -1164,7 +1164,11 @@ struct ChatViewModelMediaTransferTests { viewModel.sendImage(from: url) let didNotify = await TestHelpers.waitUntil({ - viewModel.messages.contains(where: { $0.sender == "system" && $0.content.contains("Failed to prepare image") }) + viewModel.messages.contains(where: { $0.sender == "system" && $0.content.contains(String( + localized: "media.system.image_prepare_failed", + defaultValue: "Failed to prepare image for sending.", + comment: "System message when an image cannot be prepared for sending" + )) }) }, timeout: TestConstants.longTimeout) #expect(didNotify) #expect(transport.sentPrivateFiles.isEmpty)