Fix locales

This commit is contained in:
Arkadiusz Fal
2026-02-09 00:13:46 +01:00
parent 100df744d9
commit 425a2c590d
55 changed files with 644 additions and 548 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -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")
}
}
}

View File

@@ -76,7 +76,7 @@ struct ChannelPlaylistRow: View {
}
if style == .compact {
Text("\(playlist.videoCount) videos")
Text("playlist.videoCount \(playlist.videoCount)")
.font(.caption2)
.foregroundStyle(.secondary)
} else {

View File

@@ -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"))
}

View File

@@ -193,7 +193,7 @@ extension BlurredImageBackground {
.fill(.gray)
.frame(width: 280, height: 158)
Text("Video Title")
Text(verbatim: "Video Title")
.font(.headline)
}
}

View File

@@ -63,7 +63,7 @@ struct ChannelCardGridView: View {
}
} else {
// Reserve space with invisible text
Text(" ")
Text(verbatim: " ")
.font(subscriberFont)
}
}

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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) {

View File

@@ -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 {

View File

@@ -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) {

View File

@@ -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?()
}

View File

@@ -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)

View File

@@ -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)
}

View File

@@ -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"

View File

@@ -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)
}
}

View File

@@ -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)

View File

@@ -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)
}
}

View File

@@ -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))
}

View File

@@ -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))

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)
}
}

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)
}
}

View File

@@ -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)
}
}

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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)
}
}

View File

@@ -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))
}

View File

@@ -343,7 +343,7 @@ private struct DeviceRowContent: View {
#Preview {
NavigationStack {
RemoteControlContentView(navigationStyle: .link)
.navigationTitle("Remote Control")
.navigationTitle(String(localized: "remoteControl.title"))
}
.appEnvironment(.preview)
}

View File

@@ -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)
}

View File

@@ -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)
}
}

View File

@@ -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 {

View File

@@ -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)
}

View File

@@ -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)
}
}

View File

@@ -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)
}

View File

@@ -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)

View File

@@ -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)
}
}

View File

@@ -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)
}
}

View File

@@ -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)
}
}

View File

@@ -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)

View File

@@ -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)

View File

@@ -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

View File

@@ -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")

View File

@@ -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)
}
}

View File

@@ -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)

View File

@@ -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 {

View File

@@ -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)

View File

@@ -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)
}
}

View File

@@ -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
View 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