mirror of
https://github.com/kiwix/kiwix-apple.git
synced 2025-09-26 13:29:31 -04:00
Merge branch 'main' into ci-pr
This commit is contained in:
commit
a1e178ea28
@ -33,10 +33,10 @@ struct Kiwix: App {
|
||||
}.commands {
|
||||
SidebarCommands()
|
||||
CommandGroup(replacing: .importExport) {
|
||||
OpenFileButton(context: .command) { Text("Open...") }
|
||||
OpenFileButton(context: .command) { Text("Open...".localized) }
|
||||
}
|
||||
CommandGroup(replacing: .newItem) {
|
||||
Button("New Tab") {
|
||||
Button("New Tab".localized) {
|
||||
guard let currentWindow = NSApp.keyWindow,
|
||||
let controller = currentWindow.windowController else { return }
|
||||
controller.newWindowForTab(nil)
|
||||
@ -93,11 +93,11 @@ struct RootView: View {
|
||||
NavigationView {
|
||||
List(selection: $navigation.currentItem) {
|
||||
ForEach(primaryItems, id: \.self) { navigationItem in
|
||||
Label(navigationItem.name, systemImage: navigationItem.icon)
|
||||
Label(navigationItem.name.localized, systemImage: navigationItem.icon)
|
||||
}
|
||||
Section("Library") {
|
||||
Section("Library".localized) {
|
||||
ForEach(libraryItems, id: \.self) { navigationItem in
|
||||
Label(navigationItem.name, systemImage: navigationItem.icon)
|
||||
Label(navigationItem.name.localized, systemImage: navigationItem.icon)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -108,7 +108,7 @@ struct RootView: View {
|
||||
responder.tryToPerform(#selector(NSSplitViewController.toggleSidebar(_:)), with: nil)
|
||||
} label: {
|
||||
Image(systemName: "sidebar.leading")
|
||||
}.help("Show sidebar")
|
||||
}.help("Show sidebar".localized)
|
||||
}
|
||||
switch navigation.currentItem {
|
||||
case .reading:
|
||||
|
@ -86,7 +86,7 @@ class SidebarViewController: UICollectionViewController, NSFetchedResultsControl
|
||||
},
|
||||
menu: UIMenu(children: [
|
||||
UIAction(
|
||||
title: "Close This Tab",
|
||||
title: "Close This Tab".localized,
|
||||
image: UIImage(systemName: "xmark.square"),
|
||||
attributes: .destructive
|
||||
) { [unowned self] _ in
|
||||
@ -95,7 +95,7 @@ class SidebarViewController: UICollectionViewController, NSFetchedResultsControl
|
||||
splitViewController.navigationViewModel.deleteTab(tabID: tabID)
|
||||
},
|
||||
UIAction(
|
||||
title: "Close All Tabs",
|
||||
title: "Close All Tabs".localized,
|
||||
image: UIImage(systemName: "xmark.square.fill"),
|
||||
attributes: .destructive
|
||||
) { [unowned self] _ in
|
||||
@ -188,11 +188,11 @@ class SidebarViewController: UICollectionViewController, NSFetchedResultsControl
|
||||
switch section {
|
||||
case .tabs:
|
||||
var config = UIListContentConfiguration.sidebarHeader()
|
||||
config.text = "Tabs"
|
||||
config.text = "Tabs".localized
|
||||
headerView.contentConfiguration = config
|
||||
case .library:
|
||||
var config = UIListContentConfiguration.sidebarHeader()
|
||||
config.text = "Library"
|
||||
config.text = "Library".localized
|
||||
headerView.contentConfiguration = config
|
||||
default:
|
||||
headerView.contentConfiguration = nil
|
||||
@ -203,7 +203,7 @@ class SidebarViewController: UICollectionViewController, NSFetchedResultsControl
|
||||
guard let splitViewController = splitViewController as? SplitViewController,
|
||||
let item = dataSource.itemIdentifier(for: indexPath),
|
||||
case let .tab(tabID) = item else { return nil }
|
||||
let action = UIContextualAction(style: .destructive, title: "Close") { _, _, _ in
|
||||
let action = UIContextualAction(style: .destructive, title: "Close".localized) { _, _, _ in
|
||||
splitViewController.navigationViewModel.deleteTab(tabID: tabID)
|
||||
}
|
||||
action.image = UIImage(systemName: "xmark")
|
||||
|
@ -128,7 +128,7 @@ class SplitViewController: UISplitViewController {
|
||||
let controller = UIHostingController(rootView: Settings())
|
||||
setViewController(UINavigationController(rootViewController: controller), for: .secondary)
|
||||
default:
|
||||
let controller = UIHostingController(rootView: Text("Not yet implemented"))
|
||||
let controller = UIHostingController(rootView: Text("Not yet implemented".localized))
|
||||
setViewController(UINavigationController(rootViewController: controller), for: .secondary)
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,10 @@
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
8E4396462B02E455007F0BC4 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 8E4396492B02E455007F0BC4 /* Localizable.strings */; };
|
||||
8E4396472B02E455007F0BC4 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 8E4396492B02E455007F0BC4 /* Localizable.strings */; };
|
||||
8E43964C2B02E4C6007F0BC4 /* String+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E43964B2B02E4C6007F0BC4 /* String+Extension.swift */; };
|
||||
8E43964D2B02E4C6007F0BC4 /* String+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E43964B2B02E4C6007F0BC4 /* String+Extension.swift */; };
|
||||
97008ABD2974A5BF0076E60C /* OPDSParserTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97008ABC2974A5BF0076E60C /* OPDSParserTests.swift */; };
|
||||
9709C0982A8E4C5700E4564C /* Commands.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9709C0972A8E4C5700E4564C /* Commands.swift */; };
|
||||
97121EBE28849F0000371AEB /* ZimFileMissingIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97121EBC28849F0000371AEB /* ZimFileMissingIndicator.swift */; };
|
||||
@ -125,6 +129,11 @@
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
8E4396442B02E443007F0BC4 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Main.strings; sourceTree = "<group>"; };
|
||||
8E4396452B02E443007F0BC4 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/LaunchScreen.strings; sourceTree = "<group>"; };
|
||||
8E4396482B02E455007F0BC4 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
8E43964A2B02E458007F0BC4 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
8E43964B2B02E4C6007F0BC4 /* String+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Extension.swift"; sourceTree = "<group>"; };
|
||||
97008AB42974A5A70076E60C /* WikiMed.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = WikiMed.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
97008ABA2974A5BF0076E60C /* Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
97008ABC2974A5BF0076E60C /* OPDSParserTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OPDSParserTests.swift; sourceTree = "<group>"; };
|
||||
@ -294,6 +303,7 @@
|
||||
9779A7E224567A5A00F6F6FF /* Log.swift */,
|
||||
97B3BACD2736CE3500A23F49 /* URL.swift */,
|
||||
9779A73A2456796B00F6F6FF /* WebKitHandler.swift */,
|
||||
8E43964B2B02E4C6007F0BC4 /* String+Extension.swift */,
|
||||
);
|
||||
path = Utilities;
|
||||
sourceTree = "<group>";
|
||||
@ -387,8 +397,8 @@
|
||||
974E7EE22930201500BDF59C /* ZimFileService */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
974E7EE32930201500BDF59C /* ZimFileService.mm */,
|
||||
974E7EE42930201500BDF59C /* ZimFileService.h */,
|
||||
974E7EE32930201500BDF59C /* ZimFileService.mm */,
|
||||
974E7EE52930201500BDF59C /* ZimFileService.swift */,
|
||||
);
|
||||
path = ZimFileService;
|
||||
@ -550,6 +560,7 @@
|
||||
97E94B22271EF250005B0295 /* Kiwix.entitlements */,
|
||||
9779A5D02456796A00F6F6FF /* Kiwix-Bridging-Header.h */,
|
||||
970885D0271339A300C5795C /* wikipedia_dark.css */,
|
||||
8E4396492B02E455007F0BC4 /* Localizable.strings */,
|
||||
);
|
||||
path = Support;
|
||||
sourceTree = "<group>";
|
||||
@ -650,6 +661,7 @@
|
||||
knownRegions = (
|
||||
en,
|
||||
Base,
|
||||
es,
|
||||
);
|
||||
mainGroup = 97A2AB7F1C1B80FF00052E74;
|
||||
packageReferences = (
|
||||
@ -682,6 +694,7 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
973A0DF72830929C00B41E71 /* Assets.xcassets in Resources */,
|
||||
8E4396462B02E455007F0BC4 /* Localizable.strings in Resources */,
|
||||
979D3A7C284159BF00E396B8 /* injection.js in Resources */,
|
||||
97DE2BAD283B133700C63D9B /* wikipedia_dark.css in Resources */,
|
||||
);
|
||||
@ -692,6 +705,7 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
97B448A1210FBC2E0004B056 /* LaunchScreen.storyboard in Resources */,
|
||||
8E4396472B02E455007F0BC4 /* Localizable.strings in Resources */,
|
||||
97B4489E210FBC2E0004B056 /* Assets.xcassets in Resources */,
|
||||
97B4489C210FBC2C0004B056 /* Main.storyboard in Resources */,
|
||||
);
|
||||
@ -755,6 +769,7 @@
|
||||
files = (
|
||||
983ED7192B08AFE700409078 /* Kiwix-Bridging-Header.h in Sources */,
|
||||
972DE4BB2814A5A4004FD9B9 /* Errors.swift in Sources */,
|
||||
8E43964C2B02E4C6007F0BC4 /* String+Extension.swift in Sources */,
|
||||
9790CA5A28A05EBB00D39FC6 /* ZimFilesCategories.swift in Sources */,
|
||||
97486D08284A42B90096E4DD /* SearchResultRow.swift in Sources */,
|
||||
9753D949285B55F100A626CC /* DefaultKeys.swift in Sources */,
|
||||
@ -838,6 +853,7 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
97B44899210FBC2C0004B056 /* ViewController.swift in Sources */,
|
||||
8E43964D2B02E4C6007F0BC4 /* String+Extension.swift in Sources */,
|
||||
97B44897210FBC2C0004B056 /* AppDelegate.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@ -853,10 +869,20 @@
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin PBXVariantGroup section */
|
||||
8E4396492B02E455007F0BC4 /* Localizable.strings */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
8E4396482B02E455007F0BC4 /* en */,
|
||||
8E43964A2B02E458007F0BC4 /* es */,
|
||||
);
|
||||
name = Localizable.strings;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
97B4489A210FBC2C0004B056 /* Main.storyboard */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
97B4489B210FBC2C0004B056 /* Base */,
|
||||
8E4396442B02E443007F0BC4 /* es */,
|
||||
);
|
||||
name = Main.storyboard;
|
||||
sourceTree = "<group>";
|
||||
@ -865,6 +891,7 @@
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
97B448A0210FBC2E0004B056 /* Base */,
|
||||
8E4396452B02E443007F0BC4 /* es */,
|
||||
);
|
||||
name = LaunchScreen.storyboard;
|
||||
sourceTree = "<group>";
|
||||
|
@ -191,10 +191,10 @@ class DownloadService: NSObject, URLSessionDelegate, URLSessionTaskDelegate, URL
|
||||
Database.shared.container.performBackgroundTask { context in
|
||||
// configure notification content
|
||||
let content = UNMutableNotificationContent()
|
||||
content.title = "Download Completed"
|
||||
content.title = "Download Completed".localized
|
||||
content.sound = .default
|
||||
if let zimFile = try? context.fetch(ZimFile.fetchRequest(fileID: zimFileID)).first {
|
||||
content.body = "\(zimFile.name) has been downloaded successfully."
|
||||
content.body = "%@ has been downloaded successfully.".localizedWithFormat(withArgs: zimFile.name)
|
||||
}
|
||||
|
||||
// schedule notification
|
||||
|
@ -16,12 +16,12 @@ public enum LibraryRefreshError: LocalizedError {
|
||||
public var errorDescription: String? {
|
||||
switch self {
|
||||
case .retrieve(let description):
|
||||
let prefix = NSLocalizedString("Error retrieving library data.", comment: "Library Refresh Error")
|
||||
let prefix = "Error retrieving library data.".localized(withComment: "Library Refresh Error")
|
||||
return [prefix, description].compactMap({ $0 }).joined(separator: " ")
|
||||
case .parse:
|
||||
return NSLocalizedString("Error parsing library data.", comment: "Library Refresh Error")
|
||||
return "Error parsing library data.".localized(withComment: "Library Refresh Error")
|
||||
case .process:
|
||||
return NSLocalizedString("Error processing library data.", comment: "Library Refresh Error")
|
||||
return "Error processing library data.".localized(withComment: "Library Refresh Error")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
31
Model/Utilities/String+Extension.swift
Normal file
31
Model/Utilities/String+Extension.swift
Normal file
@ -0,0 +1,31 @@
|
||||
//
|
||||
// String+Extension.swift
|
||||
// Kiwix
|
||||
//
|
||||
// Created by tvision251 on 11/13/23.
|
||||
// Copyright © 2023 Chris Li. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
extension String {
|
||||
|
||||
var localized: String {
|
||||
return NSLocalizedString(self, tableName: nil, bundle: Bundle.main, value: "", comment: "")
|
||||
}
|
||||
|
||||
func localized(withComment: String) -> String {
|
||||
return NSLocalizedString(self, tableName: nil, bundle: Bundle.main, value: "", comment: withComment)
|
||||
}
|
||||
|
||||
func localizedWithFormat(withArgs: CVarArg...) -> String {
|
||||
let format = NSLocalizedString(self, tableName: nil, bundle: Bundle.main, value: "", comment: "")
|
||||
|
||||
switch withArgs.count {
|
||||
case 1: return String.localizedStringWithFormat(format, withArgs[0])
|
||||
case 2: return String.localizedStringWithFormat(format, withArgs[0], withArgs[1])
|
||||
default: return String.localizedStringWithFormat(format, withArgs)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
203
Support/en.lproj/Localizable.strings
Normal file
203
Support/en.lproj/Localizable.strings
Normal file
@ -0,0 +1,203 @@
|
||||
/*
|
||||
Localizable.strings
|
||||
Kiwix
|
||||
|
||||
Created by tvision251 on 11/13/23.
|
||||
Copyright © 2023 Chris Li. All rights reserved.
|
||||
*/
|
||||
|
||||
/* Library Refresh Error */
|
||||
"Error retrieving library data." = "Error retrieving library data.";
|
||||
/* Library Refresh Error */
|
||||
"Error parsing library data." = "Error parsing library data.";
|
||||
/* Library Refresh Error */
|
||||
"Error processing library data." = "Error processing library data.";
|
||||
|
||||
"Download Completed" = "Download Completed";
|
||||
"%@ has been downloaded successfully." = "%@ has been downloaded successfully.";
|
||||
"Done" = "Done";
|
||||
"Go Back" = "Go Back";
|
||||
"Go Forward" = "Go Forward";
|
||||
"Outline" = "Outline";
|
||||
"Show article outline" = "Show article outline";
|
||||
"No outline available" = "No outline available";
|
||||
"Remove Bookmark" = "Remove Bookmark";
|
||||
"Add Bookmark" = "Add Bookmark";
|
||||
"Show Bookmarks" = "Show Bookmarks";
|
||||
"Show bookmarks. Long press to bookmark or unbookmark the current article." = "Show bookmarks. Long press to bookmark or unbookmark the current article.";
|
||||
"Main Article" = "Main Article";
|
||||
"Show main article" = "Show main article";
|
||||
"Random Article" = "Random Article";
|
||||
"Show random article" = "Show random article";
|
||||
"Random Page" = "Random Page";
|
||||
"Tabs" = "Tabs";
|
||||
"New Tab" = "New Tab";
|
||||
"Close This Tab" = "Close This Tab";
|
||||
"Close All Tabs" = "Close All Tabs";
|
||||
"Close Tab" = "Close Tab";
|
||||
"Library" = "Library";
|
||||
"Settings" = "Settings";
|
||||
"Tabs Manager" = "Tabs Manager";
|
||||
"Unable to load the article requested." = "Unable to load the article requested.";
|
||||
"View" = "View";
|
||||
"Remove" = "Remove";
|
||||
"External Link" = "External Link";
|
||||
"Load the link" = "Load the link";
|
||||
"Cancel" = "Cancel";
|
||||
"An external link is tapped, do you wish to load the link?" = "An external link is tapped, do you wish to load the link?";
|
||||
"loc-external-alert" = "An external link is tapped. \
|
||||
However, your current setting does not allow it to be loaded.";
|
||||
"Open a zim file" = "Open a zim file";
|
||||
"Unable to open file" = "Unable to open file";
|
||||
"%@ cannot be opened." = "%@ cannot be opened.";
|
||||
"No snippet" = "No snippet";
|
||||
"Article Title" = "Article Title";
|
||||
"loc-article-cell-template" = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, \
|
||||
sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.";
|
||||
"Unknown" = "Unknown";
|
||||
"Yes" = "Yes";
|
||||
"No" = "No";
|
||||
"Failed" = "Failed";
|
||||
"Downloading..." = "Downloading...";
|
||||
"Paused" = "Paused";
|
||||
"everything except large media files like video/audio" = "everything except large media files like video/audio";
|
||||
"most pictures have been removed" = "most pictures have been removed";
|
||||
"only a subset of the text is available, probably the first section" = "only a subset of the text is available, probably the first section";
|
||||
"Header Text" = "Header Text";
|
||||
"Content" = "Content";
|
||||
"Just Now" = "Just Now";
|
||||
"Never" = "Never";
|
||||
"There is nothing to see" = "There is nothing to see";
|
||||
"Article Title" = "Article Title";
|
||||
"Zim file is missing." = "Zim file is missing.";
|
||||
"articles" = "articles";
|
||||
"Select a zim file to see detail" = "Select a zim file to see detail";
|
||||
"Main Page" = "Main Page";
|
||||
"Copy URL" = "Copy URL";
|
||||
"Copy ID" = "Copy ID";
|
||||
"No opened zim file" = "No opened zim file";
|
||||
"Show Sidebar" = "Show Sidebar";
|
||||
"Open..." = "Open...";
|
||||
"Category" = "Category";
|
||||
"No zim file under this category." = "No zim file under this category.";
|
||||
"No download tasks" = "No download tasks";
|
||||
"No new zim file" = "No new zim file";
|
||||
"Refresh" = "Refresh";
|
||||
"Name" = "Name";
|
||||
"Description" = "Description";
|
||||
"Actions" = "Actions";
|
||||
"Info" = "Info";
|
||||
"Locate" = "Locate";
|
||||
"Open Main Page" = "Open Main Page";
|
||||
"Reveal in Finder" = "Reveal in Finder";
|
||||
"Download using cellular" = "Download using cellular";
|
||||
"Unlink" = "Unlink";
|
||||
"Unlink %@" = "Unlink %@";
|
||||
"loc-ZimFileDetail-Alert-unlink" = "All bookmarked articles linked to this zim file will be deleted, \
|
||||
but the original file will remain in place.";
|
||||
"Delete %@" = "Delete %@";
|
||||
"loc-ZimFileDetail-Alert-Delete" = "The zim file and all bookmarked articles \
|
||||
linked to this zim file will be deleted.";
|
||||
"Download" = "Download";
|
||||
"Space Warning" = "Space Warning";
|
||||
"There might not be enough space on your device for this zim file." = "There might not be enough space on your device for this zim file.";
|
||||
"There would be less than 1GB space left after the zim file is downloaded." = "There would be less than 1GB space left after the zim file is downloaded.";
|
||||
"Download Anyway" = "Download Anyway";
|
||||
"Language" = "Language";
|
||||
"Size" = "Size";
|
||||
"Created" = "Created";
|
||||
"Pictures" = "Pictures";
|
||||
"Videos" = "Videos";
|
||||
"Details" = "Details";
|
||||
"Requires Service Workers" = "Requires Service Workers";
|
||||
"Article Count" = "Article Count";
|
||||
"Media Count" = "Media Count";
|
||||
"ID" = "ID";
|
||||
"Try to Recover" = "Try to Recover";
|
||||
"Pause" = "Pause";
|
||||
"Resume" = "Resume";
|
||||
"Zim files requiring service workers are not supported." = "Zim files requiring service workers are not supported.";
|
||||
"A very long description" = "A very long description";
|
||||
"Page zoom" = "Page zoom";
|
||||
"Reset" = "Reset";
|
||||
"External link" = "External link";
|
||||
"Search snippet" = "Search snippet";
|
||||
"Reading" = "Reading";
|
||||
"Catalog" = "Catalog";
|
||||
"Refresh Now" = "Refresh Now";
|
||||
"Last refresh" = "Last refresh";
|
||||
"Auto refresh" = "Auto refresh";
|
||||
"When enabled, the library catalog will be refreshed automatically when outdated." = "When enabled, the library catalog will be refreshed automatically when outdated.";
|
||||
"Languages" = "Languages";
|
||||
"Library" = "Library";
|
||||
"Change will only apply to new download tasks." = "Change will only apply to new download tasks.";
|
||||
"Refreshing..." = "Refreshing...";
|
||||
"Include zim files in backup" = "Include zim files in backup";
|
||||
"Backup" = "Backup";
|
||||
"Does not apply to files opened in place." = "Does not apply to files opened in place.";
|
||||
"Misc" = "Misc";
|
||||
"Feedback" = "Feedback";
|
||||
"Rate the App" = "Rate the App";
|
||||
"About" = "About";
|
||||
"Count" = "Count";
|
||||
"No language" = "No language";
|
||||
"Showing" = "Showing";
|
||||
"Hiding" = "Hiding";
|
||||
"Sorting" = "Sorting";
|
||||
"Release" = "Release";
|
||||
"Dependencies" = "Dependencies";
|
||||
"License" = "License";
|
||||
"Version" = "Version";
|
||||
"loc-About-description" = "Kiwix is an offline reader for online content like Wikipedia, Project Gutenberg, or TED Talks. \
|
||||
It makes knowledge available to people with no or limited internet access. \
|
||||
The software as well as the content is free to use for anyone.";
|
||||
"This app is released under the terms of the GNU General Public License version 3." = "This app is released under the terms of the GNU General Public License version 3.";
|
||||
"Build" = "Build";
|
||||
"Our Website" = "Our Website";
|
||||
"Source" = "Source";
|
||||
"GNU General Public License v3" = "GNU General Public License v3";
|
||||
"Bookmarks" = "Bookmarks";
|
||||
"No bookmarks" = "No bookmarks";
|
||||
"No result" = "No result";
|
||||
"Recent Search" = "Recent Search";
|
||||
"Clear" = "Clear";
|
||||
"Clear Recent Searches" = "Clear Recent Searches";
|
||||
"Clear All" = "Clear All";
|
||||
"All recent search history will be removed." = "All recent search history will be removed.";
|
||||
"Included in Search" = "Included in Search";
|
||||
"None" = "None";
|
||||
"All" = "All";
|
||||
"Open File" = "Open File";
|
||||
"Fetching..." = "Fetching...";
|
||||
"Fetch Catalog" = "Fetch Catalog";
|
||||
"Actual Size" = "Actual Size";
|
||||
"Zoom In" = "Zoom In";
|
||||
"Zoom Out" = "Zoom Out";
|
||||
"Not yet implemented" = "Not yet implemented";
|
||||
"Close" = "Close";
|
||||
"Map" = "Map";
|
||||
"Opened" = "Opened";
|
||||
"Categories" = "Categories";
|
||||
"New" = "New";
|
||||
"Downloads" = "Downloads";
|
||||
"A-Z" = "A-Z";
|
||||
"By Count" = "By Count";
|
||||
"Always Ask" = "Always Ask";
|
||||
"Always Load" = "Always Load";
|
||||
"Never Load" = "Never Load";
|
||||
"Wikipedia" = "Wikipedia";
|
||||
"Wikibooks" = "Wikibooks";
|
||||
"Wikinews" = "Wikinews";
|
||||
"Wikiquote" = "Wikiquote";
|
||||
"Wikisource" = "Wikisource";
|
||||
"Wikiversity" = "Wikiversity";
|
||||
"Wikivoyage" = "Wikivoyage";
|
||||
"Wiktionary" = "Wiktionary";
|
||||
"TED" = "TED";
|
||||
"Vikidia" = "Vikidia";
|
||||
"StackExchange" = "StackExchange";
|
||||
"Other" = "Other";
|
||||
"Disabled" = "Disabled";
|
||||
"First Paragraph" = "First Paragraph";
|
||||
"First Sentence" = "First Sentence";
|
||||
"Matches" = "Matches";
|
8
Support/es.lproj/Localizable.strings
Normal file
8
Support/es.lproj/Localizable.strings
Normal file
@ -0,0 +1,8 @@
|
||||
/*
|
||||
Localizable.strings
|
||||
Kiwix
|
||||
|
||||
Created by tvision251 on 11/13/23.
|
||||
Copyright © 2023 Chris Li. All rights reserved.
|
||||
*/
|
||||
|
@ -317,12 +317,12 @@ final class BrowserViewModel: NSObject, ObservableObject,
|
||||
|
||||
// open url
|
||||
actions.append(
|
||||
UIAction(title: "Open", image: UIImage(systemName: "doc.text")) { _ in
|
||||
UIAction(title: "Open".localized, image: UIImage(systemName: "doc.text")) { _ in
|
||||
webView.load(URLRequest(url: url))
|
||||
}
|
||||
)
|
||||
actions.append(
|
||||
UIAction(title: "Open in New Tab", image: UIImage(systemName: "doc.badge.plus")) { _ in
|
||||
UIAction(title: "Open in New Tab".localized, image: UIImage(systemName: "doc.badge.plus")) { _ in
|
||||
NotificationCenter.openURL(url, inNewTab: true)
|
||||
}
|
||||
)
|
||||
@ -333,12 +333,12 @@ final class BrowserViewModel: NSObject, ObservableObject,
|
||||
let predicate = NSPredicate(format: "articleURL == %@", url as CVarArg)
|
||||
let request = Bookmark.fetchRequest(predicate: predicate)
|
||||
if let bookmarks = try? context.fetch(request), !bookmarks.isEmpty {
|
||||
return UIAction(title: "Remove Bookmark",
|
||||
return UIAction(title: "Remove Bookmark".localized,
|
||||
image: UIImage(systemName: "star.slash.fill")) { [weak self] _ in
|
||||
self?.deleteBookmark(url: url)
|
||||
}
|
||||
} else {
|
||||
return UIAction(title: "Bookmark", image: UIImage(systemName: "star")) { [weak self] _ in
|
||||
return UIAction(title: "Bookmark".localized, image: UIImage(systemName: "star")) { [weak self] _ in
|
||||
self?.createBookmark(url: url)
|
||||
}
|
||||
}
|
||||
|
@ -36,14 +36,14 @@ struct Bookmarks: View {
|
||||
}
|
||||
.modifier(GridCommon())
|
||||
.modifier(ToolbarRoleBrowser())
|
||||
.navigationTitle("Bookmarks")
|
||||
.navigationTitle("Bookmarks".localized)
|
||||
.searchable(text: $searchText)
|
||||
.onChange(of: searchText) { searchText in
|
||||
bookmarks.nsPredicate = Bookmarks.buildPredicate(searchText: searchText)
|
||||
}
|
||||
.overlay {
|
||||
if bookmarks.isEmpty {
|
||||
Message(text: "No bookmarks")
|
||||
Message(text: "No bookmarks".localized)
|
||||
}
|
||||
}
|
||||
.toolbar {
|
||||
@ -53,7 +53,7 @@ struct Bookmarks: View {
|
||||
Button {
|
||||
NotificationCenter.toggleSidebar()
|
||||
} label: {
|
||||
Label("Show Sidebar", systemImage: "sidebar.left")
|
||||
Label("Show Sidebar".localized, systemImage: "sidebar.left")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ struct BrowserTab: View {
|
||||
Button {
|
||||
NotificationCenter.toggleSidebar()
|
||||
} label: {
|
||||
Label("Show Sidebar", systemImage: "sidebar.left")
|
||||
Label("Show Sidebar".localized, systemImage: "sidebar.left")
|
||||
}
|
||||
}
|
||||
NavigationButtons()
|
||||
|
@ -44,7 +44,7 @@ struct ArticleCell: View {
|
||||
if let snippet = snippet {
|
||||
Text(AttributedString(snippet)).lineLimit(4)
|
||||
} else if alwaysShowSnippet {
|
||||
Text("No snippet").foregroundColor(.secondary)
|
||||
Text("No snippet".localized).foregroundColor(.secondary)
|
||||
}
|
||||
}.font(.caption).multilineTextAlignment(.leading)
|
||||
Spacer(minLength: 0)
|
||||
@ -64,13 +64,8 @@ struct ArticleCell: View {
|
||||
|
||||
struct ArticleCell_Previews: PreviewProvider {
|
||||
static let result: SearchResult = {
|
||||
let result = SearchResult(zimFileID: UUID(), path: "", title: "Article Title")!
|
||||
result.snippet = NSAttributedString(string:
|
||||
"""
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, \
|
||||
sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
|
||||
"""
|
||||
)
|
||||
let result = SearchResult(zimFileID: UUID(), path: "", title: "Article Title".localized)!
|
||||
result.snippet = NSAttributedString(string: "loc-article-cell-template".localized)
|
||||
return result
|
||||
}()
|
||||
|
||||
|
@ -16,7 +16,7 @@ struct Attribute: View {
|
||||
HStack {
|
||||
Text(title)
|
||||
Spacer()
|
||||
Text(detail ?? "Unknown").foregroundColor(.secondary)
|
||||
Text(detail ?? "Unknown".localized).foregroundColor(.secondary)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -30,7 +30,7 @@ struct AttributeBool: View {
|
||||
Text(title)
|
||||
Spacer()
|
||||
#if os(macOS)
|
||||
Text(detail ? "Yes" : "No").foregroundColor(.secondary)
|
||||
Text(detail ? "Yes".localized : "No".localized).foregroundColor(.secondary)
|
||||
#elseif os(iOS)
|
||||
if detail {
|
||||
Image(systemName: "checkmark.circle.fill").foregroundColor(.green)
|
||||
|
@ -41,9 +41,9 @@ struct DownloadTaskCell: View {
|
||||
}
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
if downloadTask.error != nil {
|
||||
Text("Failed")
|
||||
Text("Failed".localized)
|
||||
} else if downloadTask.resumeData == nil {
|
||||
Text("Downloading...")
|
||||
Text("Downloading...".localized)
|
||||
} else {
|
||||
Text("Paused")
|
||||
}
|
||||
|
@ -41,11 +41,11 @@ struct FlavorTag: View {
|
||||
var help: String {
|
||||
switch flavor {
|
||||
case .max:
|
||||
return "everything except large media files like video/audio"
|
||||
return "everything except large media files like video/audio".localized
|
||||
case .noPic:
|
||||
return "most pictures have been removed"
|
||||
return "most pictures have been removed".localized
|
||||
case .mini:
|
||||
return "only a subset of the text is available, probably the first section"
|
||||
return "only a subset of the text is available, probably the first section".localized
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -29,8 +29,8 @@ struct GridSection<Content: View>: View {
|
||||
|
||||
struct GridSection_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
GridSection(title: "Header Text") {
|
||||
Text("Content")
|
||||
GridSection(title: "Header Text".localized) {
|
||||
Text("Content".localized)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,12 +16,12 @@ struct LibraryLastRefreshTime: View {
|
||||
var body: some View {
|
||||
if let lastRefresh = lastRefresh {
|
||||
if Date().timeIntervalSince(lastRefresh) < 120 {
|
||||
Text("Just Now")
|
||||
Text("Just Now".localized)
|
||||
} else {
|
||||
Text(RelativeDateTimeFormatter().localizedString(for: lastRefresh, relativeTo: Date()))
|
||||
}
|
||||
} else {
|
||||
Text("Never")
|
||||
Text("Never".localized)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ struct Message: View {
|
||||
|
||||
struct Message_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
Message(text: "There is nothing to see")
|
||||
Message(text: "There is nothing to see".localized)
|
||||
.frame(width: 250, height: 200)
|
||||
}
|
||||
}
|
||||
|
@ -38,13 +38,8 @@ struct SearchResultRow: View {
|
||||
|
||||
struct SearchResultRow_Previews: PreviewProvider {
|
||||
static let result: SearchResult = {
|
||||
let result = SearchResult(zimFileID: UUID(), path: "", title: "Article Title")!
|
||||
result.snippet = NSAttributedString(string:
|
||||
"""
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, \
|
||||
sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
|
||||
"""
|
||||
)
|
||||
let result = SearchResult(zimFileID: UUID(), path: "", title: "Article Title".localized)!
|
||||
result.snippet = NSAttributedString(string: "loc-article-cell-template".localized)
|
||||
return result
|
||||
}()
|
||||
|
||||
|
@ -27,7 +27,7 @@ struct SheetContent<Content: View>: View {
|
||||
Button {
|
||||
dismiss()
|
||||
} label: {
|
||||
Text("Done").fontWeight(.semibold)
|
||||
Text("Done".localized).fontWeight(.semibold)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,12 +15,12 @@ struct TabLabel: View {
|
||||
var body: some View {
|
||||
if let zimFile = tab.zimFile, let category = Category(rawValue: zimFile.category) {
|
||||
Label {
|
||||
Text(tab.title ?? "New Tab").lineLimit(1)
|
||||
Text(tab.title ?? "New Tab".localized).lineLimit(1)
|
||||
} icon: {
|
||||
Favicon(category: category, imageData: zimFile.faviconData).frame(width: 22, height: 22)
|
||||
}
|
||||
} else {
|
||||
Label(tab.title ?? "New Tab", systemImage: "square")
|
||||
Label(tab.title ?? "New Tab".localized, systemImage: "square")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,6 @@ struct ZimFileMissingIndicator: View {
|
||||
var body: some View {
|
||||
Image(systemName: "exclamationmark.triangle.fill")
|
||||
.renderingMode(.original)
|
||||
.help("Zim file is missing.")
|
||||
.help("Zim file is missing.".localized)
|
||||
}
|
||||
}
|
||||
|
@ -30,7 +30,8 @@ struct ZimFileRow: View {
|
||||
Formatter.size.string(fromByteCount: zimFile.size),
|
||||
{
|
||||
if #available(iOS 15.0, *) {
|
||||
return "\(zimFile.articleCount.formatted(.number.notation(.compactName))) articles"
|
||||
return "\(zimFile.articleCount.formatted(.number.notation(.compactName)))" +
|
||||
"articles".localized
|
||||
} else {
|
||||
return Formatter.largeNumber(zimFile.articleCount)
|
||||
}
|
||||
|
@ -40,10 +40,10 @@ struct ArticleShortcutButtons: View {
|
||||
browser.loadMainArticle()
|
||||
dismissSearch()
|
||||
} label: {
|
||||
Label("Main Article", systemImage: "house")
|
||||
Label("Main Article".localized, systemImage: "house")
|
||||
}
|
||||
.disabled(zimFiles.isEmpty)
|
||||
.help("Show main article")
|
||||
.help("Show main article".localized)
|
||||
#elseif os(iOS)
|
||||
Menu {
|
||||
ForEach(zimFiles) { zimFile in
|
||||
@ -53,13 +53,13 @@ struct ArticleShortcutButtons: View {
|
||||
}
|
||||
}
|
||||
} label: {
|
||||
Label("Main Article", systemImage: "house")
|
||||
Label("Main Article".localized, systemImage: "house")
|
||||
} primaryAction: {
|
||||
browser.loadMainArticle()
|
||||
dismissSearch()
|
||||
}
|
||||
.disabled(zimFiles.isEmpty)
|
||||
.help("Show main article")
|
||||
.help("Show main article".localized)
|
||||
#endif
|
||||
}
|
||||
|
||||
@ -69,10 +69,10 @@ struct ArticleShortcutButtons: View {
|
||||
browser.loadRandomArticle()
|
||||
dismissSearch()
|
||||
} label: {
|
||||
Label("Random Article", systemImage: "die.face.5")
|
||||
Label("Random Article".localized, systemImage: "die.face.5")
|
||||
}
|
||||
.disabled(zimFiles.isEmpty)
|
||||
.help("Show random article")
|
||||
.help("Show random article".localized)
|
||||
#elseif os(iOS)
|
||||
Menu {
|
||||
ForEach(zimFiles) { zimFile in
|
||||
@ -82,13 +82,13 @@ struct ArticleShortcutButtons: View {
|
||||
}
|
||||
}
|
||||
} label: {
|
||||
Label("Random Page", systemImage: "die.face.5")
|
||||
Label("Random Page".localized, systemImage: "die.face.5")
|
||||
} primaryAction: {
|
||||
browser.loadRandomArticle()
|
||||
dismissSearch()
|
||||
}
|
||||
.disabled(zimFiles.isEmpty)
|
||||
.help("Show random article")
|
||||
.help("Show random article".localized)
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ struct BookmarkButton: View {
|
||||
}
|
||||
} label: {
|
||||
Label {
|
||||
Text(browser.articleBookmarked ? "Remove Bookmark" : "Add Bookmark")
|
||||
Text(browser.articleBookmarked ? "Remove Bookmark".localized : "Add Bookmark".localized)
|
||||
} icon: {
|
||||
Image(systemName: browser.articleBookmarked ? "star.fill" : "star")
|
||||
.renderingMode(browser.articleBookmarked ? .original : .template)
|
||||
@ -35,23 +35,23 @@ struct BookmarkButton: View {
|
||||
Button(role: .destructive) {
|
||||
browser.deleteBookmark()
|
||||
} label: {
|
||||
Label("Remove Bookmark", systemImage: "star.slash.fill")
|
||||
Label("Remove Bookmark".localized, systemImage: "star.slash.fill")
|
||||
}
|
||||
} else {
|
||||
Button {
|
||||
browser.createBookmark()
|
||||
} label: {
|
||||
Label("Add Bookmark", systemImage: "star")
|
||||
Label("Add Bookmark".localized, systemImage: "star")
|
||||
}
|
||||
}
|
||||
Button {
|
||||
isShowingBookmark = true
|
||||
} label: {
|
||||
Label("Show Bookmarks", systemImage: "list.star")
|
||||
Label("Show Bookmarks".localized, systemImage: "list.star")
|
||||
}
|
||||
} label: {
|
||||
Label {
|
||||
Text("Show Bookmarks")
|
||||
Text("Show Bookmarks".localized)
|
||||
} icon: {
|
||||
Image(systemName: browser.articleBookmarked ? "star.fill" : "star")
|
||||
.renderingMode(browser.articleBookmarked ? .original : .template)
|
||||
@ -59,7 +59,7 @@ struct BookmarkButton: View {
|
||||
} primaryAction: {
|
||||
isShowingBookmark = true
|
||||
}
|
||||
.help("Show bookmarks. Long press to bookmark or unbookmark the current article.")
|
||||
.help("Show bookmarks. Long press to bookmark or unbookmark the current article.".localized)
|
||||
.popover(isPresented: $isShowingBookmark) {
|
||||
NavigationView {
|
||||
Bookmarks().navigationBarTitleDisplayMode(.inline).toolbar {
|
||||
@ -67,7 +67,7 @@ struct BookmarkButton: View {
|
||||
Button {
|
||||
isShowingBookmark = false
|
||||
} label: {
|
||||
Text("Done").fontWeight(.semibold)
|
||||
Text("Done".localized).fontWeight(.semibold)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ struct NavigationButtons: View {
|
||||
browser.webView.goBack()
|
||||
dismissSearch()
|
||||
} label: {
|
||||
Label("Go Back", systemImage: "chevron.left")
|
||||
Label("Go Back".localized, systemImage: "chevron.left")
|
||||
}.disabled(!browser.canGoBack)
|
||||
}
|
||||
|
||||
@ -38,7 +38,7 @@ struct NavigationButtons: View {
|
||||
browser.webView.goForward()
|
||||
dismissSearch()
|
||||
} label: {
|
||||
Label("Go Forward", systemImage: "chevron.right")
|
||||
Label("Go Forward".localized, systemImage: "chevron.right")
|
||||
}.disabled(!browser.canGoForward)
|
||||
}
|
||||
}
|
||||
|
@ -25,10 +25,10 @@ struct OutlineButton: View {
|
||||
}
|
||||
}
|
||||
} label: {
|
||||
Label("Outline", systemImage: "list.bullet")
|
||||
Label("Outline".localized, systemImage: "list.bullet")
|
||||
}
|
||||
.disabled(browser.outlineItems.isEmpty)
|
||||
.help("Show article outline")
|
||||
.help("Show article outline".localized)
|
||||
#elseif os(iOS)
|
||||
Button {
|
||||
isShowingOutline = true
|
||||
@ -36,12 +36,12 @@ struct OutlineButton: View {
|
||||
Image(systemName: "list.bullet")
|
||||
}
|
||||
.disabled(browser.outlineItems.isEmpty)
|
||||
.help("Show article outline")
|
||||
.help("Show article outline".localized)
|
||||
.popover(isPresented: $isShowingOutline) {
|
||||
NavigationView {
|
||||
Group {
|
||||
if browser.outlineItemTree.isEmpty {
|
||||
Message(text: "No outline available")
|
||||
Message(text: "No outline available".localized)
|
||||
} else {
|
||||
List(browser.outlineItemTree) { item in
|
||||
OutlineNode(item: item) { item in
|
||||
@ -59,7 +59,7 @@ struct OutlineButton: View {
|
||||
Button {
|
||||
isShowingOutline = false
|
||||
} label: {
|
||||
Text("Done").fontWeight(.semibold)
|
||||
Text("Done".localized).fontWeight(.semibold)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -29,18 +29,18 @@ struct TabsManagerButton: View {
|
||||
Button {
|
||||
navigation.createTab()
|
||||
} label: {
|
||||
Label("New Tab", systemImage: "plus.square")
|
||||
Label("New Tab".localized, systemImage: "plus.square")
|
||||
}
|
||||
Button(role: .destructive) {
|
||||
guard case .tab(let tabID) = navigation.currentItem else { return }
|
||||
navigation.deleteTab(tabID: tabID)
|
||||
} label: {
|
||||
Label("Close This Tab", systemImage: "xmark.square")
|
||||
Label("Close This Tab".localized, systemImage: "xmark.square")
|
||||
}
|
||||
Button(role: .destructive) {
|
||||
navigation.deleteAllTabs()
|
||||
} label: {
|
||||
Label("Close All Tabs", systemImage: "xmark.square.fill")
|
||||
Label("Close All Tabs".localized, systemImage: "xmark.square.fill")
|
||||
}
|
||||
}
|
||||
Section {
|
||||
@ -54,16 +54,16 @@ struct TabsManagerButton: View {
|
||||
Button {
|
||||
presentedSheet = .library
|
||||
} label: {
|
||||
Label("Library", systemImage: "folder")
|
||||
Label("Library".localized, systemImage: "folder")
|
||||
}
|
||||
Button {
|
||||
presentedSheet = .settings
|
||||
} label: {
|
||||
Label("Settings", systemImage: "gear")
|
||||
Label("Settings".localized, systemImage: "gear")
|
||||
}
|
||||
}
|
||||
} label: {
|
||||
Label("Tabs Manager", systemImage: "square.stack")
|
||||
Label("Tabs Manager".localized, systemImage: "square.stack")
|
||||
} primaryAction: {
|
||||
presentedSheet = .tabsManager
|
||||
}
|
||||
@ -76,7 +76,7 @@ struct TabsManagerButton: View {
|
||||
Button {
|
||||
self.presentedSheet = nil
|
||||
} label: {
|
||||
Text("Done").fontWeight(.semibold)
|
||||
Text("Done".localized).fontWeight(.semibold)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -90,7 +90,7 @@ struct TabsManagerButton: View {
|
||||
Button {
|
||||
self.presentedSheet = nil
|
||||
} label: {
|
||||
Text("Done").fontWeight(.semibold)
|
||||
Text("Done".localized).fontWeight(.semibold)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -129,12 +129,12 @@ struct TabManager: View {
|
||||
Button(role: .destructive) {
|
||||
navigation.deleteTab(tabID: tab.objectID)
|
||||
} label: {
|
||||
Label("Close Tab", systemImage: "xmark")
|
||||
Label("Close Tab".localized, systemImage: "xmark")
|
||||
}
|
||||
}
|
||||
}
|
||||
.listStyle(.plain)
|
||||
.navigationTitle("Tabs")
|
||||
.navigationTitle("Tabs".localized)
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
Menu {
|
||||
@ -142,15 +142,15 @@ struct TabManager: View {
|
||||
guard case let .tab(tabID) = navigation.currentItem else { return }
|
||||
navigation.deleteTab(tabID: tabID)
|
||||
} label: {
|
||||
Label("Close This Tab", systemImage: "xmark.square")
|
||||
Label("Close This Tab".localized, systemImage: "xmark.square")
|
||||
}
|
||||
Button(role: .destructive) {
|
||||
navigation.deleteAllTabs()
|
||||
} label: {
|
||||
Label("Close All Tabs", systemImage: "xmark.square.fill")
|
||||
Label("Close All Tabs".localized, systemImage: "xmark.square.fill")
|
||||
}
|
||||
} label: {
|
||||
Label("New Tab", systemImage: "plus.square")
|
||||
Label("New Tab".localized, systemImage: "plus.square")
|
||||
} primaryAction: {
|
||||
navigation.createTab()
|
||||
}
|
||||
|
@ -54,10 +54,10 @@ struct NavigationCommands: View {
|
||||
@FocusedValue(\.browserViewModel) var browser: BrowserViewModel?
|
||||
|
||||
var body: some View {
|
||||
Button("Go Back") { browser?.webView.goBack() }
|
||||
Button("Go Back".localized) { browser?.webView.goBack() }
|
||||
.keyboardShortcut("[")
|
||||
.disabled(canGoBack != true)
|
||||
Button("Go Forward") { browser?.webView.goForward() }
|
||||
Button("Go Forward".localized) { browser?.webView.goForward() }
|
||||
.keyboardShortcut("]")
|
||||
.disabled(canGoForward != true)
|
||||
}
|
||||
@ -68,13 +68,13 @@ struct PageZoomCommands: View {
|
||||
@FocusedValue(\.browserViewModel) var browser: BrowserViewModel?
|
||||
|
||||
var body: some View {
|
||||
Button("Actual Size") { webViewPageZoom = 1 }
|
||||
Button("Actual Size".localized) { webViewPageZoom = 1 }
|
||||
.keyboardShortcut("0")
|
||||
.disabled(webViewPageZoom == 1 || browser?.url == nil)
|
||||
Button("Zoom In") { webViewPageZoom += 0.1 }
|
||||
Button("Zoom In".localized) { webViewPageZoom += 0.1 }
|
||||
.keyboardShortcut("+")
|
||||
.disabled(webViewPageZoom >= 2 || browser?.url == nil)
|
||||
Button("Zoom Out") { webViewPageZoom -= 0.1 }
|
||||
Button("Zoom Out".localized) { webViewPageZoom -= 0.1 }
|
||||
.keyboardShortcut("-")
|
||||
.disabled(webViewPageZoom <= 0.5 || browser?.url == nil)
|
||||
}
|
||||
|
@ -32,7 +32,7 @@ struct Library: View {
|
||||
List(Category.allCases) { category in
|
||||
NavigationLink {
|
||||
ZimFilesCategory(category: .constant(category))
|
||||
.navigationTitle(category.name)
|
||||
.navigationTitle(category.name.localized)
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
} label: {
|
||||
HStack {
|
||||
@ -42,7 +42,7 @@ struct Library: View {
|
||||
}
|
||||
}
|
||||
.listStyle(.plain)
|
||||
.navigationTitle(NavigationItem.categories.name)
|
||||
.navigationTitle(NavigationItem.categories.name.localized)
|
||||
case .downloads:
|
||||
ZimFilesDownloads()
|
||||
case .new:
|
||||
@ -87,7 +87,7 @@ struct LibraryZimFileDetailSidePanel: ViewModifier {
|
||||
if let zimFile = viewModel.selectedZimFile {
|
||||
ZimFileDetail(zimFile: zimFile)
|
||||
} else {
|
||||
Message(text: "Select a zim file to see detail").background(.thickMaterial)
|
||||
Message(text: "Select a zim file to see detail".localized).background(.thickMaterial)
|
||||
}
|
||||
}.frame(width: 275).background(.ultraThinMaterial)
|
||||
}
|
||||
@ -132,13 +132,13 @@ struct LibraryZimFileContext: ViewModifier {
|
||||
guard let url = ZimFileService.shared.getMainPageURL(zimFileID: zimFile.fileID) else { return }
|
||||
NotificationCenter.openURL(url, inNewTab: true)
|
||||
} label: {
|
||||
Label("Main Page", systemImage: "house")
|
||||
Label("Main Page".localized, systemImage: "house")
|
||||
}
|
||||
Button {
|
||||
guard let url = ZimFileService.shared.getRandomPageURL(zimFileID: zimFile.fileID) else { return }
|
||||
NotificationCenter.openURL(url, inNewTab: true)
|
||||
} label: {
|
||||
Label("Random Page", systemImage: "die.face.5")
|
||||
Label("Random Page".localized, systemImage: "die.face.5")
|
||||
}
|
||||
}
|
||||
|
||||
@ -153,7 +153,7 @@ struct LibraryZimFileContext: ViewModifier {
|
||||
UIPasteboard.general.setValue(downloadURL.absoluteString, forPasteboardType: UTType.url.identifier)
|
||||
#endif
|
||||
} label: {
|
||||
Label("Copy URL", systemImage: "doc.on.doc")
|
||||
Label("Copy URL".localized, systemImage: "doc.on.doc")
|
||||
}
|
||||
}
|
||||
Button {
|
||||
@ -164,7 +164,7 @@ struct LibraryZimFileContext: ViewModifier {
|
||||
UIPasteboard.general.setValue(zimFile.fileID.uuidString, forPasteboardType: UTType.plainText.identifier)
|
||||
#endif
|
||||
} label: {
|
||||
Label("Copy ID", systemImage: "barcode.viewfinder")
|
||||
Label("Copy ID".localized, systemImage: "barcode.viewfinder")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -25,10 +25,10 @@ struct ZimFileDetail: View {
|
||||
var body: some View {
|
||||
#if os(macOS)
|
||||
List {
|
||||
Section("Name") { Text(zimFile.name).lineLimit(nil) }.collapsible(false)
|
||||
Section("Description") { Text(zimFile.fileDescription).lineLimit(nil) }.collapsible(false)
|
||||
Section("Actions") { actions }.collapsible(false)
|
||||
Section("Info") {
|
||||
Section("Name".localized) { Text(zimFile.name).lineLimit(nil) }.collapsible(false)
|
||||
Section("Description".localized) { Text(zimFile.fileDescription).lineLimit(nil) }.collapsible(false)
|
||||
Section("Actions".localized) { actions }.collapsible(false)
|
||||
Section("Info".localized) {
|
||||
basicInfo
|
||||
boolInfo
|
||||
counts
|
||||
@ -81,15 +81,15 @@ struct ZimFileDetail: View {
|
||||
if let downloadTask = zimFile.downloadTask { // zim file is being downloaded
|
||||
DownloadTaskDetail(downloadTask: downloadTask)
|
||||
} else if zimFile.isMissing { // zim file was opened, but is now missing
|
||||
Action(title: "Locate") { isPresentingFileLocator = true }
|
||||
Action(title: "Locate".localized) { isPresentingFileLocator = true }
|
||||
unlinkAction
|
||||
} else if zimFile.fileURLBookmark != nil { // zim file is opened
|
||||
Action(title: "Open Main Page") {
|
||||
Action(title: "Open Main Page".localized) {
|
||||
guard let url = ZimFileService.shared.getMainPageURL(zimFileID: zimFile.fileID) else { return }
|
||||
NotificationCenter.openURL(url, inNewTab: true)
|
||||
}
|
||||
#if os(macOS)
|
||||
Action(title: "Reveal in Finder") {
|
||||
Action(title: "Reveal in Finder".localized) {
|
||||
guard let url = ZimFileService.shared.getFileURL(zimFileID: zimFile.id) else { return }
|
||||
NSWorkspace.shared.activateFileViewerSelecting([url])
|
||||
}
|
||||
@ -105,23 +105,20 @@ struct ZimFileDetail: View {
|
||||
#endif
|
||||
} else if zimFile.downloadURL != nil { // zim file can be downloaded
|
||||
#if os(iOS)
|
||||
Toggle("Download using cellular", isOn: $downloadUsingCellular)
|
||||
Toggle("Download using cellular".localized, isOn: $downloadUsingCellular)
|
||||
#endif
|
||||
downloadAction
|
||||
}
|
||||
}
|
||||
|
||||
var unlinkAction: some View {
|
||||
Action(title: "Unlink", isDestructive: true) {
|
||||
Action(title: "Unlink".localized, isDestructive: true) {
|
||||
isPresentingUnlinkAlert = true
|
||||
}.alert(isPresented: $isPresentingUnlinkAlert) {
|
||||
Alert(
|
||||
title: Text("Unlink \(zimFile.name)"),
|
||||
message: Text("""
|
||||
All bookmarked articles linked to this zim file will be deleted, \
|
||||
but the original file will remain in place.
|
||||
"""),
|
||||
primaryButton: .destructive(Text("Unlink")) {
|
||||
title: Text("Unlink %@".localizedWithFormat(withArgs: zimFile.name)),
|
||||
message: Text("loc-ZimFileDetail-Alert-unlink".localized),
|
||||
primaryButton: .destructive(Text("Unlink".localized)) {
|
||||
LibraryOperations.unlink(zimFileID: zimFile.fileID)
|
||||
#if os(iOS)
|
||||
presentationMode.wrappedValue.dismiss()
|
||||
@ -137,9 +134,9 @@ struct ZimFileDetail: View {
|
||||
isPresentingDeleteAlert = true
|
||||
}.alert(isPresented: $isPresentingDeleteAlert) {
|
||||
Alert(
|
||||
title: Text("Delete \(zimFile.name)"),
|
||||
message: Text("The zim file and all bookmarked articles linked to this zim file will be deleted."),
|
||||
primaryButton: .destructive(Text("Delete")) {
|
||||
title: Text("Delete %@".localizedWithFormat(withArgs: zimFile.name)),
|
||||
message: Text("loc-ZimFileDetail-Alert-Delete".localized),
|
||||
primaryButton: .destructive(Text("Delete".localized)) {
|
||||
LibraryOperations.delete(zimFileID: zimFile.fileID)
|
||||
#if os(iOS)
|
||||
presentationMode.wrappedValue.dismiss()
|
||||
@ -151,7 +148,7 @@ struct ZimFileDetail: View {
|
||||
}
|
||||
|
||||
var downloadAction: some View {
|
||||
Action(title: "Download") {
|
||||
Action(title: "Download".localized) {
|
||||
if let freeSpace = freeSpace, zimFile.size >= freeSpace - 10^9 {
|
||||
isPresentingDownloadAlert = true
|
||||
} else {
|
||||
@ -159,15 +156,15 @@ struct ZimFileDetail: View {
|
||||
}
|
||||
}.alert(isPresented: $isPresentingDownloadAlert) {
|
||||
Alert(
|
||||
title: Text("Space Warning"),
|
||||
title: Text("Space Warning".localized),
|
||||
message: Text({
|
||||
if let freeSpace = freeSpace, zimFile.size > freeSpace {
|
||||
return "There might not be enough space on your device for this zim file."
|
||||
return "There might not be enough space on your device for this zim file.".localized
|
||||
} else {
|
||||
return "There would be less than 1GB space left after the zim file is downloaded."
|
||||
return "There would be less than 1GB space left after the zim file is downloaded.".localized
|
||||
}
|
||||
}()),
|
||||
primaryButton: .default(Text("Download Anyway")) {
|
||||
primaryButton: .default(Text("Download Anyway".localized)) {
|
||||
DownloadService.shared.start(zimFileID: zimFile.id, allowsCellularAccess: false)
|
||||
},
|
||||
secondaryButton: .cancel()
|
||||
@ -177,37 +174,38 @@ struct ZimFileDetail: View {
|
||||
|
||||
@ViewBuilder
|
||||
var basicInfo: some View {
|
||||
Attribute(title: "Language", detail: Locale.current.localizedString(forLanguageCode: zimFile.languageCode))
|
||||
Attribute(title: "Category", detail: Category(rawValue: zimFile.category)?.description)
|
||||
Attribute(title: "Size", detail: Formatter.size.string(fromByteCount: zimFile.size))
|
||||
Attribute(title: "Created", detail: Formatter.dateMedium.string(from: zimFile.created))
|
||||
Attribute(title: "Language".localized,
|
||||
detail: Locale.current.localizedString(forLanguageCode: zimFile.languageCode))
|
||||
Attribute(title: "Category".localized, detail: Category(rawValue: zimFile.category)?.description)
|
||||
Attribute(title: "Size".localized, detail: Formatter.size.string(fromByteCount: zimFile.size))
|
||||
Attribute(title: "Created".localized, detail: Formatter.dateMedium.string(from: zimFile.created))
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
var boolInfo: some View {
|
||||
AttributeBool(title: "Pictures", detail: zimFile.hasPictures)
|
||||
AttributeBool(title: "Videos", detail: zimFile.hasVideos)
|
||||
AttributeBool(title: "Details", detail: zimFile.hasDetails)
|
||||
AttributeBool(title: "Pictures".localized, detail: zimFile.hasPictures)
|
||||
AttributeBool(title: "Videos".localized, detail: zimFile.hasVideos)
|
||||
AttributeBool(title: "Details".localized, detail: zimFile.hasDetails)
|
||||
if zimFile.requiresServiceWorkers {
|
||||
AttributeBool(title: "Requires Service Workers", detail: zimFile.requiresServiceWorkers)
|
||||
AttributeBool(title: "Requires Service Workers".localized, detail: zimFile.requiresServiceWorkers)
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
var counts: some View {
|
||||
Attribute(
|
||||
title: "Article Count",
|
||||
title: "Article Count".localized,
|
||||
detail: Formatter.number.string(from: NSNumber(value: zimFile.articleCount))
|
||||
)
|
||||
Attribute(
|
||||
title: "Media Count",
|
||||
title: "Media Count".localized,
|
||||
detail: Formatter.number.string(from: NSNumber(value: zimFile.mediaCount))
|
||||
)
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
var id: some View {
|
||||
Attribute(title: "ID", detail: String(zimFile.fileID.uuidString.prefix(8)))
|
||||
Attribute(title: "ID".localized, detail: String(zimFile.fileID.uuidString.prefix(8)))
|
||||
}
|
||||
|
||||
private var freeSpace: Int64? {
|
||||
@ -237,27 +235,27 @@ private struct DownloadTaskDetail: View {
|
||||
@ObservedObject var downloadTask: DownloadTask
|
||||
|
||||
var body: some View {
|
||||
Action(title: "Cancel", isDestructive: true) {
|
||||
Action(title: "Cancel".localized, isDestructive: true) {
|
||||
DownloadService.shared.cancel(zimFileID: downloadTask.fileID)
|
||||
}
|
||||
if let error = downloadTask.error {
|
||||
if downloadTask.resumeData != nil {
|
||||
Action(title: "Try to Recover") {
|
||||
Action(title: "Try to Recover".localized) {
|
||||
DownloadService.shared.resume(zimFileID: downloadTask.fileID)
|
||||
}
|
||||
}
|
||||
Attribute(title: "Failed", detail: detail)
|
||||
Attribute(title: "Failed".localized, detail: detail)
|
||||
Text(error)
|
||||
} else if downloadTask.resumeData == nil {
|
||||
Action(title: "Pause") {
|
||||
Action(title: "Pause".localized) {
|
||||
DownloadService.shared.pause(zimFileID: downloadTask.fileID)
|
||||
}
|
||||
Attribute(title: "Downloading...", detail: detail)
|
||||
Attribute(title: "Downloading...".localized, detail: detail)
|
||||
} else {
|
||||
Action(title: "Resume") {
|
||||
Action(title: "Resume".localized) {
|
||||
DownloadService.shared.resume(zimFileID: downloadTask.fileID)
|
||||
}
|
||||
Attribute(title: "Paused", detail: detail)
|
||||
Attribute(title: "Paused".localized, detail: detail)
|
||||
}
|
||||
}
|
||||
|
||||
@ -313,7 +311,7 @@ private struct Action: View {
|
||||
private struct ServiceWorkerWarning: View {
|
||||
var body: some View {
|
||||
Label {
|
||||
Text("Zim files requiring service workers are not supported.")
|
||||
Text("Zim files requiring service workers are not supported.".localized)
|
||||
} icon: {
|
||||
Image(systemName: "exclamationmark.triangle.fill").renderingMode(.original)
|
||||
}
|
||||
@ -329,7 +327,7 @@ struct ZimFileDetail_Previews: PreviewProvider {
|
||||
zimFile.created = Date()
|
||||
zimFile.downloadURL = URL(string: "https://www.example.com")
|
||||
zimFile.fileID = UUID()
|
||||
zimFile.fileDescription = "A very long description"
|
||||
zimFile.fileDescription = "A very long description".localized
|
||||
zimFile.flavor = "max"
|
||||
zimFile.hasDetails = true
|
||||
zimFile.hasPictures = false
|
||||
|
@ -17,7 +17,7 @@ struct ZimFilesCategories: View {
|
||||
var body: some View {
|
||||
ZimFilesCategory(category: $selected)
|
||||
.modifier(ToolbarRoleBrowser())
|
||||
.navigationTitle(NavigationItem.categories.name)
|
||||
.navigationTitle(NavigationItem.categories.name.localized)
|
||||
.toolbar {
|
||||
#if os(iOS)
|
||||
ToolbarItem(placement: .navigationBarLeading) {
|
||||
@ -25,13 +25,13 @@ struct ZimFilesCategories: View {
|
||||
Button {
|
||||
NotificationCenter.toggleSidebar()
|
||||
} label: {
|
||||
Label("Show Sidebar", systemImage: "sidebar.left")
|
||||
Label("Show Sidebar".localized, systemImage: "sidebar.left")
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
ToolbarItem {
|
||||
Picker("Category", selection: $selected) {
|
||||
Picker("Category".localized, selection: $selected) {
|
||||
ForEach(Category.allCases) {
|
||||
Text($0.name).tag($0)
|
||||
}
|
||||
@ -91,7 +91,7 @@ private struct CategoryGrid: View {
|
||||
var body: some View {
|
||||
Group {
|
||||
if sections.isEmpty {
|
||||
Message(text: "No zim file under this category.")
|
||||
Message(text: "No zim file under this category.".localized)
|
||||
} else {
|
||||
LazyVGrid(columns: ([gridItem]), alignment: .leading, spacing: 12) {
|
||||
ForEach(sections) { section in
|
||||
@ -187,7 +187,7 @@ private struct CategoryList: View {
|
||||
var body: some View {
|
||||
Group {
|
||||
if zimFiles.isEmpty {
|
||||
Message(text: "No zim file under this category.")
|
||||
Message(text: "No zim file under this category.".localized)
|
||||
} else {
|
||||
List(zimFiles, id: \.self, selection: $viewModel.selectedZimFile) { zimFile in
|
||||
ZimFileRow(zimFile)
|
||||
|
@ -31,10 +31,10 @@ struct ZimFilesDownloads: View {
|
||||
}
|
||||
.modifier(GridCommon())
|
||||
.modifier(ToolbarRoleBrowser())
|
||||
.navigationTitle(NavigationItem.downloads.name)
|
||||
.navigationTitle(NavigationItem.downloads.name.localized)
|
||||
.overlay {
|
||||
if downloadTasks.isEmpty {
|
||||
Message(text: "No download tasks")
|
||||
Message(text: "No download tasks".localized)
|
||||
}
|
||||
}
|
||||
.toolbar {
|
||||
@ -44,7 +44,7 @@ struct ZimFilesDownloads: View {
|
||||
Button {
|
||||
NotificationCenter.toggleSidebar()
|
||||
} label: {
|
||||
Label("Show Sidebar", systemImage: "sidebar.left")
|
||||
Label("Show Sidebar".localized, systemImage: "sidebar.left")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -39,7 +39,7 @@ struct ZimFilesNew: View {
|
||||
}
|
||||
.modifier(GridCommon())
|
||||
.modifier(ToolbarRoleBrowser())
|
||||
.navigationTitle(NavigationItem.new.name)
|
||||
.navigationTitle(NavigationItem.new.name.localized)
|
||||
.searchable(text: $searchText)
|
||||
.onAppear {
|
||||
viewModel.start(isUserInitiated: false)
|
||||
@ -52,7 +52,7 @@ struct ZimFilesNew: View {
|
||||
}
|
||||
.overlay {
|
||||
if zimFiles.isEmpty {
|
||||
Message(text: "No new zim file")
|
||||
Message(text: "No new zim file".localized)
|
||||
}
|
||||
}
|
||||
.toolbar {
|
||||
@ -62,7 +62,7 @@ struct ZimFilesNew: View {
|
||||
Button {
|
||||
NotificationCenter.toggleSidebar()
|
||||
} label: {
|
||||
Label("Show Sidebar", systemImage: "sidebar.left")
|
||||
Label("Show Sidebar".localized, systemImage: "sidebar.left")
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -77,7 +77,7 @@ struct ZimFilesNew: View {
|
||||
Button {
|
||||
viewModel.start(isUserInitiated: true)
|
||||
} label: {
|
||||
Label("Refresh", systemImage: "arrow.triangle.2.circlepath.circle")
|
||||
Label("Refresh".localized, systemImage: "arrow.triangle.2.circlepath.circle")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -31,10 +31,10 @@ struct ZimFilesOpened: View {
|
||||
}
|
||||
.modifier(GridCommon(edges: .all))
|
||||
.modifier(ToolbarRoleBrowser())
|
||||
.navigationTitle(NavigationItem.opened.name)
|
||||
.navigationTitle(NavigationItem.opened.name.localized)
|
||||
.overlay {
|
||||
if zimFiles.isEmpty {
|
||||
Message(text: "No opened zim file")
|
||||
Message(text: "No opened zim file".localized)
|
||||
}
|
||||
}
|
||||
// not using OpenFileButton here, because it does not work on iOS/iPadOS 15 when this view is in a modal
|
||||
@ -53,7 +53,7 @@ struct ZimFilesOpened: View {
|
||||
Button {
|
||||
NotificationCenter.toggleSidebar()
|
||||
} label: {
|
||||
Label("Show Sidebar", systemImage: "sidebar.left")
|
||||
Label("Show Sidebar".localized, systemImage: "sidebar.left")
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -68,8 +68,8 @@ struct ZimFilesOpened: View {
|
||||
}
|
||||
isFileImporterPresented = true
|
||||
} label: {
|
||||
Label("Open...", systemImage: "plus")
|
||||
}.help("Open a zim file")
|
||||
Label("Open...".localized, systemImage: "plus")
|
||||
}.help("Open a zim file".localized)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ struct SearchResults: View {
|
||||
var body: some View {
|
||||
Group {
|
||||
if zimFiles.isEmpty {
|
||||
Message(text: "No opened zim file")
|
||||
Message(text: "No opened zim file".localized)
|
||||
} else if horizontalSizeClass == .regular {
|
||||
HStack(spacing: 0) {
|
||||
#if os(macOS)
|
||||
@ -66,7 +66,7 @@ struct SearchResults: View {
|
||||
Spacer()
|
||||
}
|
||||
} else if viewModel.results.isEmpty {
|
||||
Message(text: "No result")
|
||||
Message(text: "No result".localized)
|
||||
} else {
|
||||
ScrollView {
|
||||
LazyVGrid(columns: [GridItem(.flexible(minimum: 300, maximum: 700), alignment: .center)]) {
|
||||
@ -98,7 +98,7 @@ struct SearchResults: View {
|
||||
viewModel.searchText = searchText
|
||||
}
|
||||
}.swipeActions {
|
||||
Button("Remove", role: .destructive) {
|
||||
Button("Remove".localized, role: .destructive) {
|
||||
recentSearchTexts.removeAll { $0 == searchText }
|
||||
}
|
||||
}
|
||||
@ -126,39 +126,39 @@ struct SearchResults: View {
|
||||
|
||||
private var recentSearchHeader: some View {
|
||||
HStack {
|
||||
Text("Recent Search")
|
||||
Text("Recent Search".localized)
|
||||
Spacer()
|
||||
Button {
|
||||
isClearSearchConfirmationPresented = true
|
||||
} label: {
|
||||
Text("Clear").font(.caption).fontWeight(.medium)
|
||||
}.confirmationDialog("Clear Recent Searches", isPresented: $isClearSearchConfirmationPresented) {
|
||||
Button("Clear All", role: .destructive) {
|
||||
Text("Clear".localized).font(.caption).fontWeight(.medium)
|
||||
}.confirmationDialog("Clear Recent Searches".localized, isPresented: $isClearSearchConfirmationPresented) {
|
||||
Button("Clear All".localized, role: .destructive) {
|
||||
recentSearchTexts.removeAll()
|
||||
}
|
||||
} message: {
|
||||
Text("All recent search history will be removed.")
|
||||
Text("All recent search history will be removed.".localized)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var searchFilterHeader: some View {
|
||||
HStack {
|
||||
Text("Included in Search")
|
||||
Text("Included in Search".localized)
|
||||
Spacer()
|
||||
if zimFiles.count == zimFiles.filter({ $0.includedInSearch }).count {
|
||||
Button {
|
||||
zimFiles.forEach { $0.includedInSearch = false }
|
||||
try? managedObjectContext.save()
|
||||
} label: {
|
||||
Text("None").font(.caption).fontWeight(.medium)
|
||||
Text("None".localized).font(.caption).fontWeight(.medium)
|
||||
}
|
||||
} else {
|
||||
Button {
|
||||
zimFiles.forEach { $0.includedInSearch = true }
|
||||
try? managedObjectContext.save()
|
||||
} label: {
|
||||
Text("All").font(.caption).fontWeight(.medium)
|
||||
Text("All".localized).font(.caption).fontWeight(.medium)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -17,27 +17,27 @@ struct About: View {
|
||||
var body: some View {
|
||||
#if os(macOS)
|
||||
VStack(spacing: 16) {
|
||||
SettingSection(name: "About") {
|
||||
SettingSection(name: "About".localized) {
|
||||
about
|
||||
ourWebsite
|
||||
}
|
||||
SettingSection(name: "Release") {
|
||||
SettingSection(name: "Release".localized) {
|
||||
release
|
||||
HStack {
|
||||
source
|
||||
license
|
||||
}
|
||||
}
|
||||
SettingSection(name: "Dependencies", alignment: .top) {
|
||||
SettingSection(name: "Dependencies".localized, alignment: .top) {
|
||||
Table(dependencies) {
|
||||
TableColumn("Name", value: \.name)
|
||||
TableColumn("License") { dependency in Text(dependency.license ?? "") }
|
||||
TableColumn("Version", value: \.version)
|
||||
TableColumn("Name".localized, value: \.name)
|
||||
TableColumn("License".localized) { dependency in Text(dependency.license ?? "") }
|
||||
TableColumn("Version".localized, value: \.version)
|
||||
}.tableStyle(.bordered(alternatesRowBackgrounds: true))
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
.tabItem { Label("About", systemImage: "info.circle") }
|
||||
.tabItem { Label("About".localized, systemImage: "info.circle") }
|
||||
.task { await getDependencies() }
|
||||
.onChange(of: externalLinkURL) { url in
|
||||
guard let url = url else { return }
|
||||
@ -49,14 +49,14 @@ struct About: View {
|
||||
about
|
||||
ourWebsite
|
||||
}
|
||||
Section("Release") {
|
||||
Section("Release".localized) {
|
||||
release
|
||||
appVersion
|
||||
buildNumber
|
||||
source
|
||||
license
|
||||
}
|
||||
Section("Dependencies") {
|
||||
Section("Dependencies".localized) {
|
||||
ForEach(dependencies) { dependency in
|
||||
HStack {
|
||||
Text(dependency.name)
|
||||
@ -70,7 +70,7 @@ struct About: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle("About")
|
||||
.navigationTitle("About".localized)
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.sheet(item: $externalLinkURL) { SafariView(url: $0) }
|
||||
.task { await getDependencies() }
|
||||
@ -78,41 +78,36 @@ struct About: View {
|
||||
}
|
||||
|
||||
private var about: some View {
|
||||
Text(
|
||||
"""
|
||||
Kiwix is an offline reader for online content like Wikipedia, Project Gutenberg, or TED Talks. \
|
||||
It makes knowledge available to people with no or limited internet access. \
|
||||
The software as well as the content is free to use for anyone.
|
||||
"""
|
||||
)
|
||||
Text("loc-About-description".localized)
|
||||
}
|
||||
|
||||
private var release: some View {
|
||||
Text("This app is released under the terms of the GNU General Public License version 3.")
|
||||
Text("This app is released under the terms of the GNU General Public License version 3.".localized)
|
||||
}
|
||||
|
||||
private var appVersion: some View {
|
||||
Attribute(title: "Version", detail: Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String)
|
||||
Attribute(title: "Version".localized,
|
||||
detail: Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String)
|
||||
}
|
||||
|
||||
private var buildNumber: some View {
|
||||
Attribute(title: "Build", detail: Bundle.main.infoDictionary?["CFBundleVersion"] as? String)
|
||||
Attribute(title: "Build".localized, detail: Bundle.main.infoDictionary?["CFBundleVersion"] as? String)
|
||||
}
|
||||
|
||||
private var ourWebsite: some View {
|
||||
Button("Our Website") {
|
||||
Button("Our Website".localized) {
|
||||
externalLinkURL = URL(string: "https://www.kiwix.org")
|
||||
}
|
||||
}
|
||||
|
||||
private var source: some View {
|
||||
Button("Source") {
|
||||
Button("Source".localized) {
|
||||
externalLinkURL = URL(string: "https://github.com/kiwix/apple")
|
||||
}
|
||||
}
|
||||
|
||||
private var license: some View {
|
||||
Button("GNU General Public License v3") {
|
||||
Button("GNU General Public License v3".localized) {
|
||||
externalLinkURL = URL(string: "https://www.gnu.org/licenses/gpl-3.0.en.html")
|
||||
}
|
||||
}
|
||||
|
@ -30,8 +30,8 @@ struct LanguageSelector: View {
|
||||
}
|
||||
})
|
||||
}.width(14)
|
||||
TableColumn("Name", value: \.name)
|
||||
TableColumn("Count", value: \.count) { language in Text(language.count.formatted()) }
|
||||
TableColumn("Name".localized, value: \.name)
|
||||
TableColumn("Count".localized, value: \.count) { language in Text(language.count.formatted()) }
|
||||
}
|
||||
.tableStyle(.bordered(alternatesRowBackgrounds: true))
|
||||
.onChange(of: sortOrder) { languages.sort(using: $0) }
|
||||
@ -51,29 +51,29 @@ struct LanguageSelector: View {
|
||||
List {
|
||||
Section {
|
||||
if showing.isEmpty {
|
||||
Text("No language").foregroundColor(.secondary)
|
||||
Text("No language".localized).foregroundColor(.secondary)
|
||||
} else {
|
||||
ForEach(showing) { language in
|
||||
Button { hide(language) } label: { LanguageLabel(language: language) }
|
||||
}
|
||||
}
|
||||
} header: { Text("Showing") }
|
||||
} header: { Text("Showing".localized) }
|
||||
Section {
|
||||
ForEach(hiding) { language in
|
||||
Button { show(language) } label: { LanguageLabel(language: language) }
|
||||
}
|
||||
} header: { Text("Hiding") }
|
||||
} header: { Text("Hiding".localized) }
|
||||
}
|
||||
.listStyle(.insetGrouped)
|
||||
.navigationTitle("Languages")
|
||||
.navigationTitle("Languages".localized)
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
Picker(selection: $sortingMode) {
|
||||
ForEach(LibraryLanguageSortingMode.allCases) { sortingMode in
|
||||
Text(sortingMode.name).tag(sortingMode)
|
||||
Text(sortingMode.name.localized).tag(sortingMode)
|
||||
}
|
||||
} label: {
|
||||
Label("Sorting", systemImage: "arrow.up.arrow.down")
|
||||
Label("Sorting".localized, systemImage: "arrow.up.arrow.down")
|
||||
}.pickerStyle(.menu)
|
||||
}
|
||||
.onAppear {
|
||||
|
@ -18,30 +18,30 @@ struct ReadingSettings: View {
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 16) {
|
||||
SettingSection(name: "Page zoom") {
|
||||
SettingSection(name: "Page zoom".localized) {
|
||||
HStack {
|
||||
Stepper(webViewPageZoom.formatted(.percent), value: $webViewPageZoom, in: 0.5...2, step: 0.05)
|
||||
Spacer()
|
||||
Button("Reset") { webViewPageZoom = 1 }.disabled(webViewPageZoom == 1)
|
||||
Button("Reset".localized) { webViewPageZoom = 1 }.disabled(webViewPageZoom == 1)
|
||||
}
|
||||
}
|
||||
SettingSection(name: "External link") {
|
||||
SettingSection(name: "External link".localized) {
|
||||
Picker(selection: $externalLinkLoadingPolicy) {
|
||||
ForEach(ExternalLinkLoadingPolicy.allCases) { loadingPolicy in
|
||||
Text(loadingPolicy.name).tag(loadingPolicy)
|
||||
Text(loadingPolicy.name.localized).tag(loadingPolicy)
|
||||
}
|
||||
} label: { }
|
||||
}
|
||||
SettingSection(name: "Search snippet") {
|
||||
SettingSection(name: "Search snippet".localized) {
|
||||
Picker(selection: $searchResultSnippetMode) {
|
||||
ForEach(SearchResultSnippetMode.allCases) { snippetMode in
|
||||
Text(snippetMode.name).tag(snippetMode)
|
||||
Text(snippetMode.name.localized).tag(snippetMode)
|
||||
}
|
||||
} label: { }
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
.tabItem { Label("Reading", systemImage: "book") }
|
||||
.tabItem { Label("Reading".localized, systemImage: "book") }
|
||||
}
|
||||
}
|
||||
|
||||
@ -51,30 +51,30 @@ struct LibrarySettings: View {
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 16) {
|
||||
SettingSection(name: "Catalog") {
|
||||
SettingSection(name: "Catalog".localized) {
|
||||
HStack(spacing: 6) {
|
||||
Button("Refresh Now") {
|
||||
Button("Refresh Now".localized) {
|
||||
library.start(isUserInitiated: true)
|
||||
}.disabled(library.isInProgress)
|
||||
if library.isInProgress {
|
||||
ProgressView().progressViewStyle(.circular).scaleEffect(0.5).frame(height: 1)
|
||||
}
|
||||
Spacer()
|
||||
Text("Last refresh:").foregroundColor(.secondary)
|
||||
Text("Last refresh".localized + ":").foregroundColor(.secondary)
|
||||
LibraryLastRefreshTime().foregroundColor(.secondary)
|
||||
}
|
||||
VStack(alignment: .leading) {
|
||||
Toggle("Auto refresh", isOn: $libraryAutoRefresh)
|
||||
Text("When enabled, the library catalog will be refreshed automatically when outdated.")
|
||||
Toggle("Auto refresh".localized, isOn: $libraryAutoRefresh)
|
||||
Text("When enabled, the library catalog will be refreshed automatically when outdated.".localized)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
}
|
||||
SettingSection(name: "Languages", alignment: .top) {
|
||||
SettingSection(name: "Languages".localized, alignment: .top) {
|
||||
LanguageSelector()
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
.tabItem { Label("Library", systemImage: "folder.badge.gearshape") }
|
||||
.tabItem { Label("Library".localized, systemImage: "folder.badge.gearshape") }
|
||||
}
|
||||
}
|
||||
|
||||
@ -126,22 +126,23 @@ struct Settings: View {
|
||||
miscellaneous
|
||||
}
|
||||
.modifier(ToolbarRoleBrowser())
|
||||
.navigationTitle("Settings")
|
||||
.navigationTitle("Settings".localized)
|
||||
}
|
||||
|
||||
var readingSettings: some View {
|
||||
Section("Reading") {
|
||||
Section("Reading".localized) {
|
||||
Stepper(value: $webViewPageZoom, in: 0.5...2, step: 0.05) {
|
||||
Text("Page zoom: \(Formatter.percent.string(from: NSNumber(value: webViewPageZoom)) ?? "")")
|
||||
Text("Page zoom".localized +
|
||||
": \(Formatter.percent.string(from: NSNumber(value: webViewPageZoom)) ?? "")")
|
||||
}
|
||||
Picker("External link", selection: $externalLinkLoadingPolicy) {
|
||||
Picker("External link".localized, selection: $externalLinkLoadingPolicy) {
|
||||
ForEach(ExternalLinkLoadingPolicy.allCases) { loadingPolicy in
|
||||
Text(loadingPolicy.name).tag(loadingPolicy)
|
||||
Text(loadingPolicy.name.localized).tag(loadingPolicy)
|
||||
}
|
||||
}
|
||||
Picker("Search snippet", selection: $searchResultSnippetMode) {
|
||||
Picker("Search snippet".localized, selection: $searchResultSnippetMode) {
|
||||
ForEach(SearchResultSnippetMode.allCases) { snippetMode in
|
||||
Text(snippetMode.name).tag(snippetMode)
|
||||
Text(snippetMode.name.localized).tag(snippetMode)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -154,58 +155,58 @@ struct Settings: View {
|
||||
} label: {
|
||||
SelectedLanaguageLabel()
|
||||
}
|
||||
Toggle("Download using cellular", isOn: $downloadUsingCellular)
|
||||
Toggle("Download using cellular".localized, isOn: $downloadUsingCellular)
|
||||
} header: {
|
||||
Text("Library")
|
||||
Text("Library".localized)
|
||||
} footer: {
|
||||
Text("Change will only apply to new download tasks.")
|
||||
Text("Change will only apply to new download tasks.".localized)
|
||||
}
|
||||
}
|
||||
|
||||
var catalogSettings: some View {
|
||||
Section {
|
||||
HStack {
|
||||
Text("Last refresh")
|
||||
Text("Last refresh".localized)
|
||||
Spacer()
|
||||
LibraryLastRefreshTime().foregroundColor(.secondary)
|
||||
}
|
||||
if library.isInProgress {
|
||||
HStack {
|
||||
Text("Refreshing...").foregroundColor(.secondary)
|
||||
Text("Refreshing...".localized).foregroundColor(.secondary)
|
||||
Spacer()
|
||||
ProgressView().progressViewStyle(.circular)
|
||||
}
|
||||
} else {
|
||||
Button("Refresh Now") {
|
||||
Button("Refresh Now".localized) {
|
||||
library.start(isUserInitiated: true)
|
||||
}
|
||||
}
|
||||
Toggle("Auto refresh", isOn: $libraryAutoRefresh)
|
||||
Toggle("Auto refresh".localized, isOn: $libraryAutoRefresh)
|
||||
} header: {
|
||||
Text("Catalog")
|
||||
Text("Catalog".localized)
|
||||
} footer: {
|
||||
Text("When enabled, the library catalog will be refreshed automatically when outdated.")
|
||||
Text("When enabled, the library catalog will be refreshed automatically when outdated.".localized)
|
||||
}.onChange(of: libraryAutoRefresh) { LibraryOperations.applyLibraryAutoRefreshSetting(isEnabled: $0) }
|
||||
}
|
||||
|
||||
var backupSettings: some View {
|
||||
Section {
|
||||
Toggle("Include zim files in backup", isOn: $backupDocumentDirectory)
|
||||
Toggle("Include zim files in backup".localized, isOn: $backupDocumentDirectory)
|
||||
} header: {
|
||||
Text("Backup")
|
||||
Text("Backup".localized)
|
||||
} footer: {
|
||||
Text("Does not apply to files opened in place.")
|
||||
Text("Does not apply to files opened in place.".localized)
|
||||
}.onChange(of: backupDocumentDirectory) { LibraryOperations.applyFileBackupSetting(isEnabled: $0) }
|
||||
}
|
||||
|
||||
var miscellaneous: some View {
|
||||
Section("Misc") {
|
||||
Button("Feedback") { UIApplication.shared.open(URL(string: "mailto:feedback@kiwix.org")!) }
|
||||
Button("Rate the App") {
|
||||
Section("Misc".localized) {
|
||||
Button("Feedback".localized) { UIApplication.shared.open(URL(string: "mailto:feedback@kiwix.org")!) }
|
||||
Button("Rate the App".localized) {
|
||||
let url = URL(string: "itms-apps://itunes.apple.com/us/app/kiwix/id997079563?action=write-review")!
|
||||
UIApplication.shared.open(url)
|
||||
}
|
||||
NavigationLink("About") { About() }
|
||||
NavigationLink("About".localized) { About() }
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -215,7 +216,7 @@ private struct SelectedLanaguageLabel: View {
|
||||
|
||||
var body: some View {
|
||||
HStack {
|
||||
Text("Languages")
|
||||
Text("Languages".localized)
|
||||
Spacer()
|
||||
if languageCodes.count == 1,
|
||||
let languageCode = languageCodes.first,
|
||||
|
@ -21,7 +21,7 @@ struct AlertHandler: ViewModifier {
|
||||
.alert(item: $activeAlert) { alert in
|
||||
switch alert {
|
||||
case .articleFailedToLoad:
|
||||
return Alert(title: Text("Unable to load the article requested."))
|
||||
return Alert(title: Text("Unable to load the article requested.".localized))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -18,13 +18,13 @@ struct BookmarkContextMenu: ViewModifier {
|
||||
Button {
|
||||
NotificationCenter.openURL(bookmark.articleURL)
|
||||
} label: {
|
||||
Label("View", systemImage: "doc.richtext")
|
||||
Label("View".localized, systemImage: "doc.richtext")
|
||||
}
|
||||
Button(role: .destructive) {
|
||||
managedObjectContext.delete(bookmark)
|
||||
try? managedObjectContext.save()
|
||||
} label: {
|
||||
Label("Remove", systemImage: "star.slash.fill")
|
||||
Label("Remove".localized, systemImage: "star.slash.fill")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -40,19 +40,19 @@ struct ExternalLinkHandler: ViewModifier {
|
||||
activeAlert = .notLoading
|
||||
}
|
||||
}
|
||||
.alert("External Link", isPresented: $isAlertPresented, presenting: activeAlert) { alert in
|
||||
.alert("External Link".localized, isPresented: $isAlertPresented, presenting: activeAlert) { alert in
|
||||
if case .ask(let url) = alert {
|
||||
Button("Load the link") {
|
||||
Button("Load the link".localized) {
|
||||
load(url: url)
|
||||
}
|
||||
Button("Cancel", role: .cancel) { }
|
||||
Button("Cancel".localized, role: .cancel) { }
|
||||
}
|
||||
} message: { alert in
|
||||
switch alert {
|
||||
case .ask:
|
||||
Text("An external link is tapped, do you wish to load the link?")
|
||||
Text("An external link is tapped, do you wish to load the link?".localized)
|
||||
case .notLoading:
|
||||
Text("An external link is tapped. However, your current setting does not allow it to be loaded.")
|
||||
Text("loc-external-alert".localized)
|
||||
}
|
||||
}
|
||||
#if os(iOS)
|
||||
|
@ -41,7 +41,7 @@ struct OpenFileButton<Label: View>: View {
|
||||
guard case let .success(urls) = result else { return }
|
||||
NotificationCenter.openFiles(urls, context: context)
|
||||
}
|
||||
.help("Open a zim file")
|
||||
.help("Open a zim file".localized)
|
||||
.keyboardShortcut("o")
|
||||
}
|
||||
}
|
||||
@ -98,11 +98,12 @@ struct OpenFileHandler: ViewModifier {
|
||||
isAlertPresented = true
|
||||
activeAlert = .unableToOpen(filenames: invalidURLs.map({ $0.lastPathComponent }))
|
||||
}
|
||||
}.alert("Unable to open file", isPresented: $isAlertPresented, presenting: activeAlert) { _ in
|
||||
}.alert("Unable to open file".localized, isPresented: $isAlertPresented, presenting: activeAlert) { _ in
|
||||
} message: { alert in
|
||||
switch alert {
|
||||
case .unableToOpen(let filenames):
|
||||
Text("\(ListFormatter.localizedString(byJoining: filenames)) cannot be opened.")
|
||||
let name = ListFormatter.localizedString(byJoining: filenames)
|
||||
Text("%@ cannot be opened.".localizedWithFormat(withArgs: name))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -67,7 +67,7 @@ struct Welcome: View {
|
||||
alignment: .leading,
|
||||
spacing: 12
|
||||
) {
|
||||
GridSection(title: "Main Page") {
|
||||
GridSection(title: "Main Page".localized) {
|
||||
ForEach(zimFiles) { zimFile in
|
||||
Button {
|
||||
guard let url = ZimFileService.shared.getMainPageURL(zimFileID: zimFile.fileID) else { return }
|
||||
@ -78,7 +78,7 @@ struct Welcome: View {
|
||||
}
|
||||
}
|
||||
if !bookmarks.isEmpty {
|
||||
GridSection(title: "Bookmarks") {
|
||||
GridSection(title: "Bookmarks".localized) {
|
||||
ForEach(bookmarks.prefix(6)) { bookmark in
|
||||
Button {
|
||||
browser.load(url: bookmark.articleURL)
|
||||
@ -112,7 +112,7 @@ struct Welcome: View {
|
||||
OpenFileButton(context: .onBoarding) {
|
||||
HStack {
|
||||
Spacer()
|
||||
Text("Open File")
|
||||
Text("Open File".localized)
|
||||
Spacer()
|
||||
}.padding(6)
|
||||
}
|
||||
@ -123,15 +123,15 @@ struct Welcome: View {
|
||||
Spacer()
|
||||
if library.isInProgress {
|
||||
#if os(macOS)
|
||||
Text("Fetching...")
|
||||
Text("Fetching...".localized)
|
||||
#elseif os(iOS)
|
||||
HStack(spacing: 6) {
|
||||
ProgressView().frame(maxHeight: 10)
|
||||
Text("Fetching...")
|
||||
Text("Fetching...".localized)
|
||||
}
|
||||
#endif
|
||||
} else {
|
||||
Text("Fetch Catalog")
|
||||
Text("Fetch Catalog".localized)
|
||||
}
|
||||
Spacer()
|
||||
}.padding(6)
|
||||
|
1
WikiMed/es.lproj/LaunchScreen.strings
Normal file
1
WikiMed/es.lproj/LaunchScreen.strings
Normal file
@ -0,0 +1 @@
|
||||
|
1
WikiMed/es.lproj/Main.strings
Normal file
1
WikiMed/es.lproj/Main.strings
Normal file
@ -0,0 +1 @@
|
||||
|
Loading…
x
Reference in New Issue
Block a user