mirror of
https://github.com/yattee/yattee.git
synced 2026-10-17 18:22:54 +00:00
Fix locales
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ struct ChannelPlaylistRow: View {
|
||||
}
|
||||
|
||||
if style == .compact {
|
||||
Text("\(playlist.videoCount) videos")
|
||||
Text("playlist.videoCount \(playlist.videoCount)")
|
||||
.font(.caption2)
|
||||
.foregroundStyle(.secondary)
|
||||
} else {
|
||||
|
||||
@@ -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"))
|
||||
}
|
||||
|
||||
|
||||
@@ -193,7 +193,7 @@ extension BlurredImageBackground {
|
||||
.fill(.gray)
|
||||
.frame(width: 280, height: 158)
|
||||
|
||||
Text("Video Title")
|
||||
Text(verbatim: "Video Title")
|
||||
.font(.headline)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ struct ChannelCardGridView: View {
|
||||
}
|
||||
} else {
|
||||
// Reserve space with invisible text
|
||||
Text(" ")
|
||||
Text(verbatim: " ")
|
||||
.font(subscriberFont)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -81,9 +81,9 @@ struct VideoListContent<Content: View>: 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<Content: View>: 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)
|
||||
}
|
||||
|
||||
@@ -121,9 +121,9 @@ struct VideoListRow<Content: View>: 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<Content: View>: 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)
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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?()
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -343,7 +343,7 @@ private struct DeviceRowContent: View {
|
||||
#Preview {
|
||||
NavigationStack {
|
||||
RemoteControlContentView(navigationStyle: .link)
|
||||
.navigationTitle("Remote Control")
|
||||
.navigationTitle(String(localized: "remoteControl.title"))
|
||||
}
|
||||
.appEnvironment(.preview)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
45
bin/lint-localizable-keys
Executable file
45
bin/lint-localizable-keys
Executable file
@@ -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
|
||||
Reference in New Issue
Block a user