diff --git a/.github/workflows/build-artifacts.yml b/.github/workflows/build-artifacts.yml index 395a2d0..e5b0c23 100644 --- a/.github/workflows/build-artifacts.yml +++ b/.github/workflows/build-artifacts.yml @@ -12,6 +12,7 @@ permissions: env: FLUTTER_VERSION: "3.35.6" APP_NAME: meshcore-sar + ANDROID_NDK_VERSION: "27.0.12077973" jobs: build-android: @@ -28,6 +29,9 @@ jobs: distribution: temurin java-version: "17" + - name: Setup Gradle cache + uses: gradle/actions/setup-gradle@v4 + - name: Setup Flutter uses: subosito/flutter-action@v2 with: @@ -35,6 +39,14 @@ jobs: channel: stable cache: true + - name: Cache Android NDK + uses: actions/cache@v4 + with: + path: | + /usr/local/lib/android/sdk/ndk/${{ env.ANDROID_NDK_VERSION }} + /usr/local/lib/android/sdk/licenses + key: ${{ runner.os }}-android-ndk-${{ env.ANDROID_NDK_VERSION }} + - name: Install dependencies run: flutter pub get @@ -188,6 +200,56 @@ jobs: path: "${{ env.APP_NAME }}-*-macos.dmg" if-no-files-found: error + build-ios: + name: Build iOS Archive (Manual Signing) + runs-on: macos-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Flutter + uses: subosito/flutter-action@v2 + with: + flutter-version: ${{ env.FLUTTER_VERSION }} + channel: stable + cache: true + + - name: Install dependencies + run: flutter pub get + + - name: Build iOS app (no codesign) + run: flutter build ios --release --no-codesign + + - name: Package iOS unsigned artifacts + run: | + RAW_TAG="${{ github.event.release.tag_name || github.ref_name }}" + TAG="${RAW_TAG//\//-}" + APP_PATH="build/ios/iphoneos/Runner.app" + if [ ! -d "$APP_PATH" ]; then + echo "Runner.app not found at $APP_PATH" + exit 1 + fi + + mkdir -p ios_payload/Payload + cp -R "$APP_PATH" ios_payload/Payload/ + ( + cd ios_payload + zip -qry "../${APP_NAME}-${TAG}-ios-unsigned.ipa" Payload + ) + rm -rf ios_payload + + zip -qry "${APP_NAME}-${TAG}-ios-runner-app.zip" "$APP_PATH" + + - name: Upload iOS artifact + uses: actions/upload-artifact@v4 + with: + name: release-ios + path: | + "${{ env.APP_NAME }}-*-ios-unsigned.ipa" + "${{ env.APP_NAME }}-*-ios-runner-app.zip" + if-no-files-found: error + upload-release-assets: name: Upload Assets To Release if: github.event_name == 'release' @@ -197,6 +259,7 @@ jobs: - build-linux - build-windows - build-macos + - build-ios steps: - name: Download all build artifacts @@ -216,11 +279,20 @@ jobs: - Linux (`.tar.gz`) - macOS (`.dmg`) - Windows (`.zip`) + - iOS unsigned (`-ios-unsigned.ipa`, `-ios-runner-app.zip`) Windows status: currently unusable. The BLE stack on Windows is broken, so BLE features do not work. + + iOS manual signing and install: + 1. Download `*-ios-unsigned.ipa` (or `*-ios-runner-app.zip`) from this release. + 2. Re-sign it with your Apple Development/Distribution certificate and provisioning profile. + 3. If you used `*-ios-runner-app.zip`, create a signed `.ipa` from `Runner.app` during signing. + 4. Install the signed `.ipa` on your iPhone using Apple Configurator 2 or Xcode (Devices and Simulators). files: | dist/*.apk dist/*.tar.gz dist/*.zip dist/*.dmg + dist/*-ios-unsigned.ipa + dist/*-ios-runner-app.zip fail_on_unmatched_files: true diff --git a/lib/l10n/app_el.arb b/lib/l10n/app_el.arb new file mode 100644 index 0000000..a85f587 --- /dev/null +++ b/lib/l10n/app_el.arb @@ -0,0 +1,3793 @@ +{ + "@@locale": "el", + + "appTitle": "MeshCore SAR", + "@appTitle": { + "description": "The application title" + }, + + "messages": "Messages", + "@messages": { + "description": "Messages tab label" + }, + + "contacts": "Contacts", + "@contacts": { + "description": "Contacts tab label" + }, + + "map": "Map", + "@map": { + "description": "Map tab label" + }, + + "settings": "Settings", + "@settings": { + "description": "Settings screen title" + }, + + "connect": "Connect", + "@connect": { + "description": "Connect button label" + }, + + "disconnect": "Disconnect", + "@disconnect": { + "description": "Disconnect button label" + }, + + "scanningForDevices": "Scanning for devices...", + "@scanningForDevices": { + "description": "Text shown when scanning for BLE devices" + }, + + "noDevicesFound": "No devices found", + "@noDevicesFound": { + "description": "Text shown when no BLE devices are found" + }, + + "scanAgain": "Scan Again", + "@scanAgain": { + "description": "Button to restart BLE scanning" + }, + + "tapToConnect": "Tap to connect", + "@tapToConnect": { + "description": "Subtitle text for device in scan list" + }, + + "deviceNotConnected": "Device not connected", + "@deviceNotConnected": { + "description": "Error message when device is not connected" + }, + + "locationPermissionDenied": "Location permission denied", + "@locationPermissionDenied": { + "description": "Error when location permission is denied" + }, + + "locationPermissionPermanentlyDenied": "Location permission permanently denied. Please enable in Settings.", + "@locationPermissionPermanentlyDenied": { + "description": "Error when location permission is permanently denied" + }, + + "locationPermissionRequired": "Location permission is required for GPS tracking and team coordination. You can enable it later in Settings.", + "@locationPermissionRequired": { + "description": "Message when location permission is needed" + }, + + "locationServicesDisabled": "Location services are disabled. Please enable them in Settings.", + "@locationServicesDisabled": { + "description": "Error when location services are disabled" + }, + + "failedToGetGpsLocation": "Failed to get GPS location", + "@failedToGetGpsLocation": { + "description": "Error when GPS location cannot be obtained" + }, + + "advertisedAtLocation": "Advertised at {latitude}, {longitude}", + "@advertisedAtLocation": { + "description": "Success message showing advertised location", + "placeholders": { + "latitude": { + "type": "String" + }, + "longitude": { + "type": "String" + } + } + }, + + "failedToAdvertise": "Failed to advertise: {error}", + "@failedToAdvertise": { + "description": "Error message for failed advertisement", + "placeholders": { + "error": { + "type": "String" + } + } + }, + + "reconnecting": "Reconnecting... ({attempt}/{max})", + "@reconnecting": { + "description": "Text shown during reconnection attempts", + "placeholders": { + "attempt": { + "type": "int" + }, + "max": { + "type": "int" + } + } + }, + + "cancelReconnection": "Cancel reconnection", + "@cancelReconnection": { + "description": "Tooltip for cancel reconnection button" + }, + + "mapManagement": "Map Management", + "@mapManagement": { + "description": "Menu item for map management" + }, + + "general": "General", + "@general": { + "description": "General settings section header" + }, + + "theme": "Theme", + "@theme": { + "description": "Theme setting label" + }, + + "chooseTheme": "Choose Theme", + "@chooseTheme": { + "description": "Theme selection dialog title" + }, + + "light": "Light", + "@light": { + "description": "Light theme option" + }, + + "dark": "Dark", + "@dark": { + "description": "Dark theme option" + }, + + "blueLightTheme": "Blue light theme", + "@blueLightTheme": { + "description": "Description for blue light theme" + }, + + "blueDarkTheme": "Blue dark theme", + "@blueDarkTheme": { + "description": "Description for blue dark theme" + }, + + "sarRed": "SAR Red", + "@sarRed": { + "description": "SAR Red theme option" + }, + + "alertEmergencyMode": "Alert/Emergency mode", + "@alertEmergencyMode": { + "description": "Description for SAR Red theme" + }, + + "sarGreen": "SAR Green", + "@sarGreen": { + "description": "SAR Green theme option" + }, + + "safeAllClearMode": "Safe/All Clear mode", + "@safeAllClearMode": { + "description": "Description for SAR Green theme" + }, + + "autoSystem": "Auto (System)", + "@autoSystem": { + "description": "Auto/System theme option" + }, + + "followSystemTheme": "Follow system theme", + "@followSystemTheme": { + "description": "Description for system theme" + }, + + "showRxTxIndicators": "Show RX/TX Indicators", + "@showRxTxIndicators": { + "description": "Setting to show RX/TX indicators" + }, + + "displayPacketActivity": "Display packet activity indicators in top bar", + "@displayPacketActivity": { + "description": "Description for RX/TX indicators setting" + }, + + "simpleMode": "Simple Mode", + "@simpleMode": { + "description": "Setting to enable simple mode" + }, + + "simpleModeDescription": "Hide non-essential information in messages and contacts", + "@simpleModeDescription": { + "description": "Description for simple mode setting" + }, + + "disableMap": "Disable Map", + "@disableMap": { + "description": "Setting to disable the map tab" + }, + + "disableMapDescription": "Hide the map tab to reduce battery usage", + "@disableMapDescription": { + "description": "Description for disable map setting" + }, + + "language": "Language", + "@language": { + "description": "Language setting label" + }, + + "chooseLanguage": "Choose Language", + "@chooseLanguage": { + "description": "Language selection dialog title" + }, + + "english": "English", + "@english": { + "description": "English language option" + }, + + "slovenian": "Slovenian", + "@slovenian": { + "description": "Slovenian language option" + }, + + "croatian": "Croatian", + "@croatian": { + "description": "Croatian language option" + }, + + "german": "German", + "@german": { + "description": "German language option" + }, + + "spanish": "Spanish", + "@spanish": { + "description": "Spanish language option" + }, + + "french": "French", + "@french": { + "description": "French language option" + }, + + "italian": "Italian", + "@italian": { + "description": "Italian language option" + }, + + "locationBroadcasting": "Location Broadcasting", + "@locationBroadcasting": { + "description": "Location settings section header" + }, + + "autoLocationTracking": "Auto Location Tracking", + "@autoLocationTracking": { + "description": "Auto location tracking setting" + }, + + "automaticallyBroadcastPosition": "Automatically broadcast position updates", + "@automaticallyBroadcastPosition": { + "description": "Description for auto location tracking" + }, + + "configureTracking": "Configure Tracking", + "@configureTracking": { + "description": "Configure tracking button label" + }, + + "distanceAndTimeThresholds": "Distance and time thresholds", + "@distanceAndTimeThresholds": { + "description": "Description for tracking configuration" + }, + + "locationTrackingConfiguration": "Location Tracking Configuration", + "@locationTrackingConfiguration": { + "description": "Tracking configuration dialog title" + }, + + "configureWhenLocationBroadcasts": "Configure when location broadcasts are sent to the mesh network", + "@configureWhenLocationBroadcasts": { + "description": "Description for tracking configuration dialog" + }, + + "minimumDistance": "Minimum Distance", + "@minimumDistance": { + "description": "Minimum distance setting label" + }, + + "broadcastAfterMoving": "Broadcast only after moving {distance} meters", + "@broadcastAfterMoving": { + "description": "Description for minimum distance", + "placeholders": { + "distance": { + "type": "String" + } + } + }, + + "maximumDistance": "Maximum Distance", + "@maximumDistance": { + "description": "Maximum distance setting label" + }, + + "alwaysBroadcastAfterMoving": "Always broadcast after moving {distance} meters", + "@alwaysBroadcastAfterMoving": { + "description": "Description for maximum distance", + "placeholders": { + "distance": { + "type": "String" + } + } + }, + + "minimumTimeInterval": "Minimum Time Interval", + "@minimumTimeInterval": { + "description": "Minimum time interval setting label" + }, + + "alwaysBroadcastEvery": "Always broadcast every {duration}", + "@alwaysBroadcastEvery": { + "description": "Description for time interval", + "placeholders": { + "duration": { + "type": "String" + } + } + }, + + "save": "Save", + "@save": { + "description": "Save button label" + }, + + "cancel": "Cancel", + "@cancel": { + "description": "Cancel button label" + }, + + "close": "Close", + "@close": { + "description": "Close button label" + }, + + "about": "About", + "@about": { + "description": "About section header" + }, + + "appVersion": "App Version", + "@appVersion": { + "description": "App version label" + }, + + "appName": "App Name", + "@appName": { + "description": "App name label" + }, + + "aboutMeshCoreSar": "About MeshCore SAR", + "@aboutMeshCoreSar": { + "description": "About dialog title" + }, + + "aboutDescription": "A Search & Rescue application designed for emergency response teams. Features include:\n\n• BLE mesh networking for device-to-device communication\n• Offline maps with multiple layer options\n• Real-time team member tracking\n• SAR tactical markers (found person, fire, staging)\n• Contact management and messaging\n• GPS tracking with compass heading\n• Map tile caching for offline use", + "@aboutDescription": { + "description": "About dialog description" + }, + + "technologiesUsed": "Technologies Used:", + "@technologiesUsed": { + "description": "Technologies used section title" + }, + + "technologiesList": "• Flutter for cross-platform development\n• BLE (Bluetooth Low Energy) for mesh networking\n• OpenStreetMap for mapping\n• Provider for state management\n• SharedPreferences for local storage", + "@technologiesList": { + "description": "List of technologies used" + }, + + "moreInfo": "More Info", + "@moreInfo": { + "description": "More info button label" + }, + + "learnMoreAbout": "Learn more about MeshCore SAR", + "@learnMoreAbout": { + "description": "Learn more link description" + }, + + "developer": "Developer", + "@developer": { + "description": "Developer section header" + }, + + "packageName": "Package Name", + "@packageName": { + "description": "Package name label" + }, + + "sampleData": "Sample Data", + "@sampleData": { + "description": "Sample data section header" + }, + + "sampleDataDescription": "Load or clear sample contacts, channel messages, and SAR markers for testing", + "@sampleDataDescription": { + "description": "Sample data section description" + }, + + "loadSampleData": "Load Sample Data", + "@loadSampleData": { + "description": "Load sample data button" + }, + + "clearAllData": "Clear All Data", + "@clearAllData": { + "description": "Clear all data button" + }, + + "clearAllDataConfirmTitle": "Clear All Data", + "@clearAllDataConfirmTitle": { + "description": "Clear data confirmation dialog title" + }, + + "clearAllDataConfirmMessage": "This will clear all contacts and SAR markers. Are you sure?", + "@clearAllDataConfirmMessage": { + "description": "Clear data confirmation message" + }, + + "clear": "Clear", + "@clear": { + "description": "Clear button label" + }, + + "loadedSampleData": "Loaded {teamCount} team members, {channelCount} channels, {sarCount} SAR markers, {messageCount} messages", + "@loadedSampleData": { + "description": "Success message after loading sample data", + "placeholders": { + "teamCount": { + "type": "int" + }, + "channelCount": { + "type": "int" + }, + "sarCount": { + "type": "int" + }, + "messageCount": { + "type": "int" + } + } + }, + + "failedToLoadSampleData": "Failed to load sample data: {error}", + "@failedToLoadSampleData": { + "description": "Error message when sample data fails to load", + "placeholders": { + "error": { + "type": "String" + } + } + }, + + "allDataCleared": "All data cleared", + "@allDataCleared": { + "description": "Success message after clearing all data" + }, + + "failedToStartBackgroundTracking": "Failed to start background tracking. Check permissions and BLE connection.", + "@failedToStartBackgroundTracking": { + "description": "Error message when background tracking fails to start" + }, + + "locationBroadcast": "Location broadcast: {latitude}, {longitude}", + "@locationBroadcast": { + "description": "Success message for location broadcast", + "placeholders": { + "latitude": { + "type": "String" + }, + "longitude": { + "type": "String" + } + } + }, + + "defaultPinInfo": "The default pin for devices without a screen is 123456. Trouble pairing? Forget the bluetooth device in system settings.", + "@defaultPinInfo": { + "description": "Information about default PIN for pairing" + }, + + "noMessagesYet": "No messages yet", + "@noMessagesYet": { + "description": "Empty state message when there are no messages" + }, + + "pullDownToSync": "Pull down to sync messages", + "@pullDownToSync": { + "description": "Instruction to pull down to refresh messages" + }, + + "deleteContact": "Delete Contact", + "@deleteContact": { + "description": "Delete contact action label" + }, + + "delete": "Delete", + "@delete": { + "description": "Delete button label" + }, + + "viewOnMap": "View on Map", + "@viewOnMap": { + "description": "Action to view contact location on map" + }, + + "refresh": "Refresh", + "@refresh": { + "description": "Refresh button label" + }, + + "sendDirectMessage": "Send", + "@sendDirectMessage": { + "description": "Action to send direct message to contact" + }, + + "resetPath": "Reset Path (Re-route)", + "@resetPath": { + "description": "Action to reset contact path for re-routing" + }, + + "publicKeyCopied": "Public key copied to clipboard", + "@publicKeyCopied": { + "description": "Success message when public key is copied" + }, + + "copiedToClipboard": "{label} copied to clipboard", + "@copiedToClipboard": { + "description": "Success message when a value is copied to clipboard", + "placeholders": { + "label": { + "type": "String" + } + } + }, + + "pleaseEnterPassword": "Please enter a password", + "@pleaseEnterPassword": { + "description": "Validation message for empty password field" + }, + + "failedToSyncContacts": "Failed to sync contacts: {error}", + "@failedToSyncContacts": { + "description": "Error message when contact sync fails", + "placeholders": { + "error": { + "type": "String" + } + } + }, + + "loggedInSuccessfully": "Logged in successfully! Waiting for room messages...", + "@loggedInSuccessfully": { + "description": "Success message after successful room login" + }, + + "loginFailed": "Login failed - incorrect password", + "@loginFailed": { + "description": "Error message when room login fails" + }, + + "loggingIn": "Logging in to {roomName}...", + "@loggingIn": { + "description": "Status message during room login process", + "placeholders": { + "roomName": { + "type": "String" + } + } + }, + + "failedToSendLogin": "Failed to send login: {error}", + "@failedToSendLogin": { + "description": "Error message when login command fails to send", + "placeholders": { + "error": { + "type": "String" + } + } + }, + + "lowLocationAccuracy": "Low Location Accuracy", + "@lowLocationAccuracy": { + "description": "Warning title for low GPS accuracy" + }, + + "continue_": "Continue", + "@continue_": { + "description": "Continue button label" + }, + + "sendSarMarker": "Send SAR marker", + "@sendSarMarker": { + "description": "Action to send SAR marker" + }, + + "deleteDrawing": "Delete Drawing", + "@deleteDrawing": { + "description": "Action to delete a map drawing" + }, + + "drawingTools": "Drawing Tools", + "@drawingTools": { + "description": "Drawing tools section or menu title" + }, + + "drawLine": "Draw Line", + "@drawLine": { + "description": "Map drawing mode: line" + }, + + "drawLineDesc": "Draw a freehand line on the map", + "@drawLineDesc": { + "description": "Description for line drawing mode" + }, + + "drawRectangle": "Draw Rectangle", + "@drawRectangle": { + "description": "Map drawing mode: rectangle" + }, + + "drawRectangleDesc": "Draw a rectangular area on the map", + "@drawRectangleDesc": { + "description": "Description for rectangle drawing mode" + }, + + "measureDistance": "Measure Distance", + "@measureDistance": { + "description": "Map drawing mode: measure distance" + }, + + "measureDistanceDesc": "Long press two points to measure", + "@measureDistanceDesc": { + "description": "Description for distance measurement mode" + }, + + "clearMeasurement": "Clear Measurement", + "@clearMeasurement": { + "description": "Tooltip to clear measurement" + }, + + "distanceLabel": "Distance: {distance}", + "@distanceLabel": { + "description": "Label showing measured distance", + "placeholders": { + "distance": {"type": "String"} + } + }, + + "longPressForSecondPoint": "Long press for second point", + "@longPressForSecondPoint": { + "description": "Instruction when first measurement point is set" + }, + + "longPressToStartMeasurement": "Long press to set first point", + "@longPressToStartMeasurement": { + "description": "Instruction to start measurement" + }, + + "longPressToStartNewMeasurement": "Long press to start new measurement", + "@longPressToStartNewMeasurement": { + "description": "Instruction to restart measurement after completion" + }, + + "shareDrawings": "Share Drawings", + "@shareDrawings": { + "description": "Action to share drawings to network" + }, + + "clearAllDrawings": "Clear All Drawings", + "@clearAllDrawings": { + "description": "Action to clear all local drawings" + }, + + "completeLine": "Complete Line", + "@completeLine": { + "description": "Tooltip to complete drawing a line" + }, + + "broadcastDrawingsToTeam": "Broadcast {count} drawing{plural} to team", + "@broadcastDrawingsToTeam": { + "description": "Subtitle showing how many drawings will be broadcast", + "placeholders": { + "count": {"type": "int"}, + "plural": {"type": "String"} + } + }, + + "removeAllDrawings": "Remove all {count} drawing{plural}", + "@removeAllDrawings": { + "description": "Subtitle for remove all drawings action", + "placeholders": { + "count": {"type": "int"}, + "plural": {"type": "String"} + } + }, + + "deleteAllDrawingsConfirm": "Delete all {count} drawing{plural} from the map?", + "@deleteAllDrawingsConfirm": { + "description": "Confirmation dialog message for deleting all drawings", + "placeholders": { + "count": {"type": "int"}, + "plural": {"type": "String"} + } + }, + + "drawing": "Drawing", + "@drawing": { + "description": "Generic drawing label" + }, + + "shareDrawingsCount": "Share {count} Drawing{plural}", + "@shareDrawingsCount": { + "description": "Title for share drawings dialog", + "placeholders": { + "count": {"type": "int"}, + "plural": {"type": "String"} + } + }, + + "sentDrawingsToRoom": "Sent {count} map drawing{plural} to {roomName}", + "@sentDrawingsToRoom": { + "description": "System message when drawings are sent to room", + "placeholders": { + "count": {"type": "int"}, + "plural": {"type": "String"}, + "roomName": {"type": "String"} + } + }, + + "sharedDrawingsToRoom": "Shared {success}/{total} drawing{plural} to {roomName}", + "@sharedDrawingsToRoom": { + "description": "Snackbar message showing drawings shared to room", + "placeholders": { + "success": {"type": "int"}, + "total": {"type": "int"}, + "plural": {"type": "String"}, + "roomName": {"type": "String"} + } + }, + + "showReceivedDrawings": "Show Received Drawings", + "@showReceivedDrawings": { + "description": "Toggle to show/hide received drawings from other team members" + }, + + "showingAllDrawings": "Showing all drawings", + "@showingAllDrawings": { + "description": "Subtitle when received drawings are visible" + }, + + "showingOnlyYourDrawings": "Showing only your drawings", + "@showingOnlyYourDrawings": { + "description": "Subtitle when received drawings are hidden" + }, + + "showSarMarkers": "Show SAR Markers", + "@showSarMarkers": { + "description": "Toggle to show/hide SAR markers on map" + }, + + "showingSarMarkers": "Showing SAR markers", + "@showingSarMarkers": { + "description": "Subtitle when SAR markers are visible" + }, + + "hidingSarMarkers": "Hiding SAR markers", + "@hidingSarMarkers": { + "description": "Subtitle when SAR markers are hidden" + }, + + "clearAll": "Clear All", + "@clearAll": { + "description": "Clear all button label" + }, + + "noLocalDrawings": "No local drawings to share", + "@noLocalDrawings": { + "description": "Message when there are no drawings to share" + }, + + "publicChannel": "Public Channel", + "@publicChannel": { + "description": "Public channel option for sharing" + }, + + "broadcastToAll": "Broadcast to all nearby nodes (ephemeral)", + "@broadcastToAll": { + "description": "Description for public channel broadcast" + }, + + "storedPermanently": "Stored permanently in room", + "@storedPermanently": { + "description": "Description for room storage permanence" + }, + + "drawingsSentToPublicChannel": "Sent {count} map drawing{plural} to Public Channel", + "@drawingsSentToPublicChannel": { + "description": "System message when drawings are sent to public channel", + "placeholders": { + "count": {"type": "int"}, + "plural": {"type": "String"} + } + }, + + "drawingsSharedToPublicChannel": "Shared {success}/{total} drawings to Public Channel", + "@drawingsSharedToPublicChannel": { + "description": "Snackbar message showing success count for drawings shared to public channel", + "placeholders": { + "success": {"type": "int"}, + "total": {"type": "int"} + } + }, + + "notConnectedToDevice": "Not connected to device", + "@notConnectedToDevice": { + "description": "Error message when device is not connected for direct messaging" + }, + + "directMessage": "Direct Message", + "@directMessage": { + "description": "Title for direct message sheet" + }, + + "directMessageSentTo": "Direct message sent to {contactName}", + "@directMessageSentTo": { + "description": "Success message after sending direct message", + "placeholders": { + "contactName": { + "type": "String" + } + } + }, + + "failedToSend": "Failed to send: {error}", + "@failedToSend": { + "description": "Error message when sending direct message fails", + "placeholders": { + "error": { + "type": "String" + } + } + }, + + "directMessageInfo": "This message will be sent directly to {contactName}. It will also appear in the main messages feed.", + "@directMessageInfo": { + "description": "Information about direct messaging behavior", + "placeholders": { + "contactName": { + "type": "String" + } + } + }, + + "typeYourMessage": "Type your message...", + "@typeYourMessage": { + "description": "Placeholder text for message input field" + }, + + "quickLocationMarker": "Quick location marker", + "@quickLocationMarker": { + "description": "Subtitle for SAR marker sheet header" + }, + + "markerType": "Marker Type", + "@markerType": { + "description": "Label for marker type selection section" + }, + + "sendTo": "Send To", + "@sendTo": { + "description": "Label for destination selection section" + }, + + "noDestinationsAvailable": "No destinations available.", + "@noDestinationsAvailable": { + "description": "Warning when no rooms or channels exist" + }, + + "selectDestination": "Select destination...", + "@selectDestination": { + "description": "Placeholder for destination dropdown" + }, + + "ephemeralBroadcastInfo": "Ephemeral: Broadcast over-the-air only. Not stored - nodes must be online.", + "@ephemeralBroadcastInfo": { + "description": "Information about ephemeral channel broadcasts" + }, + + "persistentRoomInfo": "Persistent: Stored immutably in room. Synced automatically and preserved offline.", + "@persistentRoomInfo": { + "description": "Information about persistent room storage" + }, + + "location": "Location", + "@location": { + "description": "Label for location section" + }, + + "myLocation": "My Location", + "@myLocation": { + "description": "Button label to insert current GPS location" + }, + + "fromMap": "From Map", + "@fromMap": { + "description": "Badge showing location is from map tap" + }, + + "gettingLocation": "Getting location...", + "@gettingLocation": { + "description": "Loading message while fetching GPS location" + }, + + "locationError": "Location Error", + "@locationError": { + "description": "Title for location error messages" + }, + + "retry": "Retry", + "@retry": { + "description": "Retry button label" + }, + + "refreshLocation": "Refresh location", + "@refreshLocation": { + "description": "Tooltip for refresh location button" + }, + + "accuracyMeters": "Accuracy: ±{accuracy}m", + "@accuracyMeters": { + "description": "Display of GPS accuracy in meters", + "placeholders": { + "accuracy": { + "type": "int" + } + } + }, + + "notesOptional": "Notes (optional)", + "@notesOptional": { + "description": "Label for optional notes field" + }, + + "addAdditionalInformation": "Add additional information...", + "@addAdditionalInformation": { + "description": "Placeholder for notes field" + }, + + "lowAccuracyWarning": "Location accuracy is ±{accuracy}m. This may not be accurate enough for SAR operations.\n\nContinue anyway?", + "@lowAccuracyWarning": { + "description": "Warning dialog content for low GPS accuracy", + "placeholders": { + "accuracy": { + "type": "int" + } + } + }, + + "loginToRoom": "Login to Room", + "@loginToRoom": { + "description": "Title for room login dialog" + }, + + "enterPasswordInfo": "Enter the password to access this room. The password will be saved for future use.", + "@enterPasswordInfo": { + "description": "Information about room password" + }, + + "password": "Password", + "@password": { + "description": "Password field label" + }, + + "enterRoomPassword": "Enter room password", + "@enterRoomPassword": { + "description": "Password field hint" + }, + + "loggingInDots": "Logging in...", + "@loggingInDots": { + "description": "Button text while logging in" + }, + + "login": "Login", + "@login": { + "description": "Login button label" + }, + + "failedToAddRoom": "Failed to add room to device: {error}\n\nThe room may not have advertised yet.\nTry waiting for the room to broadcast.", + "@failedToAddRoom": { + "description": "Error message when adding room fails", + "placeholders": { + "error": { + "type": "String" + } + } + }, + + "direct": "Direct", + "@direct": { + "description": "Direct routing indicator" + }, + + "flood": "Flood", + "@flood": { + "description": "Flood routing indicator" + }, + + "admin": "Admin", + "@admin": { + "description": "Admin badge label" + }, + + "loggedIn": "Logged In", + "@loggedIn": { + "description": "Logged in status badge" + }, + + "noGpsData": "No GPS data", + "@noGpsData": { + "description": "Message when GPS data is not available" + }, + + "distance": "Distance", + "@distance": { + "description": "Distance label" + }, + + "pingingDirect": "Pinging {name} (direct via path)...", + "@pingingDirect": { + "description": "Status message for direct ping", + "placeholders": { + "name": { + "type": "String" + } + } + }, + + "pingingFlood": "Pinging {name} (flooding - no path)...", + "@pingingFlood": { + "description": "Status message for flood ping", + "placeholders": { + "name": { + "type": "String" + } + } + }, + + "directPingTimeout": "Direct ping timeout - retrying {name} with flooding...", + "@directPingTimeout": { + "description": "Warning when direct ping times out", + "placeholders": { + "name": { + "type": "String" + } + } + }, + + "pingSuccessful": "Ping successful to {name}{fallback}", + "@pingSuccessful": { + "description": "Success message for ping", + "placeholders": { + "name": { + "type": "String" + }, + "fallback": { + "type": "String" + } + } + }, + + "viaFloodingFallback": " (via flooding fallback)", + "@viaFloodingFallback": { + "description": "Suffix for ping success with fallback" + }, + + "pingFailed": "Ping failed to {name} - no response received", + "@pingFailed": { + "description": "Error message when ping fails", + "placeholders": { + "name": { + "type": "String" + } + } + }, + + "deleteContactConfirmation": "Are you sure you want to delete \"{name}\"?\n\nThis will remove the contact from both the app and the companion radio device.", + "@deleteContactConfirmation": { + "description": "Confirmation message for deleting contact", + "placeholders": { + "name": { + "type": "String" + } + } + }, + + "removingContact": "Removing {name}...", + "@removingContact": { + "description": "Status message while removing contact", + "placeholders": { + "name": { + "type": "String" + } + } + }, + + "contactRemoved": "Contact \"{name}\" removed", + "@contactRemoved": { + "description": "Success message after removing contact", + "placeholders": { + "name": { + "type": "String" + } + } + }, + + "failedToRemoveContact": "Failed to remove contact: {error}", + "@failedToRemoveContact": { + "description": "Error message when contact removal fails", + "placeholders": { + "error": { + "type": "String" + } + } + }, + + "type": "Type", + "@type": { + "description": "Contact type label" + }, + + "publicKey": "Public Key", + "@publicKey": { + "description": "Public key label" + }, + + "lastSeen": "Last Seen", + "@lastSeen": { + "description": "Last seen label" + }, + + "roomStatus": "Room Status", + "@roomStatus": { + "description": "Room status section header" + }, + + "loginStatus": "Login Status", + "@loginStatus": { + "description": "Login status label" + }, + + "notLoggedIn": "Not Logged In", + "@notLoggedIn": { + "description": "Not logged in status" + }, + + "adminAccess": "Admin Access", + "@adminAccess": { + "description": "Admin access label" + }, + + "yes": "Yes", + "@yes": { + "description": "Yes answer" + }, + + "no": "No", + "@no": { + "description": "No answer" + }, + + "permissions": "Permissions", + "@permissions": { + "description": "Permissions label" + }, + + "passwordSaved": "Password Saved", + "@passwordSaved": { + "description": "Password saved label" + }, + + "locationColon": "Location:", + "@locationColon": { + "description": "Location section header" + }, + + "telemetry": "Telemetry", + "@telemetry": { + "description": "Telemetry section header" + }, + + "requestingTelemetry": "Requesting telemetry from {name}...", + "@requestingTelemetry": { + "description": "Status message while requesting telemetry", + "placeholders": { + "name": { + "type": "String" + } + } + }, + + "voltage": "Voltage", + "@voltage": { + "description": "Voltage label" + }, + + "battery": "Battery", + "@battery": { + "description": "Battery label" + }, + + "temperature": "Temperature", + "@temperature": { + "description": "Temperature label" + }, + + "humidity": "Humidity", + "@humidity": { + "description": "Humidity label" + }, + + "pressure": "Pressure", + "@pressure": { + "description": "Pressure label" + }, + + "gpsTelemetry": "GPS (Telemetry)", + "@gpsTelemetry": { + "description": "GPS from telemetry label" + }, + + "updated": "Updated", + "@updated": { + "description": "Updated timestamp label" + }, + + "pathResetInfo": "Path reset for {name}. Next message will find a new route.", + "@pathResetInfo": { + "description": "Info message after path reset", + "placeholders": { + "name": { + "type": "String" + } + } + }, + + "reLoginToRoom": "Re-Login to Room", + "@reLoginToRoom": { + "description": "Button to re-login to room" + }, + + "heading": "Heading", + "@heading": { + "description": "Compass heading label" + }, + + "elevation": "Elevation", + "@elevation": { + "description": "Elevation/altitude label" + }, + + "accuracy": "Accuracy", + "@accuracy": { + "description": "GPS accuracy label" + }, + + "distance": "Distance", + "@distance": { + "description": "Distance label in compass" + }, + + "bearing": "Bearing", + "@bearing": { + "description": "Bearing label in compass" + }, + + "direction": "Direction", + "@direction": { + "description": "Direction label in compass" + }, + + "filterMarkers": "Filter Markers", + "@filterMarkers": { + "description": "Title for filter markers dialog" + }, + + "filterMarkersTooltip": "Filter markers", + "@filterMarkersTooltip": { + "description": "Tooltip for filter button" + }, + + "contactsFilter": "Contacts", + "@contactsFilter": { + "description": "Filter option for contacts" + }, + + "repeatersFilter": "Repeaters", + "@repeatersFilter": { + "description": "Filter option for repeaters" + }, + + "sarMarkers": "SAR Markers", + "@sarMarkers": { + "description": "SAR markers section header" + }, + + "foundPerson": "Found Person", + "@foundPerson": { + "description": "Found person SAR marker type" + }, + + "fire": "Fire", + "@fire": { + "description": "Fire SAR marker type" + }, + + "stagingArea": "Staging Area", + "@stagingArea": { + "description": "Staging area SAR marker type" + }, + + "showAll": "Show All", + "@showAll": { + "description": "Button to show all filters" + }, + + "nearbyContacts": "Nearby Contacts", + "@nearbyContacts": { + "description": "Title for nearby contacts list in compass" + }, + + "locationUnavailable": "Location unavailable", + "@locationUnavailable": { + "description": "Message when GPS location is unavailable" + }, + + "ahead": "ahead", + "@ahead": { + "description": "Relative bearing direction - ahead" + }, + + "degreesRight": "{degrees}° right", + "@degreesRight": { + "description": "Relative bearing direction - right", + "placeholders": { + "degrees": { + "type": "int" + } + } + }, + + "degreesLeft": "{degrees}° left", + "@degreesLeft": { + "description": "Relative bearing direction - left", + "placeholders": { + "degrees": { + "type": "int" + } + } + }, + + "latLonFormat": "Lat: {latitude} Lon: {longitude}", + "@latLonFormat": { + "description": "Latitude and longitude display format", + "placeholders": { + "latitude": { + "type": "String" + }, + "longitude": { + "type": "String" + } + } + }, + + "noContactsYet": "No contacts yet", + "@noContactsYet": { + "description": "Empty state message when there are no contacts" + }, + + "connectToDeviceToLoadContacts": "Connect to a device to load contacts", + "@connectToDeviceToLoadContacts": { + "description": "Instruction to connect device to load contacts" + }, + + "teamMembers": "Team Members", + "@teamMembers": { + "description": "Section header for team members (chat contacts)" + }, + + "repeaters": "Repeaters", + "@repeaters": { + "description": "Section header for repeater nodes" + }, + + "rooms": "Rooms", + "@rooms": { + "description": "Section header for rooms" + }, + + "channels": "Channels", + "@channels": { + "description": "Section header for broadcast channels" + }, + + "cacheStatistics": "Cache Statistics", + "@cacheStatistics": { + "description": "Title for cache statistics section" + }, + + "totalTiles": "Total Tiles", + "@totalTiles": { + "description": "Label for total number of cached tiles" + }, + + "cacheSize": "Cache Size", + "@cacheSize": { + "description": "Label for cache size in MB" + }, + + "storeName": "Store Name", + "@storeName": { + "description": "Label for cache store name" + }, + + "noCacheStatistics": "No cache statistics available", + "@noCacheStatistics": { + "description": "Message when cache statistics are unavailable" + }, + + "downloadRegion": "Download Region", + "@downloadRegion": { + "description": "Title for download region section" + }, + + "mapLayer": "Map Layer", + "@mapLayer": { + "description": "Label for map layer selection" + }, + + "regionBounds": "Region Bounds", + "@regionBounds": { + "description": "Title for region bounds input section" + }, + + "north": "North", + "@north": { + "description": "Label for north coordinate" + }, + + "south": "South", + "@south": { + "description": "Label for south coordinate" + }, + + "east": "East", + "@east": { + "description": "Label for east coordinate" + }, + + "west": "West", + "@west": { + "description": "Label for west coordinate" + }, + + "zoomLevels": "Zoom Levels", + "@zoomLevels": { + "description": "Title for zoom levels section" + }, + + "minZoom": "Min: {zoom}", + "@minZoom": { + "description": "Label for minimum zoom level", + "placeholders": { + "zoom": { + "type": "int" + } + } + }, + + "maxZoom": "Max: {zoom}", + "@maxZoom": { + "description": "Label for maximum zoom level", + "placeholders": { + "zoom": { + "type": "int" + } + } + }, + + "downloadingDots": "Downloading...", + "@downloadingDots": { + "description": "Status message during download" + }, + + "cancelDownload": "Cancel Download", + "@cancelDownload": { + "description": "Button to cancel download" + }, + + "downloadRegionButton": "Download Region", + "@downloadRegionButton": { + "description": "Button to start region download" + }, + + "downloadNote": "Note: Large regions or high zoom levels may take significant time and storage.", + "@downloadNote": { + "description": "Warning about download size and time" + }, + + "cacheManagement": "Cache Management", + "@cacheManagement": { + "description": "Title for cache management section" + }, + + "clearAllMaps": "Clear All Maps", + "@clearAllMaps": { + "description": "Button to clear all cached maps" + }, + + "clearMapsConfirmTitle": "Clear All Maps", + "@clearMapsConfirmTitle": { + "description": "Title for clear maps confirmation dialog" + }, + + "clearMapsConfirmMessage": "Are you sure you want to delete all downloaded maps? This action cannot be undone.", + "@clearMapsConfirmMessage": { + "description": "Confirmation message for clearing maps" + }, + + "mapDownloadCompleted": "Map download completed!", + "@mapDownloadCompleted": { + "description": "Success message after map download" + }, + + "cacheClearedSuccessfully": "Cache cleared successfully!", + "@cacheClearedSuccessfully": { + "description": "Success message after clearing cache" + }, + + "downloadCancelled": "Download cancelled", + "@downloadCancelled": { + "description": "Message when download is cancelled" + }, + + "startingDownload": "Starting download...", + "@startingDownload": { + "description": "Initial status when download begins" + }, + + "downloadingMapTiles": "Downloading map tiles...", + "@downloadingMapTiles": { + "description": "Status during tile download" + }, + + "downloadCompletedSuccessfully": "Download completed successfully!", + "@downloadCompletedSuccessfully": { + "description": "Status after successful download" + }, + + "cancellingDownload": "Cancelling download...", + "@cancellingDownload": { + "description": "Status while cancelling download" + }, + + "errorLoadingStats": "Error loading stats: {error}", + "@errorLoadingStats": { + "description": "Error message when loading cache stats fails", + "placeholders": { + "error": { + "type": "String" + } + } + }, + + "downloadFailed": "Download failed: {error}", + "@downloadFailed": { + "description": "Error message when download fails", + "placeholders": { + "error": { + "type": "String" + } + } + }, + + "cancelFailed": "Cancel failed: {error}", + "@cancelFailed": { + "description": "Error message when cancel fails", + "placeholders": { + "error": { + "type": "String" + } + } + }, + + "clearCacheFailed": "Clear cache failed: {error}", + "@clearCacheFailed": { + "description": "Error message when clearing cache fails", + "placeholders": { + "error": { + "type": "String" + } + } + }, + + "minZoomError": "Min zoom: {error}", + "@minZoomError": { + "description": "Validation error for minimum zoom", + "placeholders": { + "error": { + "type": "String" + } + } + }, + + "maxZoomError": "Max zoom: {error}", + "@maxZoomError": { + "description": "Validation error for maximum zoom", + "placeholders": { + "error": { + "type": "String" + } + } + }, + + "minZoomGreaterThanMax": "Minimum zoom must be less than or equal to maximum zoom", + "@minZoomGreaterThanMax": { + "description": "Validation error when min zoom > max zoom" + }, + + "selectMapLayer": "Select Map Layer", + "@selectMapLayer": { + "description": "Title for map layer selection dialog" + }, + + "mapOptions": "Map Options", + "@mapOptions": { + "description": "Title for map options dialog" + }, + + "showLegend": "Show Legend", + "@showLegend": { + "description": "Toggle for showing map legend" + }, + + "displayMarkerTypeCounts": "Display marker type counts", + "@displayMarkerTypeCounts": { + "description": "Description for show legend toggle" + }, + + "rotateMapWithHeading": "Rotate Map with Heading", + "@rotateMapWithHeading": { + "description": "Toggle for rotating map with compass heading" + }, + + "mapFollowsDirection": "Map follows your direction when moving", + "@mapFollowsDirection": { + "description": "Description for rotate map toggle" + }, + + "resetMapRotation": "Reset Rotation", + "@resetMapRotation": { + "description": "Button to reset map rotation to north" + }, + + "resetMapRotationTooltip": "Reset map to north", + "@resetMapRotationTooltip": { + "description": "Tooltip for reset rotation button" + }, + + "showMapDebugInfo": "Show Map Debug Info", + "@showMapDebugInfo": { + "description": "Toggle for showing map debug information" + }, + + "displayZoomLevelBounds": "Display zoom level and bounds", + "@displayZoomLevelBounds": { + "description": "Description for debug info toggle" + }, + + "fullscreenMode": "Fullscreen Mode", + "@fullscreenMode": { + "description": "Toggle for fullscreen map mode" + }, + + "hideUiFullMapView": "Hide all UI controls for full map view", + "@hideUiFullMapView": { + "description": "Description for fullscreen mode toggle" + }, + + "openStreetMap": "OpenStreetMap", + "@openStreetMap": { + "description": "OpenStreetMap layer name" + }, + + "openTopoMap": "OpenTopoMap", + "@openTopoMap": { + "description": "OpenTopoMap layer name" + }, + + "esriSatellite": "ESRI Satellite", + "@esriSatellite": { + "description": "ESRI Satellite imagery layer name" + }, + + "googleHybrid": "Google Hybrid", + "@googleHybrid": { + "description": "Google Hybrid layer name (satellite + labels)" + }, + + "googleRoadmap": "Google Roadmap", + "@googleRoadmap": { + "description": "Google Roadmap layer name (street map)" + }, + + "googleTerrain": "Google Terrain", + "@googleTerrain": { + "description": "Google Terrain layer name (topographic)" + }, + + "downloadVisibleArea": "Download visible area", + "@downloadVisibleArea": { + "description": "Tooltip for download visible area button" + }, + + "initializingMap": "Initializing map...", + "@initializingMap": { + "description": "Loading message for map initialization" + }, + + "dragToPosition": "Drag to Position", + "@dragToPosition": { + "description": "Label when dragging a pin on map" + }, + + "createSarMarker": "Create SAR Marker", + "@createSarMarker": { + "description": "Label for creating SAR marker from pin" + }, + + "compass": "Compass", + "@compass": { + "description": "Compass title in detailed compass dialog" + }, + + "navigationAndContacts": "Navigation & Contacts", + "@navigationAndContacts": { + "description": "Subtitle for compass dialog" + }, + + "sarAlert": "SAR ALERT", + "@sarAlert": { + "description": "Label for SAR alert badge on messages" + }, + + "messageSentToPublicChannel": "Message sent to public channel", + "@messageSentToPublicChannel": { + "description": "Success message when message is sent to public channel" + }, + + "pleaseSelectRoomToSendSar": "Please select a room to send SAR marker", + "@pleaseSelectRoomToSendSar": { + "description": "Error when no room is selected for SAR marker" + }, + + "failedToSendSarMarker": "Failed to send SAR marker: {error}", + "@failedToSendSarMarker": { + "description": "Error message when SAR marker fails to send", + "placeholders": { + "error": { + "type": "String" + } + } + }, + + "sarMarkerSentTo": "SAR marker sent to {roomName}", + "@sarMarkerSentTo": { + "description": "Success message when SAR marker is sent to room", + "placeholders": { + "roomName": { + "type": "String" + } + } + }, + + "notConnectedCannotSync": "Not connected - cannot sync messages", + "@notConnectedCannotSync": { + "description": "Warning when trying to sync messages while not connected" + }, + + "syncedMessageCount": "Synced {count} message(s)", + "@syncedMessageCount": { + "description": "Success message showing number of synced messages", + "placeholders": { + "count": { + "type": "int" + } + } + }, + + "noNewMessages": "No new messages", + "@noNewMessages": { + "description": "Info message when no new messages to sync" + }, + + "syncFailed": "Sync failed: {error}", + "@syncFailed": { + "description": "Error message when sync fails", + "placeholders": { + "error": { + "type": "String" + } + } + }, + + "failedToResendMessage": "Failed to resend message", + "@failedToResendMessage": { + "description": "Error when message retry fails" + }, + + "retryingMessage": "Retrying message...", + "@retryingMessage": { + "description": "Info message when retrying a failed message" + }, + + "retryFailed": "Retry failed: {error}", + "@retryFailed": { + "description": "Error message when retry fails", + "placeholders": { + "error": { + "type": "String" + } + } + }, + + "textCopiedToClipboard": "Text copied to clipboard", + "@textCopiedToClipboard": { + "description": "Success message when text is copied" + }, + + "cannotReplySenderMissing": "Cannot reply: sender information missing", + "@cannotReplySenderMissing": { + "description": "Error when sender info is missing for reply" + }, + + "cannotReplyContactNotFound": "Cannot reply: contact not found", + "@cannotReplyContactNotFound": { + "description": "Error when contact not found for reply" + }, + + "messageDeleted": "Message deleted", + "@messageDeleted": { + "description": "Info message when message is deleted" + }, + + "copyText": "Copy text", + "@copyText": { + "description": "Option to copy message text to clipboard" + }, + + "textCopiedToClipboard": "Text copied to clipboard", + "@textCopiedToClipboard": { + "description": "Success message when text is copied" + }, + + "saveAsTemplate": "Save as Template", + "@saveAsTemplate": { + "description": "Option to save SAR message as a reusable template" + }, + + "templateSaved": "Template saved successfully", + "@templateSaved": { + "description": "Success message when SAR template is saved" + }, + + "templateAlreadyExists": "Template with this emoji already exists", + "@templateAlreadyExists": { + "description": "Error message when trying to save duplicate template" + }, + + "deleteMessage": "Delete message", + "@deleteMessage": { + "description": "Dialog title for deleting a message" + }, + + "deleteMessageConfirmation": "Are you sure you want to delete this message?", + "@deleteMessageConfirmation": { + "description": "Confirmation text for message deletion" + }, + + "shareLocation": "Share location", + "@shareLocation": { + "description": "Option to share SAR marker location" + }, + + "shareLocationText": "{markerInfo}\n\nCoordinates: {lat}, {lon}\n\nGoogle Maps: {url}", + "@shareLocationText": { + "description": "Formatted text for sharing SAR marker location", + "placeholders": { + "markerInfo": { + "type": "String" + }, + "lat": { + "type": "String" + }, + "lon": { + "type": "String" + }, + "url": { + "type": "String" + } + } + }, + + "sarLocationShare": "SAR Location", + "@sarLocationShare": { + "description": "Subject line when sharing SAR marker location" + }, + + "locationShared": "Location shared", + "@locationShared": { + "description": "Success message when location is shared" + }, + + "refreshedContacts": "Refreshed contacts", + "@refreshedContacts": { + "description": "Success message when contacts are refreshed" + }, + + "justNow": "Just now", + "@justNow": { + "description": "Time indicator for very recent activity" + }, + + "minutesAgo": "{minutes}m ago", + "@minutesAgo": { + "description": "Time indicator for minutes ago", + "placeholders": { + "minutes": { + "type": "int" + } + } + }, + + "hoursAgo": "{hours}h ago", + "@hoursAgo": { + "description": "Time indicator for hours ago", + "placeholders": { + "hours": { + "type": "int" + } + } + }, + + "daysAgo": "{days}d ago", + "@daysAgo": { + "description": "Time indicator for days ago", + "placeholders": { + "days": { + "type": "int" + } + } + }, + + "secondsAgo": "{seconds}s ago", + "@secondsAgo": { + "description": "Time indicator for seconds ago", + "placeholders": { + "seconds": { + "type": "int" + } + } + }, + + "sending": "Sending...", + "@sending": { + "description": "Delivery status: sending" + }, + + "sent": "Sent", + "@sent": { + "description": "Delivery status: sent" + }, + + "delivered": "Delivered", + "@delivered": { + "description": "Delivery status: delivered" + }, + + "deliveredWithTime": "Delivered ({time}ms)", + "@deliveredWithTime": { + "description": "Delivery status with round-trip time", + "placeholders": { + "time": { + "type": "int" + } + } + }, + + "failed": "Failed", + "@failed": { + "description": "Delivery status: failed" + }, + + "broadcast": "Broadcast", + "@broadcast": { + "description": "Delivery status for channel messages (no echoes yet)" + }, + + "deliveredToContacts": "Delivered to {delivered}/{total} contacts", + "@deliveredToContacts": { + "description": "Grouped message delivery count", + "placeholders": { + "delivered": { + "type": "int" + }, + "total": { + "type": "int" + } + } + }, + + "allDelivered": "All delivered", + "@allDelivered": { + "description": "Status when all recipients received the message" + }, + + "recipientDetails": "Recipient Details", + "@recipientDetails": { + "description": "Header for expandable recipient list" + }, + + "pending": "Pending", + "@pending": { + "description": "Delivery status: pending/waiting" + }, + + "sarMarkerFoundPerson": "Found Person", + "@sarMarkerFoundPerson": { + "description": "SAR marker type: found person" + }, + + "sarMarkerFire": "Fire Location", + "@sarMarkerFire": { + "description": "SAR marker type: fire" + }, + + "sarMarkerStagingArea": "Staging Area", + "@sarMarkerStagingArea": { + "description": "SAR marker type: staging area" + }, + + "sarMarkerObject": "Object Found", + "@sarMarkerObject": { + "description": "SAR marker type: object" + }, + + "from": "From", + "@from": { + "description": "Sender label in notifications" + }, + + "coordinates": "Coordinates", + "@coordinates": { + "description": "Coordinates label" + }, + + "tapToViewOnMap": "Tap to view on map", + "@tapToViewOnMap": { + "description": "Notification action text" + }, + + "radioSettings": "Radio Settings", + "@radioSettings": { + "description": "Section title for radio settings" + }, + + "frequencyMHz": "Frequency (MHz)", + "@frequencyMHz": { + "description": "Label for radio frequency field" + }, + + "frequencyExample": "e.g., 869.618", + "@frequencyExample": { + "description": "Helper text example for frequency" + }, + + "bandwidth": "Bandwidth", + "@bandwidth": { + "description": "Label for bandwidth dropdown" + }, + + "spreadingFactor": "Spreading Factor", + "@spreadingFactor": { + "description": "Label for spreading factor dropdown" + }, + + "codingRate": "Coding Rate", + "@codingRate": { + "description": "Label for coding rate dropdown" + }, + + "txPowerDbm": "TX Power (dBm)", + "@txPowerDbm": { + "description": "Label for TX power field" + }, + + "maxPowerDbm": "Max: {power} dBm", + "@maxPowerDbm": { + "description": "Helper text showing maximum TX power", + "placeholders": { + "power": { "type": "int" } + } + }, + + "you": "You", + "@you": { + "description": "Label for the current user in message bubbles" + }, + + "offlineVectorMaps": "Offline Vector Maps", + "@offlineVectorMaps": { + "description": "Title for offline vector maps section" + }, + + "offlineVectorMapsDescription": "Import and manage offline vector map tiles (MBTiles format) for use without internet connection", + "@offlineVectorMapsDescription": { + "description": "Description for offline vector maps section" + }, + + "importMbtiles": "Import MBTiles File", + "@importMbtiles": { + "description": "Button to import MBTiles file" + }, + + "importMbtilesNote": "Supports MBTiles files with vector tiles (PBF/MVT format). Geofabrik extracts work great!", + "@importMbtilesNote": { + "description": "Note about supported MBTiles file types" + }, + + "noMbtilesFiles": "No offline vector maps found", + "@noMbtilesFiles": { + "description": "Message when no MBTiles files are available" + }, + + "mbtilesImportedSuccessfully": "MBTiles file imported successfully", + "@mbtilesImportedSuccessfully": { + "description": "Success message after importing MBTiles file" + }, + + "failedToImportMbtiles": "Failed to import MBTiles file", + "@failedToImportMbtiles": { + "description": "Error message when MBTiles import fails" + }, + + "deleteMbtilesConfirmTitle": "Delete Offline Map", + "@deleteMbtilesConfirmTitle": { + "description": "Title for delete MBTiles confirmation dialog" + }, + + "deleteMbtilesConfirmMessage": "Are you sure you want to delete \"{name}\"? This will permanently remove the offline map.", + "@deleteMbtilesConfirmMessage": { + "description": "Confirmation message for deleting MBTiles file", + "placeholders": { + "name": { + "type": "String" + } + } + }, + + "mbtilesDeletedSuccessfully": "Offline map deleted successfully", + "@mbtilesDeletedSuccessfully": { + "description": "Success message after deleting MBTiles file" + }, + + "failedToDeleteMbtiles": "Failed to delete offline map", + "@failedToDeleteMbtiles": { + "description": "Error message when MBTiles deletion fails" + }, + + "importExportCachedTiles": "Import/Export Cached Tiles", + "@importExportCachedTiles": { + "description": "Title for import/export section" + }, + + "importExportDescription": "Backup, share, and restore downloaded map tiles between devices", + "@importExportDescription": { + "description": "Description for import/export functionality" + }, + + "exportTilesToFile": "Export Tiles to File", + "@exportTilesToFile": { + "description": "Button to export tiles to archive file" + }, + + "importTilesFromFile": "Import Tiles from File", + "@importTilesFromFile": { + "description": "Button to import tiles from archive file" + }, + + "selectExportLocation": "Select Export Location", + "@selectExportLocation": { + "description": "Title for export file picker" + }, + + "selectImportFile": "Select Tile Archive", + "@selectImportFile": { + "description": "Title for import file picker" + }, + + "exportingTiles": "Exporting tiles...", + "@exportingTiles": { + "description": "Status message during export" + }, + + "importingTiles": "Importing tiles...", + "@importingTiles": { + "description": "Status message during import" + }, + + "exportSuccess": "Exported {count} tiles successfully", + "@exportSuccess": { + "description": "Success message after export", + "placeholders": { + "count": { + "type": "int" + } + } + }, + + "importSuccess": "Imported {count} stores successfully", + "@importSuccess": { + "description": "Success message after import", + "placeholders": { + "count": { + "type": "int" + } + } + }, + + "exportFailed": "Export failed: {error}", + "@exportFailed": { + "description": "Error message when export fails", + "placeholders": { + "error": { + "type": "String" + } + } + }, + + "importFailed": "Import failed: {error}", + "@importFailed": { + "description": "Error message when import fails", + "placeholders": { + "error": { + "type": "String" + } + } + }, + + "exportNote": "Creates a compressed archive (.fmtc) file that can be shared and imported on other devices.", + "@exportNote": { + "description": "Note about export functionality" + }, + + "importNote": "Imports map tiles from a previously exported archive file. Tiles will be merged with existing cache.", + "@importNote": { + "description": "Note about import functionality" + }, + + "noTilesToExport": "No tiles available to export", + "@noTilesToExport": { + "description": "Message when cache is empty" + }, + + "archiveContainsStores": "Archive contains {count} stores", + "@archiveContainsStores": { + "description": "Information about archive contents", + "placeholders": { + "count": { + "type": "int" + } + } + }, + + "vectorTiles": "Vector Tiles", + "@vectorTiles": { + "description": "Label for vector tile type" + }, + + "schema": "Schema", + "@schema": { + "description": "Label for vector tile schema" + }, + + "unknown": "Unknown", + "@unknown": { + "description": "Unknown value label" + }, + + "bounds": "Bounds", + "@bounds": { + "description": "Label for geographic bounds" + }, + + "onlineLayers": "Online Layers", + "@onlineLayers": { + "description": "Section header for online map layers" + }, + + "offlineLayers": "Offline Layers", + "@offlineLayers": { + "description": "Section header for offline map layers (MBTiles)" + }, + + "locationTrail": "Location Trail", + "@locationTrail": { + "description": "Location trail title" + }, + + "showTrailOnMap": "Show Trail on Map", + "@showTrailOnMap": { + "description": "Toggle to show/hide trail on map" + }, + + "trailVisible": "Trail is visible on the map", + "@trailVisible": { + "description": "Trail visibility status - visible" + }, + + "trailHiddenRecording": "Trail is hidden (still recording)", + "@trailHiddenRecording": { + "description": "Trail visibility status - hidden but recording" + }, + + "distance": "Distance", + "@distance": { + "description": "Distance label" + }, + + "duration": "Duration", + "@duration": { + "description": "Duration label" + }, + + "points": "Points", + "@points": { + "description": "Trail points count label" + }, + + "clearTrail": "Clear Trail", + "@clearTrail": { + "description": "Button to clear location trail" + }, + + "clearTrailQuestion": "Clear Trail?", + "@clearTrailQuestion": { + "description": "Confirmation dialog title" + }, + + "clearTrailConfirmation": "Are you sure you want to clear the current location trail? This action cannot be undone.", + "@clearTrailConfirmation": { + "description": "Confirmation dialog message" + }, + + "noTrailRecorded": "No trail recorded yet", + "@noTrailRecorded": { + "description": "Message when no trail exists" + }, + + "startTrackingToRecord": "Start location tracking to record your trail", + "@startTrackingToRecord": { + "description": "Instructions to start trail recording" + }, + + "trailControls": "Trail Controls", + "@trailControls": { + "description": "Trail controls tooltip" + }, + + "exportTrailToGpx": "Export Trail to GPX", + "@exportTrailToGpx": { + "description": "Button label to export trail to GPX file" + }, + + "importTrailFromGpx": "Import Trail from GPX", + "@importTrailFromGpx": { + "description": "Button label to import trail from GPX file" + }, + + "trailExportedSuccessfully": "Trail exported successfully!", + "@trailExportedSuccessfully": { + "description": "Success message when trail is exported" + }, + + "failedToExportTrail": "Failed to export trail", + "@failedToExportTrail": { + "description": "Error message when trail export fails" + }, + + "failedToImportTrail": "Failed to import trail: {error}", + "@failedToImportTrail": { + "description": "Error message when trail import fails", + "placeholders": { + "error": { + "type": "String" + } + } + }, + + "importTrail": "Import Trail", + "@importTrail": { + "description": "Import trail dialog title" + }, + + "importTrailQuestion": "Import trail with {pointCount} points?\n\nYou can replace your current trail or view it alongside.", + "@importTrailQuestion": { + "description": "Import trail confirmation dialog content", + "placeholders": { + "pointCount": { + "type": "int" + } + } + }, + + "viewAlongside": "View Alongside", + "@viewAlongside": { + "description": "Button to import trail alongside current trail" + }, + + "replaceCurrent": "Replace Current", + "@replaceCurrent": { + "description": "Button to replace current trail with imported trail" + }, + + "trailImported": "Trail imported! ({pointCount} points)", + "@trailImported": { + "description": "Success message when trail is imported", + "placeholders": { + "pointCount": { + "type": "int" + } + } + }, + + "trailReplaced": "Trail replaced! ({pointCount} points)", + "@trailReplaced": { + "description": "Success message when trail is replaced", + "placeholders": { + "pointCount": { + "type": "int" + } + } + }, + + "contactTrails": "Contact Trails", + "@contactTrails": { + "description": "Contact trails section header" + }, + + "showAllContactTrails": "Show All Contact Trails", + "@showAllContactTrails": { + "description": "Toggle label to show all contact trails" + }, + + "noContactsWithLocationHistory": "No contacts with location history", + "@noContactsWithLocationHistory": { + "description": "Subtitle when no contacts have trails" + }, + + "showingTrailsForContacts": "Showing trails for {count} contacts", + "@showingTrailsForContacts": { + "description": "Subtitle showing number of contacts with trails", + "placeholders": { + "count": { + "type": "int" + } + } + }, + + "individualContactTrails": "Individual Contact Trails", + "@individualContactTrails": { + "description": "Expansion tile title for individual contact trails" + }, + + "deviceInformation": "Device Information", + "@deviceInformation": { + "description": "Device information section header" + }, + + "bleName": "BLE Name", + "@bleName": { + "description": "Bluetooth Low Energy device name label" + }, + + "meshName": "Mesh Name", + "@meshName": { + "description": "Mesh network name label" + }, + + "notSet": "Not set", + "@notSet": { + "description": "Label when a value is not set" + }, + + "model": "Model", + "@model": { + "description": "Device model label" + }, + + "version": "Version", + "@version": { + "description": "Version label" + }, + + "buildDate": "Build Date", + "@buildDate": { + "description": "Firmware build date label" + }, + + "firmware": "Firmware", + "@firmware": { + "description": "Firmware label" + }, + + "maxContacts": "Max Contacts", + "@maxContacts": { + "description": "Maximum contacts capacity label" + }, + + "maxChannels": "Max Channels", + "@maxChannels": { + "description": "Maximum channels capacity label" + }, + + "publicInfo": "Public Info", + "@publicInfo": { + "description": "Public information section header" + }, + + "meshNetworkName": "Mesh Network Name", + "@meshNetworkName": { + "description": "Mesh network name field label" + }, + + "nameBroadcastInMesh": "Name broadcast in mesh advertisements", + "@nameBroadcastInMesh": { + "description": "Helper text for mesh network name field" + }, + + "telemetryAndLocationSharing": "Telemetry & Location Sharing", + "@telemetryAndLocationSharing": { + "description": "Telemetry and location sharing toggle label" + }, + + "lat": "Lat", + "@lat": { + "description": "Latitude field label (short form)" + }, + + "lon": "Lon", + "@lon": { + "description": "Longitude field label (short form)" + }, + + "useCurrentLocation": "Use current location", + "@useCurrentLocation": { + "description": "Tooltip for use current location button" + }, + + "noneUnknown": "None/Unknown", + "@noneUnknown": { + "description": "Device type: none or unknown" + }, + + "chatNode": "Chat Node", + "@chatNode": { + "description": "Device type: chat node" + }, + + "repeater": "Repeater", + "@repeater": { + "description": "Device type: repeater" + }, + + "roomChannel": "Room/Channel", + "@roomChannel": { + "description": "Device type: room or channel" + }, + + "typeNumber": "Type {number}", + "@typeNumber": { + "description": "Generic device type with number", + "placeholders": { + "number": { + "type": "int" + } + } + }, + + "copiedToClipboardShort": "Copied {label} to clipboard", + "@copiedToClipboardShort": { + "description": "Short success message when copying to clipboard", + "placeholders": { + "label": { + "type": "String" + } + } + }, + + "failedToSave": "Failed to save: {error}", + "@failedToSave": { + "description": "Generic error message for save failures", + "placeholders": { + "error": { + "type": "String" + } + } + }, + + "failedToGetLocation": "Failed to get location: {error}", + "@failedToGetLocation": { + "description": "Error message when getting location fails", + "placeholders": { + "error": { + "type": "String" + } + } + }, + + "sarTemplates": "SAR Templates", + "@sarTemplates": { + "description": "SAR templates menu title" + }, + + "manageSarTemplates": "Manage cursor on target templates", + "@manageSarTemplates": { + "description": "Subtitle for SAR templates settings" + }, + + "addTemplate": "Add Template", + "@addTemplate": { + "description": "Button to add new SAR template" + }, + + "editTemplate": "Edit Template", + "@editTemplate": { + "description": "Dialog title for editing template" + }, + + "deleteTemplate": "Delete Template", + "@deleteTemplate": { + "description": "Action to delete template" + }, + + "templateName": "Template Name", + "@templateName": { + "description": "Label for template name field" + }, + + "templateNameHint": "e.g. Found Person", + "@templateNameHint": { + "description": "Hint text for template name" + }, + + "templateEmoji": "Emoji", + "@templateEmoji": { + "description": "Label for template emoji field" + }, + + "emojiRequired": "Emoji is required", + "@emojiRequired": { + "description": "Validation error when emoji field is empty" + }, + + "nameRequired": "Name is required", + "@nameRequired": { + "description": "Validation error when name field is empty" + }, + + "templateDescription": "Description (Optional)", + "@templateDescription": { + "description": "Label for template description field" + }, + + "templateDescriptionHint": "Add additional context...", + "@templateDescriptionHint": { + "description": "Hint text for template description" + }, + + "templateColor": "Color", + "@templateColor": { + "description": "Label for template color picker" + }, + + "previewFormat": "Preview (SAR Message Format)", + "@previewFormat": { + "description": "Label for format preview" + }, + + "importFromClipboard": "Import", + "@importFromClipboard": { + "description": "Button to import templates from clipboard" + }, + + "exportToClipboard": "Export", + "@exportToClipboard": { + "description": "Button to export templates to clipboard" + }, + + "deleteTemplateConfirmation": "Delete template '{name}'?", + "@deleteTemplateConfirmation": { + "description": "Confirmation message for template deletion", + "placeholders": { + "name": { + "type": "String" + } + } + }, + + "templateAdded": "Template added", + "@templateAdded": { + "description": "Success message when template is added" + }, + + "templateUpdated": "Template updated", + "@templateUpdated": { + "description": "Success message when template is updated" + }, + + "templateDeleted": "Template deleted", + "@templateDeleted": { + "description": "Success message when template is deleted" + }, + + "templatesImported": "{count, plural, =0{No templates imported} =1{Imported 1 template} other{Imported {count} templates}}", + "@templatesImported": { + "description": "Success message after importing templates", + "placeholders": { + "count": { + "type": "int" + } + } + }, + + "templatesExported": "{count, plural, =1{Exported 1 template to clipboard} other{Exported {count} templates to clipboard}}", + "@templatesExported": { + "description": "Success message after exporting templates", + "placeholders": { + "count": { + "type": "int" + } + } + }, + + "importFailed": "Import failed: {error}", + "@importFailed": { + "description": "Error message when import fails", + "placeholders": { + "error": { + "type": "String" + } + } + }, + + "exportFailed": "Export failed: {error}", + "@exportFailed": { + "description": "Error message when export fails", + "placeholders": { + "error": { + "type": "String" + } + } + }, + + "resetToDefaults": "Reset to Defaults", + "@resetToDefaults": { + "description": "Action to reset templates to defaults" + }, + + "resetToDefaultsConfirmation": "This will delete all custom templates and restore the 4 default templates. Continue?", + "@resetToDefaultsConfirmation": { + "description": "Confirmation message for reset to defaults" + }, + + "reset": "Reset", + "@reset": { + "description": "Reset button label" + }, + + "resetComplete": "Templates reset to defaults", + "@resetComplete": { + "description": "Success message after reset" + }, + + "noTemplates": "No templates available", + "@noTemplates": { + "description": "Message when no templates exist" + }, + + "tapAddToCreate": "Tap + to create your first template", + "@tapAddToCreate": { + "description": "Helper text when no templates exist" + }, + + "ok": "OK", + "@ok": { + "description": "OK button label" + }, + + "delete": "Delete", + "@delete": { + "description": "Delete button label" + }, + + "permissionsSection": "Permissions", + "@permissionsSection": { + "description": "Permissions section header" + }, + + "locationPermission": "Location Permission", + "@locationPermission": { + "description": "Location permission label" + }, + + "checking": "Checking...", + "@checking": { + "description": "Loading state indicator" + }, + + "locationPermissionGrantedAlways": "Granted (Always)", + "@locationPermissionGrantedAlways": { + "description": "Location permission status: granted always" + }, + + "locationPermissionGrantedWhileInUse": "Granted (While In Use)", + "@locationPermissionGrantedWhileInUse": { + "description": "Location permission status: granted while in use" + }, + + "locationPermissionDeniedTapToRequest": "Denied - Tap to request", + "@locationPermissionDeniedTapToRequest": { + "description": "Location permission status: denied, user can request" + }, + + "locationPermissionPermanentlyDeniedOpenSettings": "Permanently Denied - Open Settings", + "@locationPermissionPermanentlyDeniedOpenSettings": { + "description": "Location permission status: permanently denied" + }, + + "locationPermissionDialogContent": "Location permission is permanently denied. Please enable it in your device settings to use GPS tracking and location sharing features.", + "@locationPermissionDialogContent": { + "description": "Content for location permission dialog when permanently denied" + }, + + "openSettings": "Open Settings", + "@openSettings": { + "description": "Button to open device settings" + }, + + "locationPermissionGranted": "Location permission granted!", + "@locationPermissionGranted": { + "description": "Success message when location permission is granted" + }, + + "locationPermissionRequiredForGps": "Location permission is required for GPS tracking and location sharing.", + "@locationPermissionRequiredForGps": { + "description": "Info message about location permission requirement" + }, + + "locationPermissionAlreadyGranted": "Location permission is already granted.", + "@locationPermissionAlreadyGranted": { + "description": "Info message when permission is already granted" + }, + + "sarNavyBlue": "SAR Navy Blue", + "@sarNavyBlue": { + "description": "SAR Navy Blue theme name" + }, + + "sarNavyBlueDescription": "Professional/Operations Mode", + "@sarNavyBlueDescription": { + "description": "Description for SAR Navy Blue theme" + }, + + "selectRecipient": "Select Recipient", + "@selectRecipient": { + "description": "Title for recipient selector sheet" + }, + + "broadcastToAllNearby": "Broadcast to all nearby", + "@broadcastToAllNearby": { + "description": "Subtitle for public channel option" + }, + + "searchRecipients": "Search recipients...", + "@searchRecipients": { + "description": "Placeholder text for recipient search field" + }, + + "noContactsFound": "No contacts found", + "@noContactsFound": { + "description": "Message when no contacts match search" + }, + + "noRoomsFound": "No rooms found", + "@noRoomsFound": { + "description": "Message when no rooms match search" + }, + + "noContactsOrRoomsAvailable": "No contacts or rooms available", + "@noContactsOrRoomsAvailable": { + "description": "Message when no contacts or rooms exist" + }, + + "noRecipientsAvailable": "No recipients available", + "@noRecipientsAvailable": { + "description": "Message when no recipients exist (contacts, rooms, or channels)" + }, + + "noChannelsFound": "No channels found", + "@noChannelsFound": { + "description": "Message when no channels match the search" + }, + + "messagesWillBeSentToPublicChannel": "Messages will be sent to public channel", + "@messagesWillBeSentToPublicChannel": { + "description": "Info message when only public channel is available" + }, + + "newMessage": "New message", + "@newMessage": { + "description": "Notification title for new message" + }, + + "channel": "Channel", + "@channel": { + "description": "Channel label in notifications" + }, + + "samplePoliceLead": "Police Lead", + "@samplePoliceLead": { + "description": "Sample team member name" + }, + + "sampleDroneOperator": "Drone Operator", + "@sampleDroneOperator": { + "description": "Sample team member name" + }, + + "sampleFirefighterAlpha": "Firefighter", + "@sampleFirefighterAlpha": { + "description": "Sample team member name" + }, + + "sampleMedicCharlie": "Medic", + "@sampleMedicCharlie": { + "description": "Sample team member name" + }, + + "sampleCommandDelta": "Command", + "@sampleCommandDelta": { + "description": "Sample team member name" + }, + + "sampleFireEngine": "Fire Engine", + "@sampleFireEngine": { + "description": "Sample team member name" + }, + + "sampleAirSupport": "Air Support", + "@sampleAirSupport": { + "description": "Sample team member name" + }, + + "sampleBaseCoordinator": "Base Coordinator", + "@sampleBaseCoordinator": { + "description": "Sample team member name" + }, + + "channelEmergency": "Emergency", + "@channelEmergency": { + "description": "Emergency channel name" + }, + + "channelCoordination": "Coordination", + "@channelCoordination": { + "description": "Coordination channel name" + }, + + "channelUpdates": "Updates", + "@channelUpdates": { + "description": "Updates channel name" + }, + + "sampleTeamMember": "Sample Team Member", + "@sampleTeamMember": { + "description": "Sample sender name" + }, + + "sampleScout": "Sample Scout", + "@sampleScout": { + "description": "Sample sender name" + }, + + "sampleBase": "Sample Base", + "@sampleBase": { + "description": "Sample sender name" + }, + + "sampleSearcher": "Sample Searcher", + "@sampleSearcher": { + "description": "Sample sender name" + }, + + "sampleObjectBackpack": " Backpack found - blue color", + "@sampleObjectBackpack": { + "description": "Sample object note" + }, + + "sampleObjectVehicle": " Vehicle abandoned - check for owner", + "@sampleObjectVehicle": { + "description": "Sample object note" + }, + + "sampleObjectCamping": " Camping equipment discovered", + "@sampleObjectCamping": { + "description": "Sample object note" + }, + + "sampleObjectTrailMarker": " Trail marker found off-path", + "@sampleObjectTrailMarker": { + "description": "Sample object note" + }, + + "sampleMsgAllTeamsCheckIn": "All teams check in", + "@sampleMsgAllTeamsCheckIn": { + "description": "Sample channel message" + }, + + "sampleMsgWeatherUpdate": "Weather update: Clear skies, temp 18°C", + "@sampleMsgWeatherUpdate": { + "description": "Sample channel message" + }, + + "sampleMsgBaseCamp": "Base camp established at staging area", + "@sampleMsgBaseCamp": { + "description": "Sample channel message" + }, + + "sampleMsgTeamAlpha": "Team moving to sector 2", + "@sampleMsgTeamAlpha": { + "description": "Sample channel message" + }, + + "sampleMsgRadioCheck": "Radio check - all stations respond", + "@sampleMsgRadioCheck": { + "description": "Sample channel message" + }, + + "sampleMsgWaterSupply": "Water supply available at checkpoint 3", + "@sampleMsgWaterSupply": { + "description": "Sample channel message" + }, + + "sampleMsgTeamBravo": "Team reporting: sector 1 clear", + "@sampleMsgTeamBravo": { + "description": "Sample channel message" + }, + + "sampleMsgEtaRallyPoint": "ETA to rally point: 15 minutes", + "@sampleMsgEtaRallyPoint": { + "description": "Sample channel message" + }, + + "sampleMsgSupplyDrop": "Supply drop confirmed for 14:00", + "@sampleMsgSupplyDrop": { + "description": "Sample channel message" + }, + + "sampleMsgDroneSurvey": "Drone survey completed - no findings", + "@sampleMsgDroneSurvey": { + "description": "Sample channel message" + }, + + "sampleMsgTeamCharlie": "Team requesting backup", + "@sampleMsgTeamCharlie": { + "description": "Sample channel message" + }, + + "sampleMsgRadioDiscipline": "All units: maintain radio discipline", + "@sampleMsgRadioDiscipline": { + "description": "Sample channel message" + }, + + "sampleMsgUrgentMedical": "URGENT: Medical assistance needed at sector 4", + "@sampleMsgUrgentMedical": { + "description": "Sample emergency message" + }, + + "sampleMsgAdultMale": " Adult male, conscious", + "@sampleMsgAdultMale": { + "description": "Sample emergency message note" + }, + + "sampleMsgFireSpotted": "Fire spotted - coordinates incoming", + "@sampleMsgFireSpotted": { + "description": "Sample emergency message" + }, + + "sampleMsgSpreadingRapidly": " Spreading rapidly!", + "@sampleMsgSpreadingRapidly": { + "description": "Sample emergency message note" + }, + + "sampleMsgPriorityHelicopter": "PRIORITY: Need helicopter support", + "@sampleMsgPriorityHelicopter": { + "description": "Sample emergency message" + }, + + "sampleMsgMedicalTeamEnRoute": "Medical team en route to your location", + "@sampleMsgMedicalTeamEnRoute": { + "description": "Sample emergency message" + }, + + "sampleMsgEvacHelicopter": "Evac helicopter ETA 10 minutes", + "@sampleMsgEvacHelicopter": { + "description": "Sample emergency message" + }, + + "sampleMsgEmergencyResolved": "Emergency resolved - all clear", + "@sampleMsgEmergencyResolved": { + "description": "Sample emergency message" + }, + + "sampleMsgEmergencyStagingArea": " Emergency staging area", + "@sampleMsgEmergencyStagingArea": { + "description": "Sample emergency message note" + }, + + "sampleMsgEmergencyServices": "Emergency services notified and responding", + "@sampleMsgEmergencyServices": { + "description": "Sample emergency message" + }, + + "sampleAlphaTeamLead": "Team Lead", + "@sampleAlphaTeamLead": { + "description": "Sample team name" + }, + + "sampleBravoScout": "Scout", + "@sampleBravoScout": { + "description": "Sample team name" + }, + + "sampleCharlieMedic": "Medic", + "@sampleCharlieMedic": { + "description": "Sample team name" + }, + + "sampleDeltaNavigator": "Navigator", + "@sampleDeltaNavigator": { + "description": "Sample team name" + }, + + "sampleEchoSupport": "Support", + "@sampleEchoSupport": { + "description": "Sample team name" + }, + + "sampleBaseCommand": "Base Command", + "@sampleBaseCommand": { + "description": "Sample team name" + }, + + "sampleFieldCoordinator": "Field Coordinator", + "@sampleFieldCoordinator": { + "description": "Sample team name" + }, + + "sampleMedicalTeam": "Medical Team", + "@sampleMedicalTeam": { + "description": "Sample team name" + }, + + "mapDrawing": "Map Drawing", + "@mapDrawing": { + "description": "Label for map drawing messages" + }, + + "navigateToDrawing": "Navigate to Drawing", + "@navigateToDrawing": { + "description": "Option to navigate to drawing on map" + }, + + "copyCoordinates": "Copy Coordinates", + "@copyCoordinates": { + "description": "Option to copy coordinates to clipboard" + }, + + "hideFromMap": "Hide from Map", + "@hideFromMap": { + "description": "Option to hide drawing from map" + }, + + "lineDrawing": "Line Drawing", + "@lineDrawing": { + "description": "Label for line type drawings" + }, + + "rectangleDrawing": "Rectangle Drawing", + "@rectangleDrawing": { + "description": "Label for rectangle type drawings" + }, + + "coordinatesCopiedToClipboard": "Coordinates copied to clipboard", + "@coordinatesCopiedToClipboard": { + "description": "Success message when coordinates are copied" + }, + + "manualCoordinates": "Manual Coordinates", + "@manualCoordinates": { + "description": "Label for manual coordinate input toggle" + }, + + "enterCoordinatesManually": "Enter coordinates manually", + "@enterCoordinatesManually": { + "description": "Description for manual coordinate input option" + }, + + "latitudeLabel": "Latitude", + "@latitudeLabel": { + "description": "Label for latitude input field" + }, + + "longitudeLabel": "Longitude", + "@longitudeLabel": { + "description": "Label for longitude input field" + }, + + "invalidLatitude": "Invalid latitude (-90 to 90)", + "@invalidLatitude": { + "description": "Error message for invalid latitude value" + }, + + "invalidLongitude": "Invalid longitude (-180 to 180)", + "@invalidLongitude": { + "description": "Error message for invalid longitude value" + }, + + "exampleCoordinates": "Example: 46.0569, 14.5058", + "@exampleCoordinates": { + "description": "Example coordinate format hint" + }, + + "drawingShared": "Map Drawing", + "@drawingShared": { + "description": "Label for shared drawing notifications" + }, + + "drawingHidden": "Drawing hidden from map", + "@drawingHidden": { + "description": "Success message when drawing is hidden" + }, + + "alreadyShared": "{count, plural, =1{1 already shared} other{{count} already shared}}", + "@alreadyShared": { + "description": "Message showing how many drawings were already shared", + "placeholders": { + "count": { + "type": "int" + } + } + }, + + "newDrawingsShared": "Shared {count} new drawing{plural}", + "@newDrawingsShared": { + "description": "Success message after sharing new drawings", + "placeholders": { + "count": { + "type": "int" + }, + "plural": { + "type": "String" + } + } + }, + + "shareDrawing": "Share Drawing", + "@shareDrawing": { + "description": "Title for share single drawing dialog" + }, + + "shareWithAllNearbyDevices": "Share with all nearby devices", + "@shareWithAllNearbyDevices": { + "description": "Subtitle for public channel sharing option" + }, + + "shareToRoom": "Share to Room", + "@shareToRoom": { + "description": "Header for room sharing section" + }, + + "sendToPersistentStorage": "Send to persistent room storage", + "@sendToPersistentStorage": { + "description": "Subtitle for room sharing option" + }, + + "deleteDrawingConfirm": "Are you sure you want to delete this drawing?", + "@deleteDrawingConfirm": { + "description": "Confirmation message for deleting a single drawing" + }, + + "drawingDeleted": "Drawing deleted", + "@drawingDeleted": { + "description": "Success message after deleting a drawing" + }, + + "yourDrawingsCount": "Your Drawings ({count})", + "@yourDrawingsCount": { + "description": "Header showing count of user's drawings", + "placeholders": { + "count": { + "type": "int" + } + } + }, + + "shared": "Shared", + "@shared": { + "description": "Status label for shared drawings" + }, + + "line": "Line", + "@line": { + "description": "Line drawing type label" + }, + + "rectangle": "Rectangle", + "@rectangle": { + "description": "Rectangle drawing type label" + }, + + "updateAvailable": "Update Available", + "@updateAvailable": { + "description": "Title for update dialog when new version is available" + }, + + "currentVersion": "Current", + "@currentVersion": { + "description": "Label for current app version" + }, + + "latestVersion": "Latest", + "@latestVersion": { + "description": "Label for latest available app version" + }, + + "downloadUpdate": "Download", + "@downloadUpdate": { + "description": "Button to download app update" + }, + + "updateLater": "Later", + "@updateLater": { + "description": "Button to dismiss update dialog" + }, + + "cadastralParcels": "Cadastral Parcels", + "@cadastralParcels": { + "description": "Label for cadastral parcels WMS overlay layer" + }, + + "forestRoads": "Forest Roads", + "@forestRoads": { + "description": "Label for forest roads WMS overlay layer" + }, + + "showCadastralParcels": "Show Cadastral Parcels", + "@showCadastralParcels": { + "description": "Tooltip for cadastral parcels overlay toggle button" + }, + + "showForestRoads": "Show Forest Roads", + "@showForestRoads": { + "description": "Tooltip for forest roads overlay toggle button" + }, + + "wmsOverlays": "WMS Overlays", + "@wmsOverlays": { + "description": "Section header for WMS overlay layers in layer selector" + }, + + "hikingTrails": "Hiking Trails", + "@hikingTrails": { + "description": "Label for hiking/mountain trails WMS overlay layer" + }, + + "mainRoads": "Main Roads", + "@mainRoads": { + "description": "Label for main roads WMS overlay layer" + }, + + "houseNumbers": "House Numbers", + "@houseNumbers": { + "description": "Label for house numbers WMS overlay layer" + }, + + "fireHazardZones": "Fire Hazard Zones", + "@fireHazardZones": { + "description": "Label for fire hazard risk zones WMS overlay layer" + }, + + "historicalFires": "Historical Fires", + "@historicalFires": { + "description": "Label for historical forest fires WMS overlay layer" + }, + + "firebreaks": "Firebreaks", + "@firebreaks": { + "description": "Label for firebreaks WMS overlay layer" + }, + + "krasFireZones": "Kras Fire Zones", + "@krasFireZones": { + "description": "Label for Kras fire zones WMS overlay layer" + }, + + "placeNames": "Place Names", + "@placeNames": { + "description": "Label for geographic place names WMS overlay layer" + }, + + "municipalityBorders": "Municipality Borders", + "@municipalityBorders": { + "description": "Label for municipality borders WMS overlay layer" + }, + + "topographicMap": "Topographic Map 1:25000", + "@topographicMap": { + "description": "Label for DTK25 topographic base map layer" + }, + + "recentMessages": "Recent Messages", + "@recentMessages": { + "description": "Header for recent messages overlay on map in fullscreen mode" + }, + + "addChannel": "Add Channel", + "@addChannel": { + "description": "Button to add a new channel" + }, + + "channelName": "Channel Name", + "@channelName": { + "description": "Label for channel name field" + }, + + "channelNameHint": "e.g., Rescue Team Alpha", + "@channelNameHint": { + "description": "Hint for channel name field" + }, + + "channelSecret": "Channel Secret", + "@channelSecret": { + "description": "Label for channel secret field" + }, + + "channelSecretHint": "Shared password for this channel", + "@channelSecretHint": { + "description": "Hint for channel secret field" + }, + + "channelSecretHelp": "This secret must be shared with all team members who need access to this channel", + "@channelSecretHelp": { + "description": "Help text explaining channel secret" + }, + + "channelTypesInfo": "Hash channels (#team): Secret auto-generated from name. Same name = same channel across devices.\n\nPrivate channels: Use explicit secret. Only those with the secret can join.", + "@channelTypesInfo": { + "description": "Information banner explaining hash and private channel types" + }, + + "hashChannelInfo": "Hash channel: Secret will be auto-generated from the channel name. Anyone using the same name will join the same channel.", + "@hashChannelInfo": { + "description": "Help text for hash channels (# prefix)" + }, + + "channelNameRequired": "Channel name is required", + "@channelNameRequired": { + "description": "Validation error for empty channel name" + }, + + "channelNameTooLong": "Channel name must be 31 characters or less", + "@channelNameTooLong": { + "description": "Validation error for channel name too long" + }, + + "channelSecretRequired": "Channel secret is required", + "@channelSecretRequired": { + "description": "Validation error for empty channel secret" + }, + + "channelSecretTooLong": "Channel secret must be 32 characters or less", + "@channelSecretTooLong": { + "description": "Validation error for channel secret too long" + }, + + "invalidAsciiCharacters": "Only ASCII characters are allowed", + "@invalidAsciiCharacters": { + "description": "Validation error for non-ASCII characters" + }, + + "channelCreatedSuccessfully": "Channel created successfully", + "@channelCreatedSuccessfully": { + "description": "Success message after creating channel" + }, + + "channelCreationFailed": "Failed to create channel: {error}", + "@channelCreationFailed": { + "description": "Error message when channel creation fails", + "placeholders": { + "error": { + "type": "String" + } + } + }, + + "deleteChannel": "Delete Channel", + "@deleteChannel": { + "description": "Delete channel button/menu item" + }, + + "deleteChannelConfirmation": "Are you sure you want to delete channel \"{channelName}\"? This action cannot be undone.", + "@deleteChannelConfirmation": { + "description": "Confirmation dialog when deleting a channel", + "placeholders": { + "channelName": { + "type": "String" + } + } + }, + + "channelDeletedSuccessfully": "Channel deleted successfully", + "@channelDeletedSuccessfully": { + "description": "Success message after deleting channel" + }, + + "channelDeletionFailed": "Failed to delete channel: {error}", + "@channelDeletionFailed": { + "description": "Error message when channel deletion fails", + "placeholders": { + "error": { + "type": "String" + } + } + }, + + "allChannelSlotsInUse": "All channel slots are in use (maximum 39 custom channels)", + "@allChannelSlotsInUse": { + "description": "Error when no channel slots available" + }, + + "createChannel": "Create Channel", + "@createChannel": { + "description": "Button text for creating a channel" + }, + + "wizardBack": "Back", + "@wizardBack": { + "description": "Wizard back button text" + }, + + "wizardSkip": "Skip", + "@wizardSkip": { + "description": "Wizard skip button text" + }, + + "wizardNext": "Next", + "@wizardNext": { + "description": "Wizard next button text" + }, + + "wizardGetStarted": "Get Started", + "@wizardGetStarted": { + "description": "Wizard final button text to complete onboarding" + }, + + "wizardWelcomeTitle": "Welcome to MeshCore SAR", + "@wizardWelcomeTitle": { + "description": "Welcome wizard first page title" + }, + + "wizardWelcomeDescription": "A powerful off-grid communication tool for search and rescue operations. Connect with your team using mesh radio technology when traditional networks are unavailable.", + "@wizardWelcomeDescription": { + "description": "Welcome wizard first page description" + }, + + "wizardConnectingTitle": "Connecting to Your Radio", + "@wizardConnectingTitle": { + "description": "Wizard connecting page title" + }, + + "wizardConnectingDescription": "Connect your smartphone to a MeshCore radio device via Bluetooth to start communicating off-grid.", + "@wizardConnectingDescription": { + "description": "Wizard connecting page description" + }, + + "wizardConnectingFeature1": "Scan for nearby MeshCore devices", + "@wizardConnectingFeature1": { + "description": "Wizard connecting feature 1" + }, + + "wizardConnectingFeature2": "Pair with your radio via Bluetooth", + "@wizardConnectingFeature2": { + "description": "Wizard connecting feature 2" + }, + + "wizardConnectingFeature3": "Works completely offline - no internet required", + "@wizardConnectingFeature3": { + "description": "Wizard connecting feature 3" + }, + + "wizardSimpleModeTitle": "Simple Mode", + "@wizardSimpleModeTitle": { + "description": "Wizard simple mode page title" + }, + + "wizardSimpleModeDescription": "New to mesh networking? Enable Simple Mode for a streamlined interface with essential features only.", + "@wizardSimpleModeDescription": { + "description": "Wizard simple mode page description" + }, + + "wizardSimpleModeFeature1": "Beginner-friendly interface with core functions", + "@wizardSimpleModeFeature1": { + "description": "Wizard simple mode feature 1" + }, + + "wizardSimpleModeFeature2": "Switch to Advanced Mode anytime in Settings", + "@wizardSimpleModeFeature2": { + "description": "Wizard simple mode feature 2" + }, + + "wizardChannelTitle": "Channels", + "@wizardChannelTitle": { + "description": "Wizard channel page title" + }, + + "wizardChannelDescription": "Broadcast messages to everyone on a channel, perfect for team-wide announcements and coordination.", + "@wizardChannelDescription": { + "description": "Wizard channel page description" + }, + + "wizardChannelFeature1": "Public Channel for general team communication", + "@wizardChannelFeature1": { + "description": "Wizard channel feature 1" + }, + + "wizardChannelFeature2": "Create custom channels for specific groups", + "@wizardChannelFeature2": { + "description": "Wizard channel feature 2" + }, + + "wizardChannelFeature3": "Messages are automatically relayed by the mesh", + "@wizardChannelFeature3": { + "description": "Wizard channel feature 3" + }, + + "wizardContactsTitle": "Contacts", + "@wizardContactsTitle": { + "description": "Wizard contacts page title" + }, + + "wizardContactsDescription": "Your team members appear automatically as they join the mesh network. Send them direct messages or view their location.", + "@wizardContactsDescription": { + "description": "Wizard contacts page description" + }, + + "wizardContactsFeature1": "Contacts discovered automatically", + "@wizardContactsFeature1": { + "description": "Wizard contacts feature 1" + }, + + "wizardContactsFeature2": "Send private direct messages", + "@wizardContactsFeature2": { + "description": "Wizard contacts feature 2" + }, + + "wizardContactsFeature3": "View battery level and last seen time", + "@wizardContactsFeature3": { + "description": "Wizard contacts feature 3" + }, + + "wizardMapTitle": "Map & Location", + "@wizardMapTitle": { + "description": "Wizard map page title" + }, + + "wizardMapDescription": "Track your team in real-time and mark important locations for search and rescue operations.", + "@wizardMapDescription": { + "description": "Wizard map page description" + }, + + "wizardMapFeature1": "SAR markers for found persons, fires, and staging areas", + "@wizardMapFeature1": { + "description": "Wizard map feature 1" + }, + + "wizardMapFeature2": "Real-time GPS tracking of team members", + "@wizardMapFeature2": { + "description": "Wizard map feature 2" + }, + + "wizardMapFeature3": "Download offline maps for remote areas", + "@wizardMapFeature3": { + "description": "Wizard map feature 3" + }, + + "wizardMapFeature4": "Draw shapes and share tactical information", + "@wizardMapFeature4": { + "description": "Wizard map feature 4" + }, + + "viewWelcomeTutorial": "View Welcome Tutorial", + "@viewWelcomeTutorial": { + "description": "Settings option to re-show welcome wizard" + }, + + "allTeamContacts": "All Team Contacts", + "@allTeamContacts": { + "description": "Destination option to send SAR marker to all team contacts" + }, + + "directMessagesInfo": "Direct messages with ACKs. Sent to {count} team members.", + "@directMessagesInfo": { + "description": "Information about sending to all contacts", + "placeholders": { + "count": { + "type": "int" + } + } + }, + + "sarMarkerSentToContacts": "SAR marker sent to {count} contacts", + "@sarMarkerSentToContacts": { + "description": "Success message after sending SAR marker to all contacts", + "placeholders": { + "count": { + "type": "int" + } + } + }, + + "noContactsAvailable": "No team contacts available", + "@noContactsAvailable": { + "description": "Message when there are no chat contacts to send to" + } +} diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index fb4f651..b1c44e8 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -6,6 +6,7 @@ import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:intl/intl.dart' as intl; import 'app_localizations_de.dart'; +import 'app_localizations_el.dart'; import 'app_localizations_en.dart'; import 'app_localizations_es.dart'; import 'app_localizations_fr.dart'; @@ -100,6 +101,7 @@ abstract class AppLocalizations { /// A list of this localizations delegate's supported locales. static const List supportedLocales = [ Locale('de'), + Locale('el'), Locale('en'), Locale('es'), Locale('fr'), @@ -4066,6 +4068,7 @@ class _AppLocalizationsDelegate @override bool isSupported(Locale locale) => [ 'de', + 'el', 'en', 'es', 'fr', @@ -4083,6 +4086,8 @@ AppLocalizations lookupAppLocalizations(Locale locale) { switch (locale.languageCode) { case 'de': return AppLocalizationsDe(); + case 'el': + return AppLocalizationsEl(); case 'en': return AppLocalizationsEn(); case 'es': diff --git a/lib/l10n/app_localizations_el.dart b/lib/l10n/app_localizations_el.dart new file mode 100644 index 0000000..c122f87 --- /dev/null +++ b/lib/l10n/app_localizations_el.dart @@ -0,0 +1,2251 @@ +// ignore: unused_import +import 'package:intl/intl.dart' as intl; +import 'app_localizations.dart'; + +// ignore_for_file: type=lint + +/// The translations for Modern Greek (`el`). +class AppLocalizationsEl extends AppLocalizations { + AppLocalizationsEl([String locale = 'el']) : super(locale); + + @override + String get appTitle => 'MeshCore SAR'; + + @override + String get messages => 'Messages'; + + @override + String get contacts => 'Contacts'; + + @override + String get map => 'Map'; + + @override + String get settings => 'Settings'; + + @override + String get connect => 'Connect'; + + @override + String get disconnect => 'Disconnect'; + + @override + String get scanningForDevices => 'Scanning for devices...'; + + @override + String get noDevicesFound => 'No devices found'; + + @override + String get scanAgain => 'Scan Again'; + + @override + String get tapToConnect => 'Tap to connect'; + + @override + String get deviceNotConnected => 'Device not connected'; + + @override + String get locationPermissionDenied => 'Location permission denied'; + + @override + String get locationPermissionPermanentlyDenied => + 'Location permission permanently denied. Please enable in Settings.'; + + @override + String get locationPermissionRequired => + 'Location permission is required for GPS tracking and team coordination. You can enable it later in Settings.'; + + @override + String get locationServicesDisabled => + 'Location services are disabled. Please enable them in Settings.'; + + @override + String get failedToGetGpsLocation => 'Failed to get GPS location'; + + @override + String advertisedAtLocation(String latitude, String longitude) { + return 'Advertised at $latitude, $longitude'; + } + + @override + String failedToAdvertise(String error) { + return 'Failed to advertise: $error'; + } + + @override + String reconnecting(int attempt, int max) { + return 'Reconnecting... ($attempt/$max)'; + } + + @override + String get cancelReconnection => 'Cancel reconnection'; + + @override + String get mapManagement => 'Map Management'; + + @override + String get general => 'General'; + + @override + String get theme => 'Theme'; + + @override + String get chooseTheme => 'Choose Theme'; + + @override + String get light => 'Light'; + + @override + String get dark => 'Dark'; + + @override + String get blueLightTheme => 'Blue light theme'; + + @override + String get blueDarkTheme => 'Blue dark theme'; + + @override + String get sarRed => 'SAR Red'; + + @override + String get alertEmergencyMode => 'Alert/Emergency mode'; + + @override + String get sarGreen => 'SAR Green'; + + @override + String get safeAllClearMode => 'Safe/All Clear mode'; + + @override + String get autoSystem => 'Auto (System)'; + + @override + String get followSystemTheme => 'Follow system theme'; + + @override + String get showRxTxIndicators => 'Show RX/TX Indicators'; + + @override + String get displayPacketActivity => + 'Display packet activity indicators in top bar'; + + @override + String get simpleMode => 'Simple Mode'; + + @override + String get simpleModeDescription => + 'Hide non-essential information in messages and contacts'; + + @override + String get disableMap => 'Disable Map'; + + @override + String get disableMapDescription => + 'Hide the map tab to reduce battery usage'; + + @override + String get language => 'Language'; + + @override + String get chooseLanguage => 'Choose Language'; + + @override + String get english => 'English'; + + @override + String get slovenian => 'Slovenian'; + + @override + String get croatian => 'Croatian'; + + @override + String get german => 'German'; + + @override + String get spanish => 'Spanish'; + + @override + String get french => 'French'; + + @override + String get italian => 'Italian'; + + @override + String get locationBroadcasting => 'Location Broadcasting'; + + @override + String get autoLocationTracking => 'Auto Location Tracking'; + + @override + String get automaticallyBroadcastPosition => + 'Automatically broadcast position updates'; + + @override + String get configureTracking => 'Configure Tracking'; + + @override + String get distanceAndTimeThresholds => 'Distance and time thresholds'; + + @override + String get locationTrackingConfiguration => 'Location Tracking Configuration'; + + @override + String get configureWhenLocationBroadcasts => + 'Configure when location broadcasts are sent to the mesh network'; + + @override + String get minimumDistance => 'Minimum Distance'; + + @override + String broadcastAfterMoving(String distance) { + return 'Broadcast only after moving $distance meters'; + } + + @override + String get maximumDistance => 'Maximum Distance'; + + @override + String alwaysBroadcastAfterMoving(String distance) { + return 'Always broadcast after moving $distance meters'; + } + + @override + String get minimumTimeInterval => 'Minimum Time Interval'; + + @override + String alwaysBroadcastEvery(String duration) { + return 'Always broadcast every $duration'; + } + + @override + String get save => 'Save'; + + @override + String get cancel => 'Cancel'; + + @override + String get close => 'Close'; + + @override + String get about => 'About'; + + @override + String get appVersion => 'App Version'; + + @override + String get appName => 'App Name'; + + @override + String get aboutMeshCoreSar => 'About MeshCore SAR'; + + @override + String get aboutDescription => + 'A Search & Rescue application designed for emergency response teams. Features include:\n\n• BLE mesh networking for device-to-device communication\n• Offline maps with multiple layer options\n• Real-time team member tracking\n• SAR tactical markers (found person, fire, staging)\n• Contact management and messaging\n• GPS tracking with compass heading\n• Map tile caching for offline use'; + + @override + String get technologiesUsed => 'Technologies Used:'; + + @override + String get technologiesList => + '• Flutter for cross-platform development\n• BLE (Bluetooth Low Energy) for mesh networking\n• OpenStreetMap for mapping\n• Provider for state management\n• SharedPreferences for local storage'; + + @override + String get moreInfo => 'More Info'; + + @override + String get learnMoreAbout => 'Learn more about MeshCore SAR'; + + @override + String get developer => 'Developer'; + + @override + String get packageName => 'Package Name'; + + @override + String get sampleData => 'Sample Data'; + + @override + String get sampleDataDescription => + 'Load or clear sample contacts, channel messages, and SAR markers for testing'; + + @override + String get loadSampleData => 'Load Sample Data'; + + @override + String get clearAllData => 'Clear All Data'; + + @override + String get clearAllDataConfirmTitle => 'Clear All Data'; + + @override + String get clearAllDataConfirmMessage => + 'This will clear all contacts and SAR markers. Are you sure?'; + + @override + String get clear => 'Clear'; + + @override + String loadedSampleData( + int teamCount, + int channelCount, + int sarCount, + int messageCount, + ) { + return 'Loaded $teamCount team members, $channelCount channels, $sarCount SAR markers, $messageCount messages'; + } + + @override + String failedToLoadSampleData(String error) { + return 'Failed to load sample data: $error'; + } + + @override + String get allDataCleared => 'All data cleared'; + + @override + String get failedToStartBackgroundTracking => + 'Failed to start background tracking. Check permissions and BLE connection.'; + + @override + String locationBroadcast(String latitude, String longitude) { + return 'Location broadcast: $latitude, $longitude'; + } + + @override + String get defaultPinInfo => + 'The default pin for devices without a screen is 123456. Trouble pairing? Forget the bluetooth device in system settings.'; + + @override + String get noMessagesYet => 'No messages yet'; + + @override + String get pullDownToSync => 'Pull down to sync messages'; + + @override + String get deleteContact => 'Delete Contact'; + + @override + String get delete => 'Delete'; + + @override + String get viewOnMap => 'View on Map'; + + @override + String get refresh => 'Refresh'; + + @override + String get sendDirectMessage => 'Send'; + + @override + String get resetPath => 'Reset Path (Re-route)'; + + @override + String get publicKeyCopied => 'Public key copied to clipboard'; + + @override + String copiedToClipboard(String label) { + return '$label copied to clipboard'; + } + + @override + String get pleaseEnterPassword => 'Please enter a password'; + + @override + String failedToSyncContacts(String error) { + return 'Failed to sync contacts: $error'; + } + + @override + String get loggedInSuccessfully => + 'Logged in successfully! Waiting for room messages...'; + + @override + String get loginFailed => 'Login failed - incorrect password'; + + @override + String loggingIn(String roomName) { + return 'Logging in to $roomName...'; + } + + @override + String failedToSendLogin(String error) { + return 'Failed to send login: $error'; + } + + @override + String get lowLocationAccuracy => 'Low Location Accuracy'; + + @override + String get continue_ => 'Continue'; + + @override + String get sendSarMarker => 'Send SAR marker'; + + @override + String get deleteDrawing => 'Delete Drawing'; + + @override + String get drawingTools => 'Drawing Tools'; + + @override + String get drawLine => 'Draw Line'; + + @override + String get drawLineDesc => 'Draw a freehand line on the map'; + + @override + String get drawRectangle => 'Draw Rectangle'; + + @override + String get drawRectangleDesc => 'Draw a rectangular area on the map'; + + @override + String get measureDistance => 'Measure Distance'; + + @override + String get measureDistanceDesc => 'Long press two points to measure'; + + @override + String get clearMeasurement => 'Clear Measurement'; + + @override + String distanceLabel(String distance) { + return 'Distance: $distance'; + } + + @override + String get longPressForSecondPoint => 'Long press for second point'; + + @override + String get longPressToStartMeasurement => 'Long press to set first point'; + + @override + String get longPressToStartNewMeasurement => + 'Long press to start new measurement'; + + @override + String get shareDrawings => 'Share Drawings'; + + @override + String get clearAllDrawings => 'Clear All Drawings'; + + @override + String get completeLine => 'Complete Line'; + + @override + String broadcastDrawingsToTeam(int count, String plural) { + return 'Broadcast $count drawing$plural to team'; + } + + @override + String removeAllDrawings(int count, String plural) { + return 'Remove all $count drawing$plural'; + } + + @override + String deleteAllDrawingsConfirm(int count, String plural) { + return 'Delete all $count drawing$plural from the map?'; + } + + @override + String get drawing => 'Drawing'; + + @override + String shareDrawingsCount(int count, String plural) { + return 'Share $count Drawing$plural'; + } + + @override + String sentDrawingsToRoom(int count, String plural, String roomName) { + return 'Sent $count map drawing$plural to $roomName'; + } + + @override + String sharedDrawingsToRoom( + int success, + int total, + String plural, + String roomName, + ) { + return 'Shared $success/$total drawing$plural to $roomName'; + } + + @override + String get showReceivedDrawings => 'Show Received Drawings'; + + @override + String get showingAllDrawings => 'Showing all drawings'; + + @override + String get showingOnlyYourDrawings => 'Showing only your drawings'; + + @override + String get showSarMarkers => 'Show SAR Markers'; + + @override + String get showingSarMarkers => 'Showing SAR markers'; + + @override + String get hidingSarMarkers => 'Hiding SAR markers'; + + @override + String get clearAll => 'Clear All'; + + @override + String get noLocalDrawings => 'No local drawings to share'; + + @override + String get publicChannel => 'Public Channel'; + + @override + String get broadcastToAll => 'Broadcast to all nearby nodes (ephemeral)'; + + @override + String get storedPermanently => 'Stored permanently in room'; + + @override + String drawingsSentToPublicChannel(int count, String plural) { + return 'Sent $count map drawing$plural to Public Channel'; + } + + @override + String drawingsSharedToPublicChannel(int success, int total) { + return 'Shared $success/$total drawings to Public Channel'; + } + + @override + String get notConnectedToDevice => 'Not connected to device'; + + @override + String get directMessage => 'Direct Message'; + + @override + String directMessageSentTo(String contactName) { + return 'Direct message sent to $contactName'; + } + + @override + String failedToSend(String error) { + return 'Failed to send: $error'; + } + + @override + String directMessageInfo(String contactName) { + return 'This message will be sent directly to $contactName. It will also appear in the main messages feed.'; + } + + @override + String get typeYourMessage => 'Type your message...'; + + @override + String get quickLocationMarker => 'Quick location marker'; + + @override + String get markerType => 'Marker Type'; + + @override + String get sendTo => 'Send To'; + + @override + String get noDestinationsAvailable => 'No destinations available.'; + + @override + String get selectDestination => 'Select destination...'; + + @override + String get ephemeralBroadcastInfo => + 'Ephemeral: Broadcast over-the-air only. Not stored - nodes must be online.'; + + @override + String get persistentRoomInfo => + 'Persistent: Stored immutably in room. Synced automatically and preserved offline.'; + + @override + String get location => 'Location'; + + @override + String get myLocation => 'My Location'; + + @override + String get fromMap => 'From Map'; + + @override + String get gettingLocation => 'Getting location...'; + + @override + String get locationError => 'Location Error'; + + @override + String get retry => 'Retry'; + + @override + String get refreshLocation => 'Refresh location'; + + @override + String accuracyMeters(int accuracy) { + return 'Accuracy: ±${accuracy}m'; + } + + @override + String get notesOptional => 'Notes (optional)'; + + @override + String get addAdditionalInformation => 'Add additional information...'; + + @override + String lowAccuracyWarning(int accuracy) { + return 'Location accuracy is ±${accuracy}m. This may not be accurate enough for SAR operations.\n\nContinue anyway?'; + } + + @override + String get loginToRoom => 'Login to Room'; + + @override + String get enterPasswordInfo => + 'Enter the password to access this room. The password will be saved for future use.'; + + @override + String get password => 'Password'; + + @override + String get enterRoomPassword => 'Enter room password'; + + @override + String get loggingInDots => 'Logging in...'; + + @override + String get login => 'Login'; + + @override + String failedToAddRoom(String error) { + return 'Failed to add room to device: $error\n\nThe room may not have advertised yet.\nTry waiting for the room to broadcast.'; + } + + @override + String get direct => 'Direct'; + + @override + String get flood => 'Flood'; + + @override + String get admin => 'Admin'; + + @override + String get loggedIn => 'Logged In'; + + @override + String get noGpsData => 'No GPS data'; + + @override + String get distance => 'Distance'; + + @override + String pingingDirect(String name) { + return 'Pinging $name (direct via path)...'; + } + + @override + String pingingFlood(String name) { + return 'Pinging $name (flooding - no path)...'; + } + + @override + String directPingTimeout(String name) { + return 'Direct ping timeout - retrying $name with flooding...'; + } + + @override + String pingSuccessful(String name, String fallback) { + return 'Ping successful to $name$fallback'; + } + + @override + String get viaFloodingFallback => ' (via flooding fallback)'; + + @override + String pingFailed(String name) { + return 'Ping failed to $name - no response received'; + } + + @override + String deleteContactConfirmation(String name) { + return 'Are you sure you want to delete \"$name\"?\n\nThis will remove the contact from both the app and the companion radio device.'; + } + + @override + String removingContact(String name) { + return 'Removing $name...'; + } + + @override + String contactRemoved(String name) { + return 'Contact \"$name\" removed'; + } + + @override + String failedToRemoveContact(String error) { + return 'Failed to remove contact: $error'; + } + + @override + String get type => 'Type'; + + @override + String get publicKey => 'Public Key'; + + @override + String get lastSeen => 'Last Seen'; + + @override + String get roomStatus => 'Room Status'; + + @override + String get loginStatus => 'Login Status'; + + @override + String get notLoggedIn => 'Not Logged In'; + + @override + String get adminAccess => 'Admin Access'; + + @override + String get yes => 'Yes'; + + @override + String get no => 'No'; + + @override + String get permissions => 'Permissions'; + + @override + String get passwordSaved => 'Password Saved'; + + @override + String get locationColon => 'Location:'; + + @override + String get telemetry => 'Telemetry'; + + @override + String requestingTelemetry(String name) { + return 'Requesting telemetry from $name...'; + } + + @override + String get voltage => 'Voltage'; + + @override + String get battery => 'Battery'; + + @override + String get temperature => 'Temperature'; + + @override + String get humidity => 'Humidity'; + + @override + String get pressure => 'Pressure'; + + @override + String get gpsTelemetry => 'GPS (Telemetry)'; + + @override + String get updated => 'Updated'; + + @override + String pathResetInfo(String name) { + return 'Path reset for $name. Next message will find a new route.'; + } + + @override + String get reLoginToRoom => 'Re-Login to Room'; + + @override + String get heading => 'Heading'; + + @override + String get elevation => 'Elevation'; + + @override + String get accuracy => 'Accuracy'; + + @override + String get bearing => 'Bearing'; + + @override + String get direction => 'Direction'; + + @override + String get filterMarkers => 'Filter Markers'; + + @override + String get filterMarkersTooltip => 'Filter markers'; + + @override + String get contactsFilter => 'Contacts'; + + @override + String get repeatersFilter => 'Repeaters'; + + @override + String get sarMarkers => 'SAR Markers'; + + @override + String get foundPerson => 'Found Person'; + + @override + String get fire => 'Fire'; + + @override + String get stagingArea => 'Staging Area'; + + @override + String get showAll => 'Show All'; + + @override + String get nearbyContacts => 'Nearby Contacts'; + + @override + String get locationUnavailable => 'Location unavailable'; + + @override + String get ahead => 'ahead'; + + @override + String degreesRight(int degrees) { + return '$degrees° right'; + } + + @override + String degreesLeft(int degrees) { + return '$degrees° left'; + } + + @override + String latLonFormat(String latitude, String longitude) { + return 'Lat: $latitude Lon: $longitude'; + } + + @override + String get noContactsYet => 'No contacts yet'; + + @override + String get connectToDeviceToLoadContacts => + 'Connect to a device to load contacts'; + + @override + String get teamMembers => 'Team Members'; + + @override + String get repeaters => 'Repeaters'; + + @override + String get rooms => 'Rooms'; + + @override + String get channels => 'Channels'; + + @override + String get cacheStatistics => 'Cache Statistics'; + + @override + String get totalTiles => 'Total Tiles'; + + @override + String get cacheSize => 'Cache Size'; + + @override + String get storeName => 'Store Name'; + + @override + String get noCacheStatistics => 'No cache statistics available'; + + @override + String get downloadRegion => 'Download Region'; + + @override + String get mapLayer => 'Map Layer'; + + @override + String get regionBounds => 'Region Bounds'; + + @override + String get north => 'North'; + + @override + String get south => 'South'; + + @override + String get east => 'East'; + + @override + String get west => 'West'; + + @override + String get zoomLevels => 'Zoom Levels'; + + @override + String minZoom(int zoom) { + return 'Min: $zoom'; + } + + @override + String maxZoom(int zoom) { + return 'Max: $zoom'; + } + + @override + String get downloadingDots => 'Downloading...'; + + @override + String get cancelDownload => 'Cancel Download'; + + @override + String get downloadRegionButton => 'Download Region'; + + @override + String get downloadNote => + 'Note: Large regions or high zoom levels may take significant time and storage.'; + + @override + String get cacheManagement => 'Cache Management'; + + @override + String get clearAllMaps => 'Clear All Maps'; + + @override + String get clearMapsConfirmTitle => 'Clear All Maps'; + + @override + String get clearMapsConfirmMessage => + 'Are you sure you want to delete all downloaded maps? This action cannot be undone.'; + + @override + String get mapDownloadCompleted => 'Map download completed!'; + + @override + String get cacheClearedSuccessfully => 'Cache cleared successfully!'; + + @override + String get downloadCancelled => 'Download cancelled'; + + @override + String get startingDownload => 'Starting download...'; + + @override + String get downloadingMapTiles => 'Downloading map tiles...'; + + @override + String get downloadCompletedSuccessfully => + 'Download completed successfully!'; + + @override + String get cancellingDownload => 'Cancelling download...'; + + @override + String errorLoadingStats(String error) { + return 'Error loading stats: $error'; + } + + @override + String downloadFailed(String error) { + return 'Download failed: $error'; + } + + @override + String cancelFailed(String error) { + return 'Cancel failed: $error'; + } + + @override + String clearCacheFailed(String error) { + return 'Clear cache failed: $error'; + } + + @override + String minZoomError(String error) { + return 'Min zoom: $error'; + } + + @override + String maxZoomError(String error) { + return 'Max zoom: $error'; + } + + @override + String get minZoomGreaterThanMax => + 'Minimum zoom must be less than or equal to maximum zoom'; + + @override + String get selectMapLayer => 'Select Map Layer'; + + @override + String get mapOptions => 'Map Options'; + + @override + String get showLegend => 'Show Legend'; + + @override + String get displayMarkerTypeCounts => 'Display marker type counts'; + + @override + String get rotateMapWithHeading => 'Rotate Map with Heading'; + + @override + String get mapFollowsDirection => 'Map follows your direction when moving'; + + @override + String get resetMapRotation => 'Reset Rotation'; + + @override + String get resetMapRotationTooltip => 'Reset map to north'; + + @override + String get showMapDebugInfo => 'Show Map Debug Info'; + + @override + String get displayZoomLevelBounds => 'Display zoom level and bounds'; + + @override + String get fullscreenMode => 'Fullscreen Mode'; + + @override + String get hideUiFullMapView => 'Hide all UI controls for full map view'; + + @override + String get openStreetMap => 'OpenStreetMap'; + + @override + String get openTopoMap => 'OpenTopoMap'; + + @override + String get esriSatellite => 'ESRI Satellite'; + + @override + String get googleHybrid => 'Google Hybrid'; + + @override + String get googleRoadmap => 'Google Roadmap'; + + @override + String get googleTerrain => 'Google Terrain'; + + @override + String get downloadVisibleArea => 'Download visible area'; + + @override + String get initializingMap => 'Initializing map...'; + + @override + String get dragToPosition => 'Drag to Position'; + + @override + String get createSarMarker => 'Create SAR Marker'; + + @override + String get compass => 'Compass'; + + @override + String get navigationAndContacts => 'Navigation & Contacts'; + + @override + String get sarAlert => 'SAR ALERT'; + + @override + String get messageSentToPublicChannel => 'Message sent to public channel'; + + @override + String get pleaseSelectRoomToSendSar => + 'Please select a room to send SAR marker'; + + @override + String failedToSendSarMarker(String error) { + return 'Failed to send SAR marker: $error'; + } + + @override + String sarMarkerSentTo(String roomName) { + return 'SAR marker sent to $roomName'; + } + + @override + String get notConnectedCannotSync => 'Not connected - cannot sync messages'; + + @override + String syncedMessageCount(int count) { + return 'Synced $count message(s)'; + } + + @override + String get noNewMessages => 'No new messages'; + + @override + String syncFailed(String error) { + return 'Sync failed: $error'; + } + + @override + String get failedToResendMessage => 'Failed to resend message'; + + @override + String get retryingMessage => 'Retrying message...'; + + @override + String retryFailed(String error) { + return 'Retry failed: $error'; + } + + @override + String get textCopiedToClipboard => 'Text copied to clipboard'; + + @override + String get cannotReplySenderMissing => + 'Cannot reply: sender information missing'; + + @override + String get cannotReplyContactNotFound => 'Cannot reply: contact not found'; + + @override + String get messageDeleted => 'Message deleted'; + + @override + String get copyText => 'Copy text'; + + @override + String get saveAsTemplate => 'Save as Template'; + + @override + String get templateSaved => 'Template saved successfully'; + + @override + String get templateAlreadyExists => 'Template with this emoji already exists'; + + @override + String get deleteMessage => 'Delete message'; + + @override + String get deleteMessageConfirmation => + 'Are you sure you want to delete this message?'; + + @override + String get shareLocation => 'Share location'; + + @override + String shareLocationText( + String markerInfo, + String lat, + String lon, + String url, + ) { + return '$markerInfo\n\nCoordinates: $lat, $lon\n\nGoogle Maps: $url'; + } + + @override + String get sarLocationShare => 'SAR Location'; + + @override + String get locationShared => 'Location shared'; + + @override + String get refreshedContacts => 'Refreshed contacts'; + + @override + String get justNow => 'Just now'; + + @override + String minutesAgo(int minutes) { + return '${minutes}m ago'; + } + + @override + String hoursAgo(int hours) { + return '${hours}h ago'; + } + + @override + String daysAgo(int days) { + return '${days}d ago'; + } + + @override + String secondsAgo(int seconds) { + return '${seconds}s ago'; + } + + @override + String get sending => 'Sending...'; + + @override + String get sent => 'Sent'; + + @override + String get delivered => 'Delivered'; + + @override + String deliveredWithTime(int time) { + return 'Delivered (${time}ms)'; + } + + @override + String get failed => 'Failed'; + + @override + String get broadcast => 'Broadcast'; + + @override + String deliveredToContacts(int delivered, int total) { + return 'Delivered to $delivered/$total contacts'; + } + + @override + String get allDelivered => 'All delivered'; + + @override + String get recipientDetails => 'Recipient Details'; + + @override + String get pending => 'Pending'; + + @override + String get sarMarkerFoundPerson => 'Found Person'; + + @override + String get sarMarkerFire => 'Fire Location'; + + @override + String get sarMarkerStagingArea => 'Staging Area'; + + @override + String get sarMarkerObject => 'Object Found'; + + @override + String get from => 'From'; + + @override + String get coordinates => 'Coordinates'; + + @override + String get tapToViewOnMap => 'Tap to view on map'; + + @override + String get radioSettings => 'Radio Settings'; + + @override + String get frequencyMHz => 'Frequency (MHz)'; + + @override + String get frequencyExample => 'e.g., 869.618'; + + @override + String get bandwidth => 'Bandwidth'; + + @override + String get spreadingFactor => 'Spreading Factor'; + + @override + String get codingRate => 'Coding Rate'; + + @override + String get txPowerDbm => 'TX Power (dBm)'; + + @override + String maxPowerDbm(int power) { + return 'Max: $power dBm'; + } + + @override + String get you => 'You'; + + @override + String get offlineVectorMaps => 'Offline Vector Maps'; + + @override + String get offlineVectorMapsDescription => + 'Import and manage offline vector map tiles (MBTiles format) for use without internet connection'; + + @override + String get importMbtiles => 'Import MBTiles File'; + + @override + String get importMbtilesNote => + 'Supports MBTiles files with vector tiles (PBF/MVT format). Geofabrik extracts work great!'; + + @override + String get noMbtilesFiles => 'No offline vector maps found'; + + @override + String get mbtilesImportedSuccessfully => + 'MBTiles file imported successfully'; + + @override + String get failedToImportMbtiles => 'Failed to import MBTiles file'; + + @override + String get deleteMbtilesConfirmTitle => 'Delete Offline Map'; + + @override + String deleteMbtilesConfirmMessage(String name) { + return 'Are you sure you want to delete \"$name\"? This will permanently remove the offline map.'; + } + + @override + String get mbtilesDeletedSuccessfully => 'Offline map deleted successfully'; + + @override + String get failedToDeleteMbtiles => 'Failed to delete offline map'; + + @override + String get importExportCachedTiles => 'Import/Export Cached Tiles'; + + @override + String get importExportDescription => + 'Backup, share, and restore downloaded map tiles between devices'; + + @override + String get exportTilesToFile => 'Export Tiles to File'; + + @override + String get importTilesFromFile => 'Import Tiles from File'; + + @override + String get selectExportLocation => 'Select Export Location'; + + @override + String get selectImportFile => 'Select Tile Archive'; + + @override + String get exportingTiles => 'Exporting tiles...'; + + @override + String get importingTiles => 'Importing tiles...'; + + @override + String exportSuccess(int count) { + return 'Exported $count tiles successfully'; + } + + @override + String importSuccess(int count) { + return 'Imported $count stores successfully'; + } + + @override + String exportFailed(String error) { + return 'Export failed: $error'; + } + + @override + String importFailed(String error) { + return 'Import failed: $error'; + } + + @override + String get exportNote => + 'Creates a compressed archive (.fmtc) file that can be shared and imported on other devices.'; + + @override + String get importNote => + 'Imports map tiles from a previously exported archive file. Tiles will be merged with existing cache.'; + + @override + String get noTilesToExport => 'No tiles available to export'; + + @override + String archiveContainsStores(int count) { + return 'Archive contains $count stores'; + } + + @override + String get vectorTiles => 'Vector Tiles'; + + @override + String get schema => 'Schema'; + + @override + String get unknown => 'Unknown'; + + @override + String get bounds => 'Bounds'; + + @override + String get onlineLayers => 'Online Layers'; + + @override + String get offlineLayers => 'Offline Layers'; + + @override + String get locationTrail => 'Location Trail'; + + @override + String get showTrailOnMap => 'Show Trail on Map'; + + @override + String get trailVisible => 'Trail is visible on the map'; + + @override + String get trailHiddenRecording => 'Trail is hidden (still recording)'; + + @override + String get duration => 'Duration'; + + @override + String get points => 'Points'; + + @override + String get clearTrail => 'Clear Trail'; + + @override + String get clearTrailQuestion => 'Clear Trail?'; + + @override + String get clearTrailConfirmation => + 'Are you sure you want to clear the current location trail? This action cannot be undone.'; + + @override + String get noTrailRecorded => 'No trail recorded yet'; + + @override + String get startTrackingToRecord => + 'Start location tracking to record your trail'; + + @override + String get trailControls => 'Trail Controls'; + + @override + String get exportTrailToGpx => 'Export Trail to GPX'; + + @override + String get importTrailFromGpx => 'Import Trail from GPX'; + + @override + String get trailExportedSuccessfully => 'Trail exported successfully!'; + + @override + String get failedToExportTrail => 'Failed to export trail'; + + @override + String failedToImportTrail(String error) { + return 'Failed to import trail: $error'; + } + + @override + String get importTrail => 'Import Trail'; + + @override + String importTrailQuestion(int pointCount) { + return 'Import trail with $pointCount points?\n\nYou can replace your current trail or view it alongside.'; + } + + @override + String get viewAlongside => 'View Alongside'; + + @override + String get replaceCurrent => 'Replace Current'; + + @override + String trailImported(int pointCount) { + return 'Trail imported! ($pointCount points)'; + } + + @override + String trailReplaced(int pointCount) { + return 'Trail replaced! ($pointCount points)'; + } + + @override + String get contactTrails => 'Contact Trails'; + + @override + String get showAllContactTrails => 'Show All Contact Trails'; + + @override + String get noContactsWithLocationHistory => + 'No contacts with location history'; + + @override + String showingTrailsForContacts(int count) { + return 'Showing trails for $count contacts'; + } + + @override + String get individualContactTrails => 'Individual Contact Trails'; + + @override + String get deviceInformation => 'Device Information'; + + @override + String get bleName => 'BLE Name'; + + @override + String get meshName => 'Mesh Name'; + + @override + String get notSet => 'Not set'; + + @override + String get model => 'Model'; + + @override + String get version => 'Version'; + + @override + String get buildDate => 'Build Date'; + + @override + String get firmware => 'Firmware'; + + @override + String get maxContacts => 'Max Contacts'; + + @override + String get maxChannels => 'Max Channels'; + + @override + String get publicInfo => 'Public Info'; + + @override + String get meshNetworkName => 'Mesh Network Name'; + + @override + String get nameBroadcastInMesh => 'Name broadcast in mesh advertisements'; + + @override + String get telemetryAndLocationSharing => 'Telemetry & Location Sharing'; + + @override + String get lat => 'Lat'; + + @override + String get lon => 'Lon'; + + @override + String get useCurrentLocation => 'Use current location'; + + @override + String get noneUnknown => 'None/Unknown'; + + @override + String get chatNode => 'Chat Node'; + + @override + String get repeater => 'Repeater'; + + @override + String get roomChannel => 'Room/Channel'; + + @override + String typeNumber(int number) { + return 'Type $number'; + } + + @override + String copiedToClipboardShort(String label) { + return 'Copied $label to clipboard'; + } + + @override + String failedToSave(String error) { + return 'Failed to save: $error'; + } + + @override + String failedToGetLocation(String error) { + return 'Failed to get location: $error'; + } + + @override + String get sarTemplates => 'SAR Templates'; + + @override + String get manageSarTemplates => 'Manage cursor on target templates'; + + @override + String get addTemplate => 'Add Template'; + + @override + String get editTemplate => 'Edit Template'; + + @override + String get deleteTemplate => 'Delete Template'; + + @override + String get templateName => 'Template Name'; + + @override + String get templateNameHint => 'e.g. Found Person'; + + @override + String get templateEmoji => 'Emoji'; + + @override + String get emojiRequired => 'Emoji is required'; + + @override + String get nameRequired => 'Name is required'; + + @override + String get templateDescription => 'Description (Optional)'; + + @override + String get templateDescriptionHint => 'Add additional context...'; + + @override + String get templateColor => 'Color'; + + @override + String get previewFormat => 'Preview (SAR Message Format)'; + + @override + String get importFromClipboard => 'Import'; + + @override + String get exportToClipboard => 'Export'; + + @override + String deleteTemplateConfirmation(String name) { + return 'Delete template \'$name\'?'; + } + + @override + String get templateAdded => 'Template added'; + + @override + String get templateUpdated => 'Template updated'; + + @override + String get templateDeleted => 'Template deleted'; + + @override + String templatesImported(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: 'Imported $count templates', + one: 'Imported 1 template', + zero: 'No templates imported', + ); + return '$_temp0'; + } + + @override + String templatesExported(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: 'Exported $count templates to clipboard', + one: 'Exported 1 template to clipboard', + ); + return '$_temp0'; + } + + @override + String get resetToDefaults => 'Reset to Defaults'; + + @override + String get resetToDefaultsConfirmation => + 'This will delete all custom templates and restore the 4 default templates. Continue?'; + + @override + String get reset => 'Reset'; + + @override + String get resetComplete => 'Templates reset to defaults'; + + @override + String get noTemplates => 'No templates available'; + + @override + String get tapAddToCreate => 'Tap + to create your first template'; + + @override + String get ok => 'OK'; + + @override + String get permissionsSection => 'Permissions'; + + @override + String get locationPermission => 'Location Permission'; + + @override + String get checking => 'Checking...'; + + @override + String get locationPermissionGrantedAlways => 'Granted (Always)'; + + @override + String get locationPermissionGrantedWhileInUse => 'Granted (While In Use)'; + + @override + String get locationPermissionDeniedTapToRequest => 'Denied - Tap to request'; + + @override + String get locationPermissionPermanentlyDeniedOpenSettings => + 'Permanently Denied - Open Settings'; + + @override + String get locationPermissionDialogContent => + 'Location permission is permanently denied. Please enable it in your device settings to use GPS tracking and location sharing features.'; + + @override + String get openSettings => 'Open Settings'; + + @override + String get locationPermissionGranted => 'Location permission granted!'; + + @override + String get locationPermissionRequiredForGps => + 'Location permission is required for GPS tracking and location sharing.'; + + @override + String get locationPermissionAlreadyGranted => + 'Location permission is already granted.'; + + @override + String get sarNavyBlue => 'SAR Navy Blue'; + + @override + String get sarNavyBlueDescription => 'Professional/Operations Mode'; + + @override + String get selectRecipient => 'Select Recipient'; + + @override + String get broadcastToAllNearby => 'Broadcast to all nearby'; + + @override + String get searchRecipients => 'Search recipients...'; + + @override + String get noContactsFound => 'No contacts found'; + + @override + String get noRoomsFound => 'No rooms found'; + + @override + String get noContactsOrRoomsAvailable => 'No contacts or rooms available'; + + @override + String get noRecipientsAvailable => 'No recipients available'; + + @override + String get noChannelsFound => 'No channels found'; + + @override + String get messagesWillBeSentToPublicChannel => + 'Messages will be sent to public channel'; + + @override + String get newMessage => 'New message'; + + @override + String get channel => 'Channel'; + + @override + String get samplePoliceLead => 'Police Lead'; + + @override + String get sampleDroneOperator => 'Drone Operator'; + + @override + String get sampleFirefighterAlpha => 'Firefighter'; + + @override + String get sampleMedicCharlie => 'Medic'; + + @override + String get sampleCommandDelta => 'Command'; + + @override + String get sampleFireEngine => 'Fire Engine'; + + @override + String get sampleAirSupport => 'Air Support'; + + @override + String get sampleBaseCoordinator => 'Base Coordinator'; + + @override + String get channelEmergency => 'Emergency'; + + @override + String get channelCoordination => 'Coordination'; + + @override + String get channelUpdates => 'Updates'; + + @override + String get sampleTeamMember => 'Sample Team Member'; + + @override + String get sampleScout => 'Sample Scout'; + + @override + String get sampleBase => 'Sample Base'; + + @override + String get sampleSearcher => 'Sample Searcher'; + + @override + String get sampleObjectBackpack => ' Backpack found - blue color'; + + @override + String get sampleObjectVehicle => ' Vehicle abandoned - check for owner'; + + @override + String get sampleObjectCamping => ' Camping equipment discovered'; + + @override + String get sampleObjectTrailMarker => ' Trail marker found off-path'; + + @override + String get sampleMsgAllTeamsCheckIn => 'All teams check in'; + + @override + String get sampleMsgWeatherUpdate => 'Weather update: Clear skies, temp 18°C'; + + @override + String get sampleMsgBaseCamp => 'Base camp established at staging area'; + + @override + String get sampleMsgTeamAlpha => 'Team moving to sector 2'; + + @override + String get sampleMsgRadioCheck => 'Radio check - all stations respond'; + + @override + String get sampleMsgWaterSupply => 'Water supply available at checkpoint 3'; + + @override + String get sampleMsgTeamBravo => 'Team reporting: sector 1 clear'; + + @override + String get sampleMsgEtaRallyPoint => 'ETA to rally point: 15 minutes'; + + @override + String get sampleMsgSupplyDrop => 'Supply drop confirmed for 14:00'; + + @override + String get sampleMsgDroneSurvey => 'Drone survey completed - no findings'; + + @override + String get sampleMsgTeamCharlie => 'Team requesting backup'; + + @override + String get sampleMsgRadioDiscipline => 'All units: maintain radio discipline'; + + @override + String get sampleMsgUrgentMedical => + 'URGENT: Medical assistance needed at sector 4'; + + @override + String get sampleMsgAdultMale => ' Adult male, conscious'; + + @override + String get sampleMsgFireSpotted => 'Fire spotted - coordinates incoming'; + + @override + String get sampleMsgSpreadingRapidly => ' Spreading rapidly!'; + + @override + String get sampleMsgPriorityHelicopter => 'PRIORITY: Need helicopter support'; + + @override + String get sampleMsgMedicalTeamEnRoute => + 'Medical team en route to your location'; + + @override + String get sampleMsgEvacHelicopter => 'Evac helicopter ETA 10 minutes'; + + @override + String get sampleMsgEmergencyResolved => 'Emergency resolved - all clear'; + + @override + String get sampleMsgEmergencyStagingArea => ' Emergency staging area'; + + @override + String get sampleMsgEmergencyServices => + 'Emergency services notified and responding'; + + @override + String get sampleAlphaTeamLead => 'Team Lead'; + + @override + String get sampleBravoScout => 'Scout'; + + @override + String get sampleCharlieMedic => 'Medic'; + + @override + String get sampleDeltaNavigator => 'Navigator'; + + @override + String get sampleEchoSupport => 'Support'; + + @override + String get sampleBaseCommand => 'Base Command'; + + @override + String get sampleFieldCoordinator => 'Field Coordinator'; + + @override + String get sampleMedicalTeam => 'Medical Team'; + + @override + String get mapDrawing => 'Map Drawing'; + + @override + String get navigateToDrawing => 'Navigate to Drawing'; + + @override + String get copyCoordinates => 'Copy Coordinates'; + + @override + String get hideFromMap => 'Hide from Map'; + + @override + String get lineDrawing => 'Line Drawing'; + + @override + String get rectangleDrawing => 'Rectangle Drawing'; + + @override + String get coordinatesCopiedToClipboard => 'Coordinates copied to clipboard'; + + @override + String get manualCoordinates => 'Manual Coordinates'; + + @override + String get enterCoordinatesManually => 'Enter coordinates manually'; + + @override + String get latitudeLabel => 'Latitude'; + + @override + String get longitudeLabel => 'Longitude'; + + @override + String get invalidLatitude => 'Invalid latitude (-90 to 90)'; + + @override + String get invalidLongitude => 'Invalid longitude (-180 to 180)'; + + @override + String get exampleCoordinates => 'Example: 46.0569, 14.5058'; + + @override + String get drawingShared => 'Map Drawing'; + + @override + String get drawingHidden => 'Drawing hidden from map'; + + @override + String alreadyShared(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: '$count already shared', + one: '1 already shared', + ); + return '$_temp0'; + } + + @override + String newDrawingsShared(int count, String plural) { + return 'Shared $count new drawing$plural'; + } + + @override + String get shareDrawing => 'Share Drawing'; + + @override + String get shareWithAllNearbyDevices => 'Share with all nearby devices'; + + @override + String get shareToRoom => 'Share to Room'; + + @override + String get sendToPersistentStorage => 'Send to persistent room storage'; + + @override + String get deleteDrawingConfirm => + 'Are you sure you want to delete this drawing?'; + + @override + String get drawingDeleted => 'Drawing deleted'; + + @override + String yourDrawingsCount(int count) { + return 'Your Drawings ($count)'; + } + + @override + String get shared => 'Shared'; + + @override + String get line => 'Line'; + + @override + String get rectangle => 'Rectangle'; + + @override + String get updateAvailable => 'Update Available'; + + @override + String get currentVersion => 'Current'; + + @override + String get latestVersion => 'Latest'; + + @override + String get downloadUpdate => 'Download'; + + @override + String get updateLater => 'Later'; + + @override + String get cadastralParcels => 'Cadastral Parcels'; + + @override + String get forestRoads => 'Forest Roads'; + + @override + String get showCadastralParcels => 'Show Cadastral Parcels'; + + @override + String get showForestRoads => 'Show Forest Roads'; + + @override + String get wmsOverlays => 'WMS Overlays'; + + @override + String get hikingTrails => 'Hiking Trails'; + + @override + String get mainRoads => 'Main Roads'; + + @override + String get houseNumbers => 'House Numbers'; + + @override + String get fireHazardZones => 'Fire Hazard Zones'; + + @override + String get historicalFires => 'Historical Fires'; + + @override + String get firebreaks => 'Firebreaks'; + + @override + String get krasFireZones => 'Kras Fire Zones'; + + @override + String get placeNames => 'Place Names'; + + @override + String get municipalityBorders => 'Municipality Borders'; + + @override + String get topographicMap => 'Topographic Map 1:25000'; + + @override + String get recentMessages => 'Recent Messages'; + + @override + String get addChannel => 'Add Channel'; + + @override + String get channelName => 'Channel Name'; + + @override + String get channelNameHint => 'e.g., Rescue Team Alpha'; + + @override + String get channelSecret => 'Channel Secret'; + + @override + String get channelSecretHint => 'Shared password for this channel'; + + @override + String get channelSecretHelp => + 'This secret must be shared with all team members who need access to this channel'; + + @override + String get channelTypesInfo => + 'Hash channels (#team): Secret auto-generated from name. Same name = same channel across devices.\n\nPrivate channels: Use explicit secret. Only those with the secret can join.'; + + @override + String get hashChannelInfo => + 'Hash channel: Secret will be auto-generated from the channel name. Anyone using the same name will join the same channel.'; + + @override + String get channelNameRequired => 'Channel name is required'; + + @override + String get channelNameTooLong => 'Channel name must be 31 characters or less'; + + @override + String get channelSecretRequired => 'Channel secret is required'; + + @override + String get channelSecretTooLong => + 'Channel secret must be 32 characters or less'; + + @override + String get invalidAsciiCharacters => 'Only ASCII characters are allowed'; + + @override + String get channelCreatedSuccessfully => 'Channel created successfully'; + + @override + String channelCreationFailed(String error) { + return 'Failed to create channel: $error'; + } + + @override + String get deleteChannel => 'Delete Channel'; + + @override + String deleteChannelConfirmation(String channelName) { + return 'Are you sure you want to delete channel \"$channelName\"? This action cannot be undone.'; + } + + @override + String get channelDeletedSuccessfully => 'Channel deleted successfully'; + + @override + String channelDeletionFailed(String error) { + return 'Failed to delete channel: $error'; + } + + @override + String get allChannelSlotsInUse => + 'All channel slots are in use (maximum 39 custom channels)'; + + @override + String get createChannel => 'Create Channel'; + + @override + String get wizardBack => 'Back'; + + @override + String get wizardSkip => 'Skip'; + + @override + String get wizardNext => 'Next'; + + @override + String get wizardGetStarted => 'Get Started'; + + @override + String get wizardWelcomeTitle => 'Welcome to MeshCore SAR'; + + @override + String get wizardWelcomeDescription => + 'A powerful off-grid communication tool for search and rescue operations. Connect with your team using mesh radio technology when traditional networks are unavailable.'; + + @override + String get wizardConnectingTitle => 'Connecting to Your Radio'; + + @override + String get wizardConnectingDescription => + 'Connect your smartphone to a MeshCore radio device via Bluetooth to start communicating off-grid.'; + + @override + String get wizardConnectingFeature1 => 'Scan for nearby MeshCore devices'; + + @override + String get wizardConnectingFeature2 => 'Pair with your radio via Bluetooth'; + + @override + String get wizardConnectingFeature3 => + 'Works completely offline - no internet required'; + + @override + String get wizardSimpleModeTitle => 'Simple Mode'; + + @override + String get wizardSimpleModeDescription => + 'New to mesh networking? Enable Simple Mode for a streamlined interface with essential features only.'; + + @override + String get wizardSimpleModeFeature1 => + 'Beginner-friendly interface with core functions'; + + @override + String get wizardSimpleModeFeature2 => + 'Switch to Advanced Mode anytime in Settings'; + + @override + String get wizardChannelTitle => 'Channels'; + + @override + String get wizardChannelDescription => + 'Broadcast messages to everyone on a channel, perfect for team-wide announcements and coordination.'; + + @override + String get wizardChannelFeature1 => + 'Public Channel for general team communication'; + + @override + String get wizardChannelFeature2 => + 'Create custom channels for specific groups'; + + @override + String get wizardChannelFeature3 => + 'Messages are automatically relayed by the mesh'; + + @override + String get wizardContactsTitle => 'Contacts'; + + @override + String get wizardContactsDescription => + 'Your team members appear automatically as they join the mesh network. Send them direct messages or view their location.'; + + @override + String get wizardContactsFeature1 => 'Contacts discovered automatically'; + + @override + String get wizardContactsFeature2 => 'Send private direct messages'; + + @override + String get wizardContactsFeature3 => 'View battery level and last seen time'; + + @override + String get wizardMapTitle => 'Map & Location'; + + @override + String get wizardMapDescription => + 'Track your team in real-time and mark important locations for search and rescue operations.'; + + @override + String get wizardMapFeature1 => + 'SAR markers for found persons, fires, and staging areas'; + + @override + String get wizardMapFeature2 => 'Real-time GPS tracking of team members'; + + @override + String get wizardMapFeature3 => 'Download offline maps for remote areas'; + + @override + String get wizardMapFeature4 => 'Draw shapes and share tactical information'; + + @override + String get viewWelcomeTutorial => 'View Welcome Tutorial'; + + @override + String get allTeamContacts => 'All Team Contacts'; + + @override + String directMessagesInfo(int count) { + return 'Direct messages with ACKs. Sent to $count team members.'; + } + + @override + String sarMarkerSentToContacts(int count) { + return 'SAR marker sent to $count contacts'; + } + + @override + String get noContactsAvailable => 'No team contacts available'; +} diff --git a/lib/screens/messages_tab.dart b/lib/screens/messages_tab.dart index 4162803..50f4c54 100644 --- a/lib/screens/messages_tab.dart +++ b/lib/screens/messages_tab.dart @@ -54,7 +54,7 @@ class _MessagesTabState extends State { bool _isRecording = false; bool _isSendingVoice = false; static const int _maxVoicePackets = 10; - bool get _voiceSupported => Platform.isIOS || Platform.isMacOS; + bool get _voiceSupported => Platform.isIOS; StreamSubscription? _voiceStreamSub; String? _currentVoiceSessionId; final List _recordedChunks = []; diff --git a/lib/services/locale_preferences.dart b/lib/services/locale_preferences.dart index 0ac0ae2..90f4fae 100644 --- a/lib/services/locale_preferences.dart +++ b/lib/services/locale_preferences.dart @@ -14,6 +14,7 @@ class LocalePreferences { Locale('es'), // Spanish Locale('fr'), // French Locale('it'), // Italian + Locale('el'), // Greek ]; /// Get the saved locale or return null to use system locale @@ -61,6 +62,8 @@ class LocalePreferences { return 'Français'; case 'it': return 'Italiano'; + case 'el': + return 'Greek'; default: return locale.languageCode; } @@ -83,6 +86,8 @@ class LocalePreferences { return 'Français'; case 'it': return 'Italiano'; + case 'el': + return 'Ελληνικά'; default: return locale.languageCode; } diff --git a/macos/Podfile b/macos/Podfile index ff5ddb3..51f3332 100644 --- a/macos/Podfile +++ b/macos/Podfile @@ -38,5 +38,13 @@ end post_install do |installer| installer.pods_project.targets.each do |target| flutter_additional_macos_build_settings(target) + # codec2_flutter uses ARM-specific register asm("sp") in debug_alloc.h that + # does not compile on x86_64 macOS. Voice messages are iOS-only, so exclude + # all C source files from the macOS codec2_flutter build entirely. + if target.name == 'codec2_flutter' + target.build_configurations.each do |config| + config.build_settings['EXCLUDED_SOURCE_FILE_NAMES'] = '*.c *.cpp' + end + end end end