From f5caa1751a67af8d753981b67da13860016542ac Mon Sep 17 00:00:00 2001 From: jack <212554440+jackjackbits@users.noreply.github.com> Date: Wed, 24 Sep 2025 15:12:42 +0200 Subject: [PATCH] Add base localization infrastructure and externalize strings (#670) * Add base localization infrastructure and externalize strings * Add Spanish localization scaffolding with translations * Add machine translations for expanded locales --------- Co-authored-by: jack --- README.md | 7 + bitchat.xcodeproj/project.pbxproj | 13 + .../Base.lproj/Localizable.strings | 192 ++++++++++++ .../Base.lproj/Localizable.stringsdict | 54 ++++ .../Localization/ar.lproj/Localizable.strings | 190 ++++++++++++ .../ar.lproj/Localizable.stringsdict | 78 +++++ .../Localization/de.lproj/Localizable.strings | 190 ++++++++++++ .../de.lproj/Localizable.stringsdict | 54 ++++ .../Localization/es.lproj/Localizable.strings | 190 ++++++++++++ .../es.lproj/Localizable.stringsdict | 54 ++++ .../Localization/fr.lproj/Localizable.strings | 190 ++++++++++++ .../fr.lproj/Localizable.stringsdict | 54 ++++ .../Localization/he.lproj/Localizable.strings | 190 ++++++++++++ .../he.lproj/Localizable.stringsdict | 66 ++++ .../Localization/id.lproj/Localizable.strings | 190 ++++++++++++ .../id.lproj/Localizable.stringsdict | 54 ++++ .../Localization/it.lproj/Localizable.strings | 190 ++++++++++++ .../it.lproj/Localizable.stringsdict | 54 ++++ .../Localization/ja.lproj/Localizable.strings | 190 ++++++++++++ .../ja.lproj/Localizable.stringsdict | 54 ++++ .../Localization/ne.lproj/Localizable.strings | 190 ++++++++++++ .../ne.lproj/Localizable.stringsdict | 54 ++++ .../pt-BR.lproj/Localizable.strings | 190 ++++++++++++ .../pt-BR.lproj/Localizable.stringsdict | 54 ++++ .../Localization/ru.lproj/Localizable.strings | 190 ++++++++++++ .../ru.lproj/Localizable.stringsdict | 66 ++++ .../Localization/uk.lproj/Localizable.strings | 190 ++++++++++++ .../uk.lproj/Localizable.stringsdict | 66 ++++ .../zh-Hans.lproj/Localizable.strings | 190 ++++++++++++ .../zh-Hans.lproj/Localizable.stringsdict | 54 ++++ bitchat/Protocols/LocationChannel.swift | 20 +- bitchat/Services/LocationNotesManager.swift | 21 +- bitchat/Services/NoiseEncryptionService.swift | 55 +++- bitchat/Utils/Localization.swift | 12 + bitchat/ViewModels/ChatViewModel.swift | 170 ++++++++-- bitchat/Views/AppInfoView.swift | 174 ++++++----- bitchat/Views/ContentView.swift | 291 ++++++++++++++---- bitchat/Views/FingerprintView.swift | 58 +++- bitchat/Views/GeohashPeopleList.swift | 21 +- bitchat/Views/LocationChannelsSheet.swift | 136 +++++--- bitchat/Views/LocationNotesView.swift | 52 +++- bitchat/Views/MeshPeerList.swift | 18 +- bitchat/Views/VerificationViews.swift | 51 ++- .../Base.lproj/Localizable.strings | 14 + .../Localization/ar.lproj/Localizable.strings | 11 + .../Localization/de.lproj/Localizable.strings | 11 + .../Localization/es.lproj/Localizable.strings | 12 + .../Localization/fr.lproj/Localizable.strings | 11 + .../Localization/he.lproj/Localizable.strings | 11 + .../Localization/id.lproj/Localizable.strings | 11 + .../Localization/it.lproj/Localizable.strings | 11 + .../Localization/ja.lproj/Localizable.strings | 11 + .../Localization/ne.lproj/Localizable.strings | 11 + .../pt-BR.lproj/Localizable.strings | 11 + .../Localization/ru.lproj/Localizable.strings | 11 + .../Localization/uk.lproj/Localizable.strings | 11 + .../zh-Hans.lproj/Localizable.strings | 11 + .../ShareViewController.swift | 23 +- 58 files changed, 4482 insertions(+), 276 deletions(-) create mode 100644 bitchat/Localization/Base.lproj/Localizable.strings create mode 100644 bitchat/Localization/Base.lproj/Localizable.stringsdict create mode 100644 bitchat/Localization/ar.lproj/Localizable.strings create mode 100644 bitchat/Localization/ar.lproj/Localizable.stringsdict create mode 100644 bitchat/Localization/de.lproj/Localizable.strings create mode 100644 bitchat/Localization/de.lproj/Localizable.stringsdict create mode 100644 bitchat/Localization/es.lproj/Localizable.strings create mode 100644 bitchat/Localization/es.lproj/Localizable.stringsdict create mode 100644 bitchat/Localization/fr.lproj/Localizable.strings create mode 100644 bitchat/Localization/fr.lproj/Localizable.stringsdict create mode 100644 bitchat/Localization/he.lproj/Localizable.strings create mode 100644 bitchat/Localization/he.lproj/Localizable.stringsdict create mode 100644 bitchat/Localization/id.lproj/Localizable.strings create mode 100644 bitchat/Localization/id.lproj/Localizable.stringsdict create mode 100644 bitchat/Localization/it.lproj/Localizable.strings create mode 100644 bitchat/Localization/it.lproj/Localizable.stringsdict create mode 100644 bitchat/Localization/ja.lproj/Localizable.strings create mode 100644 bitchat/Localization/ja.lproj/Localizable.stringsdict create mode 100644 bitchat/Localization/ne.lproj/Localizable.strings create mode 100644 bitchat/Localization/ne.lproj/Localizable.stringsdict create mode 100644 bitchat/Localization/pt-BR.lproj/Localizable.strings create mode 100644 bitchat/Localization/pt-BR.lproj/Localizable.stringsdict create mode 100644 bitchat/Localization/ru.lproj/Localizable.strings create mode 100644 bitchat/Localization/ru.lproj/Localizable.stringsdict create mode 100644 bitchat/Localization/uk.lproj/Localizable.strings create mode 100644 bitchat/Localization/uk.lproj/Localizable.stringsdict create mode 100644 bitchat/Localization/zh-Hans.lproj/Localizable.strings create mode 100644 bitchat/Localization/zh-Hans.lproj/Localizable.stringsdict create mode 100644 bitchat/Utils/Localization.swift create mode 100644 bitchatShareExtension/Localization/Base.lproj/Localizable.strings create mode 100644 bitchatShareExtension/Localization/ar.lproj/Localizable.strings create mode 100644 bitchatShareExtension/Localization/de.lproj/Localizable.strings create mode 100644 bitchatShareExtension/Localization/es.lproj/Localizable.strings create mode 100644 bitchatShareExtension/Localization/fr.lproj/Localizable.strings create mode 100644 bitchatShareExtension/Localization/he.lproj/Localizable.strings create mode 100644 bitchatShareExtension/Localization/id.lproj/Localizable.strings create mode 100644 bitchatShareExtension/Localization/it.lproj/Localizable.strings create mode 100644 bitchatShareExtension/Localization/ja.lproj/Localizable.strings create mode 100644 bitchatShareExtension/Localization/ne.lproj/Localizable.strings create mode 100644 bitchatShareExtension/Localization/pt-BR.lproj/Localizable.strings create mode 100644 bitchatShareExtension/Localization/ru.lproj/Localizable.strings create mode 100644 bitchatShareExtension/Localization/uk.lproj/Localizable.strings create mode 100644 bitchatShareExtension/Localization/zh-Hans.lproj/Localizable.strings 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) {