diff --git a/Yattee/Localizable.xcstrings b/Yattee/Localizable.xcstrings index a2d2f87c..ad752fcc 100644 --- a/Yattee/Localizable.xcstrings +++ b/Yattee/Localizable.xcstrings @@ -3,18 +3,6 @@ "strings" : { "" : { - }, - " " : { - - }, - " / " : { - - }, - "-%@" : { - - }, - "/" : { - }, "%@" : { @@ -59,39 +47,6 @@ } } }, - "%@ from %@" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "new", - "value" : "%1$@ from %2$@" - } - } - } - }, - "%@ subscribers" : { - - }, - "%@: %lld" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "new", - "value" : "%1$@: %2$lld" - } - } - } - }, - "%@: %llds" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "new", - "value" : "%1$@: %2$llds" - } - } - } - }, "%@%llds" : { "localizations" : { "en" : { @@ -104,35 +59,6 @@ }, "%lld" : { - }, - "%lld item%@" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "new", - "value" : "%1$lld item%2$@" - } - } - } - }, - "%lld pt" : { - - }, - "%lld replies" : { - - }, - "%lld videos" : { - - }, - "%lld videos • %@" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "new", - "value" : "%1$lld videos • %2$@" - } - } - } }, "%lld/%lld" : { "localizations" : { @@ -158,9 +84,6 @@ }, "%llds" : { - }, - "%llds ago" : { - }, "%llu.%llu" : { "localizations" : { @@ -171,30 +94,12 @@ } } } - }, - "•" : { - - }, - "• %@" : { - - }, - "↑↓ scroll • click to close" : { - }, "+%lld" : { - }, - "0:00 / 3:45" : { - }, "1.2M subscribers" : { - }, - "2 weeks ago" : { - - }, - "Account" : { - }, "alert.openVideo.message %@" : { "localizations" : { @@ -216,22 +121,6 @@ } } }, - "Apple HLS" : { - - }, - "AUTO" : { - - }, - "availW: %lld fullW: %lld" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "new", - "value" : "availW: %1$lld fullW: %2$lld" - } - } - } - }, "batchDownload.complete.allSkipped.subtitle" : { "comment" : "Toast subtitle when all videos were already downloaded", "localizations" : { @@ -521,27 +410,15 @@ } } }, - "Browse %@" : { - - }, - "Cancel" : { - - }, - "centerOff: %lld btmExtra: %lld" : { + "bookmarks.search.placeholder" : { "localizations" : { "en" : { "stringUnit" : { - "state" : "new", - "value" : "centerOff: %1$lld btmExtra: %2$lld" + "state" : "translated", + "value" : "Search bookmarks" } } } - }, - "Channel" : { - - }, - "Channel Name" : { - }, "channel.menu.disableNotifications" : { "localizations" : { @@ -780,6 +657,56 @@ } } }, + "channel.view" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "View Channel" + } + } + } + }, + "channels.search.placeholder" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Search channels" + } + } + } + }, + "channelStrip.size.compact" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Compact" + } + } + } + }, + "channelStrip.size.large" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Large" + } + } + } + }, + "channelStrip.size.normal" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Normal" + } + } + } + }, "channelView.loadError" : { "comment" : "Error message when loading channel fails", "localizations" : { @@ -790,21 +717,6 @@ } } } - }, - "click to expand" : { - - }, - "Close" : { - - }, - "Comments" : { - - }, - "Comments disabled" : { - - }, - "Comments unavailable" : { - }, "comments.disabled" : { "comment" : "Shown when comments are disabled for a video", @@ -860,6 +772,16 @@ } } }, + "comments.replyCount %lld" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld replies" + } + } + } + }, "comments.showReplies %lld" : { "localizations" : { "en" : { @@ -870,6 +792,16 @@ } } }, + "comments.unavailable" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Comments unavailable" + } + } + } + }, "commentsPill.mode.button" : { "localizations" : { "en" : { @@ -960,6 +892,16 @@ } } }, + "common.channel" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Channel" + } + } + } + }, "common.close" : { "localizations" : { "en" : { @@ -1215,6 +1157,26 @@ } } }, + "common.unit.points" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "pt" + } + } + } + }, + "common.videoTitle" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Video Title" + } + } + } + }, "common.yes" : { "comment" : "Yes", "localizations" : { @@ -1226,19 +1188,6 @@ } } }, - "Community" : { - - }, - "Compact" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Compact" - } - } - } - }, "continueWatching.clearAll" : { "comment" : "Clear all continue watching entries", "localizations" : { @@ -1260,9 +1209,6 @@ } } } - }, - "Controls (cyan):" : { - }, "controls.button.addToPlaylist" : { "localizations" : { @@ -1864,25 +1810,6 @@ } } }, - "DASH" : { - - }, - "Debug" : { - - }, - "Description" : { - - }, - "Disabled" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Disabled" - } - } - } - }, "discovery.empty.description" : { "comment" : "Description when no network shares are found", "localizations" : { @@ -2025,9 +1952,6 @@ } } } - }, - "Download: %@" : { - }, "download.error.audioRequired" : { "localizations" : { @@ -2225,6 +2149,16 @@ } } }, + "downloads.search.placeholder" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Search downloads" + } + } + } + }, "downloads.sort.ascending" : { "comment" : "Ascending sort direction", "localizations" : { @@ -2517,26 +2451,6 @@ } } }, - "fullH: %lld offset: %lld" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "new", - "value" : "fullH: %1$lld offset: %2$lld" - } - } - } - }, - "geom W:%lld H:%lld" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "new", - "value" : "geom W:%1$lld H:%2$lld" - } - } - } - }, "gestures.action.cyclePlaybackSpeed" : { "extractionState" : "extracted_with_value", "localizations" : { @@ -3406,8 +3320,15 @@ } } }, - "HLS" : { - + "history.search.placeholder" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Search history" + } + } + } }, "home.bookmarks.empty" : { "comment" : "Empty bookmarks message", @@ -3843,6 +3764,16 @@ } } }, + "home.removeConfirmation.title" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Remove from Home?" + } + } + } + }, "home.section.bookmarks" : { "comment" : "Bookmarks section label in library settings", "localizations" : { @@ -4524,9 +4455,6 @@ } } } - }, - "Info" : { - }, "Initializing player..." : { @@ -4684,41 +4612,6 @@ } } } - }, - "Large" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Large" - } - } - } - }, - "Last manual sync: %@" : { - - }, - "Last Synced" : { - - }, - "Layout (yellow):" : { - - }, - "leadSA: %lld trailSA: %lld" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "new", - "value" : "leadSA: %1$lld trailSA: %2$lld" - } - } - } - }, - "LIVE" : { - - }, - "Load more replies" : { - }, "login.email" : { "comment" : "Email field label in login form", @@ -5064,6 +4957,16 @@ } } }, + "mediaSources.browse %@" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Browse %@" + } + } + } + }, "menu.file.openLink" : { "localizations" : { "en" : { @@ -5588,18 +5491,6 @@ } } } - }, - "MPEG-DASH (Best for MPV)" : { - - }, - "MPV Debug" : { - - }, - "MPV Debug Stats" : { - - }, - "MUXED" : { - }, "my-server.example.com" : { @@ -5624,19 +5515,6 @@ } } }, - "No comments" : { - - }, - "Normal" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Normal" - } - } - } - }, "notification.action.watch" : { "comment" : "Action button on notification to watch video", "localizations" : { @@ -5713,15 +5591,6 @@ } } } - }, - "Off" : { - - }, - "OK" : { - - }, - "On" : { - }, "onboarding.cloud.complete.description" : { "comment" : "iCloud sync complete description on onboarding", @@ -6327,12 +6196,6 @@ } } } - }, - "orient: %@" : { - - }, - "ORIGINAL" : { - }, "peertube.explore.added" : { "localizations" : { @@ -6413,9 +6276,6 @@ } } } - }, - "Pending" : { - }, "pill.addButton.header" : { "localizations" : { @@ -6527,16 +6387,6 @@ } } }, - "pinned: %@ vis: %@ side: %@" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "new", - "value" : "pinned: %1$@ vis: %2$@ side: %3$@" - } - } - } - }, "player.autoplay.cancel" : { "comment" : "Cancel autoplay button", "localizations" : { @@ -6627,6 +6477,46 @@ } } }, + "player.controls.subtitles" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Subtitles" + } + } + } + }, + "player.debug.stats" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "MPV Debug Stats" + } + } + } + }, + "player.debug.title" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "MPV Debug" + } + } + } + }, + "player.debug.titleShort" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Debug" + } + } + } + }, "player.deleteDownload.cancel" : { "comment" : "Cancel button in delete download dialog", "localizations" : { @@ -6794,6 +6684,16 @@ }, "player.expandedSheet" : { + }, + "player.live" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "LIVE" + } + } + } }, "player.miniPlayer" : { @@ -7025,6 +6925,46 @@ } } }, + "player.tvos.scrubHint" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Press to scrub" + } + } + } + }, + "player.tvos.seekHint" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Swipe ← → • press to seek" + } + } + } + }, + "player.tvos.volumeDown" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vol -" + } + } + } + }, + "player.tvos.volumeUp" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vol +" + } + } + } + }, "playlist.addTo" : { "localizations" : { "en" : { @@ -7292,6 +7232,16 @@ } } }, + "playlist.videoCountDuration %lld %@" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld videos • %@" + } + } + } + }, "playlists.empty.description" : { "comment" : "Description shown when user has no playlists", "localizations" : { @@ -7335,15 +7285,6 @@ } } } - }, - "Press to scrub" : { - - }, - "pt" : { - - }, - "Quality" : { - }, "queue.action.addToQueue" : { "comment" : "Add to queue action in action sheet", @@ -7584,9 +7525,6 @@ } } } - }, - "Remote Control" : { - }, "remoteControl.closeVideo" : { "comment" : "Button label", @@ -7709,6 +7647,16 @@ } } }, + "remoteControl.lastSeen %lld" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "%llds ago" + } + } + } + }, "remoteControl.noDevicesFooter" : { "comment" : "Footer when no devices", "localizations" : { @@ -7808,6 +7756,16 @@ } } }, + "remoteControl.title" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Remote Control" + } + } + } + }, "remoteControl.tryReconnect" : { "comment" : "Button label", "localizations" : { @@ -7818,12 +7776,6 @@ } } } - }, - "Remove" : { - - }, - "Remove from Home?" : { - }, "resume.action.continueAt %@" : { "comment" : "Continue watching action with timestamp", @@ -7846,21 +7798,6 @@ } } } - }, - "Retry Sync" : { - - }, - "Search bookmarks" : { - - }, - "Search channels" : { - - }, - "Search downloads" : { - - }, - "Search history" : { - }, "search.clearAllRecents" : { "comment" : "Button to clear all recent searches, channels, and playlists", @@ -8271,8 +8208,15 @@ } } }, - "Settings" : { - + "settings.about.community" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Community" + } + } + } }, "settings.about.mpvInfo" : { "localizations" : { @@ -10793,6 +10737,16 @@ } } }, + "settings.icloud.account" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Account" + } + } + } + }, "settings.icloud.categories.footer" : { "comment" : "Footer explaining sync categories", "localizations" : { @@ -11045,6 +10999,76 @@ } } }, + "settings.icloud.lastManualSync %@" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Last manual sync: %@" + } + } + } + }, + "settings.icloud.lastSynced" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Last Synced" + } + } + } + }, + "settings.icloud.pending" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pending" + } + } + } + }, + "settings.icloud.retrySync" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Retry Sync" + } + } + } + }, + "settings.icloud.status" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Status" + } + } + } + }, + "settings.icloud.syncError" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sync Error" + } + } + } + }, + "settings.icloud.syncNow" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sync Now" + } + } + } + }, "settings.icloud.title" : { "comment" : "Title for iCloud settings section", "localizations" : { @@ -13623,6 +13647,16 @@ } } }, + "settings.subtitles.fontSize %lld" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld pt" + } + } + } + }, "settings.subtitles.italic" : { "localizations" : { "en" : { @@ -14238,6 +14272,26 @@ } } }, + "sources.bandwidth.download %@" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Download: %@" + } + } + } + }, + "sources.bandwidth.upload %@" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Upload: %@" + } + } + } + }, "sources.browsePeerTube" : { "comment" : "Menu item to browse PeerTube instances", "localizations" : { @@ -15493,12 +15547,6 @@ } } } - }, - "Status" : { - - }, - "STREAM" : { - }, "stream.adaptive" : { "localizations" : { @@ -15530,6 +15578,86 @@ } } }, + "stream.audio.original" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "ORIGINAL" + } + } + } + }, + "stream.badge.muxed" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "MUXED" + } + } + } + }, + "stream.badge.stream" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "STREAM" + } + } + } + }, + "stream.format.appleHLS" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Apple HLS" + } + } + } + }, + "stream.format.dash" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "DASH" + } + } + } + }, + "stream.format.hls" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "HLS" + } + } + } + }, + "stream.format.mpegDash" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "MPEG-DASH (Best for MPV)" + } + } + } + }, + "stream.subtitle.auto" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "AUTO" + } + } + } + }, "stream.subtitles" : { "localizations" : { "en" : { @@ -15866,9 +15994,6 @@ } } } - }, - "Subtitles" : { - }, "subtitles.default" : { "extractionState" : "extracted_with_value", @@ -15880,9 +16005,6 @@ } } } - }, - "Swipe ← → • press to seek" : { - }, "swipeAction.addToBookmarks" : { "localizations" : { @@ -15973,12 +16095,6 @@ } } } - }, - "Sync Error" : { - - }, - "Sync Now" : { - }, "tabBar.item.bookmarks" : { "comment" : "Tab bar item title for bookmarks", @@ -16132,16 +16248,6 @@ } } }, - "toast.download.fileMissing.title" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Download file missing — streaming instead" - } - } - } - }, "toast.download.completedWithWarnings.title" : { "localizations" : { "en" : { @@ -16152,6 +16258,16 @@ } } }, + "toast.download.fileMissing.title" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Download file missing — streaming instead" + } + } + } + }, "toast.download.started.title" : { "localizations" : { "en" : { @@ -16235,16 +16351,6 @@ } } }, - "topPad: %lld btmPad: %lld wide: %@" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "new", - "value" : "topPad: %1$lld btmPad: %2$lld wide: %3$@" - } - } - } - }, "trending.title" : { "comment" : "Trending view title", "localizations" : { @@ -16255,12 +16361,6 @@ } } } - }, - "Upload: %@" : { - - }, - "Video Title" : { - }, "video.badge.live" : { "comment" : "Label for live streams", @@ -16962,22 +17062,6 @@ } } } - }, - "vidTop: %lld vidH: %lld" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "new", - "value" : "vidTop: %1$lld vidH: %2$lld" - } - } - } - }, - "View Channel" : { - - }, - "View Options" : { - }, "viewOptions.channelStrip" : { "localizations" : { @@ -17119,22 +17203,6 @@ } } } - }, - "vis: %@ fitH: %lld effVH: %lld" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "new", - "value" : "vis: %1$@ fitH: %2$lld effVH: %3$lld" - } - } - } - }, - "Vol -" : { - - }, - "Vol +" : { - }, "wideLayoutPanel.alignmentButton.moveLeft" : { "localizations" : { @@ -17175,23 +17243,7 @@ } } } - }, - "winSA T:%lld B:%lld L:%lld R:%lld" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "new", - "value" : "winSA T:%1$lld B:%2$lld L:%3$lld R:%4$lld" - } - } - } - }, - "Y" : { - - }, - "Yattee" : { - } }, "version" : "1.0" -} \ No newline at end of file +} diff --git a/Yattee/Models/ChannelStripSize.swift b/Yattee/Models/ChannelStripSize.swift index 4394f422..96b823a1 100644 --- a/Yattee/Models/ChannelStripSize.swift +++ b/Yattee/Models/ChannelStripSize.swift @@ -50,10 +50,10 @@ enum ChannelStripSize: String, CaseIterable, Codable, Hashable, Sendable { var displayName: String { switch self { - case .disabled: return String(localized: "Disabled") - case .compact: return String(localized: "Compact") - case .normal: return String(localized: "Normal") - case .large: return String(localized: "Large") + case .disabled: return String(localized: "common.disabled") + case .compact: return String(localized: "channelStrip.size.compact") + case .normal: return String(localized: "channelStrip.size.normal") + case .large: return String(localized: "channelStrip.size.large") } } } diff --git a/Yattee/Views/Channel/ChannelPlaylistRow.swift b/Yattee/Views/Channel/ChannelPlaylistRow.swift index 061847ec..52b22c90 100644 --- a/Yattee/Views/Channel/ChannelPlaylistRow.swift +++ b/Yattee/Views/Channel/ChannelPlaylistRow.swift @@ -76,7 +76,7 @@ struct ChannelPlaylistRow: View { } if style == .compact { - Text("\(playlist.videoCount) videos") + Text("playlist.videoCount \(playlist.videoCount)") .font(.caption2) .foregroundStyle(.secondary) } else { diff --git a/Yattee/Views/Channel/ChannelView.swift b/Yattee/Views/Channel/ChannelView.swift index b5dd18cf..09d4bfa1 100644 --- a/Yattee/Views/Channel/ChannelView.swift +++ b/Yattee/Views/Channel/ChannelView.swift @@ -780,7 +780,7 @@ struct ChannelView: View { if let subscriberCount = channel.subscriberCount { Text(CountFormatter.compact(subscriberCount)) .fontWeight(.bold) - + Text(" ") + + Text(verbatim: " ") + Text(String(localized: "channel.subscribers")) } diff --git a/Yattee/Views/Components/BlurredImageBackground.swift b/Yattee/Views/Components/BlurredImageBackground.swift index c2f46d98..7d6e0fcc 100644 --- a/Yattee/Views/Components/BlurredImageBackground.swift +++ b/Yattee/Views/Components/BlurredImageBackground.swift @@ -193,7 +193,7 @@ extension BlurredImageBackground { .fill(.gray) .frame(width: 280, height: 158) - Text("Video Title") + Text(verbatim: "Video Title") .font(.headline) } } diff --git a/Yattee/Views/Components/ChannelCardGridView.swift b/Yattee/Views/Components/ChannelCardGridView.swift index 579c4267..5e6f1a23 100644 --- a/Yattee/Views/Components/ChannelCardGridView.swift +++ b/Yattee/Views/Components/ChannelCardGridView.swift @@ -63,7 +63,7 @@ struct ChannelCardGridView: View { } } else { // Reserve space with invisible text - Text(" ") + Text(verbatim: " ") .font(subscriberFont) } } diff --git a/Yattee/Views/Components/VideoInfoComponents.swift b/Yattee/Views/Components/VideoInfoComponents.swift index 1a9305da..02c68844 100644 --- a/Yattee/Views/Components/VideoInfoComponents.swift +++ b/Yattee/Views/Components/VideoInfoComponents.swift @@ -49,16 +49,16 @@ struct VideoStatsRow: View { Text(publishedText) .onTapGesture { showFormattedDate.toggle() } } else if isLoadingAPIStats { - Text("2 weeks ago") + Text(verbatim: "2 weeks ago") .redacted(reason: .placeholder) } // View count if let viewCount = video.formattedViewCount { - Text("•") + Text(verbatim: "•") Text("video.views \(viewCount)") } else if isLoadingAPIStats { - Text("•") + Text(verbatim: "•") Text("video.views 1.2M") .redacted(reason: .placeholder) } diff --git a/Yattee/Views/Components/VideoListContainer.swift b/Yattee/Views/Components/VideoListContainer.swift index 2bc9fa70..52e40cf6 100644 --- a/Yattee/Views/Components/VideoListContainer.swift +++ b/Yattee/Views/Components/VideoListContainer.swift @@ -171,9 +171,9 @@ extension VideoListContainer where Header == EmptyView { .fill(Color.gray.opacity(0.3)) .frame(width: 120, height: 68) VStack(alignment: .leading) { - Text("Video Title \(index + 1)") + Text(verbatim: "Video Title \(index + 1)") .font(.subheadline) - Text("Channel Name") + Text(verbatim: "Channel Name") .font(.caption) .foregroundStyle(.secondary) } @@ -197,9 +197,9 @@ extension VideoListContainer where Header == EmptyView { .fill(Color.gray.opacity(0.3)) .frame(width: 120, height: 68) VStack(alignment: .leading) { - Text("Video Title \(index + 1)") + Text(verbatim: "Video Title \(index + 1)") .font(.subheadline) - Text("Channel Name") + Text(verbatim: "Channel Name") .font(.caption) .foregroundStyle(.secondary) } diff --git a/Yattee/Views/Components/VideoListContent.swift b/Yattee/Views/Components/VideoListContent.swift index 3b354ef4..f2573c94 100644 --- a/Yattee/Views/Components/VideoListContent.swift +++ b/Yattee/Views/Components/VideoListContent.swift @@ -81,9 +81,9 @@ struct VideoListContent: View { .fill(Color.gray.opacity(0.3)) .frame(width: 120, height: 68) VStack(alignment: .leading) { - Text("Video Title \(index + 1)") + Text(verbatim: "Video Title \(index + 1)") .font(.subheadline) - Text("Channel Name") + Text(verbatim: "Channel Name") .font(.caption) .foregroundStyle(.secondary) } @@ -112,9 +112,9 @@ struct VideoListContent: View { .fill(Color.gray.opacity(0.3)) .frame(width: 120, height: 68) VStack(alignment: .leading) { - Text("Video Title \(index + 1)") + Text(verbatim: "Video Title \(index + 1)") .font(.subheadline) - Text("Channel Name") + Text(verbatim: "Channel Name") .font(.caption) .foregroundStyle(.secondary) } diff --git a/Yattee/Views/Components/VideoListRow.swift b/Yattee/Views/Components/VideoListRow.swift index c3972395..413f19b0 100644 --- a/Yattee/Views/Components/VideoListRow.swift +++ b/Yattee/Views/Components/VideoListRow.swift @@ -121,9 +121,9 @@ struct VideoListRow: View { .fill(Color.gray.opacity(0.3)) .frame(width: 120, height: 68) VStack(alignment: .leading) { - Text("Video Title \(index + 1)") + Text(verbatim: "Video Title \(index + 1)") .font(.subheadline) - Text("Channel Name") + Text(verbatim: "Channel Name") .font(.caption) .foregroundStyle(.secondary) } @@ -152,9 +152,9 @@ struct VideoListRow: View { .fill(Color.gray.opacity(0.3)) .frame(width: 120, height: 68) VStack(alignment: .leading) { - Text("Video Title \(index + 1)") + Text(verbatim: "Video Title \(index + 1)") .font(.subheadline) - Text("Channel Name") + Text(verbatim: "Channel Name") .font(.caption) .foregroundStyle(.secondary) } diff --git a/Yattee/Views/Downloads/DownloadsView.swift b/Yattee/Views/Downloads/DownloadsView.swift index fd4718e4..767532e0 100644 --- a/Yattee/Views/Downloads/DownloadsView.swift +++ b/Yattee/Views/Downloads/DownloadsView.swift @@ -61,7 +61,7 @@ struct DownloadsView: View { flatDownloadsList(manager, settings: settings) } } - .searchable(text: $searchText, prompt: Text("Search downloads")) + .searchable(text: $searchText, prompt: Text(String(localized: "downloads.search.placeholder"))) .onChange(of: selectedDownload) { _, newValue in if let download = newValue { if let result = downloadManager?.videoAndStream(for: download) { diff --git a/Yattee/Views/Home/BookmarksListView.swift b/Yattee/Views/Home/BookmarksListView.swift index 5cf23884..7feed46e 100644 --- a/Yattee/Views/Home/BookmarksListView.swift +++ b/Yattee/Views/Home/BookmarksListView.swift @@ -140,7 +140,7 @@ struct BookmarksListView: View { .navigationTitle(String(localized: "home.bookmarks.title")) #if !os(tvOS) .toolbarTitleDisplayMode(.inlineLarge) - .searchable(text: $searchText, prompt: Text("Search bookmarks")) + .searchable(text: $searchText, prompt: Text(String(localized: "bookmarks.search.placeholder"))) .toolbar { ToolbarItem(placement: .primaryAction) { Button { diff --git a/Yattee/Views/Home/HistoryListView.swift b/Yattee/Views/Home/HistoryListView.swift index 0feabd62..6c25639f 100644 --- a/Yattee/Views/Home/HistoryListView.swift +++ b/Yattee/Views/Home/HistoryListView.swift @@ -139,7 +139,7 @@ struct HistoryListView: View { .navigationTitle(String(localized: "home.history.title")) #if !os(tvOS) .toolbarTitleDisplayMode(.inlineLarge) - .searchable(text: $searchText, prompt: Text("Search history")) + .searchable(text: $searchText, prompt: Text(String(localized: "history.search.placeholder"))) .toolbar { // View options button (always visible) ToolbarItem(placement: .primaryAction) { diff --git a/Yattee/Views/Home/HomeSettingsView.swift b/Yattee/Views/Home/HomeSettingsView.swift index fa0b2993..53659cf3 100644 --- a/Yattee/Views/Home/HomeSettingsView.swift +++ b/Yattee/Views/Home/HomeSettingsView.swift @@ -737,8 +737,8 @@ private struct TVHomeItemRow: View { .frame(width: 30, height: 24) } .buttonStyle(TVCompactButtonStyle()) - .alert("Remove from Home?", isPresented: $showingDeleteConfirmation) { - Button("Cancel", role: .cancel) { } + .alert(String(localized: "home.removeConfirmation.title"), isPresented: $showingDeleteConfirmation) { + Button(String(localized: "common.cancel"), role: .cancel) { } Button("Remove", role: .destructive) { onDelete?() } diff --git a/Yattee/Views/Home/HomeView.swift b/Yattee/Views/Home/HomeView.swift index acc8c6ab..767ab242 100644 --- a/Yattee/Views/Home/HomeView.swift +++ b/Yattee/Views/Home/HomeView.swift @@ -80,7 +80,7 @@ struct HomeView: View { Image(systemName: "gear") } .accessibilityIdentifier("home.settingsButton") - .accessibilityLabel("Settings") + .accessibilityLabel(String(localized: "settings.title")) .liquidGlassTransitionSource(id: "homeSettings", in: sheetTransition) } #endif @@ -1137,7 +1137,7 @@ struct HomeView: View { ) } label: { HStack(spacing: 4) { - Text("\(contentType.localizedTitle) from \(instance.displayName)") + Text(verbatim: "\(contentType.localizedTitle) - \(instance.displayName)") .fontWeight(.semibold) Image(systemName: "chevron.right") .font(.caption) @@ -1145,7 +1145,6 @@ struct HomeView: View { .foregroundStyle(Color.accentColor) } .buttonStyle(.plain) - .padding(.horizontal, 32) .padding(.top, 16) .padding(.bottom, 8) .frame(maxWidth: .infinity, alignment: .leading) @@ -1201,7 +1200,7 @@ struct HomeView: View { HStack { Image(systemName: source.type.systemImage) .foregroundStyle(.secondary) - Text("Browse \(source.name)") + Text("mediaSources.browse \(source.name)") Spacer() Image(systemName: "chevron.right") .foregroundStyle(.tertiary) diff --git a/Yattee/Views/Home/PlaylistRowView.swift b/Yattee/Views/Home/PlaylistRowView.swift index e767a5be..ed536a8a 100644 --- a/Yattee/Views/Home/PlaylistRowView.swift +++ b/Yattee/Views/Home/PlaylistRowView.swift @@ -37,7 +37,7 @@ struct PlaylistRowView: View { .font(.headline) .lineLimit(1) - Text("\(playlist.videoCount) videos • \(playlist.formattedTotalDuration)") + Text("playlist.videoCountDuration \(playlist.videoCount) \(playlist.formattedTotalDuration)") .font(.caption.monospacedDigit()) .foregroundStyle(.secondary) } diff --git a/Yattee/Views/MediaBrowser/MediaBrowserView.swift b/Yattee/Views/MediaBrowser/MediaBrowserView.swift index c841d197..dd1c35ad 100644 --- a/Yattee/Views/MediaBrowser/MediaBrowserView.swift +++ b/Yattee/Views/MediaBrowser/MediaBrowserView.swift @@ -91,7 +91,7 @@ struct MediaBrowserView: View { showViewOptions = true } label: { Label( - "View Options", + String(localized: "viewOptions.title"), systemImage: showOnlyPlayable ? "line.3.horizontal.decrease.circle.fill" : "line.3.horizontal.decrease.circle" diff --git a/Yattee/Views/MediaBrowser/MediaBrowserViewOptionsSheet.swift b/Yattee/Views/MediaBrowser/MediaBrowserViewOptionsSheet.swift index 4f56eef7..cd0a6f92 100644 --- a/Yattee/Views/MediaBrowser/MediaBrowserViewOptionsSheet.swift +++ b/Yattee/Views/MediaBrowser/MediaBrowserViewOptionsSheet.swift @@ -62,7 +62,7 @@ struct MediaBrowserViewOptionsSheet: View { Button(role: .cancel) { dismiss() } label: { - Label("Close", systemImage: "xmark") + Label(String(localized: "common.close"), systemImage: "xmark") .labelStyle(.iconOnly) } } diff --git a/Yattee/Views/Onboarding/OnboardingTitleScreen.swift b/Yattee/Views/Onboarding/OnboardingTitleScreen.swift index b120c663..da849b3f 100644 --- a/Yattee/Views/Onboarding/OnboardingTitleScreen.swift +++ b/Yattee/Views/Onboarding/OnboardingTitleScreen.swift @@ -26,7 +26,7 @@ struct OnboardingTitleScreen: View { .frame(width: iconSize, height: iconSize) .clipShape(RoundedRectangle(cornerRadius: cornerRadius)) - Text("Yattee") + Text(verbatim: "Yattee") .font(.largeTitle) .fontWeight(.bold) diff --git a/Yattee/Views/Player/ChaptersView.swift b/Yattee/Views/Player/ChaptersView.swift index 44b7cd79..5927649d 100644 --- a/Yattee/Views/Player/ChaptersView.swift +++ b/Yattee/Views/Player/ChaptersView.swift @@ -35,7 +35,7 @@ struct ChaptersView: View { Button(role: .cancel) { dismiss() } label: { - Label("Close", systemImage: "xmark") + Label(String(localized: "common.close"), systemImage: "xmark") .labelStyle(.iconOnly) } } diff --git a/Yattee/Views/Player/ControlsSectionRenderer.swift b/Yattee/Views/Player/ControlsSectionRenderer.swift index 28328307..29ae3b92 100644 --- a/Yattee/Views/Player/ControlsSectionRenderer.swift +++ b/Yattee/Views/Player/ControlsSectionRenderer.swift @@ -602,7 +602,7 @@ struct ControlsSectionRenderer: View { Circle() .fill(.red) .frame(width: 6, height: 6) - Text("LIVE") + Text(String(localized: "player.live")) .font(.caption.weight(.semibold)) .foregroundStyle(.red) } @@ -622,7 +622,7 @@ struct ControlsSectionRenderer: View { .font(timeFont) .foregroundStyle(.white) - Text(" / ") + Text(verbatim: " / ") .font(timeFont) .foregroundStyle(.white.opacity(0.7)) @@ -638,7 +638,7 @@ struct ControlsSectionRenderer: View { .font(timeFont) .foregroundStyle(.white) - Text(" / ") + Text(verbatim: " / ") .font(timeFont) .foregroundStyle(.white.opacity(0.7)) @@ -653,11 +653,11 @@ struct ControlsSectionRenderer: View { .font(timeFont) .foregroundStyle(.white) - Text(" / ") + Text(verbatim: " / ") .font(timeFont) .foregroundStyle(.white.opacity(0.7)) - Text("-\(formattedRemainingTime)") + Text(verbatim: "-\(formattedRemainingTime)") .font(timeFont) .foregroundStyle(.white.opacity(0.7)) } @@ -669,11 +669,11 @@ struct ControlsSectionRenderer: View { .font(timeFont) .foregroundStyle(.white) - Text(" / ") + Text(verbatim: " / ") .font(timeFont) .foregroundStyle(.white.opacity(0.7)) - Text("-\(formattedRemainingTime)") + Text(verbatim: "-\(formattedRemainingTime)") .font(timeFont) .foregroundStyle(.white.opacity(0.7)) } diff --git a/Yattee/Views/Player/ExpandedPlayerSheet+Layouts.swift b/Yattee/Views/Player/ExpandedPlayerSheet+Layouts.swift index 17264851..be098197 100644 --- a/Yattee/Views/Player/ExpandedPlayerSheet+Layouts.swift +++ b/Yattee/Views/Player/ExpandedPlayerSheet+Layouts.swift @@ -1486,17 +1486,17 @@ extension ExpandedPlayerSheet { .overlay(alignment: panelSide == .right ? .topLeading : .topTrailing) { if appEnvironment?.settingsManager.showPlayerAreaDebug == true { VStack(alignment: .leading, spacing: 2) { - Text("Layout (yellow):") + Text(verbatim: "Layout (yellow):") .fontWeight(.bold) - Text("leadSA: \(Int(leadingSafeArea)) trailSA: \(Int(trailingSafeArea))") - Text("availW: \(Int(availableWidth)) fullW: \(Int(fullWidth))") - Text("fullH: \(Int(fullHeight)) offset: \(Int(controlsOffset))") - Text("pinned: \(isPanelPinned ? "Y" : "N") vis: \(isPanelVisible ? "Y" : "N") side: \(panelSide == .left ? "L" : "R")") + Text(verbatim: "leadSA: \(Int(leadingSafeArea)) trailSA: \(Int(trailingSafeArea))") + Text(verbatim: "availW: \(Int(availableWidth)) fullW: \(Int(fullWidth))") + Text(verbatim: "fullH: \(Int(fullHeight)) offset: \(Int(controlsOffset))") + Text(verbatim: "pinned: \(isPanelPinned ? "Y" : "N") vis: \(isPanelVisible ? "Y" : "N") side: \(panelSide == .left ? "L" : "R")") #if os(iOS) let orientation = UIApplication.shared.connectedScenes .compactMap { $0 as? UIWindowScene } .first?.interfaceOrientation - Text("orient: \(orientation == .landscapeLeft ? "LL" : orientation == .landscapeRight ? "LR" : "P")") + Text(verbatim: "orient: \(orientation == .landscapeLeft ? "LL" : orientation == .landscapeRight ? "LR" : "P")") #endif } .font(.system(size: 9, weight: .medium, design: .monospaced)) diff --git a/Yattee/Views/Player/MPVDebugOverlay.swift b/Yattee/Views/Player/MPVDebugOverlay.swift index 98de7e5f..e8b1ef90 100644 --- a/Yattee/Views/Player/MPVDebugOverlay.swift +++ b/Yattee/Views/Player/MPVDebugOverlay.swift @@ -105,11 +105,11 @@ struct MPVDebugOverlay: View { // Header with close button (non-tvOS only - tvOS has button at bottom) HStack(spacing: 4) { #if os(tvOS) - Text("MPV Debug Stats") + Text(String(localized: "player.debug.stats")) .font(.system(size: headerSize, weight: .semibold, design: .monospaced)) .foregroundStyle(primaryTextColor) #else - Text(isLandscape ? "MPV Debug" : "Debug") + Text(String(localized: isLandscape ? "player.debug.title" : "player.debug.titleShort")) .font(.system(size: headerSize, weight: .semibold, design: .monospaced)) .foregroundStyle(primaryTextColor) @@ -168,7 +168,7 @@ struct MPVDebugOverlay: View { HStack(spacing: 12) { Image(systemName: "xmark.circle") .font(.system(size: closeButtonSize)) - Text("Close") + Text(String(localized: "common.close")) .font(.system(size: rowSize, weight: .medium)) } .foregroundStyle(.white) diff --git a/Yattee/Views/Player/PlayerControlsView.swift b/Yattee/Views/Player/PlayerControlsView.swift index 4e41b989..2c5734f3 100644 --- a/Yattee/Views/Player/PlayerControlsView.swift +++ b/Yattee/Views/Player/PlayerControlsView.swift @@ -612,14 +612,14 @@ struct PlayerControlsView: View { // Position at bottom-left to avoid overlap with yellow layout debug if showPlayerAreaDebug { VStack(alignment: .leading, spacing: 2) { - Text("Controls (cyan):") + Text(verbatim: "Controls (cyan):") .fontWeight(.bold) - Text("winSA T:\(Int(safeArea.top)) B:\(Int(safeArea.bottom)) L:\(Int(safeArea.left)) R:\(Int(safeArea.right))") - Text("topPad: \(Int(topPadding)) btmPad: \(Int(bottomPadding)) wide: \(isWideScreenLayout ? "Y" : "N")") - Text("geom W:\(Int(geometry.size.width)) H:\(Int(geometry.size.height))") - Text("vidTop: \(Int(effectiveVideoTop)) vidH: \(Int(effectiveVideoHeight))") - Text("centerOff: \(Int(centerYOffset)) btmExtra: \(Int(bottomExtraPadding))") - Text("vis: \(isPanelVisible ? "Y" : "N") fitH: \(Int(videoFitHeight ?? -1)) effVH: \(Int(effectiveVideoHeight))") + Text(verbatim: "winSA T:\(Int(safeArea.top)) B:\(Int(safeArea.bottom)) L:\(Int(safeArea.left)) R:\(Int(safeArea.right))") + Text(verbatim: "topPad: \(Int(topPadding)) btmPad: \(Int(bottomPadding)) wide: \(isWideScreenLayout ? "Y" : "N")") + Text(verbatim: "geom W:\(Int(geometry.size.width)) H:\(Int(geometry.size.height))") + Text(verbatim: "vidTop: \(Int(effectiveVideoTop)) vidH: \(Int(effectiveVideoHeight))") + Text(verbatim: "centerOff: \(Int(centerYOffset)) btmExtra: \(Int(bottomExtraPadding))") + Text(verbatim: "vis: \(isPanelVisible ? "Y" : "N") fitH: \(Int(videoFitHeight ?? -1)) effVH: \(Int(effectiveVideoHeight))") } .font(.system(size: 9, weight: .medium, design: .monospaced)) .foregroundStyle(.cyan) diff --git a/Yattee/Views/Player/PlayerHelperViews.swift b/Yattee/Views/Player/PlayerHelperViews.swift index 104e67a2..22263a56 100644 --- a/Yattee/Views/Player/PlayerHelperViews.swift +++ b/Yattee/Views/Player/PlayerHelperViews.swift @@ -117,7 +117,7 @@ struct ErrorDetailsSheet: View { Button(role: .cancel) { dismiss() } label: { - Label("Close", systemImage: "xmark") + Label(String(localized: "common.close"), systemImage: "xmark") .labelStyle(.iconOnly) } } diff --git a/Yattee/Views/Player/QualitySelector/QualitySelectorDownloadRows.swift b/Yattee/Views/Player/QualitySelector/QualitySelectorDownloadRows.swift index 87caddc4..c5492f8b 100644 --- a/Yattee/Views/Player/QualitySelector/QualitySelectorDownloadRows.swift +++ b/Yattee/Views/Player/QualitySelector/QualitySelectorDownloadRows.swift @@ -52,7 +52,7 @@ struct DownloadedVideoRowView: View { @ViewBuilder private var codecBadge: some View { if isMuxed { - Text("STREAM") + Text(String(localized: "stream.badge.stream")) .font(.caption2) .padding(.horizontal, 6) .padding(.vertical, 2) diff --git a/Yattee/Views/Player/QualitySelector/QualitySelectorRowViews.swift b/Yattee/Views/Player/QualitySelector/QualitySelectorRowViews.swift index f178c93f..3ac4497a 100644 --- a/Yattee/Views/Player/QualitySelector/QualitySelectorRowViews.swift +++ b/Yattee/Views/Player/QualitySelector/QualitySelectorRowViews.swift @@ -36,7 +36,7 @@ struct AdaptiveStreamRowView: View { HStack { VStack(alignment: .leading, spacing: 2) { HStack(spacing: 6) { - Text(format == .hls ? "HLS" : "DASH") + Text(String(localized: format == .hls ? "stream.format.hls" : "stream.format.dash")) .font(.headline) // Show quality badge when available @@ -49,7 +49,7 @@ struct AdaptiveStreamRowView: View { .clipShape(Capsule()) } } - Text(format == .hls ? "Apple HLS" : "MPEG-DASH (Best for MPV)") + Text(String(localized: format == .hls ? "stream.format.appleHLS" : "stream.format.mpegDash")) .font(.caption) .foregroundStyle(.secondary) } @@ -134,7 +134,7 @@ struct VideoStreamRowView: View { @ViewBuilder private var codecBadge: some View { if stream.isMuxed { - Text("STREAM") + Text(String(localized: "stream.badge.stream")) .font(.caption2) .padding(.horizontal, 6) .padding(.vertical, 2) @@ -359,7 +359,7 @@ struct CaptionRowView: View { .font(.headline) if let caption, caption.isAutoGenerated { - Text("AUTO") + Text(String(localized: "stream.subtitle.auto")) .font(.caption2) .padding(.horizontal, 6) .padding(.vertical, 2) diff --git a/Yattee/Views/Player/QualitySelectorView.swift b/Yattee/Views/Player/QualitySelectorView.swift index 64875b8f..632b987c 100644 --- a/Yattee/Views/Player/QualitySelectorView.swift +++ b/Yattee/Views/Player/QualitySelectorView.swift @@ -194,7 +194,7 @@ struct QualitySelectorView: View { Button(role: .cancel) { dismiss() } label: { - Label("Close", systemImage: "xmark") + Label(String(localized: "common.close"), systemImage: "xmark") .labelStyle(.iconOnly) } } diff --git a/Yattee/Views/Player/QueueManagementSheet.swift b/Yattee/Views/Player/QueueManagementSheet.swift index f12bf7e5..02294d93 100644 --- a/Yattee/Views/Player/QueueManagementSheet.swift +++ b/Yattee/Views/Player/QueueManagementSheet.swift @@ -45,7 +45,7 @@ struct QueueManagementSheet: View { Button(role: .cancel) { dismiss() } label: { - Label("Close", systemImage: "xmark") + Label(String(localized: "common.close"), systemImage: "xmark") .labelStyle(.iconOnly) } } diff --git a/Yattee/Views/Player/macOS/MacOSControlBar.swift b/Yattee/Views/Player/macOS/MacOSControlBar.swift index 2df72a78..69e56ea9 100644 --- a/Yattee/Views/Player/macOS/MacOSControlBar.swift +++ b/Yattee/Views/Player/macOS/MacOSControlBar.swift @@ -214,7 +214,7 @@ struct MacOSControlBar: View { Circle() .fill(.red) .frame(width: 6, height: 6) - Text("LIVE") + Text(String(localized: "player.live")) .font(.system(size: 11, weight: .semibold)) .foregroundStyle(.red) } diff --git a/Yattee/Views/Player/tvOS/TVDetailsPanel.swift b/Yattee/Views/Player/tvOS/TVDetailsPanel.swift index ec51284c..8f3ebf36 100644 --- a/Yattee/Views/Player/tvOS/TVDetailsPanel.swift +++ b/Yattee/Views/Player/tvOS/TVDetailsPanel.swift @@ -185,7 +185,7 @@ struct TVDetailsPanel: View { // Subscriber count if let subscriberCount = video?.author.subscriberCount { - Text("\(CountFormatter.compact(subscriberCount)) subscribers") + Text("channel.subscriberCount \(CountFormatter.compact(subscriberCount))") .font(.subheadline) .foregroundStyle(.white.opacity(0.6)) } @@ -200,7 +200,7 @@ struct TVDetailsPanel: View { navigateToChannel(video.author) } } label: { - Text("View Channel") + Text(String(localized: "channel.view")) .font(.callout) .fontWeight(.medium) } @@ -272,7 +272,7 @@ struct TVDetailsPanel: View { ) .padding(.vertical, 16) } else { - Text("Comments unavailable") + Text(String(localized: "comments.unavailable")) .font(.body) .foregroundStyle(.white.opacity(0.5)) .frame(maxWidth: .infinity, alignment: .center) @@ -476,7 +476,7 @@ struct TVCommentsListView: View { Image(systemName: "bubble.left.and.exclamationmark.bubble.right") .font(.title2) .foregroundStyle(.white.opacity(0.5)) - Text("Comments disabled") + Text(String(localized: "comments.disabled")) .font(.subheadline) .foregroundStyle(.white.opacity(0.5)) } @@ -506,7 +506,7 @@ struct TVCommentsListView: View { Image(systemName: "bubble.left.and.bubble.right") .font(.title2) .foregroundStyle(.white.opacity(0.5)) - Text("No comments") + Text(String(localized: "comments.empty")) .font(.subheadline) .foregroundStyle(.white.opacity(0.5)) } @@ -707,7 +707,7 @@ struct TVFocusableCommentView: View { HStack(spacing: 4) { Image(systemName: showReplies ? "chevron.up" : "chevron.down") .font(.caption2) - Text("\(comment.replyCount) replies") + Text("comments.replyCount \(comment.replyCount)") .font(.caption) } .foregroundStyle(.blue) @@ -749,7 +749,7 @@ struct TVFocusableCommentView: View { Button { Task { await loadReplies() } } label: { - Text("Load more replies") + Text(String(localized: "comments.loadMoreReplies")) .font(.caption) .foregroundStyle(.blue) } diff --git a/Yattee/Views/Player/tvOS/TVPlayerControlsView.swift b/Yattee/Views/Player/tvOS/TVPlayerControlsView.swift index 34ba5e19..2c86e0db 100644 --- a/Yattee/Views/Player/tvOS/TVPlayerControlsView.swift +++ b/Yattee/Views/Player/tvOS/TVPlayerControlsView.swift @@ -233,7 +233,7 @@ struct TVPlayerControlsView: View { VStack(spacing: 6) { Image(systemName: "captions.bubble") .font(.system(size: 28)) - Text("Subtitles") + Text(String(localized: "player.controls.subtitles")) .font(.caption) } } @@ -247,7 +247,7 @@ struct TVPlayerControlsView: View { VStack(spacing: 6) { Image(systemName: "ant.circle") .font(.system(size: 28)) - Text("Debug") + Text(String(localized: "player.debug.titleShort")) .font(.caption) } } @@ -281,7 +281,7 @@ struct TVPlayerControlsView: View { VStack(spacing: 6) { Image(systemName: "speaker.minus") .font(.system(size: 28)) - Text("Vol -") + Text(String(localized: "player.tvos.volumeDown")) .font(.caption) } } @@ -299,7 +299,7 @@ struct TVPlayerControlsView: View { VStack(spacing: 6) { Image(systemName: "speaker.plus") .font(.system(size: 28)) - Text("Vol +") + Text(String(localized: "player.tvos.volumeUp")) .font(.caption) } } diff --git a/Yattee/Views/Player/tvOS/TVPlayerProgressBar.swift b/Yattee/Views/Player/tvOS/TVPlayerProgressBar.swift index 2f166734..24fe7c96 100644 --- a/Yattee/Views/Player/tvOS/TVPlayerProgressBar.swift +++ b/Yattee/Views/Player/tvOS/TVPlayerProgressBar.swift @@ -155,7 +155,7 @@ struct TVPlayerProgressBar: View { Circle() .fill(.red) .frame(width: 8, height: 8) - Text("LIVE") + Text(String(localized: "player.live")) .font(.subheadline.weight(.semibold)) .foregroundStyle(.red) } @@ -171,11 +171,11 @@ struct TVPlayerProgressBar: View { // Scrub hint when focused (only for non-live) if !isLive && isFocused && !isScrubbing { - Text("Press to scrub") + Text(String(localized: "player.tvos.scrubHint")) .font(.caption) .foregroundStyle(.white.opacity(0.5)) } else if !isLive && isScrubbing { - Text("Swipe ← → • press to seek") + Text(String(localized: "player.tvos.seekHint")) .font(.caption) .foregroundStyle(.white.opacity(0.7)) } diff --git a/Yattee/Views/RemoteControl/RemoteControlContentView.swift b/Yattee/Views/RemoteControl/RemoteControlContentView.swift index 8893e3f9..b67c9e1c 100644 --- a/Yattee/Views/RemoteControl/RemoteControlContentView.swift +++ b/Yattee/Views/RemoteControl/RemoteControlContentView.swift @@ -343,7 +343,7 @@ private struct DeviceRowContent: View { #Preview { NavigationStack { RemoteControlContentView(navigationStyle: .link) - .navigationTitle("Remote Control") + .navigationTitle(String(localized: "remoteControl.title")) } .appEnvironment(.preview) } diff --git a/Yattee/Views/RemoteControl/RemoteControlView.swift b/Yattee/Views/RemoteControl/RemoteControlView.swift index 933bd2a4..2ab50912 100644 --- a/Yattee/Views/RemoteControl/RemoteControlView.swift +++ b/Yattee/Views/RemoteControl/RemoteControlView.swift @@ -337,7 +337,7 @@ struct RemoteControlView: View { .font(.caption) .foregroundStyle(.secondary) } - Text("\(Int(ago))s ago") + Text("remoteControl.lastSeen \(Int(ago))") .font(.caption2.monospacedDigit()) .foregroundStyle(.tertiary) } diff --git a/Yattee/Views/RemoteControl/RemoteDevicesSheet.swift b/Yattee/Views/RemoteControl/RemoteDevicesSheet.swift index 58cd8056..bd770329 100644 --- a/Yattee/Views/RemoteControl/RemoteDevicesSheet.swift +++ b/Yattee/Views/RemoteControl/RemoteDevicesSheet.swift @@ -16,7 +16,7 @@ struct RemoteDevicesSheet: View { DynamicSheetContainer { NavigationStack { RemoteControlContentView(navigationStyle: .selection($selectedDevice)) - .navigationTitle("Remote Control") + .navigationTitle(String(localized: "remoteControl.title")) #if os(iOS) .navigationBarTitleDisplayMode(.inline) #endif @@ -28,7 +28,7 @@ struct RemoteDevicesSheet: View { Button(role: .cancel) { dismiss() } label: { - Label("Close", systemImage: "xmark") + Label(String(localized: "common.close"), systemImage: "xmark") .labelStyle(.iconOnly) } } diff --git a/Yattee/Views/Settings/AboutView.swift b/Yattee/Views/Settings/AboutView.swift index 37f216ef..e7a9affe 100644 --- a/Yattee/Views/Settings/AboutView.swift +++ b/Yattee/Views/Settings/AboutView.swift @@ -17,7 +17,7 @@ struct AboutView: View { communityLink("GitHub", icon: "github", url: "https://github.com/yattee/yattee") communityLink("Discord", icon: "discord", url: "https://yattee.stream/discord") } header: { - Text("Community") + Text(String(localized: "settings.about.community")) } Section { diff --git a/Yattee/Views/Settings/AddSource/AddSourceShared.swift b/Yattee/Views/Settings/AddSource/AddSourceShared.swift index f2ae834f..97d5ece5 100644 --- a/Yattee/Views/Settings/AddSource/AddSourceShared.swift +++ b/Yattee/Views/Settings/AddSource/AddSourceShared.swift @@ -35,13 +35,13 @@ struct SourceTestResultSection: View { .foregroundStyle(.green) if bandwidth.hasWriteAccess { if let upload = bandwidth.formattedUploadSpeed { - Label("Upload: \(upload)", systemImage: "arrow.up.circle") + Label(String(localized: "sources.bandwidth.upload \(upload)"), systemImage: "arrow.up.circle") .font(.subheadline) .foregroundStyle(.secondary) } } if let download = bandwidth.formattedDownloadSpeed { - Label("Download: \(download)", systemImage: "arrow.down.circle") + Label(String(localized: "sources.bandwidth.download \(download)"), systemImage: "arrow.down.circle") .font(.subheadline) .foregroundStyle(.secondary) } diff --git a/Yattee/Views/Settings/AddSourceView.swift b/Yattee/Views/Settings/AddSourceView.swift index 08ca1e13..30dc97ba 100644 --- a/Yattee/Views/Settings/AddSourceView.swift +++ b/Yattee/Views/Settings/AddSourceView.swift @@ -72,7 +72,7 @@ struct AddSourceView: View { Button(role: .cancel) { dismiss() } label: { - Label("Close", systemImage: "xmark") + Label(String(localized: "common.close"), systemImage: "xmark") .labelStyle(.iconOnly) } } diff --git a/Yattee/Views/Settings/EditSourceView.swift b/Yattee/Views/Settings/EditSourceView.swift index e58d41bb..9c8515cf 100644 --- a/Yattee/Views/Settings/EditSourceView.swift +++ b/Yattee/Views/Settings/EditSourceView.swift @@ -728,13 +728,13 @@ private struct EditFileSourceContent: View { .foregroundStyle(.green) if bandwidth.hasWriteAccess { if let upload = bandwidth.formattedUploadSpeed { - Label("Upload: \(upload)", systemImage: "arrow.up.circle") + Label(String(localized: "sources.bandwidth.upload \(upload)"), systemImage: "arrow.up.circle") .font(.subheadline) .foregroundStyle(.secondary) } } if let download = bandwidth.formattedDownloadSpeed { - Label("Download: \(download)", systemImage: "arrow.down.circle") + Label(String(localized: "sources.bandwidth.download \(download)"), systemImage: "arrow.down.circle") .font(.subheadline) .foregroundStyle(.secondary) } diff --git a/Yattee/Views/Settings/InstancePickerSheet.swift b/Yattee/Views/Settings/InstancePickerSheet.swift index 5e63fd31..eb45cefd 100644 --- a/Yattee/Views/Settings/InstancePickerSheet.swift +++ b/Yattee/Views/Settings/InstancePickerSheet.swift @@ -58,7 +58,7 @@ struct InstancePickerSheet: View { Button(role: .cancel) { dismiss() } label: { - Label("Close", systemImage: "xmark") + Label(String(localized: "common.close"), systemImage: "xmark") .labelStyle(.iconOnly) } } @@ -95,7 +95,7 @@ private struct PickerInstanceRow: View { .font(.caption) .foregroundStyle(.secondary) - Text("•") + Text(verbatim: "•") .font(.caption) .foregroundStyle(.tertiary) diff --git a/Yattee/Views/Settings/LogViewerView.swift b/Yattee/Views/Settings/LogViewerView.swift index c5877570..cda8f3ac 100644 --- a/Yattee/Views/Settings/LogViewerView.swift +++ b/Yattee/Views/Settings/LogViewerView.swift @@ -241,7 +241,7 @@ private struct LogEntryDetailView: View { Button(role: .cancel) { dismiss() } label: { - Label("Close", systemImage: "xmark") + Label(String(localized: "common.close"), systemImage: "xmark") .labelStyle(.iconOnly) } } @@ -300,7 +300,7 @@ private struct LogFiltersSheet: View { Button(role: .cancel) { dismiss() } label: { - Label("Close", systemImage: "xmark") + Label(String(localized: "common.close"), systemImage: "xmark") .labelStyle(.iconOnly) } } diff --git a/Yattee/Views/Settings/PeerTubeInstancesExploreView.swift b/Yattee/Views/Settings/PeerTubeInstancesExploreView.swift index 84603cdc..1044db27 100644 --- a/Yattee/Views/Settings/PeerTubeInstancesExploreView.swift +++ b/Yattee/Views/Settings/PeerTubeInstancesExploreView.swift @@ -166,7 +166,7 @@ struct PeerTubeInstancesExploreView: View { Button(role: .cancel) { dismiss() } label: { - Label("Close", systemImage: "xmark") + Label(String(localized: "common.close"), systemImage: "xmark") .labelStyle(.iconOnly) } } diff --git a/Yattee/Views/Settings/PlayerControls/Gestures/TapGesturesSettingsView.swift b/Yattee/Views/Settings/PlayerControls/Gestures/TapGesturesSettingsView.swift index 86d48ea1..c56f48bf 100644 --- a/Yattee/Views/Settings/PlayerControls/Gestures/TapGesturesSettingsView.swift +++ b/Yattee/Views/Settings/PlayerControls/Gestures/TapGesturesSettingsView.swift @@ -191,7 +191,7 @@ struct TapGesturesSettingsView: View { Button(role: .cancel) { selectedZonePosition = nil } label: { - Label("Close", systemImage: "xmark") + Label(String(localized: "common.close"), systemImage: "xmark") .labelStyle(.iconOnly) } } diff --git a/Yattee/Views/Settings/PlayerControls/PlayerControlsPreviewView.swift b/Yattee/Views/Settings/PlayerControls/PlayerControlsPreviewView.swift index 30f52efb..32812b59 100644 --- a/Yattee/Views/Settings/PlayerControls/PlayerControlsPreviewView.swift +++ b/Yattee/Views/Settings/PlayerControls/PlayerControlsPreviewView.swift @@ -323,7 +323,7 @@ private struct PreviewButton: View { } } else if configuration.buttonType == .timeDisplay { // Time display - Text("0:00 / 3:45") + Text(verbatim: "0:00 / 3:45") .font(fontStyle.font(.caption)) .foregroundStyle(.white.opacity(0.9)) } else if configuration.buttonType == .brightness || configuration.buttonType == .volume { @@ -372,7 +372,7 @@ private struct PreviewButton: View { .fill(.white.opacity(0.3)) .frame(width: avatarSize, height: avatarSize) .overlay { - Text("Y") + Text(verbatim: "Y") .font(.system(size: size * 0.6, weight: .semibold)) .foregroundStyle(.white.opacity(0.8)) } @@ -382,14 +382,14 @@ private struct PreviewButton: View { if settings.showTitle || settings.showSourceName { VStack(alignment: .leading, spacing: 0) { if settings.showTitle { - Text("Video Title") + Text(String(localized: "common.videoTitle")) .font(fontStyle.font(size: size * 0.5, weight: .medium)) .foregroundStyle(.white.opacity(0.9)) .lineLimit(1) } if settings.showSourceName { - Text("Channel") + Text(String(localized: "common.channel")) .font(fontStyle.font(size: size * 0.4)) .foregroundStyle(.white.opacity(0.6)) .lineLimit(1) diff --git a/Yattee/Views/Settings/PlayerControls/SectionEditorView.swift b/Yattee/Views/Settings/PlayerControls/SectionEditorView.swift index a10785cd..ca24e9b5 100644 --- a/Yattee/Views/Settings/PlayerControls/SectionEditorView.swift +++ b/Yattee/Views/Settings/PlayerControls/SectionEditorView.swift @@ -288,7 +288,7 @@ private struct PreviewButtonView: View { .frame(width: spacerWidth) } } else if configuration.buttonType == .timeDisplay { - Text("0:00 / 3:45") + Text(verbatim: "0:00 / 3:45") .font(fontStyle.font(size: size * 0.6, weight: .medium)) .foregroundStyle(.white.opacity(0.9)) } else if configuration.buttonType == .brightness || configuration.buttonType == .volume { @@ -340,7 +340,7 @@ private struct PreviewButtonView: View { .fill(.white.opacity(0.3)) .frame(width: avatarSize, height: avatarSize) .overlay { - Text("Y") + Text(verbatim: "Y") .font(.system(size: size * 0.7, weight: .semibold)) .foregroundStyle(.white.opacity(0.8)) } @@ -350,14 +350,14 @@ private struct PreviewButtonView: View { if settings.showTitle || settings.showSourceName { VStack(alignment: .leading, spacing: 1) { if settings.showTitle { - Text("Video Title") + Text(verbatim: "Video Title") .font(fontStyle.font(size: size * 0.55, weight: .medium)) .foregroundStyle(.white.opacity(0.9)) .lineLimit(1) } if settings.showSourceName { - Text("Channel Name") + Text(verbatim: "Channel Name") .font(fontStyle.font(size: size * 0.45)) .foregroundStyle(.white.opacity(0.6)) .lineLimit(1) diff --git a/Yattee/Views/Settings/RemoteControlSettingsView.swift b/Yattee/Views/Settings/RemoteControlSettingsView.swift index 2510a330..1de3ad90 100644 --- a/Yattee/Views/Settings/RemoteControlSettingsView.swift +++ b/Yattee/Views/Settings/RemoteControlSettingsView.swift @@ -10,7 +10,7 @@ import SwiftUI struct RemoteControlSettingsView: View { var body: some View { RemoteControlContentView(navigationStyle: .link) - .navigationTitle("Remote Control") + .navigationTitle(String(localized: "remoteControl.title")) #if os(iOS) .navigationBarTitleDisplayMode(.inline) #endif diff --git a/Yattee/Views/Settings/SettingsView.swift b/Yattee/Views/Settings/SettingsView.swift index 556baf78..98488c35 100644 --- a/Yattee/Views/Settings/SettingsView.swift +++ b/Yattee/Views/Settings/SettingsView.swift @@ -74,7 +74,7 @@ struct SettingsView: View { Button(role: .cancel) { dismiss() } label: { - Label("Close", systemImage: "xmark") + Label(String(localized: "common.close"), systemImage: "xmark") .labelStyle(.iconOnly) } } @@ -184,7 +184,7 @@ struct SettingsView: View { Section { VStack(spacing: 4) { - Text("Yattee") + Text(verbatim: "Yattee") .font(.headline) Text("\(appVersion) (\(buildNumber))") .font(.subheadline) @@ -205,7 +205,7 @@ struct SettingsView: View { Button(role: .cancel) { dismiss() } label: { - Label("Close", systemImage: "xmark") + Label(String(localized: "common.close"), systemImage: "xmark") .labelStyle(.iconOnly) } .accessibilityIdentifier("settings.doneButton") diff --git a/Yattee/Views/Settings/SubtitlesSettingsView.swift b/Yattee/Views/Settings/SubtitlesSettingsView.swift index 89dae553..7cdad2f8 100644 --- a/Yattee/Views/Settings/SubtitlesSettingsView.swift +++ b/Yattee/Views/Settings/SubtitlesSettingsView.swift @@ -51,7 +51,7 @@ struct SubtitlesSettingsView: View { // tvOS uses Picker instead of Slider (Slider unavailable) Picker(String(localized: "settings.subtitles.fontSize"), selection: $settings.fontSize) { ForEach([20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95, 100], id: \.self) { size in - Text("\(size) pt").tag(size) + Text("settings.subtitles.fontSize \(size)").tag(size) } } .onChange(of: settings.fontSize) { _, _ in saveSettings() } @@ -73,7 +73,7 @@ struct SubtitlesSettingsView: View { .frame(width: 60) .multilineTextAlignment(.center) .onChange(of: settings.fontSize) { _, _ in saveSettings() } - Text("pt") + Text(String(localized: "common.unit.points")) .foregroundStyle(.secondary) } } diff --git a/Yattee/Views/Settings/iCloudSettingsView.swift b/Yattee/Views/Settings/iCloudSettingsView.swift index 5ad462f2..7d31a83a 100644 --- a/Yattee/Views/Settings/iCloudSettingsView.swift +++ b/Yattee/Views/Settings/iCloudSettingsView.swift @@ -267,7 +267,7 @@ struct iCloudSettingsView: View { Section { // iCloud Account Status HStack { - Label("Account", systemImage: "person.crop.circle") + Label(String(localized: "settings.icloud.account"), systemImage: "person.crop.circle") Spacer() HStack(spacing: 6) { accountStatusIcon @@ -278,7 +278,7 @@ struct iCloudSettingsView: View { // Sync Status HStack { - Label("Status", systemImage: "arrow.triangle.2.circlepath") + Label(String(localized: "settings.icloud.status"), systemImage: "arrow.triangle.2.circlepath") Spacer() HStack(spacing: 6) { syncStatusIcon @@ -290,9 +290,9 @@ struct iCloudSettingsView: View { // Pending Changes (if any) if let count = cloudKitSync?.pendingChangesCount, count > 0 { HStack { - Label("Pending", systemImage: "clock") + Label(String(localized: "settings.icloud.pending"), systemImage: "clock") Spacer() - Text("\(count) item\(count == 1 ? "" : "s")") + Text(verbatim: "\(count) item\(count == 1 ? "" : "s")") .foregroundStyle(.secondary) } } @@ -300,7 +300,7 @@ struct iCloudSettingsView: View { // Last Sync (automatic) if let lastSync = cloudKitSync?.lastSyncDate { HStack { - Label("Last Synced", systemImage: "checkmark.circle") + Label(String(localized: "settings.icloud.lastSynced"), systemImage: "checkmark.circle") Spacer() Text(lastSync, style: .relative) .foregroundStyle(.secondary) @@ -338,13 +338,13 @@ struct iCloudSettingsView: View { Button { syncNow() } label: { - Label("Sync Now", systemImage: syncNowIcon) + Label(String(localized: "settings.icloud.syncNow"), systemImage: syncNowIcon) } .disabled(cloudKitSync?.isSyncing == true) } footer: { if let lastSync = lastManualSyncRelative { - Text("Last manual sync: \(lastSync)") + Text(String(localized: "settings.icloud.lastManualSync \(lastSync)")) } } .animation(.easeInOut(duration: 0.3), value: cloudKitSync?.syncStatus) @@ -423,7 +423,7 @@ struct iCloudSettingsView: View { HStack { Image(systemName: "exclamationmark.circle.fill") .foregroundStyle(.red) - Text("Sync Error") + Text(String(localized: "settings.icloud.syncError")) .foregroundStyle(.primary) Spacer() Image(systemName: expandedError ? "chevron.up" : "chevron.down") @@ -446,7 +446,7 @@ struct iCloudSettingsView: View { await cloudKitSync?.sync() } } label: { - Label("Retry Sync", systemImage: "arrow.clockwise") + Label(String(localized: "settings.icloud.retrySync"), systemImage: "arrow.clockwise") .font(.caption) } .buttonStyle(.bordered) diff --git a/Yattee/Views/Subscriptions/ManageChannelsView.swift b/Yattee/Views/Subscriptions/ManageChannelsView.swift index 4d16e82d..7cb151fa 100644 --- a/Yattee/Views/Subscriptions/ManageChannelsView.swift +++ b/Yattee/Views/Subscriptions/ManageChannelsView.swift @@ -102,7 +102,7 @@ struct ManageChannelsView: View { .navigationTitle(String(localized: "subscriptions.channels.title")) #if !os(tvOS) .toolbarTitleDisplayMode(.inlineLarge) - .searchable(text: $searchText, prompt: Text("Search channels")) + .searchable(text: $searchText, prompt: Text(String(localized: "channels.search.placeholder"))) .toolbar { ToolbarItem(placement: .primaryAction) { Button { diff --git a/Yattee/Views/Video/DownloadQualitySheet.swift b/Yattee/Views/Video/DownloadQualitySheet.swift index b4565150..811c3639 100644 --- a/Yattee/Views/Video/DownloadQualitySheet.swift +++ b/Yattee/Views/Video/DownloadQualitySheet.swift @@ -580,7 +580,7 @@ struct DownloadQualitySheet: View { if showAdvancedStreamDetails { if stream.isMuxed { - Text("MUXED") + Text(String(localized: "stream.badge.muxed")) .font(.caption2) .padding(.horizontal, 6) .padding(.vertical, 2) @@ -666,7 +666,7 @@ struct DownloadQualitySheet: View { if showAdvancedStreamDetails { HStack(spacing: 4) { if stream.isOriginalAudio { - Text("ORIGINAL") + Text(String(localized: "stream.audio.original")) .font(.caption2) .foregroundStyle(.secondary) } @@ -719,7 +719,7 @@ struct DownloadQualitySheet: View { .font(.headline) if let caption, caption.isAutoGenerated { - Text("AUTO") + Text(String(localized: "stream.subtitle.auto")) .font(.caption2) .padding(.horizontal, 6) .padding(.vertical, 2) diff --git a/Yattee/Views/Video/PlaylistSelectorSheet.swift b/Yattee/Views/Video/PlaylistSelectorSheet.swift index 2cdc3518..0c0a1d3f 100644 --- a/Yattee/Views/Video/PlaylistSelectorSheet.swift +++ b/Yattee/Views/Video/PlaylistSelectorSheet.swift @@ -87,7 +87,7 @@ struct PlaylistSelectorSheet: View { Button(role: .cancel) { dismiss() } label: { - Label("Close", systemImage: "xmark") + Label(String(localized: "common.close"), systemImage: "xmark") .labelStyle(.iconOnly) } } diff --git a/Yattee/Views/Video/VideoInfoView.swift b/Yattee/Views/Video/VideoInfoView.swift index 01b5e5d9..9f2f5af4 100644 --- a/Yattee/Views/Video/VideoInfoView.swift +++ b/Yattee/Views/Video/VideoInfoView.swift @@ -1596,7 +1596,7 @@ struct VideoInfoView: View { Button(role: .cancel) { showingCommentsSheet = false } label: { - Label("Close", systemImage: "xmark") + Label(String(localized: "common.close"), systemImage: "xmark") .labelStyle(.iconOnly) } } diff --git a/bin/lint-localizable-keys b/bin/lint-localizable-keys new file mode 100755 index 00000000..84af0304 --- /dev/null +++ b/bin/lint-localizable-keys @@ -0,0 +1,45 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# Lint Localizable Key Format +# +# Checks that all keys in Localizable.xcstrings follow the dotted format +# (e.g., "group.subgroup.name"). Keys without dots are flagged for migration. +# +# Usage: +# ./bin/lint-localizable-keys +# + +require 'json' + +# Change to project directory +Dir.chdir(File.expand_path('..', __dir__)) + +XCSTRINGS_FILE = 'Yattee/Localizable.xcstrings' + +unless File.exist?(XCSTRINGS_FILE) + warn "Error: #{XCSTRINGS_FILE} not found" + exit 1 +end + +data = JSON.parse(File.read(XCSTRINGS_FILE)) +keys = data.fetch('strings', {}).keys + +# Strip trailing format specifiers to get the base key +def base_key(key) + key.gsub(/\s+%[@dlf]+(\.?\d*[dlf])?/, '').strip +end + +violations = keys.select { |key| !base_key(key).include?('.') } + .sort + +if violations.empty? + puts "All #{keys.size} keys use dotted format." + exit 0 +end + +puts "Keys missing dotted format (#{violations.size} of #{keys.size}):\n\n" +violations.each { |key| puts " #{key}" } +puts "\n#{violations.size} key(s) need migration to dotted format." +exit 1