diff --git a/README.md b/README.md
index 5be099de..4e1056c9 100644
--- a/README.md
+++ b/README.md
@@ -116,3 +116,10 @@ For detailed protocol documentation, see the [Technical Whitepaper](WHITEPAPER.m
Want to try this on macos: `just run` will set it up and run from source.
Run `just clean` afterwards to restore things to original state for mobile app building and development.
+
+## Localization
+
+- Base app resources live under `bitchat/Localization/Base.lproj/`. Add new copy to `Localizable.strings` and plural rules to `Localizable.stringsdict`.
+- Share extension strings are separate in `bitchatShareExtension/Localization/Base.lproj/Localizable.strings`.
+- Prefer keys that describe intent (`app_info.features.offline.title`) and reuse existing ones where possible.
+- Run `xcodebuild -project bitchat.xcodeproj -scheme "bitchat (macOS)" -configuration Debug CODE_SIGNING_ALLOWED=NO build` to compile-check any localization updates.
diff --git a/bitchat.xcodeproj/project.pbxproj b/bitchat.xcodeproj/project.pbxproj
index 4dccc5ea..2e1e5860 100644
--- a/bitchat.xcodeproj/project.pbxproj
+++ b/bitchat.xcodeproj/project.pbxproj
@@ -304,6 +304,19 @@
knownRegions = (
Base,
en,
+ es,
+ ar,
+ de,
+ fr,
+ he,
+ id,
+ it,
+ ja,
+ ne,
+ "pt-BR",
+ ru,
+ uk,
+ "zh-Hans",
);
mainGroup = 18198ED912AAF495D8AF7763;
minimizedProjectReferenceProxies = 1;
diff --git a/bitchat/Localization/Base.lproj/Localizable.strings b/bitchat/Localization/Base.lproj/Localizable.strings
new file mode 100644
index 00000000..99bf6f5f
--- /dev/null
+++ b/bitchat/Localization/Base.lproj/Localizable.strings
@@ -0,0 +1,192 @@
+/*
+ Localizable.strings
+ Bitchat
+
+ Base English localization entries. Keep keys sorted alphabetically.
+*/
+
+"app_info.app_name" = "bitchat";
+"app_info.close" = "close";
+"app_info.done" = "DONE";
+"app_info.features.encryption.description" = "private messages encrypted with noise protocol";
+"app_info.features.encryption.title" = "end-to-end encryption";
+"app_info.features.extended_range.description" = "messages relay through peers, going the distance";
+"app_info.features.extended_range.title" = "extended range";
+"app_info.features.favorites.description" = "get notified when your favorite people join";
+"app_info.features.favorites.title" = "favorites";
+"app_info.features.geohash.description" = "geohash channels to chat with people in nearby regions over decentralized anonymous relays";
+"app_info.features.geohash.title" = "local channels";
+"app_info.features.mentions.description" = "use @nickname to notify specific people";
+"app_info.features.mentions.title" = "mentions";
+"app_info.features.offline.description" = "works without internet using Bluetooth low energy";
+"app_info.features.offline.title" = "offline communication";
+"app_info.features.title" = "FEATURES";
+"app_info.how_to_use.change_channels" = "• tap #mesh to change channels";
+"app_info.how_to_use.clear_chat" = "• triple-tap chat to clear";
+"app_info.how_to_use.commands" = "• type / for commands";
+"app_info.how_to_use.open_sidebar" = "• tap people icon for sidebar";
+"app_info.how_to_use.set_nickname" = "• set your nickname by tapping it";
+"app_info.how_to_use.start_dm" = "• tap a peer's name to start a DM";
+"app_info.how_to_use.title" = "HOW TO USE";
+"app_info.privacy.ephemeral.description" = "new peer ID generated regularly";
+"app_info.privacy.ephemeral.title" = "ephemeral identity";
+"app_info.privacy.no_tracking.description" = "no servers, accounts, or data collection";
+"app_info.privacy.no_tracking.title" = "no tracking";
+"app_info.privacy.panic.description" = "triple-tap logo to instantly clear all data";
+"app_info.privacy.panic.title" = "panic mode";
+"app_info.privacy.title" = "PRIVACY";
+"app_info.tagline" = "sidegroupchat";
+"app_info.warning.message" = "private message security has not yet been fully audited. do not use for critical situations until this warning disappears.";
+"app_info.warning.title" = "WARNING";
+"common.cancel" = "cancel";
+"common.close" = "close";
+"common.copy" = "copy";
+"common.ok" = "OK";
+"common.toggle.off" = "off";
+"common.toggle.on" = "on";
+"common.unknown" = "unknown";
+"content.accessibility.add_favorite" = "add to favorites";
+"content.accessibility.available_nostr" = "available via Nostr";
+"content.accessibility.back_to_main_chat" = "back to main chat";
+"content.accessibility.connected_mesh" = "connected via mesh";
+"content.accessibility.encryption_status" = "encryption status: %@";
+"content.accessibility.location_channels" = "location channels";
+"content.accessibility.location_notes" = "location notes for this place";
+"content.accessibility.open_unread_private_chat" = "open unread private chat";
+"content.accessibility.private_chat_header" = "private chat with %@";
+"content.accessibility.reachable_mesh" = "reachable via mesh";
+"content.accessibility.remove_favorite" = "remove from favorites";
+"content.accessibility.send_hint_empty" = "enter a message to send";
+"content.accessibility.send_hint_ready" = "double tap to send";
+"content.accessibility.send_message" = "send message";
+"content.accessibility.toggle_bookmark" = "toggle bookmark for #%@";
+"content.accessibility.toggle_favorite_hint" = "double tap to toggle favorite status";
+"content.accessibility.view_fingerprint_hint" = "tap to view encryption fingerprint";
+"content.actions.block" = "block";
+"content.actions.direct_message" = "direct message";
+"content.actions.hug" = "hug";
+"content.actions.mention" = "mention";
+"content.actions.slap" = "slap";
+"content.actions.title" = "actions";
+"content.alert.bluetooth_required.off" = "bluetooth is turned off. please turn on bluetooth in settings to use bitchat.";
+"content.alert.bluetooth_required.permission" = "bitchat needs bluetooth permission to connect with nearby devices. please enable bluetooth access in settings.";
+"content.alert.bluetooth_required.settings" = "settings";
+"content.alert.bluetooth_required.title" = "bluetooth required";
+"content.alert.bluetooth_required.unsupported" = "this device does not support bluetooth. bitchat requires bluetooth to function.";
+"content.alert.screenshot.message" = "screenshots of location channels will reveal your location. think before sharing publicly.";
+"content.alert.screenshot.title" = "heads up";
+"content.commands.block" = "block or list blocked peers";
+"content.commands.clear" = "clear chat messages";
+"content.commands.favorite" = "add to favorites";
+"content.commands.hug" = "send someone a warm hug";
+"content.commands.message" = "send private message";
+"content.commands.slap" = "slap someone with a trout";
+"content.commands.unblock" = "unblock a peer";
+"content.commands.unfavorite" = "remove from favorites";
+"content.commands.who" = "see who's online";
+"content.delivery.delivered_members" = "delivered to %1$d of %2$d members";
+"content.delivery.delivered_to" = "delivered to %@";
+"content.delivery.failed" = "failed: %@";
+"content.delivery.read_by" = "read by %@";
+"content.delivery.reason.blocked" = "user is blocked";
+"content.delivery.reason.self" = "cannot message yourself";
+"content.delivery.reason.send_error" = "send error";
+"content.delivery.reason.unknown_recipient" = "unknown recipient";
+"content.delivery.reason.unreachable" = "peer not reachable";
+"content.header.people" = "PEOPLE";
+"content.help.verification" = "verification: show my QR or scan a friend";
+"content.input.message_placeholder" = "type a message...";
+"content.input.nickname_placeholder" = "nickname";
+"content.location.enable" = "enable location";
+"content.message.copy" = "copy message";
+"content.message.show_less" = "show less";
+"content.message.show_more" = "show more";
+"content.notes.location_unavailable" = "location unavailable";
+"content.notes.title" = "notes";
+"content.payment.cashu" = "pay via cashu";
+"content.payment.lightning" = "pay via lightning";
+"encryption.accessibility.establishing" = "establishing encryption";
+"encryption.accessibility.failed" = "encryption failed";
+"encryption.accessibility.not_encrypted" = "not encrypted";
+"encryption.accessibility.secured" = "encrypted";
+"encryption.accessibility.verified" = "encrypted and verified";
+"encryption.status.establishing" = "sstablishing encryption...";
+"encryption.status.failed" = "encryption failed";
+"encryption.status.not_encrypted" = "not encrypted";
+"encryption.status.secured" = "encrypted";
+"encryption.status.verified" = "encrypted & verified";
+"fingerprint.action.mark_verified" = "mark as verified";
+"fingerprint.action.remove_verification" = "remove verification";
+"fingerprint.badge.not_verified" = "⚠️ NOT VERIFIED";
+"fingerprint.badge.verified" = "✓ VERIFIED";
+"fingerprint.handshake_pending" = "not available - handshake in progress";
+"fingerprint.message.verified" = "uou have verified this person's identity.";
+"fingerprint.message.verify_hint" = "compare these fingerprints with %@ using a secure channel.";
+"fingerprint.their_label" = "their fingerprint:";
+"fingerprint.title" = "security verification";
+"fingerprint.your_label" = "your fingerprint:";
+"geohash_people.action.block" = "block";
+"geohash_people.action.unblock" = "unblock";
+"geohash_people.none_nearby" = "nobody around...";
+"geohash_people.tooltip.blocked" = "blocked in geohash";
+"geohash_people.you_suffix" = " (you)";
+"location_channels.action.open_settings" = "open settings";
+"location_channels.action.remove_access" = "remove location access";
+"location_channels.action.request_permissions" = "get location and my geohashes";
+"location_channels.action.teleport" = "teleport";
+"location_channels.bookmarked_section_title" = "bookmarked";
+"location_channels.description" = "chat with people near you using geohash channels. only a coarse geohash is shared, never exact GPS. your IP address is hidden by routing all traffic over tor.";
+"location_channels.error.invalid_geohash" = "invalid geohash";
+"location_channels.loading_nearby" = "finding nearby channels…";
+"location_channels.mesh_label" = "mesh";
+"location_channels.permission_denied" = "location permission denied. enable in settings to use location channels.";
+"location_channels.subtitle_prefix" = "#%@ • %@";
+"location_channels.subtitle_with_name" = "%1$@ • %2$@";
+"location_channels.title" = "#location channels";
+"location_channels.tor.subtitle" = "hides your IP for location channels. recommended: on.";
+"location_channels.tor.title" = "tor routing";
+"location_levels.block" = "block";
+"location_levels.building" = "building";
+"location_levels.city" = "city";
+"location_levels.neighborhood" = "neighborhood";
+"location_levels.province" = "province";
+"location_levels.region" = "region";
+"location_notes.action.dismiss" = "dismiss";
+"location_notes.action.retry" = "retry";
+"location_notes.description" = "add short permanent notes to this location for other visitors to find.";
+"location_notes.empty_subtitle" = "be the first to add one for this spot.";
+"location_notes.empty_title" = "no notes yet";
+"location_notes.error.failed_to_send" = "failed to send note. %@";
+"location_notes.error.no_relays" = "no geo relays available near this location. try again soon.";
+"location_notes.loading_notes" = "loading notes…";
+"location_notes.loading_recent" = "loading recent notes…";
+"location_notes.no_relays_nearby" = "no geo relays nearby";
+"location_notes.placeholder" = "add a note for this place";
+"location_notes.relays_paused" = "geo relays unavailable; notes paused";
+"location_notes.relays_retry_hint" = "notes rely on geo relays. check connection and try again.";
+"mesh_peers.tooltip.new_messages" = "new messages";
+"system.chat.blocked" = "cannot start chat with %@: person is blocked.";
+"system.chat.requires_favorite" = "cannot start chat with %@: mutual favorite required for offline messaging.";
+"system.common.user" = "user";
+"system.dm.blocked_generic" = "cannot send message: person is blocked.";
+"system.dm.blocked_recipient" = "cannot send message to %@: person is blocked.";
+"system.dm.unreachable" = "cannot send message to %@ - peer is not reachable via mesh or nostr.";
+"system.geohash.blocked" = "blocked %@ in geohash chats";
+"system.geohash.unblocked" = "unblocked %@ in geohash chats";
+"system.location.not_in_channel" = "cannot send: not in a location channel";
+"system.location.send_failed" = "failed to send to location channel";
+"system.tor.dev_bypass" = "development build: Tor bypass enabled.";
+"system.tor.restarted" = "tor restarted. network routing restored.";
+"system.tor.restarting" = "tor restarting to recover connectivity...";
+"system.tor.started" = "tor started. routing all chats via tor for IP privacy.";
+"system.tor.starting" = "starting tor...";
+"verification.my_qr.accessibility_label" = "verification QR code";
+"verification.my_qr.title" = "scan to verify me";
+"verification.my_qr.unavailable" = "QR unavailable";
+"verification.scan.paste_prompt" = "paste QR content to validate:";
+"verification.scan.prompt_friend" = "scan a friend's QR";
+"verification.scan.status.invalid" = "invalid or expired QR payload";
+"verification.scan.status.no_peer" = "could not find matching peer";
+"verification.scan.status.requested" = "verification requested for %@";
+"verification.scan.validate" = "validate";
+"verification.sheet.title" = "VERIFY";
diff --git a/bitchat/Localization/Base.lproj/Localizable.stringsdict b/bitchat/Localization/Base.lproj/Localizable.stringsdict
new file mode 100644
index 00000000..1c25bb6b
--- /dev/null
+++ b/bitchat/Localization/Base.lproj/Localizable.stringsdict
@@ -0,0 +1,54 @@
+
+
+
+
+ location_notes.header
+
+ NSStringLocalizedFormatKey
+ #%@ • %#@note_count@
+ note_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ d
+ one
+ %d note
+ other
+ %d notes
+
+
+ location_channels.row_title
+
+ NSStringLocalizedFormatKey
+ %@ [%#@people_count@]
+ people_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ d
+ one
+ %d person
+ other
+ %d people
+
+
+ content.accessibility.people_count
+
+ NSStringLocalizedFormatKey
+ %#@people@
+ people
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ d
+ one
+ %d person
+ other
+ %d people
+
+
+
+
diff --git a/bitchat/Localization/ar.lproj/Localizable.strings b/bitchat/Localization/ar.lproj/Localizable.strings
new file mode 100644
index 00000000..afe0eb65
--- /dev/null
+++ b/bitchat/Localization/ar.lproj/Localizable.strings
@@ -0,0 +1,190 @@
+/*
+ Localizable.strings
+ bitchat (Arabic)
+*/
+
+"app_info.app_name" = "bitchat";
+"app_info.close" = "إغلاق";
+"app_info.done" = "تم";
+"app_info.features.encryption.description" = "الرسائل الخاصة مشفرة ببروتوكول noise";
+"app_info.features.encryption.title" = "تشفير طرف لطرف";
+"app_info.features.extended_range.description" = "يُعاد تمرير الرسائل بين الأقران لتصل لمسافات أبعد";
+"app_info.features.extended_range.title" = "نطاق ممتد";
+"app_info.features.favorites.description" = "تلقَّ تنبيهات عندما ينضم أحباؤك";
+"app_info.features.favorites.title" = "المفضلة";
+"app_info.features.geohash.description" = "قنوات geohash للدردشة مع أشخاص قريبين عبر مرحلات لامركزية مجهولة";
+"app_info.features.geohash.title" = "قنوات محلية";
+"app_info.features.mentions.description" = "استخدم @nickname لتنبيه أشخاص محددين";
+"app_info.features.mentions.title" = "إشارات";
+"app_info.features.offline.description" = "يعمل بدون إنترنت باستخدام bluetooth منخفض الطاقة";
+"app_info.features.offline.title" = "تواصل بدون اتصال";
+"app_info.features.title" = "مزايا";
+"app_info.how_to_use.change_channels" = "• اضغط #mesh لتغيير القناة";
+"app_info.how_to_use.clear_chat" = "• اضغط الدردشة ثلاث مرات للمسح";
+"app_info.how_to_use.commands" = "• اكتب / لعرض الأوامر";
+"app_info.how_to_use.open_sidebar" = "• اضغط أيقونة الأشخاص لفتح الشريط الجانبي";
+"app_info.how_to_use.set_nickname" = "• اضبط لقبك بلمسه";
+"app_info.how_to_use.start_dm" = "• اضغط اسم القرين لبدء رسائل خاصة";
+"app_info.how_to_use.title" = "طريقة الاستخدام";
+"app_info.privacy.ephemeral.description" = "يُولد معرف قرين جديد بانتظام";
+"app_info.privacy.ephemeral.title" = "هوية مؤقتة";
+"app_info.privacy.no_tracking.description" = "لا خوادم أو حسابات أو جمع بيانات";
+"app_info.privacy.no_tracking.title" = "لا تتبع";
+"app_info.privacy.panic.description" = "اضغط الشعار ثلاث مرات لمسح كل البيانات فوراً";
+"app_info.privacy.panic.title" = "وضع الذعر";
+"app_info.privacy.title" = "خصوصية";
+"app_info.tagline" = "sidegroupchat";
+"app_info.warning.message" = "أمان الرسائل الخاصة لم يتم تدقيقه بالكامل بعد. لا تستخدمها في الحالات الحرجة حتى يختفي هذا التحذير.";
+"app_info.warning.title" = "تحذير";
+"common.cancel" = "إلغاء";
+"common.close" = "إغلاق";
+"common.copy" = "نسخ";
+"common.ok" = "موافق";
+"common.toggle.off" = "إيقاف";
+"common.toggle.on" = "تشغيل";
+"common.unknown" = "غير معروف";
+"content.accessibility.add_favorite" = "إضافة إلى المفضلة";
+"content.accessibility.available_nostr" = "متاح عبر nostr";
+"content.accessibility.back_to_main_chat" = "عودة إلى الدردشة الرئيسية";
+"content.accessibility.connected_mesh" = "متصل عبر mesh";
+"content.accessibility.encryption_status" = "حالة التشفير: %@";
+"content.accessibility.location_channels" = "قنوات الموقع";
+"content.accessibility.location_notes" = "ملاحظات الموقع لهذا المكان";
+"content.accessibility.open_unread_private_chat" = "فتح دردشة خاصة غير مقروءة";
+"content.accessibility.private_chat_header" = "دردشة خاصة مع %@";
+"content.accessibility.reachable_mesh" = "قابل للوصول عبر mesh";
+"content.accessibility.remove_favorite" = "إزالة من المفضلة";
+"content.accessibility.send_hint_empty" = "أدخل رسالة للإرسال";
+"content.accessibility.send_hint_ready" = "اضغط مرتين للإرسال";
+"content.accessibility.send_message" = "إرسال رسالة";
+"content.accessibility.toggle_bookmark" = "تبديل الإشارة لـ #%@";
+"content.accessibility.toggle_favorite_hint" = "اضغط مرتين لتبديل حالة المفضلة";
+"content.accessibility.view_fingerprint_hint" = "اضغط لمشاهدة بصمة التشفير";
+"content.actions.block" = "حظر";
+"content.actions.direct_message" = "رسالة مباشرة";
+"content.actions.hug" = "عناق";
+"content.actions.mention" = "ذكر";
+"content.actions.slap" = "صفعة";
+"content.actions.title" = "إجراءات";
+"content.alert.bluetooth_required.off" = "bluetooth متوقف. فعّل bluetooth في الإعدادات لاستخدام bitchat.";
+"content.alert.bluetooth_required.permission" = "تحتاج bitchat إلى إذن bluetooth للاتصال بالأجهزة القريبة. فعّل الوصول في الإعدادات.";
+"content.alert.bluetooth_required.settings" = "الإعدادات";
+"content.alert.bluetooth_required.title" = "مطلوب bluetooth";
+"content.alert.bluetooth_required.unsupported" = "هذا الجهاز لا يدعم bluetooth. يحتاج bitchat إلى bluetooth للعمل.";
+"content.alert.screenshot.message" = "لقطات قنوات الموقع تكشف موقعك. فكر قبل المشاركة علناً.";
+"content.alert.screenshot.title" = "تنبيه";
+"content.commands.block" = "حظر أو عرض المحظورين";
+"content.commands.clear" = "مسح رسائل الدردشة";
+"content.commands.favorite" = "إضافة للمفضلة";
+"content.commands.hug" = "إرسال عناق دافئ";
+"content.commands.message" = "إرسال رسالة خاصة";
+"content.commands.slap" = "صفع شخص بسمكة تراوت";
+"content.commands.unblock" = "إلغاء حظر قرين";
+"content.commands.unfavorite" = "إزالة من المفضلة";
+"content.commands.who" = "عرض من هو متصل";
+"content.delivery.delivered_members" = "تم التسليم إلى %1$d من %2$d عضو";
+"content.delivery.delivered_to" = "سُلّم إلى %@";
+"content.delivery.failed" = "فشل: %@";
+"content.delivery.read_by" = "قُرِئ بواسطة %@";
+"content.delivery.reason.blocked" = "المستخدم محظور";
+"content.delivery.reason.self" = "لا يمكن الإرسال لنفسك";
+"content.delivery.reason.send_error" = "خطأ في الإرسال";
+"content.delivery.reason.unknown_recipient" = "مستلم غير معروف";
+"content.delivery.reason.unreachable" = "القرين غير متاح";
+"content.header.people" = "أشخاص";
+"content.help.verification" = "التحقق: عرض رمز qr الخاص بي أو مسح صديق";
+"content.input.message_placeholder" = "اكتب رسالة...";
+"content.input.nickname_placeholder" = "لقب";
+"content.location.enable" = "تفعيل الموقع";
+"content.message.copy" = "نسخ الرسالة";
+"content.message.show_less" = "عرض أقل";
+"content.message.show_more" = "عرض المزيد";
+"content.notes.location_unavailable" = "الموقع غير متاح";
+"content.notes.title" = "ملاحظات";
+"content.payment.cashu" = "الدفع عبر cashu";
+"content.payment.lightning" = "الدفع عبر lightning";
+"encryption.accessibility.establishing" = "جار إعداد التشفير";
+"encryption.accessibility.failed" = "فشل التشفير";
+"encryption.accessibility.not_encrypted" = "غير مشفر";
+"encryption.accessibility.secured" = "مشفر";
+"encryption.accessibility.verified" = "مشفر ومُتحقق";
+"encryption.status.establishing" = "جار إعداد التشفير...";
+"encryption.status.failed" = "فشل التشفير";
+"encryption.status.not_encrypted" = "غير مشفر";
+"encryption.status.secured" = "مشفر";
+"encryption.status.verified" = "مشفر ومُتحقق";
+"fingerprint.action.mark_verified" = "وضع علامة تم التحقق";
+"fingerprint.action.remove_verification" = "إزالة التحقق";
+"fingerprint.badge.not_verified" = "⚠️ غير مُتحقق";
+"fingerprint.badge.verified" = "✓ مُتحقق";
+"fingerprint.handshake_pending" = "غير متاح - جار تنفيذ handshake";
+"fingerprint.message.verified" = "لقد تحققت من هوية هذا الشخص.";
+"fingerprint.message.verify_hint" = "قارن هذه البصمات مع %@ عبر قناة آمنة.";
+"fingerprint.their_label" = "بصمتهم:";
+"fingerprint.title" = "تحقق الأمان";
+"fingerprint.your_label" = "بصمتك:";
+"geohash_people.action.block" = "حظر";
+"geohash_people.action.unblock" = "إلغاء الحظر";
+"geohash_people.none_nearby" = "لا أحد قريب...";
+"geohash_people.tooltip.blocked" = "محظور في geohash";
+"geohash_people.you_suffix" = " (أنت)";
+"location_channels.action.open_settings" = "فتح الإعدادات";
+"location_channels.action.remove_access" = "إزالة صلاحية الموقع";
+"location_channels.action.request_permissions" = "جلب موقعي و geohash";
+"location_channels.action.teleport" = "انتقال فوري";
+"location_channels.bookmarked_section_title" = "محفوظ";
+"location_channels.description" = "تحدث مع أشخاص قريبين عبر قنوات geohash. نشارك geohash تقريبي فقط، وليس gps الدقيق. يتم إخفاء عنوان ip لأن كل المرور يمر عبر tor.";
+"location_channels.error.invalid_geohash" = "geohash غير صالح";
+"location_channels.loading_nearby" = "جار البحث عن قنوات قريبة…";
+"location_channels.mesh_label" = "mesh";
+"location_channels.permission_denied" = "تم رفض إذن الموقع. فعّله في الإعدادات لاستخدام قنوات الموقع.";
+"location_channels.subtitle_prefix" = "#%@ • %@";
+"location_channels.subtitle_with_name" = "%1$@ • %2$@";
+"location_channels.title" = "#قنوات الموقع";
+"location_channels.tor.subtitle" = "يخفي ip لقنوات الموقع. الموصى به: تشغيل.";
+"location_channels.tor.title" = "توجيه tor";
+"location_levels.block" = "مربع";
+"location_levels.building" = "مبنى";
+"location_levels.city" = "مدينة";
+"location_levels.neighborhood" = "حي";
+"location_levels.province" = "مقاطعة";
+"location_levels.region" = "منطقة";
+"location_notes.action.dismiss" = "إغلاق";
+"location_notes.action.retry" = "إعادة المحاولة";
+"location_notes.description" = "أضف ملاحظات قصيرة دائمة لهذا المكان ليجدها الآخرون.";
+"location_notes.empty_subtitle" = "كن أول من يضيف هنا.";
+"location_notes.empty_title" = "لا توجد ملاحظات بعد";
+"location_notes.error.failed_to_send" = "تعذر إرسال الملاحظة. %@";
+"location_notes.error.no_relays" = "لا توجد مرحلات جغرافية قريبة من هذا المكان. حاول لاحقاً.";
+"location_notes.loading_notes" = "جار تحميل الملاحظات…";
+"location_notes.loading_recent" = "جار تحميل الملاحظات الحديثة…";
+"location_notes.no_relays_nearby" = "لا مرحلات جغرافية قريبة";
+"location_notes.placeholder" = "أضف ملاحظة لهذا المكان";
+"location_notes.relays_paused" = "المرحلات الجغرافية غير متاحة؛ الملاحظات متوقفة";
+"location_notes.relays_retry_hint" = "الملاحظات تعتمد على المرحلات الجغرافية. تحقق من الاتصال ثم أعد المحاولة.";
+"mesh_peers.tooltip.new_messages" = "رسائل جديدة";
+"system.chat.blocked" = "لا يمكن بدء دردشة مع %@: المستخدم محظور.";
+"system.chat.requires_favorite" = "لا يمكن بدء دردشة مع %@: يجب أن تكونا مفضلين متبادلين للتشغيل بدون اتصال.";
+"system.common.user" = "مستخدم";
+"system.dm.blocked_generic" = "تعذر الإرسال: المستخدم محظور.";
+"system.dm.blocked_recipient" = "لا يمكن الإرسال إلى %@: المستخدم محظور.";
+"system.dm.unreachable" = "لا يمكن الإرسال إلى %@: المستلم غير متاح عبر mesh أو nostr.";
+"system.geohash.blocked" = "تم حظر %@ في محادثات geohash";
+"system.geohash.unblocked" = "تم إلغاء حظر %@ في محادثات geohash";
+"system.location.not_in_channel" = "تعذر الإرسال: لست داخل قناة موقع";
+"system.location.send_failed" = "تعذر الإرسال إلى قناة الموقع";
+"system.tor.dev_bypass" = "بناء تطوير: تجاوز tor مفعل.";
+"system.tor.restarted" = "tor أُعيد تشغيله. تمت استعادة التوجيه.";
+"system.tor.restarting" = "tor يعاد تشغيله لاستعادة الاتصال...";
+"system.tor.started" = "tor يعمل. كل الدردشة تمر عبر tor للخصوصية.";
+"system.tor.starting" = "يتم تشغيل tor...";
+"verification.my_qr.accessibility_label" = "رمز qr للتحقق";
+"verification.my_qr.title" = "امسح للتحقق مني";
+"verification.my_qr.unavailable" = "qr غير متاح";
+"verification.scan.paste_prompt" = "الصق محتوى qr للتحقق:";
+"verification.scan.prompt_friend" = "امسح qr لصديق";
+"verification.scan.status.invalid" = "qr غير صالح أو منتهٍ";
+"verification.scan.status.no_peer" = "لم يتم العثور على قرين مطابق";
+"verification.scan.status.requested" = "تم طلب التحقق لـ %@";
+"verification.scan.validate" = "تحقق";
+"verification.sheet.title" = "تحقق";
diff --git a/bitchat/Localization/ar.lproj/Localizable.stringsdict b/bitchat/Localization/ar.lproj/Localizable.stringsdict
new file mode 100644
index 00000000..de6c861e
--- /dev/null
+++ b/bitchat/Localization/ar.lproj/Localizable.stringsdict
@@ -0,0 +1,78 @@
+
+
+
+
+ location_notes.header
+
+ NSStringLocalizedFormatKey
+ #%@ • %#@note_count@
+ note_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ d
+ zero
+ %d ملاحظات
+ one
+ %d ملاحظة
+ two
+ %d ملاحظتان
+ few
+ %d ملاحظات
+ many
+ %d ملاحظة
+ other
+ %d ملاحظة
+
+
+ location_channels.row_title
+
+ NSStringLocalizedFormatKey
+ %@ [%#@people_count@]
+ people_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ d
+ zero
+ %d أشخاص
+ one
+ %d شخص
+ two
+ %d شخصان
+ few
+ %d أشخاص
+ many
+ %d شخص
+ other
+ %d شخص
+
+
+ content.accessibility.people_count
+
+ NSStringLocalizedFormatKey
+ %#@people@
+ people
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ d
+ zero
+ %d أشخاص
+ one
+ %d شخص
+ two
+ %d شخصان
+ few
+ %d أشخاص
+ many
+ %d شخص
+ other
+ %d شخص
+
+
+
+
diff --git a/bitchat/Localization/de.lproj/Localizable.strings b/bitchat/Localization/de.lproj/Localizable.strings
new file mode 100644
index 00000000..69ee58bb
--- /dev/null
+++ b/bitchat/Localization/de.lproj/Localizable.strings
@@ -0,0 +1,190 @@
+/*
+ Localizable.strings
+ bitchat (German)
+*/
+
+"app_info.app_name" = "bitchat";
+"app_info.close" = "schließen";
+"app_info.done" = "FERTIG";
+"app_info.features.encryption.description" = "private nachrichten werden mit dem noise-protokoll verschlüsselt";
+"app_info.features.encryption.title" = "end-to-end-verschlüsselung";
+"app_info.features.extended_range.description" = "nachrichten werden zwischen peers weitergeleitet und reichen weiter";
+"app_info.features.extended_range.title" = "erweiterte reichweite";
+"app_info.features.favorites.description" = "erhalte hinweise, wenn deine lieblingsmenschen online kommen";
+"app_info.features.favorites.title" = "favoriten";
+"app_info.features.geohash.description" = "geohash-kanäle zum chatten mit menschen in der nähe über dezentrale anonyme relays";
+"app_info.features.geohash.title" = "lokale kanäle";
+"app_info.features.mentions.description" = "nutze @nickname, um bestimmte personen zu benachrichtigen";
+"app_info.features.mentions.title" = "erwähnungen";
+"app_info.features.offline.description" = "funktioniert ohne internet per bluetooth low energy";
+"app_info.features.offline.title" = "offline-kommunikation";
+"app_info.features.title" = "FUNKTIONEN";
+"app_info.how_to_use.change_channels" = "• tippe auf #mesh, um den kanal zu wechseln";
+"app_info.how_to_use.clear_chat" = "• tippe den chat dreimal, um ihn zu leeren";
+"app_info.how_to_use.commands" = "• tippe /, um befehle zu sehen";
+"app_info.how_to_use.open_sidebar" = "• tippe auf das personen-icon, um die seitenleiste zu öffnen";
+"app_info.how_to_use.set_nickname" = "• tippe auf deinen nickname, um ihn zu ändern";
+"app_info.how_to_use.start_dm" = "• tippe auf den namen eines peers, um eine pn zu starten";
+"app_info.how_to_use.title" = "SO FUNKTIONIERT'S";
+"app_info.privacy.ephemeral.description" = "neue peer-id wird regelmäßig erzeugt";
+"app_info.privacy.ephemeral.title" = "flüchtige identität";
+"app_info.privacy.no_tracking.description" = "keine server, konten oder datensammlung";
+"app_info.privacy.no_tracking.title" = "kein tracking";
+"app_info.privacy.panic.description" = "tippe dreimal auf das logo, um alle daten sofort zu löschen";
+"app_info.privacy.panic.title" = "panikmodus";
+"app_info.privacy.title" = "PRIVATSPHÄRE";
+"app_info.tagline" = "sidegroupchat";
+"app_info.warning.message" = "die sicherheit privater nachrichten wurde noch nicht vollständig geprüft. nutze sie nicht für kritische situationen, solange dieser hinweis erscheint.";
+"app_info.warning.title" = "WARNUNG";
+"common.cancel" = "abbrechen";
+"common.close" = "schließen";
+"common.copy" = "kopieren";
+"common.ok" = "OK";
+"common.toggle.off" = "aus";
+"common.toggle.on" = "an";
+"common.unknown" = "unbekannt";
+"content.accessibility.add_favorite" = "zu favoriten hinzufügen";
+"content.accessibility.available_nostr" = "verfügbar über nostr";
+"content.accessibility.back_to_main_chat" = "zurück zum hauptchat";
+"content.accessibility.connected_mesh" = "verbunden über mesh";
+"content.accessibility.encryption_status" = "verschlüsselungsstatus: %@";
+"content.accessibility.location_channels" = "kanäle für standorte";
+"content.accessibility.location_notes" = "standortnotizen für diesen ort";
+"content.accessibility.open_unread_private_chat" = "ungelesene privatnachricht öffnen";
+"content.accessibility.private_chat_header" = "privatchat mit %@";
+"content.accessibility.reachable_mesh" = "erreichbar über mesh";
+"content.accessibility.remove_favorite" = "aus favoriten entfernen";
+"content.accessibility.send_hint_empty" = "gib eine nachricht zum senden ein";
+"content.accessibility.send_hint_ready" = "doppelt tippen zum senden";
+"content.accessibility.send_message" = "nachricht senden";
+"content.accessibility.toggle_bookmark" = "bookmark für #%@ umschalten";
+"content.accessibility.toggle_favorite_hint" = "doppelt tippen, um favoritenstatus zu wechseln";
+"content.accessibility.view_fingerprint_hint" = "tippe, um den verschlüsselungs-fingerprint zu sehen";
+"content.actions.block" = "blockieren";
+"content.actions.direct_message" = "direktnachricht";
+"content.actions.hug" = "umarmen";
+"content.actions.mention" = "erwähnen";
+"content.actions.slap" = "ohrfeige";
+"content.actions.title" = "aktionen";
+"content.alert.bluetooth_required.off" = "bluetooth ist ausgeschaltet. aktiviere bluetooth in den einstellungen, um bitchat zu verwenden.";
+"content.alert.bluetooth_required.permission" = "bitchat benötigt bluetooth-berechtigung, um sich mit geräten in der nähe zu verbinden. erlaube den zugriff in den einstellungen.";
+"content.alert.bluetooth_required.settings" = "einstellungen";
+"content.alert.bluetooth_required.title" = "bluetooth erforderlich";
+"content.alert.bluetooth_required.unsupported" = "dieses gerät unterstützt kein bluetooth. bitchat benötigt bluetooth zum funktionieren.";
+"content.alert.screenshot.message" = "screenshots von standortkanälen verraten deinen standort. überleg dir das teilen vorher gut.";
+"content.alert.screenshot.title" = "achtung";
+"content.commands.block" = "blocked peers anzeigen oder blockieren";
+"content.commands.clear" = "chatnachrichten löschen";
+"content.commands.favorite" = "zu favoriten hinzufügen";
+"content.commands.hug" = "eine warme umarmung senden";
+"content.commands.message" = "privatnachricht senden";
+"content.commands.slap" = "jemandem eine forelle um die ohren schlagen";
+"content.commands.unblock" = "peer entsperren";
+"content.commands.unfavorite" = "aus favoriten entfernen";
+"content.commands.who" = "sehen, wer online ist";
+"content.delivery.delivered_members" = "zugestellt an %1$d von %2$d mitgliedern";
+"content.delivery.delivered_to" = "zugestellt an %@";
+"content.delivery.failed" = "fehlgeschlagen: %@";
+"content.delivery.read_by" = "gelesen von %@";
+"content.delivery.reason.blocked" = "nutzer blockiert";
+"content.delivery.reason.self" = "kann nicht an dich selbst senden";
+"content.delivery.reason.send_error" = "sende-fehler";
+"content.delivery.reason.unknown_recipient" = "unbekannter empfänger";
+"content.delivery.reason.unreachable" = "peer nicht erreichbar";
+"content.header.people" = "PERSONEN";
+"content.help.verification" = "verifizierung: meinen qr zeigen oder freund scannen";
+"content.input.message_placeholder" = "nachricht eingeben...";
+"content.input.nickname_placeholder" = "nickname";
+"content.location.enable" = "standort aktivieren";
+"content.message.copy" = "nachricht kopieren";
+"content.message.show_less" = "weniger anzeigen";
+"content.message.show_more" = "mehr anzeigen";
+"content.notes.location_unavailable" = "standort nicht verfügbar";
+"content.notes.title" = "notizen";
+"content.payment.cashu" = "per cashu bezahlen";
+"content.payment.lightning" = "per lightning bezahlen";
+"encryption.accessibility.establishing" = "verschlüsselung wird aufgebaut";
+"encryption.accessibility.failed" = "verschlüsselung fehlgeschlagen";
+"encryption.accessibility.not_encrypted" = "nicht verschlüsselt";
+"encryption.accessibility.secured" = "verschlüsselt";
+"encryption.accessibility.verified" = "verschlüsselt und verifiziert";
+"encryption.status.establishing" = "verschlüsselung wird aufgebaut...";
+"encryption.status.failed" = "verschlüsselung fehlgeschlagen";
+"encryption.status.not_encrypted" = "nicht verschlüsselt";
+"encryption.status.secured" = "verschlüsselt";
+"encryption.status.verified" = "verschlüsselt und verifiziert";
+"fingerprint.action.mark_verified" = "als verifiziert markieren";
+"fingerprint.action.remove_verification" = "verifizierung entfernen";
+"fingerprint.badge.not_verified" = "⚠️ NICHT VERIFIZIERT";
+"fingerprint.badge.verified" = "✓ VERIFIZIERT";
+"fingerprint.handshake_pending" = "nicht verfügbar – handshake läuft";
+"fingerprint.message.verified" = "du hast die identität dieser person verifiziert.";
+"fingerprint.message.verify_hint" = "vergleiche diese fingerabdrücke mit %@ über einen sicheren kanal.";
+"fingerprint.their_label" = "deren fingerabdruck:";
+"fingerprint.title" = "sicherheitsverifizierung";
+"fingerprint.your_label" = "dein fingerabdruck:";
+"geohash_people.action.block" = "blockieren";
+"geohash_people.action.unblock" = "entsperren";
+"geohash_people.none_nearby" = "niemand in der nähe...";
+"geohash_people.tooltip.blocked" = "in geohash blockiert";
+"geohash_people.you_suffix" = " (du)";
+"location_channels.action.open_settings" = "einstellungen öffnen";
+"location_channels.action.remove_access" = "standortzugriff entfernen";
+"location_channels.action.request_permissions" = "standort und geohash abrufen";
+"location_channels.action.teleport" = "teleportieren";
+"location_channels.bookmarked_section_title" = "gespeichert";
+"location_channels.description" = "chatte mit menschen in deiner nähe über geohash-kanäle. geteilt wird nur ein grober geohash, niemals exakte gps-daten. deine ip bleibt verborgen, weil der gesamte verkehr über tor läuft.";
+"location_channels.error.invalid_geohash" = "ungültiger geohash";
+"location_channels.loading_nearby" = "suche nach kanälen in der nähe…";
+"location_channels.mesh_label" = "mesh";
+"location_channels.permission_denied" = "standortberechtigung verweigert. aktiviere sie in den einstellungen für standortkanäle.";
+"location_channels.subtitle_prefix" = "#%@ • %@";
+"location_channels.subtitle_with_name" = "%1$@ • %2$@";
+"location_channels.title" = "#standort-kanäle";
+"location_channels.tor.subtitle" = "verbirgt deine ip für standortkanäle. empfohlen: an.";
+"location_channels.tor.title" = "tor-routing";
+"location_levels.block" = "block";
+"location_levels.building" = "gebäude";
+"location_levels.city" = "stadt";
+"location_levels.neighborhood" = "viertel";
+"location_levels.province" = "bundesland";
+"location_levels.region" = "region";
+"location_notes.action.dismiss" = "schließen";
+"location_notes.action.retry" = "erneut versuchen";
+"location_notes.description" = "füge diesem ort kurze dauerhafte notizen hinzu, damit andere sie finden.";
+"location_notes.empty_subtitle" = "sei die erste person, die hier eine notiz hinterlässt.";
+"location_notes.empty_title" = "noch keine notizen";
+"location_notes.error.failed_to_send" = "notiz konnte nicht gesendet werden. %@";
+"location_notes.error.no_relays" = "keine geo-relays in der nähe verfügbar. versuch es später erneut.";
+"location_notes.loading_notes" = "notizen werden geladen…";
+"location_notes.loading_recent" = "aktuelle notizen werden geladen…";
+"location_notes.no_relays_nearby" = "keine geo-relays in der nähe";
+"location_notes.placeholder" = "notiz für diesen ort hinzufügen";
+"location_notes.relays_paused" = "geo-relays nicht verfügbar; notizen pausiert";
+"location_notes.relays_retry_hint" = "notizen hängen von geo-relays ab. prüfe die verbindung und versuch es erneut.";
+"mesh_peers.tooltip.new_messages" = "neue nachrichten";
+"system.chat.blocked" = "chat mit %@ kann nicht gestartet werden: nutzer blockiert.";
+"system.chat.requires_favorite" = "chat mit %@ kann nicht gestartet werden: gegenseitige favoriten für offline nötig.";
+"system.common.user" = "nutzer";
+"system.dm.blocked_generic" = "senden nicht möglich: nutzer blockiert.";
+"system.dm.blocked_recipient" = "senden an %@ nicht möglich: nutzer blockiert.";
+"system.dm.unreachable" = "senden an %@ nicht möglich: empfänger über mesh oder nostr nicht erreichbar.";
+"system.geohash.blocked" = "%@ wurde in geohash-chats blockiert";
+"system.geohash.unblocked" = "%@ wurde in geohash-chats entsperrt";
+"system.location.not_in_channel" = "senden fehlgeschlagen: du bist nicht in einem standortkanal";
+"system.location.send_failed" = "konnte nicht an den standortkanal senden";
+"system.tor.dev_bypass" = "dev-build: tor-bypass aktiv.";
+"system.tor.restarted" = "tor wurde neu gestartet. routing wiederhergestellt.";
+"system.tor.restarting" = "tor startet neu, um die verbindung herzustellen...";
+"system.tor.started" = "tor läuft. der gesamte chat wird über tor geleitet.";
+"system.tor.starting" = "tor wird gestartet...";
+"verification.my_qr.accessibility_label" = "verifizierungs-qr-code";
+"verification.my_qr.title" = "scanne, um mich zu verifizieren";
+"verification.my_qr.unavailable" = "qr nicht verfügbar";
+"verification.scan.paste_prompt" = "füge den qr-inhalt zum prüfen ein:";
+"verification.scan.prompt_friend" = "scanne den qr eines freundes";
+"verification.scan.status.invalid" = "qr ungültig oder abgelaufen";
+"verification.scan.status.no_peer" = "kein passender peer gefunden";
+"verification.scan.status.requested" = "verifizierung für %@ angefordert";
+"verification.scan.validate" = "prüfen";
+"verification.sheet.title" = "VERIFIZIEREN";
diff --git a/bitchat/Localization/de.lproj/Localizable.stringsdict b/bitchat/Localization/de.lproj/Localizable.stringsdict
new file mode 100644
index 00000000..704c84aa
--- /dev/null
+++ b/bitchat/Localization/de.lproj/Localizable.stringsdict
@@ -0,0 +1,54 @@
+
+
+
+
+ location_notes.header
+
+ NSStringLocalizedFormatKey
+ #%@ • %#@note_count@
+ note_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ d
+ one
+ %d notiz
+ other
+ %d notizen
+
+
+ location_channels.row_title
+
+ NSStringLocalizedFormatKey
+ %@ [%#@people_count@]
+ people_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ d
+ one
+ %d person
+ other
+ %d personen
+
+
+ content.accessibility.people_count
+
+ NSStringLocalizedFormatKey
+ %#@people@
+ people
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ d
+ one
+ %d person
+ other
+ %d personen
+
+
+
+
diff --git a/bitchat/Localization/es.lproj/Localizable.strings b/bitchat/Localization/es.lproj/Localizable.strings
new file mode 100644
index 00000000..1516c1f8
--- /dev/null
+++ b/bitchat/Localization/es.lproj/Localizable.strings
@@ -0,0 +1,190 @@
+/*
+ Localizable.strings
+ bitchat (Spanish)
+*/
+
+"app_info.app_name" = "bitchat";
+"app_info.close" = "cerrar";
+"app_info.done" = "LISTO";
+"app_info.features.encryption.description" = "mensajes privados cifrados con el protocolo Noise";
+"app_info.features.encryption.title" = "cifrado de extremo a extremo";
+"app_info.features.extended_range.description" = "los mensajes se retransmiten entre pares y llegan lejos";
+"app_info.features.extended_range.title" = "alcance ampliado";
+"app_info.features.favorites.description" = "recibe avisos cuando tus personas favoritas se conecten";
+"app_info.features.favorites.title" = "favoritos";
+"app_info.features.geohash.description" = "canales geohash para chatear con personas en regiones cercanas a través de relays descentralizados anónimos";
+"app_info.features.geohash.title" = "canales locales";
+"app_info.features.mentions.description" = "usa @nickname para avisar a personas concretas";
+"app_info.features.mentions.title" = "menciones";
+"app_info.features.offline.description" = "funciona sin internet utilizando Bluetooth de bajo consumo";
+"app_info.features.offline.title" = "comunicación sin conexión";
+"app_info.features.title" = "FUNCIONES";
+"app_info.how_to_use.change_channels" = "• toca #mesh para cambiar de canal";
+"app_info.how_to_use.clear_chat" = "• toca tres veces el chat para limpiarlo";
+"app_info.how_to_use.commands" = "• escribe / para ver los comandos";
+"app_info.how_to_use.open_sidebar" = "• toca el ícono de personas para abrir la barra lateral";
+"app_info.how_to_use.set_nickname" = "• define tu apodo tocándolo";
+"app_info.how_to_use.start_dm" = "• toca el nombre de un participante para iniciar un MD";
+"app_info.how_to_use.title" = "CÓMO USARLO";
+"app_info.privacy.ephemeral.description" = "nuevo ID de peer generado periódicamente";
+"app_info.privacy.ephemeral.title" = "identidad efímera";
+"app_info.privacy.no_tracking.description" = "sin servidores, cuentas ni recopilación de datos";
+"app_info.privacy.no_tracking.title" = "sin seguimiento";
+"app_info.privacy.panic.description" = "toca el logotipo tres veces para borrar todos los datos al instante";
+"app_info.privacy.panic.title" = "modo pánico";
+"app_info.privacy.title" = "PRIVACIDAD";
+"app_info.tagline" = "sidegroupchat";
+"app_info.warning.message" = "la seguridad de los mensajes privados aún no ha sido auditada por completo. no lo uses en situaciones críticas hasta que este aviso desaparezca.";
+"app_info.warning.title" = "ADVERTENCIA";
+"common.cancel" = "cancelar";
+"common.close" = "cerrar";
+"common.copy" = "copiar";
+"common.ok" = "aceptar";
+"common.toggle.off" = "desactivado";
+"common.toggle.on" = "activado";
+"common.unknown" = "desconocido";
+"content.accessibility.add_favorite" = "agregar a favoritos";
+"content.accessibility.available_nostr" = "disponible vía Nostr";
+"content.accessibility.back_to_main_chat" = "volver al chat principal";
+"content.accessibility.connected_mesh" = "conectado por mesh";
+"content.accessibility.encryption_status" = "estado de cifrado: %@";
+"content.accessibility.location_channels" = "canales de ubicación";
+"content.accessibility.location_notes" = "notas de ubicación de este lugar";
+"content.accessibility.open_unread_private_chat" = "abrir chat privado sin leer";
+"content.accessibility.private_chat_header" = "chat privado con %@";
+"content.accessibility.reachable_mesh" = "disponible por mesh";
+"content.accessibility.remove_favorite" = "quitar de favoritos";
+"content.accessibility.send_hint_empty" = "introduce un mensaje para enviarlo";
+"content.accessibility.send_hint_ready" = "toca dos veces para enviar";
+"content.accessibility.send_message" = "enviar mensaje";
+"content.accessibility.toggle_bookmark" = "alternar marcador para #%@";
+"content.accessibility.toggle_favorite_hint" = "toca dos veces para alternar el estado de favorito";
+"content.accessibility.view_fingerprint_hint" = "toca para ver la huella de cifrado";
+"content.actions.block" = "bloquear";
+"content.actions.direct_message" = "mensaje directo";
+"content.actions.hug" = "abrazo";
+"content.actions.mention" = "mencionar";
+"content.actions.slap" = "bofetada";
+"content.actions.title" = "acciones";
+"content.alert.bluetooth_required.off" = "bluetooth está desactivado. Actívalo en Ajustes para usar BitChat.";
+"content.alert.bluetooth_required.permission" = "bitChat necesita permiso de Bluetooth para conectarse con dispositivos cercanos. Habilita el acceso en Ajustes.";
+"content.alert.bluetooth_required.settings" = "ajustes";
+"content.alert.bluetooth_required.title" = "se requiere Bluetooth";
+"content.alert.bluetooth_required.unsupported" = "este dispositivo no admite Bluetooth. BitChat necesita Bluetooth para funcionar.";
+"content.alert.screenshot.message" = "las capturas de pantalla de los canales de ubicación revelarán tu ubicación. Piensa antes de compartirlas públicamente.";
+"content.alert.screenshot.title" = "atención";
+"content.commands.block" = "bloquear o listar usuarios bloqueados";
+"content.commands.clear" = "borrar los mensajes del chat";
+"content.commands.favorite" = "agregar a favoritos";
+"content.commands.hug" = "enviar un abrazo caluroso";
+"content.commands.message" = "enviar mensaje privado";
+"content.commands.slap" = "abofetear a alguien con una trucha";
+"content.commands.unblock" = "desbloquear a un usuario";
+"content.commands.unfavorite" = "quitar de favoritos";
+"content.commands.who" = "ver quién está en línea";
+"content.delivery.delivered_members" = "entregado a %1$d de %2$d miembros";
+"content.delivery.delivered_to" = "entregado a %@";
+"content.delivery.failed" = "falló: %@";
+"content.delivery.read_by" = "leído por %@";
+"content.delivery.reason.blocked" = "el usuario está bloqueado";
+"content.delivery.reason.self" = "no puedes enviarte mensajes a ti mismo";
+"content.delivery.reason.send_error" = "error al enviar";
+"content.delivery.reason.unknown_recipient" = "destinatario desconocido";
+"content.delivery.reason.unreachable" = "el destinatario no es alcanzable";
+"content.header.people" = "PERSONAS";
+"content.help.verification" = "verificación: mostrar mi QR o escanear a un amigo";
+"content.input.message_placeholder" = "escribe un mensaje...";
+"content.input.nickname_placeholder" = "apodo";
+"content.location.enable" = "activar ubicación";
+"content.message.copy" = "copiar mensaje";
+"content.message.show_less" = "mostrar menos";
+"content.message.show_more" = "mostrar más";
+"content.notes.location_unavailable" = "ubicación no disponible";
+"content.notes.title" = "notas";
+"content.payment.cashu" = "pagar con Cashu";
+"content.payment.lightning" = "pagar con Lightning";
+"encryption.accessibility.establishing" = "estableciendo cifrado";
+"encryption.accessibility.failed" = "cifrado fallido";
+"encryption.accessibility.not_encrypted" = "sin cifrar";
+"encryption.accessibility.secured" = "cifrado";
+"encryption.accessibility.verified" = "cifrado y verificado";
+"encryption.status.establishing" = "estableciendo cifrado...";
+"encryption.status.failed" = "cifrado fallido";
+"encryption.status.not_encrypted" = "sin cifrar";
+"encryption.status.secured" = "cifrado";
+"encryption.status.verified" = "cifrado y verificado";
+"fingerprint.action.mark_verified" = "marcar como verificado";
+"fingerprint.action.remove_verification" = "eliminar verificación";
+"fingerprint.badge.not_verified" = "⚠️ NO VERIFICADO";
+"fingerprint.badge.verified" = "✓ VERIFICADO";
+"fingerprint.handshake_pending" = "no disponible: el handshake está en curso";
+"fingerprint.message.verified" = "has verificado la identidad de esta persona.";
+"fingerprint.message.verify_hint" = "compara estas huellas con %@ mediante un canal seguro.";
+"fingerprint.their_label" = "huella de la otra persona:";
+"fingerprint.title" = "verificación de seguridad";
+"fingerprint.your_label" = "tu huella:";
+"geohash_people.action.block" = "bloquear";
+"geohash_people.action.unblock" = "desbloquear";
+"geohash_people.none_nearby" = "nadie cerca...";
+"geohash_people.tooltip.blocked" = "bloqueado en geohash";
+"geohash_people.you_suffix" = " (tú)";
+"location_channels.action.open_settings" = "abrir ajustes";
+"location_channels.action.remove_access" = "eliminar acceso a la ubicación";
+"location_channels.action.request_permissions" = "obtener mi ubicación y mis geohashes";
+"location_channels.action.teleport" = "teletransportar";
+"location_channels.bookmarked_section_title" = "marcados";
+"location_channels.description" = "chatea con personas cercanas usando canales geohash. Solo se comparte un geohash aproximado, nunca GPS exacto. Tu IP se oculta al enrutar todo el tráfico por Tor.";
+"location_channels.error.invalid_geohash" = "geohash no válido";
+"location_channels.loading_nearby" = "buscando canales cercanos…";
+"location_channels.mesh_label" = "mesh";
+"location_channels.permission_denied" = "permiso de ubicación denegado. Actívalo en Ajustes para usar los canales de ubicación.";
+"location_channels.subtitle_prefix" = "#%@ • %@";
+"location_channels.subtitle_with_name" = "%1$@ • %2$@";
+"location_channels.title" = "#canales de ubicación";
+"location_channels.tor.subtitle" = "oculta tu IP para los canales de ubicación. Recomendado: activado.";
+"location_channels.tor.title" = "enrutamiento Tor";
+"location_levels.block" = "bloque";
+"location_levels.building" = "edificio";
+"location_levels.city" = "ciudad";
+"location_levels.neighborhood" = "barrio";
+"location_levels.province" = "provincia";
+"location_levels.region" = "región";
+"location_notes.action.dismiss" = "descartar";
+"location_notes.action.retry" = "reintentar";
+"location_notes.description" = "añade notas permanentes cortas sobre este lugar para que otras personas las encuentren.";
+"location_notes.empty_subtitle" = "sé la primera persona en añadir una en este lugar.";
+"location_notes.empty_title" = "aún no hay notas";
+"location_notes.error.failed_to_send" = "no se pudo enviar la nota. %@";
+"location_notes.error.no_relays" = "no hay relays geográficos disponibles cerca de este lugar. Inténtalo de nuevo pronto.";
+"location_notes.loading_notes" = "cargando notas…";
+"location_notes.loading_recent" = "cargando notas recientes…";
+"location_notes.no_relays_nearby" = "no hay relays geográficos cercanos";
+"location_notes.placeholder" = "añade una nota para este lugar";
+"location_notes.relays_paused" = "relays geográficos no disponibles; notas en pausa";
+"location_notes.relays_retry_hint" = "las notas dependen de los relays geográficos. Comprueba la conexión e inténtalo de nuevo.";
+"mesh_peers.tooltip.new_messages" = "nuevos mensajes";
+"system.chat.blocked" = "no se puede iniciar un chat con %@: el usuario está bloqueado.";
+"system.chat.requires_favorite" = "no se puede iniciar un chat con %@: necesitas ser favoritos mutuos para mensajería sin conexión.";
+"system.common.user" = "usuario";
+"system.dm.blocked_generic" = "no se puede enviar el mensaje: el usuario está bloqueado.";
+"system.dm.blocked_recipient" = "no se puede enviar un mensaje a %@: el usuario está bloqueado.";
+"system.dm.unreachable" = "no se puede enviar un mensaje a %@: el destinatario no es alcanzable por mesh ni Nostr.";
+"system.geohash.blocked" = "se bloqueó a %@ en los chats geohash";
+"system.geohash.unblocked" = "se desbloqueó a %@ en los chats geohash";
+"system.location.not_in_channel" = "no se puede enviar: no estás en un canal de ubicación";
+"system.location.send_failed" = "no se pudo enviar al canal de ubicación";
+"system.tor.dev_bypass" = "compilación de desarrollo: bypass de Tor activado.";
+"system.tor.restarted" = "tor se reinició. Se restauró el enrutamiento de la red.";
+"system.tor.restarting" = "tor se está reiniciando para recuperar la conectividad...";
+"system.tor.started" = "tor se inició. Todo el chat se enruta por Tor para privacidad.";
+"system.tor.starting" = "iniciando Tor...";
+"verification.my_qr.accessibility_label" = "código QR de verificación";
+"verification.my_qr.title" = "escanea para verificarme";
+"verification.my_qr.unavailable" = "QR no disponible";
+"verification.scan.paste_prompt" = "pega el contenido del QR para validarlo:";
+"verification.scan.prompt_friend" = "escanea el QR de un amigo";
+"verification.scan.status.invalid" = "QR inválido o caducado";
+"verification.scan.status.no_peer" = "no se encontró un peer coincidente";
+"verification.scan.status.requested" = "se solicitó la verificación de %@";
+"verification.scan.validate" = "validar";
+"verification.sheet.title" = "VERIFICAR";
diff --git a/bitchat/Localization/es.lproj/Localizable.stringsdict b/bitchat/Localization/es.lproj/Localizable.stringsdict
new file mode 100644
index 00000000..76eef112
--- /dev/null
+++ b/bitchat/Localization/es.lproj/Localizable.stringsdict
@@ -0,0 +1,54 @@
+
+
+
+
+ location_notes.header
+
+ NSStringLocalizedFormatKey
+ #%@ • %#@note_count@
+ note_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ d
+ one
+ %d nota
+ other
+ %d notas
+
+
+ location_channels.row_title
+
+ NSStringLocalizedFormatKey
+ %@ [%#@people_count@]
+ people_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ d
+ one
+ %d persona
+ other
+ %d personas
+
+
+ content.accessibility.people_count
+
+ NSStringLocalizedFormatKey
+ %#@people@
+ people
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ d
+ one
+ %d persona
+ other
+ %d personas
+
+
+
+
diff --git a/bitchat/Localization/fr.lproj/Localizable.strings b/bitchat/Localization/fr.lproj/Localizable.strings
new file mode 100644
index 00000000..c4143391
--- /dev/null
+++ b/bitchat/Localization/fr.lproj/Localizable.strings
@@ -0,0 +1,190 @@
+/*
+ Localizable.strings
+ bitchat (French)
+*/
+
+"app_info.app_name" = "bitchat";
+"app_info.close" = "fermer";
+"app_info.done" = "TERMINÉ";
+"app_info.features.encryption.description" = "messages privés chiffrés avec le protocole noise";
+"app_info.features.encryption.title" = "chiffrement de bout en bout";
+"app_info.features.extended_range.description" = "messages relayés entre pairs pour aller plus loin";
+"app_info.features.extended_range.title" = "portée étendue";
+"app_info.features.favorites.description" = "reçois une alerte quand tes personnes favorites arrivent";
+"app_info.features.favorites.title" = "favoris";
+"app_info.features.geohash.description" = "canaux geohash pour discuter avec des personnes proches via des relais décentralisés anonymes";
+"app_info.features.geohash.title" = "canaux locaux";
+"app_info.features.mentions.description" = "utilise @nickname pour avertir des personnes précises";
+"app_info.features.mentions.title" = "mentions";
+"app_info.features.offline.description" = "fonctionne sans internet avec le bluetooth basse énergie";
+"app_info.features.offline.title" = "communication hors ligne";
+"app_info.features.title" = "FONCTIONNALITÉS";
+"app_info.how_to_use.change_channels" = "• tape sur #mesh pour changer de canal";
+"app_info.how_to_use.clear_chat" = "• tape trois fois sur le chat pour le vider";
+"app_info.how_to_use.commands" = "• tape / pour voir les commandes";
+"app_info.how_to_use.open_sidebar" = "• tape sur l'icône personnes pour ouvrir la barre latérale";
+"app_info.how_to_use.set_nickname" = "• règle ton pseudo en le touchant";
+"app_info.how_to_use.start_dm" = "• tape sur le nom d'un pair pour démarrer un mp";
+"app_info.how_to_use.title" = "MODE D'EMPLOI";
+"app_info.privacy.ephemeral.description" = "nouvel id de pair généré régulièrement";
+"app_info.privacy.ephemeral.title" = "identité éphémère";
+"app_info.privacy.no_tracking.description" = "sans serveurs, comptes ni collecte de données";
+"app_info.privacy.no_tracking.title" = "sans suivi";
+"app_info.privacy.panic.description" = "tape trois fois sur le logo pour tout effacer instantanément";
+"app_info.privacy.panic.title" = "mode panique";
+"app_info.privacy.title" = "CONFIDENTIALITÉ";
+"app_info.tagline" = "sidegroupchat";
+"app_info.warning.message" = "la sécurité des messages privés n'a pas encore été entièrement auditée. n'utilise pas pour des situations critiques tant que cet avertissement reste.";
+"app_info.warning.title" = "AVERTISSEMENT";
+"common.cancel" = "annuler";
+"common.close" = "fermer";
+"common.copy" = "copier";
+"common.ok" = "OK";
+"common.toggle.off" = "désactivé";
+"common.toggle.on" = "activé";
+"common.unknown" = "inconnu";
+"content.accessibility.add_favorite" = "ajouter aux favoris";
+"content.accessibility.available_nostr" = "disponible via nostr";
+"content.accessibility.back_to_main_chat" = "retour au chat principal";
+"content.accessibility.connected_mesh" = "connecté via mesh";
+"content.accessibility.encryption_status" = "état du chiffrement : %@";
+"content.accessibility.location_channels" = "canaux de localisation";
+"content.accessibility.location_notes" = "notes de localisation pour cet endroit";
+"content.accessibility.open_unread_private_chat" = "ouvrir le chat privé non lu";
+"content.accessibility.private_chat_header" = "chat privé avec %@";
+"content.accessibility.reachable_mesh" = "joignable via mesh";
+"content.accessibility.remove_favorite" = "retirer des favoris";
+"content.accessibility.send_hint_empty" = "saisis un message à envoyer";
+"content.accessibility.send_hint_ready" = "tape deux fois pour envoyer";
+"content.accessibility.send_message" = "envoyer le message";
+"content.accessibility.toggle_bookmark" = "basculer le favori pour #%@";
+"content.accessibility.toggle_favorite_hint" = "tape deux fois pour basculer le statut favori";
+"content.accessibility.view_fingerprint_hint" = "tape pour voir l'empreinte de chiffrement";
+"content.actions.block" = "bloquer";
+"content.actions.direct_message" = "message direct";
+"content.actions.hug" = "câlin";
+"content.actions.mention" = "mentionner";
+"content.actions.slap" = "gifle";
+"content.actions.title" = "actions";
+"content.alert.bluetooth_required.off" = "bluetooth est désactivé. active le bluetooth dans réglages pour utiliser bitchat.";
+"content.alert.bluetooth_required.permission" = "bitchat a besoin de l'autorisation bluetooth pour se connecter aux appareils proches. active l'accès dans réglages.";
+"content.alert.bluetooth_required.settings" = "réglages";
+"content.alert.bluetooth_required.title" = "bluetooth requis";
+"content.alert.bluetooth_required.unsupported" = "cet appareil ne prend pas en charge le bluetooth. bitchat en a besoin pour fonctionner.";
+"content.alert.screenshot.message" = "les captures des canaux de localisation révéleront ta position. réfléchis avant de partager publiquement.";
+"content.alert.screenshot.title" = "attention";
+"content.commands.block" = "bloquer ou lister les pairs bloqués";
+"content.commands.clear" = "effacer les messages du chat";
+"content.commands.favorite" = "ajouter aux favoris";
+"content.commands.hug" = "envoyer un câlin chaleureux";
+"content.commands.message" = "envoyer un message privé";
+"content.commands.slap" = "gifler quelqu'un avec une truite";
+"content.commands.unblock" = "débloquer un pair";
+"content.commands.unfavorite" = "retirer des favoris";
+"content.commands.who" = "voir qui est en ligne";
+"content.delivery.delivered_members" = "livré à %1$d sur %2$d membres";
+"content.delivery.delivered_to" = "livré à %@";
+"content.delivery.failed" = "échec : %@";
+"content.delivery.read_by" = "lu par %@";
+"content.delivery.reason.blocked" = "utilisateur bloqué";
+"content.delivery.reason.self" = "impossible d'envoyer à toi-même";
+"content.delivery.reason.send_error" = "erreur d'envoi";
+"content.delivery.reason.unknown_recipient" = "destinataire inconnu";
+"content.delivery.reason.unreachable" = "pair injoignable";
+"content.header.people" = "PERSONNES";
+"content.help.verification" = "vérification : afficher mon qr ou scanner un ami";
+"content.input.message_placeholder" = "écris un message...";
+"content.input.nickname_placeholder" = "pseudo";
+"content.location.enable" = "activer la localisation";
+"content.message.copy" = "copier le message";
+"content.message.show_less" = "afficher moins";
+"content.message.show_more" = "afficher plus";
+"content.notes.location_unavailable" = "localisation indisponible";
+"content.notes.title" = "notes";
+"content.payment.cashu" = "payer via cashu";
+"content.payment.lightning" = "payer via lightning";
+"encryption.accessibility.establishing" = "établissement du chiffrement";
+"encryption.accessibility.failed" = "chiffrement échoué";
+"encryption.accessibility.not_encrypted" = "non chiffré";
+"encryption.accessibility.secured" = "chiffré";
+"encryption.accessibility.verified" = "chiffré et vérifié";
+"encryption.status.establishing" = "mise en place du chiffrement...";
+"encryption.status.failed" = "chiffrement échoué";
+"encryption.status.not_encrypted" = "non chiffré";
+"encryption.status.secured" = "chiffré";
+"encryption.status.verified" = "chiffré et vérifié";
+"fingerprint.action.mark_verified" = "marquer comme vérifié";
+"fingerprint.action.remove_verification" = "retirer la vérification";
+"fingerprint.badge.not_verified" = "⚠️ NON VÉRIFIÉ";
+"fingerprint.badge.verified" = "✓ VÉRIFIÉ";
+"fingerprint.handshake_pending" = "indisponible - handshake en cours";
+"fingerprint.message.verified" = "tu as vérifié l'identité de cette personne.";
+"fingerprint.message.verify_hint" = "compare ces empreintes avec %@ via un canal sécurisé.";
+"fingerprint.their_label" = "leur empreinte :";
+"fingerprint.title" = "vérification de sécurité";
+"fingerprint.your_label" = "ton empreinte :";
+"geohash_people.action.block" = "bloquer";
+"geohash_people.action.unblock" = "débloquer";
+"geohash_people.none_nearby" = "personne à proximité...";
+"geohash_people.tooltip.blocked" = "bloqué dans geohash";
+"geohash_people.you_suffix" = " (toi)";
+"location_channels.action.open_settings" = "ouvrir réglages";
+"location_channels.action.remove_access" = "retirer l'accès localisation";
+"location_channels.action.request_permissions" = "obtenir ma localisation et mes geohash";
+"location_channels.action.teleport" = "téléporter";
+"location_channels.bookmarked_section_title" = "enregistrés";
+"location_channels.description" = "discute avec les personnes proches grâce aux canaux geohash. seul un geohash grossier est partagé, jamais de gps exact. ton ip reste cachée car tout le trafic passe par tor.";
+"location_channels.error.invalid_geohash" = "geohash invalide";
+"location_channels.loading_nearby" = "recherche de canaux proches…";
+"location_channels.mesh_label" = "mesh";
+"location_channels.permission_denied" = "autorisation de localisation refusée. active-la dans réglages pour utiliser les canaux.";
+"location_channels.subtitle_prefix" = "#%@ • %@";
+"location_channels.subtitle_with_name" = "%1$@ • %2$@";
+"location_channels.title" = "#canaux localisation";
+"location_channels.tor.subtitle" = "cache ton ip pour les canaux localisation. recommandé : activé.";
+"location_channels.tor.title" = "routage tor";
+"location_levels.block" = "bloc";
+"location_levels.building" = "bâtiment";
+"location_levels.city" = "ville";
+"location_levels.neighborhood" = "quartier";
+"location_levels.province" = "province";
+"location_levels.region" = "région";
+"location_notes.action.dismiss" = "ignorer";
+"location_notes.action.retry" = "réessayer";
+"location_notes.description" = "ajoute de courtes notes permanentes ici pour aider les autres.";
+"location_notes.empty_subtitle" = "sois la première personne à en ajouter ici.";
+"location_notes.empty_title" = "pas encore de notes";
+"location_notes.error.failed_to_send" = "impossible d'envoyer la note. %@";
+"location_notes.error.no_relays" = "aucun relais géo disponible près d'ici. réessaie bientôt.";
+"location_notes.loading_notes" = "chargement des notes…";
+"location_notes.loading_recent" = "chargement des notes récentes…";
+"location_notes.no_relays_nearby" = "aucun relais géo à proximité";
+"location_notes.placeholder" = "ajoute une note pour cet endroit";
+"location_notes.relays_paused" = "relais géo indisponibles ; notes en pause";
+"location_notes.relays_retry_hint" = "les notes dépendent des relais géo. vérifie la connexion et réessaie.";
+"mesh_peers.tooltip.new_messages" = "nouveaux messages";
+"system.chat.blocked" = "impossible de démarrer un chat avec %@ : utilisateur bloqué.";
+"system.chat.requires_favorite" = "impossible de démarrer un chat avec %@ : favoris mutuels requis pour le hors ligne.";
+"system.common.user" = "utilisateur";
+"system.dm.blocked_generic" = "envoi impossible : utilisateur bloqué.";
+"system.dm.blocked_recipient" = "impossible d'envoyer à %@ : utilisateur bloqué.";
+"system.dm.unreachable" = "impossible d'envoyer à %@ : destinataire injoignable via mesh ou nostr.";
+"system.geohash.blocked" = "%@ a été bloqué dans les chats geohash";
+"system.geohash.unblocked" = "%@ a été débloqué dans les chats geohash";
+"system.location.not_in_channel" = "envoi impossible : tu n'es pas dans un canal localisation";
+"system.location.send_failed" = "envoi au canal localisation impossible";
+"system.tor.dev_bypass" = "build de développement : bypass tor actif.";
+"system.tor.restarted" = "tor a redémarré. routage restauré.";
+"system.tor.restarting" = "tor redémarre pour rétablir la connectivité...";
+"system.tor.started" = "tor a démarré. tout le chat passe par tor pour la confidentialité.";
+"system.tor.starting" = "lancement de tor...";
+"verification.my_qr.accessibility_label" = "code qr de vérification";
+"verification.my_qr.title" = "scanne pour me vérifier";
+"verification.my_qr.unavailable" = "qr indisponible";
+"verification.scan.paste_prompt" = "colle le contenu du qr pour valider :";
+"verification.scan.prompt_friend" = "scanne le qr d'un ami";
+"verification.scan.status.invalid" = "qr invalide ou expiré";
+"verification.scan.status.no_peer" = "aucun pair correspondant trouvé";
+"verification.scan.status.requested" = "vérification demandée pour %@";
+"verification.scan.validate" = "valider";
+"verification.sheet.title" = "VÉRIFIER";
diff --git a/bitchat/Localization/fr.lproj/Localizable.stringsdict b/bitchat/Localization/fr.lproj/Localizable.stringsdict
new file mode 100644
index 00000000..524ea159
--- /dev/null
+++ b/bitchat/Localization/fr.lproj/Localizable.stringsdict
@@ -0,0 +1,54 @@
+
+
+
+
+ location_notes.header
+
+ NSStringLocalizedFormatKey
+ #%@ • %#@note_count@
+ note_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ d
+ one
+ %d note
+ other
+ %d notes
+
+
+ location_channels.row_title
+
+ NSStringLocalizedFormatKey
+ %@ [%#@people_count@]
+ people_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ d
+ one
+ %d personne
+ other
+ %d personnes
+
+
+ content.accessibility.people_count
+
+ NSStringLocalizedFormatKey
+ %#@people@
+ people
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ d
+ one
+ %d personne
+ other
+ %d personnes
+
+
+
+
diff --git a/bitchat/Localization/he.lproj/Localizable.strings b/bitchat/Localization/he.lproj/Localizable.strings
new file mode 100644
index 00000000..3bac63cc
--- /dev/null
+++ b/bitchat/Localization/he.lproj/Localizable.strings
@@ -0,0 +1,190 @@
+/*
+ Localizable.strings
+ bitchat (Hebrew)
+*/
+
+"app_info.app_name" = "bitchat";
+"app_info.close" = "סגור";
+"app_info.done" = "בוצע";
+"app_info.features.encryption.description" = "הודעות פרטיות מוצפנות בפרוטוקול noise";
+"app_info.features.encryption.title" = "הצפנה מקצה לקצה";
+"app_info.features.extended_range.description" = "הודעות משודרות בין עמיתים ומגיעות רחוק יותר";
+"app_info.features.extended_range.title" = "טווח מורחב";
+"app_info.features.favorites.description" = "קבל התראות כשהאנשים המועדפים שלך מצטרפים";
+"app_info.features.favorites.title" = "מועדפים";
+"app_info.features.geohash.description" = "ערוצי geohash לשיחה עם אנשים קרובים דרך ממסרים אנונימיים מבוזרים";
+"app_info.features.geohash.title" = "ערוצים מקומיים";
+"app_info.features.mentions.description" = "השתמש ב-@nickname כדי להתריע לאנשים ספציפיים";
+"app_info.features.mentions.title" = "אזכורים";
+"app_info.features.offline.description" = "עובד בלי אינטרנט באמצעות bluetooth בתצריכת אנרגיה נמוכה";
+"app_info.features.offline.title" = "תקשורת לא מקוונת";
+"app_info.features.title" = "יכולות";
+"app_info.how_to_use.change_channels" = "• הקש על #mesh כדי להחליף ערוץ";
+"app_info.how_to_use.clear_chat" = "• הקש שלוש פעמים על הצ'אט כדי לנקות";
+"app_info.how_to_use.commands" = "• הקלד / כדי לראות פקודות";
+"app_info.how_to_use.open_sidebar" = "• הקש על אייקון האנשים כדי לפתוח סרגל צד";
+"app_info.how_to_use.set_nickname" = "• הקש על הכינוי שלך כדי לעדכן";
+"app_info.how_to_use.start_dm" = "• הקש על שם עמית כדי להתחיל הודעה פרטית";
+"app_info.how_to_use.title" = "איך להשתמש";
+"app_info.privacy.ephemeral.description" = "מזהה עמית חדש נוצר באופן קבוע";
+"app_info.privacy.ephemeral.title" = "זהות זמנית";
+"app_info.privacy.no_tracking.description" = "אין שרתים, חשבונות או איסוף נתונים";
+"app_info.privacy.no_tracking.title" = "ללא מעקב";
+"app_info.privacy.panic.description" = "הקש על הלוגו שלוש פעמים למחיקת כל הנתונים מייד";
+"app_info.privacy.panic.title" = "מצב בהלה";
+"app_info.privacy.title" = "פרטיות";
+"app_info.tagline" = "sidegroupchat";
+"app_info.warning.message" = "אבטחת ההודעות הפרטיות עדיין לא נבדקה במלואה. אל תשתמש למצבים קריטיים עד שהאזהרה תיעלם.";
+"app_info.warning.title" = "אזהרה";
+"common.cancel" = "ביטול";
+"common.close" = "סגור";
+"common.copy" = "העתק";
+"common.ok" = "OK";
+"common.toggle.off" = "כבוי";
+"common.toggle.on" = "פעיל";
+"common.unknown" = "לא ידוע";
+"content.accessibility.add_favorite" = "הוסף למועדפים";
+"content.accessibility.available_nostr" = "זמין דרך nostr";
+"content.accessibility.back_to_main_chat" = "חזרה לצ'אט הראשי";
+"content.accessibility.connected_mesh" = "מחובר דרך mesh";
+"content.accessibility.encryption_status" = "מצב הצפנה: %@";
+"content.accessibility.location_channels" = "ערוצי מיקום";
+"content.accessibility.location_notes" = "הערות מיקום למקום הזה";
+"content.accessibility.open_unread_private_chat" = "פתח צ'אט פרטי שלא נקרא";
+"content.accessibility.private_chat_header" = "צ'אט פרטי עם %@";
+"content.accessibility.reachable_mesh" = "זמין דרך mesh";
+"content.accessibility.remove_favorite" = "הסר מהמועדפים";
+"content.accessibility.send_hint_empty" = "הזן הודעה לשליחה";
+"content.accessibility.send_hint_ready" = "הקש פעמיים לשליחה";
+"content.accessibility.send_message" = "שלח הודעה";
+"content.accessibility.toggle_bookmark" = "החלף סימנייה עבור #%@";
+"content.accessibility.toggle_favorite_hint" = "הקש פעמיים כדי להחליף מצב מועדפים";
+"content.accessibility.view_fingerprint_hint" = "הקש להצגת טביעת ההצפנה";
+"content.actions.block" = "חסום";
+"content.actions.direct_message" = "הודעה ישירה";
+"content.actions.hug" = "חיבוק";
+"content.actions.mention" = "אזכור";
+"content.actions.slap" = "סטירה";
+"content.actions.title" = "פעולות";
+"content.alert.bluetooth_required.off" = "bluetooth כבוי. הפעל bluetooth בהגדרות כדי להשתמש ב-bitchat.";
+"content.alert.bluetooth_required.permission" = "bitchat צריכה הרשאת bluetooth כדי להתחבר למכשירים קרובים. אפשר גישה בהגדרות.";
+"content.alert.bluetooth_required.settings" = "הגדרות";
+"content.alert.bluetooth_required.title" = "נדרש bluetooth";
+"content.alert.bluetooth_required.unsupported" = "המכשיר הזה לא תומך ב-bluetooth. bitchat זקוקה ל-bluetooth כדי לעבוד.";
+"content.alert.screenshot.message" = "צילומי מסך של ערוצי מיקום יחשפו את מיקומך. חשב לפני שיתוף פומבי.";
+"content.alert.screenshot.title" = "שים לב";
+"content.commands.block" = "חסום או הצג עמיתים חסומים";
+"content.commands.clear" = "נקה הודעות צ'אט";
+"content.commands.favorite" = "הוסף למועדפים";
+"content.commands.hug" = "שלח חיבוק חם";
+"content.commands.message" = "שלח הודעה פרטית";
+"content.commands.slap" = "תן למישהו סטירת פורל";
+"content.commands.unblock" = "בטל חסימה לעמית";
+"content.commands.unfavorite" = "הסר מהמועדפים";
+"content.commands.who" = "ראה מי מחובר";
+"content.delivery.delivered_members" = "נמסר ל-%1$d מתוך %2$d חברים";
+"content.delivery.delivered_to" = "נמסר ל-%@";
+"content.delivery.failed" = "נכשל: %@";
+"content.delivery.read_by" = "נקרא על ידי %@";
+"content.delivery.reason.blocked" = "המשתמש חסום";
+"content.delivery.reason.self" = "אי אפשר לשלוח לעצמך";
+"content.delivery.reason.send_error" = "שגיאת שליחה";
+"content.delivery.reason.unknown_recipient" = "נמען לא ידוע";
+"content.delivery.reason.unreachable" = "עמית לא זמין";
+"content.header.people" = "אנשים";
+"content.help.verification" = "אימות: הצג את ה-qr שלי או סרוק חבר";
+"content.input.message_placeholder" = "כתוב הודעה...";
+"content.input.nickname_placeholder" = "כינוי";
+"content.location.enable" = "הפעל מיקום";
+"content.message.copy" = "העתק הודעה";
+"content.message.show_less" = "הצג פחות";
+"content.message.show_more" = "הצג עוד";
+"content.notes.location_unavailable" = "המיקום לא זמין";
+"content.notes.title" = "הערות";
+"content.payment.cashu" = "תשלום דרך cashu";
+"content.payment.lightning" = "תשלום דרך lightning";
+"encryption.accessibility.establishing" = "הצפנה בהקמה";
+"encryption.accessibility.failed" = "הצפנה נכשלה";
+"encryption.accessibility.not_encrypted" = "לא מוצפן";
+"encryption.accessibility.secured" = "מוצפן";
+"encryption.accessibility.verified" = "מוצפן ומאומת";
+"encryption.status.establishing" = "מקימים הצפנה...";
+"encryption.status.failed" = "הצפנה נכשלה";
+"encryption.status.not_encrypted" = "לא מוצפן";
+"encryption.status.secured" = "מוצפן";
+"encryption.status.verified" = "מוצפן ומאומת";
+"fingerprint.action.mark_verified" = "סמן כמאומת";
+"fingerprint.action.remove_verification" = "הסר אימות";
+"fingerprint.badge.not_verified" = "⚠️ לא מאומת";
+"fingerprint.badge.verified" = "✓ מאומת";
+"fingerprint.handshake_pending" = "לא זמין - handshake מתבצע";
+"fingerprint.message.verified" = "אישרת את זהותו של האדם הזה.";
+"fingerprint.message.verify_hint" = "השווה את הטביעות עם %@ בערוץ מאובטח.";
+"fingerprint.their_label" = "הטבעת שלהם:";
+"fingerprint.title" = "אימות אבטחה";
+"fingerprint.your_label" = "הטבעת שלך:";
+"geohash_people.action.block" = "חסום";
+"geohash_people.action.unblock" = "בטל חסימה";
+"geohash_people.none_nearby" = "אין אף אחד בסביבה...";
+"geohash_people.tooltip.blocked" = "חסום ב-geohash";
+"geohash_people.you_suffix" = " (אתה)";
+"location_channels.action.open_settings" = "פתח הגדרות";
+"location_channels.action.remove_access" = "הסר גישת מיקום";
+"location_channels.action.request_permissions" = "קבל את המיקום וה-geohash שלי";
+"location_channels.action.teleport" = "טלפורט";
+"location_channels.bookmarked_section_title" = "שמורים";
+"location_channels.description" = "שוחח עם אנשים קרובים בערוצי geohash. משתף רק geohash גס, אף פעם לא gps מדויק. כתובת ה-ip מוסתרת כי כל התעבורה עוברת דרך tor.";
+"location_channels.error.invalid_geohash" = "geohash לא תקף";
+"location_channels.loading_nearby" = "מחפש ערוצים קרובים…";
+"location_channels.mesh_label" = "mesh";
+"location_channels.permission_denied" = "הרשאת מיקום נדחתה. אפשר בהגדרות כדי להשתמש בערוצי מיקום.";
+"location_channels.subtitle_prefix" = "#%@ • %@";
+"location_channels.subtitle_with_name" = "%1$@ • %2$@";
+"location_channels.title" = "#ערוצי מיקום";
+"location_channels.tor.subtitle" = "מסתיר את ה-ip שלך לערוצי מיקום. מומלץ: פעיל.";
+"location_channels.tor.title" = "ניתוב tor";
+"location_levels.block" = "בלוק";
+"location_levels.building" = "מבנה";
+"location_levels.city" = "עיר";
+"location_levels.neighborhood" = "שכונה";
+"location_levels.province" = "מחוז";
+"location_levels.region" = "אזור";
+"location_notes.action.dismiss" = "סגור";
+"location_notes.action.retry" = "ניסיון שוב";
+"location_notes.description" = "הוסף הערות קצרות וקבועות למקום הזה כדי שאחרים ימצאו.";
+"location_notes.empty_subtitle" = "היה הראשון להוסיף כאן.";
+"location_notes.empty_title" = "אין הערות עדיין";
+"location_notes.error.failed_to_send" = "לא ניתן לשלוח את ההערה. %@";
+"location_notes.error.no_relays" = "אין ממסרי geo זמינים בקרבת מקום. נסה שוב מאוחר יותר.";
+"location_notes.loading_notes" = "טוען הערות…";
+"location_notes.loading_recent" = "טוען הערות אחרונות…";
+"location_notes.no_relays_nearby" = "אין ממסרי geo קרובים";
+"location_notes.placeholder" = "הוסף הערה למקום הזה";
+"location_notes.relays_paused" = "ממסרי geo אינם זמינים; הערות הושהו";
+"location_notes.relays_retry_hint" = "הערות תלויות בממסרי geo. בדוק את החיבור ונסה שוב.";
+"mesh_peers.tooltip.new_messages" = "הודעות חדשות";
+"system.chat.blocked" = "לא ניתן להתחיל צ'אט עם %@: המשתמש חסום.";
+"system.chat.requires_favorite" = "לא ניתן להתחיל צ'אט עם %@: נדרשים מועדפים הדדיים לאופליין.";
+"system.common.user" = "משתמש";
+"system.dm.blocked_generic" = "אי אפשר לשלוח: המשתמש חסום.";
+"system.dm.blocked_recipient" = "אי אפשר לשלוח ל-%@: המשתמש חסום.";
+"system.dm.unreachable" = "אי אפשר לשלוח ל-%@: הנמען אינו נגיש דרך mesh או nostr.";
+"system.geohash.blocked" = "%@ נחסם בצ'אטי geohash";
+"system.geohash.unblocked" = "%@ הוסר מהחסימה בצ'אטי geohash";
+"system.location.not_in_channel" = "אי אפשר לשלוח: אינך בערוץ מיקום";
+"system.location.send_failed" = "השליחה לערוץ המיקום נכשלה";
+"system.tor.dev_bypass" = "בנייה לפיתוח: עקיפת tor פעילה.";
+"system.tor.restarted" = "tor הופעל מחדש. הניתוב שוחזר.";
+"system.tor.restarting" = "tor מופעל מחדש להשבת החיבור...";
+"system.tor.started" = "tor פעיל. כל הצ'אט עובר דרך tor לפרטיות.";
+"system.tor.starting" = "tor מופעל...";
+"verification.my_qr.accessibility_label" = "קוד qr לאימות";
+"verification.my_qr.title" = "סרוק כדי לאמת";
+"verification.my_qr.unavailable" = "qr לא זמין";
+"verification.scan.paste_prompt" = "הדבק תוכן qr לאימות:";
+"verification.scan.prompt_friend" = "סרוק qr של חבר";
+"verification.scan.status.invalid" = "qr לא תקף או שפג תוקפו";
+"verification.scan.status.no_peer" = "לא נמצא עמית תואם";
+"verification.scan.status.requested" = "התבקש אימות עבור %@";
+"verification.scan.validate" = "אשר";
+"verification.sheet.title" = "אימות";
diff --git a/bitchat/Localization/he.lproj/Localizable.stringsdict b/bitchat/Localization/he.lproj/Localizable.stringsdict
new file mode 100644
index 00000000..0aa0672c
--- /dev/null
+++ b/bitchat/Localization/he.lproj/Localizable.stringsdict
@@ -0,0 +1,66 @@
+
+
+
+
+ location_notes.header
+
+ NSStringLocalizedFormatKey
+ #%@ • %#@note_count@
+ note_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ d
+ one
+ %d הערה
+ two
+ %d הערות
+ many
+ %d הערות
+ other
+ %d הערות
+
+
+ location_channels.row_title
+
+ NSStringLocalizedFormatKey
+ %@ [%#@people_count@]
+ people_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ d
+ one
+ %d אדם
+ two
+ %d אנשים
+ many
+ %d אנשים
+ other
+ %d אנשים
+
+
+ content.accessibility.people_count
+
+ NSStringLocalizedFormatKey
+ %#@people@
+ people
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ d
+ one
+ %d אדם
+ two
+ %d אנשים
+ many
+ %d אנשים
+ other
+ %d אנשים
+
+
+
+
diff --git a/bitchat/Localization/id.lproj/Localizable.strings b/bitchat/Localization/id.lproj/Localizable.strings
new file mode 100644
index 00000000..d131516c
--- /dev/null
+++ b/bitchat/Localization/id.lproj/Localizable.strings
@@ -0,0 +1,190 @@
+/*
+ Localizable.strings
+ bitchat (Indonesian)
+*/
+
+"app_info.app_name" = "bitchat";
+"app_info.close" = "tutup";
+"app_info.done" = "SELESAI";
+"app_info.features.encryption.description" = "pesan pribadi dienkripsi dengan protokol noise";
+"app_info.features.encryption.title" = "enkripsi ujung ke ujung";
+"app_info.features.extended_range.description" = "pesan diteruskan antar peer sehingga jangkauannya lebih jauh";
+"app_info.features.extended_range.title" = "jangkauan diperluas";
+"app_info.features.favorites.description" = "dapatkan notifikasi saat orang favoritmu bergabung";
+"app_info.features.favorites.title" = "favorit";
+"app_info.features.geohash.description" = "kanal geohash untuk ngobrol dengan orang di wilayah sekitar lewat relay anonim terdesentralisasi";
+"app_info.features.geohash.title" = "kanal lokal";
+"app_info.features.mentions.description" = "pakai @nickname untuk memberi tahu orang tertentu";
+"app_info.features.mentions.title" = "mention";
+"app_info.features.offline.description" = "bekerja tanpa internet memakai bluetooth low energy";
+"app_info.features.offline.title" = "komunikasi offline";
+"app_info.features.title" = "FITUR";
+"app_info.how_to_use.change_channels" = "• ketuk #mesh untuk ganti kanal";
+"app_info.how_to_use.clear_chat" = "• ketuk chat tiga kali untuk menghapus";
+"app_info.how_to_use.commands" = "• ketik / untuk melihat perintah";
+"app_info.how_to_use.open_sidebar" = "• ketuk ikon orang untuk membuka sidebar";
+"app_info.how_to_use.set_nickname" = "• atur nama panggilanmu dengan mengetuknya";
+"app_info.how_to_use.start_dm" = "• ketuk nama peer untuk mulai dm";
+"app_info.how_to_use.title" = "CARA PAKAI";
+"app_info.privacy.ephemeral.description" = "id peer baru dibuat secara berkala";
+"app_info.privacy.ephemeral.title" = "identitas sementara";
+"app_info.privacy.no_tracking.description" = "tanpa server, akun, atau pengumpulan data";
+"app_info.privacy.no_tracking.title" = "tanpa pelacakan";
+"app_info.privacy.panic.description" = "ketuk logo tiga kali untuk langsung menghapus semua data";
+"app_info.privacy.panic.title" = "mode panik";
+"app_info.privacy.title" = "PRIVASI";
+"app_info.tagline" = "sidegroupchat";
+"app_info.warning.message" = "keamanan pesan pribadi belum diaudit sepenuhnya. jangan dipakai untuk situasi kritis sampai peringatan ini hilang.";
+"app_info.warning.title" = "PERINGATAN";
+"common.cancel" = "batal";
+"common.close" = "tutup";
+"common.copy" = "salin";
+"common.ok" = "OK";
+"common.toggle.off" = "mati";
+"common.toggle.on" = "nyala";
+"common.unknown" = "tidak diketahui";
+"content.accessibility.add_favorite" = "tambah ke favorit";
+"content.accessibility.available_nostr" = "tersedia melalui nostr";
+"content.accessibility.back_to_main_chat" = "kembali ke chat utama";
+"content.accessibility.connected_mesh" = "terhubung lewat mesh";
+"content.accessibility.encryption_status" = "status enkripsi: %@";
+"content.accessibility.location_channels" = "kanal lokasi";
+"content.accessibility.location_notes" = "catatan lokasi untuk tempat ini";
+"content.accessibility.open_unread_private_chat" = "buka chat pribadi belum dibaca";
+"content.accessibility.private_chat_header" = "chat pribadi dengan %@";
+"content.accessibility.reachable_mesh" = "dapat dijangkau lewat mesh";
+"content.accessibility.remove_favorite" = "hapus dari favorit";
+"content.accessibility.send_hint_empty" = "masukkan pesan untuk dikirim";
+"content.accessibility.send_hint_ready" = "ketuk dua kali untuk mengirim";
+"content.accessibility.send_message" = "kirim pesan";
+"content.accessibility.toggle_bookmark" = "ubah penanda untuk #%@";
+"content.accessibility.toggle_favorite_hint" = "ketuk dua kali untuk mengubah status favorit";
+"content.accessibility.view_fingerprint_hint" = "ketuk untuk melihat sidik enkripsi";
+"content.actions.block" = "blokir";
+"content.actions.direct_message" = "pesan langsung";
+"content.actions.hug" = "peluk";
+"content.actions.mention" = "sebut";
+"content.actions.slap" = "tampar";
+"content.actions.title" = "aksi";
+"content.alert.bluetooth_required.off" = "bluetooth dimatikan. aktifkan bluetooth di pengaturan untuk memakai bitchat.";
+"content.alert.bluetooth_required.permission" = "bitchat memerlukan izin bluetooth untuk terhubung dengan perangkat dekat. aktifkan akses di pengaturan.";
+"content.alert.bluetooth_required.settings" = "pengaturan";
+"content.alert.bluetooth_required.title" = "butuh bluetooth";
+"content.alert.bluetooth_required.unsupported" = "perangkat ini tidak mendukung bluetooth. bitchat memerlukan bluetooth untuk berjalan.";
+"content.alert.screenshot.message" = "tangkapan layar kanal lokasi akan mengungkap lokasimu. pikirkan dulu sebelum membagikannya.";
+"content.alert.screenshot.title" = "perhatian";
+"content.commands.block" = "blokir atau lihat peer yang diblokir";
+"content.commands.clear" = "hapus pesan chat";
+"content.commands.favorite" = "tambah ke favorit";
+"content.commands.hug" = "kirim pelukan hangat";
+"content.commands.message" = "kirim pesan pribadi";
+"content.commands.slap" = "tampar seseorang dengan ikan trout";
+"content.commands.unblock" = "buka blokir peer";
+"content.commands.unfavorite" = "hapus dari favorit";
+"content.commands.who" = "lihat siapa yang online";
+"content.delivery.delivered_members" = "terkirim ke %1$d dari %2$d anggota";
+"content.delivery.delivered_to" = "terkirim ke %@";
+"content.delivery.failed" = "gagal: %@";
+"content.delivery.read_by" = "dibaca oleh %@";
+"content.delivery.reason.blocked" = "pengguna diblokir";
+"content.delivery.reason.self" = "tidak bisa kirim ke diri sendiri";
+"content.delivery.reason.send_error" = "kesalahan pengiriman";
+"content.delivery.reason.unknown_recipient" = "penerima tidak dikenal";
+"content.delivery.reason.unreachable" = "peer tidak dapat dijangkau";
+"content.header.people" = "ORANG";
+"content.help.verification" = "verifikasi: tampilkan qr-ku atau pindai teman";
+"content.input.message_placeholder" = "ketik pesan...";
+"content.input.nickname_placeholder" = "nama panggilan";
+"content.location.enable" = "aktifkan lokasi";
+"content.message.copy" = "salin pesan";
+"content.message.show_less" = "tampilkan lebih sedikit";
+"content.message.show_more" = "tampilkan lebih banyak";
+"content.notes.location_unavailable" = "lokasi tidak tersedia";
+"content.notes.title" = "catatan";
+"content.payment.cashu" = "bayar via cashu";
+"content.payment.lightning" = "bayar via lightning";
+"encryption.accessibility.establishing" = "menyiapkan enkripsi";
+"encryption.accessibility.failed" = "enkripsi gagal";
+"encryption.accessibility.not_encrypted" = "tidak terenkripsi";
+"encryption.accessibility.secured" = "terenkripsi";
+"encryption.accessibility.verified" = "terenkripsi dan terverifikasi";
+"encryption.status.establishing" = "menyiapkan enkripsi...";
+"encryption.status.failed" = "enkripsi gagal";
+"encryption.status.not_encrypted" = "tidak terenkripsi";
+"encryption.status.secured" = "terenkripsi";
+"encryption.status.verified" = "terenkripsi dan terverifikasi";
+"fingerprint.action.mark_verified" = "tandai sebagai terverifikasi";
+"fingerprint.action.remove_verification" = "hapus verifikasi";
+"fingerprint.badge.not_verified" = "⚠️ BELUM TERVERIFIKASI";
+"fingerprint.badge.verified" = "✓ TERVERIFIKASI";
+"fingerprint.handshake_pending" = "tidak tersedia - handshake sedang berlangsung";
+"fingerprint.message.verified" = "kamu sudah memverifikasi identitas orang ini.";
+"fingerprint.message.verify_hint" = "bandingkan sidik ini dengan %@ lewat kanal aman.";
+"fingerprint.their_label" = "sidik mereka:";
+"fingerprint.title" = "verifikasi keamanan";
+"fingerprint.your_label" = "sidikmu:";
+"geohash_people.action.block" = "blokir";
+"geohash_people.action.unblock" = "buka blokir";
+"geohash_people.none_nearby" = "tidak ada siapa pun...";
+"geohash_people.tooltip.blocked" = "diblokir di geohash";
+"geohash_people.you_suffix" = " (kamu)";
+"location_channels.action.open_settings" = "buka pengaturan";
+"location_channels.action.remove_access" = "cabut akses lokasi";
+"location_channels.action.request_permissions" = "ambil lokasiku dan geohash";
+"location_channels.action.teleport" = "teleport";
+"location_channels.bookmarked_section_title" = "disimpan";
+"location_channels.description" = "ngobrol dengan orang terdekat lewat kanal geohash. hanya geohash kasar yang dibagikan, tidak pernah gps tepat. alamat ip-mu tersembunyi karena seluruh trafik lewat tor.";
+"location_channels.error.invalid_geohash" = "geohash tidak valid";
+"location_channels.loading_nearby" = "mencari kanal sekitar…";
+"location_channels.mesh_label" = "mesh";
+"location_channels.permission_denied" = "izin lokasi ditolak. aktifkan di pengaturan untuk memakai kanal lokasi.";
+"location_channels.subtitle_prefix" = "#%@ • %@";
+"location_channels.subtitle_with_name" = "%1$@ • %2$@";
+"location_channels.title" = "#kanal lokasi";
+"location_channels.tor.subtitle" = "menyembunyikan ip-mu untuk kanal lokasi. disarankan: aktif.";
+"location_channels.tor.title" = "perutean tor";
+"location_levels.block" = "blok";
+"location_levels.building" = "gedung";
+"location_levels.city" = "kota";
+"location_levels.neighborhood" = "lingkungan";
+"location_levels.province" = "provinsi";
+"location_levels.region" = "wilayah";
+"location_notes.action.dismiss" = "tutup";
+"location_notes.action.retry" = "coba lagi";
+"location_notes.description" = "tambahkan catatan permanen singkat di tempat ini agar orang lain menemukannya.";
+"location_notes.empty_subtitle" = "jadilah orang pertama yang menambahkannya di sini.";
+"location_notes.empty_title" = "belum ada catatan";
+"location_notes.error.failed_to_send" = "tidak bisa mengirim catatan. %@";
+"location_notes.error.no_relays" = "tidak ada relay geo tersedia dekat sini. coba lagi nanti.";
+"location_notes.loading_notes" = "memuat catatan…";
+"location_notes.loading_recent" = "memuat catatan terbaru…";
+"location_notes.no_relays_nearby" = "tidak ada relay geo di dekatmu";
+"location_notes.placeholder" = "tambahkan catatan untuk tempat ini";
+"location_notes.relays_paused" = "relay geo tidak tersedia; catatan dijeda";
+"location_notes.relays_retry_hint" = "catatan bergantung pada relay geo. cek koneksi lalu coba lagi.";
+"mesh_peers.tooltip.new_messages" = "pesan baru";
+"system.chat.blocked" = "tidak bisa mulai chat dengan %@: pengguna diblokir.";
+"system.chat.requires_favorite" = "tidak bisa mulai chat dengan %@: butuh favorit bersama untuk offline.";
+"system.common.user" = "pengguna";
+"system.dm.blocked_generic" = "tidak bisa mengirim: pengguna diblokir.";
+"system.dm.blocked_recipient" = "tidak bisa mengirim ke %@: pengguna diblokir.";
+"system.dm.unreachable" = "tidak bisa mengirim ke %@: penerima tidak dapat dijangkau lewat mesh atau nostr.";
+"system.geohash.blocked" = "%@ diblokir di chat geohash";
+"system.geohash.unblocked" = "%@ dibuka blokirnya di chat geohash";
+"system.location.not_in_channel" = "gagal mengirim: kamu tidak berada di kanal lokasi";
+"system.location.send_failed" = "gagal mengirim ke kanal lokasi";
+"system.tor.dev_bypass" = "build pengembangan: bypass tor aktif.";
+"system.tor.restarted" = "tor dimulai ulang. perutean dipulihkan.";
+"system.tor.restarting" = "tor sedang dimulai ulang untuk memulihkan konektivitas...";
+"system.tor.started" = "tor berjalan. seluruh chat dirutekan lewat tor demi privasi.";
+"system.tor.starting" = "menjalankan tor...";
+"verification.my_qr.accessibility_label" = "kode qr verifikasi";
+"verification.my_qr.title" = "pindai untuk verifikasi";
+"verification.my_qr.unavailable" = "qr tidak tersedia";
+"verification.scan.paste_prompt" = "tempel konten qr untuk validasi:";
+"verification.scan.prompt_friend" = "pindai qr teman";
+"verification.scan.status.invalid" = "qr tidak valid atau kedaluwarsa";
+"verification.scan.status.no_peer" = "tidak ada peer yang cocok";
+"verification.scan.status.requested" = "verifikasi diminta untuk %@";
+"verification.scan.validate" = "validasi";
+"verification.sheet.title" = "VERIFIKASI";
diff --git a/bitchat/Localization/id.lproj/Localizable.stringsdict b/bitchat/Localization/id.lproj/Localizable.stringsdict
new file mode 100644
index 00000000..7ad47950
--- /dev/null
+++ b/bitchat/Localization/id.lproj/Localizable.stringsdict
@@ -0,0 +1,54 @@
+
+
+
+
+ location_notes.header
+
+ NSStringLocalizedFormatKey
+ #%@ • %#@note_count@
+ note_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ d
+ one
+ %d catatan
+ other
+ %d catatan
+
+
+ location_channels.row_title
+
+ NSStringLocalizedFormatKey
+ %@ [%#@people_count@]
+ people_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ d
+ one
+ %d orang
+ other
+ %d orang
+
+
+ content.accessibility.people_count
+
+ NSStringLocalizedFormatKey
+ %#@people@
+ people
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ d
+ one
+ %d orang
+ other
+ %d orang
+
+
+
+
diff --git a/bitchat/Localization/it.lproj/Localizable.strings b/bitchat/Localization/it.lproj/Localizable.strings
new file mode 100644
index 00000000..c04ae729
--- /dev/null
+++ b/bitchat/Localization/it.lproj/Localizable.strings
@@ -0,0 +1,190 @@
+/*
+ Localizable.strings
+ bitchat (Italian)
+*/
+
+"app_info.app_name" = "bitchat";
+"app_info.close" = "chiudi";
+"app_info.done" = "FATTO";
+"app_info.features.encryption.description" = "messaggi privati cifrati con il protocollo noise";
+"app_info.features.encryption.title" = "crittografia end-to-end";
+"app_info.features.extended_range.description" = "i messaggi vengono inoltrati tra peer per arrivare più lontano";
+"app_info.features.extended_range.title" = "portata estesa";
+"app_info.features.favorites.description" = "ricevi avvisi quando entrano le tue persone preferite";
+"app_info.features.favorites.title" = "preferiti";
+"app_info.features.geohash.description" = "canali geohash per chattare con persone vicine tramite relay anonimi decentralizzati";
+"app_info.features.geohash.title" = "canali locali";
+"app_info.features.mentions.description" = "usa @nickname per avvisare persone specifiche";
+"app_info.features.mentions.title" = "menzioni";
+"app_info.features.offline.description" = "funziona senza internet usando bluetooth a basso consumo";
+"app_info.features.offline.title" = "comunicazione offline";
+"app_info.features.title" = "FUNZIONI";
+"app_info.how_to_use.change_channels" = "• tocca #mesh per cambiare canale";
+"app_info.how_to_use.clear_chat" = "• tocca tre volte la chat per svuotarla";
+"app_info.how_to_use.commands" = "• digita / per vedere i comandi";
+"app_info.how_to_use.open_sidebar" = "• tocca l'icona persone per aprire la barra laterale";
+"app_info.how_to_use.set_nickname" = "• imposta il tuo nickname toccandolo";
+"app_info.how_to_use.start_dm" = "• tocca il nome di un peer per avviare un dm";
+"app_info.how_to_use.title" = "COME SI USA";
+"app_info.privacy.ephemeral.description" = "nuovo id peer generato regolarmente";
+"app_info.privacy.ephemeral.title" = "identità effimera";
+"app_info.privacy.no_tracking.description" = "niente server, account o raccolta dati";
+"app_info.privacy.no_tracking.title" = "senza tracciamento";
+"app_info.privacy.panic.description" = "tocca il logo tre volte per cancellare subito tutti i dati";
+"app_info.privacy.panic.title" = "modalità panico";
+"app_info.privacy.title" = "PRIVACY";
+"app_info.tagline" = "sidegroupchat";
+"app_info.warning.message" = "la sicurezza dei messaggi privati non è stata ancora auditata completamente. non usarli in situazioni critiche finché questo avviso resta.";
+"app_info.warning.title" = "AVVISO";
+"common.cancel" = "annulla";
+"common.close" = "chiudi";
+"common.copy" = "copia";
+"common.ok" = "OK";
+"common.toggle.off" = "spento";
+"common.toggle.on" = "acceso";
+"common.unknown" = "sconosciuto";
+"content.accessibility.add_favorite" = "aggiungi ai preferiti";
+"content.accessibility.available_nostr" = "disponibile via nostr";
+"content.accessibility.back_to_main_chat" = "torna alla chat principale";
+"content.accessibility.connected_mesh" = "connesso tramite mesh";
+"content.accessibility.encryption_status" = "stato crittografia: %@";
+"content.accessibility.location_channels" = "canali posizione";
+"content.accessibility.location_notes" = "note di posizione per questo posto";
+"content.accessibility.open_unread_private_chat" = "apri chat privata non letta";
+"content.accessibility.private_chat_header" = "chat privata con %@";
+"content.accessibility.reachable_mesh" = "raggiungibile via mesh";
+"content.accessibility.remove_favorite" = "rimuovi dai preferiti";
+"content.accessibility.send_hint_empty" = "inserisci un messaggio da inviare";
+"content.accessibility.send_hint_ready" = "tocca due volte per inviare";
+"content.accessibility.send_message" = "invia messaggio";
+"content.accessibility.toggle_bookmark" = "cambia segnalibro per #%@";
+"content.accessibility.toggle_favorite_hint" = "tocca due volte per cambiare stato preferito";
+"content.accessibility.view_fingerprint_hint" = "tocca per vedere l'impronta di cifratura";
+"content.actions.block" = "blocca";
+"content.actions.direct_message" = "messaggio diretto";
+"content.actions.hug" = "abbraccia";
+"content.actions.mention" = "menziona";
+"content.actions.slap" = "schiaffo";
+"content.actions.title" = "azioni";
+"content.alert.bluetooth_required.off" = "bluetooth è disattivato. attiva bluetooth nelle impostazioni per usare bitchat.";
+"content.alert.bluetooth_required.permission" = "bitchat richiede l'autorizzazione bluetooth per collegarsi ai dispositivi vicini. abilita l'accesso nelle impostazioni.";
+"content.alert.bluetooth_required.settings" = "impostazioni";
+"content.alert.bluetooth_required.title" = "serve bluetooth";
+"content.alert.bluetooth_required.unsupported" = "questo dispositivo non supporta bluetooth. bitchat richiede bluetooth per funzionare.";
+"content.alert.screenshot.message" = "gli screenshot dei canali posizione rivelano la tua posizione. pensaci prima di condividerli.";
+"content.alert.screenshot.title" = "attenzione";
+"content.commands.block" = "blocca o mostra i peer bloccati";
+"content.commands.clear" = "svuota la chat";
+"content.commands.favorite" = "aggiungi ai preferiti";
+"content.commands.hug" = "invia un caldo abbraccio";
+"content.commands.message" = "invia messaggio privato";
+"content.commands.slap" = "schiaffeggia qualcuno con una trota";
+"content.commands.unblock" = "sblocca un peer";
+"content.commands.unfavorite" = "rimuovi dai preferiti";
+"content.commands.who" = "vedi chi è online";
+"content.delivery.delivered_members" = "consegnato a %1$d di %2$d membri";
+"content.delivery.delivered_to" = "consegnato a %@";
+"content.delivery.failed" = "non riuscito: %@";
+"content.delivery.read_by" = "letto da %@";
+"content.delivery.reason.blocked" = "utente bloccato";
+"content.delivery.reason.self" = "impossibile inviarti il messaggio";
+"content.delivery.reason.send_error" = "errore di invio";
+"content.delivery.reason.unknown_recipient" = "destinatario sconosciuto";
+"content.delivery.reason.unreachable" = "peer irraggiungibile";
+"content.header.people" = "PERSONE";
+"content.help.verification" = "verifica: mostra il mio qr o scansiona un amico";
+"content.input.message_placeholder" = "scrivi un messaggio...";
+"content.input.nickname_placeholder" = "nickname";
+"content.location.enable" = "attiva posizione";
+"content.message.copy" = "copia messaggio";
+"content.message.show_less" = "mostra meno";
+"content.message.show_more" = "mostra di più";
+"content.notes.location_unavailable" = "posizione non disponibile";
+"content.notes.title" = "note";
+"content.payment.cashu" = "paga con cashu";
+"content.payment.lightning" = "paga con lightning";
+"encryption.accessibility.establishing" = "avvio crittografia";
+"encryption.accessibility.failed" = "crittografia fallita";
+"encryption.accessibility.not_encrypted" = "non crittografato";
+"encryption.accessibility.secured" = "crittografato";
+"encryption.accessibility.verified" = "crittografato e verificato";
+"encryption.status.establishing" = "avvio della crittografia...";
+"encryption.status.failed" = "crittografia fallita";
+"encryption.status.not_encrypted" = "non crittografato";
+"encryption.status.secured" = "crittografato";
+"encryption.status.verified" = "crittografato e verificato";
+"fingerprint.action.mark_verified" = "segna come verificato";
+"fingerprint.action.remove_verification" = "rimuovi verifica";
+"fingerprint.badge.not_verified" = "⚠️ NON VERIFICATO";
+"fingerprint.badge.verified" = "✓ VERIFICATO";
+"fingerprint.handshake_pending" = "non disponibile - handshake in corso";
+"fingerprint.message.verified" = "hai verificato l'identità di questa persona.";
+"fingerprint.message.verify_hint" = "confronta queste impronte con %@ tramite un canale sicuro.";
+"fingerprint.their_label" = "impronta loro:";
+"fingerprint.title" = "verifica di sicurezza";
+"fingerprint.your_label" = "tua impronta:";
+"geohash_people.action.block" = "blocca";
+"geohash_people.action.unblock" = "sblocca";
+"geohash_people.none_nearby" = "nessuno nei dintorni...";
+"geohash_people.tooltip.blocked" = "bloccato su geohash";
+"geohash_people.you_suffix" = " (tu)";
+"location_channels.action.open_settings" = "apri impostazioni";
+"location_channels.action.remove_access" = "revoca accesso alla posizione";
+"location_channels.action.request_permissions" = "ottieni la mia posizione e i geohash";
+"location_channels.action.teleport" = "teletrasporto";
+"location_channels.bookmarked_section_title" = "salvati";
+"location_channels.description" = "chatta con le persone vicine tramite canali geohash. condividiamo solo geohash approssimativi, mai il gps esatto. il tuo ip resta nascosto perché tutto il traffico passa da tor.";
+"location_channels.error.invalid_geohash" = "geohash non valido";
+"location_channels.loading_nearby" = "ricerca canali vicini…";
+"location_channels.mesh_label" = "mesh";
+"location_channels.permission_denied" = "autorizzazione posizione negata. abilitala nelle impostazioni per usare i canali.";
+"location_channels.subtitle_prefix" = "#%@ • %@";
+"location_channels.subtitle_with_name" = "%1$@ • %2$@";
+"location_channels.title" = "#canali posizione";
+"location_channels.tor.subtitle" = "nasconde il tuo ip per i canali posizione. consigliato: attivo.";
+"location_channels.tor.title" = "instradamento tor";
+"location_levels.block" = "isolato";
+"location_levels.building" = "edificio";
+"location_levels.city" = "città";
+"location_levels.neighborhood" = "quartiere";
+"location_levels.province" = "provincia";
+"location_levels.region" = "regione";
+"location_notes.action.dismiss" = "chiudi";
+"location_notes.action.retry" = "riprova";
+"location_notes.description" = "aggiungi brevi note permanenti su questo luogo per chi verrà.";
+"location_notes.empty_subtitle" = "fai tu la prima nota qui.";
+"location_notes.empty_title" = "ancora nessuna nota";
+"location_notes.error.failed_to_send" = "impossibile inviare la nota. %@";
+"location_notes.error.no_relays" = "nessun relay geo disponibile qui vicino. riprova presto.";
+"location_notes.loading_notes" = "caricamento note…";
+"location_notes.loading_recent" = "caricamento note recenti…";
+"location_notes.no_relays_nearby" = "nessun relay geo vicino";
+"location_notes.placeholder" = "aggiungi una nota per questo posto";
+"location_notes.relays_paused" = "relay geo non disponibili; note in pausa";
+"location_notes.relays_retry_hint" = "le note dipendono dai relay geo. controlla la connessione e riprova.";
+"mesh_peers.tooltip.new_messages" = "nuovi messaggi";
+"system.chat.blocked" = "impossibile avviare una chat con %@: utente bloccato.";
+"system.chat.requires_favorite" = "impossibile avviare una chat con %@: servono preferiti reciproci per l'offline.";
+"system.common.user" = "utente";
+"system.dm.blocked_generic" = "invio non riuscito: utente bloccato.";
+"system.dm.blocked_recipient" = "impossibile inviare a %@: utente bloccato.";
+"system.dm.unreachable" = "impossibile inviare a %@: destinatario irraggiungibile via mesh o nostr.";
+"system.geohash.blocked" = "%@ è stato bloccato nei chat geohash";
+"system.geohash.unblocked" = "%@ è stato sbloccato nei chat geohash";
+"system.location.not_in_channel" = "invio fallito: non sei in un canale posizione";
+"system.location.send_failed" = "impossibile inviare al canale posizione";
+"system.tor.dev_bypass" = "build di sviluppo: bypass tor attivo.";
+"system.tor.restarted" = "tor è stato riavviato. instradamento ripristinato.";
+"system.tor.restarting" = "tor si sta riavviando per ripristinare la connettività...";
+"system.tor.started" = "tor è avviato. tutta la chat passa da tor per la privacy.";
+"system.tor.starting" = "avvio tor...";
+"verification.my_qr.accessibility_label" = "codice qr di verifica";
+"verification.my_qr.title" = "scansiona per verificarmi";
+"verification.my_qr.unavailable" = "qr non disponibile";
+"verification.scan.paste_prompt" = "incolla il contenuto del qr per convalidare:";
+"verification.scan.prompt_friend" = "scansiona il qr di un amico";
+"verification.scan.status.invalid" = "qr non valido o scaduto";
+"verification.scan.status.no_peer" = "nessun peer corrispondente trovato";
+"verification.scan.status.requested" = "verifica richiesta per %@";
+"verification.scan.validate" = "convalida";
+"verification.sheet.title" = "VERIFICA";
diff --git a/bitchat/Localization/it.lproj/Localizable.stringsdict b/bitchat/Localization/it.lproj/Localizable.stringsdict
new file mode 100644
index 00000000..4382c27e
--- /dev/null
+++ b/bitchat/Localization/it.lproj/Localizable.stringsdict
@@ -0,0 +1,54 @@
+
+
+
+
+ location_notes.header
+
+ NSStringLocalizedFormatKey
+ #%@ • %#@note_count@
+ note_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ d
+ one
+ %d nota
+ other
+ %d note
+
+
+ location_channels.row_title
+
+ NSStringLocalizedFormatKey
+ %@ [%#@people_count@]
+ people_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ d
+ one
+ %d persona
+ other
+ %d persone
+
+
+ content.accessibility.people_count
+
+ NSStringLocalizedFormatKey
+ %#@people@
+ people
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ d
+ one
+ %d persona
+ other
+ %d persone
+
+
+
+
diff --git a/bitchat/Localization/ja.lproj/Localizable.strings b/bitchat/Localization/ja.lproj/Localizable.strings
new file mode 100644
index 00000000..d7dd8275
--- /dev/null
+++ b/bitchat/Localization/ja.lproj/Localizable.strings
@@ -0,0 +1,190 @@
+/*
+ Localizable.strings
+ bitchat (Japanese)
+*/
+
+"app_info.app_name" = "bitchat";
+"app_info.close" = "閉じる";
+"app_info.done" = "完了";
+"app_info.features.encryption.description" = "プライベートメッセージはnoiseプロトコルで暗号化されます";
+"app_info.features.encryption.title" = "エンドツーエンド暗号";
+"app_info.features.extended_range.description" = "メッセージはピア間でリレーされより遠くに届きます";
+"app_info.features.extended_range.title" = "拡張レンジ";
+"app_info.features.favorites.description" = "お気に入りの人が参加したら通知を受け取れます";
+"app_info.features.favorites.title" = "お気に入り";
+"app_info.features.geohash.description" = "geohashチャンネルで近くの人と匿名分散リレー越しにチャット";
+"app_info.features.geohash.title" = "ローカルチャンネル";
+"app_info.features.mentions.description" = "@nicknameで特定の人に通知";
+"app_info.features.mentions.title" = "メンション";
+"app_info.features.offline.description" = "bluetooth low energyでインターネットなしでも動作";
+"app_info.features.offline.title" = "オフライン通信";
+"app_info.features.title" = "機能";
+"app_info.how_to_use.change_channels" = "• #meshをタップしてチャンネルを切り替え";
+"app_info.how_to_use.clear_chat" = "• チャットを3回タップするとクリア";
+"app_info.how_to_use.commands" = "• /を入力してコマンド表示";
+"app_info.how_to_use.open_sidebar" = "• 人アイコンをタップしてサイドバーを開く";
+"app_info.how_to_use.set_nickname" = "• ニックネームをタップして設定";
+"app_info.how_to_use.start_dm" = "• ピアの名前をタップしてdm開始";
+"app_info.how_to_use.title" = "使い方";
+"app_info.privacy.ephemeral.description" = "新しいpeer idが定期的に生成されます";
+"app_info.privacy.ephemeral.title" = "一時的なアイデンティティ";
+"app_info.privacy.no_tracking.description" = "サーバーもアカウントもデータ収集もなし";
+"app_info.privacy.no_tracking.title" = "追跡なし";
+"app_info.privacy.panic.description" = "ロゴを3回タップすると全データを即削除";
+"app_info.privacy.panic.title" = "パニックモード";
+"app_info.privacy.title" = "プライバシー";
+"app_info.tagline" = "sidegroupchat";
+"app_info.warning.message" = "プライベートメッセージの安全性はまだ完全に監査されていません。この警告が消えるまで重要な場面では使わないでください。";
+"app_info.warning.title" = "警告";
+"common.cancel" = "キャンセル";
+"common.close" = "閉じる";
+"common.copy" = "コピー";
+"common.ok" = "OK";
+"common.toggle.off" = "オフ";
+"common.toggle.on" = "オン";
+"common.unknown" = "不明";
+"content.accessibility.add_favorite" = "お気に入りに追加";
+"content.accessibility.available_nostr" = "nostrで利用可能";
+"content.accessibility.back_to_main_chat" = "メインチャットに戻る";
+"content.accessibility.connected_mesh" = "mesh経由で接続";
+"content.accessibility.encryption_status" = "暗号状態: %@";
+"content.accessibility.location_channels" = "ロケーションチャンネル";
+"content.accessibility.location_notes" = "この場所のロケーションノート";
+"content.accessibility.open_unread_private_chat" = "未読のプライベートチャットを開く";
+"content.accessibility.private_chat_header" = "%@とのプライベートチャット";
+"content.accessibility.reachable_mesh" = "meshで到達可能";
+"content.accessibility.remove_favorite" = "お気に入りから削除";
+"content.accessibility.send_hint_empty" = "送信するメッセージを入力";
+"content.accessibility.send_hint_ready" = "ダブルタップで送信";
+"content.accessibility.send_message" = "メッセージ送信";
+"content.accessibility.toggle_bookmark" = "#%@のブックマークを切り替え";
+"content.accessibility.toggle_favorite_hint" = "ダブルタップでお気に入り状態を切り替え";
+"content.accessibility.view_fingerprint_hint" = "暗号フィンガープリントを見る";
+"content.actions.block" = "ブロック";
+"content.actions.direct_message" = "ダイレクトメッセージ";
+"content.actions.hug" = "ハグ";
+"content.actions.mention" = "メンション";
+"content.actions.slap" = "ビンタ";
+"content.actions.title" = "アクション";
+"content.alert.bluetooth_required.off" = "bluetoothがオフです。設定でbluetoothをオンにしてbitchatを使ってください。";
+"content.alert.bluetooth_required.permission" = "bitchatは近くのデバイスと接続するためbluetooth権限が必要です。設定でアクセスを有効にしてください。";
+"content.alert.bluetooth_required.settings" = "設定";
+"content.alert.bluetooth_required.title" = "bluetoothが必要";
+"content.alert.bluetooth_required.unsupported" = "このデバイスはbluetoothをサポートしていません。bitchatにはbluetoothが必要です。";
+"content.alert.screenshot.message" = "ロケーションチャンネルのスクリーンショットはあなたの場所を明かします。公開前によく考えてください。";
+"content.alert.screenshot.title" = "注意";
+"content.commands.block" = "ブロックまたはブロック済みを表示";
+"content.commands.clear" = "チャットをクリア";
+"content.commands.favorite" = "お気に入りに追加";
+"content.commands.hug" = "あたたかいハグを送る";
+"content.commands.message" = "プライベートメッセージを送る";
+"content.commands.slap" = "誰かをトラウトでたたく";
+"content.commands.unblock" = "ピアのブロックを解除";
+"content.commands.unfavorite" = "お気に入りから外す";
+"content.commands.who" = "オンラインの人を見る";
+"content.delivery.delivered_members" = "%2$d人中%1$d人に配信";
+"content.delivery.delivered_to" = "%@に配信";
+"content.delivery.failed" = "失敗: %@";
+"content.delivery.read_by" = "%@が既読";
+"content.delivery.reason.blocked" = "ユーザーをブロック中";
+"content.delivery.reason.self" = "自分には送れません";
+"content.delivery.reason.send_error" = "送信エラー";
+"content.delivery.reason.unknown_recipient" = "不明な宛先";
+"content.delivery.reason.unreachable" = "ピアに到達できません";
+"content.header.people" = "ユーザー";
+"content.help.verification" = "検証: 自分のqrを表示するか友達をスキャン";
+"content.input.message_placeholder" = "メッセージを入力...";
+"content.input.nickname_placeholder" = "ニックネーム";
+"content.location.enable" = "位置情報を有効化";
+"content.message.copy" = "メッセージをコピー";
+"content.message.show_less" = "表示を減らす";
+"content.message.show_more" = "さらに表示";
+"content.notes.location_unavailable" = "位置情報を取得できません";
+"content.notes.title" = "ノート";
+"content.payment.cashu" = "cashuで支払う";
+"content.payment.lightning" = "lightningで支払う";
+"encryption.accessibility.establishing" = "暗号を確立しています";
+"encryption.accessibility.failed" = "暗号に失敗";
+"encryption.accessibility.not_encrypted" = "未暗号";
+"encryption.accessibility.secured" = "暗号化済み";
+"encryption.accessibility.verified" = "暗号化し検証済み";
+"encryption.status.establishing" = "暗号を確立中...";
+"encryption.status.failed" = "暗号に失敗";
+"encryption.status.not_encrypted" = "未暗号";
+"encryption.status.secured" = "暗号化済み";
+"encryption.status.verified" = "暗号化し検証済み";
+"fingerprint.action.mark_verified" = "検証済みにする";
+"fingerprint.action.remove_verification" = "検証を削除";
+"fingerprint.badge.not_verified" = "⚠️ 未検証";
+"fingerprint.badge.verified" = "✓ 検証済み";
+"fingerprint.handshake_pending" = "利用不可 - handshake進行中";
+"fingerprint.message.verified" = "この人の身元を確認しました。";
+"fingerprint.message.verify_hint" = "これらのフィンガープリントを%@と安全なチャネルで比較";
+"fingerprint.their_label" = "相手のフィンガープリント:";
+"fingerprint.title" = "セキュリティ検証";
+"fingerprint.your_label" = "あなたのフィンガープリント:";
+"geohash_people.action.block" = "ブロック";
+"geohash_people.action.unblock" = "ブロック解除";
+"geohash_people.none_nearby" = "近くに誰もいません...";
+"geohash_people.tooltip.blocked" = "geohashでブロック中";
+"geohash_people.you_suffix" = " (あなた)";
+"location_channels.action.open_settings" = "設定を開く";
+"location_channels.action.remove_access" = "位置アクセスを解除";
+"location_channels.action.request_permissions" = "位置情報とgeohashを取得";
+"location_channels.action.teleport" = "テレポート";
+"location_channels.bookmarked_section_title" = "保存済み";
+"location_channels.description" = "geohashチャンネルで近くの人と会話。共有されるのはざっくりしたgeohashだけで正確なgpsは含みません。全トラフィックをtor経由にすることであなたのipを隠します。";
+"location_channels.error.invalid_geohash" = "無効なgeohash";
+"location_channels.loading_nearby" = "近くのチャンネルを検索中…";
+"location_channels.mesh_label" = "mesh";
+"location_channels.permission_denied" = "位置情報の許可が拒否されました。チャンネルを使うには設定で許可してください。";
+"location_channels.subtitle_prefix" = "#%@ • %@";
+"location_channels.subtitle_with_name" = "%1$@ • %2$@";
+"location_channels.title" = "#ロケーションチャンネル";
+"location_channels.tor.subtitle" = "ロケーションチャンネル用にipを隠します。推奨: オン";
+"location_channels.tor.title" = "torルーティング";
+"location_levels.block" = "ブロック";
+"location_levels.building" = "建物";
+"location_levels.city" = "都市";
+"location_levels.neighborhood" = "近所";
+"location_levels.province" = "州";
+"location_levels.region" = "地域";
+"location_notes.action.dismiss" = "閉じる";
+"location_notes.action.retry" = "再試行";
+"location_notes.description" = "他の人が見つけられるようこの場所に短いノートを追加";
+"location_notes.empty_subtitle" = "ここで最初のノートを残そう。";
+"location_notes.empty_title" = "ノートはまだありません";
+"location_notes.error.failed_to_send" = "ノートを送信できませんでした。%@";
+"location_notes.error.no_relays" = "近くに利用できるジオリレーがありません。後で再試行してください。";
+"location_notes.loading_notes" = "ノートを読み込み中…";
+"location_notes.loading_recent" = "最新ノートを読み込み中…";
+"location_notes.no_relays_nearby" = "近くにジオリレーなし";
+"location_notes.placeholder" = "この場所のノートを追加";
+"location_notes.relays_paused" = "ジオリレーが利用不可: ノート一時停止";
+"location_notes.relays_retry_hint" = "ノートはジオリレーに依存します。接続を確認して再試行してください。";
+"mesh_peers.tooltip.new_messages" = "新しいメッセージ";
+"system.chat.blocked" = "%@とはチャットできません: ユーザーをブロック中。";
+"system.chat.requires_favorite" = "%@とはチャットできません: オフラインには相互のお気に入りが必要です。";
+"system.common.user" = "ユーザー";
+"system.dm.blocked_generic" = "送信できません: ユーザーをブロック中。";
+"system.dm.blocked_recipient" = "%@に送れません: ユーザーをブロック中。";
+"system.dm.unreachable" = "%@に送れません: 受信者はmeshやnostrで到達できません。";
+"system.geohash.blocked" = "%@をgeohashチャットでブロックしました";
+"system.geohash.unblocked" = "%@のgeohashチャットでのブロックを解除しました";
+"system.location.not_in_channel" = "送信失敗: ロケーションチャンネルに参加していません";
+"system.location.send_failed" = "ロケーションチャンネルに送信できませんでした";
+"system.tor.dev_bypass" = "開発ビルド: torバイパスが有効です。";
+"system.tor.restarted" = "torを再起動しました。ルーティングを復旧。";
+"system.tor.restarting" = "接続回復のためtorを再起動しています...";
+"system.tor.started" = "torを起動しました。全チャットをtor経由で配信します。";
+"system.tor.starting" = "torを起動中...";
+"verification.my_qr.accessibility_label" = "検証用qrコード";
+"verification.my_qr.title" = "スキャンして確認";
+"verification.my_qr.unavailable" = "qrは利用不可";
+"verification.scan.paste_prompt" = "確認するqr内容を貼り付け:";
+"verification.scan.prompt_friend" = "友達のqrをスキャン";
+"verification.scan.status.invalid" = "qrが無効または期限切れ";
+"verification.scan.status.no_peer" = "該当するピアが見つかりません";
+"verification.scan.status.requested" = "%@の検証をリクエストしました";
+"verification.scan.validate" = "確認";
+"verification.sheet.title" = "確認";
diff --git a/bitchat/Localization/ja.lproj/Localizable.stringsdict b/bitchat/Localization/ja.lproj/Localizable.stringsdict
new file mode 100644
index 00000000..fc85efdf
--- /dev/null
+++ b/bitchat/Localization/ja.lproj/Localizable.stringsdict
@@ -0,0 +1,54 @@
+
+
+
+
+ location_notes.header
+
+ NSStringLocalizedFormatKey
+ #%@ • %#@note_count@
+ note_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ d
+ one
+ %d件のノート
+ other
+ %d件のノート
+
+
+ location_channels.row_title
+
+ NSStringLocalizedFormatKey
+ %@ [%#@people_count@]
+ people_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ d
+ one
+ %d人
+ other
+ %d人
+
+
+ content.accessibility.people_count
+
+ NSStringLocalizedFormatKey
+ %#@people@
+ people
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ d
+ one
+ %d人
+ other
+ %d人
+
+
+
+
diff --git a/bitchat/Localization/ne.lproj/Localizable.strings b/bitchat/Localization/ne.lproj/Localizable.strings
new file mode 100644
index 00000000..0ee5a1d2
--- /dev/null
+++ b/bitchat/Localization/ne.lproj/Localizable.strings
@@ -0,0 +1,190 @@
+/*
+ Localizable.strings
+ bitchat (Nepali)
+*/
+
+"app_info.app_name" = "bitchat";
+"app_info.close" = "बन्द";
+"app_info.done" = "सम्पन्न";
+"app_info.features.encryption.description" = "व्यक्तिगत सन्देशहरू noise प्रोटोकलले सङ्केत गर्छ";
+"app_info.features.encryption.title" = "एन्ड-टु-एन्ड सङ्केत";
+"app_info.features.extended_range.description" = "सन्देशहरू सहकर्मीमार्फत रिले भएर टाढासम्म पुग्छन्";
+"app_info.features.extended_range.title" = "विस्तारित पहुँच";
+"app_info.features.favorites.description" = "तिम्रा मनपर्ने मानिस जोडिएपछि सूचनाहरू पाऊ";
+"app_info.features.favorites.title" = "मनपर्ने";
+"app_info.features.geohash.description" = "geohash च्यानलहरूले नजिकका व्यक्तिसँग विकेन्द्रित गोप्य रिलेबाट कुराकानी गर्न मद्दत गर्छ";
+"app_info.features.geohash.title" = "स्थानीय च्यानल";
+"app_info.features.mentions.description" = "विशेष व्यक्तिलाई सूचित गर्न @nickname प्रयोग गर";
+"app_info.features.mentions.title" = "उल्लेख";
+"app_info.features.offline.description" = "bluetooth low energy प्रयोग गरेर इन्टरनेट बिना काम गर्छ";
+"app_info.features.offline.title" = "अफलाइन सञ्चार";
+"app_info.features.title" = "विशेषता";
+"app_info.how_to_use.change_channels" = "• च्यानल बदल्न #mesh ट्याप गर";
+"app_info.how_to_use.clear_chat" = "• च्याट खाली गर्न तीन पटक ट्याप गर";
+"app_info.how_to_use.commands" = "• आदेशहरू हेर्न / टाइप गर";
+"app_info.how_to_use.open_sidebar" = "• साइडबार खोल्न मान्छे आइकन ट्याप गर";
+"app_info.how_to_use.set_nickname" = "• आफ्नो उपनाममा ट्याप गरेर मिलाऊ";
+"app_info.how_to_use.start_dm" = "• dm सुरु गर्न कुनै सहकर्मीको नाम ट्याप गर";
+"app_info.how_to_use.title" = "प्रयोग गर्ने तरिका";
+"app_info.privacy.ephemeral.description" = "नयाँ peer id नियमित रूपमा सिर्जना हुन्छ";
+"app_info.privacy.ephemeral.title" = "क्षणिक पहिचान";
+"app_info.privacy.no_tracking.description" = "सर्भर, खाताहरू वा तथ्याङ्क सङ्कलन छैन";
+"app_info.privacy.no_tracking.title" = "ट्र्याकिङ छैन";
+"app_info.privacy.panic.description" = "लगो तीन पटक ट्याप गर्दा सबै डाटा तुरुन्त मेटिन्छ";
+"app_info.privacy.panic.title" = "घबराहट मोड";
+"app_info.privacy.title" = "गोपनीयता";
+"app_info.tagline" = "sidegroupchat";
+"app_info.warning.message" = "व्यक्तिगत सन्देशको सुरक्षा पूर्ण रूपमा अडिट भएको छैन। यो चेतावनी हट्दासम्म गम्भीर अवस्थामा प्रयोग नगर्नु।";
+"app_info.warning.title" = "चेतावनी";
+"common.cancel" = "रद्द";
+"common.close" = "बन्द";
+"common.copy" = "प्रतिलिपि";
+"common.ok" = "ठिक";
+"common.toggle.off" = "अफ";
+"common.toggle.on" = "अन";
+"common.unknown" = "अज्ञात";
+"content.accessibility.add_favorite" = "मनपर्नेमा थप";
+"content.accessibility.available_nostr" = "nostr मार्फत उपलब्ध";
+"content.accessibility.back_to_main_chat" = "मुख्य च्याटमा फर्क";
+"content.accessibility.connected_mesh" = "mesh मार्फत जडान";
+"content.accessibility.encryption_status" = "सङ्केतको अवस्था: %@";
+"content.accessibility.location_channels" = "स्थान च्यानल";
+"content.accessibility.location_notes" = "यस ठाउँका स्थान नोटहरू";
+"content.accessibility.open_unread_private_chat" = "नपढिएको निजी च्याट खोल";
+"content.accessibility.private_chat_header" = "%@ सँग निजी च्याट";
+"content.accessibility.reachable_mesh" = "mesh मार्फत पहुँचयोग्य";
+"content.accessibility.remove_favorite" = "मनपर्नेबाट हटाउ";
+"content.accessibility.send_hint_empty" = "पठाउन सन्देश लेख";
+"content.accessibility.send_hint_ready" = "पठाउन दोहोरो ट्याप गर";
+"content.accessibility.send_message" = "सन्देश पठाउ";
+"content.accessibility.toggle_bookmark" = "#%@ का लागि बुकमार्क बदल";
+"content.accessibility.toggle_favorite_hint" = "मनपर्ने स्थिति बदल्न दोहोरो ट्याप गर";
+"content.accessibility.view_fingerprint_hint" = "सङ्केत फिङ्गरप्रिन्ट हेर्न ट्याप गर";
+"content.actions.block" = "ब्लक";
+"content.actions.direct_message" = "प्रत्यक्ष सन्देश";
+"content.actions.hug" = "अँगालो";
+"content.actions.mention" = "उल्लेख";
+"content.actions.slap" = "थप्पड";
+"content.actions.title" = "कार्य";
+"content.alert.bluetooth_required.off" = "bluetooth बन्द छ। bitchat प्रयोग गर्न bluetooth सेटिङमा अन गर।";
+"content.alert.bluetooth_required.permission" = "bitchat लाई नजिकका उपकरणसँग जडान हुन bluetooth अनुमति चाहिन्छ। सेटिङमा पहुँच सक्षम गर।";
+"content.alert.bluetooth_required.settings" = "सेटिङ";
+"content.alert.bluetooth_required.title" = "bluetooth आवश्यक";
+"content.alert.bluetooth_required.unsupported" = "यो उपकरणले bluetooth समर्थन गर्दैन। bitchat चलाउन bluetooth चाहिन्छ।";
+"content.alert.screenshot.message" = "स्थान च्यानलको स्क्रिनसटले तिम्रो स्थान खुलाउँछ। सार्वजनिकरूपमा बाँड्नु अघि सोच।";
+"content.alert.screenshot.title" = "ध्यान";
+"content.commands.block" = "ब्लक गर वा ब्लक गरिएको सूची देखाउ";
+"content.commands.clear" = "च्याट सन्देश खाली गर";
+"content.commands.favorite" = "मनपर्नेमा थप";
+"content.commands.hug" = "न्यानो अँगालो पठाउ";
+"content.commands.message" = "निजी सन्देश पठाउ";
+"content.commands.slap" = "कसैलाई ट्राउटले थप्पड दे";
+"content.commands.unblock" = "पीयर अनब्लक गर";
+"content.commands.unfavorite" = "मनपर्नेबाट हटाउ";
+"content.commands.who" = "अनलाइन को-को छन् हेर्नु";
+"content.delivery.delivered_members" = "%2$d सदस्यमध्ये %1$d जनालाई पुर्याइयो";
+"content.delivery.delivered_to" = "%@ लाई पुर्याइयो";
+"content.delivery.failed" = "असफल: %@";
+"content.delivery.read_by" = "%@ ले पढ्यो";
+"content.delivery.reason.blocked" = "प्रयोगकर्ता ब्लक गरिएको";
+"content.delivery.reason.self" = "आफूलाई पठाउन मिल्दैन";
+"content.delivery.reason.send_error" = "पठाउने त्रुटि";
+"content.delivery.reason.unknown_recipient" = "अज्ञात प्राप्तकर्ता";
+"content.delivery.reason.unreachable" = "पीयर पहुँचयोग्य छैन";
+"content.header.people" = "मानिस";
+"content.help.verification" = "प्रमाणीकरण: मेरो qr देखाउ वा साथी स्क्यान गर";
+"content.input.message_placeholder" = "सन्देश टाइप गर...";
+"content.input.nickname_placeholder" = "उपनाम";
+"content.location.enable" = "स्थान सक्षम गर";
+"content.message.copy" = "सन्देश प्रतिलिपि गर";
+"content.message.show_less" = "थोरै देखाउ";
+"content.message.show_more" = "थप देखाउ";
+"content.notes.location_unavailable" = "स्थान उपलब्ध छैन";
+"content.notes.title" = "नोट";
+"content.payment.cashu" = "cashu मार्फत तिर्नु";
+"content.payment.lightning" = "lightning मार्फत तिर्नु";
+"encryption.accessibility.establishing" = "सङ्केत सेट हुँदै";
+"encryption.accessibility.failed" = "सङ्केत असफल";
+"encryption.accessibility.not_encrypted" = "सङ्केत छैन";
+"encryption.accessibility.secured" = "सङ्केत गरिएको";
+"encryption.accessibility.verified" = "सङ्केत र प्रमाणित";
+"encryption.status.establishing" = "सङ्केत सेट गर्दै...";
+"encryption.status.failed" = "सङ्केत असफल";
+"encryption.status.not_encrypted" = "सङ्केत छैन";
+"encryption.status.secured" = "सङ्केत गरिएको";
+"encryption.status.verified" = "सङ्केत र प्रमाणित";
+"fingerprint.action.mark_verified" = "प्रमाणित चिन्ह लगाउ";
+"fingerprint.action.remove_verification" = "प्रमाणीकरण हटाउ";
+"fingerprint.badge.not_verified" = "⚠️ प्रमाणित छैन";
+"fingerprint.badge.verified" = "✓ प्रमाणित";
+"fingerprint.handshake_pending" = "उपलब्ध छैन - handshake हुँदै";
+"fingerprint.message.verified" = "तिमीले यस व्यक्तिको पहिचान प्रमाणित गरेको छौ.";
+"fingerprint.message.verify_hint" = "यी फिङ्गरप्रिन्टहरू %@ सँग सुरक्षित च्यानलमा तुलना गर।";
+"fingerprint.their_label" = "उनको फिङ्गरप्रिन्ट:";
+"fingerprint.title" = "सुरक्षा प्रमाणीकरण";
+"fingerprint.your_label" = "तिम्रो फिङ्गरप्रिन्ट:";
+"geohash_people.action.block" = "ब्लक";
+"geohash_people.action.unblock" = "अनब्लक";
+"geohash_people.none_nearby" = "वरिपरि कोही छैन...";
+"geohash_people.tooltip.blocked" = "geohash मा ब्लक";
+"geohash_people.you_suffix" = " (तिमी)";
+"location_channels.action.open_settings" = "सेटिङ खोल";
+"location_channels.action.remove_access" = "स्थान पहुँच हटाउ";
+"location_channels.action.request_permissions" = "मेरो स्थान र geohash प्राप्त गर";
+"location_channels.action.teleport" = "टेलिपोर्ट";
+"location_channels.bookmarked_section_title" = "बुकमार्क";
+"location_channels.description" = "geohash च्यानलबाट नजिकका मानिससँग कुरा गर। केवल मोटामो geohash साझा हुन्छ, कहिल्यै सहि gps होइन। सबै ट्राफिक tor मार्फत गएका कारण तिम्रो ip लुकेको हुन्छ।";
+"location_channels.error.invalid_geohash" = "अवैध geohash";
+"location_channels.loading_nearby" = "नजिकका च्यानल खोज्दै…";
+"location_channels.mesh_label" = "mesh";
+"location_channels.permission_denied" = "स्थान अनुमति अस्वीकार। स्थान च्यानल प्रयोग गर्न सेटिङमा सक्षम गर।";
+"location_channels.subtitle_prefix" = "#%@ • %@";
+"location_channels.subtitle_with_name" = "%1$@ • %2$@";
+"location_channels.title" = "#स्थान च्यानल";
+"location_channels.tor.subtitle" = "स्थान च्यानलका लागि तिम्रो ip लुकाउँछ। सिफारिस: अन।";
+"location_channels.tor.title" = "tor रूटिङ";
+"location_levels.block" = "ब्लक";
+"location_levels.building" = "भवन";
+"location_levels.city" = "सहर";
+"location_levels.neighborhood" = "छिमेक";
+"location_levels.province" = "प्रदेश";
+"location_levels.region" = "क्षेत्र";
+"location_notes.action.dismiss" = "बन्द गर";
+"location_notes.action.retry" = "फेरि प्रयास गर";
+"location_notes.description" = "अन्यले भेटून् भनी यस स्थानमा छोटो स्थायी नोट थप।";
+"location_notes.empty_subtitle" = "यस ठाउँमा नोट थप्ने पहिलो व्यक्ती बन।";
+"location_notes.empty_title" = "अहिले नोट छैन";
+"location_notes.error.failed_to_send" = "नोट पठाउन सकेन। %@";
+"location_notes.error.no_relays" = "यस स्थान नजिक georelay उपलब्ध छैन। केही बेरपछि प्रयास गर।";
+"location_notes.loading_notes" = "नोट लोड हुँदै…";
+"location_notes.loading_recent" = "हालैका नोट लोड गर्दै…";
+"location_notes.no_relays_nearby" = "नजिक georelay छैन";
+"location_notes.placeholder" = "यस स्थानका लागि नोट थप";
+"location_notes.relays_paused" = "georelay उपलब्ध छैन; नोट रोकिएको";
+"location_notes.relays_retry_hint" = "नोट georelay मा निर्भर छन्। जडान जाँच गरेर फेरि प्रयास गर.";
+"mesh_peers.tooltip.new_messages" = "नयाँ सन्देश";
+"system.chat.blocked" = "%@ सँग च्याट सुरु गर्न मिलेन: प्रयोगकर्ता ब्लक गरिएको";
+"system.chat.requires_favorite" = "%@ सँग च्याट सुरु गर्न मिलेन: अफलाइनका लागि दुवै मनपर्ने हुनुपर्छ";
+"system.common.user" = "प्रयोगकर्ता";
+"system.dm.blocked_generic" = "पठाउन मिलेन: प्रयोगकर्ता ब्लक";
+"system.dm.blocked_recipient" = "%@ लाई पठाउन मिलेन: प्रयोगकर्ता ब्लक";
+"system.dm.unreachable" = "%@ लाई पठाउन मिलेन: प्राप्तकर्ता mesh वा nostr बाट उपलब्ध छैन";
+"system.geohash.blocked" = "%@ लाई geohash च्याटमा ब्लक गरियो";
+"system.geohash.unblocked" = "%@ लाई geohash च्याटमा अनब्लक गरियो";
+"system.location.not_in_channel" = "पठाउन मिलेन: तिमी स्थान च्यानलमा छैनौ";
+"system.location.send_failed" = "स्थान च्यानलमा पठाउन सकेन";
+"system.tor.dev_bypass" = "डेभ बिल्ड: tor बाइपास सक्षम।";
+"system.tor.restarted" = "tor फेरि सुरु भयो। रूटिङ पुनःस्थापित।";
+"system.tor.restarting" = "tor जडान फर्काउन पुनः सुरु हुँदैछ...";
+"system.tor.started" = "tor सुरु भयो। गोपनीयताका लागि पूरा च्याट tor मार्फत जान्छ।";
+"system.tor.starting" = "tor सुरु हुँदै...";
+"verification.my_qr.accessibility_label" = "प्रमाणीकरण qr कोड";
+"verification.my_qr.title" = "मलाई प्रमाणित गर्न स्क्यान गर";
+"verification.my_qr.unavailable" = "qr उपलब्ध छैन";
+"verification.scan.paste_prompt" = "प्रमाणित गर्न qr सामग्री पेस्ट गर:";
+"verification.scan.prompt_friend" = "साथीको qr स्क्यान गर";
+"verification.scan.status.invalid" = "qr अवैध या म्याद सकिएको";
+"verification.scan.status.no_peer" = "मिल्ने peer फेला परेन";
+"verification.scan.status.requested" = "%@ को लागि प्रमाणीकरण अनुरोध भयो";
+"verification.scan.validate" = "प्रमाणित गर";
+"verification.sheet.title" = "प्रमाणित";
diff --git a/bitchat/Localization/ne.lproj/Localizable.stringsdict b/bitchat/Localization/ne.lproj/Localizable.stringsdict
new file mode 100644
index 00000000..4bb118b4
--- /dev/null
+++ b/bitchat/Localization/ne.lproj/Localizable.stringsdict
@@ -0,0 +1,54 @@
+
+
+
+
+ location_notes.header
+
+ NSStringLocalizedFormatKey
+ #%@ • %#@note_count@
+ note_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ d
+ one
+ %d नोट
+ other
+ %d नोटहरू
+
+
+ location_channels.row_title
+
+ NSStringLocalizedFormatKey
+ %@ [%#@people_count@]
+ people_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ d
+ one
+ %d व्यक्ति
+ other
+ %d व्यक्तिहरू
+
+
+ content.accessibility.people_count
+
+ NSStringLocalizedFormatKey
+ %#@people@
+ people
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ d
+ one
+ %d व्यक्ति
+ other
+ %d व्यक्तिहरू
+
+
+
+
diff --git a/bitchat/Localization/pt-BR.lproj/Localizable.strings b/bitchat/Localization/pt-BR.lproj/Localizable.strings
new file mode 100644
index 00000000..45ddacf3
--- /dev/null
+++ b/bitchat/Localization/pt-BR.lproj/Localizable.strings
@@ -0,0 +1,190 @@
+/*
+ Localizable.strings
+ bitchat (Portuguese - Brazil)
+*/
+
+"app_info.app_name" = "bitchat";
+"app_info.close" = "fechar";
+"app_info.done" = "CONCLUÍDO";
+"app_info.features.encryption.description" = "mensagens privadas criptografadas com o protocolo noise";
+"app_info.features.encryption.title" = "criptografia ponto a ponto";
+"app_info.features.extended_range.description" = "mensagens retransmitidas entre pares para alcançar mais longe";
+"app_info.features.extended_range.title" = "alcance estendido";
+"app_info.features.favorites.description" = "receba avisos quando suas pessoas favoritas entrarem";
+"app_info.features.favorites.title" = "favoritos";
+"app_info.features.geohash.description" = "canais geohash para conversar com pessoas em regiões próximas por relays descentralizados anônimos";
+"app_info.features.geohash.title" = "canais locais";
+"app_info.features.mentions.description" = "use @nickname para notificar pessoas específicas";
+"app_info.features.mentions.title" = "menções";
+"app_info.features.offline.description" = "funciona sem internet usando bluetooth de baixa energia";
+"app_info.features.offline.title" = "comunicação offline";
+"app_info.features.title" = "RECURSOS";
+"app_info.how_to_use.change_channels" = "• toque #mesh para trocar de canal";
+"app_info.how_to_use.clear_chat" = "• toque o chat três vezes para limpar";
+"app_info.how_to_use.commands" = "• digite / para ver comandos";
+"app_info.how_to_use.open_sidebar" = "• toque o ícone de pessoas para abrir a barra lateral";
+"app_info.how_to_use.set_nickname" = "• defina seu apelido tocando nele";
+"app_info.how_to_use.start_dm" = "• toque o nome de um par para iniciar um dm";
+"app_info.how_to_use.title" = "COMO USAR";
+"app_info.privacy.ephemeral.description" = "novo id de peer gerado regularmente";
+"app_info.privacy.ephemeral.title" = "identidade efêmera";
+"app_info.privacy.no_tracking.description" = "sem servidores, contas ou coleta de dados";
+"app_info.privacy.no_tracking.title" = "sem rastreamento";
+"app_info.privacy.panic.description" = "toque o logo três vezes para limpar todos os dados instantaneamente";
+"app_info.privacy.panic.title" = "modo pânico";
+"app_info.privacy.title" = "PRIVACIDADE";
+"app_info.tagline" = "sidegroupchat";
+"app_info.warning.message" = "a segurança das mensagens privadas ainda não foi totalmente auditada. não use em situações críticas até que este aviso desapareça.";
+"app_info.warning.title" = "AVISO";
+"common.cancel" = "cancelar";
+"common.close" = "fechar";
+"common.copy" = "copiar";
+"common.ok" = "OK";
+"common.toggle.off" = "desligado";
+"common.toggle.on" = "ligado";
+"common.unknown" = "desconhecido";
+"content.accessibility.add_favorite" = "adicionar aos favoritos";
+"content.accessibility.available_nostr" = "disponível via nostr";
+"content.accessibility.back_to_main_chat" = "voltar ao chat principal";
+"content.accessibility.connected_mesh" = "conectado por mesh";
+"content.accessibility.encryption_status" = "status da criptografia: %@";
+"content.accessibility.location_channels" = "canais de localização";
+"content.accessibility.location_notes" = "notas de localização deste lugar";
+"content.accessibility.open_unread_private_chat" = "abrir chat privado não lido";
+"content.accessibility.private_chat_header" = "chat privado com %@";
+"content.accessibility.reachable_mesh" = "alcançável por mesh";
+"content.accessibility.remove_favorite" = "remover dos favoritos";
+"content.accessibility.send_hint_empty" = "digite uma mensagem para enviar";
+"content.accessibility.send_hint_ready" = "toque duas vezes para enviar";
+"content.accessibility.send_message" = "enviar mensagem";
+"content.accessibility.toggle_bookmark" = "alternar favorito para #%@";
+"content.accessibility.toggle_favorite_hint" = "toque duas vezes para alternar status de favorito";
+"content.accessibility.view_fingerprint_hint" = "toque para ver a impressão de criptografia";
+"content.actions.block" = "bloquear";
+"content.actions.direct_message" = "mensagem direta";
+"content.actions.hug" = "abraço";
+"content.actions.mention" = "mencionar";
+"content.actions.slap" = "tapa";
+"content.actions.title" = "ações";
+"content.alert.bluetooth_required.off" = "bluetooth está desligado. ative o bluetooth em ajustes para usar bitchat.";
+"content.alert.bluetooth_required.permission" = "bitchat precisa de permissão de bluetooth para conectar com dispositivos próximos. habilite o acesso em ajustes.";
+"content.alert.bluetooth_required.settings" = "ajustes";
+"content.alert.bluetooth_required.title" = "bluetooth necessário";
+"content.alert.bluetooth_required.unsupported" = "este dispositivo não suporta bluetooth. bitchat precisa de bluetooth para funcionar.";
+"content.alert.screenshot.message" = "capturas de canais de localização revelam sua localização. pense antes de compartilhar publicamente.";
+"content.alert.screenshot.title" = "atenção";
+"content.commands.block" = "bloquear ou listar pares bloqueados";
+"content.commands.clear" = "limpar mensagens do chat";
+"content.commands.favorite" = "adicionar aos favoritos";
+"content.commands.hug" = "enviar um abraço quente";
+"content.commands.message" = "enviar mensagem privada";
+"content.commands.slap" = "dar um tapa em alguém com uma truta";
+"content.commands.unblock" = "desbloquear um par";
+"content.commands.unfavorite" = "remover dos favoritos";
+"content.commands.who" = "ver quem está online";
+"content.delivery.delivered_members" = "entregue a %1$d de %2$d membros";
+"content.delivery.delivered_to" = "entregue para %@";
+"content.delivery.failed" = "falhou: %@";
+"content.delivery.read_by" = "lido por %@";
+"content.delivery.reason.blocked" = "usuário bloqueado";
+"content.delivery.reason.self" = "não é possível enviar mensagem para si mesmo";
+"content.delivery.reason.send_error" = "erro ao enviar";
+"content.delivery.reason.unknown_recipient" = "destinatário desconhecido";
+"content.delivery.reason.unreachable" = "par inalcançável";
+"content.header.people" = "PESSOAS";
+"content.help.verification" = "verificação: mostrar meu qr ou escanear um amigo";
+"content.input.message_placeholder" = "digite uma mensagem...";
+"content.input.nickname_placeholder" = "apelido";
+"content.location.enable" = "habilitar localização";
+"content.message.copy" = "copiar mensagem";
+"content.message.show_less" = "mostrar menos";
+"content.message.show_more" = "mostrar mais";
+"content.notes.location_unavailable" = "localização indisponível";
+"content.notes.title" = "notas";
+"content.payment.cashu" = "pagar via cashu";
+"content.payment.lightning" = "pagar via lightning";
+"encryption.accessibility.establishing" = "estabelecendo criptografia";
+"encryption.accessibility.failed" = "falha na criptografia";
+"encryption.accessibility.not_encrypted" = "não criptografado";
+"encryption.accessibility.secured" = "criptografado";
+"encryption.accessibility.verified" = "criptografado e verificado";
+"encryption.status.establishing" = "estabelecendo criptografia...";
+"encryption.status.failed" = "falha na criptografia";
+"encryption.status.not_encrypted" = "não criptografado";
+"encryption.status.secured" = "criptografado";
+"encryption.status.verified" = "criptografado e verificado";
+"fingerprint.action.mark_verified" = "marcar como verificado";
+"fingerprint.action.remove_verification" = "remover verificação";
+"fingerprint.badge.not_verified" = "⚠️ NÃO VERIFICADO";
+"fingerprint.badge.verified" = "✓ VERIFICADO";
+"fingerprint.handshake_pending" = "indisponível - handshake em andamento";
+"fingerprint.message.verified" = "você verificou a identidade dessa pessoa.";
+"fingerprint.message.verify_hint" = "compare essas impressões com %@ usando um canal seguro.";
+"fingerprint.their_label" = "impressão digital deles:";
+"fingerprint.title" = "verificação de segurança";
+"fingerprint.your_label" = "sua impressão digital:";
+"geohash_people.action.block" = "bloquear";
+"geohash_people.action.unblock" = "desbloquear";
+"geohash_people.none_nearby" = "ninguém por perto...";
+"geohash_people.tooltip.blocked" = "bloqueado em geohash";
+"geohash_people.you_suffix" = " (você)";
+"location_channels.action.open_settings" = "abrir ajustes";
+"location_channels.action.remove_access" = "remover acesso à localização";
+"location_channels.action.request_permissions" = "obter localização e meus geohashes";
+"location_channels.action.teleport" = "teletransportar";
+"location_channels.bookmarked_section_title" = "marcados";
+"location_channels.description" = "converse com pessoas próximas usando canais geohash. apenas um geohash grosseiro é compartilhado, nunca gps exato. seu ip fica oculto ao rotear todo o tráfego por tor.";
+"location_channels.error.invalid_geohash" = "geohash inválido";
+"location_channels.loading_nearby" = "procurando canais próximos…";
+"location_channels.mesh_label" = "mesh";
+"location_channels.permission_denied" = "permissão de localização negada. habilite em ajustes para usar canais de localização.";
+"location_channels.subtitle_prefix" = "#%@ • %@";
+"location_channels.subtitle_with_name" = "%1$@ • %2$@";
+"location_channels.title" = "#canais de localização";
+"location_channels.tor.subtitle" = "oculta seu ip para canais de localização. recomendado: ligado.";
+"location_channels.tor.title" = "roteamento tor";
+"location_levels.block" = "quadra";
+"location_levels.building" = "prédio";
+"location_levels.city" = "cidade";
+"location_levels.neighborhood" = "bairro";
+"location_levels.province" = "estado";
+"location_levels.region" = "região";
+"location_notes.action.dismiss" = "dispensar";
+"location_notes.action.retry" = "tentar novamente";
+"location_notes.description" = "adicione notas curtas permanentes neste local para outras pessoas encontrarem.";
+"location_notes.empty_subtitle" = "seja a primeira pessoa a adicionar uma aqui.";
+"location_notes.empty_title" = "nenhuma nota ainda";
+"location_notes.error.failed_to_send" = "não foi possível enviar a nota. %@";
+"location_notes.error.no_relays" = "nenhum relay geográfico disponível perto deste local. tente novamente em breve.";
+"location_notes.loading_notes" = "carregando notas…";
+"location_notes.loading_recent" = "carregando notas recentes…";
+"location_notes.no_relays_nearby" = "nenhum relay geográfico próximo";
+"location_notes.placeholder" = "adicione uma nota para este lugar";
+"location_notes.relays_paused" = "relays geográficos indisponíveis; notas pausadas";
+"location_notes.relays_retry_hint" = "notas dependem de relays geográficos. verifique a conexão e tente de novo.";
+"mesh_peers.tooltip.new_messages" = "novas mensagens";
+"system.chat.blocked" = "não é possível iniciar chat com %@: usuário bloqueado.";
+"system.chat.requires_favorite" = "não é possível iniciar chat com %@: vocês precisam ser favoritos mútuos para mensagens offline.";
+"system.common.user" = "usuário";
+"system.dm.blocked_generic" = "não foi possível enviar: usuário bloqueado.";
+"system.dm.blocked_recipient" = "não é possível enviar mensagem para %@: usuário bloqueado.";
+"system.dm.unreachable" = "não é possível enviar mensagem para %@: destinatário inalcançável por mesh ou nostr.";
+"system.geohash.blocked" = "%@ foi bloqueado nos chats geohash";
+"system.geohash.unblocked" = "%@ foi desbloqueado nos chats geohash";
+"system.location.not_in_channel" = "não foi possível enviar: você não está em um canal de localização";
+"system.location.send_failed" = "não foi possível enviar para o canal de localização";
+"system.tor.dev_bypass" = "compilação de desenvolvimento: bypass de tor ativo.";
+"system.tor.restarted" = "tor reiniciou. roteamento restaurado.";
+"system.tor.restarting" = "tor está reiniciando para recuperar conectividade...";
+"system.tor.started" = "tor iniciou. todo o chat é roteado por tor para privacidade.";
+"system.tor.starting" = "iniciando tor...";
+"verification.my_qr.accessibility_label" = "código qr de verificação";
+"verification.my_qr.title" = "escaneie para me verificar";
+"verification.my_qr.unavailable" = "qr indisponível";
+"verification.scan.paste_prompt" = "cole o conteúdo do qr para validar:";
+"verification.scan.prompt_friend" = "escaneie o qr de um amigo";
+"verification.scan.status.invalid" = "qr inválido ou expirado";
+"verification.scan.status.no_peer" = "nenhum peer correspondente encontrado";
+"verification.scan.status.requested" = "verificação solicitada para %@";
+"verification.scan.validate" = "validar";
+"verification.sheet.title" = "VERIFICAR";
diff --git a/bitchat/Localization/pt-BR.lproj/Localizable.stringsdict b/bitchat/Localization/pt-BR.lproj/Localizable.stringsdict
new file mode 100644
index 00000000..31b46b16
--- /dev/null
+++ b/bitchat/Localization/pt-BR.lproj/Localizable.stringsdict
@@ -0,0 +1,54 @@
+
+
+
+
+ location_notes.header
+
+ NSStringLocalizedFormatKey
+ #%@ • %#@note_count@
+ note_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ d
+ one
+ %d nota
+ other
+ %d notas
+
+
+ location_channels.row_title
+
+ NSStringLocalizedFormatKey
+ %@ [%#@people_count@]
+ people_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ d
+ one
+ %d pessoa
+ other
+ %d pessoas
+
+
+ content.accessibility.people_count
+
+ NSStringLocalizedFormatKey
+ %#@people@
+ people
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ d
+ one
+ %d pessoa
+ other
+ %d pessoas
+
+
+
+
diff --git a/bitchat/Localization/ru.lproj/Localizable.strings b/bitchat/Localization/ru.lproj/Localizable.strings
new file mode 100644
index 00000000..67edc57c
--- /dev/null
+++ b/bitchat/Localization/ru.lproj/Localizable.strings
@@ -0,0 +1,190 @@
+/*
+ Localizable.strings
+ bitchat (Russian)
+*/
+
+"app_info.app_name" = "bitchat";
+"app_info.close" = "закрыть";
+"app_info.done" = "ГОТОВО";
+"app_info.features.encryption.description" = "личные сообщения шифруются протоколом noise";
+"app_info.features.encryption.title" = "сквозное шифрование";
+"app_info.features.extended_range.description" = "сообщения ретранслируются между пирами и уходят дальше";
+"app_info.features.extended_range.title" = "расширенный радиус";
+"app_info.features.favorites.description" = "получай уведомления, когда подключаются любимые люди";
+"app_info.features.favorites.title" = "избранное";
+"app_info.features.geohash.description" = "каналы geohash для чата с людьми поблизости через децентрализованные анонимные реле";
+"app_info.features.geohash.title" = "локальные каналы";
+"app_info.features.mentions.description" = "используй @nickname, чтобы уведомить конкретных людей";
+"app_info.features.mentions.title" = "упоминания";
+"app_info.features.offline.description" = "работает без интернета через bluetooth low energy";
+"app_info.features.offline.title" = "офлайн-связь";
+"app_info.features.title" = "ВОЗМОЖНОСТИ";
+"app_info.how_to_use.change_channels" = "• нажми #mesh, чтобы сменить канал";
+"app_info.how_to_use.clear_chat" = "• тройной тап по чату очистит его";
+"app_info.how_to_use.commands" = "• введи /, чтобы увидеть команды";
+"app_info.how_to_use.open_sidebar" = "• нажми на иконку людей, чтобы открыть боковое меню";
+"app_info.how_to_use.set_nickname" = "• коснись своего ника, чтобы изменить его";
+"app_info.how_to_use.start_dm" = "• нажми имя пользователя, чтобы начать лс";
+"app_info.how_to_use.title" = "КАК ИСПОЛЬЗОВАТЬ";
+"app_info.privacy.ephemeral.description" = "новый id пира создаётся регулярно";
+"app_info.privacy.ephemeral.title" = "эфемерная личность";
+"app_info.privacy.no_tracking.description" = "без серверов, аккаунтов и сбора данных";
+"app_info.privacy.no_tracking.title" = "без трекинга";
+"app_info.privacy.panic.description" = "тройной тап по логотипу мгновенно очищает все данные";
+"app_info.privacy.panic.title" = "режим паники";
+"app_info.privacy.title" = "КОНФИДЕНЦИАЛЬНОСТЬ";
+"app_info.tagline" = "sidegroupchat";
+"app_info.warning.message" = "безопасность приватных сообщений ещё не прошла полный аудит. не используй для критичных случаев, пока предупреждение не исчезнет.";
+"app_info.warning.title" = "ПРЕДУПРЕЖДЕНИЕ";
+"common.cancel" = "отмена";
+"common.close" = "закрыть";
+"common.copy" = "копировать";
+"common.ok" = "OK";
+"common.toggle.off" = "выкл";
+"common.toggle.on" = "вкл";
+"common.unknown" = "неизвестно";
+"content.accessibility.add_favorite" = "добавить в избранное";
+"content.accessibility.available_nostr" = "доступно через nostr";
+"content.accessibility.back_to_main_chat" = "назад в основной чат";
+"content.accessibility.connected_mesh" = "подключено через mesh";
+"content.accessibility.encryption_status" = "статус шифрования: %@";
+"content.accessibility.location_channels" = "каналы локации";
+"content.accessibility.location_notes" = "заметки для этого места";
+"content.accessibility.open_unread_private_chat" = "открыть непрочитанный приватный чат";
+"content.accessibility.private_chat_header" = "приватный чат с %@";
+"content.accessibility.reachable_mesh" = "достижим через mesh";
+"content.accessibility.remove_favorite" = "убрать из избранного";
+"content.accessibility.send_hint_empty" = "введи сообщение для отправки";
+"content.accessibility.send_hint_ready" = "дважды тапни, чтобы отправить";
+"content.accessibility.send_message" = "отправить сообщение";
+"content.accessibility.toggle_bookmark" = "переключить закладку для #%@";
+"content.accessibility.toggle_favorite_hint" = "дважды тапни, чтобы переключить статус избранного";
+"content.accessibility.view_fingerprint_hint" = "нажми, чтобы увидеть криптографический отпечаток";
+"content.actions.block" = "заблокировать";
+"content.actions.direct_message" = "личное сообщение";
+"content.actions.hug" = "обнять";
+"content.actions.mention" = "упомянуть";
+"content.actions.slap" = "дать леща";
+"content.actions.title" = "действия";
+"content.alert.bluetooth_required.off" = "bluetooth выключен. включи bluetooth в настройках, чтобы использовать bitchat.";
+"content.alert.bluetooth_required.permission" = "bitchat нужен доступ к bluetooth, чтобы соединяться с ближайшими устройствами. включи разрешение в настройках.";
+"content.alert.bluetooth_required.settings" = "настройки";
+"content.alert.bluetooth_required.title" = "bluetooth обязателен";
+"content.alert.bluetooth_required.unsupported" = "это устройство не поддерживает bluetooth. bitchat нужен bluetooth для работы.";
+"content.alert.screenshot.message" = "скриншоты каналов местоположения раскроют твою позицию. подумай, прежде чем делиться публично.";
+"content.alert.screenshot.title" = "внимание";
+"content.commands.block" = "заблокировать или показать заблокированных";
+"content.commands.clear" = "очистить чат";
+"content.commands.favorite" = "добавить в избранное";
+"content.commands.hug" = "отправить тёплое объятие";
+"content.commands.message" = "отправить приватное сообщение";
+"content.commands.slap" = "дать кому-то пощёчину форелью";
+"content.commands.unblock" = "разблокировать пира";
+"content.commands.unfavorite" = "убрать из избранного";
+"content.commands.who" = "посмотреть, кто онлайн";
+"content.delivery.delivered_members" = "доставлено %1$d из %2$d участников";
+"content.delivery.delivered_to" = "доставлено %@";
+"content.delivery.failed" = "ошибка: %@";
+"content.delivery.read_by" = "прочитано %@";
+"content.delivery.reason.blocked" = "пользователь заблокирован";
+"content.delivery.reason.self" = "нельзя отправить себе";
+"content.delivery.reason.send_error" = "ошибка отправки";
+"content.delivery.reason.unknown_recipient" = "неизвестный получатель";
+"content.delivery.reason.unreachable" = "пир недостижим";
+"content.header.people" = "ЛЮДИ";
+"content.help.verification" = "верификация: показать мой qr или сканировать друга";
+"content.input.message_placeholder" = "напиши сообщение...";
+"content.input.nickname_placeholder" = "ник";
+"content.location.enable" = "включить локацию";
+"content.message.copy" = "копировать сообщение";
+"content.message.show_less" = "показать меньше";
+"content.message.show_more" = "показать больше";
+"content.notes.location_unavailable" = "локация недоступна";
+"content.notes.title" = "заметки";
+"content.payment.cashu" = "оплатить через cashu";
+"content.payment.lightning" = "оплатить через lightning";
+"encryption.accessibility.establishing" = "устанавливается шифрование";
+"encryption.accessibility.failed" = "шифрование не удалось";
+"encryption.accessibility.not_encrypted" = "не зашифровано";
+"encryption.accessibility.secured" = "зашифровано";
+"encryption.accessibility.verified" = "зашифровано и проверено";
+"encryption.status.establishing" = "устанавливаем шифрование...";
+"encryption.status.failed" = "шифрование не удалось";
+"encryption.status.not_encrypted" = "не зашифровано";
+"encryption.status.secured" = "зашифровано";
+"encryption.status.verified" = "зашифровано и проверено";
+"fingerprint.action.mark_verified" = "пометить как проверено";
+"fingerprint.action.remove_verification" = "удалить проверку";
+"fingerprint.badge.not_verified" = "⚠️ НЕ ПРОВЕРЕНО";
+"fingerprint.badge.verified" = "✓ ПРОВЕРЕНО";
+"fingerprint.handshake_pending" = "недоступно — handshake выполняется";
+"fingerprint.message.verified" = "ты подтвердил личность этого человека.";
+"fingerprint.message.verify_hint" = "сравни эти отпечатки с %@ по безопасному каналу.";
+"fingerprint.their_label" = "их отпечаток:";
+"fingerprint.title" = "проверка безопасности";
+"fingerprint.your_label" = "твой отпечаток:";
+"geohash_people.action.block" = "заблокировать";
+"geohash_people.action.unblock" = "разблокировать";
+"geohash_people.none_nearby" = "никого рядом...";
+"geohash_people.tooltip.blocked" = "заблокирован в geohash";
+"geohash_people.you_suffix" = " (ты)";
+"location_channels.action.open_settings" = "открыть настройки";
+"location_channels.action.remove_access" = "отключить доступ к локации";
+"location_channels.action.request_permissions" = "получить мою локацию и geohash";
+"location_channels.action.teleport" = "телепорт";
+"location_channels.bookmarked_section_title" = "закреплённые";
+"location_channels.description" = "общайся с людьми рядом через каналы geohash. делится только грубый geohash, без точного gps. твой ip скрывается за счёт маршрутизации трафика через tor.";
+"location_channels.error.invalid_geohash" = "некорректный geohash";
+"location_channels.loading_nearby" = "поиск каналов рядом…";
+"location_channels.mesh_label" = "mesh";
+"location_channels.permission_denied" = "доступ к локации запрещён. включи разрешение в настройках, чтобы использовать каналы.";
+"location_channels.subtitle_prefix" = "#%@ • %@";
+"location_channels.subtitle_with_name" = "%1$@ • %2$@";
+"location_channels.title" = "#каналы локации";
+"location_channels.tor.subtitle" = "скрывает твой ip для каналов локации. рекомендуем включить.";
+"location_channels.tor.title" = "маршрутизация tor";
+"location_levels.block" = "квартал";
+"location_levels.building" = "здание";
+"location_levels.city" = "город";
+"location_levels.neighborhood" = "район";
+"location_levels.province" = "область";
+"location_levels.region" = "регион";
+"location_notes.action.dismiss" = "закрыть";
+"location_notes.action.retry" = "повторить";
+"location_notes.description" = "добавь короткие постоянные заметки об этом месте для других.";
+"location_notes.empty_subtitle" = "стань первым, кто добавит здесь заметку.";
+"location_notes.empty_title" = "заметок пока нет";
+"location_notes.error.failed_to_send" = "не удалось отправить заметку. %@";
+"location_notes.error.no_relays" = "рядом нет георелеев. попробуй позже.";
+"location_notes.loading_notes" = "загрузка заметок…";
+"location_notes.loading_recent" = "загрузка свежих заметок…";
+"location_notes.no_relays_nearby" = "рядом нет георелеев";
+"location_notes.placeholder" = "добавь заметку для этого места";
+"location_notes.relays_paused" = "геореле недоступны; заметки приостановлены";
+"location_notes.relays_retry_hint" = "заметки зависят от георелеев. проверь подключение и попробуй снова.";
+"mesh_peers.tooltip.new_messages" = "новые сообщения";
+"system.chat.blocked" = "нельзя начать чат с %@: пользователь заблокирован.";
+"system.chat.requires_favorite" = "нельзя начать чат с %@: нужны взаимные избранные для офлайна.";
+"system.common.user" = "пользователь";
+"system.dm.blocked_generic" = "отправка невозможна: пользователь заблокирован.";
+"system.dm.blocked_recipient" = "нельзя отправить %@: пользователь заблокирован.";
+"system.dm.unreachable" = "нельзя отправить %@: адресат недоступен через mesh или nostr.";
+"system.geohash.blocked" = "%@ заблокирован в geohash-чатах";
+"system.geohash.unblocked" = "%@ разблокирован в geohash-чатах";
+"system.location.not_in_channel" = "отправка невозможна: ты не в канале локации";
+"system.location.send_failed" = "не удалось отправить в канал локации";
+"system.tor.dev_bypass" = "dev-сборка: обход tor включён.";
+"system.tor.restarted" = "tor перезапущен. маршрутизация восстановлена.";
+"system.tor.restarting" = "tor перезапускается, чтобы вернуть связь...";
+"system.tor.started" = "tor запущен. весь чат идёт через tor для приватности.";
+"system.tor.starting" = "запуск tor...";
+"verification.my_qr.accessibility_label" = "qr-код проверки";
+"verification.my_qr.title" = "отсканируй, чтобы подтвердить меня";
+"verification.my_qr.unavailable" = "qr недоступен";
+"verification.scan.paste_prompt" = "вставь содержимое qr для проверки:";
+"verification.scan.prompt_friend" = "отсканируй qr друга";
+"verification.scan.status.invalid" = "qr недействителен или просрочен";
+"verification.scan.status.no_peer" = "соответствующий пир не найден";
+"verification.scan.status.requested" = "проверка запрошена для %@";
+"verification.scan.validate" = "проверить";
+"verification.sheet.title" = "ПРОВЕРИТЬ";
diff --git a/bitchat/Localization/ru.lproj/Localizable.stringsdict b/bitchat/Localization/ru.lproj/Localizable.stringsdict
new file mode 100644
index 00000000..4997f3e5
--- /dev/null
+++ b/bitchat/Localization/ru.lproj/Localizable.stringsdict
@@ -0,0 +1,66 @@
+
+
+
+
+ location_notes.header
+
+ NSStringLocalizedFormatKey
+ #%@ • %#@note_count@
+ note_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ d
+ one
+ %d заметка
+ few
+ %d заметки
+ many
+ %d заметок
+ other
+ %d заметки
+
+
+ location_channels.row_title
+
+ NSStringLocalizedFormatKey
+ %@ [%#@people_count@]
+ people_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ d
+ one
+ %d человек
+ few
+ %d человека
+ many
+ %d человек
+ other
+ %d человека
+
+
+ content.accessibility.people_count
+
+ NSStringLocalizedFormatKey
+ %#@people@
+ people
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ d
+ one
+ %d человек
+ few
+ %d человека
+ many
+ %d человек
+ other
+ %d человека
+
+
+
+
diff --git a/bitchat/Localization/uk.lproj/Localizable.strings b/bitchat/Localization/uk.lproj/Localizable.strings
new file mode 100644
index 00000000..8ef9e3e7
--- /dev/null
+++ b/bitchat/Localization/uk.lproj/Localizable.strings
@@ -0,0 +1,190 @@
+/*
+ Localizable.strings
+ bitchat (Ukrainian)
+*/
+
+"app_info.app_name" = "bitchat";
+"app_info.close" = "закрити";
+"app_info.done" = "ГОТОВО";
+"app_info.features.encryption.description" = "приватні повідомлення шифруються протоколом noise";
+"app_info.features.encryption.title" = "скрізьове шифрування";
+"app_info.features.extended_range.description" = "повідомлення ретранслюються між пірами й долітають далі";
+"app_info.features.extended_range.title" = "розширена дальність";
+"app_info.features.favorites.description" = "отримуй сповіщення, коли підключаються улюблені люди";
+"app_info.features.favorites.title" = "вибране";
+"app_info.features.geohash.description" = "канали geohash для спілкування з людьми поблизу через децентралізовані анонімні ретранслятори";
+"app_info.features.geohash.title" = "локальні канали";
+"app_info.features.mentions.description" = "використовуй @nickname, щоб сповістити конкретних людей";
+"app_info.features.mentions.title" = "згадки";
+"app_info.features.offline.description" = "працює без інтернету через bluetooth low energy";
+"app_info.features.offline.title" = "офлайн-зв'язок";
+"app_info.features.title" = "МОЖЛИВОСТІ";
+"app_info.how_to_use.change_channels" = "• торкнися #mesh, щоб змінити канал";
+"app_info.how_to_use.clear_chat" = "• торкни чат тричі, щоб очистити";
+"app_info.how_to_use.commands" = "• введи /, щоб побачити команди";
+"app_info.how_to_use.open_sidebar" = "• торкни піктограму людей, щоб відкрити бічну панель";
+"app_info.how_to_use.set_nickname" = "• змінюй свій нік, торкаючись його";
+"app_info.how_to_use.start_dm" = "• торкни ім'я піра, щоб почати приватний чат";
+"app_info.how_to_use.title" = "ЯК КОРИСТУВАТИСЯ";
+"app_info.privacy.ephemeral.description" = "новий id піра генерується регулярно";
+"app_info.privacy.ephemeral.title" = "ефемерна ідентичність";
+"app_info.privacy.no_tracking.description" = "жодних серверів, обліковок чи збору даних";
+"app_info.privacy.no_tracking.title" = "без відстеження";
+"app_info.privacy.panic.description" = "тричі торкни логотип, щоб миттєво стерти всі дані";
+"app_info.privacy.panic.title" = "режим паніки";
+"app_info.privacy.title" = "КОНФІДЕНЦІЙНІСТЬ";
+"app_info.tagline" = "sidegroupchat";
+"app_info.warning.message" = "безпека приватних повідомлень ще не пройшла повний аудит. не використовуй для критичних ситуацій, поки це попередження не зникне.";
+"app_info.warning.title" = "ПОПЕРЕДЖЕННЯ";
+"common.cancel" = "скасувати";
+"common.close" = "закрити";
+"common.copy" = "скопіювати";
+"common.ok" = "OK";
+"common.toggle.off" = "вимк";
+"common.toggle.on" = "увімк";
+"common.unknown" = "невідомо";
+"content.accessibility.add_favorite" = "додати до вибраного";
+"content.accessibility.available_nostr" = "доступно через nostr";
+"content.accessibility.back_to_main_chat" = "назад до основного чату";
+"content.accessibility.connected_mesh" = "з'єднано через mesh";
+"content.accessibility.encryption_status" = "стан шифрування: %@";
+"content.accessibility.location_channels" = "канали локації";
+"content.accessibility.location_notes" = "замітки про це місце";
+"content.accessibility.open_unread_private_chat" = "відкрити непрочитаний приватний чат";
+"content.accessibility.private_chat_header" = "приватний чат з %@";
+"content.accessibility.reachable_mesh" = "досяжно через mesh";
+"content.accessibility.remove_favorite" = "видалити з вибраного";
+"content.accessibility.send_hint_empty" = "введи повідомлення для надсилання";
+"content.accessibility.send_hint_ready" = "торкни двічі, щоб надіслати";
+"content.accessibility.send_message" = "надіслати повідомлення";
+"content.accessibility.toggle_bookmark" = "перемкнути закладку для #%@";
+"content.accessibility.toggle_favorite_hint" = "торкни двічі, щоб змінити статус вибраного";
+"content.accessibility.view_fingerprint_hint" = "торкни, щоб переглянути криптографічний відбиток";
+"content.actions.block" = "заблокувати";
+"content.actions.direct_message" = "приватне повідомлення";
+"content.actions.hug" = "обійняти";
+"content.actions.mention" = "згадати";
+"content.actions.slap" = "ляпас";
+"content.actions.title" = "дії";
+"content.alert.bluetooth_required.off" = "bluetooth вимкнений. увімкни bluetooth у налаштуваннях, щоб користуватися bitchat.";
+"content.alert.bluetooth_required.permission" = "bitchat потребує дозволу bluetooth для з'єднання з пристроями поруч. ввімкни доступ у налаштуваннях.";
+"content.alert.bluetooth_required.settings" = "налаштування";
+"content.alert.bluetooth_required.title" = "потрібен bluetooth";
+"content.alert.bluetooth_required.unsupported" = "цей пристрій не підтримує bluetooth. bitchat потрібен bluetooth для роботи.";
+"content.alert.screenshot.message" = "скріншоти каналів локації розкриють твоє місце. подумай, перш ніж ділитися публічно.";
+"content.alert.screenshot.title" = "увага";
+"content.commands.block" = "заблокувати або показати заблокованих";
+"content.commands.clear" = "очистити чат";
+"content.commands.favorite" = "додати до вибраного";
+"content.commands.hug" = "відправити теплі обійми";
+"content.commands.message" = "надіслати приватне повідомлення";
+"content.commands.slap" = "лупнути когось фореллю";
+"content.commands.unblock" = "розблокувати піра";
+"content.commands.unfavorite" = "видалити з вибраного";
+"content.commands.who" = "подивитися, хто онлайн";
+"content.delivery.delivered_members" = "доставлено %1$d з %2$d учасників";
+"content.delivery.delivered_to" = "доставлено %@";
+"content.delivery.failed" = "не вдалося: %@";
+"content.delivery.read_by" = "прочитано %@";
+"content.delivery.reason.blocked" = "користувач заблокований";
+"content.delivery.reason.self" = "не можна надіслати собі";
+"content.delivery.reason.send_error" = "помилка надсилання";
+"content.delivery.reason.unknown_recipient" = "невідомий одержувач";
+"content.delivery.reason.unreachable" = "пір недосяжний";
+"content.header.people" = "ЛЮДИ";
+"content.help.verification" = "верифікація: показати мій qr або сканувати друга";
+"content.input.message_placeholder" = "напиши повідомлення...";
+"content.input.nickname_placeholder" = "нік";
+"content.location.enable" = "увімкнути локацію";
+"content.message.copy" = "скопіювати повідомлення";
+"content.message.show_less" = "показати менше";
+"content.message.show_more" = "показати більше";
+"content.notes.location_unavailable" = "локація недоступна";
+"content.notes.title" = "замітки";
+"content.payment.cashu" = "оплатити через cashu";
+"content.payment.lightning" = "оплатити через lightning";
+"encryption.accessibility.establishing" = "встановлюється шифрування";
+"encryption.accessibility.failed" = "шифрування не вдалося";
+"encryption.accessibility.not_encrypted" = "не зашифровано";
+"encryption.accessibility.secured" = "зашифровано";
+"encryption.accessibility.verified" = "зашифровано та перевірено";
+"encryption.status.establishing" = "встановлюємо шифрування...";
+"encryption.status.failed" = "шифрування не вдалося";
+"encryption.status.not_encrypted" = "не зашифровано";
+"encryption.status.secured" = "зашифровано";
+"encryption.status.verified" = "зашифровано та перевірено";
+"fingerprint.action.mark_verified" = "позначити як перевірено";
+"fingerprint.action.remove_verification" = "зняти перевірку";
+"fingerprint.badge.not_verified" = "⚠️ НЕ ПЕРЕВІРЕНО";
+"fingerprint.badge.verified" = "✓ ПЕРЕВІРЕНО";
+"fingerprint.handshake_pending" = "недоступно — handshake триває";
+"fingerprint.message.verified" = "ти підтвердив особу цієї людини.";
+"fingerprint.message.verify_hint" = "порівняй ці відбитки з %@ у безпечному каналі.";
+"fingerprint.their_label" = "їхній відбиток:";
+"fingerprint.title" = "перевірка безпеки";
+"fingerprint.your_label" = "твій відбиток:";
+"geohash_people.action.block" = "заблокувати";
+"geohash_people.action.unblock" = "розблокувати";
+"geohash_people.none_nearby" = "поруч нікого...";
+"geohash_people.tooltip.blocked" = "заблоковано в geohash";
+"geohash_people.you_suffix" = " (ти)";
+"location_channels.action.open_settings" = "відкрити налаштування";
+"location_channels.action.remove_access" = "відключити доступ до локації";
+"location_channels.action.request_permissions" = "отримати мою локацію та geohash";
+"location_channels.action.teleport" = "телепорт";
+"location_channels.bookmarked_section_title" = "закладені";
+"location_channels.description" = "спілкуйся з людьми поруч у каналах geohash. передається лише грубий geohash, без точного gps. твій ip приховується, бо весь трафік йде через tor.";
+"location_channels.error.invalid_geohash" = "некоректний geohash";
+"location_channels.loading_nearby" = "пошук каналів поруч…";
+"location_channels.mesh_label" = "mesh";
+"location_channels.permission_denied" = "доступ до локації заборонено. увімкни дозвіл у налаштуваннях, щоб користуватися каналами.";
+"location_channels.subtitle_prefix" = "#%@ • %@";
+"location_channels.subtitle_with_name" = "%1$@ • %2$@";
+"location_channels.title" = "#канали локації";
+"location_channels.tor.subtitle" = "приховує твій ip для каналів локації. рекомендовано ввімкнути.";
+"location_channels.tor.title" = "маршрутизація tor";
+"location_levels.block" = "квартал";
+"location_levels.building" = "будівля";
+"location_levels.city" = "місто";
+"location_levels.neighborhood" = "район";
+"location_levels.province" = "область";
+"location_levels.region" = "регіон";
+"location_notes.action.dismiss" = "закрити";
+"location_notes.action.retry" = "повторити";
+"location_notes.description" = "додай короткі постійні замітки про це місце для інших.";
+"location_notes.empty_subtitle" = "стань першим, хто додасть тут замітку.";
+"location_notes.empty_title" = "заміток ще немає";
+"location_notes.error.failed_to_send" = "не вдалося надіслати замітку. %@";
+"location_notes.error.no_relays" = "поруч немає гео-релеїв. спробуй пізніше.";
+"location_notes.loading_notes" = "завантаження заміток…";
+"location_notes.loading_recent" = "завантаження свіжих заміток…";
+"location_notes.no_relays_nearby" = "немає гео-релеїв поблизу";
+"location_notes.placeholder" = "додай замітку для цього місця";
+"location_notes.relays_paused" = "гео-релеї недоступні; замітки призупинено";
+"location_notes.relays_retry_hint" = "замітки залежать від гео-релеїв. перевір з'єднання й спробуй ще раз.";
+"mesh_peers.tooltip.new_messages" = "нові повідомлення";
+"system.chat.blocked" = "не можна почати чат з %@: користувач заблокований.";
+"system.chat.requires_favorite" = "не можна почати чат з %@: потрібне взаємне вибране для офлайна.";
+"system.common.user" = "користувач";
+"system.dm.blocked_generic" = "не вдалося надіслати: користувач заблокований.";
+"system.dm.blocked_recipient" = "неможливо надіслати %@: користувач заблокований.";
+"system.dm.unreachable" = "неможливо надіслати %@: одержувач недосяжний через mesh або nostr.";
+"system.geohash.blocked" = "%@ заблоковано в geohash-чатах";
+"system.geohash.unblocked" = "%@ розблоковано в geohash-чатах";
+"system.location.not_in_channel" = "не вдалося надіслати: ти не в каналі локації";
+"system.location.send_failed" = "не вдалося надіслати в канал локації";
+"system.tor.dev_bypass" = "dev-збірка: обхід tor увімкнено.";
+"system.tor.restarted" = "tor перезапущено. маршрутизацію відновлено.";
+"system.tor.restarting" = "tor перезапускається, щоб відновити підключення...";
+"system.tor.started" = "tor запущено. увесь чат іде через tor для приватності.";
+"system.tor.starting" = "запуск tor...";
+"verification.my_qr.accessibility_label" = "qr-код підтвердження";
+"verification.my_qr.title" = "скануй, щоб підтвердити мене";
+"verification.my_qr.unavailable" = "qr недоступний";
+"verification.scan.paste_prompt" = "встав вміст qr для перевірки:";
+"verification.scan.prompt_friend" = "скануй qr друга";
+"verification.scan.status.invalid" = "qr недійсний або прострочений";
+"verification.scan.status.no_peer" = "відповідний пір не знайдений";
+"verification.scan.status.requested" = "перевірка запитана для %@";
+"verification.scan.validate" = "перевірити";
+"verification.sheet.title" = "ПЕРЕВІРИТИ";
diff --git a/bitchat/Localization/uk.lproj/Localizable.stringsdict b/bitchat/Localization/uk.lproj/Localizable.stringsdict
new file mode 100644
index 00000000..8f280ccb
--- /dev/null
+++ b/bitchat/Localization/uk.lproj/Localizable.stringsdict
@@ -0,0 +1,66 @@
+
+
+
+
+ location_notes.header
+
+ NSStringLocalizedFormatKey
+ #%@ • %#@note_count@
+ note_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ d
+ one
+ %d замітка
+ few
+ %d замітки
+ many
+ %d заміток
+ other
+ %d замітки
+
+
+ location_channels.row_title
+
+ NSStringLocalizedFormatKey
+ %@ [%#@people_count@]
+ people_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ d
+ one
+ %d людина
+ few
+ %d людини
+ many
+ %d людей
+ other
+ %d людини
+
+
+ content.accessibility.people_count
+
+ NSStringLocalizedFormatKey
+ %#@people@
+ people
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ d
+ one
+ %d людина
+ few
+ %d людини
+ many
+ %d людей
+ other
+ %d людини
+
+
+
+
diff --git a/bitchat/Localization/zh-Hans.lproj/Localizable.strings b/bitchat/Localization/zh-Hans.lproj/Localizable.strings
new file mode 100644
index 00000000..13d9ffce
--- /dev/null
+++ b/bitchat/Localization/zh-Hans.lproj/Localizable.strings
@@ -0,0 +1,190 @@
+/*
+ Localizable.strings
+ bitchat (Simplified Chinese)
+*/
+
+"app_info.app_name" = "bitchat";
+"app_info.close" = "关闭";
+"app_info.done" = "完成";
+"app_info.features.encryption.description" = "私密消息使用 noise 协议加密";
+"app_info.features.encryption.title" = "端到端加密";
+"app_info.features.extended_range.description" = "消息通过同伴中继,传得更远";
+"app_info.features.extended_range.title" = "扩展范围";
+"app_info.features.favorites.description" = "你喜欢的人加入时立刻提醒";
+"app_info.features.favorites.title" = "收藏";
+"app_info.features.geohash.description" = "geohash 频道让你通过去中心化匿名中继与附近地区的人聊天";
+"app_info.features.geohash.title" = "本地频道";
+"app_info.features.mentions.description" = "使用 @nickname 提醒特定的人";
+"app_info.features.mentions.title" = "提及";
+"app_info.features.offline.description" = "利用低功耗 bluetooth 离线工作";
+"app_info.features.offline.title" = "离线通信";
+"app_info.features.title" = "功能";
+"app_info.how_to_use.change_channels" = "• 轻点 #mesh 切换频道";
+"app_info.how_to_use.clear_chat" = "• 三击聊天即可清除";
+"app_info.how_to_use.commands" = "• 输入 / 查看指令";
+"app_info.how_to_use.open_sidebar" = "• 轻点人物图标打开侧栏";
+"app_info.how_to_use.set_nickname" = "• 轻点昵称即可设置";
+"app_info.how_to_use.start_dm" = "• 轻点同伴名字开始 dm";
+"app_info.how_to_use.title" = "使用方法";
+"app_info.privacy.ephemeral.description" = "定期生成新的 peer id";
+"app_info.privacy.ephemeral.title" = "临时身份";
+"app_info.privacy.no_tracking.description" = "无服务器、无账号、无数据收集";
+"app_info.privacy.no_tracking.title" = "无跟踪";
+"app_info.privacy.panic.description" = "三击标志立即清除全部数据";
+"app_info.privacy.panic.title" = "紧急模式";
+"app_info.privacy.title" = "隐私";
+"app_info.tagline" = "sidegroupchat";
+"app_info.warning.message" = "私信安全尚未完全审计。在此警告消失前不要用于关键情境。";
+"app_info.warning.title" = "警告";
+"common.cancel" = "取消";
+"common.close" = "关闭";
+"common.copy" = "复制";
+"common.ok" = "确定";
+"common.toggle.off" = "关闭";
+"common.toggle.on" = "开启";
+"common.unknown" = "未知";
+"content.accessibility.add_favorite" = "加入收藏";
+"content.accessibility.available_nostr" = "通过 Nostr 可用";
+"content.accessibility.back_to_main_chat" = "返回主聊天";
+"content.accessibility.connected_mesh" = "通过 mesh 已连接";
+"content.accessibility.encryption_status" = "加密状态:%@";
+"content.accessibility.location_channels" = "位置频道";
+"content.accessibility.location_notes" = "此位置的笔记";
+"content.accessibility.open_unread_private_chat" = "打开未读私聊";
+"content.accessibility.private_chat_header" = "与 %@ 的私聊";
+"content.accessibility.reachable_mesh" = "可通过 mesh 到达";
+"content.accessibility.remove_favorite" = "移出收藏";
+"content.accessibility.send_hint_empty" = "输入要发送的消息";
+"content.accessibility.send_hint_ready" = "双击发送";
+"content.accessibility.send_message" = "发送消息";
+"content.accessibility.toggle_bookmark" = "切换 #%@ 的书签";
+"content.accessibility.toggle_favorite_hint" = "双击切换收藏状态";
+"content.accessibility.view_fingerprint_hint" = "轻点查看加密指纹";
+"content.actions.block" = "屏蔽";
+"content.actions.direct_message" = "私信";
+"content.actions.hug" = "拥抱";
+"content.actions.mention" = "提及";
+"content.actions.slap" = "拍打";
+"content.actions.title" = "操作";
+"content.alert.bluetooth_required.off" = "bluetooth 已关闭。请在设置中开启以使用 bitchat。";
+"content.alert.bluetooth_required.permission" = "bitchat 需要 bluetooth 权限以连接附近设备。请在设置中启用访问。";
+"content.alert.bluetooth_required.settings" = "设置";
+"content.alert.bluetooth_required.title" = "需要 bluetooth";
+"content.alert.bluetooth_required.unsupported" = "此设备不支持 bluetooth。bitchat 需要 bluetooth 才能运行。";
+"content.alert.screenshot.message" = "位置频道的截图会暴露你的位置。公开分享前请三思。";
+"content.alert.screenshot.title" = "注意";
+"content.commands.block" = "屏蔽或查看已屏蔽的同伴";
+"content.commands.clear" = "清除聊天消息";
+"content.commands.favorite" = "加入收藏";
+"content.commands.hug" = "送出温暖拥抱";
+"content.commands.message" = "发送私信";
+"content.commands.slap" = "用鳟鱼拍某人";
+"content.commands.unblock" = "取消屏蔽同伴";
+"content.commands.unfavorite" = "移出收藏";
+"content.commands.who" = "查看谁在线";
+"content.delivery.delivered_members" = "已送达 %2$d 人中的 %1$d 人";
+"content.delivery.delivered_to" = "已送达 %@";
+"content.delivery.failed" = "失败:%@";
+"content.delivery.read_by" = "已读:%@";
+"content.delivery.reason.blocked" = "用户已被屏蔽";
+"content.delivery.reason.self" = "不能给自己发消息";
+"content.delivery.reason.send_error" = "发送错误";
+"content.delivery.reason.unknown_recipient" = "未知收件人";
+"content.delivery.reason.unreachable" = "同伴不可达";
+"content.header.people" = "成员";
+"content.help.verification" = "验证:展示我的 qr 或扫描好友";
+"content.input.message_placeholder" = "输入消息...";
+"content.input.nickname_placeholder" = "昵称";
+"content.location.enable" = "启用位置";
+"content.message.copy" = "复制消息";
+"content.message.show_less" = "收起";
+"content.message.show_more" = "展开";
+"content.notes.location_unavailable" = "位置不可用";
+"content.notes.title" = "笔记";
+"content.payment.cashu" = "通过 cashu 支付";
+"content.payment.lightning" = "通过 lightning 支付";
+"encryption.accessibility.establishing" = "正在建立加密";
+"encryption.accessibility.failed" = "加密失败";
+"encryption.accessibility.not_encrypted" = "未加密";
+"encryption.accessibility.secured" = "已加密";
+"encryption.accessibility.verified" = "已加密并验证";
+"encryption.status.establishing" = "正在建立加密...";
+"encryption.status.failed" = "加密失败";
+"encryption.status.not_encrypted" = "未加密";
+"encryption.status.secured" = "已加密";
+"encryption.status.verified" = "已加密并验证";
+"fingerprint.action.mark_verified" = "标记为已验证";
+"fingerprint.action.remove_verification" = "移除验证";
+"fingerprint.badge.not_verified" = "⚠️ 未验证";
+"fingerprint.badge.verified" = "✓ 已验证";
+"fingerprint.handshake_pending" = "暂不可用 - handshake 进行中";
+"fingerprint.message.verified" = "你已经核实了此人的身份。";
+"fingerprint.message.verify_hint" = "通过安全渠道与 %@ 比对这些指纹。";
+"fingerprint.their_label" = "对方指纹:";
+"fingerprint.title" = "安全验证";
+"fingerprint.your_label" = "你的指纹:";
+"geohash_people.action.block" = "屏蔽";
+"geohash_people.action.unblock" = "取消屏蔽";
+"geohash_people.none_nearby" = "附近没人...";
+"geohash_people.tooltip.blocked" = "在 geohash 中已屏蔽";
+"geohash_people.you_suffix" = " (你)";
+"location_channels.action.open_settings" = "打开设置";
+"location_channels.action.remove_access" = "移除位置访问";
+"location_channels.action.request_permissions" = "获取位置和我的 geohash";
+"location_channels.action.teleport" = "瞬移";
+"location_channels.bookmarked_section_title" = "已收藏";
+"location_channels.description" = "使用 geohash 频道与附近的人聊天。只会共享粗略 geohash,从不泄露精确 GPS。所有流量通过 tor 路由来隐藏你的 IP。";
+"location_channels.error.invalid_geohash" = "无效的 geohash";
+"location_channels.loading_nearby" = "正在寻找附近频道…";
+"location_channels.mesh_label" = "mesh";
+"location_channels.permission_denied" = "位置权限被拒。请在设置中启用以使用位置频道。";
+"location_channels.subtitle_prefix" = "#%@ • %@";
+"location_channels.subtitle_with_name" = "%1$@ • %2$@";
+"location_channels.title" = "#位置频道";
+"location_channels.tor.subtitle" = "为位置频道隐藏你的 IP。推荐:开启。";
+"location_channels.tor.title" = "tor 路由";
+"location_levels.block" = "街区";
+"location_levels.building" = "楼栋";
+"location_levels.city" = "城市";
+"location_levels.neighborhood" = "社区";
+"location_levels.province" = "省份";
+"location_levels.region" = "区域";
+"location_notes.action.dismiss" = "关闭";
+"location_notes.action.retry" = "重试";
+"location_notes.description" = "为此地点添加简短的常驻笔记,方便其他访客发现。";
+"location_notes.empty_subtitle" = "成为这里的第一条笔记。";
+"location_notes.empty_title" = "尚无笔记";
+"location_notes.error.failed_to_send" = "无法发送笔记。%@";
+"location_notes.error.no_relays" = "附近没有可用的地理中继。稍后再试。";
+"location_notes.loading_notes" = "正在加载笔记…";
+"location_notes.loading_recent" = "正在加载最新笔记…";
+"location_notes.no_relays_nearby" = "附近没有地理中继";
+"location_notes.placeholder" = "为此地点添加笔记";
+"location_notes.relays_paused" = "地理中继不可用;笔记已暂停";
+"location_notes.relays_retry_hint" = "笔记依赖地理中继。检查连接后再试。";
+"mesh_peers.tooltip.new_messages" = "新消息";
+"system.chat.blocked" = "无法与 %@ 开始聊天:用户已被屏蔽。";
+"system.chat.requires_favorite" = "无法与 %@ 开始聊天:离线消息需要互相关注。";
+"system.common.user" = "用户";
+"system.dm.blocked_generic" = "无法发送:用户已被屏蔽。";
+"system.dm.blocked_recipient" = "无法向 %@ 发送:用户已被屏蔽。";
+"system.dm.unreachable" = "无法向 %@ 发送:对方无法通过 mesh 或 Nostr 到达。";
+"system.geohash.blocked" = "已在 geohash 聊天中屏蔽 %@";
+"system.geohash.unblocked" = "已在 geohash 聊天中解除屏蔽 %@";
+"system.location.not_in_channel" = "发送失败:你不在位置频道中";
+"system.location.send_failed" = "无法发送到位置频道";
+"system.tor.dev_bypass" = "开发构建:tor 绕过已启用。";
+"system.tor.restarted" = "tor 已重启。网络路由已恢复。";
+"system.tor.restarting" = "tor 正在重启以恢复连接...";
+"system.tor.started" = "tor 已启动。所有聊天通过 tor 路由以保护 IP。";
+"system.tor.starting" = "正在启动 tor...";
+"verification.my_qr.accessibility_label" = "验证 QR 码";
+"verification.my_qr.title" = "扫描验证我";
+"verification.my_qr.unavailable" = "QR 不可用";
+"verification.scan.paste_prompt" = "粘贴 QR 内容以验证:";
+"verification.scan.prompt_friend" = "扫描好友的 QR";
+"verification.scan.status.invalid" = "QR 无效或已过期";
+"verification.scan.status.no_peer" = "未找到匹配的同伴";
+"verification.scan.status.requested" = "已请求 %@ 的验证";
+"verification.scan.validate" = "验证";
+"verification.sheet.title" = "验证";
diff --git a/bitchat/Localization/zh-Hans.lproj/Localizable.stringsdict b/bitchat/Localization/zh-Hans.lproj/Localizable.stringsdict
new file mode 100644
index 00000000..e89ceeba
--- /dev/null
+++ b/bitchat/Localization/zh-Hans.lproj/Localizable.stringsdict
@@ -0,0 +1,54 @@
+
+
+
+
+ location_notes.header
+
+ NSStringLocalizedFormatKey
+ #%@ • %#@note_count@
+ note_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ d
+ one
+ %d 条笔记
+ other
+ %d 条笔记
+
+
+ location_channels.row_title
+
+ NSStringLocalizedFormatKey
+ %@ [%#@people_count@]
+ people_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ d
+ one
+ %d 人
+ other
+ %d 人
+
+
+ content.accessibility.people_count
+
+ NSStringLocalizedFormatKey
+ %#@people@
+ people
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ d
+ one
+ %d 人
+ other
+ %d 人
+
+
+
+
diff --git a/bitchat/Protocols/LocationChannel.swift b/bitchat/Protocols/LocationChannel.swift
index 5aea0575..546d20f8 100644
--- a/bitchat/Protocols/LocationChannel.swift
+++ b/bitchat/Protocols/LocationChannel.swift
@@ -23,15 +23,21 @@ enum GeohashChannelLevel: CaseIterable, Codable, Equatable {
var displayName: String {
switch self {
- case .building: return "Building"
- case .block: return "Block"
- case .neighborhood: return "Neighborhood"
- case .city: return "City"
- case .province: return "Province"
- case .region: return "Region"
+ case .building:
+ return L10n.string("location_levels.building", comment: "Name for building-level location channel")
+ case .block:
+ return L10n.string("location_levels.block", comment: "Name for block-level location channel")
+ case .neighborhood:
+ return L10n.string("location_levels.neighborhood", comment: "Name for neighborhood-level location channel")
+ case .city:
+ return L10n.string("location_levels.city", comment: "Name for city-level location channel")
+ case .province:
+ return L10n.string("location_levels.province", comment: "Name for province-level location channel")
+ case .region:
+ return L10n.string("location_levels.region", comment: "Name for region-level location channel")
+ }
}
}
-}
// Backward-compatible Codable for renamed cases
extension GeohashChannelLevel {
init(from decoder: Decoder) throws {
diff --git a/bitchat/Services/LocationNotesManager.swift b/bitchat/Services/LocationNotesManager.swift
index afcf052e..30de5d33 100644
--- a/bitchat/Services/LocationNotesManager.swift
+++ b/bitchat/Services/LocationNotesManager.swift
@@ -76,6 +76,21 @@ final class LocationNotesManager: ObservableObject {
private var subscriptionID: String?
private let dependencies: LocationNotesDependencies
+ private enum Strings {
+ static let noRelays = NSLocalizedString(
+ "location_notes.error.no_relays",
+ comment: "Shown when no geo relays are available near the selected location"
+ )
+
+ static func failedToSend(_ detail: String) -> String {
+ let format = NSLocalizedString(
+ "location_notes.error.failed_to_send",
+ comment: "Shown when a location note fails to send"
+ )
+ return String(format: format, detail)
+ }
+ }
+
init(geohash: String, dependencies: LocationNotesDependencies = .live) {
self.geohash = geohash.lowercased()
self.dependencies = dependencies
@@ -120,7 +135,7 @@ final class LocationNotesManager: ObservableObject {
subscriptionID = nil
initialLoadComplete = true
state = .noRelays
- errorMessage = "No geo relays available near this location. Try again soon."
+ errorMessage = Strings.noRelays
SecureLogger.warning("LocationNotesManager: no geo relays for geohash=\(geohash)", category: .session)
return
}
@@ -158,7 +173,7 @@ final class LocationNotesManager: ObservableObject {
let relays = dependencies.relayLookup(geohash, TransportConfig.nostrGeoRelayCount)
guard !relays.isEmpty else {
state = .noRelays
- errorMessage = "No geo relays available near this location. Try again soon."
+ errorMessage = Strings.noRelays
SecureLogger.warning("LocationNotesManager: send blocked, no geo relays for geohash=\(geohash)", category: .session)
return
}
@@ -184,7 +199,7 @@ final class LocationNotesManager: ObservableObject {
self.errorMessage = nil
} catch {
SecureLogger.error("LocationNotesManager: failed to send note: \(error)", category: .session)
- errorMessage = "Failed to send note. \(error.localizedDescription)"
+ errorMessage = Strings.failedToSend(error.localizedDescription)
}
}
diff --git a/bitchat/Services/NoiseEncryptionService.swift b/bitchat/Services/NoiseEncryptionService.swift
index 11046a55..9bd015f7 100644
--- a/bitchat/Services/NoiseEncryptionService.swift
+++ b/bitchat/Services/NoiseEncryptionService.swift
@@ -115,15 +115,60 @@ enum EncryptionStatus: Equatable {
var description: String {
switch self {
case .none:
- return "Encryption failed"
+ return L10n.string(
+ "encryption.status.failed",
+ comment: "Status text when encryption failed"
+ )
case .noHandshake:
- return "Not encrypted"
+ return L10n.string(
+ "encryption.status.not_encrypted",
+ comment: "Status text when no encryption handshake happened"
+ )
case .noiseHandshaking:
- return "Establishing encryption..."
+ return L10n.string(
+ "encryption.status.establishing",
+ comment: "Status text when encryption is being established"
+ )
case .noiseSecured:
- return "Encrypted"
+ return L10n.string(
+ "encryption.status.secured",
+ comment: "Status text when encryption is secured but not verified"
+ )
case .noiseVerified:
- return "Encrypted & Verified"
+ return L10n.string(
+ "encryption.status.verified",
+ comment: "Status text when encryption is verified"
+ )
+ }
+ }
+
+ var accessibilityDescription: String {
+ switch self {
+ case .none:
+ return L10n.string(
+ "encryption.accessibility.failed",
+ comment: "Accessibility text when encryption failed"
+ )
+ case .noHandshake:
+ return L10n.string(
+ "encryption.accessibility.not_encrypted",
+ comment: "Accessibility text when encryption is not established"
+ )
+ case .noiseHandshaking:
+ return L10n.string(
+ "encryption.accessibility.establishing",
+ comment: "Accessibility text when encryption is being established"
+ )
+ case .noiseSecured:
+ return L10n.string(
+ "encryption.accessibility.secured",
+ comment: "Accessibility text when encryption is secured"
+ )
+ case .noiseVerified:
+ return L10n.string(
+ "encryption.accessibility.verified",
+ comment: "Accessibility text when encryption is verified"
+ )
}
}
}
diff --git a/bitchat/Utils/Localization.swift b/bitchat/Utils/Localization.swift
new file mode 100644
index 00000000..d1f71111
--- /dev/null
+++ b/bitchat/Utils/Localization.swift
@@ -0,0 +1,12 @@
+import Foundation
+
+enum L10n {
+ static func string(_ key: String, comment: String) -> String {
+ NSLocalizedString(key, comment: comment)
+ }
+
+ static func format(_ key: String, comment: String, _ args: CVarArg...) -> String {
+ let format = NSLocalizedString(key, comment: comment)
+ return String(format: format, locale: Locale.current, arguments: args)
+ }
+}
diff --git a/bitchat/ViewModels/ChatViewModel.swift b/bitchat/ViewModels/ChatViewModel.swift
index 732a2d91..f149910d 100644
--- a/bitchat/ViewModels/ChatViewModel.swift
+++ b/bitchat/ViewModels/ChatViewModel.swift
@@ -553,12 +553,22 @@ final class ChatViewModel: ObservableObject, BitchatDelegate {
// Announce Tor status (geohash-only; do not show in mesh chat). Only when auto-start is allowed.
if TorManager.shared.torEnforced && !torStatusAnnounced && TorManager.shared.isAutoStartAllowed() {
torStatusAnnounced = true
- addGeohashOnlySystemMessage("starting tor...")
+ addGeohashOnlySystemMessage(
+ L10n.string(
+ "system.tor.starting",
+ comment: "System message when Tor is starting"
+ )
+ )
// Suppress incremental Tor progress messages
torProgressCancellable = nil
} else if !TorManager.shared.torEnforced && !torStatusAnnounced {
torStatusAnnounced = true
- addGeohashOnlySystemMessage("development build: tor bypass enabled.")
+ addGeohashOnlySystemMessage(
+ L10n.string(
+ "system.tor.dev_bypass",
+ comment: "System message when Tor bypass is enabled in development"
+ )
+ )
}
// Initialize Nostr relay manager regardless of Tor readiness; connection is controlled elsewhere
@@ -855,7 +865,12 @@ final class ChatViewModel: ObservableObject, BitchatDelegate {
if !self.torStatusAnnounced && TorManager.shared.torEnforced {
self.torStatusAnnounced = true
// Post only in geohash channels (queue if not active)
- self.addGeohashOnlySystemMessage("starting tor...")
+ self.addGeohashOnlySystemMessage(
+ L10n.string(
+ "system.tor.starting",
+ comment: "System message when Tor is starting"
+ )
+ )
}
}
}
@@ -863,7 +878,12 @@ final class ChatViewModel: ObservableObject, BitchatDelegate {
Task { @MainActor in
self.torRestartPending = true
// Post only in geohash channels (queue if not active)
- self.addGeohashOnlySystemMessage("tor restarting to recover connectivity...")
+ self.addGeohashOnlySystemMessage(
+ L10n.string(
+ "system.tor.restarting",
+ comment: "System message when Tor is restarting"
+ )
+ )
}
}
@@ -872,11 +892,21 @@ final class ChatViewModel: ObservableObject, BitchatDelegate {
// Only announce "restarted" if we actually restarted this session
if self.torRestartPending {
// Post only in geohash channels (queue if not active)
- self.addGeohashOnlySystemMessage("tor restarted. network routing restored.")
+ self.addGeohashOnlySystemMessage(
+ L10n.string(
+ "system.tor.restarted",
+ comment: "System message when Tor has restarted"
+ )
+ )
self.torRestartPending = false
} else if TorManager.shared.torEnforced && !self.torInitialReadyAnnounced {
// Initial start completed
- self.addGeohashOnlySystemMessage("tor started. routing all chats via tor for privacy.")
+ self.addGeohashOnlySystemMessage(
+ L10n.string(
+ "system.tor.started",
+ comment: "System message when Tor has started"
+ )
+ )
self.torInitialReadyAnnounced = true
}
}
@@ -1557,7 +1587,12 @@ final class ChatViewModel: ObservableObject, BitchatDelegate {
}
} catch {
SecureLogger.error("❌ Failed to send geohash message: \(error)", category: .session)
- addSystemMessage("failed to send to location channel")
+ addSystemMessage(
+ L10n.string(
+ "system.location.send_failed",
+ comment: "System message when a location channel send fails"
+ )
+ )
}
}
@@ -2041,12 +2076,24 @@ final class ChatViewModel: ObservableObject, BitchatDelegate {
// Remove mapping keys pointing to this pubkey to avoid accidental resolution
for (k, v) in nostrKeyMapping where v.lowercased() == hex { nostrKeyMapping.removeValue(forKey: k) }
- addSystemMessage("blocked \(displayName) in geohash chats")
+ addSystemMessage(
+ L10n.format(
+ "system.geohash.blocked",
+ comment: "System message shown when a user is blocked in geohash chats",
+ displayName
+ )
+ )
}
@MainActor
func unblockGeohashUser(pubkeyHexLowercased: String, displayName: String) {
identityManager.setNostrBlocked(pubkeyHexLowercased, isBlocked: false)
- addSystemMessage("unblocked \(displayName) in geohash chats")
+ addSystemMessage(
+ L10n.format(
+ "system.geohash.unblocked",
+ comment: "System message shown when a user is unblocked in geohash chats",
+ displayName
+ )
+ )
}
/// Begin sampling multiple geohashes (used by channel sheet) without changing active channel.
@@ -2230,7 +2277,13 @@ final class ChatViewModel: ObservableObject, BitchatDelegate {
// Check if blocked
if unifiedPeerService.isBlocked(peerID) {
let nickname = meshService.peerNickname(peerID: peerID) ?? "user"
- addSystemMessage("cannot send message to \(nickname): user is blocked.")
+ addSystemMessage(
+ L10n.format(
+ "system.dm.blocked_recipient",
+ comment: "System message when attempting to message a blocked user",
+ nickname
+ )
+ )
return
}
@@ -2292,15 +2345,31 @@ final class ChatViewModel: ObservableObject, BitchatDelegate {
} else {
// Update delivery status to failed
if let index = privateChats[peerID]?.firstIndex(where: { $0.id == messageID }) {
- privateChats[peerID]?[index].deliveryStatus = .failed(reason: "Peer not reachable")
+ privateChats[peerID]?[index].deliveryStatus = .failed(
+ reason: L10n.string(
+ "content.delivery.reason.unreachable",
+ comment: "Failure reason when a peer is unreachable"
+ )
+ )
}
- addSystemMessage("Cannot send message to \(recipientNickname ?? "user") - peer is not reachable via mesh or Nostr.")
+ addSystemMessage(
+ L10n.format(
+ "system.dm.unreachable",
+ comment: "System message when a recipient is unreachable",
+ recipientNickname ?? L10n.string("system.common.user", comment: "Fallback recipient name")
+ )
+ )
}
}
private func sendGeohashDM(_ content: String, to peerID: String) {
guard case .location(let ch) = activeChannel else {
- addSystemMessage("cannot send: not in a location channel")
+ addSystemMessage(
+ L10n.string(
+ "system.location.not_in_channel",
+ comment: "System message when attempting to send without being in a location channel"
+ )
+ )
return
}
let messageID = UUID().uuidString
@@ -2329,7 +2398,12 @@ final class ChatViewModel: ObservableObject, BitchatDelegate {
// Resolve recipient hex from mapping
guard let recipientHex = nostrKeyMapping[peerID] else {
if let msgIdx = privateChats[peerID]?.firstIndex(where: { $0.id == messageID }) {
- privateChats[peerID]?[msgIdx].deliveryStatus = .failed(reason: "unknown recipient")
+ privateChats[peerID]?[msgIdx].deliveryStatus = .failed(
+ reason: L10n.string(
+ "content.delivery.reason.unknown_recipient",
+ comment: "Failure reason when the recipient is unknown"
+ )
+ )
}
return
}
@@ -2337,9 +2411,19 @@ final class ChatViewModel: ObservableObject, BitchatDelegate {
// Respect geohash blocks
if identityManager.isNostrBlocked(pubkeyHexLowercased: recipientHex) {
if let msgIdx = privateChats[peerID]?.firstIndex(where: { $0.id == messageID }) {
- privateChats[peerID]?[msgIdx].deliveryStatus = .failed(reason: "user is blocked")
+ privateChats[peerID]?[msgIdx].deliveryStatus = .failed(
+ reason: L10n.string(
+ "content.delivery.reason.blocked",
+ comment: "Failure reason when the user is blocked"
+ )
+ )
}
- addSystemMessage("cannot send message: user is blocked.")
+ addSystemMessage(
+ L10n.string(
+ "system.dm.blocked_generic",
+ comment: "System message when sending fails because user is blocked"
+ )
+ )
return
}
@@ -2349,8 +2433,13 @@ final class ChatViewModel: ObservableObject, BitchatDelegate {
// Prevent messaging ourselves
if recipientHex.lowercased() == id.publicKeyHex.lowercased() {
if let idx = privateChats[peerID]?.firstIndex(where: { $0.id == messageID }) {
- privateChats[peerID]?[idx].deliveryStatus = .failed(reason: "cannot message yourself")
- }
+ privateChats[peerID]?[idx].deliveryStatus = .failed(
+ reason: L10n.string(
+ "content.delivery.reason.self",
+ comment: "Failure reason when attempting to message yourself"
+ )
+ )
+ }
return
}
SecureLogger.debug("GeoDM: local send mid=\(messageID.prefix(8))… to=\(recipientHex.prefix(8))… conv=\(peerID)", category: .session)
@@ -2362,7 +2451,12 @@ final class ChatViewModel: ObservableObject, BitchatDelegate {
}
} catch {
if let idx = privateChats[peerID]?.firstIndex(where: { $0.id == messageID }) {
- privateChats[peerID]?[idx].deliveryStatus = .failed(reason: "send error")
+ privateChats[peerID]?[idx].deliveryStatus = .failed(
+ reason: L10n.string(
+ "content.delivery.reason.send_error",
+ comment: "Failure reason for a generic send error"
+ )
+ )
}
}
}
@@ -2421,13 +2515,22 @@ final class ChatViewModel: ObservableObject, BitchatDelegate {
switch state {
case .poweredOff:
- bluetoothAlertMessage = "Bluetooth is turned off. Please turn on Bluetooth in Settings to use BitChat."
+ bluetoothAlertMessage = L10n.string(
+ "content.alert.bluetooth_required.off",
+ comment: "Message shown when Bluetooth is turned off"
+ )
showBluetoothAlert = true
case .unauthorized:
- bluetoothAlertMessage = "BitChat needs Bluetooth permission to connect with nearby devices. Please enable Bluetooth access in Settings."
+ bluetoothAlertMessage = L10n.string(
+ "content.alert.bluetooth_required.permission",
+ comment: "Message shown when Bluetooth permission is missing"
+ )
showBluetoothAlert = true
case .unsupported:
- bluetoothAlertMessage = "This device does not support Bluetooth. BitChat requires Bluetooth to function."
+ bluetoothAlertMessage = L10n.string(
+ "content.alert.bluetooth_required.unsupported",
+ comment: "Message shown when the device lacks Bluetooth support"
+ )
showBluetoothAlert = true
case .poweredOn:
// Hide alert when Bluetooth is powered on
@@ -2457,14 +2560,26 @@ final class ChatViewModel: ObservableObject, BitchatDelegate {
// Check if the peer is blocked
if unifiedPeerService.isBlocked(peerID) {
- addSystemMessage("cannot start chat with \(peerNickname): user is blocked.")
+ addSystemMessage(
+ L10n.format(
+ "system.chat.blocked",
+ comment: "System message when starting chat fails because peer is blocked",
+ peerNickname
+ )
+ )
return
}
// Check mutual favorites for offline messaging
if let peer = unifiedPeerService.getPeer(by: peerID),
peer.isFavorite && !peer.theyFavoritedUs && !peer.isConnected {
- addSystemMessage("cannot start chat with \(peerNickname): mutual favorite required for offline messaging.")
+ addSystemMessage(
+ L10n.format(
+ "system.chat.requires_favorite",
+ comment: "System message when mutual favorite requirement blocks chat",
+ peerNickname
+ )
+ )
return
}
@@ -2918,7 +3033,12 @@ final class ChatViewModel: ObservableObject, BitchatDelegate {
self.recordGeoParticipant(pubkeyHex: identity.publicKeyHex)
} catch {
SecureLogger.error("❌ Failed to send geohash screenshot message: \(error)", category: .session)
- self.addSystemMessage("failed to send to location channel")
+ self.addSystemMessage(
+ L10n.string(
+ "system.location.send_failed",
+ comment: "System message when a location channel send fails"
+ )
+ )
}
}
}
diff --git a/bitchat/Views/AppInfoView.swift b/bitchat/Views/AppInfoView.swift
index 33ef3d3e..6bb42f55 100644
--- a/bitchat/Views/AppInfoView.swift
+++ b/bitchat/Views/AppInfoView.swift
@@ -18,41 +18,77 @@ struct AppInfoView: View {
// MARK: - Constants
private enum Strings {
- static let appName = "bitchat"
- static let tagline = "sidegroupchat"
-
+ static let appName: LocalizedStringKey = "app_info.app_name"
+ static let tagline: LocalizedStringKey = "app_info.tagline"
+
enum Features {
- static let title = "FEATURES"
- static let offlineComm = ("wifi.slash", "offline communication", "works without internet using Bluetooth low energy")
- static let encryption = ("lock.shield", "end-to-end encryption", "private messages encrypted with noise protocol")
- static let extendedRange = ("antenna.radiowaves.left.and.right", "extended range", "messages relay through peers, going the distance")
- static let mentions = ("at", "mentions", "use @nickname to notify specific people")
- static let favorites = ("star.fill", "favorites", "get notified when your favorite people join")
- static let geohash = ("number", "local channels", "geohash channels to chat with people in nearby regions over decentralized anonymous relays")
+ static let title: LocalizedStringKey = "app_info.features.title"
+ static let offlineComm = AppInfoFeatureInfo(
+ icon: "wifi.slash",
+ title: "app_info.features.offline.title",
+ description: "app_info.features.offline.description"
+ )
+ static let encryption = AppInfoFeatureInfo(
+ icon: "lock.shield",
+ title: "app_info.features.encryption.title",
+ description: "app_info.features.encryption.description"
+ )
+ static let extendedRange = AppInfoFeatureInfo(
+ icon: "antenna.radiowaves.left.and.right",
+ title: "app_info.features.extended_range.title",
+ description: "app_info.features.extended_range.description"
+ )
+ static let mentions = AppInfoFeatureInfo(
+ icon: "at",
+ title: "app_info.features.mentions.title",
+ description: "app_info.features.mentions.description"
+ )
+ static let favorites = AppInfoFeatureInfo(
+ icon: "star.fill",
+ title: "app_info.features.favorites.title",
+ description: "app_info.features.favorites.description"
+ )
+ static let geohash = AppInfoFeatureInfo(
+ icon: "number",
+ title: "app_info.features.geohash.title",
+ description: "app_info.features.geohash.description"
+ )
}
-
+
enum Privacy {
- static let title = "PRIVACY"
- static let noTracking = ("eye.slash", "no tracking", "no servers, accounts, or data collection")
- static let ephemeral = ("shuffle", "ephemeral identity", "new peer ID generated regularly")
- static let panic = ("hand.raised.fill", "panic mode", "triple-tap logo to instantly clear all data")
+ static let title: LocalizedStringKey = "app_info.privacy.title"
+ static let noTracking = AppInfoFeatureInfo(
+ icon: "eye.slash",
+ title: "app_info.privacy.no_tracking.title",
+ description: "app_info.privacy.no_tracking.description"
+ )
+ static let ephemeral = AppInfoFeatureInfo(
+ icon: "shuffle",
+ title: "app_info.privacy.ephemeral.title",
+ description: "app_info.privacy.ephemeral.description"
+ )
+ static let panic = AppInfoFeatureInfo(
+ icon: "hand.raised.fill",
+ title: "app_info.privacy.panic.title",
+ description: "app_info.privacy.panic.description"
+ )
}
-
+
enum HowToUse {
- static let title = "HOW TO USE"
- static let instructions = [
- "• set your nickname by tapping it",
- "• tap #mesh to change channels",
- "• tap people icon for sidebar",
- "• tap a peer's name to start a DM",
- "• triple-tap chat to clear",
- "• type / for commands"
+ static let title: LocalizedStringKey = "app_info.how_to_use.title"
+ static let instructions: [LocalizedStringKey] = [
+ "app_info.how_to_use.set_nickname",
+ "app_info.how_to_use.change_channels",
+ "app_info.how_to_use.open_sidebar",
+ "app_info.how_to_use.start_dm",
+ "app_info.how_to_use.clear_chat",
+ "app_info.how_to_use.commands"
]
}
-
+
enum Warning {
- static let title = "WARNING"
- static let message = "private message security has not yet been fully audited. do not use for critical situations until this warning disappears."
+ static let title: LocalizedStringKey = "app_info.warning.title"
+ static let message: LocalizedStringKey = "app_info.warning.message"
}
}
@@ -62,7 +98,7 @@ struct AppInfoView: View {
// Custom header for macOS
HStack {
Spacer()
- Button("DONE") {
+ Button("app_info.done") {
dismiss()
}
.buttonStyle(.plain)
@@ -93,7 +129,7 @@ struct AppInfoView: View {
.frame(width: 32, height: 32)
}
.buttonStyle(.plain)
- .accessibilityLabel("Close")
+ .accessibilityLabel("app_info.close")
}
}
}
@@ -121,7 +157,7 @@ struct AppInfoView: View {
SectionHeader(Strings.HowToUse.title)
VStack(alignment: .leading, spacing: 8) {
- ForEach(Strings.HowToUse.instructions, id: \.self) { instruction in
+ ForEach(Array(Strings.HowToUse.instructions.enumerated()), id: \.offset) { _, instruction in
Text(instruction)
}
}
@@ -132,49 +168,31 @@ struct AppInfoView: View {
// Features
VStack(alignment: .leading, spacing: 16) {
SectionHeader(Strings.Features.title)
-
- FeatureRow(icon: Strings.Features.offlineComm.0,
- title: Strings.Features.offlineComm.1,
- description: Strings.Features.offlineComm.2)
-
- FeatureRow(icon: Strings.Features.encryption.0,
- title: Strings.Features.encryption.1,
- description: Strings.Features.encryption.2)
-
- FeatureRow(icon: Strings.Features.extendedRange.0,
- title: Strings.Features.extendedRange.1,
- description: Strings.Features.extendedRange.2)
-
- FeatureRow(icon: Strings.Features.favorites.0,
- title: Strings.Features.favorites.1,
- description: Strings.Features.favorites.2)
-
- FeatureRow(icon: Strings.Features.geohash.0,
- title: Strings.Features.geohash.1,
- description: Strings.Features.geohash.2)
-
- FeatureRow(icon: Strings.Features.mentions.0,
- title: Strings.Features.mentions.1,
- description: Strings.Features.mentions.2)
+
+ FeatureRow(info: Strings.Features.offlineComm)
+
+ FeatureRow(info: Strings.Features.encryption)
+
+ FeatureRow(info: Strings.Features.extendedRange)
+
+ FeatureRow(info: Strings.Features.favorites)
+
+ FeatureRow(info: Strings.Features.geohash)
+
+ FeatureRow(info: Strings.Features.mentions)
}
-
+
// Privacy
VStack(alignment: .leading, spacing: 16) {
SectionHeader(Strings.Privacy.title)
-
- FeatureRow(icon: Strings.Privacy.noTracking.0,
- title: Strings.Privacy.noTracking.1,
- description: Strings.Privacy.noTracking.2)
-
- FeatureRow(icon: Strings.Privacy.ephemeral.0,
- title: Strings.Privacy.ephemeral.1,
- description: Strings.Privacy.ephemeral.2)
-
- FeatureRow(icon: Strings.Privacy.panic.0,
- title: Strings.Privacy.panic.1,
- description: Strings.Privacy.panic.2)
+
+ FeatureRow(info: Strings.Privacy.noTracking)
+
+ FeatureRow(info: Strings.Privacy.ephemeral)
+
+ FeatureRow(info: Strings.Privacy.panic)
}
-
+
// Warning
VStack(alignment: .leading, spacing: 6) {
SectionHeader(Strings.Warning.title)
@@ -197,15 +215,21 @@ struct AppInfoView: View {
}
}
+struct AppInfoFeatureInfo {
+ let icon: String
+ let title: LocalizedStringKey
+ let description: LocalizedStringKey
+}
+
struct SectionHeader: View {
- let title: String
+ let title: LocalizedStringKey
@Environment(\.colorScheme) var colorScheme
private var textColor: Color {
colorScheme == .dark ? Color.green : Color(red: 0, green: 0.5, blue: 0)
}
- init(_ title: String) {
+ init(_ title: LocalizedStringKey) {
self.title = title
}
@@ -218,9 +242,7 @@ struct SectionHeader: View {
}
struct FeatureRow: View {
- let icon: String
- let title: String
- let description: String
+ let info: AppInfoFeatureInfo
@Environment(\.colorScheme) var colorScheme
private var textColor: Color {
@@ -233,17 +255,17 @@ struct FeatureRow: View {
var body: some View {
HStack(alignment: .top, spacing: 12) {
- Image(systemName: icon)
+ Image(systemName: info.icon)
.font(.bitchatSystem(size: 20))
.foregroundColor(textColor)
.frame(width: 30)
VStack(alignment: .leading, spacing: 4) {
- Text(title)
+ Text(info.title)
.font(.bitchatSystem(size: 14, weight: .semibold, design: .monospaced))
.foregroundColor(textColor)
- Text(description)
+ Text(info.description)
.font(.bitchatSystem(size: 12, design: .monospaced))
.foregroundColor(secondaryTextColor)
.fixedSize(horizontal: false, vertical: true)
diff --git a/bitchat/Views/ContentView.swift b/bitchat/Views/ContentView.swift
index 4d6623e3..8ca1f3c5 100644
--- a/bitchat/Views/ContentView.swift
+++ b/bitchat/Views/ContentView.swift
@@ -190,11 +190,14 @@ struct ContentView: View {
}
}
.confirmationDialog(
- selectedMessageSender.map { "@\($0)" } ?? "Actions",
+ selectedMessageSender.map { "@\($0)" } ?? L10n.string(
+ "content.actions.title",
+ comment: "Fallback title for the message action sheet"
+ ),
isPresented: $showMessageActions,
titleVisibility: .visible
) {
- Button("mention") {
+ Button("content.actions.mention") {
if let sender = selectedMessageSender {
// Pre-fill the input with an @mention and focus the field
messageText = "@\(sender) "
@@ -202,7 +205,7 @@ struct ContentView: View {
}
}
- Button("direct message") {
+ Button("content.actions.direct_message") {
if let peerID = selectedMessageSenderID {
if peerID.hasPrefix("nostr:") {
if let full = viewModel.fullNostrHex(forSenderPeerID: peerID) {
@@ -217,20 +220,20 @@ struct ContentView: View {
}
}
}
-
- Button("hug") {
+
+ Button("content.actions.hug") {
if let sender = selectedMessageSender {
viewModel.sendMessage("/hug @\(sender)")
}
}
-
- Button("slap") {
+
+ Button("content.actions.slap") {
if let sender = selectedMessageSender {
viewModel.sendMessage("/slap @\(sender)")
}
}
-
- Button("BLOCK", role: .destructive) {
+
+ Button("content.actions.block", role: .destructive) {
// Prefer direct geohash block when we have a Nostr sender ID
if let peerID = selectedMessageSenderID, peerID.hasPrefix("nostr:"),
let full = viewModel.fullNostrHex(forSenderPeerID: peerID),
@@ -240,18 +243,18 @@ struct ContentView: View {
viewModel.sendMessage("/block \(sender)")
}
}
-
- Button("cancel", role: .cancel) {}
+
+ Button("common.cancel", role: .cancel) {}
}
- .alert("Bluetooth Required", isPresented: $viewModel.showBluetoothAlert) {
- Button("Settings") {
+ .alert("content.alert.bluetooth_required.title", isPresented: $viewModel.showBluetoothAlert) {
+ Button("content.alert.bluetooth_required.settings") {
#if os(iOS)
if let url = URL(string: UIApplication.openSettingsURLString) {
UIApplication.shared.open(url)
}
#endif
}
- Button("OK", role: .cancel) {}
+ Button("common.ok", role: .cancel) {}
} message: {
Text(viewModel.bluetoothAlertMessage)
}
@@ -332,7 +335,8 @@ struct ContentView: View {
// Expand/Collapse for very long messages
if (message.content.count > TransportConfig.uiLongMessageLengthThreshold || message.content.hasVeryLongToken(threshold: TransportConfig.uiVeryLongTokenThreshold)) && cashuTokens.isEmpty {
let isExpanded = expandedMessageIDs.contains(message.id)
- Button(isExpanded ? "show less" : "show more") {
+ let labelKey = isExpanded ? LocalizedStringKey("content.message.show_less") : LocalizedStringKey("content.message.show_more")
+ Button(labelKey) {
if isExpanded { expandedMessageIDs.remove(message.id) }
else { expandedMessageIDs.insert(message.id) }
}
@@ -348,7 +352,10 @@ struct ContentView: View {
let link = lightningLinks[i]
PaymentChipView(
emoji: "⚡",
- label: "pay via lightning",
+ label: L10n.string(
+ "content.payment.lightning",
+ comment: "Label for Lightning payment chip"
+ ),
colorScheme: colorScheme
) {
#if os(iOS)
@@ -364,7 +371,10 @@ struct ContentView: View {
let urlStr = "cashu:\(enc)"
PaymentChipView(
emoji: "🥜",
- label: "pay via cashu",
+ label: L10n.string(
+ "content.payment.cashu",
+ comment: "Label for Cashu payment chip"
+ ),
colorScheme: colorScheme
) {
#if os(iOS)
@@ -434,7 +444,7 @@ struct ContentView: View {
}
}
.contextMenu {
- Button("Copy message") {
+ Button("content.message.copy") {
#if os(iOS)
UIPasteboard.general.string = message.content
#else
@@ -808,7 +818,7 @@ struct ContentView: View {
}
HStack(alignment: .center, spacing: 4) {
- TextField("type a message...", text: $messageText)
+ TextField("content.input.message_placeholder", text: $messageText)
.textFieldStyle(.plain)
.font(.bitchatSystem(size: 14, design: .monospaced))
.foregroundColor(textColor)
@@ -835,18 +845,18 @@ struct ContentView: View {
}()
let isGeoDM: Bool = (viewModel.selectedPrivateChatPeer?.hasPrefix("nostr_") == true)
var commandDescriptions = [
- ("/block", "block or list blocked peers"),
- ("/clear", "clear chat messages"),
- ("/hug", "send someone a warm hug"),
- ("/m", "send private message"),
- ("/slap", "slap someone with a trout"),
- ("/unblock", "unblock a peer"),
- ("/w", "see who's online")
+ ("/block", L10n.string("content.commands.block", comment: "Description for /block command")),
+ ("/clear", L10n.string("content.commands.clear", comment: "Description for /clear command")),
+ ("/hug", L10n.string("content.commands.hug", comment: "Description for /hug command")),
+ ("/m", L10n.string("content.commands.message", comment: "Description for /m command")),
+ ("/slap", L10n.string("content.commands.slap", comment: "Description for /slap command")),
+ ("/unblock", L10n.string("content.commands.unblock", comment: "Description for /unblock command")),
+ ("/w", L10n.string("content.commands.who", comment: "Description for /w command"))
]
// Only show favorites commands when not in geohash context
if !(isGeoPublic || isGeoDM) {
- commandDescriptions.append(("/fav", "add to favorites"))
- commandDescriptions.append(("/unfav", "remove from favorites"))
+ commandDescriptions.append(("/fav", L10n.string("content.commands.favorite", comment: "Description for /fav command")))
+ commandDescriptions.append(("/unfav", L10n.string("content.commands.unfavorite", comment: "Description for /unfav command")))
}
let input = newValue.lowercased()
@@ -892,8 +902,23 @@ struct ContentView: View {
}
.buttonStyle(.plain)
.padding(.trailing, 12)
- .accessibilityLabel("Send message")
- .accessibilityHint(messageText.isEmpty ? "Enter a message to send" : "Double tap to send")
+ .accessibilityLabel(
+ L10n.string(
+ "content.accessibility.send_message",
+ comment: "Accessibility label for the send message button"
+ )
+ )
+ .accessibilityHint(
+ messageText.isEmpty
+ ? L10n.string(
+ "content.accessibility.send_hint_empty",
+ comment: "Hint prompting the user to enter a message"
+ )
+ : L10n.string(
+ "content.accessibility.send_hint_ready",
+ comment: "Hint prompting the user to send the message"
+ )
+ )
}
.padding(.vertical, 8)
.background(backgroundColor.opacity(0.95))
@@ -925,7 +950,7 @@ struct ContentView: View {
VStack(alignment: .leading, spacing: 0) {
// Header - match main toolbar height
HStack {
- Text("PEOPLE")
+ Text("content.header.people")
.font(.bitchatSystem(size: 16, weight: .bold, design: .monospaced))
.foregroundColor(textColor)
Spacer()
@@ -936,7 +961,12 @@ struct ContentView: View {
.font(.bitchatSystem(size: 14))
}
.buttonStyle(.plain)
- .help("Verification: show my QR or scan a friend")
+ .help(
+ L10n.string(
+ "content.help.verification",
+ comment: "Help text for verification button"
+ )
+ )
}
}
.frame(height: headerHeight) // Match header height
@@ -1106,7 +1136,7 @@ struct ContentView: View {
.font(.bitchatSystem(size: 14, design: .monospaced))
.foregroundColor(secondaryTextColor)
- TextField("nickname", text: $viewModel.nickname)
+ TextField("content.input.nickname_placeholder", text: $viewModel.nickname)
.textFieldStyle(.plain)
.font(.bitchatSystem(size: 14, design: .monospaced))
.frame(maxWidth: 80)
@@ -1151,7 +1181,12 @@ struct ContentView: View {
.foregroundColor(Color.orange)
}
.buttonStyle(.plain)
- .accessibilityLabel("Open unread private chat")
+ .accessibilityLabel(
+ L10n.string(
+ "content.accessibility.open_unread_private_chat",
+ comment: "Accessibility label for the unread private chat button"
+ )
+ )
}
// Notes icon (mesh only and when location is authorized), to the left of #mesh
if case .mesh = locationManager.selectedChannel, locationManager.permissionState == .authorized {
@@ -1174,7 +1209,12 @@ struct ContentView: View {
.fixedSize(horizontal: true, vertical: false)
}
.buttonStyle(.plain)
- .accessibilityLabel("Location notes for this place")
+ .accessibilityLabel(
+ L10n.string(
+ "content.accessibility.location_notes",
+ comment: "Accessibility label for location notes button"
+ )
+ )
}
// Bookmark toggle (geochats): to the left of #geohash
@@ -1184,7 +1224,13 @@ struct ContentView: View {
.font(.bitchatSystem(size: 12))
}
.buttonStyle(.plain)
- .accessibilityLabel("Toggle bookmark for #\(ch.geohash)")
+ .accessibilityLabel(
+ L10n.format(
+ "content.accessibility.toggle_bookmark",
+ comment: "Accessibility label for toggling a geohash bookmark",
+ ch.geohash
+ )
+ )
}
// Location channels button '#'
@@ -1209,7 +1255,12 @@ struct ContentView: View {
.lineLimit(headerLineLimit)
.fixedSize(horizontal: true, vertical: false)
.layoutPriority(2)
- .accessibilityLabel("location channels")
+ .accessibilityLabel(
+ L10n.string(
+ "content.accessibility.location_channels",
+ comment: "Accessibility label for the location channels button"
+ )
+ )
}
.buttonStyle(.plain)
.padding(.leading, 4)
@@ -1219,7 +1270,15 @@ struct ContentView: View {
// People icon with count
Image(systemName: "person.2.fill")
.font(.system(size: headerPeerIconSize, weight: .regular))
- .accessibilityLabel("\(headerOtherPeersCount) people")
+ .accessibilityLabel(
+ NSString.localizedStringWithFormat(
+ NSLocalizedString(
+ "content.accessibility.people_count",
+ comment: "Accessibility label announcing number of people in header"
+ ) as NSString,
+ headerOtherPeersCount
+ ) as String
+ )
Text("\(headerOtherPeersCount)")
.font(.system(size: headerPeerCountFontSize, weight: .regular, design: .monospaced))
.accessibilityHidden(true)
@@ -1258,7 +1317,7 @@ struct ContentView: View {
} else {
VStack(spacing: 12) {
HStack {
- Text("notes")
+ Text("content.notes.title")
.font(.bitchatSystem(size: 16, weight: .bold, design: .monospaced))
Spacer()
Button(action: { showLocationNotes = false }) {
@@ -1268,15 +1327,18 @@ struct ContentView: View {
.frame(width: 32, height: 32)
}
.buttonStyle(.plain)
- .accessibilityLabel("Close")
+ .accessibilityLabel(L10n.string(
+ "common.close",
+ comment: "Accessibility label for close buttons"
+ ))
}
.frame(height: headerHeight)
.padding(.horizontal, 12)
.background(backgroundColor.opacity(0.95))
- Text("location unavailable")
+ Text("content.notes.location_unavailable")
.font(.bitchatSystem(size: 14, design: .monospaced))
.foregroundColor(secondaryTextColor)
- Button("enable location") {
+ Button("content.location.enable") {
LocationChannelManager.shared.enableLocationChannels()
LocationChannelManager.shared.refreshChannels()
}
@@ -1334,10 +1396,10 @@ struct ContentView: View {
LocationChannelManager.shared.refreshChannels()
}
}
- .alert("heads up", isPresented: $viewModel.showScreenshotPrivacyWarning) {
- Button("ok", role: .cancel) {}
+ .alert("content.alert.screenshot.title", isPresented: $viewModel.showScreenshotPrivacyWarning) {
+ Button("common.ok", role: .cancel) {}
} message: {
- Text("screenshots of location channels will reveal your location. think before sharing publicly.")
+ Text("content.alert.screenshot.message")
}
.background(backgroundColor.opacity(0.95))
}
@@ -1393,7 +1455,10 @@ struct ContentView: View {
if !social.claimedNickname.isEmpty { return social.claimedNickname }
}
}
- return "Unknown"
+ return L10n.string(
+ "common.unknown",
+ comment: "Fallback label for unknown peer"
+ )
}()
let isNostrAvailable: Bool = {
guard let connectionState = peer?.connectionState else {
@@ -1427,19 +1492,34 @@ struct ContentView: View {
Image(systemName: "dot.radiowaves.left.and.right")
.font(.bitchatSystem(size: 14))
.foregroundColor(textColor)
- .accessibilityLabel("Connected via mesh")
+ .accessibilityLabel(
+ L10n.string(
+ "content.accessibility.connected_mesh",
+ comment: "Accessibility label for mesh-connected peer indicator"
+ )
+ )
case .meshReachable:
// point.3 filled icon for reachable via mesh (not directly connected)
Image(systemName: "point.3.filled.connected.trianglepath.dotted")
.font(.bitchatSystem(size: 14))
.foregroundColor(textColor)
- .accessibilityLabel("Reachable via mesh")
+ .accessibilityLabel(
+ L10n.string(
+ "content.accessibility.reachable_mesh",
+ comment: "Accessibility label for mesh-reachable peer indicator"
+ )
+ )
case .nostrAvailable:
// Purple globe for Nostr
Image(systemName: "globe")
.font(.bitchatSystem(size: 14))
.foregroundColor(.purple)
- .accessibilityLabel("Available via Nostr")
+ .accessibilityLabel(
+ L10n.string(
+ "content.accessibility.available_nostr",
+ comment: "Accessibility label for Nostr-available peer indicator"
+ )
+ )
case .offline:
// Should not happen for PM header, but handle gracefully
EmptyView()
@@ -1449,19 +1529,34 @@ struct ContentView: View {
Image(systemName: "point.3.filled.connected.trianglepath.dotted")
.font(.bitchatSystem(size: 14))
.foregroundColor(textColor)
- .accessibilityLabel("Reachable via mesh")
+ .accessibilityLabel(
+ L10n.string(
+ "content.accessibility.reachable_mesh",
+ comment: "Accessibility label for mesh-reachable peer indicator"
+ )
+ )
} else if isNostrAvailable {
// Fallback to Nostr if peer not in list but is mutual favorite
Image(systemName: "globe")
.font(.bitchatSystem(size: 14))
.foregroundColor(.purple)
- .accessibilityLabel("Available via Nostr")
+ .accessibilityLabel(
+ L10n.string(
+ "content.accessibility.available_nostr",
+ comment: "Accessibility label for Nostr-available peer indicator"
+ )
+ )
} else if viewModel.meshService.isPeerConnected(headerPeerID) || viewModel.connectedPeers.contains(headerPeerID) {
// Fallback: if peer lookup is missing but mesh reports connected, show radio
Image(systemName: "dot.radiowaves.left.and.right")
.font(.bitchatSystem(size: 14))
.foregroundColor(textColor)
- .accessibilityLabel("Connected via mesh")
+ .accessibilityLabel(
+ L10n.string(
+ "content.accessibility.connected_mesh",
+ comment: "Accessibility label for mesh-connected peer indicator"
+ )
+ )
}
Text("\(privatePeerNick)")
@@ -1482,12 +1577,29 @@ struct ContentView: View {
.foregroundColor(encryptionStatus == .noiseVerified ? textColor :
encryptionStatus == .noiseSecured ? textColor :
Color.red)
- .accessibilityLabel("Encryption status: \(encryptionStatus == .noiseVerified ? "verified" : encryptionStatus == .noiseSecured ? "secured" : "not encrypted")")
+ .accessibilityLabel(
+ L10n.format(
+ "content.accessibility.encryption_status",
+ comment: "Accessibility label announcing encryption status",
+ encryptionStatus.accessibilityDescription
+ )
+ )
}
}
}
- .accessibilityLabel("Private chat with \(privatePeerNick)")
- .accessibilityHint("Tap to view encryption fingerprint")
+ .accessibilityLabel(
+ L10n.format(
+ "content.accessibility.private_chat_header",
+ comment: "Accessibility label describing the private chat header",
+ privatePeerNick
+ )
+ )
+ .accessibilityHint(
+ L10n.string(
+ "content.accessibility.view_fingerprint_hint",
+ comment: "Accessibility hint for viewing encryption fingerprint"
+ )
+ )
}
.buttonStyle(.plain)
@@ -1506,7 +1618,12 @@ struct ContentView: View {
.contentShape(Rectangle())
}
.buttonStyle(.plain)
- .accessibilityLabel("Back to main chat")
+ .accessibilityLabel(
+ L10n.string(
+ "content.accessibility.back_to_main_chat",
+ comment: "Accessibility label for returning to main chat"
+ )
+ )
Spacer()
@@ -1520,8 +1637,23 @@ struct ContentView: View {
.foregroundColor(viewModel.isFavorite(peerID: headerPeerID) ? Color.yellow : textColor)
}
.buttonStyle(.plain)
- .accessibilityLabel(viewModel.isFavorite(peerID: privatePeerID) ? "Remove from favorites" : "Add to favorites")
- .accessibilityHint("Double tap to toggle favorite status")
+ .accessibilityLabel(
+ viewModel.isFavorite(peerID: privatePeerID)
+ ? L10n.string(
+ "content.accessibility.remove_favorite",
+ comment: "Accessibility label to remove a favorite"
+ )
+ : L10n.string(
+ "content.accessibility.add_favorite",
+ comment: "Accessibility label to add a favorite"
+ )
+ )
+ .accessibilityHint(
+ L10n.string(
+ "content.accessibility.toggle_favorite_hint",
+ comment: "Accessibility hint for toggling favorite status"
+ )
+ )
}
}
}
@@ -1616,6 +1748,41 @@ struct DeliveryStatusView: View {
private var secondaryTextColor: Color {
colorScheme == .dark ? Color.green.opacity(0.8) : Color(red: 0, green: 0.5, blue: 0).opacity(0.8)
}
+
+ private enum Strings {
+ static func delivered(to nickname: String) -> String {
+ L10n.format(
+ "content.delivery.delivered_to",
+ comment: "Tooltip for delivered private messages",
+ nickname
+ )
+ }
+
+ static func read(by nickname: String) -> String {
+ L10n.format(
+ "content.delivery.read_by",
+ comment: "Tooltip for read private messages",
+ nickname
+ )
+ }
+
+ static func failed(_ reason: String) -> String {
+ L10n.format(
+ "content.delivery.failed",
+ comment: "Tooltip for failed message delivery",
+ reason
+ )
+ }
+
+ static func deliveredToMembers(_ reached: Int, _ total: Int) -> String {
+ L10n.format(
+ "content.delivery.delivered_members",
+ comment: "Tooltip for partially delivered messages",
+ reached,
+ total
+ )
+ }
+ }
// MARK: - Body
@@ -1639,7 +1806,7 @@ struct DeliveryStatusView: View {
.font(.bitchatSystem(size: 10))
}
.foregroundColor(textColor.opacity(0.8))
- .help("Delivered to \(nickname)")
+ .help(Strings.delivered(to: nickname))
case .read(let nickname, _):
HStack(spacing: -2) {
@@ -1649,13 +1816,13 @@ struct DeliveryStatusView: View {
.font(.bitchatSystem(size: 10, weight: .bold))
}
.foregroundColor(Color(red: 0.0, green: 0.478, blue: 1.0)) // Bright blue
- .help("Read by \(nickname)")
+ .help(Strings.read(by: nickname))
case .failed(let reason):
Image(systemName: "exclamationmark.triangle")
.font(.bitchatSystem(size: 10))
.foregroundColor(Color.red.opacity(0.8))
- .help("Failed: \(reason)")
+ .help(Strings.failed(reason))
case .partiallyDelivered(let reached, let total):
HStack(spacing: 1) {
@@ -1665,7 +1832,7 @@ struct DeliveryStatusView: View {
.font(.bitchatSystem(size: 10, design: .monospaced))
}
.foregroundColor(secondaryTextColor.opacity(0.6))
- .help("Delivered to \(reached) of \(total) members")
+ .help(Strings.deliveredToMembers(reached, total))
}
}
}
diff --git a/bitchat/Views/FingerprintView.swift b/bitchat/Views/FingerprintView.swift
index 4d600dff..3aed8fb8 100644
--- a/bitchat/Views/FingerprintView.swift
+++ b/bitchat/Views/FingerprintView.swift
@@ -21,12 +21,38 @@ struct FingerprintView: View {
private var backgroundColor: Color {
colorScheme == .dark ? Color.black : Color.white
}
+
+ private enum Strings {
+ static let title: LocalizedStringKey = "fingerprint.title"
+ static let theirFingerprint: LocalizedStringKey = "fingerprint.their_label"
+ static let handshakePending: LocalizedStringKey = "fingerprint.handshake_pending"
+ static let yourFingerprint: LocalizedStringKey = "fingerprint.your_label"
+ static let copy: LocalizedStringKey = "common.copy"
+ static let verifiedBadge: LocalizedStringKey = "fingerprint.badge.verified"
+ static let notVerifiedBadge: LocalizedStringKey = "fingerprint.badge.not_verified"
+ static let verifiedMessage: LocalizedStringKey = "fingerprint.message.verified"
+ static func verifyHint(_ nickname: String) -> String {
+ L10n.format(
+ "fingerprint.message.verify_hint",
+ comment: "Instruction to compare fingerprints with a named peer",
+ nickname
+ )
+ }
+ static let markVerified: LocalizedStringKey = "fingerprint.action.mark_verified"
+ static let removeVerification: LocalizedStringKey = "fingerprint.action.remove_verification"
+ static func unknownPeer() -> String {
+ L10n.string(
+ "common.unknown",
+ comment: "Label for an unknown peer"
+ )
+ }
+ }
var body: some View {
VStack(spacing: 20) {
// Header
HStack {
- Text("SECURITY VERIFICATION")
+ Text(Strings.title)
.font(.bitchatSystem(size: 16, weight: .bold, design: .monospaced))
.foregroundColor(textColor)
@@ -58,7 +84,7 @@ struct FingerprintView: View {
if !social.claimedNickname.isEmpty { return social.claimedNickname }
}
}
- return "Unknown"
+ return Strings.unknownPeer()
}()
// Accurate encryption state based on short ID session
let encryptionStatus = viewModel.getEncryptionStatus(for: statusPeerID)
@@ -88,7 +114,7 @@ struct FingerprintView: View {
// Their fingerprint
VStack(alignment: .leading, spacing: 8) {
- Text("THEIR FINGERPRINT:")
+ Text(Strings.theirFingerprint)
.font(.bitchatSystem(size: 12, weight: .bold, design: .monospaced))
.foregroundColor(textColor.opacity(0.7))
@@ -104,7 +130,7 @@ struct FingerprintView: View {
.background(Color.gray.opacity(0.1))
.cornerRadius(8)
.contextMenu {
- Button("Copy") {
+ Button(Strings.copy) {
#if os(iOS)
UIPasteboard.general.string = fingerprint
#else
@@ -114,16 +140,16 @@ struct FingerprintView: View {
}
}
} else {
- Text("not available - handshake in progress")
+ Text(Strings.handshakePending)
.font(.bitchatSystem(size: 14, design: .monospaced))
.foregroundColor(Color.orange)
.padding()
}
}
-
+
// My fingerprint
VStack(alignment: .leading, spacing: 8) {
- Text("YOUR FINGERPRINT:")
+ Text(Strings.yourFingerprint)
.font(.bitchatSystem(size: 12, weight: .bold, design: .monospaced))
.foregroundColor(textColor.opacity(0.7))
@@ -139,7 +165,7 @@ struct FingerprintView: View {
.background(Color.gray.opacity(0.1))
.cornerRadius(8)
.contextMenu {
- Button("Copy") {
+ Button(Strings.copy) {
#if os(iOS)
UIPasteboard.general.string = myFingerprint
#else
@@ -155,14 +181,18 @@ struct FingerprintView: View {
let isVerified = encryptionStatus == .noiseVerified
VStack(spacing: 12) {
- Text(isVerified ? "✓ VERIFIED" : "⚠️ NOT VERIFIED")
+ Text(isVerified ? Strings.verifiedBadge : Strings.notVerifiedBadge)
.font(.bitchatSystem(size: 14, weight: .bold, design: .monospaced))
.foregroundColor(isVerified ? Color.green : Color.orange)
.frame(maxWidth: .infinity)
- Text(isVerified ?
- "you have verified this person's identity." :
- "compare these fingerprints with \(peerNickname) using a secure channel.")
+ Group {
+ if isVerified {
+ Text(Strings.verifiedMessage)
+ } else {
+ Text(Strings.verifyHint(peerNickname))
+ }
+ }
.font(.bitchatSystem(size: 12, design: .monospaced))
.foregroundColor(textColor.opacity(0.7))
.multilineTextAlignment(.center)
@@ -175,7 +205,7 @@ struct FingerprintView: View {
viewModel.verifyFingerprint(for: peerID)
dismiss()
}) {
- Text("MARK AS VERIFIED")
+ Text(Strings.markVerified)
.font(.bitchatSystem(size: 14, weight: .bold, design: .monospaced))
.foregroundColor(.white)
.padding(.horizontal, 20)
@@ -189,7 +219,7 @@ struct FingerprintView: View {
viewModel.unverifyFingerprint(for: peerID)
dismiss()
}) {
- Text("REMOVE VERIFICATION")
+ Text(Strings.removeVerification)
.font(.bitchatSystem(size: 14, weight: .bold, design: .monospaced))
.foregroundColor(.white)
.padding(.horizontal, 20)
diff --git a/bitchat/Views/GeohashPeopleList.swift b/bitchat/Views/GeohashPeopleList.swift
index cfd9f1fa..db7c3aa8 100644
--- a/bitchat/Views/GeohashPeopleList.swift
+++ b/bitchat/Views/GeohashPeopleList.swift
@@ -8,10 +8,21 @@ struct GeohashPeopleList: View {
@Environment(\.colorScheme) var colorScheme
@State private var orderedIDs: [String] = []
+ private enum Strings {
+ static let noneNearby: LocalizedStringKey = "geohash_people.none_nearby"
+ static let youSuffix: LocalizedStringKey = "geohash_people.you_suffix"
+ static let blockedTooltip = L10n.string(
+ "geohash_people.tooltip.blocked",
+ comment: "Tooltip shown next to users blocked in geohash channels"
+ )
+ static let unblock: LocalizedStringKey = "geohash_people.action.unblock"
+ static let block: LocalizedStringKey = "geohash_people.action.block"
+ }
+
var body: some View {
if viewModel.visibleGeohashPeople().isEmpty {
VStack(alignment: .leading, spacing: 0) {
- Text("nobody around...")
+ Text(Strings.noneNearby)
.font(.bitchatSystem(size: 14, design: .monospaced))
.foregroundColor(secondaryTextColor)
.padding(.horizontal)
@@ -66,7 +77,7 @@ struct GeohashPeopleList: View {
.foregroundColor(suffixColor)
}
if isMe {
- Text(" (you)")
+ Text(Strings.youSuffix)
.font(.bitchatSystem(size: 14, design: .monospaced))
.foregroundColor(rowColor)
}
@@ -76,7 +87,7 @@ struct GeohashPeopleList: View {
Image(systemName: "nosign")
.font(.bitchatSystem(size: 10))
.foregroundColor(.red)
- .help("Blocked in geochash")
+ .help(Strings.blockedTooltip)
}
}
Spacer()
@@ -97,9 +108,9 @@ struct GeohashPeopleList: View {
} else {
let blocked = viewModel.isGeohashUserBlocked(pubkeyHexLowercased: person.id)
if blocked {
- Button("Unblock") { viewModel.unblockGeohashUser(pubkeyHexLowercased: person.id, displayName: person.displayName) }
+ Button(Strings.unblock) { viewModel.unblockGeohashUser(pubkeyHexLowercased: person.id, displayName: person.displayName) }
} else {
- Button("Block") { viewModel.blockGeohashUser(pubkeyHexLowercased: person.id, displayName: person.displayName) }
+ Button(Strings.block) { viewModel.blockGeohashUser(pubkeyHexLowercased: person.id, displayName: person.displayName) }
}
}
}
diff --git a/bitchat/Views/LocationChannelsSheet.swift b/bitchat/Views/LocationChannelsSheet.swift
index 8fac888f..184a6341 100644
--- a/bitchat/Views/LocationChannelsSheet.swift
+++ b/bitchat/Views/LocationChannelsSheet.swift
@@ -17,16 +17,80 @@ struct LocationChannelsSheet: View {
private var backgroundColor: Color { colorScheme == .dark ? .black : .white }
+ private enum Strings {
+ static let title: LocalizedStringKey = "location_channels.title"
+ static let description: LocalizedStringKey = "location_channels.description"
+ static let requestPermissions: LocalizedStringKey = "location_channels.action.request_permissions"
+ static let permissionDenied: LocalizedStringKey = "location_channels.permission_denied"
+ static let openSettings: LocalizedStringKey = "location_channels.action.open_settings"
+ static let loadingNearby: LocalizedStringKey = "location_channels.loading_nearby"
+ static let teleport: LocalizedStringKey = "location_channels.action.teleport"
+ static let bookmarked: LocalizedStringKey = "location_channels.bookmarked_section_title"
+ static let removeAccess: LocalizedStringKey = "location_channels.action.remove_access"
+ static let torTitle: LocalizedStringKey = "location_channels.tor.title"
+ static let torSubtitle: LocalizedStringKey = "location_channels.tor.subtitle"
+ static let toggleOn: LocalizedStringKey = "common.toggle.on"
+ static let toggleOff: LocalizedStringKey = "common.toggle.off"
+
+ static let invalidGeohash = L10n.string(
+ "location_channels.error.invalid_geohash",
+ comment: "Error shown when a custom geohash is invalid"
+ )
+
+ static func meshTitle(_ count: Int) -> String {
+ let label = L10n.string(
+ "location_channels.mesh_label",
+ comment: "Label for the mesh channel row"
+ )
+ return rowTitle(label: label, count: count)
+ }
+
+ static func levelTitle(for level: GeohashChannelLevel, count: Int) -> String {
+ return rowTitle(label: level.displayName, count: count)
+ }
+
+ static func bookmarkTitle(geohash: String, count: Int) -> String {
+ return rowTitle(label: "#\(geohash)", count: count)
+ }
+
+ static func subtitlePrefix(geohash: String, coverage: String) -> String {
+ L10n.format(
+ "location_channels.subtitle_prefix",
+ comment: "Subtitle prefix showing geohash and coverage",
+ geohash,
+ coverage
+ )
+ }
+
+ static func subtitle(prefix: String, name: String?) -> String {
+ guard let name, !name.isEmpty else { return prefix }
+ return L10n.format(
+ "location_channels.subtitle_with_name",
+ comment: "Subtitle combining prefix and resolved location name",
+ prefix,
+ name
+ )
+ }
+
+ private static func rowTitle(label: String, count: Int) -> String {
+ let format = NSLocalizedString(
+ "location_channels.row_title",
+ comment: "List row title with participant count"
+ )
+ return NSString.localizedStringWithFormat(format as NSString, label, count) as String
+ }
+ }
+
var body: some View {
NavigationView {
VStack(alignment: .leading, spacing: 12) {
HStack(spacing: 12) {
- Text("#location channels")
+ Text(Strings.title)
.font(.bitchatSystem(size: 18, design: .monospaced))
Spacer()
closeButton
}
- Text("chat with people near you using geohash channels. only a coarse geohash is shared, never exact gps. your IP address is hidden by routing all traffic over tor.")
+ Text(Strings.description)
.font(.bitchatSystem(size: 12, design: .monospaced))
.foregroundColor(.secondary)
@@ -34,7 +98,7 @@ struct LocationChannelsSheet: View {
switch manager.permissionState {
case LocationChannelManager.PermissionState.notDetermined:
Button(action: { manager.enableLocationChannels() }) {
- Text("get location and my geohashes")
+ Text(Strings.requestPermissions)
.font(.bitchatSystem(size: 12, design: .monospaced))
.foregroundColor(standardGreen)
.frame(maxWidth: .infinity)
@@ -45,10 +109,10 @@ struct LocationChannelsSheet: View {
.buttonStyle(.plain)
case LocationChannelManager.PermissionState.denied, LocationChannelManager.PermissionState.restricted:
VStack(alignment: .leading, spacing: 8) {
- Text("location permission denied. enable in settings to use location channels.")
+ Text(Strings.permissionDenied)
.font(.bitchatSystem(size: 12, design: .monospaced))
.foregroundColor(.secondary)
- Button("open settings") { openSystemLocationSettings() }
+ Button(Strings.openSettings) { openSystemLocationSettings() }
.buttonStyle(.plain)
}
case LocationChannelManager.PermissionState.authorized:
@@ -109,7 +173,7 @@ struct LocationChannelsSheet: View {
private var channelList: some View {
ScrollView {
LazyVStack(spacing: 0) {
- channelRow(title: meshTitleWithCount(), subtitlePrefix: "#bluetooth • \(bluetoothRangeString())", isSelected: isMeshSelected, titleColor: standardBlue, titleBold: meshCount() > 0) {
+ channelRow(title: Strings.meshTitle(meshCount()), subtitlePrefix: Strings.subtitlePrefix(geohash: "bluetooth", coverage: bluetoothRangeString()), isSelected: isMeshSelected, titleColor: standardBlue, titleBold: meshCount() > 0) {
manager.select(ChannelID.mesh)
isPresented = false
}
@@ -122,10 +186,11 @@ struct LocationChannelsSheet: View {
let coverage = coverageString(forPrecision: channel.geohash.count)
let nameBase = locationName(for: channel.level)
let namePart = nameBase.map { formattedNamePrefix(for: channel.level) + $0 }
- let subtitlePrefix = "#\(channel.geohash) • \(coverage)"
- let highlight = viewModel.geohashParticipantCount(for: channel.geohash) > 0
+ let participantCount = viewModel.geohashParticipantCount(for: channel.geohash)
+ let subtitlePrefix = Strings.subtitlePrefix(geohash: channel.geohash, coverage: coverage)
+ let highlight = participantCount > 0
channelRow(
- title: geohashTitleWithCount(for: channel),
+ title: Strings.levelTitle(for: channel.level, count: participantCount),
subtitlePrefix: subtitlePrefix,
subtitleName: namePart,
isSelected: isSelected(channel),
@@ -149,7 +214,7 @@ struct LocationChannelsSheet: View {
sectionDivider
HStack(spacing: 8) {
ProgressView()
- Text("finding nearby channels…")
+ Text(Strings.loadingNearby)
.font(.bitchatSystem(size: 12, design: .monospaced))
}
.frame(maxWidth: .infinity, alignment: .leading)
@@ -174,7 +239,7 @@ struct LocationChannelsSheet: View {
Button(action: {
openSystemLocationSettings()
}) {
- Text("remove location access")
+ Text(Strings.removeAccess)
.font(.bitchatSystem(size: 12, design: .monospaced))
.foregroundColor(Color(red: 0.75, green: 0.1, blue: 0.1))
.frame(maxWidth: .infinity)
@@ -235,7 +300,7 @@ struct LocationChannelsSheet: View {
let isValid = validateGeohash(normalized)
Button(action: {
let gh = normalized
- guard isValid else { customError = "invalid geohash"; return }
+ guard isValid else { customError = Strings.invalidGeohash; return }
let level = levelForLength(gh.count)
let ch = GeohashChannel(level: level, geohash: gh)
manager.markTeleported(for: ch.geohash, true)
@@ -243,7 +308,7 @@ struct LocationChannelsSheet: View {
isPresented = false
}) {
HStack(spacing: 6) {
- Text("teleport")
+ Text(Strings.teleport)
.font(.bitchatSystem(size: 14, design: .monospaced))
Image(systemName: "face.dashed")
.font(.bitchatSystem(size: 14))
@@ -268,7 +333,7 @@ struct LocationChannelsSheet: View {
private func bookmarkedSection(_ entries: [String]) -> some View {
VStack(alignment: .leading, spacing: 8) {
- Text("bookmarked")
+ Text(Strings.bookmarked)
.font(.bitchatSystem(size: 12, design: .monospaced))
.foregroundColor(.secondary)
LazyVStack(spacing: 0) {
@@ -276,10 +341,11 @@ struct LocationChannelsSheet: View {
let level = levelForLength(gh.count)
let channel = GeohashChannel(level: level, geohash: gh)
let coverage = coverageString(forPrecision: gh.count)
- let subtitle = "#\(gh) • \(coverage)"
+ let subtitle = Strings.subtitlePrefix(geohash: gh, coverage: coverage)
let name = bookmarks.bookmarkNames[gh]
+ let participantCount = viewModel.geohashParticipantCount(for: gh)
channelRow(
- title: geohashHashTitleWithCount(gh),
+ title: Strings.bookmarkTitle(geohash: gh, count: participantCount),
subtitlePrefix: subtitle,
subtitleName: name.map { formattedNamePrefix(for: level) + $0 },
isSelected: isSelected(channel),
@@ -352,12 +418,7 @@ struct LocationChannelsSheet: View {
.foregroundColor(.secondary)
}
}
- let subtitleFull: String = {
- if let name = subtitleName, !name.isEmpty {
- return subtitlePrefix + " • " + name
- }
- return subtitlePrefix
- }()
+ let subtitleFull = Strings.subtitle(prefix: subtitlePrefix, name: subtitleName)
Text(subtitleFull)
.font(.bitchatSystem(size: 12, design: .monospaced))
.foregroundColor(.secondary)
@@ -386,13 +447,6 @@ struct LocationChannelsSheet: View {
}
// MARK: - Helpers for counts
- private func meshTitleWithCount() -> String {
- // Count currently connected mesh peers (excluding self)
- let meshCount = meshCount()
- let noun = meshCount == 1 ? "person" : "people"
- return "mesh [\(meshCount) \(noun)]"
- }
-
private func meshCount() -> Int {
// Count mesh-connected OR mesh-reachable peers (exclude self)
let myID = viewModel.meshService.myPeerID
@@ -402,20 +456,6 @@ struct LocationChannelsSheet: View {
}
}
- private func geohashTitleWithCount(for channel: GeohashChannel) -> String {
- // Main list: keep level labels (block/neighborhood/city/province/region)
- let count = viewModel.geohashParticipantCount(for: channel.geohash)
- let noun = count == 1 ? "person" : "people"
- return "\(channel.level.displayName.lowercased()) [\(count) \(noun)]"
- }
-
- private func geohashHashTitleWithCount(_ geohash: String) -> String {
- // Bookmarked list: show the #geohash as the main label
- let count = viewModel.geohashParticipantCount(for: geohash)
- let noun = count == 1 ? "person" : "people"
- return "#\(geohash) [\(count) \(noun)]"
- }
-
private func validateGeohash(_ s: String) -> Bool {
let allowed = Set("0123456789bcdefghjkmnpqrstuvwxyz")
guard !s.isEmpty, s.count <= 12 else { return false }
@@ -448,15 +488,15 @@ extension LocationChannelsSheet {
VStack(alignment: .leading, spacing: 8) {
Toggle(isOn: torToggleBinding) {
VStack(alignment: .leading, spacing: 2) {
- Text("tor routing")
+ Text(Strings.torTitle)
.font(.bitchatSystem(size: 12, weight: .semibold, design: .monospaced))
.foregroundColor(.primary)
- Text("hides your ip for location channels. recommended: on.")
+ Text(Strings.torSubtitle)
.font(.bitchatSystem(size: 11, design: .monospaced))
.foregroundColor(.secondary)
}
}
- .toggleStyle(IRCToggleStyle(accent: standardGreen))
+ .toggleStyle(IRCToggleStyle(accent: standardGreen, onLabel: Strings.toggleOn, offLabel: Strings.toggleOff))
}
.padding(12)
.background(Color.secondary.opacity(0.12))
@@ -473,13 +513,15 @@ extension LocationChannelsSheet {
private struct IRCToggleStyle: ToggleStyle {
let accent: Color
+ let onLabel: LocalizedStringKey
+ let offLabel: LocalizedStringKey
func makeBody(configuration: Configuration) -> some View {
Button(action: { configuration.isOn.toggle() }) {
HStack(spacing: 12) {
configuration.label
Spacer()
- Text(configuration.isOn ? "on" : "off")
+ Text(configuration.isOn ? onLabel : offLabel)
.textCase(.uppercase)
.font(.bitchatSystem(size: 12, weight: .semibold, design: .monospaced))
.foregroundColor(configuration.isOn ? accent : .secondary)
diff --git a/bitchat/Views/LocationNotesView.swift b/bitchat/Views/LocationNotesView.swift
index 1c42ae5d..171a0e42 100644
--- a/bitchat/Views/LocationNotesView.swift
+++ b/bitchat/Views/LocationNotesView.swift
@@ -23,6 +23,24 @@ struct LocationNotesView: View {
private var accentGreen: Color { colorScheme == .dark ? .green : Color(red: 0, green: 0.5, blue: 0) }
private var maxDraftLines: Int { dynamicTypeSize.isAccessibilitySize ? 5 : 3 }
+ private enum Strings {
+ static let closeAccessibility = L10n.string(
+ "common.close",
+ comment: "Accessibility label for close buttons"
+ )
+ static let description: LocalizedStringKey = "location_notes.description"
+ static let loadingRecent: LocalizedStringKey = "location_notes.loading_recent"
+ static let relaysPaused: LocalizedStringKey = "location_notes.relays_paused"
+ static let noRelaysNearby: LocalizedStringKey = "location_notes.no_relays_nearby"
+ static let retry: LocalizedStringKey = "location_notes.action.retry"
+ static let relaysRetryHint: LocalizedStringKey = "location_notes.relays_retry_hint"
+ static let loadingNotes: LocalizedStringKey = "location_notes.loading_notes"
+ static let emptyTitle: LocalizedStringKey = "location_notes.empty_title"
+ static let emptySubtitle: LocalizedStringKey = "location_notes.empty_subtitle"
+ static let dismissError: LocalizedStringKey = "location_notes.action.dismiss"
+ static let addPlaceholder: LocalizedStringKey = "location_notes.placeholder"
+ }
+
var body: some View {
#if os(macOS)
VStack(spacing: 0) {
@@ -85,14 +103,14 @@ struct LocationNotesView: View {
.frame(width: 32, height: 32)
}
.buttonStyle(.plain)
- .accessibilityLabel("Close")
+ .accessibilityLabel(Strings.closeAccessibility)
}
private var headerSection: some View {
let count = manager.notes.count
return VStack(alignment: .leading, spacing: 8) {
HStack(spacing: 12) {
- Text("#\(geohash) • \(count) \(count == 1 ? "note" : "notes")")
+ Text(headerTitle(for: count))
.font(.bitchatSystem(size: 18, design: .monospaced))
Spacer()
closeButton
@@ -106,16 +124,16 @@ struct LocationNotesView: View {
.font(.bitchatSystem(size: 12, design: .monospaced))
.foregroundColor(accentGreen)
}
- Text("add short permanent notes to this location for other visitors to find.")
+ Text(Strings.description)
.font(.bitchatSystem(size: 12, design: .monospaced))
.foregroundColor(.secondary)
.fixedSize(horizontal: false, vertical: true)
if manager.state == .loading && !manager.initialLoadComplete {
- Text("loading recent notes…")
+ Text(Strings.loadingRecent)
.font(.bitchatSystem(size: 11, design: .monospaced))
.foregroundColor(.secondary)
} else if manager.state == .noRelays {
- Text("geo relays unavailable; notes paused")
+ Text(Strings.relaysPaused)
.font(.bitchatSystem(size: 11, design: .monospaced))
.foregroundColor(.secondary)
}
@@ -126,6 +144,14 @@ struct LocationNotesView: View {
.background(backgroundColor)
}
+ private func headerTitle(for count: Int) -> String {
+ let format = NSLocalizedString(
+ "location_notes.header",
+ comment: "Header displaying the geohash and localized note count"
+ )
+ return NSString.localizedStringWithFormat(format as NSString, geohash, count) as String
+ }
+
private var notesContent: some View {
LazyVStack(alignment: .leading, spacing: 12) {
if manager.state == .noRelays {
@@ -171,12 +197,12 @@ struct LocationNotesView: View {
private var noRelaysRow: some View {
VStack(alignment: .leading, spacing: 4) {
- Text("no geo relays nearby")
+ Text(Strings.noRelaysNearby)
.font(.bitchatSystem(size: 13, weight: .semibold, design: .monospaced))
- Text("notes rely on geo relays. check connection and try again.")
+ Text(Strings.relaysRetryHint)
.font(.bitchatSystem(size: 12, design: .monospaced))
.foregroundColor(.secondary)
- Button("retry") { manager.refresh() }
+ Button(Strings.retry) { manager.refresh() }
.font(.bitchatSystem(size: 12, design: .monospaced))
.buttonStyle(.plain)
}
@@ -186,7 +212,7 @@ struct LocationNotesView: View {
private var loadingRow: some View {
HStack(spacing: 10) {
ProgressView()
- Text("loading notes…")
+ Text(Strings.loadingNotes)
.font(.bitchatSystem(size: 12, design: .monospaced))
.foregroundColor(.secondary)
Spacer()
@@ -196,9 +222,9 @@ struct LocationNotesView: View {
private var emptyRow: some View {
VStack(alignment: .leading, spacing: 4) {
- Text("no notes yet")
+ Text(Strings.emptyTitle)
.font(.bitchatSystem(size: 13, weight: .semibold, design: .monospaced))
- Text("be the first to add one for this spot.")
+ Text(Strings.emptySubtitle)
.font(.bitchatSystem(size: 12, design: .monospaced))
.foregroundColor(.secondary)
}
@@ -214,7 +240,7 @@ struct LocationNotesView: View {
.font(.bitchatSystem(size: 12, design: .monospaced))
Spacer()
}
- Button("dismiss") { manager.clearError() }
+ Button(Strings.dismissError) { manager.clearError() }
.font(.bitchatSystem(size: 12, design: .monospaced))
.buttonStyle(.plain)
}
@@ -223,7 +249,7 @@ struct LocationNotesView: View {
private var inputSection: some View {
HStack(alignment: .top, spacing: 10) {
- TextField("add a note for this place", text: $draft, axis: .vertical)
+ TextField(Strings.addPlaceholder, text: $draft, axis: .vertical)
.textFieldStyle(.plain)
.font(.bitchatSystem(size: 14, design: .monospaced))
.lineLimit(maxDraftLines, reservesSpace: true)
diff --git a/bitchat/Views/MeshPeerList.swift b/bitchat/Views/MeshPeerList.swift
index 2ba20a7c..94390c7f 100644
--- a/bitchat/Views/MeshPeerList.swift
+++ b/bitchat/Views/MeshPeerList.swift
@@ -11,10 +11,22 @@ struct MeshPeerList: View {
@State private var orderedIDs: [String] = []
+ private enum Strings {
+ static let noneNearby: LocalizedStringKey = "geohash_people.none_nearby"
+ static let blockedTooltip = L10n.string(
+ "geohash_people.tooltip.blocked",
+ comment: "Tooltip shown next to a blocked peer indicator"
+ )
+ static let newMessagesTooltip = L10n.string(
+ "mesh_peers.tooltip.new_messages",
+ comment: "Tooltip for the unread messages indicator"
+ )
+ }
+
var body: some View {
if viewModel.allPeers.isEmpty {
VStack(alignment: .leading, spacing: 0) {
- Text("nobody around...")
+ Text(Strings.noneNearby)
.font(.bitchatSystem(size: 14, design: .monospaced))
.foregroundColor(secondaryTextColor)
.padding(.horizontal)
@@ -87,7 +99,7 @@ struct MeshPeerList: View {
Image(systemName: "nosign")
.font(.bitchatSystem(size: 10))
.foregroundColor(.red)
- .help("Blocked")
+ .help(Strings.blockedTooltip)
}
if !isMe {
@@ -120,7 +132,7 @@ struct MeshPeerList: View {
Image(systemName: "envelope.fill")
.font(.bitchatSystem(size: 10))
.foregroundColor(.orange)
- .help("New messages")
+ .help(Strings.newMessagesTooltip)
}
if !isMe {
diff --git a/bitchat/Views/VerificationViews.swift b/bitchat/Views/VerificationViews.swift
index 275d0946..f71559f7 100644
--- a/bitchat/Views/VerificationViews.swift
+++ b/bitchat/Views/VerificationViews.swift
@@ -13,14 +13,22 @@ struct MyQRView: View {
@Environment(\.colorScheme) var colorScheme
private var boxColor: Color { Color.gray.opacity(0.1) }
+ private enum Strings {
+ static let title: LocalizedStringKey = "verification.my_qr.title"
+ static let accessibilityLabel = L10n.string(
+ "verification.my_qr.accessibility_label",
+ comment: "Accessibility label describing the verification QR code"
+ )
+ }
+
var body: some View {
VStack(spacing: 12) {
- Text("scan to verify me")
+ Text(Strings.title)
.font(.bitchatSystem(size: 16, weight: .bold, design: .monospaced))
VStack(spacing: 10) {
QRCodeImage(data: qrString, size: 240)
- .accessibilityLabel("verification qr code")
+ .accessibilityLabel(Strings.accessibilityLabel)
// Non-scrolling, fully visible URL (wraps across lines)
Text(qrString)
@@ -49,6 +57,10 @@ struct QRCodeImage: View {
private let context = CIContext()
private let filter = CIFilter.qrCodeGenerator()
+ private enum Strings {
+ static let unavailable: LocalizedStringKey = "verification.my_qr.unavailable"
+ }
+
var body: some View {
Group {
if let image = generateImage() {
@@ -59,7 +71,7 @@ struct QRCodeImage: View {
.stroke(Color.gray.opacity(0.5), lineWidth: 1)
.frame(width: size, height: size)
.overlay(
- Text("qr unavailable")
+ Text(Strings.unavailable)
.font(.bitchatSystem(size: 12, design: .monospaced))
.foregroundColor(.gray)
)
@@ -103,6 +115,27 @@ struct QRScanView: View {
@State private var input = ""
@State private var result: String = "" // not shown for iOS scanner
@State private var lastValid: String = ""
+
+ private enum Strings {
+ static let pastePrompt: LocalizedStringKey = "verification.scan.paste_prompt"
+ static let validate: LocalizedStringKey = "verification.scan.validate"
+ static func requested(_ nickname: String) -> String {
+ L10n.format(
+ "verification.scan.status.requested",
+ comment: "Status text when verification is requested for a nickname",
+ nickname
+ )
+ }
+ static let notFound = L10n.string(
+ "verification.scan.status.no_peer",
+ comment: "Status when no matching peer is found for a verification request"
+ )
+ static let invalid = L10n.string(
+ "verification.scan.status.invalid",
+ comment: "Status when a scanned QR payload is invalid"
+ )
+ }
+
var body: some View {
VStack(alignment: .leading, spacing: 12) {
#if os(iOS)
@@ -118,17 +151,17 @@ struct QRScanView: View {
.frame(height: 260)
.clipShape(RoundedRectangle(cornerRadius: 8))
#else
- Text("paste qr content to validate:")
+ Text(Strings.pastePrompt)
.font(.bitchatSystem(size: 14, weight: .medium, design: .monospaced))
TextEditor(text: $input)
.frame(height: 100)
.border(Color.gray.opacity(0.4))
- Button("validate") {
+ Button(Strings.validate) {
if let qr = VerificationService.shared.verifyScannedQR(input) {
let ok = viewModel.beginQRVerification(with: qr)
- result = ok ? "verification requested for \(qr.nickname)" : "could not find matching peer"
+ result = ok ? Strings.requested(qr.nickname) : Strings.notFound
} else {
- result = "invalid or expired qr payload"
+ result = Strings.invalid
}
}
.buttonStyle(.bordered)
@@ -254,7 +287,7 @@ struct VerificationSheetView: View {
VStack(spacing: 0) {
// Top header (always at top)
HStack {
- Text("VERIFY")
+ Text("verification.sheet.title")
.font(.bitchatSystem(size: 14, weight: .bold, design: .monospaced))
.foregroundColor(accentColor)
Spacer()
@@ -278,7 +311,7 @@ struct VerificationSheetView: View {
Group {
if showingScanner {
VStack(alignment: .leading, spacing: 12) {
- Text("scan a friend's qr")
+ Text("verification.scan.prompt_friend")
.font(.bitchatSystem(size: 16, weight: .bold, design: .monospaced))
.frame(maxWidth: .infinity)
.multilineTextAlignment(.center)
diff --git a/bitchatShareExtension/Localization/Base.lproj/Localizable.strings b/bitchatShareExtension/Localization/Base.lproj/Localizable.strings
new file mode 100644
index 00000000..6d59e166
--- /dev/null
+++ b/bitchatShareExtension/Localization/Base.lproj/Localizable.strings
@@ -0,0 +1,14 @@
+/*
+ Localizable.strings
+ bitchatShareExtension
+
+ Base English strings for the share extension.
+*/
+
+"share.status.nothing_to_share" = "nothing to share";
+"share.status.no_shareable_content" = "no shareable content";
+"share.fallback.shared_link_title" = "shared Link";
+"share.status.shared_link" = "✓ shared link to bitchat";
+"share.status.shared_text" = "✓ shared text to bitchat";
+"share.status.failed_to_encode" = "failed to encode link";
+
diff --git a/bitchatShareExtension/Localization/ar.lproj/Localizable.strings b/bitchatShareExtension/Localization/ar.lproj/Localizable.strings
new file mode 100644
index 00000000..586d4633
--- /dev/null
+++ b/bitchatShareExtension/Localization/ar.lproj/Localizable.strings
@@ -0,0 +1,11 @@
+/*
+ Localizable.strings
+ bitchatShareExtension (Arabic)
+*/
+
+"share.status.nothing_to_share" = "لا شيء لمشاركته";
+"share.status.no_shareable_content" = "لا محتوى قابلاً للمشاركة";
+"share.fallback.shared_link_title" = "رابط مشترك";
+"share.status.shared_link" = "✓ تم إرسال الرابط إلى bitchat";
+"share.status.shared_text" = "✓ تم إرسال النص إلى bitchat";
+"share.status.failed_to_encode" = "تعذر ترميز الرابط";
diff --git a/bitchatShareExtension/Localization/de.lproj/Localizable.strings b/bitchatShareExtension/Localization/de.lproj/Localizable.strings
new file mode 100644
index 00000000..ada0e628
--- /dev/null
+++ b/bitchatShareExtension/Localization/de.lproj/Localizable.strings
@@ -0,0 +1,11 @@
+/*
+ Localizable.strings
+ bitchatShareExtension (German)
+*/
+
+"share.status.nothing_to_share" = "nichts zum teilen";
+"share.status.no_shareable_content" = "kein teilbarer inhalt";
+"share.fallback.shared_link_title" = "geteilter link";
+"share.status.shared_link" = "✓ link zu bitchat geteilt";
+"share.status.shared_text" = "✓ text zu bitchat geteilt";
+"share.status.failed_to_encode" = "link konnte nicht codiert werden";
diff --git a/bitchatShareExtension/Localization/es.lproj/Localizable.strings b/bitchatShareExtension/Localization/es.lproj/Localizable.strings
new file mode 100644
index 00000000..ca1c1a96
--- /dev/null
+++ b/bitchatShareExtension/Localization/es.lproj/Localizable.strings
@@ -0,0 +1,12 @@
+/*
+ Localizable.strings
+ bitchatShareExtension (Spanish)
+*/
+
+"share.status.nothing_to_share" = "nada que compartir";
+"share.status.no_shareable_content" = "sin contenido que se pueda compartir";
+"share.fallback.shared_link_title" = "enlace compartido";
+"share.status.shared_link" = "✓ enlace compartido con bitchat";
+"share.status.shared_text" = "✓ texto compartido con bitchat";
+"share.status.failed_to_encode" = "no se pudo codificar el enlace";
+
diff --git a/bitchatShareExtension/Localization/fr.lproj/Localizable.strings b/bitchatShareExtension/Localization/fr.lproj/Localizable.strings
new file mode 100644
index 00000000..87fee5ad
--- /dev/null
+++ b/bitchatShareExtension/Localization/fr.lproj/Localizable.strings
@@ -0,0 +1,11 @@
+/*
+ Localizable.strings
+ bitchatShareExtension (French)
+*/
+
+"share.status.nothing_to_share" = "rien à partager";
+"share.status.no_shareable_content" = "aucun contenu partageable";
+"share.fallback.shared_link_title" = "lien partagé";
+"share.status.shared_link" = "✓ lien partagé vers bitchat";
+"share.status.shared_text" = "✓ texte partagé vers bitchat";
+"share.status.failed_to_encode" = "échec de l'encodage du lien";
diff --git a/bitchatShareExtension/Localization/he.lproj/Localizable.strings b/bitchatShareExtension/Localization/he.lproj/Localizable.strings
new file mode 100644
index 00000000..a74c9a46
--- /dev/null
+++ b/bitchatShareExtension/Localization/he.lproj/Localizable.strings
@@ -0,0 +1,11 @@
+/*
+ Localizable.strings
+ bitchatShareExtension (Hebrew)
+*/
+
+"share.status.nothing_to_share" = "אין מה לשתף";
+"share.status.no_shareable_content" = "אין תוכן שניתן לשתף";
+"share.fallback.shared_link_title" = "קישור משותף";
+"share.status.shared_link" = "✓ הקישור נשלח אל bitchat";
+"share.status.shared_text" = "✓ הטקסט נשלח אל bitchat";
+"share.status.failed_to_encode" = "לא ניתן לקודד את הקישור";
diff --git a/bitchatShareExtension/Localization/id.lproj/Localizable.strings b/bitchatShareExtension/Localization/id.lproj/Localizable.strings
new file mode 100644
index 00000000..6a76efed
--- /dev/null
+++ b/bitchatShareExtension/Localization/id.lproj/Localizable.strings
@@ -0,0 +1,11 @@
+/*
+ Localizable.strings
+ bitchatShareExtension (Indonesian)
+*/
+
+"share.status.nothing_to_share" = "tidak ada yang bisa dibagikan";
+"share.status.no_shareable_content" = "tidak ada konten yang bisa dibagikan";
+"share.fallback.shared_link_title" = "tautan dibagikan";
+"share.status.shared_link" = "✓ tautan dikirim ke bitchat";
+"share.status.shared_text" = "✓ teks dikirim ke bitchat";
+"share.status.failed_to_encode" = "gagal mengodekan tautan";
diff --git a/bitchatShareExtension/Localization/it.lproj/Localizable.strings b/bitchatShareExtension/Localization/it.lproj/Localizable.strings
new file mode 100644
index 00000000..9c205c5e
--- /dev/null
+++ b/bitchatShareExtension/Localization/it.lproj/Localizable.strings
@@ -0,0 +1,11 @@
+/*
+ Localizable.strings
+ bitchatShareExtension (Italian)
+*/
+
+"share.status.nothing_to_share" = "niente da condividere";
+"share.status.no_shareable_content" = "nessun contenuto condivisibile";
+"share.fallback.shared_link_title" = "link condiviso";
+"share.status.shared_link" = "✓ link inviato a bitchat";
+"share.status.shared_text" = "✓ testo inviato a bitchat";
+"share.status.failed_to_encode" = "impossibile codificare il link";
diff --git a/bitchatShareExtension/Localization/ja.lproj/Localizable.strings b/bitchatShareExtension/Localization/ja.lproj/Localizable.strings
new file mode 100644
index 00000000..73a6f0f8
--- /dev/null
+++ b/bitchatShareExtension/Localization/ja.lproj/Localizable.strings
@@ -0,0 +1,11 @@
+/*
+ Localizable.strings
+ bitchatShareExtension (Japanese)
+*/
+
+"share.status.nothing_to_share" = "共有できるものがありません";
+"share.status.no_shareable_content" = "共有可能なコンテンツがありません";
+"share.fallback.shared_link_title" = "共有リンク";
+"share.status.shared_link" = "✓ bitchatにリンクを共有";
+"share.status.shared_text" = "✓ bitchatにテキストを共有";
+"share.status.failed_to_encode" = "リンクのエンコードに失敗しました";
diff --git a/bitchatShareExtension/Localization/ne.lproj/Localizable.strings b/bitchatShareExtension/Localization/ne.lproj/Localizable.strings
new file mode 100644
index 00000000..19e3b760
--- /dev/null
+++ b/bitchatShareExtension/Localization/ne.lproj/Localizable.strings
@@ -0,0 +1,11 @@
+/*
+ Localizable.strings
+ bitchatShareExtension (Nepali)
+*/
+
+"share.status.nothing_to_share" = "बाँड्ने केही छैन";
+"share.status.no_shareable_content" = "बाँड्न मिल्ने सामग्री छैन";
+"share.fallback.shared_link_title" = "साझा गरिएको लिङ्क";
+"share.status.shared_link" = "✓ bitchat मा लिङ्क पठाइयो";
+"share.status.shared_text" = "✓ bitchat मा पाठ पठाइयो";
+"share.status.failed_to_encode" = "लिङ्क सङ्केत गर्न सकेन";
diff --git a/bitchatShareExtension/Localization/pt-BR.lproj/Localizable.strings b/bitchatShareExtension/Localization/pt-BR.lproj/Localizable.strings
new file mode 100644
index 00000000..1d9b2a74
--- /dev/null
+++ b/bitchatShareExtension/Localization/pt-BR.lproj/Localizable.strings
@@ -0,0 +1,11 @@
+/*
+ Localizable.strings
+ bitchatShareExtension (Portuguese - Brazil)
+*/
+
+"share.status.nothing_to_share" = "nada para compartilhar";
+"share.status.no_shareable_content" = "nenhum conteúdo compartilhável";
+"share.fallback.shared_link_title" = "link compartilhado";
+"share.status.shared_link" = "✓ link enviado para bitchat";
+"share.status.shared_text" = "✓ texto enviado para bitchat";
+"share.status.failed_to_encode" = "falha ao codificar link";
diff --git a/bitchatShareExtension/Localization/ru.lproj/Localizable.strings b/bitchatShareExtension/Localization/ru.lproj/Localizable.strings
new file mode 100644
index 00000000..bd019d4c
--- /dev/null
+++ b/bitchatShareExtension/Localization/ru.lproj/Localizable.strings
@@ -0,0 +1,11 @@
+/*
+ Localizable.strings
+ bitchatShareExtension (Russian)
+*/
+
+"share.status.nothing_to_share" = "нечем поделиться";
+"share.status.no_shareable_content" = "нет подходящего контента";
+"share.fallback.shared_link_title" = "поделился ссылкой";
+"share.status.shared_link" = "✓ ссылка отправлена в bitchat";
+"share.status.shared_text" = "✓ текст отправлен в bitchat";
+"share.status.failed_to_encode" = "не удалось закодировать ссылку";
diff --git a/bitchatShareExtension/Localization/uk.lproj/Localizable.strings b/bitchatShareExtension/Localization/uk.lproj/Localizable.strings
new file mode 100644
index 00000000..c7eb85a6
--- /dev/null
+++ b/bitchatShareExtension/Localization/uk.lproj/Localizable.strings
@@ -0,0 +1,11 @@
+/*
+ Localizable.strings
+ bitchatShareExtension (Ukrainian)
+*/
+
+"share.status.nothing_to_share" = "нема чим ділитися";
+"share.status.no_shareable_content" = "нема відповідного контенту";
+"share.fallback.shared_link_title" = "спільне посилання";
+"share.status.shared_link" = "✓ посилання надіслано в bitchat";
+"share.status.shared_text" = "✓ текст надіслано в bitchat";
+"share.status.failed_to_encode" = "не вдалося закодувати посилання";
diff --git a/bitchatShareExtension/Localization/zh-Hans.lproj/Localizable.strings b/bitchatShareExtension/Localization/zh-Hans.lproj/Localizable.strings
new file mode 100644
index 00000000..841bc80c
--- /dev/null
+++ b/bitchatShareExtension/Localization/zh-Hans.lproj/Localizable.strings
@@ -0,0 +1,11 @@
+/*
+ Localizable.strings
+ bitchatShareExtension (Simplified Chinese)
+*/
+
+"share.status.nothing_to_share" = "没有可分享的内容";
+"share.status.no_shareable_content" = "没有可分享的素材";
+"share.fallback.shared_link_title" = "分享的链接";
+"share.status.shared_link" = "✓ 已将链接分享至 bitchat";
+"share.status.shared_text" = "✓ 已将文本分享至 bitchat";
+"share.status.failed_to_encode" = "无法编码链接";
diff --git a/bitchatShareExtension/ShareViewController.swift b/bitchatShareExtension/ShareViewController.swift
index 3dffdadc..dda0ae77 100644
--- a/bitchatShareExtension/ShareViewController.swift
+++ b/bitchatShareExtension/ShareViewController.swift
@@ -14,6 +14,15 @@ import UniformTypeIdentifiers
final class ShareViewController: UIViewController {
// Bundle.main.bundleIdentifier would get the extension's bundleID
private static let groupID = "group.chat.bitchat"
+
+ private enum Strings {
+ static let nothingToShare = NSLocalizedString("share.status.nothing_to_share", comment: "Shown when the share extension receives no content")
+ static let noShareableContent = NSLocalizedString("share.status.no_shareable_content", comment: "Shown when provided content cannot be shared")
+ static let sharedLinkTitleFallback = NSLocalizedString("share.fallback.shared_link_title", comment: "Fallback title when saving a shared link")
+ static let sharedLinkConfirmation = NSLocalizedString("share.status.shared_link", comment: "Confirmation after successfully sharing a link")
+ static let sharedTextConfirmation = NSLocalizedString("share.status.shared_text", comment: "Confirmation after successfully sharing text")
+ static let failedToEncode = NSLocalizedString("share.status.failed_to_encode", comment: "Shown when the share payload cannot be encoded")
+ }
private let statusLabel: UILabel = {
let l = UILabel()
@@ -44,7 +53,7 @@ final class ShareViewController: UIViewController {
private func processShare() {
guard let ctx = self.extensionContext,
let item = ctx.inputItems.first as? NSExtensionItem else {
- finishWithMessage("Nothing to share")
+ finishWithMessage(Strings.nothingToShare)
return
}
@@ -61,7 +70,7 @@ final class ShareViewController: UIViewController {
if let title = item.attributedTitle?.string, !title.isEmpty {
saveAndFinish(text: title)
} else {
- finishWithMessage("No shareable content")
+ finishWithMessage(Strings.noShareableContent)
}
return
}
@@ -81,7 +90,7 @@ final class ShareViewController: UIViewController {
self.saveAndFinish(text: t)
}
} else {
- self.finishWithMessage("No shareable content")
+ self.finishWithMessage(Strings.noShareableContent)
}
}
}
@@ -136,20 +145,20 @@ final class ShareViewController: UIViewController {
private func saveAndFinish(url: URL, title: String?) {
let payload: [String: String] = [
"url": url.absoluteString,
- "title": title ?? url.host ?? "Shared Link"
+ "title": title ?? url.host ?? Strings.sharedLinkTitleFallback
]
if let json = try? JSONSerialization.data(withJSONObject: payload),
let s = String(data: json, encoding: .utf8) {
saveToSharedDefaults(content: s, type: "url")
- finishWithMessage("✓ Shared link to bitchat")
+ finishWithMessage(Strings.sharedLinkConfirmation)
} else {
- finishWithMessage("Failed to encode link")
+ finishWithMessage(Strings.failedToEncode)
}
}
private func saveAndFinish(text: String) {
saveToSharedDefaults(content: text, type: "text")
- finishWithMessage("✓ Shared text to bitchat")
+ finishWithMessage(Strings.sharedTextConfirmation)
}
private func saveToSharedDefaults(content: String, type: String) {