merge it fg (#184)
Some checks are pending
Build and Release / Build IPA (push) Waiting to run
Build and Release / Build Mac Catalyst (push) Waiting to run

This commit is contained in:
50/50 2025-06-12 21:54:58 +02:00 committed by GitHub
parent 35c4f35f5e
commit 375fe1806b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 3689 additions and 584 deletions

File diff suppressed because it is too large Load diff

View file

@ -57,16 +57,16 @@ struct DownloadView: View {
}
.animation(.easeInOut(duration: 0.2), value: selectedTab)
.navigationBarHidden(true)
.alert("Delete Download", isPresented: $showDeleteAlert) {
Button("Delete", role: .destructive) {
.alert(NSLocalizedString("Delete Download", comment: ""), isPresented: $showDeleteAlert) {
Button(NSLocalizedString("Delete", comment: ""), role: .destructive) {
if let asset = assetToDelete {
jsController.deleteAsset(asset)
}
}
Button("Cancel", role: .cancel) {}
Button(NSLocalizedString("Cancel", comment: ""), role: .cancel) {}
} message: {
if let asset = assetToDelete {
Text("Are you sure you want to delete '\(asset.episodeDisplayName)'?")
Text(String(format: NSLocalizedString("Are you sure you want to delete '%@'?", comment: ""), asset.episodeDisplayName))
}
}
}
@ -83,7 +83,7 @@ struct DownloadView: View {
VStack(spacing: 20) {
if !jsController.downloadQueue.isEmpty {
DownloadSectionView(
title: "Queue",
title: NSLocalizedString("Queue", comment: ""),
icon: "clock.fill",
downloads: jsController.downloadQueue
)
@ -91,7 +91,7 @@ struct DownloadView: View {
if !jsController.activeDownloads.isEmpty {
DownloadSectionView(
title: "Active Downloads",
title: NSLocalizedString("Active Downloads", comment: ""),
icon: "arrow.down.circle.fill",
downloads: jsController.activeDownloads
)
@ -140,12 +140,12 @@ struct DownloadView: View {
.foregroundStyle(.tertiary)
VStack(spacing: 8) {
Text("No Active Downloads")
Text(NSLocalizedString("No Active Downloads", comment: ""))
.font(.title2)
.fontWeight(.medium)
.foregroundStyle(.primary)
Text("Your active downloads will appear here.")
Text(NSLocalizedString("Actively downloading media can be tracked from here.", comment: ""))
.font(.subheadline)
.foregroundStyle(.secondary)
.multilineTextAlignment(.center)
@ -162,12 +162,12 @@ struct DownloadView: View {
.foregroundStyle(.tertiary)
VStack(spacing: 8) {
Text("No Downloads")
Text(NSLocalizedString("No Downloads", comment: ""))
.font(.title2)
.fontWeight(.medium)
.foregroundStyle(.primary)
Text("Your downloaded content will appear here")
Text(NSLocalizedString("Your downloaded episodes will appear here", comment: ""))
.font(.subheadline)
.foregroundStyle(.secondary)
.multilineTextAlignment(.center)
@ -274,7 +274,7 @@ struct CustomDownloadHeader: View {
var body: some View {
VStack(spacing: 0) {
HStack {
Text("Downloads")
Text(NSLocalizedString("Downloads", comment: ""))
.font(.largeTitle)
.fontWeight(.bold)
.foregroundStyle(.primary)
@ -348,7 +348,7 @@ struct CustomDownloadHeader: View {
.frame(width: 18, height: 18)
.foregroundColor(.secondary)
TextField("Search downloads", text: $searchText)
TextField(NSLocalizedString("Search downloads", comment: ""), text: $searchText)
.textFieldStyle(PlainTextFieldStyle())
.foregroundColor(.primary)
@ -394,14 +394,14 @@ struct CustomDownloadHeader: View {
VStack(spacing: 0) {
HStack(spacing: 0) {
TabButton(
title: "Active",
title: NSLocalizedString("Active", comment: ""),
icon: "arrow.down.circle",
isSelected: selectedTab == 0,
action: { selectedTab = 0 }
)
TabButton(
title: "Downloaded",
title: NSLocalizedString("Downloaded", comment: ""),
icon: "checkmark.circle",
isSelected: selectedTab == 1,
action: { selectedTab = 1 }
@ -526,7 +526,7 @@ struct DownloadSummaryCard: View {
HStack {
Image(systemName: "chart.bar.fill")
.foregroundColor(.accentColor)
Text("Download Summary".uppercased())
Text(NSLocalizedString("Download Summary", comment: "").uppercased())
.font(.footnote)
.fontWeight(.medium)
.foregroundColor(.secondary)
@ -538,7 +538,7 @@ struct DownloadSummaryCard: View {
VStack(alignment: .leading, spacing: 12) {
HStack(spacing: 20) {
SummaryItem(
title: "Shows",
title: NSLocalizedString("Shows", comment: ""),
value: "\(totalShows)",
icon: "tv.fill"
)
@ -546,7 +546,7 @@ struct DownloadSummaryCard: View {
Divider().frame(height: 32)
SummaryItem(
title: "Episodes",
title: NSLocalizedString("Episodes", comment: ""),
value: "\(totalEpisodes)",
icon: "play.rectangle.fill"
)
@ -559,7 +559,7 @@ struct DownloadSummaryCard: View {
let sizeUnit = components.dropFirst().first.map(String.init) ?? ""
SummaryItem(
title: "Size (\(sizeUnit))",
title: String(format: NSLocalizedString("Size (%@)", comment: ""), sizeUnit),
value: sizeValue,
icon: "internaldrive.fill"
)
@ -630,7 +630,7 @@ struct DownloadedSection: View {
HStack {
Image(systemName: "folder.fill")
.foregroundColor(.accentColor)
Text("Downloaded Shows".uppercased())
Text(NSLocalizedString("Downloaded Shows", comment: "").uppercased())
.font(.footnote)
.fontWeight(.medium)
.foregroundColor(.secondary)
@ -715,7 +715,7 @@ struct EnhancedActiveDownloadCard: View {
VStack(spacing: 6) {
HStack {
if download.queueStatus == .queued {
Text("Queued")
Text(NSLocalizedString("Queued", comment: ""))
.font(.caption)
.fontWeight(.medium)
.foregroundStyle(.orange)
@ -797,11 +797,11 @@ struct EnhancedActiveDownloadCard: View {
private var statusText: String {
if download.queueStatus == .queued {
return "Queued"
return NSLocalizedString("Queued", comment: "")
} else if taskState == .running {
return "Downloading"
return NSLocalizedString("Downloading", comment: "")
} else {
return "Paused"
return NSLocalizedString("Paused", comment: "")
}
}
@ -1026,7 +1026,7 @@ struct EnhancedShowEpisodesView: View {
HStack {
Image(systemName: "list.bullet.rectangle")
.foregroundColor(.accentColor)
Text("Episodes".uppercased())
Text(NSLocalizedString("Episodes", comment: "").uppercased())
.font(.footnote)
.fontWeight(.medium)
.foregroundColor(.secondary)
@ -1051,7 +1051,7 @@ struct EnhancedShowEpisodesView: View {
} label: {
HStack(spacing: 4) {
Image(systemName: episodeSortOption.systemImage)
Text("Sort")
Text(NSLocalizedString("Sort", comment: ""))
}
.font(.subheadline)
.foregroundColor(.accentColor)
@ -1062,7 +1062,7 @@ struct EnhancedShowEpisodesView: View {
}) {
HStack(spacing: 4) {
Image(systemName: "trash")
Text("Delete All")
Text(NSLocalizedString("Delete All", comment: ""))
}
.font(.subheadline)
.foregroundColor(.red)
@ -1073,7 +1073,7 @@ struct EnhancedShowEpisodesView: View {
// Episodes List
if group.assets.isEmpty {
Text("No episodes available")
Text(NSLocalizedString("No episodes available", comment: ""))
.foregroundColor(.secondary)
.italic()
.padding(40)
@ -1086,7 +1086,7 @@ struct EnhancedShowEpisodesView: View {
)
.contextMenu {
Button(action: { onPlay(asset) }) {
Label("Play", systemImage: "play.fill")
Label(NSLocalizedString("Play", comment: ""), systemImage: "play.fill")
}
.disabled(!asset.fileExists)
@ -1094,7 +1094,7 @@ struct EnhancedShowEpisodesView: View {
assetToDelete = asset
showDeleteAlert = true
}) {
Label("Delete", systemImage: "trash")
Label(NSLocalizedString("Delete", comment: ""), systemImage: "trash")
}
}
.onTapGesture {
@ -1107,27 +1107,27 @@ struct EnhancedShowEpisodesView: View {
}
.padding(.vertical)
}
.navigationTitle("Episodes")
.navigationTitle(NSLocalizedString("Episodes", comment: ""))
.navigationBarTitleDisplayMode(.inline)
.alert("Delete Episode", isPresented: $showDeleteAlert) {
Button("Cancel", role: .cancel) { }
Button("Delete", role: .destructive) {
.alert(NSLocalizedString("Delete Episode", comment: ""), isPresented: $showDeleteAlert) {
Button(NSLocalizedString("Cancel", comment: ""), role: .cancel) { }
Button(NSLocalizedString("Delete", comment: ""), role: .destructive) {
if let asset = assetToDelete {
onDelete(asset)
}
}
} message: {
if let asset = assetToDelete {
Text("Are you sure you want to delete '\(asset.episodeDisplayName)'?")
Text(String(format: NSLocalizedString("Are you sure you want to delete '%@'?", comment: ""), asset.episodeDisplayName))
}
}
.alert("Delete All Episodes", isPresented: $showDeleteAllAlert) {
Button("Cancel", role: .cancel) { }
Button("Delete All", role: .destructive) {
.alert(NSLocalizedString("Delete All Episodes", comment: ""), isPresented: $showDeleteAllAlert) {
Button(NSLocalizedString("Cancel", comment: ""), role: .cancel) { }
Button(NSLocalizedString("Delete All", comment: ""), role: .destructive) {
deleteAllAssets()
}
} message: {
Text("Are you sure you want to delete all \(group.assetCount) episodes in '\(group.title)'?")
Text(String(format: NSLocalizedString("Are you sure you want to delete all %d episodes in '%@'?", comment: ""), group.assetCount, group.title))
}
}

View file

@ -126,24 +126,33 @@ struct MediaInfoView: View {
if episodeLinks.count == 1 {
if let _ = unfinished {
return "Continue Watching"
return NSLocalizedString("Continue Watching", comment: "")
}
return "Start Watching"
return NSLocalizedString("Start Watching", comment: "")
}
if let finishedIndex = finished, finishedIndex < episodeLinks.count - 1 {
let nextEp = episodeLinks[finishedIndex + 1]
return "Start Watching Episode \(nextEp.number)"
return String(format: NSLocalizedString("Start Watching Episode %d", comment: ""), nextEp.number)
}
if let unfinishedIndex = unfinished {
let currentEp = episodeLinks[unfinishedIndex]
return "Continue Watching Episode \(currentEp.number)"
return String(format: NSLocalizedString("Continue Watching Episode %d", comment: ""), currentEp.number)
}
return "Start Watching"
return NSLocalizedString("Start Watching", comment: "")
}
private var singleEpisodeWatchText: String {
if let ep = episodeLinks.first {
let lastPlayedTime = UserDefaults.standard.double(forKey: "lastPlayedTime_\(ep.href)")
let totalTime = UserDefaults.standard.double(forKey: "totalTime_\(ep.href)")
let progress = totalTime > 0 ? lastPlayedTime / totalTime : 0
return progress <= 0.9 ? NSLocalizedString("Mark watched", comment: "") : NSLocalizedString("Reset progress", comment: "")
}
return NSLocalizedString("Mark watched", comment: "")
}
var body: some View {
ZStack {
@ -336,7 +345,7 @@ struct MediaInfoView: View {
.lineLimit(showFullSynopsis ? nil : 3)
.animation(nil, value: showFullSynopsis)
Text(showFullSynopsis ? "LESS" : "MORE")
Text(showFullSynopsis ? NSLocalizedString("LESS", comment: "") : NSLocalizedString("MORE", comment: ""))
.font(.system(size: 16, weight: .bold))
.foregroundColor(.accentColor)
.animation(.easeInOut(duration: 0.3), value: showFullSynopsis)
@ -405,7 +414,7 @@ struct MediaInfoView: View {
HStack(spacing: 4) {
Image(systemName: "arrow.down.circle")
.foregroundColor(.primary)
Text("Download")
Text(NSLocalizedString("Download", comment: ""))
.font(.system(size: 14, weight: .medium))
.foregroundColor(.primary)
}
@ -420,13 +429,13 @@ struct MediaInfoView: View {
}
VStack(spacing: 4) {
Text("Why am I not seeing any episodes?")
Text(NSLocalizedString("Why am I not seeing any episodes?", comment: ""))
.font(.caption)
.bold()
.foregroundColor(.gray)
.frame(maxWidth: .infinity, alignment: .leading)
Text("The module provided only a single episode, this is most likely a movie, so we decided to make separate screens for these cases.")
Text(NSLocalizedString("The module provided only a single episode, this is most likely a movie, so we decided to make separate screens for these cases.", comment: ""))
.font(.caption)
.foregroundColor(.gray)
.multilineTextAlignment(.leading)
@ -451,16 +460,6 @@ struct MediaInfoView: View {
return "checkmark.circle"
}
private var singleEpisodeWatchText: String {
if let ep = episodeLinks.first {
let lastPlayedTime = UserDefaults.standard.double(forKey: "lastPlayedTime_\(ep.href)")
let totalTime = UserDefaults.standard.double(forKey: "totalTime_\(ep.href)")
let progress = totalTime > 0 ? lastPlayedTime / totalTime : 0
return progress <= 0.9 ? "Mark watched" : "Reset progress"
}
return "Mark watched"
}
@ViewBuilder
private var episodesSection: some View {
if episodeLinks.count != 1 {
@ -474,7 +473,7 @@ struct MediaInfoView: View {
@ViewBuilder
private var episodesSectionHeader: some View {
HStack {
Text("Episodes")
Text(NSLocalizedString("Episodes", comment: ""))
.font(.system(size: 22, weight: .bold))
.foregroundColor(.primary)
@ -524,7 +523,7 @@ struct MediaInfoView: View {
Menu {
ForEach(0..<seasons.count, id: \.self) { index in
Button(action: { selectedSeason = index }) {
Text("Season \(index + 1)")
Text(String(format: NSLocalizedString("Season %d", comment: ""), index + 1))
}
}
} label: {
@ -612,12 +611,12 @@ struct MediaInfoView: View {
.font(.system(size: 48))
.foregroundColor(.secondary)
Text("No Episodes Available")
Text(NSLocalizedString("No Episodes Available", comment: ""))
.font(.title2)
.fontWeight(.semibold)
.foregroundColor(.primary)
Text("Episodes might not be available yet or there could be an issue with the source.")
Text(NSLocalizedString("Episodes might not be available yet or there could be an issue with the source.", comment: ""))
.font(.body)
.lineLimit(0)
.foregroundColor(.secondary)

View file

@ -153,13 +153,13 @@ struct SettingsViewData: View {
return ScrollView {
VStack(spacing: 24) {
SettingsSection(
title: "App Storage",
footer: "The app cache helps the app load images faster.\n\nClearing the Documents folder will delete all downloaded modules.\n\nDo not erase App Data unless you understand the consequences — it may cause the app to malfunction."
title: NSLocalizedString("App Storage", comment: ""),
footer: NSLocalizedString("The app cache helps the app load images faster.\n\nClearing the Documents folder will delete all downloaded modules.\n\nDo not erase App Data unless you understand the consequences — it may cause the app to malfunction.", comment: "")
) {
VStack(spacing: 0) {
SettingsButtonRow(
icon: "trash",
title: "Remove All Cache",
title: NSLocalizedString("Remove All Cache", comment: ""),
subtitle: cacheSizeText,
action: {
activeAlert = .clearCache
@ -171,7 +171,7 @@ struct SettingsViewData: View {
SettingsButtonRow(
icon: "film",
title: "Remove Downloads",
title: NSLocalizedString("Remove Downloads", comment: ""),
subtitle: formatSize(downloadsSize),
action: {
activeAlert = .removeDownloads
@ -183,7 +183,7 @@ struct SettingsViewData: View {
SettingsButtonRow(
icon: "doc.text",
title: "Remove All Documents",
title: NSLocalizedString("Remove All Documents", comment: ""),
subtitle: formatSize(documentsSize),
action: {
activeAlert = .removeDocs
@ -195,7 +195,7 @@ struct SettingsViewData: View {
SettingsButtonRow(
icon: "exclamationmark.triangle",
title: "Erase all App Data",
title: NSLocalizedString("Erase all App Data", comment: ""),
action: {
activeAlert = .eraseData
showAlert = true
@ -205,7 +205,7 @@ struct SettingsViewData: View {
}
}
.scrollViewBottomPadding()
.navigationTitle("App Data")
.navigationTitle(NSLocalizedString("App Data", comment: ""))
.onAppear {
calculateCacheSize()
updateSizes()
@ -215,36 +215,36 @@ struct SettingsViewData: View {
switch activeAlert {
case .eraseData:
return Alert(
title: Text("Erase App Data"),
message: Text("Are you sure you want to erase all app data? This action cannot be undone."),
primaryButton: .destructive(Text("Erase")) {
title: Text(NSLocalizedString("Erase App Data", comment: "")),
message: Text(NSLocalizedString("Are you sure you want to erase all app data? This action cannot be undone.", comment: "")),
primaryButton: .destructive(Text(NSLocalizedString("Erase", comment: ""))) {
eraseAppData()
},
secondaryButton: .cancel()
)
case .removeDocs:
return Alert(
title: Text("Remove Documents"),
message: Text("Are you sure you want to remove all files in the Documents folder? This will remove all modules."),
primaryButton: .destructive(Text("Remove")) {
title: Text(NSLocalizedString("Remove Documents", comment: "")),
message: Text(NSLocalizedString("Are you sure you want to remove all files in the Documents folder? This will remove all modules.", comment: "")),
primaryButton: .destructive(Text(NSLocalizedString("Remove", comment: ""))) {
removeAllFilesInDocuments()
},
secondaryButton: .cancel()
)
case .removeDownloads:
return Alert(
title: Text("Remove Downloaded Media"),
message: Text("Are you sure you want to remove all downloaded media files (.mov, .mp4, .pkg)? This action cannot be undone."),
primaryButton: .destructive(Text("Remove")) {
title: Text(NSLocalizedString("Remove Downloaded Media", comment: "")),
message: Text(NSLocalizedString("Are you sure you want to remove all downloaded media files (.mov, .mp4, .pkg)? This action cannot be undone.", comment: "")),
primaryButton: .destructive(Text(NSLocalizedString("Remove", comment: ""))) {
removeDownloadedMedia()
},
secondaryButton: .cancel()
)
case .clearCache:
return Alert(
title: Text("Clear Cache"),
message: Text("Are you sure you want to clear all cached data? This will help free up storage space."),
primaryButton: .destructive(Text("Clear")) {
title: Text(NSLocalizedString("Clear Cache", comment: "")),
message: Text(NSLocalizedString("Are you sure you want to clear all cached data? This will help free up storage space.", comment: "")),
primaryButton: .destructive(Text(NSLocalizedString("Clear", comment: ""))) {
clearAllCaches()
},
secondaryButton: .cancel()
@ -252,6 +252,7 @@ struct SettingsViewData: View {
}
}
}
}
func calculateCacheSize() {
isCalculatingSize = true
@ -429,5 +430,5 @@ struct SettingsViewData: View {
formatter.countStyle = .file
return formatter.string(fromByteCount: bytes)
}
}
}

View file

@ -168,20 +168,21 @@ struct SettingsViewGeneral: View {
private let TMDBimageWidhtList = ["300", "500", "780", "1280", "original"]
private let sortOrderOptions = ["Ascending", "Descending"]
@EnvironmentObject var settings: Settings
@State private var showRestartAlert = false
var body: some View {
ScrollView {
VStack(spacing: 24) {
SettingsSection(title: "Interface") {
SettingsSection(title: NSLocalizedString("Interface", comment: "")) {
SettingsPickerRow(
icon: "paintbrush",
title: "Appearance",
title: NSLocalizedString("Appearance", comment: ""),
options: [Appearance.system, .light, .dark],
optionToString: { appearance in
switch appearance {
case .system: return "System"
case .light: return "Light"
case .dark: return "Dark"
case .system: return NSLocalizedString("System", comment: "")
case .light: return NSLocalizedString("Light", comment: "")
case .dark: return NSLocalizedString("Dark", comment: "")
}
},
selection: $settings.selectedAppearance
@ -189,19 +190,32 @@ struct SettingsViewGeneral: View {
SettingsToggleRow(
icon: "wand.and.rays.inverse",
title: "Hide Splash Screen",
title: NSLocalizedString("Hide Splash Screen", comment: ""),
isOn: $hideSplashScreenEnable,
showDivider: false
)
}
SettingsSection(title: NSLocalizedString("Language", comment: "")) {
SettingsPickerRow(
icon: "globe",
title: NSLocalizedString("App Language", comment: ""),
options: ["English", "Dutch"],
optionToString: { $0 },
selection: $settings.selectedLanguage
)
.onChange(of: settings.selectedLanguage) { _ in
showRestartAlert = true
}
}
SettingsSection(
title: "Media View",
footer: "The episode range controls how many episodes appear on each page. Episodes are grouped into sets (like 125, 2650, and so on), allowing you to navigate through them more easily.\n\nFor episode metadata, it refers to the episode thumbnail and title, since sometimes it can contain spoilers."
title: NSLocalizedString("Media View", comment: ""),
footer: NSLocalizedString("The episode range controls how many episodes appear on each page. Episodes are grouped into sets (like 125, 2650, and so on), allowing you to navigate through them more easily.\n\nFor episode metadata, it refers to the episode thumbnail and title, since sometimes it can contain spoilers.", comment: "")
) {
SettingsPickerRow(
icon: "list.number",
title: "Episodes Range",
title: NSLocalizedString("Episodes Range", comment: ""),
options: [25, 50, 75, 100],
optionToString: { "\($0)" },
selection: $episodeChunkSize
@ -209,7 +223,7 @@ struct SettingsViewGeneral: View {
SettingsToggleRow(
icon: "info.circle",
title: "Fetch Episode metadata",
title: NSLocalizedString("Fetch Episode metadata", comment: ""),
isOn: $fetchEpisodeMetadata
)
@ -230,10 +244,43 @@ struct SettingsViewGeneral: View {
SettingsSection(
title: "Media Grid Layout",
footer: "Adjust the number of media items per row in portrait and landscape modes."
) {
SettingsPickerRow(
icon: "server.rack",
title: NSLocalizedString("Metadata Provider", comment: ""),
options: metadataProvidersList,
optionToString: { $0 },
selection: $metadataProviders,
showDivider: true
)
SettingsPickerRow(
icon: "square.stack.3d.down.right",
title: NSLocalizedString("Thumbnails Width", comment: ""),
options: TMDBimageWidhtList,
optionToString: { $0 },
selection: $TMDBimageWidht,
showDivider: false
)
} else {
SettingsPickerRow(
icon: "server.rack",
title: NSLocalizedString("Metadata Provider", comment: ""),
options: metadataProvidersList,
optionToString: { $0 },
selection: $metadataProviders,
showDivider: false
)
}
}
SettingsSection(
title: NSLocalizedString("Media Grid Layout", comment: ""),
footer: NSLocalizedString("Adjust the number of media items per row in portrait and landscape modes.", comment: "")
) {
SettingsPickerRow(
icon: "rectangle.portrait",
title: "Portrait Columns",
title: NSLocalizedString("Portrait Columns", comment: ""),
options: UIDevice.current.userInterfaceIdiom == .pad ? Array(1...5) : Array(1...4),
optionToString: { "\($0)" },
selection: $mediaColumnsPortrait
@ -241,7 +288,7 @@ struct SettingsViewGeneral: View {
SettingsPickerRow(
icon: "rectangle",
title: "Landscape Columns",
title: NSLocalizedString("Landscape Columns", comment: ""),
options: UIDevice.current.userInterfaceIdiom == .pad ? Array(2...8) : Array(2...5),
optionToString: { "\($0)" },
selection: $mediaColumnsLandscape,
@ -250,33 +297,40 @@ struct SettingsViewGeneral: View {
}
SettingsSection(
title: "Modules",
footer: "Note that the modules will be replaced only if there is a different version string inside the JSON file."
title: NSLocalizedString("Modules", comment: ""),
footer: NSLocalizedString("Note that the modules will be replaced only if there is a different version string inside the JSON file.", comment: "")
) {
SettingsToggleRow(
icon: "arrow.clockwise",
title: "Refresh Modules on Launch",
title: NSLocalizedString("Refresh Modules on Launch", comment: ""),
isOn: $refreshModulesOnLaunch,
showDivider: false
)
}
SettingsSection(
title: "Advanced",
footer: "Anonymous data is collected to improve the app. No personal information is collected. This can be disabled at any time."
title: NSLocalizedString("Advanced", comment: ""),
footer: NSLocalizedString("Anonymous data is collected to improve the app. No personal information is collected. This can be disabled at any time.", comment: "")
) {
SettingsToggleRow(
icon: "chart.bar",
title: "Enable Analytics",
title: NSLocalizedString("Enable Analytics", comment: ""),
isOn: $analyticsEnabled,
showDivider: false
)
}
}
.padding(.vertical, 20)
}
.navigationTitle("General")
.scrollViewBottomPadding()
}
.navigationTitle(NSLocalizedString("General", comment: ""))
.scrollViewBottomPadding()
.alert(isPresented: $showRestartAlert) {
Alert(
title: Text(NSLocalizedString("Restart Required", comment: "")),
message: Text(NSLocalizedString("Please restart the app to apply the language change.", comment: "")),
dismissButton: .default(Text("OK"))
)
}
}
}

View file

@ -76,12 +76,12 @@ struct SettingsViewLogger: View {
var body: some View {
ScrollView {
VStack(spacing: 24) {
SettingsSection(title: "Logs") {
SettingsSection(title: NSLocalizedString("Logs", comment: "")) {
if isLoading {
HStack {
ProgressView()
.scaleEffect(0.8)
Text("Loading logs...")
Text(NSLocalizedString("Loading logs...", comment: ""))
.font(.footnote)
.foregroundColor(.secondary)
}
@ -99,7 +99,7 @@ struct SettingsViewLogger: View {
Button(action: {
showFullLogs = true
}) {
Text("Show More (\(logs.count - displayCharacterLimit) more characters)")
Text(NSLocalizedString("Show More (%lld more characters)", comment: "").replacingOccurrences(of: "%lld", with: "\(logs.count - displayCharacterLimit)"))
.font(.footnote)
.foregroundColor(.accentColor)
}
@ -113,7 +113,7 @@ struct SettingsViewLogger: View {
}
.padding(.vertical, 20)
}
.navigationTitle("Logs")
.navigationTitle(NSLocalizedString("Logs", comment: ""))
.onAppear {
loadLogsAsync()
}
@ -123,14 +123,14 @@ struct SettingsViewLogger: View {
Menu {
Button(action: {
UIPasteboard.general.string = logs
DropManager.shared.showDrop(title: "Copied to Clipboard", subtitle: "", duration: 1.0, icon: UIImage(systemName: "doc.on.clipboard.fill"))
DropManager.shared.showDrop(title: NSLocalizedString("Copied to Clipboard", comment: ""), subtitle: "", duration: 1.0, icon: UIImage(systemName: "doc.on.clipboard.fill"))
}) {
Label("Copy to Clipboard", systemImage: "doc.on.doc")
Label(NSLocalizedString("Copy to Clipboard", comment: ""), systemImage: "doc.on.doc")
}
Button(role: .destructive, action: {
clearLogsAsync()
}) {
Label("Clear Logs", systemImage: "trash")
Label(NSLocalizedString("Clear Logs", comment: ""), systemImage: "trash")
}
} label: {
Image(systemName: "ellipsis.circle")

View file

@ -115,11 +115,11 @@ class LogFilterViewModel: ObservableObject {
private let userDefaultsKey = "LogFilterStates"
private let hardcodedFilters: [(type: String, description: String, defaultState: Bool)] = [
("General", "General events and activities.", true),
("Stream", "Streaming and video playback.", true),
("Error", "Errors and critical issues.", true),
("Debug", "Debugging and troubleshooting.", false),
("Download", "HLS video downloading.", true),
(NSLocalizedString("General", comment: ""), NSLocalizedString("General events and activities.", comment: ""), true),
(NSLocalizedString("Stream", comment: ""), NSLocalizedString("Streaming and video playback.", comment: ""), true),
(NSLocalizedString("Error", comment: ""), NSLocalizedString("Errors and critical issues.", comment: ""), true),
(NSLocalizedString("Debug", comment: ""), NSLocalizedString("Debugging and troubleshooting.", comment: ""), false),
(NSLocalizedString("Download", comment: ""), NSLocalizedString("HLS video downloading.", comment: ""), true),
("HTMLStrings", "", false)
]
@ -179,7 +179,7 @@ struct SettingsViewLoggerFilter: View {
var body: some View {
ScrollView {
VStack(spacing: 24) {
SettingsSection(title: "Log Types") {
SettingsSection(title: NSLocalizedString("Log Types", comment: "")) {
ForEach($viewModel.filters) { $filter in
SettingsToggleRow(
icon: iconForFilter(filter.type),
@ -192,6 +192,6 @@ struct SettingsViewLoggerFilter: View {
}
.padding(.vertical, 20)
}
.navigationTitle("Log Filters")
.navigationTitle(NSLocalizedString("Log Filters", comment: ""))
}
}

View file

@ -119,16 +119,16 @@ fileprivate struct ModuleListItemView: View {
.contextMenu {
Button(action: {
UIPasteboard.general.string = module.metadataUrl
DropManager.shared.showDrop(title: "Copied to Clipboard", subtitle: "", duration: 1.0, icon: UIImage(systemName: "doc.on.clipboard.fill"))
DropManager.shared.showDrop(title: NSLocalizedString("Copied to Clipboard", comment: ""), subtitle: "", duration: 1.0, icon: UIImage(systemName: "doc.on.clipboard.fill"))
}) {
Label("Copy URL", systemImage: "doc.on.doc")
Label(NSLocalizedString("Copy URL", comment: ""), systemImage: "doc.on.doc")
}
Button(role: .destructive) {
if selectedModuleId != module.id.uuidString {
onDelete()
}
} label: {
Label("Delete", systemImage: "trash")
Label(NSLocalizedString("Delete", comment: ""), systemImage: "trash")
}
.disabled(selectedModuleId == module.id.uuidString)
}
@ -137,7 +137,7 @@ fileprivate struct ModuleListItemView: View {
Button(role: .destructive) {
onDelete()
} label: {
Label("Delete", systemImage: "trash")
Label(NSLocalizedString("Delete", comment: ""), systemImage: "trash")
}
}
}
@ -163,25 +163,25 @@ struct SettingsViewModule: View {
ScrollView {
VStack(spacing: 24) {
if moduleManager.modules.isEmpty {
SettingsSection(title: "Modules") {
SettingsSection(title: NSLocalizedString("Modules", comment: "")) {
VStack(spacing: 16) {
Image(systemName: "plus.app")
.font(.largeTitle)
.foregroundColor(.secondary)
Text("No Modules")
Text(NSLocalizedString("No Modules", comment: ""))
.font(.headline)
if didReceiveDefaultPageLink {
NavigationLink(destination: CommunityLibraryView()
.environmentObject(moduleManager)) {
Text("Check out some community modules here!")
Text(NSLocalizedString("Check out some community modules here!", comment: ""))
.font(.caption)
.foregroundColor(.accentColor)
.frame(maxWidth: .infinity)
}
.buttonStyle(PlainButtonStyle())
} else {
Text("Click the plus button to add a module!")
Text(NSLocalizedString("Click the plus button to add a module!", comment: ""))
.font(.caption)
.foregroundColor(.secondary)
.frame(maxWidth: .infinity)
@ -191,14 +191,14 @@ struct SettingsViewModule: View {
.frame(maxWidth: .infinity)
}
} else {
SettingsSection(title: "Installed Modules") {
SettingsSection(title: NSLocalizedString("Installed Modules", comment: "")) {
ForEach(moduleManager.modules) { module in
ModuleListItemView(
module: module,
selectedModuleId: selectedModuleId,
onDelete: {
moduleManager.deleteModule(module)
DropManager.shared.showDrop(title: "Module Removed", subtitle: "", duration: 1.0, icon: UIImage(systemName: "trash"))
DropManager.shared.showDrop(title: NSLocalizedString("Module Removed", comment: ""), subtitle: "", duration: 1.0, icon: UIImage(systemName: "trash"))
},
onSelect: {
selectedModuleId = module.id.uuidString
@ -216,7 +216,7 @@ struct SettingsViewModule: View {
.padding(.vertical, 20)
}
.scrollViewBottomPadding()
.navigationTitle("Modules")
.navigationTitle(NSLocalizedString("Modules", comment: ""))
.navigationBarItems(trailing:
HStack(spacing: 16) {
if didReceiveDefaultPageLink {
@ -228,7 +228,7 @@ struct SettingsViewModule: View {
.frame(width: 20, height: 20)
.padding(5)
}
.accessibilityLabel("Open Community Library")
.accessibilityLabel(NSLocalizedString("Open Community Library", comment: ""))
}
Button(action: {
@ -239,7 +239,7 @@ struct SettingsViewModule: View {
.frame(width: 20, height: 20)
.padding(5)
}
.accessibilityLabel("Add Module")
.accessibilityLabel(NSLocalizedString("Add Module", comment: ""))
}
)
.background(

View file

@ -215,12 +215,12 @@ struct SettingsViewPlayer: View {
ScrollView {
VStack(spacing: 24) {
SettingsSection(
title: "Media Player",
footer: "Some features are limited to the Sora and Default player, such as ForceLandscape, holdSpeed and custom time skip increments."
title: NSLocalizedString("Media Player", comment: ""),
footer: NSLocalizedString("Some features are limited to the Sora and Default player, such as ForceLandscape, holdSpeed and custom time skip increments.", comment: "")
) {
SettingsPickerRow(
icon: "play.circle",
title: "Media Player",
title: NSLocalizedString("Media Player", comment: ""),
options: mediaPlayers,
optionToString: { $0 },
selection: $externalPlayer
@ -228,35 +228,35 @@ struct SettingsViewPlayer: View {
SettingsToggleRow(
icon: "rotate.right",
title: "Force Landscape",
title: NSLocalizedString("Force Landscape", comment: ""),
isOn: $isAlwaysLandscape
)
SettingsToggleRow(
icon: "hand.tap",
title: "Two Finger Hold for Pause",
title: NSLocalizedString("Two Finger Hold for Pause", comment: ""),
isOn: $holdForPauseEnabled,
showDivider: true
)
SettingsToggleRow(
icon: "pip",
title: "Show PiP Button",
title: NSLocalizedString("Show PiP Button", comment: ""),
isOn: $pipButtonVisible,
showDivider: false
)
}
SettingsSection(title: "Speed Settings") {
SettingsSection(title: NSLocalizedString("Speed Settings", comment: "")) {
SettingsToggleRow(
icon: "speedometer",
title: "Remember Playback speed",
title: NSLocalizedString("Remember Playback speed", comment: ""),
isOn: $isRememberPlaySpeed
)
SettingsStepperRow(
icon: "forward.fill",
title: "Hold Speed",
title: NSLocalizedString("Hold Speed", comment: ""),
value: $holdSpeedPlayer,
range: 0.25...2.5,
step: 0.25,
@ -264,14 +264,13 @@ struct SettingsViewPlayer: View {
showDivider: false
)
}
SettingsSection(
title: "Video Quality Preferences",
footer: "Choose preferred video resolution for WiFi and cellular connections. Higher resolutions use more data but provide better quality. If the exact quality isn't available, the closest option will be selected automatically.\n\nNote: Not all video sources and players support quality selection. This feature works best with HLS streams using the Sora player."
title: String(localized: "Video Quality Preferences"),
footer: String(localized: "Choose preferred video resolution for WiFi and cellular connections. Higher resolutions use more data but provide better quality. If the exact quality isn't available, the closest option will be selected automatically.\n\nNote: Not all video sources and players support quality selection. This feature works best with HLS streams using the Sora player.")
) {
SettingsPickerRow(
icon: "wifi",
title: "WiFi Quality",
title: String(localized: "WiFi Quality"),
options: qualityOptions,
optionToString: { $0 },
selection: $wifiQuality
@ -279,7 +278,7 @@ struct SettingsViewPlayer: View {
SettingsPickerRow(
icon: "antenna.radiowaves.left.and.right",
title: "Cellular Quality",
title: String(localized: "Cellular Quality"),
options: qualityOptions,
optionToString: { $0 },
selection: $cellularQuality,
@ -287,8 +286,8 @@ struct SettingsViewPlayer: View {
)
}
SettingsSection(title: "Progress bar Marker Color") {
ColorPicker("Segments Color", selection: Binding(
SettingsSection(title: NSLocalizedString("Progress bar Marker Color", comment: "")) {
ColorPicker(NSLocalizedString("Segments Color", comment: ""), selection: Binding(
get: {
if let data = UserDefaults.standard.data(forKey: "segmentsColorData"),
let uiColor = try? NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as? UIColor {
@ -311,12 +310,12 @@ struct SettingsViewPlayer: View {
}
SettingsSection(
title: "Skip Settings",
footer: "Double tapping the screen on it's sides will skip with the short tap setting."
title: NSLocalizedString("Skip Settings", comment: ""),
footer: NSLocalizedString("Double tapping the screen on it's sides will skip with the short tap setting.", comment: "")
) {
SettingsStepperRow(
icon: "goforward",
title: "Tap Skip",
title: NSLocalizedString("Tap Skip", comment: ""),
value: $skipIncrement,
range: 5...300,
step: 5,
@ -325,7 +324,7 @@ struct SettingsViewPlayer: View {
SettingsStepperRow(
icon: "goforward.plus",
title: "Long press Skip",
title: NSLocalizedString("Long press Skip", comment: ""),
value: $skipIncrementHold,
range: 5...300,
step: 5,
@ -334,19 +333,19 @@ struct SettingsViewPlayer: View {
SettingsToggleRow(
icon: "hand.tap.fill",
title: "Double Tap to Seek",
title: NSLocalizedString("Double Tap to Seek", comment: ""),
isOn: $doubleTapSeekEnabled
)
SettingsToggleRow(
icon: "forward.end",
title: "Show Skip 85s Button",
title: NSLocalizedString("Show Skip 85s Button", comment: ""),
isOn: $skip85Visible
)
SettingsToggleRow(
icon: "forward.frame",
title: "Show Skip Intro / Outro Buttons",
title: NSLocalizedString("Show Skip Intro / Outro Buttons", comment: ""),
isOn: $skipIntroOutroVisible,
showDivider: false
)
@ -357,7 +356,7 @@ struct SettingsViewPlayer: View {
.padding(.vertical, 20)
}
.scrollViewBottomPadding()
.navigationTitle("Player")
.navigationTitle(NSLocalizedString("Player", comment: ""))
}
}
@ -374,10 +373,10 @@ struct SubtitleSettingsSection: View {
private let shadowOptions = [0, 1, 3, 6]
var body: some View {
SettingsSection(title: "Subtitle Settings") {
SettingsSection(title: NSLocalizedString("Subtitle Settings", comment: "")) {
SettingsToggleRow(
icon: "captions.bubble",
title: "Enable Subtitles",
title: NSLocalizedString("Enable Subtitles", comment: ""),
isOn: $subtitlesEnabled,
showDivider: false
)
@ -389,7 +388,7 @@ struct SubtitleSettingsSection: View {
SettingsPickerRow(
icon: "paintbrush",
title: "Subtitle Color",
title: NSLocalizedString("Subtitle Color", comment: ""),
options: colors,
optionToString: { $0.capitalized },
selection: $foregroundColor
@ -402,7 +401,7 @@ struct SubtitleSettingsSection: View {
SettingsPickerRow(
icon: "shadow",
title: "Shadow",
title: NSLocalizedString("Shadow", comment: ""),
options: shadowOptions,
optionToString: { "\($0)" },
selection: Binding(
@ -418,7 +417,7 @@ struct SubtitleSettingsSection: View {
SettingsToggleRow(
icon: "rectangle.fill",
title: "Background Enabled",
title: NSLocalizedString("Background Enabled", comment: ""),
isOn: $backgroundEnabled
)
.onChange(of: backgroundEnabled) { newValue in
@ -429,7 +428,7 @@ struct SubtitleSettingsSection: View {
SettingsStepperRow(
icon: "textformat.size",
title: "Font Size",
title: NSLocalizedString("Font Size", comment: ""),
value: $fontSize,
range: 12...36,
step: 1
@ -442,7 +441,7 @@ struct SubtitleSettingsSection: View {
SettingsStepperRow(
icon: "arrow.up.and.down",
title: "Bottom Padding",
title: NSLocalizedString("Bottom Padding", comment: ""),
value: $bottomPadding,
range: 0...50,
step: 1,

View file

@ -117,7 +117,7 @@ struct SettingsViewTrackers: View {
var body: some View {
ScrollView {
VStack(spacing: 24) {
SettingsSection(title: "AniList") {
SettingsSection(title: NSLocalizedString("AniList", comment: "")) {
VStack(spacing: 0) {
HStack(alignment: .center, spacing: 10) {
LazyImage(url: URL(string: "https://raw.githubusercontent.com/cranci1/Ryu/2f10226aa087154974a70c1ec78aa83a47daced9/Ryu/Assets.xcassets/Listing/Anilist.imageset/anilist.png")) { state in
@ -137,18 +137,17 @@ struct SettingsViewTrackers: View {
}
VStack(alignment: .leading, spacing: 4) {
Text("AniList.co")
Text(NSLocalizedString("AniList.co", comment: ""))
.font(.title3)
.fontWeight(.semibold)
Group {
if isAnilistLoading {
ProgressView()
.scaleEffect(0.8)
.frame(height: 18)
} else if isAnilistLoggedIn {
HStack(spacing: 0) {
Text("Logged in as ")
Text(NSLocalizedString("Logged in as", comment: ""))
.font(.footnote)
.foregroundStyle(.gray)
Text(anilistUsername)
@ -158,13 +157,12 @@ struct SettingsViewTrackers: View {
}
.frame(height: 18)
} else {
Text(anilistStatus)
Text(NSLocalizedString("You are not logged in", comment: ""))
.font(.footnote)
.foregroundStyle(.gray)
.frame(height: 18)
}
}
}
.frame(height: 60, alignment: .center)
Spacer()
@ -179,7 +177,7 @@ struct SettingsViewTrackers: View {
SettingsToggleRow(
icon: "arrow.triangle.2.circlepath",
title: "Sync anime progress",
title: NSLocalizedString("Sync anime progress", comment: ""),
isOn: $isSendPushUpdates,
showDivider: false
)
@ -200,7 +198,7 @@ struct SettingsViewTrackers: View {
.frame(width: 24, height: 24)
.foregroundStyle(isAnilistLoggedIn ? .red : .accentColor)
Text(isAnilistLoggedIn ? "Log Out from AniList" : "Log In with AniList")
Text(isAnilistLoggedIn ? NSLocalizedString("Log Out from AniList", comment: "") : NSLocalizedString("Log In with AniList", comment: ""))
.foregroundStyle(isAnilistLoggedIn ? .red : .accentColor)
Spacer()
@ -212,7 +210,7 @@ struct SettingsViewTrackers: View {
}
}
SettingsSection(title: "Trakt") {
SettingsSection(title: NSLocalizedString("Trakt", comment: "")) {
VStack(spacing: 0) {
HStack(alignment: .center, spacing: 10) {
LazyImage(url: URL(string: "https://static-00.iconduck.com/assets.00/trakt-icon-2048x2048-2633ksxg.png")) { state in
@ -232,34 +230,32 @@ struct SettingsViewTrackers: View {
}
VStack(alignment: .leading, spacing: 4) {
Text("Trakt.tv")
Text(NSLocalizedString("Trakt.tv", comment: ""))
.font(.title3)
.fontWeight(.semibold)
Group {
if isTraktLoading {
ProgressView()
.scaleEffect(0.8)
.frame(height: 18)
} else if isTraktLoggedIn {
HStack(spacing: 0) {
Text("Logged in as ")
Text(NSLocalizedString("Logged in as", comment: ""))
.font(.footnote)
.foregroundStyle(.gray)
Text(traktUsername)
.font(.footnote)
.fontWeight(.medium)
.foregroundStyle(.primary)
.foregroundStyle(Color.accentColor)
}
.frame(height: 18)
} else {
Text(traktStatus)
Text(NSLocalizedString("You are not logged in", comment: ""))
.font(.footnote)
.foregroundStyle(.gray)
.frame(height: 18)
}
}
}
.frame(height: 60, alignment: .center)
Spacer()
@ -268,6 +264,18 @@ struct SettingsViewTrackers: View {
.padding(.vertical, 12)
.frame(height: 84)
if isTraktLoggedIn {
Divider()
.padding(.horizontal, 16)
SettingsToggleRow(
icon: "arrow.triangle.2.circlepath",
title: NSLocalizedString("Sync TV shows progress", comment: ""),
isOn: $isSendTraktUpdates,
showDivider: false
)
}
Divider()
.padding(.horizontal, 16)
@ -283,7 +291,7 @@ struct SettingsViewTrackers: View {
.frame(width: 24, height: 24)
.foregroundStyle(isTraktLoggedIn ? .red : .accentColor)
Text(isTraktLoggedIn ? "Log Out from Trakt" : "Log In with Trakt")
Text(isTraktLoggedIn ? NSLocalizedString("Log Out from Trakt", comment: "") : NSLocalizedString("Log In with Trakt", comment: ""))
.foregroundStyle(isTraktLoggedIn ? .red : .accentColor)
Spacer()
@ -296,14 +304,14 @@ struct SettingsViewTrackers: View {
}
SettingsSection(
title: "Info",
footer: "Sora and cranci1 are not affiliated with AniList or Trakt in any way.\n\nAlso note that progress updates may not be 100% accurate."
title: NSLocalizedString("Info", comment: ""),
footer: NSLocalizedString("Sora and cranci1 are not affiliated with AniList or Trakt in any way.\n\nAlso note that progress updates may not be 100% accurate.", comment: "")
) {}
}
.padding(.vertical, 20)
}
.scrollViewBottomPadding()
.navigationTitle("Trackers")
.navigationTitle(NSLocalizedString("Trackers", comment: ""))
.onAppear {
updateAniListStatus()
updateTraktStatus()

View file

@ -10,13 +10,13 @@ import NukeUI
fileprivate struct SettingsNavigationRow: View {
let icon: String
let title: String
let titleKey: String
let isExternal: Bool
let textColor: Color
init(icon: String, title: String, isExternal: Bool = false, textColor: Color = .primary) {
init(icon: String, titleKey: String, isExternal: Bool = false, textColor: Color = .primary) {
self.icon = icon
self.title = title
self.titleKey = titleKey
self.isExternal = isExternal
self.textColor = textColor
}
@ -27,7 +27,7 @@ fileprivate struct SettingsNavigationRow: View {
.frame(width: 24, height: 24)
.foregroundStyle(textColor)
Text(title)
Text(NSLocalizedString(titleKey, comment: ""))
.foregroundStyle(textColor)
Spacer()
@ -164,22 +164,22 @@ struct SettingsView: View {
VStack(spacing: 0) {
NavigationLink(destination: SettingsViewGeneral()) {
SettingsNavigationRow(icon: "gearshape", title: "General Settings")
SettingsNavigationRow(icon: "gearshape", titleKey: "General Preferences")
}
Divider().padding(.horizontal, 16)
NavigationLink(destination: SettingsViewPlayer()) {
SettingsNavigationRow(icon: "play.circle", title: "Player Settings")
SettingsNavigationRow(icon: "play.circle", titleKey: "Video Player")
}
Divider().padding(.horizontal, 16)
NavigationLink(destination: SettingsViewDownloads()) {
SettingsNavigationRow(icon: "arrow.down.circle", title: "Download Settings")
SettingsNavigationRow(icon: "arrow.down.circle", titleKey: "Download")
}
Divider().padding(.horizontal, 16)
NavigationLink(destination: SettingsViewTrackers()) {
SettingsNavigationRow(icon: "square.stack.3d.up", title: "Tracking Services")
SettingsNavigationRow(icon: "square.stack.3d.up", titleKey: "Trackers")
}
}
.background(.ultraThinMaterial)
@ -209,12 +209,12 @@ struct SettingsView: View {
VStack(spacing: 0) {
NavigationLink(destination: SettingsViewData()) {
SettingsNavigationRow(icon: "folder", title: "Data")
SettingsNavigationRow(icon: "folder", titleKey: "Data")
}
Divider().padding(.horizontal, 16)
NavigationLink(destination: SettingsViewLogger()) {
SettingsNavigationRow(icon: "doc.text", title: "Logs")
SettingsNavigationRow(icon: "doc.text", titleKey: "Logs")
}
}
.background(.ultraThinMaterial)
@ -237,21 +237,21 @@ struct SettingsView: View {
}
VStack(alignment: .leading, spacing: 4) {
Text("INFORMATION")
Text(NSLocalizedString("INFOS", comment: ""))
.font(.footnote)
.foregroundStyle(.gray)
.padding(.horizontal, 20)
VStack(spacing: 0) {
NavigationLink(destination: SettingsViewAbout()) {
SettingsNavigationRow(icon: "info.circle", title: "About Sora")
SettingsNavigationRow(icon: "info.circle", titleKey: "About Sora")
}
Divider().padding(.horizontal, 16)
Link(destination: URL(string: "https://github.com/cranci1/Sora")!) {
SettingsNavigationRow(
icon: "chevron.left.forwardslash.chevron.right",
title: "Sora GitHub Repository",
titleKey: "Sora GitHub Repository",
isExternal: true,
textColor: .gray
)
@ -261,7 +261,7 @@ struct SettingsView: View {
Link(destination: URL(string: "https://discord.gg/x7hppDWFDZ")!) {
SettingsNavigationRow(
icon: "bubble.left.and.bubble.right",
title: "Join Discord Community",
titleKey: "Join the Discord",
isExternal: true,
textColor: .gray
)
@ -271,7 +271,7 @@ struct SettingsView: View {
Link(destination: URL(string: "https://github.com/cranci1/Sora/issues")!) {
SettingsNavigationRow(
icon: "exclamationmark.circle",
title: "Report an Issue on GitHub",
titleKey: "Report an Issue",
isExternal: true,
textColor: .gray
)
@ -281,7 +281,7 @@ struct SettingsView: View {
Link(destination: URL(string: "https://github.com/cranci1/Sora/blob/dev/LICENSE")!) {
SettingsNavigationRow(
icon: "doc.text",
title: "License (GPLv3.0)",
titleKey: "License (GPLv3.0)",
isExternal: true,
textColor: .gray
)
@ -350,6 +350,12 @@ class Settings: ObservableObject {
updateAppearance()
}
}
@Published var selectedLanguage: String {
didSet {
UserDefaults.standard.set(selectedLanguage, forKey: "selectedLanguage")
updateLanguage()
}
}
init() {
self.accentColor = .primary
@ -359,7 +365,9 @@ class Settings: ObservableObject {
} else {
self.selectedAppearance = .system
}
self.selectedLanguage = UserDefaults.standard.string(forKey: "selectedLanguage") ?? "English"
updateAppearance()
updateLanguage()
}
func updateAccentColor(currentColorScheme: ColorScheme? = nil) {
@ -390,4 +398,10 @@ class Settings: ObservableObject {
windowScene.windows.first?.overrideUserInterfaceStyle = .dark
}
}
func updateLanguage() {
let languageCode = selectedLanguage == "Dutch" ? "nl" : "en"
UserDefaults.standard.set([languageCode], forKey: "AppleLanguages")
UserDefaults.standard.synchronize()
}
}