From 8f90ebb1b4ee6ac1badc991db0e97fa4454b8ac8 Mon Sep 17 00:00:00 2001 From: Ramazan TUNCER Date: Sun, 27 Sep 2026 01:27:21 +0300 Subject: [PATCH 1/5] Add String Catalog and Turkish region Adds LoopFollow/Resources/Localizable.xcstrings (source language en) and registers tr in knownRegions. Keys come from xcodebuild -exportLocalizations plus a scan of SwiftUI LocalizedStringKey call sites (Toggle, Picker, Section, Label, navigationTitle, ...) that the extractor does not emit; those are marked extractionState "manual". No translations yet. Co-Authored-By: Claude Fable 5.1 (cherry picked from commit 345c02582e5878ba12922177d89edbf96ef836f7) --- LoopFollow.xcodeproj/project.pbxproj | 1 + LoopFollow/Resources/Localizable.xcstrings | 955 +++++++++++++++++++++ 2 files changed, 956 insertions(+) create mode 100644 LoopFollow/Resources/Localizable.xcstrings diff --git a/LoopFollow.xcodeproj/project.pbxproj b/LoopFollow.xcodeproj/project.pbxproj index 560cd2f8e..f6c94d172 100644 --- a/LoopFollow.xcodeproj/project.pbxproj +++ b/LoopFollow.xcodeproj/project.pbxproj @@ -313,6 +313,7 @@ knownRegions = ( en, Base, + tr, ); mainGroup = FC97880B2485969B00A7906C; packageReferences = ( diff --git a/LoopFollow/Resources/Localizable.xcstrings b/LoopFollow/Resources/Localizable.xcstrings new file mode 100644 index 000000000..257d29bc2 --- /dev/null +++ b/LoopFollow/Resources/Localizable.xcstrings @@ -0,0 +1,955 @@ +{ + "sourceLanguage" : "en", + "strings" : { + " U" : {}, + "#WeAreNotWaitingAnymore" : {}, + "#WeAreWaitingForData" : {}, + "%" : {}, + "%@" : {}, + "%@ %@ → %@" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "%1$@ %2$@ → %3$@" + } + } + } + }, + "%@ %@" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "%1$@ %2$@" + } + } + } + }, + "%@ - %@" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "%1$@ - %2$@" + } + } + } + }, + "%@ gaps" : {}, + "%@ hr" : {}, + "%@ min" : {}, + "%@ of %@ expected readings" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "%1$@ of %2$@ expected readings" + } + } + } + }, + "%@ sec" : {}, + "%@%%" : {}, + "%@U" : {}, + "%@g" : {}, + "(" : { + "extractionState" : "manual" + }, + "(%@ days)" : {}, + "+ 30m" : {}, + "---" : {}, + "...because the data could not be loaded" : {}, + "00" : {}, + "15m" : {}, + "24h" : {}, + "A silent tune will play in the background, keeping the app active. May be interrupted by other apps. Allows continuous updates but consumes more battery." : {}, + "API Secret" : {}, + "APN" : { + "extractionState" : "manual" + }, + "APNS Environment: %@" : {}, + "APNS Key" : {}, + "APNS Key ID" : {}, + "APNS warning: importing APNS settings will overwrite your existing APNS Key ID and APNS Key." : {}, + "APNs credentials are missing or invalid — Live Activity updates will not work. Open Settings → APN to fix." : {}, + "Above: %@" : {}, + "Absorption Time" : {}, + "Access Token" : {}, + "Activate Override" : {}, + "Active" : {}, + "Active During" : {}, + "Active Override" : {}, + "Add Alarm" : { + "extractionState" : "manual" + }, + "Add the Apple-Calendar complication to your watch or CarPlay to see BG readings. Create a separate calendar (e.g. “Follow”) — this view will **delete** events on the same calendar each time it writes new readings." : {}, + "Add the contact named '%@' to your watch face to show the current BG value in real time. Make sure to give the app full access to Contacts when prompted." : {}, + "Adding this alarm would exceed the QR code character limit. Please remove some alarms first." : {}, + "Additional Information" : {}, + "Adjust the background refresh type." : {}, + "Advanced Settings" : {}, + "Alarm Name" : { + "extractionState" : "manual" + }, + "Alarm Settings" : {}, + "Alarm not found" : {}, + "Alarms can behave differently during the day and at night." : {}, + "Alert Sound" : {}, + "All" : {}, + "All Alerts Snoozed" : { + "extractionState" : "manual" + }, + "All Sounds Muted" : { + "extractionState" : "manual" + }, + "All alerts snoozed" : {}, + "All glucose, insulin, and carb data. Your Nightscout URL and API token. Your Dexcom credentials. Remote-command secrets and APNS keys. Time zone. Location data. Logs — these are never sent automatically; the Settings → Logs sharing flow is unchanged and only triggered by you." : {}, + "Always" : { + "extractionState" : "manual" + }, + "Ambulatory Glucose Profile (AGP)" : {}, + "App Settings" : { + "extractionState" : "manual" + }, + "App version, build date, whether this is a TestFlight build, the install instance number, an Apple-supplied per-vendor identifier (IDFV) that resets when all this developer's apps are removed from the device, the hardware identifier (e.g. iPhone15,2), and iOS version. Whether Nightscout and Dexcom are configured (yes/no — no URLs or usernames). Which app you're following (Loop, Trio, etc), if known. A small set of preference flags (units, appearance mode, calendar/contact integration enabled, remote-command type, background refresh method). The full JSON is visible under Diagnostics → What's sent." : {}, + "Appearance" : { + "extractionState" : "manual" + }, + "Are you sure you want to cancel the active override?" : {}, + "Are you sure you want to cancel the existing temp target?" : {}, + "Are you sure you want to send %@ U?" : {}, + "Available Overrides" : {}, + "Available Variables" : { + "extractionState" : "manual" + }, + "BG Update Delay (Sec): %@" : {}, + "Back" : { + "extractionState" : "manual" + }, + "Background Color" : { + "extractionState" : "manual" + }, + "Background Refresh Type" : { + "extractionState" : "manual" + }, + "Basal / BG Scale" : { + "extractionState" : "manual" + }, + "Below is the exact JSON object that LoopFollow would send to lf.bjorkert.se right now." : {}, + "Below: %@" : {}, + "Bolus" : { + "extractionState" : "manual" + }, + "Bolus Filter" : { + "extractionState" : "manual" + }, + "Bolus Increment" : {}, + "Bolus Increment: %.3f U" : {}, + "Build Information" : { + "extractionState" : "manual" + }, + "Built-in" : {}, + "Bundle ID: %@" : {}, + "CHARGING" : {}, + "COB" : {}, + "CV" : {}, + "Calculated %@ minute%@ ago" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Calculated %1$@ minute%2$@ ago" + } + } + } + }, + "Calendar" : { + "extractionState" : "manual" + }, + "Calendar Text" : { + "extractionState" : "manual" + }, + "Calendar access denied" : {}, + "Cancel" : {}, + "Cancel Active Override" : {}, + "Cancel Override" : {}, + "Cancel Temp Target" : {}, + "Carbs" : { + "extractionState" : "manual" + }, + "Category" : { + "extractionState" : "manual" + }, + "Character Limit Reached" : { + "extractionState" : "manual" + }, + "Checking Nightscout profile history…" : {}, + "Choose Tone" : { + "extractionState" : "manual" + }, + "Close" : { + "extractionState" : "manual" + }, + "Color" : {}, + "Color BG Text" : { + "extractionState" : "manual" + }, + "Color Mode" : { + "extractionState" : "manual" + }, + "Color Options" : {}, + "Coloring enabled" : { + "extractionState" : "manual" + }, + "Command Sent" : {}, + "Configure Loop APNS" : {}, + "Confirm" : {}, + "Confirm & Import" : { + "extractionState" : "manual" + }, + "Confirm Bolus" : {}, + "Confirm Cancellation" : {}, + "Confirm Carbs" : {}, + "Confirm Command" : {}, + "Confirm Insulin" : {}, + "Confirm Meal" : {}, + "Connected" : {}, + "Consumption Time" : { + "extractionState" : "manual" + }, + "Contact Integration" : {}, + "Continue" : {}, + "Copy JSON" : { + "extractionState" : "manual" + }, + "Create Read-Only Token" : {}, + "Create one for me" : {}, + "Creating read-only token…" : {}, + "Current OTP Code" : {}, + "Current TOTP Code:" : {}, + "Current Target" : {}, + "Current Token:" : {}, + "Current URL:" : {}, + "Custom" : {}, + "Custom sounds stay on this device and aren't included in settings export." : {}, + "Cutoffs in %@" : {}, + "Data Availability" : {}, + "Day / Night" : {}, + "Day / Night Schedule" : {}, + "Day starts" : { + "extractionState" : "manual" + }, + "Debug / Info" : {}, + "Debug Log Level" : { + "extractionState" : "manual" + }, + "Default Snooze:" : {}, + "Delay Between Sounds" : {}, + "Delete" : { + "extractionState" : "manual" + }, + "Delete Alarm" : { + "extractionState" : "manual" + }, + "Delete this alarm?" : { + "extractionState" : "manual" + }, + "Delta" : {}, + "Description" : {}, + "Developer Team ID" : {}, + "Device Data" : {}, + "Device Token: %@" : {}, + "Device tokens are alternating in recent profile uploads (%@ tokens involved across %@ records). This usually means more than one app installation is uploading to the same Nightscout. Remove the app from spare or unused phones." : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Device tokens are alternating in recent profile uploads (%1$@ tokens involved across %2$@ records). This usually means more than one app installation is uploading to the same Nightscout. Remove the app from spare or unused phones." + } + } + } + }, + "Dexcom Settings" : {}, + "Dexcom Share" : {}, + "Diagnostics" : { + "extractionState" : "manual" + }, + "Diagnostics unavailable: %@" : {}, + "Disconnect" : {}, + "Disconnected for %@" : {}, + "Dismiss" : { + "extractionState" : "manual" + }, + "Display" : { + "extractionState" : "manual" + }, + "Display App Badge" : { + "extractionState" : "manual" + }, + "Display Dots" : { + "extractionState" : "manual" + }, + "Display Lines" : { + "extractionState" : "manual" + }, + "Display Small Graph" : { + "extractionState" : "manual" + }, + "Display Stats" : { + "extractionState" : "manual" + }, + "Do you want to activate the override '%@'?" : {}, + "Done" : { + "extractionState" : "manual" + }, + "Download Prediction" : { + "extractionState" : "manual" + }, + "Download Treatments" : { + "extractionState" : "manual" + }, + "Drag to reorder" : {}, + "Drag to reorder. Tap a row to set its visibility and colors." : {}, + "Duration" : {}, + "Duration: %@" : {}, + "Duration: %@ minutes" : {}, + "Dynamic mode colors text based on BG range: Green (in range), Yellow (high), Red (low)" : {}, + "Enable Contact BG Updates" : { + "extractionState" : "manual" + }, + "Enable Live Activity" : { + "extractionState" : "manual" + }, + "Enable Notifications" : {}, + "Enable WebSocket" : { + "extractionState" : "manual" + }, + "Enable coloring" : { + "extractionState" : "manual" + }, + "Enable indefinitely" : { + "extractionState" : "manual" + }, + "Enable this alarm" : { + "extractionState" : "manual" + }, + "Enabled" : { + "extractionState" : "manual" + }, + "Enact" : {}, + "End Date" : {}, + "End now" : {}, + "Endpoint" : {}, + "English" : {}, + "Enter QR code URL or scan from Loop app" : { + "extractionState" : "manual" + }, + "Enter URL" : { + "extractionState" : "manual" + }, + "Enter User" : { + "extractionState" : "manual" + }, + "Enter Username" : { + "extractionState" : "manual" + }, + "Enter or paste your full Nightscout address. If the link already includes a token, we'll fill both in for you." : {}, + "Environment" : {}, + "Error" : {}, + "Eventual" : {}, + "Existing Temp Target" : {}, + "Expected bg delay: %@" : {}, + "Expected: 1 CGM reading every 5 minutes" : {}, + "Export" : { + "extractionState" : "manual" + }, + "Export %@" : {}, + "Export Settings To QR Code" : { + "extractionState" : "manual" + }, + "F%@ P%@" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "F%1$@ P%2$@" + } + } + } + }, + "FALLING BG" : {}, + "Failed to generate QR code" : {}, + "Features" : { + "extractionState" : "manual" + }, + "Filter" : { + "extractionState" : "manual" + }, + "Finish" : {}, + "Food Type" : {}, + "Force portrait mode" : { + "extractionState" : "manual" + }, + "French" : {}, + "Frequency" : {}, + "GMI" : {}, + "GRI (Glucose Risk Index)" : {}, + "General" : {}, + "Give each alarm a unique name—especially if you’ve added more than one of the same kind—so you can tell them apart at a glance." : {}, + "Glucose" : { + "extractionState" : "manual" + }, + "Glucose Unit" : { + "extractionState" : "manual" + }, + "Glucose value" : {}, + "Glycemic Metrics" : {}, + "Graph Basal" : { + "extractionState" : "manual" + }, + "Graph Bolus" : { + "extractionState" : "manual" + }, + "Graph Carbs" : { + "extractionState" : "manual" + }, + "Graph Display" : { + "extractionState" : "manual" + }, + "Graph Other Treatments" : { + "extractionState" : "manual" + }, + "Grid Slot - CarPlay / Watch" : {}, + "Grid Slots - Live Activity" : {}, + "Guardrails" : {}, + "Help us help you" : {}, + "Help us help you!" : { + "extractionState" : "manual" + }, + "Hide Information Table" : {}, + "High" : { + "extractionState" : "manual" + }, + "History" : { + "extractionState" : "manual" + }, + "Home" : { + "extractionState" : "manual" + }, + "Hours" : { + "extractionState" : "manual" + }, + "Hyperglycemia Component (%)" : {}, + "Hypoglycemia Component (%)" : {}, + "I have a token" : {}, + "IOB" : {}, + "Import Settings" : {}, + "Import Settings from QR Code" : {}, + "Import Sound…" : { + "extractionState" : "manual" + }, + "Import/Export Settings" : {}, + "In Menu" : {}, + "Inactive" : {}, + "Include Delta in" : { + "extractionState" : "manual" + }, + "Include IOB in" : { + "extractionState" : "manual" + }, + "Include Trend in" : { + "extractionState" : "manual" + }, + "Increment" : {}, + "Information Display Settings" : {}, + "Insulin" : { + "extractionState" : "manual" + }, + "Insulin Totals" : {}, + "Insulin: %@%%" : {}, + "Invalid QR code URL" : {}, + "Italian" : {}, + "Keep Screen Active" : { + "extractionState" : "manual" + }, + "Key ID must be exactly 10 uppercase letters or digits." : {}, + "Language" : { + "extractionState" : "manual" + }, + "Last connection: %@" : {}, + "Line 1" : { + "extractionState" : "manual" + }, + "Line 2" : { + "extractionState" : "manual" + }, + "Live Activity" : {}, + "Live Activity updates require APNs credentials. Configure them in Settings → APN." : {}, + "Load More" : {}, + "Loading data..." : { + "extractionState" : "manual" + }, + "Loading override presets..." : {}, + "Loading stats..." : {}, + "Logging" : { + "extractionState" : "manual" + }, + "Logging Options" : {}, + "Loop APNS Configuration" : {}, + "Loop APNS Not Configured" : {}, + "LoopFollow APNs Credentials" : {}, + "LoopFollow uses notifications to deliver your alarms — like low or high glucose, or a missed reading. Without them, alarms can't reach you when the app is in the background." : {}, + "LoopFollow/Helpers/Telemetry.swift on GitHub." : {}, + "Low" : { + "extractionState" : "manual" + }, + "Max" : {}, + "Max %@ alarms at a time" : {}, + "Max Bolus" : {}, + "Max Carbs" : {}, + "Max Fat" : {}, + "Max Protein" : {}, + "Maximum" : {}, + "Meal" : { + "extractionState" : "manual" + }, + "Meal Data" : {}, + "Meal Settings" : {}, + "Meal with Bolus" : { + "extractionState" : "manual" + }, + "Meal with Fat/Protein" : { + "extractionState" : "manual" + }, + "Menu" : {}, + "Metric" : { + "extractionState" : "manual" + }, + "Min" : {}, + "Minimum" : {}, + "Minutes" : { + "extractionState" : "manual" + }, + "Multiple devices uploading profiles" : {}, + "Name" : {}, + "New Target: %@ %@\nDuration: %@ minutes" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "New Target: %1$@ %2$@\nDuration: %3$@ minutes" + } + } + } + }, + "Night starts" : { + "extractionState" : "manual" + }, + "Nightscout URL" : {}, + "No Results" : {}, + "No alarms match “%@”." : {}, + "No background refresh. Alarms and updates will not work unless the app is open in the foreground." : {}, + "No data available" : {}, + "No devices found yet. They'll appear here when discovered." : {}, + "No issues detected" : {}, + "No matches for “%@”." : {}, + "No override presets found. Configure presets in your Loop app." : {}, + "No overrides available." : {}, + "No recent treatments" : {}, + "No thanks" : {}, + "Not now" : {}, + "Note: The meal will be scheduled, but the bolus is enacted immediately." : {}, + "OK" : {}, + "Off" : {}, + "Old Calculation Warning" : {}, + "Once a day (or after a new build), the app sends a small JSON object to https://lf.bjorkert.se. The endpoint is self-hosted by the maintainer; no third-party analytics service is involved." : {}, + "Once every 24 hours, plus once after installing a new build. The check runs in the background while the app is active or refreshing in the background." : {}, + "Opt out" : {}, + "Output Unit" : { + "extractionState" : "manual" + }, + "Outside US" : {}, + "Override" : {}, + "Override system volume" : { + "extractionState" : "manual" + }, + "Overrides" : { + "extractionState" : "manual" + }, + "Overriding the system volume lets alarms be heard even when your phone is silenced or in a Focus mode." : {}, + "Password" : {}, + "Paste a Nightscout token. If your site needs one and you only have the API secret, paste that instead — LoopFollow can create a read-only token for you." : {}, + "Paste the full .p8 contents — must include the BEGIN PRIVATE KEY and END PRIVATE KEY lines." : {}, + "Percentage: %@%%" : {}, + "Persistent Notification" : { + "extractionState" : "manual" + }, + "Pick when your day period begins and when your night period begins. " : { + "extractionState" : "manual" + }, + "Play sound during calls" : { + "extractionState" : "manual" + }, + "Please configure Loop APNS settings in Remote Settings to use APNS commands." : {}, + "Please enter your Nightscout URL in Settings." : {}, + "Please select a Remote Type in Settings." : {}, + "Please wait..." : { + "extractionState" : "manual" + }, + "Prediction" : {}, + "Prediction Style" : { + "extractionState" : "manual" + }, + "Preset Name" : { + "extractionState" : "manual" + }, + "Preset: %@" : {}, + "Presets" : {}, + "Privacy" : { + "extractionState" : "manual" + }, + "Proactive Low" : { + "extractionState" : "manual" + }, + "Production" : { + "extractionState" : "manual" + }, + "Production is used for browser builders and should be switched off for Xcode builders" : {}, + "QR Code URL" : {}, + "RISING BG" : {}, + "RSSI: %@ dBm" : {}, + "Range" : {}, + "Range Mode" : { + "extractionState" : "manual" + }, + "Real-time Updates" : {}, + "Recommended Bolus" : {}, + "Reconnecting..." : {}, + "Refresh" : { + "extractionState" : "manual" + }, + "Remote" : { + "extractionState" : "manual" + }, + "Remote Settings" : { + "extractionState" : "manual" + }, + "Requires a Dexcom G6/ONE/G7/ONE+ transmitter within Bluetooth range. Provides updates every 5 minutes and uses less battery than the silent tune method. If you have more than one device to choose from, select the one with the smallest expected bg delay." : {}, + "Requires a RileyLink-compatible device within Bluetooth range. Provides updates once per minute and uses less battery than the silent tune method." : {}, + "Requires an OmniPod DASH pod paired with this device within Bluetooth range. Provides updates once every 3 minutes and uses less battery than the silent tune method." : {}, + "Review setup anyway" : {}, + "Review the settings that will be imported" : {}, + "Right slot" : { + "extractionState" : "manual" + }, + "Run diagnostics" : {}, + "Run diagnostics again" : {}, + "SNOOZE" : {}, + "Save" : { + "extractionState" : "manual" + }, + "Save BG to Calendar" : { + "extractionState" : "manual" + }, + "Save Preset" : {}, + "Save as Preset" : {}, + "Scan QR Code from Loop App" : {}, + "Scan QR Code to Import Settings" : {}, + "Scan this QR code with another LoopFollow app to import %@" : {}, + "Scanned Token:" : {}, + "Scanned URL:" : {}, + "Scanning for %@..." : {}, + "Schedule" : {}, + "Schedule for later" : { + "extractionState" : "manual" + }, + "Search" : { + "extractionState" : "manual" + }, + "Search alarms" : { + "extractionState" : "manual" + }, + "Secret" : {}, + "Security" : {}, + "See exactly what's sent" : { + "extractionState" : "manual" + }, + "Select Alarms to Export" : { + "extractionState" : "manual" + }, + "Select the colors for your BG values. Note: not all watch faces allow control over colors. Recommend options like Activity or Modular Duo if you want to customize colors." : {}, + "Selected Alarms: %@" : {}, + "Selected Device" : {}, + "Send" : {}, + "Send %@ units of insulin?" : {}, + "Send %@g of carbs with %@h absorption time at %@?" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Send %1$@g of carbs with %2$@h absorption time at %3$@?" + } + } + } + }, + "Send Bolus" : {}, + "Send Carbs" : {}, + "Send Insulin" : {}, + "Send Meal" : {}, + "Send anonymous usage stats" : { + "extractionState" : "manual" + }, + "Sending Bolus..." : {}, + "Sending Meal Data..." : {}, + "Sending..." : {}, + "Sensitivity" : {}, + "Server" : { + "extractionState" : "manual" + }, + "Set Up Units" : { + "extractionState" : "manual" + }, + "Settings" : { + "extractionState" : "manual" + }, + "Settings imported — you're connected." : { + "extractionState" : "manual" + }, + "Settings to Import" : {}, + "Share" : { + "extractionState" : "manual" + }, + "Share Logs" : { + "extractionState" : "manual" + }, + "Shared Secret" : {}, + "Show Carb Absorption" : { + "extractionState" : "manual" + }, + "Show Carb/Bolus Values" : { + "extractionState" : "manual" + }, + "Show DIA Lines" : { + "extractionState" : "manual" + }, + "Show Delta" : { + "extractionState" : "manual" + }, + "Show Display Name" : { + "extractionState" : "manual" + }, + "Show IOB" : { + "extractionState" : "manual" + }, + "Show Midnight Lines" : { + "extractionState" : "manual" + }, + "Show Prior Day Time Lines" : { + "extractionState" : "manual" + }, + "Show Trend" : { + "extractionState" : "manual" + }, + "Show Yesterday's BG" : { + "extractionState" : "manual" + }, + "Show in Info Display" : { + "extractionState" : "manual" + }, + "Show −30 min Line" : { + "extractionState" : "manual" + }, + "Show −90 min Line" : { + "extractionState" : "manual" + }, + "Site URL" : {}, + "Skip" : {}, + "Skip if BG is falling" : { + "extractionState" : "manual" + }, + "Skip if BG is rising" : { + "extractionState" : "manual" + }, + "Skip while charging" : { + "extractionState" : "manual" + }, + "Slovak" : {}, + "Small Graph" : { + "extractionState" : "manual" + }, + "Snooze & Mute Options" : {}, + "Snooze Date" : { + "extractionState" : "manual" + }, + "Snooze Time" : { + "extractionState" : "manual" + }, + "Snooze all · 1h" : {}, + "Snooze for" : {}, + "Snoozed" : {}, + "Snoozed until %@" : {}, + "Snoozer emoji" : { + "extractionState" : "manual" + }, + "Sound" : {}, + "Source" : {}, + "Speak BG" : { + "extractionState" : "manual" + }, + "Start Date" : {}, + "Statistics" : {}, + "Status" : {}, + "Stay informed" : {}, + "Stay silent while BG is falling. The alert only sounds " : { + "extractionState" : "manual" + }, + "Stay silent while BG is rising. The alert only sounds " : { + "extractionState" : "manual" + }, + "Stay silent while the phone is charging. Requires the " : { + "extractionState" : "manual" + }, + "Std Dev" : {}, + "Std Dev — Standard Deviation, how much glucose swings around the average. CV — Coefficient of Variation, that swing relative to the average (Std Dev ÷ mean)." : {}, + "Success" : {}, + "Support & Community" : { + "extractionState" : "manual" + }, + "Swedish" : {}, + "TIR" : {}, + "TIR — Time in Range, the share of readings within %@.\nTITR — Time in Tight Range, within %@.\nCustom — set your own low and high." : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "TIR — Time in Range, the share of readings within %1$@.\nTITR — Time in Tight Range, within %2$@.\nCustom — set your own low and high." + } + } + } + }, + "TITR" : {}, + "TOTP Code Already Used" : {}, + "TOTP Code: Invalid QR code URL" : {}, + "Tab %@" : {}, + "Tab 5" : {}, + "Tab Order" : {}, + "Tabs" : { + "extractionState" : "manual" + }, + "Tap to load older treatments" : {}, + "Target" : {}, + "Target Range" : {}, + "Target: %@ %@" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Target: %1$@ %2$@" + } + } + } + }, + "Target: %@-%@" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Target: %1$@-%2$@" + } + } + } + }, + "Team ID: %@" : {}, + "Temporary Target" : {}, + "Text Color" : { + "extractionState" : "manual" + }, + "Thanks for sharing these logs to help us find the problem. Please describe it in as much detail as possible — what time did it happen, what did you do, and what did you expect to happen that didn't?" : {}, + "That's your API secret — create a read-only token" : {}, + "The order of Fat and Protein inputs has changed." : {}, + "The top 4 items appear in the tab bar. The Menu can always open every feature." : {}, + "This TOTP code has already been used for a command. Please wait for the next code to be generated before sending another command." : {}, + "This will overwrite your current settings" : {}, + "Time" : {}, + "Time Remaining" : {}, + "Time Zone" : { + "extractionState" : "manual" + }, + "Time Zone Override" : { + "extractionState" : "manual" + }, + "To see your trend, delta, or IOB, include them in another contact or create separate contacts. When using 'Include', select which contact to add the value to." : {}, + "Token" : {}, + "Tone" : {}, + "Treatments" : {}, + "Treatments on Small Graph" : { + "extractionState" : "manual" + }, + "Trend" : {}, + "Trio Remote Control Settings" : {}, + "Trio Remote Control is only supported for 'Trio'" : {}, + "Type or paste a token, or a full Nightscout URL that includes a token." : {}, + "U" : { + "comment" : "The short unit display string for international units of insulin" + }, + "URL" : {}, + "URL/Token Update" : {}, + "URL/Token Validation" : {}, + "US" : {}, + "Units and Metrics" : { + "extractionState" : "manual" + }, + "Unknown Alert" : {}, + "Use Anyway" : {}, + "Use the Send anonymous usage stats toggle above. Turning it off is immediate and persistent." : {}, + "User" : {}, + "User Information" : {}, + "Username" : {}, + "Validation Error" : {}, + "Variability" : {}, + "View Log" : { + "extractionState" : "manual" + }, + "WARNING: New treatments may have occurred since the last recommended bolus was calculated %@ ago." : {}, + "Warning" : {}, + "We'll ask iOS for permission next. You can change this any time in the Settings app." : {}, + "Welcome to LoopFollow" : {}, + "What is sent" : {}, + "What stays on your device" : {}, + "What's sent" : { + "extractionState" : "manual" + }, + "When enabled, LoopFollow maintains a live connection to your Nightscout server using WebSocket while the app is in the foreground. Data updates (new glucose readings, treatments, device status) arrive within seconds instead of waiting for the next polling cycle.\n\nThe WebSocket disconnects when LoopFollow moves to the background and reconnects when you return to the app. Polling continues to handle updates while the app is in the background.\n\nIn the foreground, polling continues at a reduced frequency as a safety net. If the WebSocket connection drops, normal polling resumes immediately." : {}, + "Yes, send anonymous stats" : {}, + "You can change this any time in Settings → General → Diagnostics." : {}, + "You can choose to share anonymous information with the developers to help improve LoopFollow — such as app and iOS version, device type, which app you're following, and a few settings. Your health data, credentials, time zone, and logs remain on your device." : {}, + "You can choose to share anonymous information with the developers to help improve LoopFollow—such as app and iOS version, device type, which app you're following, and a few settings. Your health data, credentials, time zone, and logs remain on your device." : {}, + "You're all set" : {}, + "You're ready to go. Adjust everything later from the Menu — and when you have a moment, these are worth a look:" : {}, + "Your API secret is used once to create a read-only access token and is never stored." : {}, + "eHbA1c" : {}, + "eHbA1c — an A1c estimate from your average glucose. GMI — Glucose Management Indicator, another A1c estimate from average glucose. % and mmol/mol (IFCC) are two scales for the result." : {}, + "g" : { + "comment" : "The short unit display string for grams" + }, + "mg/dL" : { + "comment" : "The short unit display string for milligrams of glucose per decilter" + }, + "minutes" : {}, + "mmol/L" : { + "comment" : "The short unit display string for millimoles of glucose per liter" + }, + "mmol/mol" : {}, + "“Default Snooze” controls the default value for how long the alert stays quiet after you press Snooze. \"A snooze duration of 0 means the alarm is acknowledged (silenced), and will alert again next time the condition applies, without time limitation. \" Toggle “Snoozed” to mute this alarm right now." : {}, + "“Snooze All” disables every alarm. “Mute All” silences phone sounds but still vibrates and shows iOS notifications." : {}, + "••••••••" : {}, + "− 30m" : {}, + "⚠️ Please enable at least one limit." : {}, + "🌮" : {}, + "🍕" : {}, + "🍭" : {}, + "🍽️" : {} + }, + "version" : "1.0" +} From cce8cce5347411010c37860181322c36b5e53fa6 Mon Sep 17 00:00:00 2001 From: Ramazan TUNCER Date: Sun, 27 Sep 2026 01:39:26 +0300 Subject: [PATCH 2/5] Add String Catalog coverage tests Fails until every translatable key has a Turkish value; also checks printf placeholders and stale keys. Co-Authored-By: Claude Fable 5.1 (cherry picked from commit 38286cfcb1ae0da53444786b2cc326e75318f23b) --- Tests/LocalizationCatalogTests.swift | 102 +++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 Tests/LocalizationCatalogTests.swift diff --git a/Tests/LocalizationCatalogTests.swift b/Tests/LocalizationCatalogTests.swift new file mode 100644 index 000000000..cbb656648 --- /dev/null +++ b/Tests/LocalizationCatalogTests.swift @@ -0,0 +1,102 @@ +// LoopFollow +// LocalizationCatalogTests.swift + +import Foundation +@testable import LoopFollow +import Testing + +/// Guards the String Catalogs checked into the repo. Reads the JSON source files, not the +/// compiled bundle, so it runs without a device language switch. +struct LocalizationCatalogTests { + /// Repo root derived from this file's location (Tests/LocalizationCatalogTests.swift). + private static let repoRoot = URL(fileURLWithPath: #filePath) + .deletingLastPathComponent() + .deletingLastPathComponent() + + static let catalogs = ["LoopFollow/Resources/Localizable.xcstrings"] + static let languages = ["tr"] + + private struct Catalog { + let sourceLanguage: String + let strings: [String: [String: Any]] + } + + private static func load(_ path: String) throws -> Catalog { + let data = try Data(contentsOf: repoRoot.appendingPathComponent(path)) + let json = try #require(try JSONSerialization.jsonObject(with: data) as? [String: Any]) + let strings = try #require(json["strings"] as? [String: [String: Any]]) + let source = try #require(json["sourceLanguage"] as? String) + return Catalog(sourceLanguage: source, strings: strings) + } + + private static func unit(_ entry: [String: Any], _ language: String) -> (state: String, value: String)? { + guard let localizations = entry["localizations"] as? [String: Any], + let lang = localizations[language] as? [String: Any], + let unit = lang["stringUnit"] as? [String: Any], + let state = unit["state"] as? String, + let value = unit["value"] as? String else { return nil } + return (state, value) + } + + /// Keys marked "Don't translate" in Xcode are skipped. + private static func translatable(_ entry: [String: Any]) -> Bool { + (entry["shouldTranslate"] as? Bool) ?? true + } + + /// printf-style specifiers, e.g. %lld, %@, %.1f, %2$@. Returns (positionalIndex?, specifier). + /// The positional group only matches digits followed by "$", so a width like "%2d" is not positional. + private static func placeholders(in s: String) -> [(Int?, String)] { + let pattern = #"%(?:(\d+)\$)?([-+ 0#]*\d*(?:\.\d+)?(?:ll|l|h)?[@dDiuUxXoOfeEgGcCsSpaAF])"# + let regex = try! NSRegularExpression(pattern: pattern) + return regex.matches(in: s, range: NSRange(s.startIndex..., in: s)).compactMap { m in + guard let specRange = Range(m.range(at: 2), in: s) else { return nil } + let index = Range(m.range(at: 1), in: s).flatMap { Int(s[$0]) } + return (index, String(s[specRange])) + } + } + + @Test("every translatable key has a translated value", arguments: Self.catalogs) + func allKeysTranslated(path: String) throws { + let catalog = try Self.load(path) + #expect(catalog.sourceLanguage == "en") + var missing: [String] = [] + for (key, entry) in catalog.strings where Self.translatable(entry) { + for language in Self.languages { + guard let t = Self.unit(entry, language), t.state == "translated", + !t.value.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty + else { missing.append("[\(language)] \(key)"); continue } + } + } + #expect(missing.isEmpty, Comment(rawValue: "Missing translations (\(missing.count)):\n" + missing.sorted().joined(separator: "\n"))) + } + + @Test("format placeholders survive translation", arguments: Self.catalogs) + func placeholdersPreserved(path: String) throws { + let catalog = try Self.load(path) + var problems: [String] = [] + for (key, entry) in catalog.strings where Self.translatable(entry) { + let source = Self.placeholders(in: key) + guard !source.isEmpty else { continue } + for language in Self.languages { + guard let t = Self.unit(entry, language) else { continue } + let target = Self.placeholders(in: t.value) + if source.map(\.1).sorted() != target.map(\.1).sorted() { + problems.append("[\(language)] \(key) -> \(t.value)") + continue + } + let reordered = source.map(\.1) != target.map(\.1) + if source.count > 1, reordered, target.contains(where: { $0.0 == nil }) { + problems.append("[\(language)] reordered without positional indexes: \(key) -> \(t.value)") + } + } + } + #expect(problems.isEmpty, Comment(rawValue: "Placeholder problems:\n" + problems.joined(separator: "\n"))) + } + + @Test("no stale keys linger in the catalog", arguments: Self.catalogs) + func noStaleKeys(path: String) throws { + let catalog = try Self.load(path) + let stale = catalog.strings.filter { ($0.value["extractionState"] as? String) == "stale" }.map(\.key) + #expect(stale.isEmpty, Comment(rawValue: "Stale keys (remove them from the catalog):\n" + stale.sorted().joined(separator: "\n"))) + } +} From d6efdc170532379e7c979a156d96ebf8f9a0aeb6 Mon Sep 17 00:00:00 2001 From: Ramazan TUNCER Date: Sun, 27 Sep 2026 01:41:09 +0300 Subject: [PATCH 3/5] Show alarm type and schedule names through localized displayName AlarmType.rawValue and the day/night option raw values are persisted, so they must stay English. Add AlarmType.displayName and make DayNightDisplayable.displayName an explicit localized switch; route the tile, editor title, list and add-sheet search, notification title and default alarm name through them. Tests pin the raw values. Co-Authored-By: Claude Fable 5.1 (cherry picked from commit ca2276f80102049c02f5e88cc3b3829725e0c3dc) --- LoopFollow/Alarm/AddAlarm/AddAlarmSheet.swift | 3 +- LoopFollow/Alarm/AddAlarm/AlarmTile.swift | 2 +- LoopFollow/Alarm/Alarm.swift | 12 +- .../Alarm/AlarmEditing/AlarmEditor.swift | 2 +- LoopFollow/Alarm/AlarmListView.swift | 1 + LoopFollow/Alarm/AlarmType/AlarmType.swift | 33 ++++++ LoopFollow/Resources/Localizable.xcstrings | 112 ++++++++++++++++++ Tests/LocalizationDisplayNameTests.swift | 67 +++++++++++ 8 files changed, 226 insertions(+), 6 deletions(-) create mode 100644 Tests/LocalizationDisplayNameTests.swift diff --git a/LoopFollow/Alarm/AddAlarm/AddAlarmSheet.swift b/LoopFollow/Alarm/AddAlarm/AddAlarmSheet.swift index b603f6152..e901e3b7c 100644 --- a/LoopFollow/Alarm/AddAlarm/AddAlarmSheet.swift +++ b/LoopFollow/Alarm/AddAlarm/AddAlarmSheet.swift @@ -16,7 +16,8 @@ struct AddAlarmSheet: View { private func matches(_ type: AlarmType) -> Bool { let query = searchText.trimmingCharacters(in: .whitespacesAndNewlines) guard !query.isEmpty else { return true } - return type.rawValue.localizedCaseInsensitiveContains(query) + return type.displayName.localizedCaseInsensitiveContains(query) + || type.rawValue.localizedCaseInsensitiveContains(query) || type.blurb.localizedCaseInsensitiveContains(query) || type.group.rawValue.localizedCaseInsensitiveContains(query) } diff --git a/LoopFollow/Alarm/AddAlarm/AlarmTile.swift b/LoopFollow/Alarm/AddAlarm/AlarmTile.swift index 4b4ab452f..a81a1f117 100644 --- a/LoopFollow/Alarm/AddAlarm/AlarmTile.swift +++ b/LoopFollow/Alarm/AddAlarm/AlarmTile.swift @@ -13,7 +13,7 @@ struct AlarmTile: View { Image(systemName: type.icon) .font(.title2) .foregroundColor(.accentColor) - Text(type.rawValue) + Text(type.displayName) .font(.subheadline) .multilineTextAlignment(.center) .lineLimit(2) diff --git a/LoopFollow/Alarm/Alarm.swift b/LoopFollow/Alarm/Alarm.swift index f429aa04a..3b9c760a9 100644 --- a/LoopFollow/Alarm/Alarm.swift +++ b/LoopFollow/Alarm/Alarm.swift @@ -11,7 +11,13 @@ protocol DayNightDisplayable { extension DayNightDisplayable where Self: RawRepresentable, Self.RawValue == String { var displayName: String { - rawValue == "always" ? "Day & Night" : rawValue.capitalized + switch rawValue { + case "always": String(localized: "Day & Night", comment: "Alarm schedule option: at any time of day") + case "day": String(localized: "Day", comment: "Alarm schedule option: daytime only") + case "night": String(localized: "Night", comment: "Alarm schedule option: nighttime only") + case "never": String(localized: "Never", comment: "Alarm schedule option: never") + default: rawValue.capitalized + } } } @@ -277,7 +283,7 @@ struct Alarm: Identifiable, Codable, Equatable { } }() - AlarmManager.shared.sendNotification(title: type.rawValue, actionTitle: snoozeDuration == 0 ? "Acknowledge" : "Snooze") + AlarmManager.shared.sendNotification(title: type.displayName, actionTitle: snoozeDuration == 0 ? "Acknowledge" : "Snooze") if playSound { AlarmSound.setSoundFile(soundFile) @@ -289,7 +295,7 @@ struct Alarm: Identifiable, Codable, Equatable { init(type: AlarmType) { self.type = type - name = type.rawValue + name = type.displayName switch type { case .buildExpire: diff --git a/LoopFollow/Alarm/AlarmEditing/AlarmEditor.swift b/LoopFollow/Alarm/AlarmEditing/AlarmEditor.swift index 14901c5a1..2c8287384 100644 --- a/LoopFollow/Alarm/AlarmEditing/AlarmEditor.swift +++ b/LoopFollow/Alarm/AlarmEditing/AlarmEditor.swift @@ -39,7 +39,7 @@ struct AlarmEditor: View { } } } - .navigationTitle(alarm.type.rawValue) + .navigationTitle(alarm.type.displayName) } .preferredColorScheme(Storage.shared.appearanceMode.value.colorScheme) } diff --git a/LoopFollow/Alarm/AlarmListView.swift b/LoopFollow/Alarm/AlarmListView.swift index 9748f99c3..8f737757b 100644 --- a/LoopFollow/Alarm/AlarmListView.swift +++ b/LoopFollow/Alarm/AlarmListView.swift @@ -30,6 +30,7 @@ struct AlarmListView: View { let query = searchText.trimmingCharacters(in: .whitespacesAndNewlines) guard !query.isEmpty else { return true } return alarm.name.localizedCaseInsensitiveContains(query) + || alarm.type.displayName.localizedCaseInsensitiveContains(query) || alarm.type.rawValue.localizedCaseInsensitiveContains(query) } diff --git a/LoopFollow/Alarm/AlarmType/AlarmType.swift b/LoopFollow/Alarm/AlarmType/AlarmType.swift index 45c2aa9af..9d08a60cd 100644 --- a/LoopFollow/Alarm/AlarmType/AlarmType.swift +++ b/LoopFollow/Alarm/AlarmType/AlarmType.swift @@ -50,3 +50,36 @@ extension AlarmType { } } } + +extension AlarmType { + /// User-facing, localized name. `rawValue` is persisted in Storage and must never be + /// shown to the user or translated; every display site goes through this property. + var displayName: String { + switch self { + case .temporary: String(localized: "Temporary Alert", comment: "Alarm type name") + case .iob: String(localized: "IOB Alert", comment: "Alarm type name") + case .cob: String(localized: "COB Alert", comment: "Alarm type name") + case .low: String(localized: "Low BG Alert", comment: "Alarm type name") + case .high: String(localized: "High BG Alert", comment: "Alarm type name") + case .fastDrop: String(localized: "Fast Drop Alert", comment: "Alarm type name") + case .fastRise: String(localized: "Fast Rise Alert", comment: "Alarm type name") + case .missedReading: String(localized: "Missed Reading Alert", comment: "Alarm type name") + case .notLooping: String(localized: "Not Looping Alert", comment: "Alarm type name") + case .missedBolus: String(localized: "Missed Bolus Alert", comment: "Alarm type name") + case .futureCarbs: String(localized: "Future Carbs Alert", comment: "Alarm type name") + case .sensorChange: String(localized: "Sensor Change Alert", comment: "Alarm type name") + case .pumpChange: String(localized: "Pump Change Alert", comment: "Alarm type name") + case .pump: String(localized: "Pump Insulin Alert", comment: "Alarm type name") + case .pumpBattery: String(localized: "Pump Battery Alert", comment: "Alarm type name") + case .battery: String(localized: "Low Battery", comment: "Alarm type name") + case .batteryDrop: String(localized: "Battery Drop", comment: "Alarm type name") + case .recBolus: String(localized: "Rec. Bolus", comment: "Alarm type name: recommended bolus") + case .overrideStart: String(localized: "Override Started", comment: "Alarm type name") + case .overrideEnd: String(localized: "Override Ended", comment: "Alarm type name") + case .tempTargetStart: String(localized: "Temp Target Started", comment: "Alarm type name") + case .tempTargetEnd: String(localized: "Temp Target Ended", comment: "Alarm type name") + case .buildExpire: String(localized: "Looping app expiration", comment: "Alarm type name") + case .dbSize: String(localized: "Nightscout Database Size", comment: "Alarm type name") + } + } +} diff --git a/LoopFollow/Resources/Localizable.xcstrings b/LoopFollow/Resources/Localizable.xcstrings index 257d29bc2..aa7f385cd 100644 --- a/LoopFollow/Resources/Localizable.xcstrings +++ b/LoopFollow/Resources/Localizable.xcstrings @@ -136,6 +136,10 @@ "Basal / BG Scale" : { "extractionState" : "manual" }, + "Battery Drop" : { + "comment" : "Alarm type name", + "extractionState" : "manual" + }, "Below is the exact JSON object that LoopFollow would send to lf.bjorkert.se right now." : {}, "Below: %@" : {}, "Bolus" : { @@ -153,6 +157,10 @@ "Bundle ID: %@" : {}, "CHARGING" : {}, "COB" : {}, + "COB Alert" : { + "comment" : "Alarm type name", + "extractionState" : "manual" + }, "CV" : {}, "Calculated %@ minute%@ ago" : { "localizations" : { @@ -235,6 +243,14 @@ "Custom sounds stay on this device and aren't included in settings export." : {}, "Cutoffs in %@" : {}, "Data Availability" : {}, + "Day" : { + "comment" : "Alarm schedule option: daytime only", + "extractionState" : "manual" + }, + "Day & Night" : { + "comment" : "Alarm schedule option: at any time of day", + "extractionState" : "manual" + }, "Day / Night" : {}, "Day / Night Schedule" : {}, "Day starts" : { @@ -380,6 +396,14 @@ }, "FALLING BG" : {}, "Failed to generate QR code" : {}, + "Fast Drop Alert" : { + "comment" : "Alarm type name", + "extractionState" : "manual" + }, + "Fast Rise Alert" : { + "comment" : "Alarm type name", + "extractionState" : "manual" + }, "Features" : { "extractionState" : "manual" }, @@ -393,6 +417,10 @@ }, "French" : {}, "Frequency" : {}, + "Future Carbs Alert" : { + "comment" : "Alarm type name", + "extractionState" : "manual" + }, "GMI" : {}, "GRI (Glucose Risk Index)" : {}, "General" : {}, @@ -431,6 +459,10 @@ "High" : { "extractionState" : "manual" }, + "High BG Alert" : { + "comment" : "Alarm type name", + "extractionState" : "manual" + }, "History" : { "extractionState" : "manual" }, @@ -444,6 +476,10 @@ "Hypoglycemia Component (%)" : {}, "I have a token" : {}, "IOB" : {}, + "IOB Alert" : { + "comment" : "Alarm type name", + "extractionState" : "manual" + }, "Import Settings" : {}, "Import Settings from QR Code" : {}, "Import Sound…" : { @@ -501,9 +537,21 @@ "LoopFollow APNs Credentials" : {}, "LoopFollow uses notifications to deliver your alarms — like low or high glucose, or a missed reading. Without them, alarms can't reach you when the app is in the background." : {}, "LoopFollow/Helpers/Telemetry.swift on GitHub." : {}, + "Looping app expiration" : { + "comment" : "Alarm type name", + "extractionState" : "manual" + }, "Low" : { "extractionState" : "manual" }, + "Low BG Alert" : { + "comment" : "Alarm type name", + "extractionState" : "manual" + }, + "Low Battery" : { + "comment" : "Alarm type name", + "extractionState" : "manual" + }, "Max" : {}, "Max %@ alarms at a time" : {}, "Max Bolus" : {}, @@ -531,8 +579,20 @@ "Minutes" : { "extractionState" : "manual" }, + "Missed Bolus Alert" : { + "comment" : "Alarm type name", + "extractionState" : "manual" + }, + "Missed Reading Alert" : { + "comment" : "Alarm type name", + "extractionState" : "manual" + }, "Multiple devices uploading profiles" : {}, "Name" : {}, + "Never" : { + "comment" : "Alarm schedule option: never", + "extractionState" : "manual" + }, "New Target: %@ %@\nDuration: %@ minutes" : { "localizations" : { "en" : { @@ -543,9 +603,17 @@ } } }, + "Night" : { + "comment" : "Alarm schedule option: nighttime only", + "extractionState" : "manual" + }, "Night starts" : { "extractionState" : "manual" }, + "Nightscout Database Size" : { + "comment" : "Alarm type name", + "extractionState" : "manual" + }, "Nightscout URL" : {}, "No Results" : {}, "No alarms match “%@”." : {}, @@ -558,6 +626,10 @@ "No overrides available." : {}, "No recent treatments" : {}, "No thanks" : {}, + "Not Looping Alert" : { + "comment" : "Alarm type name", + "extractionState" : "manual" + }, "Not now" : {}, "Note: The meal will be scheduled, but the bolus is enacted immediately." : {}, "OK" : {}, @@ -571,6 +643,14 @@ }, "Outside US" : {}, "Override" : {}, + "Override Ended" : { + "comment" : "Alarm type name", + "extractionState" : "manual" + }, + "Override Started" : { + "comment" : "Alarm type name", + "extractionState" : "manual" + }, "Override system volume" : { "extractionState" : "manual" }, @@ -616,6 +696,18 @@ "extractionState" : "manual" }, "Production is used for browser builders and should be switched off for Xcode builders" : {}, + "Pump Battery Alert" : { + "comment" : "Alarm type name", + "extractionState" : "manual" + }, + "Pump Change Alert" : { + "comment" : "Alarm type name", + "extractionState" : "manual" + }, + "Pump Insulin Alert" : { + "comment" : "Alarm type name", + "extractionState" : "manual" + }, "QR Code URL" : {}, "RISING BG" : {}, "RSSI: %@ dBm" : {}, @@ -624,6 +716,10 @@ "extractionState" : "manual" }, "Real-time Updates" : {}, + "Rec. Bolus" : { + "comment" : "Alarm type name: recommended bolus", + "extractionState" : "manual" + }, "Recommended Bolus" : {}, "Reconnecting..." : {}, "Refresh" : { @@ -704,6 +800,10 @@ "Sending Meal Data..." : {}, "Sending..." : {}, "Sensitivity" : {}, + "Sensor Change Alert" : { + "comment" : "Alarm type name", + "extractionState" : "manual" + }, "Server" : { "extractionState" : "manual" }, @@ -861,6 +961,18 @@ } }, "Team ID: %@" : {}, + "Temp Target Ended" : { + "comment" : "Alarm type name", + "extractionState" : "manual" + }, + "Temp Target Started" : { + "comment" : "Alarm type name", + "extractionState" : "manual" + }, + "Temporary Alert" : { + "comment" : "Alarm type name", + "extractionState" : "manual" + }, "Temporary Target" : {}, "Text Color" : { "extractionState" : "manual" diff --git a/Tests/LocalizationDisplayNameTests.swift b/Tests/LocalizationDisplayNameTests.swift new file mode 100644 index 000000000..47fe0355c --- /dev/null +++ b/Tests/LocalizationDisplayNameTests.swift @@ -0,0 +1,67 @@ +// LoopFollow +// LocalizationDisplayNameTests.swift + +import Foundation +@testable import LoopFollow +import Testing + +struct LocalizationDisplayNameTests { + /// Persisted raw values. Changing any of these breaks decoding of stored alarms. + private static let alarmTypeRawValues: [AlarmType: String] = [ + .temporary: "Temporary Alert", + .iob: "IOB Alert", + .cob: "COB Alert", + .low: "Low BG Alert", + .high: "High BG Alert", + .fastDrop: "Fast Drop Alert", + .fastRise: "Fast Rise Alert", + .missedReading: "Missed Reading Alert", + .notLooping: "Not Looping Alert", + .missedBolus: "Missed Bolus Alert", + .futureCarbs: "Future Carbs Alert", + .sensorChange: "Sensor Change Alert", + .pumpChange: "Pump Change Alert", + .pump: "Pump Insulin Alert", + .pumpBattery: "Pump Battery Alert", + .battery: "Low Battery", + .batteryDrop: "Battery Drop", + .recBolus: "Rec. Bolus", + .overrideStart: "Override Started", + .overrideEnd: "Override Ended", + .tempTargetStart: "Temp Target Started", + .tempTargetEnd: "Temp Target Ended", + .buildExpire: "Looping app expiration", + .dbSize: "Nightscout Database Size", + ] + + @Test("AlarmType raw values are stable (they are persisted)") + func alarmTypeRawValuesAreStable() { + #expect(AlarmType.allCases.count == Self.alarmTypeRawValues.count) + for (type, raw) in Self.alarmTypeRawValues { + #expect(type.rawValue == raw) + } + } + + @Test("day/night option raw values are stable (they are persisted)") + func dayNightRawValuesAreStable() { + #expect(PlaySoundOption.allCases.map(\.rawValue) == ["always", "day", "night", "never"]) + #expect(RepeatSoundOption.allCases.map(\.rawValue) == ["always", "day", "night", "never"]) + #expect(ActiveOption.allCases.map(\.rawValue) == ["always", "day", "night"]) + } + + @Test("AlarmType.displayName falls back to the English raw value in the test host language") + func alarmTypeDisplayNameEnglish() { + for type in AlarmType.allCases { + #expect(type.displayName == type.rawValue) + } + } + + @Test("day/night displayName keeps the English wording") + func dayNightDisplayNameEnglish() { + #expect(PlaySoundOption.always.displayName == "Day & Night") + #expect(PlaySoundOption.day.displayName == "Day") + #expect(PlaySoundOption.night.displayName == "Night") + #expect(PlaySoundOption.never.displayName == "Never") + #expect(ActiveOption.always.displayName == "Day & Night") + } +} From bd41ba68c691afbf90ac6054c5d07e141484aa39 Mon Sep 17 00:00:00 2001 From: Ramazan TUNCER Date: Sun, 27 Sep 2026 01:55:49 +0300 Subject: [PATCH 4/5] Add Turkish translations for SwiftUI strings Fills the tr locale of Localizable.xcstrings for every extracted key, marks units, symbols and product names as do-not-translate, and adds two small scripts for listing and applying translations from TSV. Two call sites are adjusted so they can be localized at all: the "minute(s) ago" plural suffix becomes a plural variation on a single "%lld minutes ago" key, and three alarm-editor footers that joined two literals with "+" (which selects the non-localized Text overload) are single literals again. Co-Authored-By: Claude Fable 5.1 (cherry picked from commit 0f417a115c1b09b3444112935a19b5ee8185b231) --- .../Editors/HighBgAlarmEditor.swift | 3 +- .../Editors/LowBgAlarmEditor.swift | 3 +- .../Editors/PhoneBatteryAlarmEditor.swift | 3 +- .../Remote/LoopAPNS/LoopAPNSBolusView.swift | 2 +- LoopFollow/Remote/TRC/BolusView.swift | 2 +- LoopFollow/Resources/Localizable.xcstrings | 6021 ++++++++++++++--- Scripts/localization/apply_translations.py | 57 + Scripts/localization/missing_translations.py | 39 + Tests/LocalizationCatalogTests.swift | 3 +- Tests/LocalizationDisplayNameTests.swift | 39 +- 10 files changed, 5351 insertions(+), 821 deletions(-) create mode 100755 Scripts/localization/apply_translations.py create mode 100755 Scripts/localization/missing_translations.py diff --git a/LoopFollow/Alarm/AlarmEditing/Editors/HighBgAlarmEditor.swift b/LoopFollow/Alarm/AlarmEditing/Editors/HighBgAlarmEditor.swift index 1adf217aa..9f4e1c374 100644 --- a/LoopFollow/Alarm/AlarmEditing/Editors/HighBgAlarmEditor.swift +++ b/LoopFollow/Alarm/AlarmEditing/Editors/HighBgAlarmEditor.swift @@ -36,8 +36,7 @@ struct HighBgAlarmEditor: View { Section( header: Text("FALLING BG"), - footer: Text("Stay silent while BG is falling. The alert only sounds " - + "when the latest reading is flat or still rising.") + footer: Text("Stay silent while BG is falling. The alert only sounds when the latest reading is flat or still rising.") ) { Toggle("Skip if BG is falling", isOn: $alarm.suppressIfFalling) } diff --git a/LoopFollow/Alarm/AlarmEditing/Editors/LowBgAlarmEditor.swift b/LoopFollow/Alarm/AlarmEditing/Editors/LowBgAlarmEditor.swift index a21db2239..d80953b02 100644 --- a/LoopFollow/Alarm/AlarmEditing/Editors/LowBgAlarmEditor.swift +++ b/LoopFollow/Alarm/AlarmEditing/Editors/LowBgAlarmEditor.swift @@ -45,8 +45,7 @@ struct LowBgAlarmEditor: View { Section( header: Text("RISING BG"), - footer: Text("Stay silent while BG is rising. The alert only sounds " - + "when the latest reading is flat or still falling.") + footer: Text("Stay silent while BG is rising. The alert only sounds when the latest reading is flat or still falling.") ) { Toggle("Skip if BG is rising", isOn: $alarm.suppressIfRising) } diff --git a/LoopFollow/Alarm/AlarmEditing/Editors/PhoneBatteryAlarmEditor.swift b/LoopFollow/Alarm/AlarmEditing/Editors/PhoneBatteryAlarmEditor.swift index d08ee68ed..b39b500e9 100644 --- a/LoopFollow/Alarm/AlarmEditing/Editors/PhoneBatteryAlarmEditor.swift +++ b/LoopFollow/Alarm/AlarmEditing/Editors/PhoneBatteryAlarmEditor.swift @@ -27,8 +27,7 @@ struct PhoneBatteryAlarmEditor: View { Section( header: Text("CHARGING"), - footer: Text("Stay silent while the phone is charging. Requires the " - + "uploader to report charging status; if it doesn't, the alert still sounds.") + footer: Text("Stay silent while the phone is charging. Requires the uploader to report charging status; if it doesn't, the alert still sounds.") ) { Toggle("Skip while charging", isOn: $alarm.suppressIfCharging) } diff --git a/LoopFollow/Remote/LoopAPNS/LoopAPNSBolusView.swift b/LoopFollow/Remote/LoopAPNS/LoopAPNSBolusView.swift index 09222270c..0a2a2848a 100644 --- a/LoopFollow/Remote/LoopAPNS/LoopAPNSBolusView.swift +++ b/LoopFollow/Remote/LoopAPNS/LoopAPNSBolusView.swift @@ -57,7 +57,7 @@ struct LoopAPNSBolusView: View { Text("\(String(format: "%.2f", recommendedBolus))U") .font(.headline) .foregroundColor(.primary) - Text("Calculated \(minutesSinceCalculation) minute\(minutesSinceCalculation == 1 ? "" : "s") ago") + Text("Calculated \(minutesSinceCalculation) minutes ago") .font(.caption) .foregroundColor(.secondary) } diff --git a/LoopFollow/Remote/TRC/BolusView.swift b/LoopFollow/Remote/TRC/BolusView.swift index e41ab89ce..5e5795eaa 100644 --- a/LoopFollow/Remote/TRC/BolusView.swift +++ b/LoopFollow/Remote/TRC/BolusView.swift @@ -241,7 +241,7 @@ struct BolusView: View { HStack { VStack(alignment: .leading, spacing: 4) { Text("\(InsulinFormatter.shared.string(steppedRec))U") - Text("Calculated \(mins) minute\(mins == 1 ? "" : "s") ago") + Text("Calculated \(mins) minutes ago") .font(.caption) .foregroundColor(.secondary) } diff --git a/LoopFollow/Resources/Localizable.xcstrings b/LoopFollow/Resources/Localizable.xcstrings index aa7f385cd..808e2ccda 100644 --- a/LoopFollow/Resources/Localizable.xcstrings +++ b/LoopFollow/Resources/Localizable.xcstrings @@ -1,11 +1,21 @@ { "sourceLanguage" : "en", "strings" : { - " U" : {}, - "#WeAreNotWaitingAnymore" : {}, - "#WeAreWaitingForData" : {}, - "%" : {}, - "%@" : {}, + " U" : { + "shouldTranslate" : false + }, + "#WeAreNotWaitingAnymore" : { + "shouldTranslate" : false + }, + "#WeAreWaitingForData" : { + "shouldTranslate" : false + }, + "%" : { + "shouldTranslate" : false + }, + "%@" : { + "shouldTranslate" : false + }, "%@ %@ → %@" : { "localizations" : { "en" : { @@ -14,7 +24,8 @@ "value" : "%1$@ %2$@ → %3$@" } } - } + }, + "shouldTranslate" : false }, "%@ %@" : { "localizations" : { @@ -24,7 +35,8 @@ "value" : "%1$@ %2$@" } } - } + }, + "shouldTranslate" : false }, "%@ - %@" : { "localizations" : { @@ -34,11 +46,39 @@ "value" : "%1$@ - %2$@" } } + }, + "shouldTranslate" : false + }, + "%@ gaps" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@ boşluk" + } + } + } + }, + "%@ hr" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@ sa" + } + } + } + }, + "%@ min" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@ dk" + } + } } }, - "%@ gaps" : {}, - "%@ hr" : {}, - "%@ min" : {}, "%@ of %@ expected readings" : { "localizations" : { "en" : { @@ -46,1022 +86,5399 @@ "state" : "new", "value" : "%1$@ of %2$@ expected readings" } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "beklenen %2$@ okumanın %1$@ tanesi" + } } } }, - "%@ sec" : {}, - "%@%%" : {}, - "%@U" : {}, - "%@g" : {}, - "(" : { - "extractionState" : "manual" - }, - "(%@ days)" : {}, - "+ 30m" : {}, - "---" : {}, - "...because the data could not be loaded" : {}, - "00" : {}, - "15m" : {}, - "24h" : {}, - "A silent tune will play in the background, keeping the app active. May be interrupted by other apps. Allows continuous updates but consumes more battery." : {}, - "API Secret" : {}, - "APN" : { - "extractionState" : "manual" - }, - "APNS Environment: %@" : {}, - "APNS Key" : {}, - "APNS Key ID" : {}, - "APNS warning: importing APNS settings will overwrite your existing APNS Key ID and APNS Key." : {}, - "APNs credentials are missing or invalid — Live Activity updates will not work. Open Settings → APN to fix." : {}, - "Above: %@" : {}, - "Absorption Time" : {}, - "Access Token" : {}, - "Activate Override" : {}, - "Active" : {}, - "Active During" : {}, - "Active Override" : {}, - "Add Alarm" : { - "extractionState" : "manual" - }, - "Add the Apple-Calendar complication to your watch or CarPlay to see BG readings. Create a separate calendar (e.g. “Follow”) — this view will **delete** events on the same calendar each time it writes new readings." : {}, - "Add the contact named '%@' to your watch face to show the current BG value in real time. Make sure to give the app full access to Contacts when prompted." : {}, - "Adding this alarm would exceed the QR code character limit. Please remove some alarms first." : {}, - "Additional Information" : {}, - "Adjust the background refresh type." : {}, - "Advanced Settings" : {}, - "Alarm Name" : { - "extractionState" : "manual" - }, - "Alarm Settings" : {}, - "Alarm not found" : {}, - "Alarms can behave differently during the day and at night." : {}, - "Alert Sound" : {}, - "All" : {}, - "All Alerts Snoozed" : { - "extractionState" : "manual" + "%@ sec" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@ sn" + } + } + } }, - "All Sounds Muted" : { - "extractionState" : "manual" + "%@%%" : { + "shouldTranslate" : false }, - "All alerts snoozed" : {}, - "All glucose, insulin, and carb data. Your Nightscout URL and API token. Your Dexcom credentials. Remote-command secrets and APNS keys. Time zone. Location data. Logs — these are never sent automatically; the Settings → Logs sharing flow is unchanged and only triggered by you." : {}, - "Always" : { - "extractionState" : "manual" + "%@U" : { + "shouldTranslate" : false }, - "Ambulatory Glucose Profile (AGP)" : {}, - "App Settings" : { - "extractionState" : "manual" + "%@g" : { + "shouldTranslate" : false }, - "App version, build date, whether this is a TestFlight build, the install instance number, an Apple-supplied per-vendor identifier (IDFV) that resets when all this developer's apps are removed from the device, the hardware identifier (e.g. iPhone15,2), and iOS version. Whether Nightscout and Dexcom are configured (yes/no — no URLs or usernames). Which app you're following (Loop, Trio, etc), if known. A small set of preference flags (units, appearance mode, calendar/contact integration enabled, remote-command type, background refresh method). The full JSON is visible under Diagnostics → What's sent." : {}, - "Appearance" : { - "extractionState" : "manual" + "(" : { + "extractionState" : "manual", + "shouldTranslate" : false }, - "Are you sure you want to cancel the active override?" : {}, - "Are you sure you want to cancel the existing temp target?" : {}, - "Are you sure you want to send %@ U?" : {}, - "Available Overrides" : {}, - "Available Variables" : { - "extractionState" : "manual" + "(%@ days)" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "(%@ gün)" + } + } + } }, - "BG Update Delay (Sec): %@" : {}, - "Back" : { - "extractionState" : "manual" + "+ 30m" : { + "shouldTranslate" : false }, - "Background Color" : { - "extractionState" : "manual" + "---" : { + "shouldTranslate" : false }, - "Background Refresh Type" : { - "extractionState" : "manual" + "...because the data could not be loaded" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "...çünkü veriler yüklenemedi" + } + } + } }, - "Basal / BG Scale" : { - "extractionState" : "manual" + "00" : { + "shouldTranslate" : false }, - "Battery Drop" : { - "comment" : "Alarm type name", - "extractionState" : "manual" + "15m" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "15dk" + } + } + } }, - "Below is the exact JSON object that LoopFollow would send to lf.bjorkert.se right now." : {}, - "Below: %@" : {}, - "Bolus" : { - "extractionState" : "manual" + "24h" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "24sa" + } + } + } }, - "Bolus Filter" : { - "extractionState" : "manual" + "A silent tune will play in the background, keeping the app active. May be interrupted by other apps. Allows continuous updates but consumes more battery." : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Arka planda sessiz bir melodi çalarak uygulamayı etkin tutar. Başka uygulamalar tarafından kesilebilir. Sürekli güncelleme sağlar ama daha fazla pil tüketir." + } + } + } }, - "Bolus Increment" : {}, - "Bolus Increment: %.3f U" : {}, - "Build Information" : { - "extractionState" : "manual" + "API Secret" : { + "shouldTranslate" : false }, - "Built-in" : {}, - "Bundle ID: %@" : {}, - "CHARGING" : {}, - "COB" : {}, - "COB Alert" : { - "comment" : "Alarm type name", - "extractionState" : "manual" + "APN" : { + "extractionState" : "manual", + "shouldTranslate" : false }, - "CV" : {}, - "Calculated %@ minute%@ ago" : { + "APNS Environment: %@" : { "localizations" : { - "en" : { + "tr" : { "stringUnit" : { - "state" : "new", - "value" : "Calculated %1$@ minute%2$@ ago" + "state" : "translated", + "value" : "APNS Ortamı: %@" } } } }, - "Calendar" : { - "extractionState" : "manual" + "APNS Key" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "APNS Anahtarı" + } + } + } }, - "Calendar Text" : { - "extractionState" : "manual" + "APNS Key ID" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "APNS Anahtar Kimliği" + } + } + } }, - "Calendar access denied" : {}, - "Cancel" : {}, - "Cancel Active Override" : {}, - "Cancel Override" : {}, - "Cancel Temp Target" : {}, - "Carbs" : { - "extractionState" : "manual" + "APNS warning: importing APNS settings will overwrite your existing APNS Key ID and APNS Key." : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "APNS uyarısı: APNS ayarlarını içe aktarmak mevcut APNS Anahtar Kimliğinizin ve APNS Anahtarınızın üzerine yazar." + } + } + } }, - "Category" : { - "extractionState" : "manual" + "APNs credentials are missing or invalid — Live Activity updates will not work. Open Settings → APN to fix." : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "APNs kimlik bilgileri eksik veya geçersiz — Canlı Etkinlik güncellemeleri çalışmayacak. Düzeltmek için Ayarlar → APN'ye gidin." + } + } + } }, - "Character Limit Reached" : { - "extractionState" : "manual" + "Above: %@" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Üst: %@" + } + } + } }, - "Checking Nightscout profile history…" : {}, - "Choose Tone" : { - "extractionState" : "manual" + "Absorption Time" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Emilim Süresi" + } + } + } }, - "Close" : { - "extractionState" : "manual" + "Access Token" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Erişim Belirteci" + } + } + } }, - "Color" : {}, - "Color BG Text" : { - "extractionState" : "manual" + "Activate Override" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Override'ı Etkinleştir" + } + } + } }, - "Color Mode" : { - "extractionState" : "manual" + "Active" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Etkin" + } + } + } }, - "Color Options" : {}, - "Coloring enabled" : { - "extractionState" : "manual" + "Active During" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Etkin Olduğu Zaman" + } + } + } }, - "Command Sent" : {}, - "Configure Loop APNS" : {}, - "Confirm" : {}, - "Confirm & Import" : { - "extractionState" : "manual" - }, - "Confirm Bolus" : {}, - "Confirm Cancellation" : {}, - "Confirm Carbs" : {}, - "Confirm Command" : {}, - "Confirm Insulin" : {}, - "Confirm Meal" : {}, - "Connected" : {}, - "Consumption Time" : { - "extractionState" : "manual" + "Active Override" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Etkin Override" + } + } + } }, - "Contact Integration" : {}, - "Continue" : {}, - "Copy JSON" : { - "extractionState" : "manual" - }, - "Create Read-Only Token" : {}, - "Create one for me" : {}, - "Creating read-only token…" : {}, - "Current OTP Code" : {}, - "Current TOTP Code:" : {}, - "Current Target" : {}, - "Current Token:" : {}, - "Current URL:" : {}, - "Custom" : {}, - "Custom sounds stay on this device and aren't included in settings export." : {}, - "Cutoffs in %@" : {}, - "Data Availability" : {}, - "Day" : { - "comment" : "Alarm schedule option: daytime only", - "extractionState" : "manual" + "Add Alarm" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Alarm Ekle" + } + } + } }, - "Day & Night" : { - "comment" : "Alarm schedule option: at any time of day", - "extractionState" : "manual" + "Add the Apple-Calendar complication to your watch or CarPlay to see BG readings. Create a separate calendar (e.g. “Follow”) — this view will **delete** events on the same calendar each time it writes new readings." : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "KŞ okumalarını görmek için saatinize veya CarPlay'e Apple Takvim komplikasyonunu ekleyin. Ayrı bir takvim oluşturun (ör. “Follow”) — bu görünüm, her yeni okuma yazdığında aynı takvimdeki etkinlikleri **siler**." + } + } + } }, - "Day / Night" : {}, - "Day / Night Schedule" : {}, - "Day starts" : { - "extractionState" : "manual" + "Add the contact named '%@' to your watch face to show the current BG value in real time. Make sure to give the app full access to Contacts when prompted." : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Güncel KŞ değerini gerçek zamanlı görmek için '%@' adlı kişiyi saat kadranınıza ekleyin. İstendiğinde uygulamaya Kişiler için tam erişim verdiğinizden emin olun." + } + } + } }, - "Debug / Info" : {}, - "Debug Log Level" : { - "extractionState" : "manual" + "Adding this alarm would exceed the QR code character limit. Please remove some alarms first." : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bu alarmı eklemek QR kodu karakter sınırını aşar. Lütfen önce bazı alarmları kaldırın." + } + } + } }, - "Default Snooze:" : {}, - "Delay Between Sounds" : {}, - "Delete" : { - "extractionState" : "manual" + "Additional Information" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ek Bilgiler" + } + } + } }, - "Delete Alarm" : { - "extractionState" : "manual" + "Adjust the background refresh type." : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Arka plan yenileme türünü ayarlayın." + } + } + } }, - "Delete this alarm?" : { - "extractionState" : "manual" + "Advanced Settings" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Gelişmiş Ayarlar" + } + } + } }, - "Delta" : {}, - "Description" : {}, - "Developer Team ID" : {}, - "Device Data" : {}, - "Device Token: %@" : {}, - "Device tokens are alternating in recent profile uploads (%@ tokens involved across %@ records). This usually means more than one app installation is uploading to the same Nightscout. Remove the app from spare or unused phones." : { + "Alarm Name" : { + "extractionState" : "manual", "localizations" : { - "en" : { + "tr" : { "stringUnit" : { - "state" : "new", - "value" : "Device tokens are alternating in recent profile uploads (%1$@ tokens involved across %2$@ records). This usually means more than one app installation is uploading to the same Nightscout. Remove the app from spare or unused phones." + "state" : "translated", + "value" : "Alarm Adı" } } } }, - "Dexcom Settings" : {}, - "Dexcom Share" : {}, - "Diagnostics" : { - "extractionState" : "manual" + "Alarm Settings" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Alarm Ayarları" + } + } + } }, - "Diagnostics unavailable: %@" : {}, - "Disconnect" : {}, - "Disconnected for %@" : {}, - "Dismiss" : { - "extractionState" : "manual" + "Alarm not found" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Alarm bulunamadı" + } + } + } }, - "Display" : { - "extractionState" : "manual" + "Alarms can behave differently during the day and at night." : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Alarmlar gündüz ve gece farklı davranabilir." + } + } + } }, - "Display App Badge" : { - "extractionState" : "manual" + "Alert Sound" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Uyarı Sesi" + } + } + } }, - "Display Dots" : { - "extractionState" : "manual" + "All" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tümü" + } + } + } }, - "Display Lines" : { - "extractionState" : "manual" + "All Alerts Snoozed" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tüm Uyarılar Ertelendi" + } + } + } }, - "Display Small Graph" : { - "extractionState" : "manual" + "All Sounds Muted" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tüm Sesler Kapalı" + } + } + } }, - "Display Stats" : { - "extractionState" : "manual" + "All alerts snoozed" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tüm uyarılar ertelendi" + } + } + } }, - "Do you want to activate the override '%@'?" : {}, - "Done" : { - "extractionState" : "manual" + "All glucose, insulin, and carb data. Your Nightscout URL and API token. Your Dexcom credentials. Remote-command secrets and APNS keys. Time zone. Location data. Logs — these are never sent automatically; the Settings → Logs sharing flow is unchanged and only triggered by you." : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tüm glukoz, insülin ve karbonhidrat verileri. Nightscout URL'niz ve API belirteciniz. Dexcom kimlik bilgileriniz. Uzaktan komut sırları ve APNS anahtarları. Saat dilimi. Konum verileri. Günlükler — bunlar asla otomatik gönderilmez; Ayarlar → Günlükler paylaşım akışı değişmedi ve yalnızca siz tetiklediğinizde çalışır." + } + } + } }, - "Download Prediction" : { - "extractionState" : "manual" + "Always" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Her Zaman" + } + } + } }, - "Download Treatments" : { - "extractionState" : "manual" - }, - "Drag to reorder" : {}, - "Drag to reorder. Tap a row to set its visibility and colors." : {}, - "Duration" : {}, - "Duration: %@" : {}, - "Duration: %@ minutes" : {}, - "Dynamic mode colors text based on BG range: Green (in range), Yellow (high), Red (low)" : {}, - "Enable Contact BG Updates" : { - "extractionState" : "manual" + "Ambulatory Glucose Profile (AGP)" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ambulatuvar Glukoz Profili (AGP)" + } + } + } }, - "Enable Live Activity" : { - "extractionState" : "manual" + "App Settings" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Uygulama Ayarları" + } + } + } }, - "Enable Notifications" : {}, - "Enable WebSocket" : { - "extractionState" : "manual" + "App version, build date, whether this is a TestFlight build, the install instance number, an Apple-supplied per-vendor identifier (IDFV) that resets when all this developer's apps are removed from the device, the hardware identifier (e.g. iPhone15,2), and iOS version. Whether Nightscout and Dexcom are configured (yes/no — no URLs or usernames). Which app you're following (Loop, Trio, etc), if known. A small set of preference flags (units, appearance mode, calendar/contact integration enabled, remote-command type, background refresh method). The full JSON is visible under Diagnostics → What's sent." : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Uygulama sürümü, derleme tarihi, TestFlight derlemesi olup olmadığı, kurulum örnek numarası, bu geliştiricinin tüm uygulamaları cihazdan kaldırıldığında sıfırlanan Apple tarafından sağlanan satıcı bazlı bir tanımlayıcı (IDFV), donanım tanımlayıcısı (ör. iPhone15,2) ve iOS sürümü. Nightscout ve Dexcom'un yapılandırılıp yapılandırılmadığı (evet/hayır — URL veya kullanıcı adı yok). Biliniyorsa hangi uygulamayı takip ettiğiniz (Loop, Trio vb.). Küçük bir tercih bayrağı kümesi (birimler, görünüm modu, takvim/kişi entegrasyonunun açık olup olmadığı, uzaktan komut türü, arka plan yenileme yöntemi). JSON'un tamamı Tanılama → Neler gönderiliyor altında görülebilir." + } + } + } }, - "Enable coloring" : { - "extractionState" : "manual" + "Appearance" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Görünüm" + } + } + } }, - "Enable indefinitely" : { - "extractionState" : "manual" + "Are you sure you want to cancel the active override?" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Etkin override'ı iptal etmek istediğinizden emin misiniz?" + } + } + } }, - "Enable this alarm" : { - "extractionState" : "manual" + "Are you sure you want to cancel the existing temp target?" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mevcut geçici hedefi iptal etmek istediğinizden emin misiniz?" + } + } + } }, - "Enabled" : { - "extractionState" : "manual" + "Are you sure you want to send %@ U?" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@ U göndermek istediğinizden emin misiniz?" + } + } + } }, - "Enact" : {}, - "End Date" : {}, - "End now" : {}, - "Endpoint" : {}, - "English" : {}, - "Enter QR code URL or scan from Loop app" : { - "extractionState" : "manual" + "Available Overrides" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kullanılabilir Override'lar" + } + } + } }, - "Enter URL" : { - "extractionState" : "manual" + "Available Variables" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kullanılabilir Değişkenler" + } + } + } }, - "Enter User" : { - "extractionState" : "manual" + "BG Update Delay (Sec): %@" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "KŞ Güncelleme Gecikmesi (sn): %@" + } + } + } }, - "Enter Username" : { - "extractionState" : "manual" - }, - "Enter or paste your full Nightscout address. If the link already includes a token, we'll fill both in for you." : {}, - "Environment" : {}, - "Error" : {}, - "Eventual" : {}, - "Existing Temp Target" : {}, - "Expected bg delay: %@" : {}, - "Expected: 1 CGM reading every 5 minutes" : {}, - "Export" : { - "extractionState" : "manual" + "Back" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Geri" + } + } + } }, - "Export %@" : {}, - "Export Settings To QR Code" : { - "extractionState" : "manual" + "Background Color" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Arka Plan Rengi" + } + } + } }, - "F%@ P%@" : { + "Background Refresh Type" : { + "extractionState" : "manual", "localizations" : { - "en" : { + "tr" : { "stringUnit" : { - "state" : "new", - "value" : "F%1$@ P%2$@" + "state" : "translated", + "value" : "Arka Plan Yenileme Türü" } } } }, - "FALLING BG" : {}, - "Failed to generate QR code" : {}, - "Fast Drop Alert" : { - "comment" : "Alarm type name", - "extractionState" : "manual" + "Basal / BG Scale" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bazal / KŞ Ölçeği" + } + } + } }, - "Fast Rise Alert" : { + "Battery Drop" : { "comment" : "Alarm type name", - "extractionState" : "manual" - }, - "Features" : { - "extractionState" : "manual" - }, - "Filter" : { - "extractionState" : "manual" + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pil Düşüşü" + } + } + } }, - "Finish" : {}, - "Food Type" : {}, - "Force portrait mode" : { - "extractionState" : "manual" + "Below is the exact JSON object that LoopFollow would send to lf.bjorkert.se right now." : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "LoopFollow'un şu anda lf.bjorkert.se adresine göndereceği JSON nesnesinin tamamı aşağıdadır." + } + } + } }, - "French" : {}, - "Frequency" : {}, - "Future Carbs Alert" : { - "comment" : "Alarm type name", - "extractionState" : "manual" + "Below: %@" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Alt: %@" + } + } + } }, - "GMI" : {}, - "GRI (Glucose Risk Index)" : {}, - "General" : {}, - "Give each alarm a unique name—especially if you’ve added more than one of the same kind—so you can tell them apart at a glance." : {}, - "Glucose" : { - "extractionState" : "manual" + "Bolus" : { + "extractionState" : "manual", + "shouldTranslate" : false }, - "Glucose Unit" : { - "extractionState" : "manual" + "Bolus Filter" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bolus Filtresi" + } + } + } }, - "Glucose value" : {}, - "Glycemic Metrics" : {}, - "Graph Basal" : { - "extractionState" : "manual" + "Bolus Increment" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bolus Artışı" + } + } + } }, - "Graph Bolus" : { - "extractionState" : "manual" + "Bolus Increment: %.3f U" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bolus Artışı: %.3f U" + } + } + } }, - "Graph Carbs" : { - "extractionState" : "manual" + "Build Information" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Derleme Bilgileri" + } + } + } }, - "Graph Display" : { - "extractionState" : "manual" + "Built-in" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Yerleşik" + } + } + } }, - "Graph Other Treatments" : { - "extractionState" : "manual" + "Bundle ID: %@" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Paket Kimliği: %@" + } + } + } }, - "Grid Slot - CarPlay / Watch" : {}, - "Grid Slots - Live Activity" : {}, - "Guardrails" : {}, - "Help us help you" : {}, - "Help us help you!" : { - "extractionState" : "manual" + "CHARGING" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "ŞARJ OLUYOR" + } + } + } }, - "Hide Information Table" : {}, - "High" : { - "extractionState" : "manual" + "COB" : { + "shouldTranslate" : false }, - "High BG Alert" : { + "COB Alert" : { "comment" : "Alarm type name", - "extractionState" : "manual" - }, - "History" : { - "extractionState" : "manual" - }, - "Home" : { - "extractionState" : "manual" + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "COB Uyarısı" + } + } + } }, - "Hours" : { - "extractionState" : "manual" + "CV" : { + "shouldTranslate" : false }, - "Hyperglycemia Component (%)" : {}, - "Hypoglycemia Component (%)" : {}, - "I have a token" : {}, - "IOB" : {}, - "IOB Alert" : { - "comment" : "Alarm type name", - "extractionState" : "manual" + "Calculated %lld minutes ago" : { + "comment" : "Age of the recommended bolus calculation", + "extractionState" : "manual", + "localizations" : { + "en" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "Calculated %lld minute ago" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "Calculated %lld minutes ago" + } + } + } + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld dakika önce hesaplandı" + } + } + } }, - "Import Settings" : {}, - "Import Settings from QR Code" : {}, - "Import Sound…" : { - "extractionState" : "manual" + "Calendar" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Takvim" + } + } + } }, - "Import/Export Settings" : {}, - "In Menu" : {}, - "Inactive" : {}, - "Include Delta in" : { - "extractionState" : "manual" + "Calendar Text" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Takvim Metni" + } + } + } }, - "Include IOB in" : { - "extractionState" : "manual" + "Calendar access denied" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Takvim erişimi reddedildi" + } + } + } }, - "Include Trend in" : { - "extractionState" : "manual" + "Cancel" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "İptal" + } + } + } }, - "Increment" : {}, - "Information Display Settings" : {}, - "Insulin" : { - "extractionState" : "manual" + "Cancel Active Override" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Etkin Override'ı İptal Et" + } + } + } }, - "Insulin Totals" : {}, - "Insulin: %@%%" : {}, - "Invalid QR code URL" : {}, - "Italian" : {}, - "Keep Screen Active" : { - "extractionState" : "manual" + "Cancel Override" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Override'ı İptal Et" + } + } + } }, - "Key ID must be exactly 10 uppercase letters or digits." : {}, - "Language" : { - "extractionState" : "manual" + "Cancel Temp Target" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Geçici Hedefi İptal Et" + } + } + } }, - "Last connection: %@" : {}, - "Line 1" : { - "extractionState" : "manual" + "Carbs" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Karbonhidrat" + } + } + } }, - "Line 2" : { - "extractionState" : "manual" + "Category" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kategori" + } + } + } }, - "Live Activity" : {}, - "Live Activity updates require APNs credentials. Configure them in Settings → APN." : {}, - "Load More" : {}, - "Loading data..." : { - "extractionState" : "manual" + "Character Limit Reached" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Karakter Sınırına Ulaşıldı" + } + } + } + }, + "Checking Nightscout profile history…" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nightscout profil geçmişi denetleniyor…" + } + } + } + }, + "Choose Tone" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ton Seç" + } + } + } + }, + "Close" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kapat" + } + } + } + }, + "Color" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Renk" + } + } + } + }, + "Color BG Text" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "KŞ Metnini Renklendir" + } + } + } + }, + "Color Mode" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Renk Modu" + } + } + } + }, + "Color Options" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Renk Seçenekleri" + } + } + } + }, + "Coloring enabled" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Renklendirme açık" + } + } + } + }, + "Command Sent" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Komut Gönderildi" + } + } + } + }, + "Configure Loop APNS" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop APNS'i Yapılandır" + } + } + } + }, + "Confirm" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Onayla" + } + } + } + }, + "Confirm & Import" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Onayla ve İçe Aktar" + } + } + } + }, + "Confirm Bolus" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bolusu Onayla" + } + } + } + }, + "Confirm Cancellation" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "İptali Onayla" + } + } + } + }, + "Confirm Carbs" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Karbonhidratı Onayla" + } + } + } + }, + "Confirm Command" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Komutu Onayla" + } + } + } + }, + "Confirm Insulin" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "İnsülini Onayla" + } + } + } + }, + "Confirm Meal" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Öğünü Onayla" + } + } + } + }, + "Connected" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bağlı" + } + } + } + }, + "Consumption Time" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tüketim Zamanı" + } + } + } + }, + "Contact Integration" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kişi Entegrasyonu" + } + } + } + }, + "Continue" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Devam" + } + } + } + }, + "Copy JSON" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "JSON'u Kopyala" + } + } + } + }, + "Create Read-Only Token" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Salt Okunur Belirteç Oluştur" + } + } + } + }, + "Create one for me" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Benim için oluştur" + } + } + } + }, + "Creating read-only token…" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Salt okunur belirteç oluşturuluyor…" + } + } + } + }, + "Current OTP Code" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Güncel OTP Kodu" + } + } + } + }, + "Current TOTP Code:" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Güncel TOTP Kodu:" + } + } + } + }, + "Current Target" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Güncel Hedef" + } + } + } + }, + "Current Token:" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Güncel Belirteç:" + } + } + } + }, + "Current URL:" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Güncel URL:" + } + } + } + }, + "Custom" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Özel" + } + } + } + }, + "Custom sounds stay on this device and aren't included in settings export." : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Özel sesler bu cihazda kalır ve ayar dışa aktarımına dahil edilmez." + } + } + } + }, + "Cutoffs in %@" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sınırlar (%@)" + } + } + } + }, + "Data Availability" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Veri Kullanılabilirliği" + } + } + } + }, + "Day" : { + "comment" : "Alarm schedule option: daytime only", + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Gündüz" + } + } + } + }, + "Day & Night" : { + "comment" : "Alarm schedule option: at any time of day", + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Gündüz ve Gece" + } + } + } + }, + "Day / Night" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Gündüz / Gece" + } + } + } + }, + "Day / Night Schedule" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Gündüz / Gece Programı" + } + } + } + }, + "Day starts" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Gündüz başlangıcı" + } + } + } + }, + "Debug / Info" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hata Ayıklama / Bilgi" + } + } + } + }, + "Debug Log Level" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hata Ayıklama Günlük Düzeyi" + } + } + } + }, + "Default Snooze:" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Varsayılan Erteleme:" + } + } + } + }, + "Delay Between Sounds" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sesler Arası Gecikme" + } + } + } + }, + "Delete" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sil" + } + } + } + }, + "Delete Alarm" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Alarmı Sil" + } + } + } + }, + "Delete this alarm?" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bu alarm silinsin mi?" + } + } + } + }, + "Delta" : { + "shouldTranslate" : false + }, + "Description" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Açıklama" + } + } + } + }, + "Developer Team ID" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Geliştirici Ekip Kimliği" + } + } + } + }, + "Device Data" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cihaz Verileri" + } + } + } + }, + "Device Token: %@" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cihaz Belirteci: %@" + } + } + } + }, + "Device tokens are alternating in recent profile uploads (%@ tokens involved across %@ records). This usually means more than one app installation is uploading to the same Nightscout. Remove the app from spare or unused phones." : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Device tokens are alternating in recent profile uploads (%1$@ tokens involved across %2$@ records). This usually means more than one app installation is uploading to the same Nightscout. Remove the app from spare or unused phones." + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Son profil yüklemelerinde cihaz belirteçleri dönüşümlü olarak değişiyor (%2$@ kayıtta %1$@ belirteç). Bu genellikle aynı Nightscout'a birden fazla uygulama kurulumunun yükleme yaptığı anlamına gelir. Uygulamayı yedek veya kullanılmayan telefonlardan kaldırın." + } + } + } + }, + "Dexcom Settings" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dexcom Ayarları" + } + } + } + }, + "Dexcom Share" : { + "shouldTranslate" : false + }, + "Diagnostics" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tanılama" + } + } + } + }, + "Diagnostics unavailable: %@" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tanılama kullanılamıyor: %@" + } + } + } + }, + "Disconnect" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bağlantıyı Kes" + } + } + } + }, + "Disconnected for %@" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@ süredir bağlantı yok" + } + } + } + }, + "Dismiss" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kapat" + } + } + } + }, + "Display" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Görüntüleme" + } + } + } + }, + "Display App Badge" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Uygulama Rozetini Göster" + } + } + } + }, + "Display Dots" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Noktaları Göster" + } + } + } + }, + "Display Lines" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Çizgileri Göster" + } + } + } + }, + "Display Small Graph" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Küçük Grafiği Göster" + } + } + } + }, + "Display Stats" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "İstatistikleri Göster" + } + } + } + }, + "Do you want to activate the override '%@'?" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "'%@' override'ını etkinleştirmek istiyor musunuz?" + } + } + } + }, + "Done" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bitti" + } + } + } + }, + "Download Prediction" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tahmini İndir" + } + } + } + }, + "Download Treatments" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tedavileri İndir" + } + } + } + }, + "Drag to reorder" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sıralamak için sürükleyin" + } + } + } + }, + "Drag to reorder. Tap a row to set its visibility and colors." : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sıralamak için sürükleyin. Görünürlüğü ve renkleri ayarlamak için bir satıra dokunun." + } + } + } + }, + "Duration" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Süre" + } + } + } + }, + "Duration: %@" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Süre: %@" + } + } + } + }, + "Duration: %@ minutes" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Süre: %@ dakika" + } + } + } + }, + "Dynamic mode colors text based on BG range: Green (in range), Yellow (high), Red (low)" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dinamik mod metni KŞ aralığına göre renklendirir: Yeşil (aralıkta), Sarı (yüksek), Kırmızı (düşük)" + } + } + } + }, + "Enable Contact BG Updates" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kişi KŞ Güncellemelerini Aç" + } + } + } + }, + "Enable Live Activity" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Canlı Etkinliği Aç" + } + } + } + }, + "Enable Notifications" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bildirimleri Aç" + } + } + } + }, + "Enable WebSocket" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "WebSocket'i Aç" + } + } + } + }, + "Enable coloring" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Renklendirmeyi aç" + } + } + } + }, + "Enable indefinitely" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Süresiz etkinleştir" + } + } + } + }, + "Enable this alarm" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bu alarmı aç" + } + } + } + }, + "Enabled" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Açık" + } + } + } + }, + "Enact" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Uygula" + } + } + } + }, + "End Date" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bitiş Tarihi" + } + } + } + }, + "End now" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Şimdi bitir" + } + } + } + }, + "Endpoint" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Uç Nokta" + } + } + } + }, + "English" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "İngilizce" + } + } + } + }, + "Enter QR code URL or scan from Loop app" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "QR kodu URL'sini girin veya Loop uygulamasından tarayın" + } + } + } + }, + "Enter URL" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "URL girin" + } + } + } + }, + "Enter User" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kullanıcı girin" + } + } + } + }, + "Enter Username" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kullanıcı adı girin" + } + } + } + }, + "Enter or paste your full Nightscout address. If the link already includes a token, we'll fill both in for you." : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tam Nightscout adresinizi girin veya yapıştırın. Bağlantı zaten bir belirteç içeriyorsa ikisini de sizin için dolduracağız." + } + } + } + }, + "Environment" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ortam" + } + } + } + }, + "Error" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hata" + } + } + } + }, + "Eventual" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nihai" + } + } + } + }, + "Existing Temp Target" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mevcut Geçici Hedef" + } + } + } + }, + "Expected bg delay: %@" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Beklenen KŞ gecikmesi: %@" + } + } + } + }, + "Expected: 1 CGM reading every 5 minutes" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Beklenen: her 5 dakikada 1 CGM okuması" + } + } + } + }, + "Export" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dışa Aktar" + } + } + } + }, + "Export %@" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@ Dışa Aktar" + } + } + } + }, + "Export Settings To QR Code" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ayarları QR Koduna Aktar" + } + } + } + }, + "F%@ P%@" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "F%1$@ P%2$@" + } + } + }, + "shouldTranslate" : false + }, + "FALLING BG" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "KŞ DÜŞÜYOR" + } + } + } + }, + "Failed to generate QR code" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "QR kodu oluşturulamadı" + } + } + } + }, + "Fast Drop Alert" : { + "comment" : "Alarm type name", + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hızlı Düşüş Uyarısı" + } + } + } + }, + "Fast Rise Alert" : { + "comment" : "Alarm type name", + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hızlı Yükseliş Uyarısı" + } + } + } + }, + "Features" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Özellikler" + } + } + } + }, + "Filter" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Filtre" + } + } + } + }, + "Finish" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bitir" + } + } + } + }, + "Food Type" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Yiyecek Türü" + } + } + } + }, + "Force portrait mode" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dikey modu zorla" + } + } + } + }, + "French" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Fransızca" + } + } + } + }, + "Frequency" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sıklık" + } + } + } + }, + "Future Carbs Alert" : { + "comment" : "Alarm type name", + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Gelecek Karbonhidrat Uyarısı" + } + } + } + }, + "GMI" : { + "shouldTranslate" : false + }, + "GRI (Glucose Risk Index)" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "GRI (Glukoz Risk İndeksi)" + } + } + } + }, + "General" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Genel" + } + } + } + }, + "Give each alarm a unique name—especially if you’ve added more than one of the same kind—so you can tell them apart at a glance." : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Her alarma benzersiz bir ad verin — özellikle aynı türden birden fazla eklediyseniz — böylece bir bakışta ayırt edebilirsiniz." + } + } + } + }, + "Glucose" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Glukoz" + } + } + } + }, + "Glucose Unit" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Glukoz Birimi" + } + } + } + }, + "Glucose value" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Glukoz değeri" + } + } + } + }, + "Glycemic Metrics" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Glisemik Ölçütler" + } + } + } + }, + "Graph Basal" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bazal Grafiği" + } + } + } + }, + "Graph Bolus" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bolus Grafiği" + } + } + } + }, + "Graph Carbs" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Karbonhidrat Grafiği" + } + } + } + }, + "Graph Display" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Grafik Görünümü" + } + } + } + }, + "Graph Other Treatments" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Diğer Tedavilerin Grafiği" + } + } + } + }, + "Grid Slot - CarPlay / Watch" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Izgara Yuvası - CarPlay / Watch" + } + } + } + }, + "Grid Slots - Live Activity" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Izgara Yuvaları - Canlı Etkinlik" + } + } + } + }, + "Guardrails" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Güvenlik Sınırları" + } + } + } + }, + "Help us help you" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Size yardımcı olmamıza yardım edin" + } + } + } + }, + "Help us help you!" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Size yardımcı olmamıza yardım edin!" + } + } + } + }, + "Hide Information Table" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bilgi Tablosunu Gizle" + } + } + } + }, + "High" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Yüksek" + } + } + } + }, + "High BG Alert" : { + "comment" : "Alarm type name", + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Yüksek KŞ Uyarısı" + } + } + } + }, + "History" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Geçmiş" + } + } + } + }, + "Home" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ana Sayfa" + } + } + } + }, + "Hours" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Saat" + } + } + } + }, + "Hyperglycemia Component (%)" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hiperglisemi Bileşeni (%)" + } + } + } + }, + "Hypoglycemia Component (%)" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hipoglisemi Bileşeni (%)" + } + } + } + }, + "I have a token" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Belirtecim var" + } + } + } + }, + "IOB" : { + "shouldTranslate" : false + }, + "IOB Alert" : { + "comment" : "Alarm type name", + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "IOB Uyarısı" + } + } + } + }, + "Import Settings" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ayarları İçe Aktar" + } + } + } + }, + "Import Settings from QR Code" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ayarları QR Kodundan İçe Aktar" + } + } + } + }, + "Import Sound…" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ses İçe Aktar…" + } + } + } + }, + "Import/Export Settings" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ayarları İçe/Dışa Aktar" + } + } + } + }, + "In Menu" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Menüde" + } + } + } + }, + "Inactive" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Etkin Değil" + } + } + } + }, + "Include Delta in" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Delta'yı şuraya ekle" + } + } + } + }, + "Include IOB in" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "IOB'yi şuraya ekle" + } + } + } + }, + "Include Trend in" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Trendi şuraya ekle" + } + } + } + }, + "Increment" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Artış" + } + } + } + }, + "Information Display Settings" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bilgi Görüntüleme Ayarları" + } + } + } + }, + "Insulin" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "İnsülin" + } + } + } + }, + "Insulin Totals" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "İnsülin Toplamları" + } + } + } + }, + "Insulin: %@%%" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "İnsülin: %@%%" + } + } + } + }, + "Invalid QR code URL" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Geçersiz QR kodu URL'si" + } + } + } + }, + "Italian" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "İtalyanca" + } + } + } + }, + "Keep Screen Active" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ekranı Açık Tut" + } + } + } + }, + "Key ID must be exactly 10 uppercase letters or digits." : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Anahtar Kimliği tam olarak 10 büyük harf veya rakamdan oluşmalıdır." + } + } + } + }, + "Language" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dil" + } + } + } + }, + "Last connection: %@" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Son bağlantı: %@" + } + } + } + }, + "Line 1" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Satır 1" + } + } + } + }, + "Line 2" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Satır 2" + } + } + } + }, + "Live Activity" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Canlı Etkinlik" + } + } + } + }, + "Live Activity updates require APNs credentials. Configure them in Settings → APN." : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Canlı Etkinlik güncellemeleri APNs kimlik bilgileri gerektirir. Ayarlar → APN'den yapılandırın." + } + } + } + }, + "Load More" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Daha Fazla Yükle" + } + } + } + }, + "Loading data..." : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Veriler yükleniyor..." + } + } + } + }, + "Loading override presets..." : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Override ön ayarları yükleniyor..." + } + } + } + }, + "Loading stats..." : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "İstatistikler yükleniyor..." + } + } + } }, - "Loading override presets..." : {}, - "Loading stats..." : {}, "Logging" : { - "extractionState" : "manual" - }, - "Logging Options" : {}, - "Loop APNS Configuration" : {}, - "Loop APNS Not Configured" : {}, - "LoopFollow APNs Credentials" : {}, - "LoopFollow uses notifications to deliver your alarms — like low or high glucose, or a missed reading. Without them, alarms can't reach you when the app is in the background." : {}, - "LoopFollow/Helpers/Telemetry.swift on GitHub." : {}, + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Günlük Kaydı" + } + } + } + }, + "Logging Options" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Günlük Seçenekleri" + } + } + } + }, + "Loop APNS Configuration" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop APNS Yapılandırması" + } + } + } + }, + "Loop APNS Not Configured" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop APNS Yapılandırılmamış" + } + } + } + }, + "LoopFollow APNs Credentials" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "LoopFollow APNs Kimlik Bilgileri" + } + } + } + }, + "LoopFollow uses notifications to deliver your alarms — like low or high glucose, or a missed reading. Without them, alarms can't reach you when the app is in the background." : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "LoopFollow alarmlarınızı — düşük veya yüksek glukoz, kaçırılan okuma gibi — bildirimlerle iletir. Bildirimler olmadan uygulama arka plandayken alarmlar size ulaşamaz." + } + } + } + }, + "LoopFollow/Helpers/Telemetry.swift on GitHub." : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "GitHub'da LoopFollow/Helpers/Telemetry.swift." + } + } + } + }, "Looping app expiration" : { "comment" : "Alarm type name", - "extractionState" : "manual" + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop uygulaması süre sonu" + } + } + } + }, + "Low" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Düşük" + } + } + } + }, + "Low BG Alert" : { + "comment" : "Alarm type name", + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Düşük KŞ Uyarısı" + } + } + } + }, + "Low Battery" : { + "comment" : "Alarm type name", + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Düşük Pil" + } + } + } + }, + "Max" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Maks" + } + } + } + }, + "Max %@ alarms at a time" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aynı anda en fazla %@ alarm" + } + } + } + }, + "Max Bolus" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Maks Bolus" + } + } + } + }, + "Max Carbs" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Maks Karbonhidrat" + } + } + } + }, + "Max Fat" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Maks Yağ" + } + } + } + }, + "Max Protein" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Maks Protein" + } + } + } + }, + "Maximum" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Maksimum" + } + } + } + }, + "Meal" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Öğün" + } + } + } + }, + "Meal Data" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Öğün Verileri" + } + } + } + }, + "Meal Settings" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Öğün Ayarları" + } + } + } + }, + "Meal with Bolus" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Boluslu Öğün" + } + } + } + }, + "Meal with Fat/Protein" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Yağ/Proteinli Öğün" + } + } + } + }, + "Menu" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Menü" + } + } + } + }, + "Metric" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ölçüt" + } + } + } + }, + "Min" : { + "shouldTranslate" : false + }, + "Minimum" : { + "shouldTranslate" : false + }, + "Minutes" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dakika" + } + } + } + }, + "Missed Bolus Alert" : { + "comment" : "Alarm type name", + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kaçırılan Bolus Uyarısı" + } + } + } + }, + "Missed Reading Alert" : { + "comment" : "Alarm type name", + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kaçırılan Okuma Uyarısı" + } + } + } + }, + "Multiple devices uploading profiles" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Birden fazla cihaz profil yüklüyor" + } + } + } + }, + "Name" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ad" + } + } + } + }, + "Never" : { + "comment" : "Alarm schedule option: never", + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hiçbir Zaman" + } + } + } + }, + "New Target: %@ %@\nDuration: %@ minutes" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "New Target: %1$@ %2$@\nDuration: %3$@ minutes" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Yeni Hedef: %@ %@\nSüre: %@ dakika" + } + } + } + }, + "Night" : { + "comment" : "Alarm schedule option: nighttime only", + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Gece" + } + } + } + }, + "Night starts" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Gece başlangıcı" + } + } + } + }, + "Nightscout Database Size" : { + "comment" : "Alarm type name", + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nightscout Veritabanı Boyutu" + } + } + } + }, + "Nightscout URL" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nightscout URL'si" + } + } + } + }, + "No Results" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sonuç Yok" + } + } + } + }, + "No alarms match “%@”." : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "“%@” ile eşleşen alarm yok." + } + } + } + }, + "No background refresh. Alarms and updates will not work unless the app is open in the foreground." : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Arka plan yenilemesi yok. Uygulama ön planda açık olmadıkça alarmlar ve güncellemeler çalışmaz." + } + } + } + }, + "No data available" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Veri yok" + } + } + } + }, + "No devices found yet. They'll appear here when discovered." : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Henüz cihaz bulunamadı. Bulunduklarında burada görünecekler." + } + } + } + }, + "No issues detected" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sorun saptanmadı" + } + } + } + }, + "No matches for “%@”." : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "“%@” için eşleşme yok." + } + } + } + }, + "No override presets found. Configure presets in your Loop app." : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Override ön ayarı bulunamadı. Ön ayarları Loop uygulamanızda yapılandırın." + } + } + } + }, + "No overrides available." : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kullanılabilir override yok." + } + } + } + }, + "No recent treatments" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Yakın zamanda tedavi yok" + } + } + } + }, + "No thanks" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hayır, teşekkürler" + } + } + } + }, + "Not Looping Alert" : { + "comment" : "Alarm type name", + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop Çalışmıyor Uyarısı" + } + } + } + }, + "Not now" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Şimdi değil" + } + } + } + }, + "Note: The meal will be scheduled, but the bolus is enacted immediately." : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Not: Öğün planlanır ancak bolus hemen uygulanır." + } + } + } + }, + "OK" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tamam" + } + } + } + }, + "Off" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kapalı" + } + } + } + }, + "Old Calculation Warning" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Eski Hesaplama Uyarısı" + } + } + } + }, + "Once a day (or after a new build), the app sends a small JSON object to https://lf.bjorkert.se. The endpoint is self-hosted by the maintainer; no third-party analytics service is involved." : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Günde bir kez (veya yeni bir derlemeden sonra) uygulama https://lf.bjorkert.se adresine küçük bir JSON nesnesi gönderir. Uç nokta proje sorumlusu tarafından barındırılır; üçüncü taraf bir analiz hizmeti kullanılmaz." + } + } + } + }, + "Once every 24 hours, plus once after installing a new build. The check runs in the background while the app is active or refreshing in the background." : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "24 saatte bir, ayrıca yeni bir derleme kurulduktan sonra bir kez. Denetim, uygulama etkinken veya arka planda yenilenirken arka planda çalışır." + } + } + } + }, + "Opt out" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vazgeç" + } + } + } + }, + "Output Unit" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Çıkış Birimi" + } + } + } + }, + "Outside US" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "ABD Dışı" + } + } + } + }, + "Override" : { + "shouldTranslate" : false + }, + "Override Ended" : { + "comment" : "Alarm type name", + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Override Sona Erdi" + } + } + } + }, + "Override Started" : { + "comment" : "Alarm type name", + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Override Başladı" + } + } + } + }, + "Override system volume" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sistem ses düzeyini geçersiz kıl" + } + } + } + }, + "Overrides" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Override'lar" + } + } + } + }, + "Overriding the system volume lets alarms be heard even when your phone is silenced or in a Focus mode." : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sistem ses düzeyini geçersiz kılmak, telefonunuz sessizdeyken veya Odaklanma modundayken bile alarmların duyulmasını sağlar." + } + } + } + }, + "Password" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Parola" + } + } + } + }, + "Paste a Nightscout token. If your site needs one and you only have the API secret, paste that instead — LoopFollow can create a read-only token for you." : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bir Nightscout belirteci yapıştırın. Siteniz belirteç gerektiriyor ve elinizde yalnızca API secret varsa onu yapıştırın — LoopFollow sizin için salt okunur bir belirteç oluşturabilir." + } + } + } + }, + "Paste the full .p8 contents — must include the BEGIN PRIVATE KEY and END PRIVATE KEY lines." : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : ".p8 dosyasının tüm içeriğini yapıştırın — BEGIN PRIVATE KEY ve END PRIVATE KEY satırlarını içermelidir." + } + } + } + }, + "Percentage: %@%%" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Yüzde: %@%%" + } + } + } + }, + "Persistent Notification" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kalıcı Bildirim" + } + } + } + }, + "Pick when your day period begins and when your night period begins. " : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Gündüz ve gece dönemlerinizin ne zaman başladığını seçin. " + } + } + } + }, + "Play sound during calls" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aramalar sırasında ses çal" + } + } + } + }, + "Please configure Loop APNS settings in Remote Settings to use APNS commands." : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "APNS komutlarını kullanmak için lütfen Uzaktan Ayarlar'da Loop APNS ayarlarını yapılandırın." + } + } + } + }, + "Please enter your Nightscout URL in Settings." : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Lütfen Ayarlar'da Nightscout URL'nizi girin." + } + } + } + }, + "Please select a Remote Type in Settings." : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Lütfen Ayarlar'da bir Uzaktan Kumanda Türü seçin." + } + } + } + }, + "Please wait..." : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Lütfen bekleyin..." + } + } + } + }, + "Prediction" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tahmin" + } + } + } + }, + "Prediction Style" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tahmin Stili" + } + } + } + }, + "Preset Name" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ön Ayar Adı" + } + } + } + }, + "Preset: %@" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ön ayar: %@" + } + } + } + }, + "Presets" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ön Ayarlar" + } + } + } + }, + "Privacy" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Gizlilik" + } + } + } + }, + "Proactive Low" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Öngörülü Düşük" + } + } + } + }, + "Production" : { + "extractionState" : "manual", + "shouldTranslate" : false + }, + "Production is used for browser builders and should be switched off for Xcode builders" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Production tarayıcı derleyicileri içindir; Xcode ile derleyenler için kapatılmalıdır" + } + } + } + }, + "Pump Battery Alert" : { + "comment" : "Alarm type name", + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pompa Pili Uyarısı" + } + } + } + }, + "Pump Change Alert" : { + "comment" : "Alarm type name", + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pompa Değişimi Uyarısı" + } + } + } + }, + "Pump Insulin Alert" : { + "comment" : "Alarm type name", + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pompa İnsülini Uyarısı" + } + } + } + }, + "QR Code URL" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "QR Kodu URL'si" + } + } + } + }, + "RISING BG" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "KŞ YÜKSELİYOR" + } + } + } + }, + "RSSI: %@ dBm" : { + "shouldTranslate" : false + }, + "Range" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aralık" + } + } + } + }, + "Range Mode" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aralık Modu" + } + } + } + }, + "Real-time Updates" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Gerçek Zamanlı Güncellemeler" + } + } + } + }, + "Rec. Bolus" : { + "comment" : "Alarm type name: recommended bolus", + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Öner. Bolus" + } + } + } + }, + "Recommended Bolus" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Önerilen Bolus" + } + } + } + }, + "Reconnecting..." : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Yeniden bağlanıyor..." + } + } + } + }, + "Refresh" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Yenile" + } + } + } + }, + "Remote" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Uzaktan" + } + } + } + }, + "Remote Settings" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Uzaktan Ayarlar" + } + } + } + }, + "Requires a Dexcom G6/ONE/G7/ONE+ transmitter within Bluetooth range. Provides updates every 5 minutes and uses less battery than the silent tune method. If you have more than one device to choose from, select the one with the smallest expected bg delay." : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bluetooth menzilinde bir Dexcom G6/ONE/G7/ONE+ vericisi gerektirir. Her 5 dakikada bir güncelleme sağlar ve sessiz melodi yönteminden daha az pil kullanır. Birden fazla cihaz seçeneğiniz varsa beklenen KŞ gecikmesi en küçük olanı seçin." + } + } + } + }, + "Requires a RileyLink-compatible device within Bluetooth range. Provides updates once per minute and uses less battery than the silent tune method." : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bluetooth menzilinde RileyLink uyumlu bir cihaz gerektirir. Dakikada bir güncelleme sağlar ve sessiz melodi yönteminden daha az pil kullanır." + } + } + } + }, + "Requires an OmniPod DASH pod paired with this device within Bluetooth range. Provides updates once every 3 minutes and uses less battery than the silent tune method." : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bluetooth menzilinde bu cihazla eşleşmiş bir OmniPod DASH pod gerektirir. Her 3 dakikada bir güncelleme sağlar ve sessiz melodi yönteminden daha az pil kullanır." + } + } + } + }, + "Review setup anyway" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kurulumu yine de gözden geçir" + } + } + } + }, + "Review the settings that will be imported" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "İçe aktarılacak ayarları gözden geçirin" + } + } + } + }, + "Right slot" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sağ yuva" + } + } + } + }, + "Run diagnostics" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tanılamayı çalıştır" + } + } + } + }, + "Run diagnostics again" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tanılamayı yeniden çalıştır" + } + } + } + }, + "SNOOZE" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "ERTELE" + } + } + } + }, + "Save" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kaydet" + } + } + } + }, + "Save BG to Calendar" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "KŞ'yi Takvime Kaydet" + } + } + } + }, + "Save Preset" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ön Ayarı Kaydet" + } + } + } + }, + "Save as Preset" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ön Ayar Olarak Kaydet" + } + } + } + }, + "Scan QR Code from Loop App" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop Uygulamasından QR Kodu Tara" + } + } + } + }, + "Scan QR Code to Import Settings" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ayarları İçe Aktarmak için QR Kodu Tara" + } + } + } + }, + "Scan this QR code with another LoopFollow app to import %@" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@ içe aktarmak için bu QR kodunu başka bir LoopFollow uygulamasıyla tarayın" + } + } + } + }, + "Scanned Token:" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Taranan Belirteç:" + } + } + } + }, + "Scanned URL:" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Taranan URL:" + } + } + } + }, + "Scanning for %@..." : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@ aranıyor..." + } + } + } + }, + "Schedule" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Program" + } + } + } + }, + "Schedule for later" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sonrası için planla" + } + } + } + }, + "Search" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ara" + } + } + } + }, + "Search alarms" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Alarmlarda ara" + } + } + } + }, + "Secret" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Gizli Anahtar" + } + } + } + }, + "Security" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Güvenlik" + } + } + } + }, + "See exactly what's sent" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tam olarak neyin gönderildiğini gör" + } + } + } + }, + "Select Alarms to Export" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dışa Aktarılacak Alarmları Seç" + } + } + } + }, + "Select the colors for your BG values. Note: not all watch faces allow control over colors. Recommend options like Activity or Modular Duo if you want to customize colors." : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "KŞ değerleriniz için renkleri seçin. Not: her saat kadranı renk denetimine izin vermez. Renkleri özelleştirmek istiyorsanız Aktivite veya Modüler Duo gibi seçenekler önerilir." + } + } + } + }, + "Selected Alarms: %@" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Seçilen Alarmlar: %@" + } + } + } + }, + "Selected Device" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Seçilen Cihaz" + } + } + } + }, + "Send" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Gönder" + } + } + } + }, + "Send %@ units of insulin?" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@ ünite insülin gönderilsin mi?" + } + } + } + }, + "Send %@g of carbs with %@h absorption time at %@?" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Send %1$@g of carbs with %2$@h absorption time at %3$@?" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@g karbonhidrat, %@sa emilim süresiyle, %@ zamanında gönderilsin mi?" + } + } + } + }, + "Send Bolus" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bolus Gönder" + } + } + } + }, + "Send Carbs" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Karbonhidrat Gönder" + } + } + } + }, + "Send Insulin" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "İnsülin Gönder" + } + } + } + }, + "Send Meal" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Öğün Gönder" + } + } + } + }, + "Send anonymous usage stats" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Anonim kullanım istatistikleri gönder" + } + } + } + }, + "Sending Bolus..." : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bolus gönderiliyor..." + } + } + } + }, + "Sending Meal Data..." : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Öğün verileri gönderiliyor..." + } + } + } + }, + "Sending..." : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Gönderiliyor..." + } + } + } + }, + "Sensitivity" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Duyarlılık" + } + } + } + }, + "Sensor Change Alert" : { + "comment" : "Alarm type name", + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sensör Değişimi Uyarısı" + } + } + } + }, + "Server" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sunucu" + } + } + } + }, + "Set Up Units" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Birimleri Ayarla" + } + } + } + }, + "Settings" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ayarlar" + } + } + } + }, + "Settings imported — you're connected." : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ayarlar içe aktarıldı — bağlandınız." + } + } + } + }, + "Settings to Import" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "İçe Aktarılacak Ayarlar" + } + } + } + }, + "Share" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Paylaş" + } + } + } }, - "Low" : { - "extractionState" : "manual" + "Share Logs" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Günlükleri Paylaş" + } + } + } }, - "Low BG Alert" : { - "comment" : "Alarm type name", - "extractionState" : "manual" + "Shared Secret" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Paylaşılan Gizli Anahtar" + } + } + } }, - "Low Battery" : { - "comment" : "Alarm type name", - "extractionState" : "manual" - }, - "Max" : {}, - "Max %@ alarms at a time" : {}, - "Max Bolus" : {}, - "Max Carbs" : {}, - "Max Fat" : {}, - "Max Protein" : {}, - "Maximum" : {}, - "Meal" : { - "extractionState" : "manual" + "Show Carb Absorption" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Karbonhidrat Emilimini Göster" + } + } + } + }, + "Show Carb/Bolus Values" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Karbonhidrat/Bolus Değerlerini Göster" + } + } + } + }, + "Show DIA Lines" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "DIA Çizgilerini Göster" + } + } + } + }, + "Show Delta" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Delta'yı Göster" + } + } + } + }, + "Show Display Name" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Görünen Adı Göster" + } + } + } + }, + "Show IOB" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "IOB'yi Göster" + } + } + } + }, + "Show Midnight Lines" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Gece Yarısı Çizgilerini Göster" + } + } + } + }, + "Show Prior Day Time Lines" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Önceki Gün Zaman Çizgilerini Göster" + } + } + } + }, + "Show Trend" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Trendi Göster" + } + } + } + }, + "Show Yesterday's BG" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dünkü KŞ'yi Göster" + } + } + } + }, + "Show in Info Display" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bilgi Ekranında Göster" + } + } + } + }, + "Show −30 min Line" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "−30 dk Çizgisini Göster" + } + } + } + }, + "Show −90 min Line" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "−90 dk Çizgisini Göster" + } + } + } + }, + "Site URL" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Site URL'si" + } + } + } + }, + "Skip" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Atla" + } + } + } + }, + "Skip if BG is falling" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "KŞ düşüyorsa atla" + } + } + } + }, + "Skip if BG is rising" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "KŞ yükseliyorsa atla" + } + } + } + }, + "Skip while charging" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Şarj olurken atla" + } + } + } + }, + "Slovak" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Slovakça" + } + } + } + }, + "Small Graph" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Küçük Grafik" + } + } + } + }, + "Snooze & Mute Options" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Erteleme ve Sessize Alma Seçenekleri" + } + } + } + }, + "Snooze Date" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Erteleme Tarihi" + } + } + } + }, + "Snooze Time" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Erteleme Saati" + } + } + } + }, + "Snooze all · 1h" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tümünü ertele · 1sa" + } + } + } + }, + "Snooze for" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Erteleme süresi" + } + } + } + }, + "Snoozed" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ertelendi" + } + } + } + }, + "Snoozed until %@" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@ tarihine kadar ertelendi" + } + } + } + }, + "Snoozer emoji" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Erteleyici emojisi" + } + } + } + }, + "Sound" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ses" + } + } + } + }, + "Source" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kaynak" + } + } + } + }, + "Speak BG" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "KŞ'yi Seslendir" + } + } + } + }, + "Start Date" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Başlangıç Tarihi" + } + } + } + }, + "Statistics" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "İstatistikler" + } + } + } + }, + "Status" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Durum" + } + } + } + }, + "Stay informed" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Haberdar olun" + } + } + } + }, + "Stay silent while BG is falling. The alert only sounds when the latest reading is flat or still rising." : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "KŞ düşerken sessiz kal. Uyarı yalnızca son okuma düz seyrederken veya hâlâ yükselirken çalar." + } + } + } + }, + "Stay silent while BG is rising. The alert only sounds when the latest reading is flat or still falling." : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "KŞ yükselirken sessiz kal. Uyarı yalnızca son okuma düz seyrederken veya hâlâ düşerken çalar." + } + } + } }, - "Meal Data" : {}, - "Meal Settings" : {}, - "Meal with Bolus" : { - "extractionState" : "manual" + "Stay silent while the phone is charging. Requires the uploader to report charging status; if it doesn't, the alert still sounds." : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Telefon şarj olurken sessiz kal. Yükleyicinin şarj durumunu bildirmesi gerekir; bildirmiyorsa uyarı yine çalar." + } + } + } }, - "Meal with Fat/Protein" : { - "extractionState" : "manual" + "Std Dev" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Std Sapma" + } + } + } }, - "Menu" : {}, - "Metric" : { - "extractionState" : "manual" + "Std Dev — Standard Deviation, how much glucose swings around the average. CV — Coefficient of Variation, that swing relative to the average (Std Dev ÷ mean)." : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Std Sapma — Standart Sapma, glukozun ortalama etrafında ne kadar dalgalandığı. CV — Varyasyon Katsayısı, bu dalgalanmanın ortalamaya oranı (Std Sapma ÷ ortalama)." + } + } + } }, - "Min" : {}, - "Minimum" : {}, - "Minutes" : { - "extractionState" : "manual" + "Success" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Başarılı" + } + } + } }, - "Missed Bolus Alert" : { - "comment" : "Alarm type name", - "extractionState" : "manual" + "Support & Community" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Destek ve Topluluk" + } + } + } }, - "Missed Reading Alert" : { - "comment" : "Alarm type name", - "extractionState" : "manual" + "Swedish" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "İsveççe" + } + } + } }, - "Multiple devices uploading profiles" : {}, - "Name" : {}, - "Never" : { - "comment" : "Alarm schedule option: never", - "extractionState" : "manual" + "TIR" : { + "shouldTranslate" : false }, - "New Target: %@ %@\nDuration: %@ minutes" : { + "TIR — Time in Range, the share of readings within %@.\nTITR — Time in Tight Range, within %@.\nCustom — set your own low and high." : { "localizations" : { "en" : { "stringUnit" : { "state" : "new", - "value" : "New Target: %1$@ %2$@\nDuration: %3$@ minutes" + "value" : "TIR — Time in Range, the share of readings within %1$@.\nTITR — Time in Tight Range, within %2$@.\nCustom — set your own low and high." + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "TIR — Hedef Aralıkta Süre, %@ içindeki okumaların payı.\nTITR — Dar Hedef Aralıkta Süre, %@ içindeki pay.\nÖzel — kendi düşük ve yüksek değerlerinizi belirleyin." } } } }, - "Night" : { - "comment" : "Alarm schedule option: nighttime only", - "extractionState" : "manual" - }, - "Night starts" : { - "extractionState" : "manual" - }, - "Nightscout Database Size" : { - "comment" : "Alarm type name", - "extractionState" : "manual" - }, - "Nightscout URL" : {}, - "No Results" : {}, - "No alarms match “%@”." : {}, - "No background refresh. Alarms and updates will not work unless the app is open in the foreground." : {}, - "No data available" : {}, - "No devices found yet. They'll appear here when discovered." : {}, - "No issues detected" : {}, - "No matches for “%@”." : {}, - "No override presets found. Configure presets in your Loop app." : {}, - "No overrides available." : {}, - "No recent treatments" : {}, - "No thanks" : {}, - "Not Looping Alert" : { - "comment" : "Alarm type name", - "extractionState" : "manual" - }, - "Not now" : {}, - "Note: The meal will be scheduled, but the bolus is enacted immediately." : {}, - "OK" : {}, - "Off" : {}, - "Old Calculation Warning" : {}, - "Once a day (or after a new build), the app sends a small JSON object to https://lf.bjorkert.se. The endpoint is self-hosted by the maintainer; no third-party analytics service is involved." : {}, - "Once every 24 hours, plus once after installing a new build. The check runs in the background while the app is active or refreshing in the background." : {}, - "Opt out" : {}, - "Output Unit" : { - "extractionState" : "manual" - }, - "Outside US" : {}, - "Override" : {}, - "Override Ended" : { - "comment" : "Alarm type name", - "extractionState" : "manual" + "TITR" : { + "shouldTranslate" : false }, - "Override Started" : { - "comment" : "Alarm type name", - "extractionState" : "manual" + "TOTP Code Already Used" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "TOTP Kodu Zaten Kullanıldı" + } + } + } }, - "Override system volume" : { - "extractionState" : "manual" + "TOTP Code: Invalid QR code URL" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "TOTP Kodu: Geçersiz QR kodu URL'si" + } + } + } }, - "Overrides" : { - "extractionState" : "manual" + "Tab %@" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sekme %@" + } + } + } }, - "Overriding the system volume lets alarms be heard even when your phone is silenced or in a Focus mode." : {}, - "Password" : {}, - "Paste a Nightscout token. If your site needs one and you only have the API secret, paste that instead — LoopFollow can create a read-only token for you." : {}, - "Paste the full .p8 contents — must include the BEGIN PRIVATE KEY and END PRIVATE KEY lines." : {}, - "Percentage: %@%%" : {}, - "Persistent Notification" : { - "extractionState" : "manual" + "Tab 5" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sekme 5" + } + } + } }, - "Pick when your day period begins and when your night period begins. " : { - "extractionState" : "manual" + "Tab Order" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sekme Sırası" + } + } + } }, - "Play sound during calls" : { - "extractionState" : "manual" + "Tabs" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sekmeler" + } + } + } }, - "Please configure Loop APNS settings in Remote Settings to use APNS commands." : {}, - "Please enter your Nightscout URL in Settings." : {}, - "Please select a Remote Type in Settings." : {}, - "Please wait..." : { - "extractionState" : "manual" + "Tap to load older treatments" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Eski tedavileri yüklemek için dokunun" + } + } + } }, - "Prediction" : {}, - "Prediction Style" : { - "extractionState" : "manual" + "Target" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hedef" + } + } + } }, - "Preset Name" : { - "extractionState" : "manual" + "Target Range" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hedef Aralığı" + } + } + } }, - "Preset: %@" : {}, - "Presets" : {}, - "Privacy" : { - "extractionState" : "manual" + "Target: %@ %@" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Target: %1$@ %2$@" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hedef: %@ %@" + } + } + } }, - "Proactive Low" : { - "extractionState" : "manual" + "Target: %@-%@" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Target: %1$@-%2$@" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hedef: %@-%@" + } + } + } }, - "Production" : { - "extractionState" : "manual" + "Team ID: %@" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ekip Kimliği: %@" + } + } + } }, - "Production is used for browser builders and should be switched off for Xcode builders" : {}, - "Pump Battery Alert" : { + "Temp Target Ended" : { "comment" : "Alarm type name", - "extractionState" : "manual" + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Geçici Hedef Sona Erdi" + } + } + } }, - "Pump Change Alert" : { + "Temp Target Started" : { "comment" : "Alarm type name", - "extractionState" : "manual" + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Geçici Hedef Başladı" + } + } + } }, - "Pump Insulin Alert" : { + "Temporary Alert" : { "comment" : "Alarm type name", - "extractionState" : "manual" + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Geçici Uyarı" + } + } + } }, - "QR Code URL" : {}, - "RISING BG" : {}, - "RSSI: %@ dBm" : {}, - "Range" : {}, - "Range Mode" : { - "extractionState" : "manual" + "Temporary Target" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Geçici Hedef" + } + } + } }, - "Real-time Updates" : {}, - "Rec. Bolus" : { - "comment" : "Alarm type name: recommended bolus", - "extractionState" : "manual" + "Text Color" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Metin Rengi" + } + } + } }, - "Recommended Bolus" : {}, - "Reconnecting..." : {}, - "Refresh" : { - "extractionState" : "manual" + "Thanks for sharing these logs to help us find the problem. Please describe it in as much detail as possible — what time did it happen, what did you do, and what did you expect to happen that didn't?" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sorunu bulmamıza yardımcı olmak için bu günlükleri paylaştığınız için teşekkürler. Lütfen olabildiğince ayrıntılı anlatın — ne zaman oldu, ne yaptınız ve ne olmasını bekliyordunuz da olmadı?" + } + } + } }, - "Remote" : { - "extractionState" : "manual" + "That's your API secret — create a read-only token" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bu sizin API secret'ınız — salt okunur bir belirteç oluşturun" + } + } + } }, - "Remote Settings" : { - "extractionState" : "manual" + "The order of Fat and Protein inputs has changed." : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Yağ ve Protein girişlerinin sırası değişti." + } + } + } }, - "Requires a Dexcom G6/ONE/G7/ONE+ transmitter within Bluetooth range. Provides updates every 5 minutes and uses less battery than the silent tune method. If you have more than one device to choose from, select the one with the smallest expected bg delay." : {}, - "Requires a RileyLink-compatible device within Bluetooth range. Provides updates once per minute and uses less battery than the silent tune method." : {}, - "Requires an OmniPod DASH pod paired with this device within Bluetooth range. Provides updates once every 3 minutes and uses less battery than the silent tune method." : {}, - "Review setup anyway" : {}, - "Review the settings that will be imported" : {}, - "Right slot" : { - "extractionState" : "manual" + "The top 4 items appear in the tab bar. The Menu can always open every feature." : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "İlk 4 öğe sekme çubuğunda görünür. Menü her zaman tüm özellikleri açabilir." + } + } + } }, - "Run diagnostics" : {}, - "Run diagnostics again" : {}, - "SNOOZE" : {}, - "Save" : { - "extractionState" : "manual" + "This TOTP code has already been used for a command. Please wait for the next code to be generated before sending another command." : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bu TOTP kodu zaten bir komut için kullanıldı. Başka bir komut göndermeden önce lütfen sonraki kodun üretilmesini bekleyin." + } + } + } }, - "Save BG to Calendar" : { - "extractionState" : "manual" - }, - "Save Preset" : {}, - "Save as Preset" : {}, - "Scan QR Code from Loop App" : {}, - "Scan QR Code to Import Settings" : {}, - "Scan this QR code with another LoopFollow app to import %@" : {}, - "Scanned Token:" : {}, - "Scanned URL:" : {}, - "Scanning for %@..." : {}, - "Schedule" : {}, - "Schedule for later" : { - "extractionState" : "manual" + "This will overwrite your current settings" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bu, mevcut ayarlarınızın üzerine yazacak" + } + } + } }, - "Search" : { - "extractionState" : "manual" + "Time" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zaman" + } + } + } }, - "Search alarms" : { - "extractionState" : "manual" + "Time Remaining" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kalan Süre" + } + } + } + }, + "Time Zone" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Saat Dilimi" + } + } + } + }, + "Time Zone Override" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Saat Dilimi Geçersiz Kılma" + } + } + } + }, + "To see your trend, delta, or IOB, include them in another contact or create separate contacts. When using 'Include', select which contact to add the value to." : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Trend, delta veya IOB'yi görmek için bunları başka bir kişiye ekleyin veya ayrı kişiler oluşturun. 'Ekle' seçeneğini kullanırken değerin hangi kişiye ekleneceğini seçin." + } + } + } + }, + "Token" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Belirteç" + } + } + } }, - "Secret" : {}, - "Security" : {}, - "See exactly what's sent" : { - "extractionState" : "manual" + "Tone" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ton" + } + } + } }, - "Select Alarms to Export" : { - "extractionState" : "manual" + "Treatments" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tedaviler" + } + } + } }, - "Select the colors for your BG values. Note: not all watch faces allow control over colors. Recommend options like Activity or Modular Duo if you want to customize colors." : {}, - "Selected Alarms: %@" : {}, - "Selected Device" : {}, - "Send" : {}, - "Send %@ units of insulin?" : {}, - "Send %@g of carbs with %@h absorption time at %@?" : { + "Treatments on Small Graph" : { + "extractionState" : "manual", "localizations" : { - "en" : { + "tr" : { "stringUnit" : { - "state" : "new", - "value" : "Send %1$@g of carbs with %2$@h absorption time at %3$@?" + "state" : "translated", + "value" : "Küçük Grafikte Tedaviler" } } } }, - "Send Bolus" : {}, - "Send Carbs" : {}, - "Send Insulin" : {}, - "Send Meal" : {}, - "Send anonymous usage stats" : { - "extractionState" : "manual" + "Trend" : { + "shouldTranslate" : false }, - "Sending Bolus..." : {}, - "Sending Meal Data..." : {}, - "Sending..." : {}, - "Sensitivity" : {}, - "Sensor Change Alert" : { - "comment" : "Alarm type name", - "extractionState" : "manual" + "Trio Remote Control Settings" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Trio Uzaktan Kumanda Ayarları" + } + } + } }, - "Server" : { - "extractionState" : "manual" + "Trio Remote Control is only supported for 'Trio'" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Trio Uzaktan Kumanda yalnızca 'Trio' için desteklenir" + } + } + } }, - "Set Up Units" : { - "extractionState" : "manual" + "Type or paste a token, or a full Nightscout URL that includes a token." : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bir belirteç ya da belirteç içeren tam bir Nightscout URL'si yazın veya yapıştırın." + } + } + } }, - "Settings" : { - "extractionState" : "manual" + "U" : { + "comment" : "The short unit display string for international units of insulin", + "shouldTranslate" : false }, - "Settings imported — you're connected." : { - "extractionState" : "manual" + "URL" : { + "shouldTranslate" : false }, - "Settings to Import" : {}, - "Share" : { - "extractionState" : "manual" + "URL/Token Update" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "URL/Belirteç Güncelleme" + } + } + } }, - "Share Logs" : { - "extractionState" : "manual" + "URL/Token Validation" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "URL/Belirteç Doğrulama" + } + } + } }, - "Shared Secret" : {}, - "Show Carb Absorption" : { - "extractionState" : "manual" + "US" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "ABD" + } + } + } }, - "Show Carb/Bolus Values" : { - "extractionState" : "manual" + "Units and Metrics" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Birimler ve Ölçütler" + } + } + } }, - "Show DIA Lines" : { - "extractionState" : "manual" + "Unknown Alert" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bilinmeyen Uyarı" + } + } + } }, - "Show Delta" : { - "extractionState" : "manual" + "Use Anyway" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Yine de Kullan" + } + } + } }, - "Show Display Name" : { - "extractionState" : "manual" + "Use the Send anonymous usage stats toggle above. Turning it off is immediate and persistent." : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Yukarıdaki Anonim kullanım istatistikleri gönder anahtarını kullanın. Kapatmak anında ve kalıcıdır." + } + } + } }, - "Show IOB" : { - "extractionState" : "manual" + "User" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kullanıcı" + } + } + } }, - "Show Midnight Lines" : { - "extractionState" : "manual" + "User Information" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kullanıcı Bilgileri" + } + } + } }, - "Show Prior Day Time Lines" : { - "extractionState" : "manual" + "Username" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kullanıcı Adı" + } + } + } }, - "Show Trend" : { - "extractionState" : "manual" + "Validation Error" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Doğrulama Hatası" + } + } + } }, - "Show Yesterday's BG" : { - "extractionState" : "manual" + "Variability" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Değişkenlik" + } + } + } }, - "Show in Info Display" : { - "extractionState" : "manual" + "View Log" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Günlüğü Görüntüle" + } + } + } }, - "Show −30 min Line" : { - "extractionState" : "manual" + "WARNING: New treatments may have occurred since the last recommended bolus was calculated %@ ago." : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "UYARI: Son önerilen bolus %@ önce hesaplandığından beri yeni tedaviler gerçekleşmiş olabilir." + } + } + } }, - "Show −90 min Line" : { - "extractionState" : "manual" + "Warning" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Uyarı" + } + } + } }, - "Site URL" : {}, - "Skip" : {}, - "Skip if BG is falling" : { - "extractionState" : "manual" + "We'll ask iOS for permission next. You can change this any time in the Settings app." : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sırada iOS'tan izin isteyeceğiz. Bunu istediğiniz zaman Ayarlar uygulamasından değiştirebilirsiniz." + } + } + } }, - "Skip if BG is rising" : { - "extractionState" : "manual" + "Welcome to LoopFollow" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "LoopFollow'a Hoş Geldiniz" + } + } + } }, - "Skip while charging" : { - "extractionState" : "manual" + "What is sent" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Neler gönderiliyor" + } + } + } }, - "Slovak" : {}, - "Small Graph" : { - "extractionState" : "manual" + "What stays on your device" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cihazınızda neler kalıyor" + } + } + } }, - "Snooze & Mute Options" : {}, - "Snooze Date" : { - "extractionState" : "manual" + "What's sent" : { + "extractionState" : "manual", + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Neler gönderiliyor" + } + } + } }, - "Snooze Time" : { - "extractionState" : "manual" + "When enabled, LoopFollow maintains a live connection to your Nightscout server using WebSocket while the app is in the foreground. Data updates (new glucose readings, treatments, device status) arrive within seconds instead of waiting for the next polling cycle.\n\nThe WebSocket disconnects when LoopFollow moves to the background and reconnects when you return to the app. Polling continues to handle updates while the app is in the background.\n\nIn the foreground, polling continues at a reduced frequency as a safety net. If the WebSocket connection drops, normal polling resumes immediately." : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Açıkken LoopFollow, uygulama ön plandayken Nightscout sunucunuzla WebSocket üzerinden canlı bir bağlantı sürdürür. Veri güncellemeleri (yeni glukoz okumaları, tedaviler, cihaz durumu) bir sonraki yoklama döngüsünü beklemek yerine saniyeler içinde gelir.\n\nLoopFollow arka plana geçtiğinde WebSocket bağlantısı kesilir ve uygulamaya döndüğünüzde yeniden bağlanır. Uygulama arka plandayken güncellemeleri yoklama sürdürür.\n\nÖn planda, güvenlik ağı olarak yoklama daha düşük sıklıkla devam eder. WebSocket bağlantısı koparsa normal yoklama hemen sürdürülür." + } + } + } }, - "Snooze all · 1h" : {}, - "Snooze for" : {}, - "Snoozed" : {}, - "Snoozed until %@" : {}, - "Snoozer emoji" : { - "extractionState" : "manual" + "Yes, send anonymous stats" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Evet, anonim istatistik gönder" + } + } + } }, - "Sound" : {}, - "Source" : {}, - "Speak BG" : { - "extractionState" : "manual" + "You can change this any time in Settings → General → Diagnostics." : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bunu istediğiniz zaman Ayarlar → Genel → Tanılama'dan değiştirebilirsiniz." + } + } + } }, - "Start Date" : {}, - "Statistics" : {}, - "Status" : {}, - "Stay informed" : {}, - "Stay silent while BG is falling. The alert only sounds " : { - "extractionState" : "manual" + "You can choose to share anonymous information with the developers to help improve LoopFollow — such as app and iOS version, device type, which app you're following, and a few settings. Your health data, credentials, time zone, and logs remain on your device." : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "LoopFollow'u geliştirmeye yardımcı olmak için geliştiricilerle anonim bilgiler paylaşmayı seçebilirsiniz — uygulama ve iOS sürümü, cihaz türü, hangi uygulamayı takip ettiğiniz ve birkaç ayar gibi. Sağlık verileriniz, kimlik bilgileriniz, saat diliminiz ve günlükleriniz cihazınızda kalır." + } + } + } }, - "Stay silent while BG is rising. The alert only sounds " : { - "extractionState" : "manual" + "You can choose to share anonymous information with the developers to help improve LoopFollow—such as app and iOS version, device type, which app you're following, and a few settings. Your health data, credentials, time zone, and logs remain on your device." : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "LoopFollow'u geliştirmeye yardımcı olmak için geliştiricilerle anonim bilgiler paylaşmayı seçebilirsiniz — uygulama ve iOS sürümü, cihaz türü, hangi uygulamayı takip ettiğiniz ve birkaç ayar gibi. Sağlık verileriniz, kimlik bilgileriniz, saat diliminiz ve günlükleriniz cihazınızda kalır." + } + } + } }, - "Stay silent while the phone is charging. Requires the " : { - "extractionState" : "manual" + "You're all set" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Her şey hazır" + } + } + } }, - "Std Dev" : {}, - "Std Dev — Standard Deviation, how much glucose swings around the average. CV — Coefficient of Variation, that swing relative to the average (Std Dev ÷ mean)." : {}, - "Success" : {}, - "Support & Community" : { - "extractionState" : "manual" + "You're ready to go. Adjust everything later from the Menu — and when you have a moment, these are worth a look:" : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Başlamaya hazırsınız. Her şeyi daha sonra Menü'den ayarlayabilirsiniz — vaktiniz olduğunda şunlara göz atmaya değer:" + } + } + } }, - "Swedish" : {}, - "TIR" : {}, - "TIR — Time in Range, the share of readings within %@.\nTITR — Time in Tight Range, within %@.\nCustom — set your own low and high." : { + "Your API secret is used once to create a read-only access token and is never stored." : { "localizations" : { - "en" : { + "tr" : { "stringUnit" : { - "state" : "new", - "value" : "TIR — Time in Range, the share of readings within %1$@.\nTITR — Time in Tight Range, within %2$@.\nCustom — set your own low and high." + "state" : "translated", + "value" : "API secret'ınız salt okunur bir erişim belirteci oluşturmak için bir kez kullanılır ve asla saklanmaz." } } } }, - "TITR" : {}, - "TOTP Code Already Used" : {}, - "TOTP Code: Invalid QR code URL" : {}, - "Tab %@" : {}, - "Tab 5" : {}, - "Tab Order" : {}, - "Tabs" : { - "extractionState" : "manual" + "eHbA1c" : { + "shouldTranslate" : false }, - "Tap to load older treatments" : {}, - "Target" : {}, - "Target Range" : {}, - "Target: %@ %@" : { + "eHbA1c — an A1c estimate from your average glucose. GMI — Glucose Management Indicator, another A1c estimate from average glucose. % and mmol/mol (IFCC) are two scales for the result." : { "localizations" : { - "en" : { + "tr" : { "stringUnit" : { - "state" : "new", - "value" : "Target: %1$@ %2$@" + "state" : "translated", + "value" : "eHbA1c — ortalama glukozunuzdan bir A1c tahmini. GMI — Glukoz Yönetim Göstergesi, ortalama glukozdan başka bir A1c tahmini. % ve mmol/mol (IFCC), sonucun iki farklı ölçeğidir." } } } }, - "Target: %@-%@" : { + "g" : { + "comment" : "The short unit display string for grams", + "shouldTranslate" : false + }, + "mg/dL" : { + "comment" : "The short unit display string for milligrams of glucose per decilter", + "shouldTranslate" : false + }, + "minutes" : { "localizations" : { - "en" : { + "tr" : { "stringUnit" : { - "state" : "new", - "value" : "Target: %1$@-%2$@" + "state" : "translated", + "value" : "dakika" } } } }, - "Team ID: %@" : {}, - "Temp Target Ended" : { - "comment" : "Alarm type name", - "extractionState" : "manual" + "mmol/L" : { + "comment" : "The short unit display string for millimoles of glucose per liter", + "shouldTranslate" : false }, - "Temp Target Started" : { - "comment" : "Alarm type name", - "extractionState" : "manual" + "mmol/mol" : { + "shouldTranslate" : false }, - "Temporary Alert" : { - "comment" : "Alarm type name", - "extractionState" : "manual" + "“Default Snooze” controls the default value for how long the alert stays quiet after you press Snooze. \"A snooze duration of 0 means the alarm is acknowledged (silenced), and will alert again next time the condition applies, without time limitation. \" Toggle “Snoozed” to mute this alarm right now." : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "“Varsayılan Erteleme”, Ertele'ye bastıktan sonra uyarının ne kadar süre sessiz kalacağının varsayılan değerini belirler. \"0 erteleme süresi, alarmın onaylandığı (susturulduğu) ve koşul yeniden oluştuğunda süre sınırı olmadan tekrar uyaracağı anlamına gelir. \" Bu alarmı hemen susturmak için “Ertelendi” anahtarını açın." + } + } + } }, - "Temporary Target" : {}, - "Text Color" : { - "extractionState" : "manual" - }, - "Thanks for sharing these logs to help us find the problem. Please describe it in as much detail as possible — what time did it happen, what did you do, and what did you expect to happen that didn't?" : {}, - "That's your API secret — create a read-only token" : {}, - "The order of Fat and Protein inputs has changed." : {}, - "The top 4 items appear in the tab bar. The Menu can always open every feature." : {}, - "This TOTP code has already been used for a command. Please wait for the next code to be generated before sending another command." : {}, - "This will overwrite your current settings" : {}, - "Time" : {}, - "Time Remaining" : {}, - "Time Zone" : { - "extractionState" : "manual" + "“Snooze All” disables every alarm. “Mute All” silences phone sounds but still vibrates and shows iOS notifications." : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "“Tümünü Ertele” her alarmı devre dışı bırakır. “Tümünü Sessize Al” telefon seslerini kapatır ama yine de titreşir ve iOS bildirimlerini gösterir." + } + } + } }, - "Time Zone Override" : { - "extractionState" : "manual" + "••••••••" : { + "shouldTranslate" : false }, - "To see your trend, delta, or IOB, include them in another contact or create separate contacts. When using 'Include', select which contact to add the value to." : {}, - "Token" : {}, - "Tone" : {}, - "Treatments" : {}, - "Treatments on Small Graph" : { - "extractionState" : "manual" + "− 30m" : { + "shouldTranslate" : false }, - "Trend" : {}, - "Trio Remote Control Settings" : {}, - "Trio Remote Control is only supported for 'Trio'" : {}, - "Type or paste a token, or a full Nightscout URL that includes a token." : {}, - "U" : { - "comment" : "The short unit display string for international units of insulin" + "⚠️ Please enable at least one limit." : { + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "⚠️ Lütfen en az bir sınır etkinleştirin." + } + } + } }, - "URL" : {}, - "URL/Token Update" : {}, - "URL/Token Validation" : {}, - "US" : {}, - "Units and Metrics" : { - "extractionState" : "manual" - }, - "Unknown Alert" : {}, - "Use Anyway" : {}, - "Use the Send anonymous usage stats toggle above. Turning it off is immediate and persistent." : {}, - "User" : {}, - "User Information" : {}, - "Username" : {}, - "Validation Error" : {}, - "Variability" : {}, - "View Log" : { - "extractionState" : "manual" - }, - "WARNING: New treatments may have occurred since the last recommended bolus was calculated %@ ago." : {}, - "Warning" : {}, - "We'll ask iOS for permission next. You can change this any time in the Settings app." : {}, - "Welcome to LoopFollow" : {}, - "What is sent" : {}, - "What stays on your device" : {}, - "What's sent" : { - "extractionState" : "manual" - }, - "When enabled, LoopFollow maintains a live connection to your Nightscout server using WebSocket while the app is in the foreground. Data updates (new glucose readings, treatments, device status) arrive within seconds instead of waiting for the next polling cycle.\n\nThe WebSocket disconnects when LoopFollow moves to the background and reconnects when you return to the app. Polling continues to handle updates while the app is in the background.\n\nIn the foreground, polling continues at a reduced frequency as a safety net. If the WebSocket connection drops, normal polling resumes immediately." : {}, - "Yes, send anonymous stats" : {}, - "You can change this any time in Settings → General → Diagnostics." : {}, - "You can choose to share anonymous information with the developers to help improve LoopFollow — such as app and iOS version, device type, which app you're following, and a few settings. Your health data, credentials, time zone, and logs remain on your device." : {}, - "You can choose to share anonymous information with the developers to help improve LoopFollow—such as app and iOS version, device type, which app you're following, and a few settings. Your health data, credentials, time zone, and logs remain on your device." : {}, - "You're all set" : {}, - "You're ready to go. Adjust everything later from the Menu — and when you have a moment, these are worth a look:" : {}, - "Your API secret is used once to create a read-only access token and is never stored." : {}, - "eHbA1c" : {}, - "eHbA1c — an A1c estimate from your average glucose. GMI — Glucose Management Indicator, another A1c estimate from average glucose. % and mmol/mol (IFCC) are two scales for the result." : {}, - "g" : { - "comment" : "The short unit display string for grams" + "🌮" : { + "shouldTranslate" : false }, - "mg/dL" : { - "comment" : "The short unit display string for milligrams of glucose per decilter" + "🍕" : { + "shouldTranslate" : false }, - "minutes" : {}, - "mmol/L" : { - "comment" : "The short unit display string for millimoles of glucose per liter" - }, - "mmol/mol" : {}, - "“Default Snooze” controls the default value for how long the alert stays quiet after you press Snooze. \"A snooze duration of 0 means the alarm is acknowledged (silenced), and will alert again next time the condition applies, without time limitation. \" Toggle “Snoozed” to mute this alarm right now." : {}, - "“Snooze All” disables every alarm. “Mute All” silences phone sounds but still vibrates and shows iOS notifications." : {}, - "••••••••" : {}, - "− 30m" : {}, - "⚠️ Please enable at least one limit." : {}, - "🌮" : {}, - "🍕" : {}, - "🍭" : {}, - "🍽️" : {} + "🍭" : { + "shouldTranslate" : false + }, + "🍽️" : { + "shouldTranslate" : false + } }, "version" : "1.0" } diff --git a/Scripts/localization/apply_translations.py b/Scripts/localization/apply_translations.py new file mode 100755 index 000000000..dff743b11 --- /dev/null +++ b/Scripts/localization/apply_translations.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python3 +"""Write translations from a TSV (keycommentvalue) into a String Catalog. + +Usage: apply_translations.py +Cells use the escapes written by missing_translations.py (\\, \t, \n). Rows with an +empty value are skipped. Keys not present in the catalog are reported and skipped. +The catalog is rewritten in Xcode's own style (2-space indent, " : " separator, sorted keys). +""" +import json +import sys + + +def unescape(s: str) -> str: + out, i = [], 0 + while i < len(s): + c = s[i] + if c == "\\" and i + 1 < len(s): + nxt = s[i + 1] + out.append({"n": "\n", "t": "\t", "\\": "\\"}.get(nxt, "\\" + nxt)) + i += 2 + else: + out.append(c) + i += 1 + return "".join(out) + + +def main() -> int: + if len(sys.argv) != 4: + print(__doc__, file=sys.stderr) + return 2 + path, lang, tsv = sys.argv[1:4] + with open(path, encoding="utf-8") as f: + catalog = json.load(f) + strings = catalog["strings"] + applied, unknown = 0, [] + with open(tsv, encoding="utf-8") as f: + for line in f.read().split("\n"): + cells = line.split("\t") + if len(cells) < 3 or not cells[2].strip(): + continue + key, value = unescape(cells[0]), unescape(cells[2]) + if key not in strings: + unknown.append(key) + continue + strings[key].setdefault("localizations", {})[lang] = {"stringUnit": {"state": "translated", "value": value}} + applied += 1 + with open(path, "w", encoding="utf-8") as f: + json.dump(catalog, f, ensure_ascii=False, indent=2, sort_keys=True, separators=(",", " : ")) + f.write("\n") + print(f"applied {applied} [{lang}] translations", file=sys.stderr) + if unknown: + print("unknown keys (not in catalog):\n " + "\n ".join(unknown), file=sys.stderr) + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/Scripts/localization/missing_translations.py b/Scripts/localization/missing_translations.py new file mode 100755 index 000000000..a116466c3 --- /dev/null +++ b/Scripts/localization/missing_translations.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python3 +"""List catalog keys that still lack a translation for a language, as TSV. + +Usage: missing_translations.py +Output columns: key, comment, (empty translation column to fill in). +Cells escape backslash, tab and newline as \\, \t and \n. Keys marked +shouldTranslate=false are skipped. +""" +import json +import sys + + +def escape(s: str) -> str: + return s.replace("\\", "\\\\").replace("\t", "\\t").replace("\n", "\\n") + + +def main() -> int: + if len(sys.argv) != 3: + print(__doc__, file=sys.stderr) + return 2 + path, lang = sys.argv[1], sys.argv[2] + with open(path, encoding="utf-8") as f: + strings = json.load(f)["strings"] + count = 0 + for key in sorted(strings): + entry = strings[key] + if entry.get("shouldTranslate") is False: + continue + unit = entry.get("localizations", {}).get(lang, {}).get("stringUnit", {}) + if unit.get("state") == "translated" and unit.get("value", "").strip(): + continue + print("\t".join([escape(key), escape(entry.get("comment", "")), ""])) + count += 1 + print(f"{count} keys missing [{lang}]", file=sys.stderr) + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/Tests/LocalizationCatalogTests.swift b/Tests/LocalizationCatalogTests.swift index cbb656648..3f6bc8a44 100644 --- a/Tests/LocalizationCatalogTests.swift +++ b/Tests/LocalizationCatalogTests.swift @@ -45,8 +45,9 @@ struct LocalizationCatalogTests { /// printf-style specifiers, e.g. %lld, %@, %.1f, %2$@. Returns (positionalIndex?, specifier). /// The positional group only matches digits followed by "$", so a width like "%2d" is not positional. + /// The space flag is deliberately not accepted so prose such as "% and" is not read as a specifier. private static func placeholders(in s: String) -> [(Int?, String)] { - let pattern = #"%(?:(\d+)\$)?([-+ 0#]*\d*(?:\.\d+)?(?:ll|l|h)?[@dDiuUxXoOfeEgGcCsSpaAF])"# + let pattern = #"%(?:(\d+)\$)?([-+0#]*\d*(?:\.\d+)?(?:ll|l|h)?[@dDiuUxXoOfeEgGcCsSpaAF])"# let regex = try! NSRegularExpression(pattern: pattern) return regex.matches(in: s, range: NSRange(s.startIndex..., in: s)).compactMap { m in guard let specRange = Range(m.range(at: 2), in: s) else { return nil } diff --git a/Tests/LocalizationDisplayNameTests.swift b/Tests/LocalizationDisplayNameTests.swift index 47fe0355c..4b3be8f0c 100644 --- a/Tests/LocalizationDisplayNameTests.swift +++ b/Tests/LocalizationDisplayNameTests.swift @@ -49,19 +49,38 @@ struct LocalizationDisplayNameTests { #expect(ActiveOption.allCases.map(\.rawValue) == ["always", "day", "night"]) } - @Test("AlarmType.displayName falls back to the English raw value in the test host language") - func alarmTypeDisplayNameEnglish() { + /// The test host runs in whatever language the simulator uses, so compare against the + /// catalog lookup for the same key instead of hardcoding English. + private func localized(_ key: String) -> String { + Bundle.main.localizedString(forKey: key, value: nil, table: nil) + } + + @Test("AlarmType.displayName resolves its raw value through the String Catalog") + func alarmTypeDisplayNameUsesCatalog() { for type in AlarmType.allCases { - #expect(type.displayName == type.rawValue) + #expect(type.displayName == localized(type.rawValue)) + #expect(!type.displayName.isEmpty) } } - @Test("day/night displayName keeps the English wording") - func dayNightDisplayNameEnglish() { - #expect(PlaySoundOption.always.displayName == "Day & Night") - #expect(PlaySoundOption.day.displayName == "Day") - #expect(PlaySoundOption.night.displayName == "Night") - #expect(PlaySoundOption.never.displayName == "Never") - #expect(ActiveOption.always.displayName == "Day & Night") + @Test("day/night displayName resolves its English key through the String Catalog") + func dayNightDisplayNameUsesCatalog() { + #expect(PlaySoundOption.always.displayName == localized("Day & Night")) + #expect(PlaySoundOption.day.displayName == localized("Day")) + #expect(PlaySoundOption.night.displayName == localized("Night")) + #expect(PlaySoundOption.never.displayName == localized("Never")) + #expect(ActiveOption.always.displayName == localized("Day & Night")) + } + + @Test("Turkish strings are compiled into the app bundle") + func turkishBundleContainsAlarmTypeNames() throws { + let path = try #require(Bundle.main.path(forResource: "tr", ofType: "lproj"), "tr.lproj missing: check knownRegions and catalog target membership") + let bundle = try #require(Bundle(path: path)) + for type in AlarmType.allCases { + let localized = bundle.localizedString(forKey: type.rawValue, value: "MISSING", table: nil) + #expect(localized != "MISSING", Comment(rawValue: "no Turkish value for \(type.rawValue)")) + #expect(localized != type.rawValue, Comment(rawValue: "Turkish value equals English for \(type.rawValue)")) + } + #expect(bundle.localizedString(forKey: "Day & Night", value: "MISSING", table: nil) == "Gündüz ve Gece") } } From 25cdbffbc7aa5484605338c253d13a123087dc3b Mon Sep 17 00:00:00 2001 From: Ramazan TUNCER Date: Sun, 27 Sep 2026 02:23:31 +0300 Subject: [PATCH 5/5] Address review: merge footer literal, positional placeholders, narrower don't-translate set - AlarmSettingsView's day/night footer joined three literals with "+", which selects the non-localized Text overload; merge into one literal and translate the whole footer. Drop the dead fragment and "(" keys. - Add a source lint test that fails on Text("a" + "b") patterns. - Require positional indexes (%1$@, %2$@) in every multi-placeholder translation so argument order can be changed safely; update the five affected Turkish values. - Keep shouldTranslate=false only for units, symbols, bare specifiers and hashtags; ordinary terms that happen to match in Turkish get an explicit translation so other languages can translate them. - Verify the English plural variation at runtime from en.lproj. - Fix "Snoozed until %@" (argument is a time, not a date) and refine "Proactive Low" and the "Remote" title. Co-Authored-By: Claude Fable 5.1 --- LoopFollow/Alarm/AlarmSettingsView.swift | 4 +- LoopFollow/Resources/Localizable.xcstrings | 186 +++++++++++++++++---- Tests/LocalizationCatalogTests.swift | 8 +- Tests/LocalizationDisplayNameTests.swift | 10 ++ Tests/LocalizationSourceLintTests.swift | 46 +++++ 5 files changed, 216 insertions(+), 38 deletions(-) create mode 100644 Tests/LocalizationSourceLintTests.swift diff --git a/LoopFollow/Alarm/AlarmSettingsView.swift b/LoopFollow/Alarm/AlarmSettingsView.swift index 3f328bfa4..27e28e04c 100644 --- a/LoopFollow/Alarm/AlarmSettingsView.swift +++ b/LoopFollow/Alarm/AlarmSettingsView.swift @@ -120,9 +120,7 @@ struct AlarmSettingsView: View { Section( header: Text("Day / Night Schedule"), - footer: Text("Pick when your day period begins and when your night period begins. " + - "Any time from your Day-starts time up until your Night-starts time will count as day; " + - "from Night-starts until the next Day-starts will count as night.") + footer: Text("Pick when your day period begins and when your night period begins. Any time from your Day-starts time up until your Night-starts time will count as day; from Night-starts until the next Day-starts will count as night.") ) { DatePicker( "Day starts", diff --git a/LoopFollow/Resources/Localizable.xcstrings b/LoopFollow/Resources/Localizable.xcstrings index 808e2ccda..cc797195c 100644 --- a/LoopFollow/Resources/Localizable.xcstrings +++ b/LoopFollow/Resources/Localizable.xcstrings @@ -114,10 +114,6 @@ "%@g" : { "shouldTranslate" : false }, - "(" : { - "extractionState" : "manual", - "shouldTranslate" : false - }, "(%@ days)" : { "localizations" : { "tr" : { @@ -178,11 +174,25 @@ } }, "API Secret" : { - "shouldTranslate" : false + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "API Secret" + } + } + } }, "APN" : { "extractionState" : "manual", - "shouldTranslate" : false + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "APN" + } + } + } }, "APNS Environment: %@" : { "localizations" : { @@ -670,7 +680,14 @@ }, "Bolus" : { "extractionState" : "manual", - "shouldTranslate" : false + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bolus" + } + } + } }, "Bolus Filter" : { "extractionState" : "manual", @@ -745,7 +762,14 @@ } }, "COB" : { - "shouldTranslate" : false + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "COB" + } + } + } }, "COB Alert" : { "comment" : "Alarm type name", @@ -760,7 +784,14 @@ } }, "CV" : { - "shouldTranslate" : false + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "CV" + } + } + } }, "Calculated %lld minutes ago" : { "comment" : "Age of the recommended bolus calculation", @@ -1385,7 +1416,14 @@ } }, "Delta" : { - "shouldTranslate" : false + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Delta" + } + } + } }, "Description" : { "localizations" : { @@ -1454,7 +1492,14 @@ } }, "Dexcom Share" : { - "shouldTranslate" : false + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dexcom Share" + } + } + } }, "Diagnostics" : { "extractionState" : "manual", @@ -2101,7 +2146,14 @@ } }, "GMI" : { - "shouldTranslate" : false + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "GMI" + } + } + } }, "GRI (Glucose Risk Index)" : { "localizations" : { @@ -2378,7 +2430,14 @@ } }, "IOB" : { - "shouldTranslate" : false + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "IOB" + } + } + } }, "IOB Alert" : { "comment" : "Alarm type name", @@ -2945,10 +3004,24 @@ } }, "Min" : { - "shouldTranslate" : false + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Min" + } + } + } }, "Minimum" : { - "shouldTranslate" : false + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Minimum" + } + } + } }, "Minutes" : { "extractionState" : "manual", @@ -3028,7 +3101,7 @@ "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Yeni Hedef: %@ %@\nSüre: %@ dakika" + "value" : "Yeni Hedef: %1$@ %2$@\nSüre: %3$@ dakika" } } } @@ -3302,7 +3375,14 @@ } }, "Override" : { - "shouldTranslate" : false + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Override" + } + } + } }, "Override Ended" : { "comment" : "Alarm type name", @@ -3411,13 +3491,13 @@ } } }, - "Pick when your day period begins and when your night period begins. " : { + "Pick when your day period begins and when your night period begins. Any time from your Day-starts time up until your Night-starts time will count as day; from Night-starts until the next Day-starts will count as night." : { "extractionState" : "manual", "localizations" : { "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Gündüz ve gece dönemlerinizin ne zaman başladığını seçin. " + "value" : "Gündüz ve gece dönemlerinizin ne zaman başladığını seçin. Gündüz başlangıcından gece başlangıcına kadar olan süre gündüz, gece başlangıcından bir sonraki gündüz başlangıcına kadar olan süre gece sayılır." } } } @@ -3543,14 +3623,21 @@ "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Öngörülü Düşük" + "value" : "Tahmini Düşük" } } } }, "Production" : { "extractionState" : "manual", - "shouldTranslate" : false + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Production" + } + } + } }, "Production is used for browser builders and should be switched off for Xcode builders" : { "localizations" : { @@ -3701,7 +3788,7 @@ "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Uzaktan" + "value" : "Uzaktan Kumanda" } } } @@ -4056,7 +4143,7 @@ "tr" : { "stringUnit" : { "state" : "translated", - "value" : "%@g karbonhidrat, %@sa emilim süresiyle, %@ zamanında gönderilsin mi?" + "value" : "%1$@g karbonhidrat, %2$@sa emilim süresiyle, %3$@ zamanında gönderilsin mi?" } } } @@ -4534,7 +4621,7 @@ "tr" : { "stringUnit" : { "state" : "translated", - "value" : "%@ tarihine kadar ertelendi" + "value" : "%@ saatine kadar ertelendi" } } } @@ -4706,7 +4793,14 @@ } }, "TIR" : { - "shouldTranslate" : false + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "TIR" + } + } + } }, "TIR — Time in Range, the share of readings within %@.\nTITR — Time in Tight Range, within %@.\nCustom — set your own low and high." : { "localizations" : { @@ -4719,13 +4813,20 @@ "tr" : { "stringUnit" : { "state" : "translated", - "value" : "TIR — Hedef Aralıkta Süre, %@ içindeki okumaların payı.\nTITR — Dar Hedef Aralıkta Süre, %@ içindeki pay.\nÖzel — kendi düşük ve yüksek değerlerinizi belirleyin." + "value" : "TIR — Hedef Aralıkta Süre, %1$@ içindeki okumaların payı.\nTITR — Dar Hedef Aralıkta Süre, %2$@ içindeki pay.\nÖzel — kendi düşük ve yüksek değerlerinizi belirleyin." } } } }, "TITR" : { - "shouldTranslate" : false + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "TITR" + } + } + } }, "TOTP Code Already Used" : { "localizations" : { @@ -4829,7 +4930,7 @@ "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Hedef: %@ %@" + "value" : "Hedef: %1$@ %2$@" } } } @@ -4845,7 +4946,7 @@ "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Hedef: %@-%@" + "value" : "Hedef: %1$@-%2$@" } } } @@ -5071,7 +5172,14 @@ } }, "Trend" : { - "shouldTranslate" : false + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Trend" + } + } + } }, "Trio Remote Control Settings" : { "localizations" : { @@ -5108,7 +5216,14 @@ "shouldTranslate" : false }, "URL" : { - "shouldTranslate" : false + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "URL" + } + } + } }, "URL/Token Update" : { "localizations" : { @@ -5394,7 +5509,14 @@ } }, "eHbA1c" : { - "shouldTranslate" : false + "localizations" : { + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "eHbA1c" + } + } + } }, "eHbA1c — an A1c estimate from your average glucose. GMI — Glucose Management Indicator, another A1c estimate from average glucose. % and mmol/mol (IFCC) are two scales for the result." : { "localizations" : { diff --git a/Tests/LocalizationCatalogTests.swift b/Tests/LocalizationCatalogTests.swift index 3f6bc8a44..66d7bec33 100644 --- a/Tests/LocalizationCatalogTests.swift +++ b/Tests/LocalizationCatalogTests.swift @@ -85,9 +85,11 @@ struct LocalizationCatalogTests { problems.append("[\(language)] \(key) -> \(t.value)") continue } - let reordered = source.map(\.1) != target.map(\.1) - if source.count > 1, reordered, target.contains(where: { $0.0 == nil }) { - problems.append("[\(language)] reordered without positional indexes: \(key) -> \(t.value)") + // Word order differs between languages, so every multi-placeholder translation must + // address its arguments explicitly (%1$@, %2$lld ...); mixing positional and + // non-positional specifiers is undefined at runtime. + if source.count > 1, target.contains(where: { $0.0 == nil }) { + problems.append("[\(language)] multi-placeholder value must use positional indexes: \(key) -> \(t.value)") } } } diff --git a/Tests/LocalizationDisplayNameTests.swift b/Tests/LocalizationDisplayNameTests.swift index 4b3be8f0c..28da45311 100644 --- a/Tests/LocalizationDisplayNameTests.swift +++ b/Tests/LocalizationDisplayNameTests.swift @@ -83,4 +83,14 @@ struct LocalizationDisplayNameTests { } #expect(bundle.localizedString(forKey: "Day & Night", value: "MISSING", table: nil) == "Gündüz ve Gece") } + + @Test("English plural variation resolves through the compiled catalog") + func englishPluralVariationApplies() throws { + let path = try #require(Bundle.main.path(forResource: "en", ofType: "lproj")) + let bundle = try #require(Bundle(path: path)) + let one = String(localized: "Calculated \(1) minutes ago", bundle: bundle) + let many = String(localized: "Calculated \(5) minutes ago", bundle: bundle) + #expect(one == "Calculated 1 minute ago") + #expect(many == "Calculated 5 minutes ago") + } } diff --git a/Tests/LocalizationSourceLintTests.swift b/Tests/LocalizationSourceLintTests.swift new file mode 100644 index 000000000..754dd4947 --- /dev/null +++ b/Tests/LocalizationSourceLintTests.swift @@ -0,0 +1,46 @@ +// LoopFollow +// LocalizationSourceLintTests.swift + +import Foundation +@testable import LoopFollow +import Testing + +/// Source-level guards for patterns that silently defeat localization. +struct LocalizationSourceLintTests { + private static let repoRoot = URL(fileURLWithPath: #filePath) + .deletingLastPathComponent() + .deletingLastPathComponent() + + private static let sourceRoots = ["LoopFollow", "Shared", "LoopFollowLAExtension"] + + private static func swiftFiles() throws -> [URL] { + var files: [URL] = [] + for root in sourceRoots { + let dir = repoRoot.appendingPathComponent(root) + guard let e = FileManager.default.enumerator(at: dir, includingPropertiesForKeys: nil) else { continue } + for case let url as URL in e where url.pathExtension == "swift" { files.append(url) } + } + return files + } + + /// `Text("a " + "b")` resolves to the String overload of Text, so neither literal is ever + /// looked up in the catalog even though the extractor may still emit the first one as a key. + @Test("no Text literal is built from two string literals joined with +") + func noConcatenatedTextLiterals() throws { + let pattern = #"Text\("(?:[^"\\]|\\.)*"\s*\+\s*"# + let regex = try NSRegularExpression(pattern: pattern) + var hits: [String] = [] + for file in try Self.swiftFiles() { + let source = try String(contentsOf: file, encoding: .utf8) + for m in regex.matches(in: source, range: NSRange(source.startIndex..., in: source)) { + guard let r = Range(m.range, in: source) else { continue } + // Only flag when the next token after "+" is another string literal. + let rest = source[r.upperBound...].drop(while: { $0 == " " || $0 == "\n" || $0 == "\t" }) + guard rest.first == "\"" else { continue } + let line = source[..