From e88d1466d45ee1802a85f71e64e876d3cb8bca5c Mon Sep 17 00:00:00 2001 From: tvision106 Date: Tue, 14 Nov 2023 16:20:36 -0500 Subject: [PATCH 01/19] Update the localization --- App/App_macOS.swift | 12 +- App/SidebarViewController.swift | 10 +- App/SplitViewController.swift | 2 +- Kiwix.xcodeproj/project.pbxproj | 33 ++- Model/DownloadService.swift | 4 +- Model/Entities/Errors.swift | 6 +- Model/Utilities/String+Extension.swift | 32 +++ Support/en.lproj/Localizable.strings | 202 ++++++++++++++++++ Support/es.lproj/Localizable.strings | 8 + ViewModel/BrowserViewModel.swift | 8 +- Views/Bookmarks.swift | 6 +- Views/BrowserTab.swift | 2 +- Views/BuildingBlocks/ArticleCell.swift | 6 +- Views/BuildingBlocks/Attribute.swift | 4 +- Views/BuildingBlocks/DownloadTaskCell.swift | 4 +- Views/BuildingBlocks/FlavorTag.swift | 6 +- Views/BuildingBlocks/GridSection.swift | 4 +- .../LibraryLastRefreshTime.swift | 4 +- Views/BuildingBlocks/Message.swift | 2 +- Views/BuildingBlocks/SearchResultRow.swift | 4 +- Views/BuildingBlocks/SheetContent.swift | 2 +- Views/BuildingBlocks/TabLabel.swift | 4 +- .../ZimFileMissingIndicator.swift | 2 +- Views/BuildingBlocks/ZimFileRow.swift | 2 +- Views/Buttons/ArticleShortcutButtons.swift | 16 +- Views/Buttons/BookmarkButton.swift | 14 +- Views/Buttons/NavigationButtons.swift | 4 +- Views/Buttons/OutlineButton.swift | 10 +- Views/Buttons/TabsManagerButton.swift | 26 +-- Views/Commands.swift | 10 +- Views/Library/Library.swift | 14 +- Views/Library/ZimFileDetail.swift | 78 +++---- Views/Library/ZimFilesCategories.swift | 10 +- Views/Library/ZimFilesDownloads.swift | 6 +- Views/Library/ZimFilesNew.swift | 8 +- Views/Library/ZimFilesOpened.swift | 10 +- Views/SearchResults.swift | 22 +- Views/Settings/About.swift | 34 +-- Views/Settings/LanguageSelector.swift | 16 +- Views/Settings/Settings.swift | 76 +++---- Views/ViewModifiers/AlertHandler.swift | 2 +- Views/ViewModifiers/BookmarkContextMenu.swift | 4 +- Views/ViewModifiers/ExternalLinkHandler.swift | 10 +- Views/ViewModifiers/FileImport.swift | 6 +- Views/Welcome.swift | 12 +- WikiMed/es.lproj/LaunchScreen.strings | 1 + WikiMed/es.lproj/Main.strings | 1 + 47 files changed, 515 insertions(+), 244 deletions(-) create mode 100644 Model/Utilities/String+Extension.swift create mode 100644 Support/en.lproj/Localizable.strings create mode 100644 Support/es.lproj/Localizable.strings create mode 100644 WikiMed/es.lproj/LaunchScreen.strings create mode 100644 WikiMed/es.lproj/Main.strings diff --git a/App/App_macOS.swift b/App/App_macOS.swift index ce6efe2c..2bbdcbf7 100644 --- a/App/App_macOS.swift +++ b/App/App_macOS.swift @@ -32,10 +32,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) @@ -91,11 +91,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) } } } @@ -106,7 +106,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: diff --git a/App/SidebarViewController.swift b/App/SidebarViewController.swift index 836850f3..a29afb15 100644 --- a/App/SidebarViewController.swift +++ b/App/SidebarViewController.swift @@ -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") diff --git a/App/SplitViewController.swift b/App/SplitViewController.swift index 1e830032..45a733c4 100644 --- a/App/SplitViewController.swift +++ b/App/SplitViewController.swift @@ -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) } } diff --git a/Kiwix.xcodeproj/project.pbxproj b/Kiwix.xcodeproj/project.pbxproj index 922d260d..aec5c37c 100644 --- a/Kiwix.xcodeproj/project.pbxproj +++ b/Kiwix.xcodeproj/project.pbxproj @@ -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 */; }; @@ -124,6 +128,11 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 8E4396442B02E443007F0BC4 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Main.strings; sourceTree = ""; }; + 8E4396452B02E443007F0BC4 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/LaunchScreen.strings; sourceTree = ""; }; + 8E4396482B02E455007F0BC4 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; + 8E43964A2B02E458007F0BC4 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Localizable.strings; sourceTree = ""; }; + 8E43964B2B02E4C6007F0BC4 /* String+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Extension.swift"; sourceTree = ""; }; 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 = ""; }; @@ -293,6 +302,7 @@ 9779A7E224567A5A00F6F6FF /* Log.swift */, 97B3BACD2736CE3500A23F49 /* URL.swift */, 9779A73A2456796B00F6F6FF /* WebKitHandler.swift */, + 8E43964B2B02E4C6007F0BC4 /* String+Extension.swift */, ); path = Utilities; sourceTree = ""; @@ -386,8 +396,8 @@ 974E7EE22930201500BDF59C /* ZimFileService */ = { isa = PBXGroup; children = ( - 974E7EE32930201500BDF59C /* ZimFileService.mm */, 974E7EE42930201500BDF59C /* ZimFileService.h */, + 974E7EE32930201500BDF59C /* ZimFileService.mm */, 974E7EE52930201500BDF59C /* ZimFileService.swift */, ); path = ZimFileService; @@ -549,6 +559,7 @@ 97E94B22271EF250005B0295 /* Kiwix.entitlements */, 9779A5D02456796A00F6F6FF /* Kiwix-Bridging-Header.h */, 970885D0271339A300C5795C /* wikipedia_dark.css */, + 8E4396492B02E455007F0BC4 /* Localizable.strings */, ); path = Support; sourceTree = ""; @@ -649,6 +660,7 @@ knownRegions = ( en, Base, + es, ); mainGroup = 97A2AB7F1C1B80FF00052E74; packageReferences = ( @@ -681,6 +693,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 */, ); @@ -691,6 +704,7 @@ buildActionMask = 2147483647; files = ( 97B448A1210FBC2E0004B056 /* LaunchScreen.storyboard in Resources */, + 8E4396472B02E455007F0BC4 /* Localizable.strings in Resources */, 97B4489E210FBC2E0004B056 /* Assets.xcassets in Resources */, 97B4489C210FBC2C0004B056 /* Main.storyboard in Resources */, ); @@ -753,6 +767,7 @@ buildActionMask = 2147483647; files = ( 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 */, @@ -836,6 +851,7 @@ buildActionMask = 2147483647; files = ( 97B44899210FBC2C0004B056 /* ViewController.swift in Sources */, + 8E43964D2B02E4C6007F0BC4 /* String+Extension.swift in Sources */, 97B44897210FBC2C0004B056 /* AppDelegate.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -851,10 +867,20 @@ /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ + 8E4396492B02E455007F0BC4 /* Localizable.strings */ = { + isa = PBXVariantGroup; + children = ( + 8E4396482B02E455007F0BC4 /* en */, + 8E43964A2B02E458007F0BC4 /* es */, + ); + name = Localizable.strings; + sourceTree = ""; + }; 97B4489A210FBC2C0004B056 /* Main.storyboard */ = { isa = PBXVariantGroup; children = ( 97B4489B210FBC2C0004B056 /* Base */, + 8E4396442B02E443007F0BC4 /* es */, ); name = Main.storyboard; sourceTree = ""; @@ -863,6 +889,7 @@ isa = PBXVariantGroup; children = ( 97B448A0210FBC2E0004B056 /* Base */, + 8E4396452B02E443007F0BC4 /* es */, ); name = LaunchScreen.storyboard; sourceTree = ""; @@ -943,7 +970,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 119; DEVELOPMENT_ASSET_PATHS = ""; - DEVELOPMENT_TEAM = L7HWM3SP3L; + DEVELOPMENT_TEAM = ""; "ENABLE_HARDENED_RUNTIME[sdk=macosx*]" = YES; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; @@ -989,7 +1016,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 119; DEVELOPMENT_ASSET_PATHS = ""; - DEVELOPMENT_TEAM = L7HWM3SP3L; + DEVELOPMENT_TEAM = ""; "ENABLE_HARDENED_RUNTIME[sdk=macosx*]" = YES; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; diff --git a/Model/DownloadService.swift b/Model/DownloadService.swift index 39761c77..a51b0e1d 100644 --- a/Model/DownloadService.swift +++ b/Model/DownloadService.swift @@ -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 diff --git a/Model/Entities/Errors.swift b/Model/Entities/Errors.swift index 45c9a26b..46ce9d55 100644 --- a/Model/Entities/Errors.swift +++ b/Model/Entities/Errors.swift @@ -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") } } } diff --git a/Model/Utilities/String+Extension.swift b/Model/Utilities/String+Extension.swift new file mode 100644 index 00000000..9f1ad136 --- /dev/null +++ b/Model/Utilities/String+Extension.swift @@ -0,0 +1,32 @@ +// +// 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) + } + } + +} + diff --git a/Support/en.lproj/Localizable.strings b/Support/en.lproj/Localizable.strings new file mode 100644 index 00000000..63237872 --- /dev/null +++ b/Support/en.lproj/Localizable.strings @@ -0,0 +1,202 @@ +/* + 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?"; +"An external link is tapped. However, your current setting does not allow it to be loaded." = "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"; +"Lorem ipsum dolor sit amet, consectetur adipiscing elit, \ +sed do eiusmod tempor incididunt ut labore et dolore magna aliqua." = "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 %@"; +"All bookmarked articles linked to this zim file will be deleted, \ +but the original file will remain in place." = "All bookmarked articles linked to this zim file will be deleted, \ +but the original file will remain in place."; +"Delete %@" = "Delete %@"; +"The zim file and all bookmarked articles linked to this zim file will be deleted." = "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"; +"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." = "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"; + diff --git a/Support/es.lproj/Localizable.strings b/Support/es.lproj/Localizable.strings new file mode 100644 index 00000000..e83d7f5c --- /dev/null +++ b/Support/es.lproj/Localizable.strings @@ -0,0 +1,8 @@ +/* + Localizable.strings + Kiwix + + Created by tvision251 on 11/13/23. + Copyright © 2023 Chris Li. All rights reserved. +*/ + diff --git a/ViewModel/BrowserViewModel.swift b/ViewModel/BrowserViewModel.swift index 2354f6b4..7eaccd1e 100644 --- a/ViewModel/BrowserViewModel.swift +++ b/ViewModel/BrowserViewModel.swift @@ -250,12 +250,12 @@ 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) } ) @@ -266,11 +266,11 @@ 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", image: UIImage(systemName: "star.slash.fill")) { _ in + return UIAction(title: "Remove Bookmark".localized, image: UIImage(systemName: "star.slash.fill")) { _ in self.deleteBookmark(url: url) } } else { - return UIAction(title: "Bookmark", image: UIImage(systemName: "star")) { _ in + return UIAction(title: "Bookmark".localized, image: UIImage(systemName: "star")) { _ in self.createBookmark(url: url) } } diff --git a/Views/Bookmarks.swift b/Views/Bookmarks.swift index f24ab25d..988023e8 100644 --- a/Views/Bookmarks.swift +++ b/Views/Bookmarks.swift @@ -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") } } } diff --git a/Views/BrowserTab.swift b/Views/BrowserTab.swift index 9a080449..aec014f5 100644 --- a/Views/BrowserTab.swift +++ b/Views/BrowserTab.swift @@ -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() diff --git a/Views/BuildingBlocks/ArticleCell.swift b/Views/BuildingBlocks/ArticleCell.swift index f5681f32..fa8069c8 100644 --- a/Views/BuildingBlocks/ArticleCell.swift +++ b/Views/BuildingBlocks/ArticleCell.swift @@ -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,12 +64,12 @@ struct ArticleCell: View { struct ArticleCell_Previews: PreviewProvider { static let result: SearchResult = { - let result = SearchResult(zimFileID: UUID(), path: "", title: "Article Title")! + let result = SearchResult(zimFileID: UUID(), path: "", title: "Article Title".localized)! result.snippet = NSAttributedString(string: """ Lorem ipsum dolor sit amet, consectetur adipiscing elit, \ sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. - """ + """.localized ) return result }() diff --git a/Views/BuildingBlocks/Attribute.swift b/Views/BuildingBlocks/Attribute.swift index 038f9202..98ff221b 100644 --- a/Views/BuildingBlocks/Attribute.swift +++ b/Views/BuildingBlocks/Attribute.swift @@ -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) diff --git a/Views/BuildingBlocks/DownloadTaskCell.swift b/Views/BuildingBlocks/DownloadTaskCell.swift index f1fb7f1a..530cd7ad 100644 --- a/Views/BuildingBlocks/DownloadTaskCell.swift +++ b/Views/BuildingBlocks/DownloadTaskCell.swift @@ -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") } diff --git a/Views/BuildingBlocks/FlavorTag.swift b/Views/BuildingBlocks/FlavorTag.swift index ffe22e3b..17e3c682 100644 --- a/Views/BuildingBlocks/FlavorTag.swift +++ b/Views/BuildingBlocks/FlavorTag.swift @@ -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 } } } diff --git a/Views/BuildingBlocks/GridSection.swift b/Views/BuildingBlocks/GridSection.swift index 6dd622d5..d820a915 100644 --- a/Views/BuildingBlocks/GridSection.swift +++ b/Views/BuildingBlocks/GridSection.swift @@ -29,8 +29,8 @@ struct GridSection: View { struct GridSection_Previews: PreviewProvider { static var previews: some View { - GridSection(title: "Header Text") { - Text("Content") + GridSection(title: "Header Text".localized) { + Text("Content".localized) } } } diff --git a/Views/BuildingBlocks/LibraryLastRefreshTime.swift b/Views/BuildingBlocks/LibraryLastRefreshTime.swift index 47156b96..9fbd4aa9 100644 --- a/Views/BuildingBlocks/LibraryLastRefreshTime.swift +++ b/Views/BuildingBlocks/LibraryLastRefreshTime.swift @@ -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) } } } diff --git a/Views/BuildingBlocks/Message.swift b/Views/BuildingBlocks/Message.swift index 4992e79d..2b0e5a88 100644 --- a/Views/BuildingBlocks/Message.swift +++ b/Views/BuildingBlocks/Message.swift @@ -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) } } diff --git a/Views/BuildingBlocks/SearchResultRow.swift b/Views/BuildingBlocks/SearchResultRow.swift index 55b33dc7..57edb5f0 100644 --- a/Views/BuildingBlocks/SearchResultRow.swift +++ b/Views/BuildingBlocks/SearchResultRow.swift @@ -38,12 +38,12 @@ struct SearchResultRow: View { struct SearchResultRow_Previews: PreviewProvider { static let result: SearchResult = { - let result = SearchResult(zimFileID: UUID(), path: "", title: "Article Title")! + let result = SearchResult(zimFileID: UUID(), path: "", title: "Article Title".localized)! result.snippet = NSAttributedString(string: """ Lorem ipsum dolor sit amet, consectetur adipiscing elit, \ sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. - """ + """.localized ) return result }() diff --git a/Views/BuildingBlocks/SheetContent.swift b/Views/BuildingBlocks/SheetContent.swift index f7f3fa06..022ea301 100644 --- a/Views/BuildingBlocks/SheetContent.swift +++ b/Views/BuildingBlocks/SheetContent.swift @@ -27,7 +27,7 @@ struct SheetContent: View { Button { dismiss() } label: { - Text("Done").fontWeight(.semibold) + Text("Done".localized).fontWeight(.semibold) } } } diff --git a/Views/BuildingBlocks/TabLabel.swift b/Views/BuildingBlocks/TabLabel.swift index ebd8fa59..e31847de 100644 --- a/Views/BuildingBlocks/TabLabel.swift +++ b/Views/BuildingBlocks/TabLabel.swift @@ -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") } } } diff --git a/Views/BuildingBlocks/ZimFileMissingIndicator.swift b/Views/BuildingBlocks/ZimFileMissingIndicator.swift index da7c3b3c..2e0a23a8 100644 --- a/Views/BuildingBlocks/ZimFileMissingIndicator.swift +++ b/Views/BuildingBlocks/ZimFileMissingIndicator.swift @@ -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) } } diff --git a/Views/BuildingBlocks/ZimFileRow.swift b/Views/BuildingBlocks/ZimFileRow.swift index 0fb4d387..73510e56 100644 --- a/Views/BuildingBlocks/ZimFileRow.swift +++ b/Views/BuildingBlocks/ZimFileRow.swift @@ -30,7 +30,7 @@ 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) } diff --git a/Views/Buttons/ArticleShortcutButtons.swift b/Views/Buttons/ArticleShortcutButtons.swift index 79ee831d..7e466047 100644 --- a/Views/Buttons/ArticleShortcutButtons.swift +++ b/Views/Buttons/ArticleShortcutButtons.swift @@ -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 } } diff --git a/Views/Buttons/BookmarkButton.swift b/Views/Buttons/BookmarkButton.swift index 38508442..96b40565 100644 --- a/Views/Buttons/BookmarkButton.swift +++ b/Views/Buttons/BookmarkButton.swift @@ -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) } } } diff --git a/Views/Buttons/NavigationButtons.swift b/Views/Buttons/NavigationButtons.swift index e30dec40..8f8f121d 100644 --- a/Views/Buttons/NavigationButtons.swift +++ b/Views/Buttons/NavigationButtons.swift @@ -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) } } diff --git a/Views/Buttons/OutlineButton.swift b/Views/Buttons/OutlineButton.swift index 671b74d9..3eb7ef87 100644 --- a/Views/Buttons/OutlineButton.swift +++ b/Views/Buttons/OutlineButton.swift @@ -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) } } } diff --git a/Views/Buttons/TabsManagerButton.swift b/Views/Buttons/TabsManagerButton.swift index f8bf203a..212ce302 100644 --- a/Views/Buttons/TabsManagerButton.swift +++ b/Views/Buttons/TabsManagerButton.swift @@ -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() } diff --git a/Views/Commands.swift b/Views/Commands.swift index 7550ec0e..61672c3d 100644 --- a/Views/Commands.swift +++ b/Views/Commands.swift @@ -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) } diff --git a/Views/Library/Library.swift b/Views/Library/Library.swift index 06d9770c..b2786227 100644 --- a/Views/Library/Library.swift +++ b/Views/Library/Library.swift @@ -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") } } } diff --git a/Views/Library/ZimFileDetail.swift b/Views/Library/ZimFileDetail.swift index 72b0d948..5ee91f1a 100644 --- a/Views/Library/ZimFileDetail.swift +++ b/Views/Library/ZimFileDetail.swift @@ -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,23 @@ 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)"), + title: Text("Unlink %@".localizedWithFormat(withArgs: 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")) { + primaryButton: .destructive(Text("Unlink".localized)) { LibraryOperations.unlink(zimFileID: zimFile.fileID) #if os(iOS) presentationMode.wrappedValue.dismiss() @@ -137,9 +137,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("The zim file and all bookmarked articles linked to this zim file will be deleted.".localized), + primaryButton: .destructive(Text("Delete".localized)) { LibraryOperations.delete(zimFileID: zimFile.fileID) #if os(iOS) presentationMode.wrappedValue.dismiss() @@ -151,7 +151,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 +159,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 +177,37 @@ 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 +237,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 +313,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 +329,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 diff --git a/Views/Library/ZimFilesCategories.swift b/Views/Library/ZimFilesCategories.swift index b5827709..f08c6298 100644 --- a/Views/Library/ZimFilesCategories.swift +++ b/Views/Library/ZimFilesCategories.swift @@ -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) diff --git a/Views/Library/ZimFilesDownloads.swift b/Views/Library/ZimFilesDownloads.swift index 54c3f678..ea83b8c0 100644 --- a/Views/Library/ZimFilesDownloads.swift +++ b/Views/Library/ZimFilesDownloads.swift @@ -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") } } } diff --git a/Views/Library/ZimFilesNew.swift b/Views/Library/ZimFilesNew.swift index 3ae5c3ae..772db276 100644 --- a/Views/Library/ZimFilesNew.swift +++ b/Views/Library/ZimFilesNew.swift @@ -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") } } } diff --git a/Views/Library/ZimFilesOpened.swift b/Views/Library/ZimFilesOpened.swift index 0b19b015..dfdf3f70 100644 --- a/Views/Library/ZimFilesOpened.swift +++ b/Views/Library/ZimFilesOpened.swift @@ -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) } } } diff --git a/Views/SearchResults.swift b/Views/SearchResults.swift index c1192cf1..a617a1fe 100644 --- a/Views/SearchResults.swift +++ b/Views/SearchResults.swift @@ -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) } } } diff --git a/Views/Settings/About.swift b/Views/Settings/About.swift index 09ea7bf0..c9a7407c 100644 --- a/Views/Settings/About.swift +++ b/Views/Settings/About.swift @@ -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() } @@ -83,36 +83,36 @@ struct About: View { 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. - """ + """.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") } } diff --git a/Views/Settings/LanguageSelector.swift b/Views/Settings/LanguageSelector.swift index 8dcaf387..d0375124 100644 --- a/Views/Settings/LanguageSelector.swift +++ b/Views/Settings/LanguageSelector.swift @@ -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 { diff --git a/Views/Settings/Settings.swift b/Views/Settings/Settings.swift index 71f652a3..bee1ddc5 100644 --- a/Views/Settings/Settings.swift +++ b/Views/Settings/Settings.swift @@ -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,22 @@ 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 +154,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".lowercased) { + 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 +215,7 @@ private struct SelectedLanaguageLabel: View { var body: some View { HStack { - Text("Languages") + Text("Languages".localized) Spacer() if languageCodes.count == 1, let languageCode = languageCodes.first, diff --git a/Views/ViewModifiers/AlertHandler.swift b/Views/ViewModifiers/AlertHandler.swift index 940a4ecc..b305b055 100644 --- a/Views/ViewModifiers/AlertHandler.swift +++ b/Views/ViewModifiers/AlertHandler.swift @@ -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)) } } } diff --git a/Views/ViewModifiers/BookmarkContextMenu.swift b/Views/ViewModifiers/BookmarkContextMenu.swift index 235e984d..5620f604 100644 --- a/Views/ViewModifiers/BookmarkContextMenu.swift +++ b/Views/ViewModifiers/BookmarkContextMenu.swift @@ -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") } } } diff --git a/Views/ViewModifiers/ExternalLinkHandler.swift b/Views/ViewModifiers/ExternalLinkHandler.swift index a3a1d0cb..aff6a661 100644 --- a/Views/ViewModifiers/ExternalLinkHandler.swift +++ b/Views/ViewModifiers/ExternalLinkHandler.swift @@ -41,19 +41,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("An external link is tapped. However, your current setting does not allow it to be loaded.".localized) } } #if os(iOS) diff --git a/Views/ViewModifiers/FileImport.swift b/Views/ViewModifiers/FileImport.swift index da40c6ea..020d7cc6 100644 --- a/Views/ViewModifiers/FileImport.swift +++ b/Views/ViewModifiers/FileImport.swift @@ -41,7 +41,7 @@ struct OpenFileButton: 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,11 @@ 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.") + Text("%@ cannot be opened.".localizedWithFormat(withArgs: ListFormatter.localizedString(byJoining: filenames))) } } } diff --git a/Views/Welcome.swift b/Views/Welcome.swift index 3c079fe4..377c58e6 100644 --- a/Views/Welcome.swift +++ b/Views/Welcome.swift @@ -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) diff --git a/WikiMed/es.lproj/LaunchScreen.strings b/WikiMed/es.lproj/LaunchScreen.strings new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/WikiMed/es.lproj/LaunchScreen.strings @@ -0,0 +1 @@ + diff --git a/WikiMed/es.lproj/Main.strings b/WikiMed/es.lproj/Main.strings new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/WikiMed/es.lproj/Main.strings @@ -0,0 +1 @@ + From 9a12338559937c9826c08e66c9b408225a410bf8 Mon Sep 17 00:00:00 2001 From: tvision106 Date: Tue, 14 Nov 2023 17:12:29 -0500 Subject: [PATCH 02/19] fix codefactor issue --- Model/Utilities/String+Extension.swift | 9 ++++----- Support/en.lproj/Localizable.strings | 8 ++++++-- ViewModel/BrowserViewModel.swift | 3 ++- Views/BuildingBlocks/ZimFileRow.swift | 3 ++- Views/Library/ZimFileDetail.swift | 8 ++++++-- Views/Settings/About.swift | 3 ++- Views/Settings/Settings.swift | 3 ++- Views/ViewModifiers/ExternalLinkHandler.swift | 5 ++++- Views/ViewModifiers/FileImport.swift | 3 ++- 9 files changed, 30 insertions(+), 15 deletions(-) diff --git a/Model/Utilities/String+Extension.swift b/Model/Utilities/String+Extension.swift index 9f1ad136..e782c0d1 100644 --- a/Model/Utilities/String+Extension.swift +++ b/Model/Utilities/String+Extension.swift @@ -14,7 +14,7 @@ extension String { return NSLocalizedString(self, tableName: nil, bundle: Bundle.main, value: "", comment: "") } - func localized(withComment:String) -> String { + func localized(withComment: String) -> String { return NSLocalizedString(self, tableName: nil, bundle: Bundle.main, value: "", comment: withComment) } @@ -22,11 +22,10 @@ extension 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) + case 1: return String.localizedStringWithFormat(format, withArgs[0]) + case 2: return String.localizedStringWithFormat(format, withArgs[0], withArgs[1]) + default: return String.localizedStringWithFormat(format, withArgs) } } } - diff --git a/Support/en.lproj/Localizable.strings b/Support/en.lproj/Localizable.strings index 63237872..fd8353f4 100644 --- a/Support/en.lproj/Localizable.strings +++ b/Support/en.lproj/Localizable.strings @@ -45,7 +45,9 @@ "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?"; -"An external link is tapped. However, your current setting does not allow it to be loaded." = "An external link is tapped. However, your current setting does not allow it to be loaded."; +"An external link is tapped. \ +However, your current setting does not allow it to be loaded." = "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."; @@ -97,7 +99,9 @@ sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."; but the original file will remain in place." = "All bookmarked articles linked to this zim file will be deleted, \ but the original file will remain in place."; "Delete %@" = "Delete %@"; -"The zim file and all bookmarked articles linked to this zim file will be deleted." = "The zim file and all bookmarked articles linked to this zim file will be deleted."; +"The zim file and all bookmarked articles \ +linked to this zim file will be deleted." = "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."; diff --git a/ViewModel/BrowserViewModel.swift b/ViewModel/BrowserViewModel.swift index 7eaccd1e..9de80614 100644 --- a/ViewModel/BrowserViewModel.swift +++ b/ViewModel/BrowserViewModel.swift @@ -266,7 +266,8 @@ 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".localized, image: UIImage(systemName: "star.slash.fill")) { _ in + return UIAction(title: "Remove Bookmark".localized, + image: UIImage(systemName: "star.slash.fill")) { _ in self.deleteBookmark(url: url) } } else { diff --git a/Views/BuildingBlocks/ZimFileRow.swift b/Views/BuildingBlocks/ZimFileRow.swift index 73510e56..df40aa9d 100644 --- a/Views/BuildingBlocks/ZimFileRow.swift +++ b/Views/BuildingBlocks/ZimFileRow.swift @@ -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".localized + return "\(zimFile.articleCount.formatted(.number.notation(.compactName)))" + + "articles".localized } else { return Formatter.largeNumber(zimFile.articleCount) } diff --git a/Views/Library/ZimFileDetail.swift b/Views/Library/ZimFileDetail.swift index 5ee91f1a..4b58afaa 100644 --- a/Views/Library/ZimFileDetail.swift +++ b/Views/Library/ZimFileDetail.swift @@ -138,7 +138,10 @@ struct ZimFileDetail: View { }.alert(isPresented: $isPresentingDeleteAlert) { Alert( title: Text("Delete %@".localizedWithFormat(withArgs: zimFile.name)), - message: Text("The zim file and all bookmarked articles linked to this zim file will be deleted.".localized), + message: Text(""" + The zim file and all bookmarked articles \ + linked to this zim file will be deleted. + """.localized), primaryButton: .destructive(Text("Delete".localized)) { LibraryOperations.delete(zimFileID: zimFile.fileID) #if os(iOS) @@ -177,7 +180,8 @@ struct ZimFileDetail: View { @ViewBuilder var basicInfo: some View { - Attribute(title: "Language".localized, detail: Locale.current.localizedString(forLanguageCode: zimFile.languageCode)) + 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)) diff --git a/Views/Settings/About.swift b/Views/Settings/About.swift index c9a7407c..72d5bed7 100644 --- a/Views/Settings/About.swift +++ b/Views/Settings/About.swift @@ -92,7 +92,8 @@ struct About: View { } private var appVersion: some View { - Attribute(title: "Version".localized, detail: Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String) + Attribute(title: "Version".localized, + detail: Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String) } private var buildNumber: some View { diff --git a/Views/Settings/Settings.swift b/Views/Settings/Settings.swift index bee1ddc5..cf6bbd85 100644 --- a/Views/Settings/Settings.swift +++ b/Views/Settings/Settings.swift @@ -132,7 +132,8 @@ struct Settings: View { var readingSettings: some View { Section("Reading".localized) { Stepper(value: $webViewPageZoom, in: 0.5...2, step: 0.05) { - Text("Page zoom".localized + ": \(Formatter.percent.string(from: NSNumber(value: webViewPageZoom)) ?? "")") + Text("Page zoom".localized + + ": \(Formatter.percent.string(from: NSNumber(value: webViewPageZoom)) ?? "")") } Picker("External link".localized, selection: $externalLinkLoadingPolicy) { ForEach(ExternalLinkLoadingPolicy.allCases) { loadingPolicy in diff --git a/Views/ViewModifiers/ExternalLinkHandler.swift b/Views/ViewModifiers/ExternalLinkHandler.swift index aff6a661..f48bde1a 100644 --- a/Views/ViewModifiers/ExternalLinkHandler.swift +++ b/Views/ViewModifiers/ExternalLinkHandler.swift @@ -53,7 +53,10 @@ struct ExternalLinkHandler: ViewModifier { case .ask: 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.".localized) + Text(""" + An external link is tapped. \ + However, your current setting does not allow it to be loaded. + """.localized) } } #if os(iOS) diff --git a/Views/ViewModifiers/FileImport.swift b/Views/ViewModifiers/FileImport.swift index 020d7cc6..c3dd86c4 100644 --- a/Views/ViewModifiers/FileImport.swift +++ b/Views/ViewModifiers/FileImport.swift @@ -102,7 +102,8 @@ struct OpenFileHandler: ViewModifier { } message: { alert in switch alert { case .unableToOpen(let filenames): - Text("%@ cannot be opened.".localizedWithFormat(withArgs: ListFormatter.localizedString(byJoining: filenames))) + Text("%@ cannot be opened.".localizedWithFormat(withArgs: + ListFormatter.localizedString(byJoining: filenames))) } } } From a5bc9053f508612a03ac8cb1d4f02e78e7016686 Mon Sep 17 00:00:00 2001 From: tvision106 Date: Tue, 14 Nov 2023 17:15:21 -0500 Subject: [PATCH 03/19] fix codefactor issue --- Views/ViewModifiers/FileImport.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Views/ViewModifiers/FileImport.swift b/Views/ViewModifiers/FileImport.swift index c3dd86c4..edd5f426 100644 --- a/Views/ViewModifiers/FileImport.swift +++ b/Views/ViewModifiers/FileImport.swift @@ -102,8 +102,8 @@ struct OpenFileHandler: ViewModifier { } message: { alert in switch alert { case .unableToOpen(let filenames): - Text("%@ cannot be opened.".localizedWithFormat(withArgs: - ListFormatter.localizedString(byJoining: filenames))) + let name = ListFormatter.localizedString(byJoining: filenames) + Text("%@ cannot be opened.".localizedWithFormat(withArgs: name)) } } } From 3c61ff68220c8119917597700295e5c2b0658e67 Mon Sep 17 00:00:00 2001 From: tvision106 Date: Wed, 15 Nov 2023 13:24:44 -0500 Subject: [PATCH 04/19] fix some issues for long strings --- Support/en.lproj/Localizable.strings | 21 ++++++++----------- Views/BuildingBlocks/ArticleCell.swift | 7 +------ Views/BuildingBlocks/SearchResultRow.swift | 7 +------ Views/Library/ZimFileDetail.swift | 10 ++------- Views/Settings/About.swift | 8 +------ Views/ViewModifiers/ExternalLinkHandler.swift | 5 +---- 6 files changed, 15 insertions(+), 43 deletions(-) diff --git a/Support/en.lproj/Localizable.strings b/Support/en.lproj/Localizable.strings index fd8353f4..fbcd8a24 100644 --- a/Support/en.lproj/Localizable.strings +++ b/Support/en.lproj/Localizable.strings @@ -45,16 +45,14 @@ "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?"; -"An external link is tapped. \ -However, your current setting does not allow it to be loaded." = "An external link is tapped. \ +"loc-extenral-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"; -"Lorem ipsum dolor sit amet, consectetur adipiscing elit, \ -sed do eiusmod tempor incididunt ut labore et dolore magna aliqua." = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, \ +"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"; @@ -95,12 +93,10 @@ sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."; "Download using cellular" = "Download using cellular"; "Unlink" = "Unlink"; "Unlink %@" = "Unlink %@"; -"All bookmarked articles linked to this zim file will be deleted, \ -but the original file will remain in place." = "All bookmarked articles linked to this zim file will be deleted, \ +"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 %@"; -"The zim file and all bookmarked articles \ -linked to this zim file will be deleted." = "The zim file and all bookmarked articles \ +"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"; @@ -152,9 +148,7 @@ linked to this zim file will be deleted."; "Dependencies" = "Dependencies"; "License" = "License"; "Version" = "Version"; -"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." = "Kiwix is an offline reader for online content like Wikipedia, Project Gutenberg, or TED Talks. \ +"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."; @@ -203,4 +197,7 @@ The software as well as the content is free to use for anyone."; "Vikidia" = "Vikidia"; "StackExchange" = "StackExchange"; "Other" = "Other"; - +"Disabled" = "Disabled"; +"First Paragraph" = "First Paragraph"; +"First Sentence" = "First Sentence"; +"Matches" = "Matches"; diff --git a/Views/BuildingBlocks/ArticleCell.swift b/Views/BuildingBlocks/ArticleCell.swift index fa8069c8..f947e7dd 100644 --- a/Views/BuildingBlocks/ArticleCell.swift +++ b/Views/BuildingBlocks/ArticleCell.swift @@ -65,12 +65,7 @@ struct ArticleCell: View { struct ArticleCell_Previews: PreviewProvider { static let result: SearchResult = { let result = SearchResult(zimFileID: UUID(), path: "", title: "Article Title".localized)! - result.snippet = NSAttributedString(string: - """ - Lorem ipsum dolor sit amet, consectetur adipiscing elit, \ - sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. - """.localized - ) + result.snippet = NSAttributedString(string:"loc-article-cell-template".localized) return result }() diff --git a/Views/BuildingBlocks/SearchResultRow.swift b/Views/BuildingBlocks/SearchResultRow.swift index 57edb5f0..249f72cd 100644 --- a/Views/BuildingBlocks/SearchResultRow.swift +++ b/Views/BuildingBlocks/SearchResultRow.swift @@ -39,12 +39,7 @@ struct SearchResultRow: View { struct SearchResultRow_Previews: PreviewProvider { static let result: SearchResult = { let result = SearchResult(zimFileID: UUID(), path: "", title: "Article Title".localized)! - result.snippet = NSAttributedString(string: - """ - Lorem ipsum dolor sit amet, consectetur adipiscing elit, \ - sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. - """.localized - ) + result.snippet = NSAttributedString(string:"loc-article-cell-template".localized) return result }() diff --git a/Views/Library/ZimFileDetail.swift b/Views/Library/ZimFileDetail.swift index 4b58afaa..7bfafb0a 100644 --- a/Views/Library/ZimFileDetail.swift +++ b/Views/Library/ZimFileDetail.swift @@ -117,10 +117,7 @@ struct ZimFileDetail: View { }.alert(isPresented: $isPresentingUnlinkAlert) { Alert( title: Text("Unlink %@".localizedWithFormat(withArgs: zimFile.name)), - message: Text(""" - All bookmarked articles linked to this zim file will be deleted, \ - but the original file will remain in place. - """), + message: Text("loc-ZimFileDetail-Alert-unlink".localized), primaryButton: .destructive(Text("Unlink".localized)) { LibraryOperations.unlink(zimFileID: zimFile.fileID) #if os(iOS) @@ -138,10 +135,7 @@ struct ZimFileDetail: View { }.alert(isPresented: $isPresentingDeleteAlert) { Alert( title: Text("Delete %@".localizedWithFormat(withArgs: zimFile.name)), - message: Text(""" - The zim file and all bookmarked articles \ - linked to this zim file will be deleted. - """.localized), + message: Text("loc-ZimFileDetail-Alert-Delete".localized), primaryButton: .destructive(Text("Delete".localized)) { LibraryOperations.delete(zimFileID: zimFile.fileID) #if os(iOS) diff --git a/Views/Settings/About.swift b/Views/Settings/About.swift index 72d5bed7..3db016d6 100644 --- a/Views/Settings/About.swift +++ b/Views/Settings/About.swift @@ -78,13 +78,7 @@ 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. - """.localized - ) + Text("loc-About-description".localized) } private var release: some View { diff --git a/Views/ViewModifiers/ExternalLinkHandler.swift b/Views/ViewModifiers/ExternalLinkHandler.swift index f48bde1a..4d3860d4 100644 --- a/Views/ViewModifiers/ExternalLinkHandler.swift +++ b/Views/ViewModifiers/ExternalLinkHandler.swift @@ -53,10 +53,7 @@ struct ExternalLinkHandler: ViewModifier { case .ask: 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. - """.localized) + Text("loc-extenral-alert".localized) } } #if os(iOS) From 4df733a179f0d841be40b1a85bd6fb37dc504f6d Mon Sep 17 00:00:00 2001 From: tvision106 Date: Wed, 15 Nov 2023 14:50:45 -0500 Subject: [PATCH 05/19] fix codefactor issue --- Views/BuildingBlocks/ArticleCell.swift | 2 +- Views/BuildingBlocks/SearchResultRow.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Views/BuildingBlocks/ArticleCell.swift b/Views/BuildingBlocks/ArticleCell.swift index f947e7dd..a3a1b3f7 100644 --- a/Views/BuildingBlocks/ArticleCell.swift +++ b/Views/BuildingBlocks/ArticleCell.swift @@ -65,7 +65,7 @@ struct ArticleCell: View { struct ArticleCell_Previews: PreviewProvider { static let result: SearchResult = { let result = SearchResult(zimFileID: UUID(), path: "", title: "Article Title".localized)! - result.snippet = NSAttributedString(string:"loc-article-cell-template".localized) + result.snippet = NSAttributedString(string: "loc-article-cell-template".localized) return result }() diff --git a/Views/BuildingBlocks/SearchResultRow.swift b/Views/BuildingBlocks/SearchResultRow.swift index 249f72cd..690a3732 100644 --- a/Views/BuildingBlocks/SearchResultRow.swift +++ b/Views/BuildingBlocks/SearchResultRow.swift @@ -39,7 +39,7 @@ struct SearchResultRow: View { struct SearchResultRow_Previews: PreviewProvider { static let result: SearchResult = { let result = SearchResult(zimFileID: UUID(), path: "", title: "Article Title".localized)! - result.snippet = NSAttributedString(string:"loc-article-cell-template".localized) + result.snippet = NSAttributedString(string: "loc-article-cell-template".localized) return result }() From 22c72ae2a2513561cdf73de9e3e0e1db8671c911 Mon Sep 17 00:00:00 2001 From: tvision106 Date: Tue, 14 Nov 2023 16:20:36 -0500 Subject: [PATCH 06/19] Update the localization --- App/App_macOS.swift | 12 +- App/SidebarViewController.swift | 10 +- App/SplitViewController.swift | 2 +- Kiwix.xcodeproj/project.pbxproj | 33 ++- Model/DownloadService.swift | 4 +- Model/Entities/Errors.swift | 6 +- Model/Utilities/String+Extension.swift | 32 +++ Support/en.lproj/Localizable.strings | 202 ++++++++++++++++++ Support/es.lproj/Localizable.strings | 8 + ViewModel/BrowserViewModel.swift | 8 +- Views/Bookmarks.swift | 6 +- Views/BrowserTab.swift | 2 +- Views/BuildingBlocks/ArticleCell.swift | 6 +- Views/BuildingBlocks/Attribute.swift | 4 +- Views/BuildingBlocks/DownloadTaskCell.swift | 4 +- Views/BuildingBlocks/FlavorTag.swift | 6 +- Views/BuildingBlocks/GridSection.swift | 4 +- .../LibraryLastRefreshTime.swift | 4 +- Views/BuildingBlocks/Message.swift | 2 +- Views/BuildingBlocks/SearchResultRow.swift | 4 +- Views/BuildingBlocks/SheetContent.swift | 2 +- Views/BuildingBlocks/TabLabel.swift | 4 +- .../ZimFileMissingIndicator.swift | 2 +- Views/BuildingBlocks/ZimFileRow.swift | 2 +- Views/Buttons/ArticleShortcutButtons.swift | 16 +- Views/Buttons/BookmarkButton.swift | 14 +- Views/Buttons/NavigationButtons.swift | 4 +- Views/Buttons/OutlineButton.swift | 10 +- Views/Buttons/TabsManagerButton.swift | 26 +-- Views/Commands.swift | 10 +- Views/Library/Library.swift | 14 +- Views/Library/ZimFileDetail.swift | 78 +++---- Views/Library/ZimFilesCategories.swift | 10 +- Views/Library/ZimFilesDownloads.swift | 6 +- Views/Library/ZimFilesNew.swift | 8 +- Views/Library/ZimFilesOpened.swift | 10 +- Views/SearchResults.swift | 22 +- Views/Settings/About.swift | 34 +-- Views/Settings/LanguageSelector.swift | 16 +- Views/Settings/Settings.swift | 76 +++---- Views/ViewModifiers/AlertHandler.swift | 2 +- Views/ViewModifiers/BookmarkContextMenu.swift | 4 +- Views/ViewModifiers/ExternalLinkHandler.swift | 10 +- Views/ViewModifiers/FileImport.swift | 6 +- Views/Welcome.swift | 12 +- WikiMed/es.lproj/LaunchScreen.strings | 1 + WikiMed/es.lproj/Main.strings | 1 + 47 files changed, 515 insertions(+), 244 deletions(-) create mode 100644 Model/Utilities/String+Extension.swift create mode 100644 Support/en.lproj/Localizable.strings create mode 100644 Support/es.lproj/Localizable.strings create mode 100644 WikiMed/es.lproj/LaunchScreen.strings create mode 100644 WikiMed/es.lproj/Main.strings diff --git a/App/App_macOS.swift b/App/App_macOS.swift index ce6efe2c..2bbdcbf7 100644 --- a/App/App_macOS.swift +++ b/App/App_macOS.swift @@ -32,10 +32,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) @@ -91,11 +91,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) } } } @@ -106,7 +106,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: diff --git a/App/SidebarViewController.swift b/App/SidebarViewController.swift index 836850f3..a29afb15 100644 --- a/App/SidebarViewController.swift +++ b/App/SidebarViewController.swift @@ -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") diff --git a/App/SplitViewController.swift b/App/SplitViewController.swift index 1e830032..45a733c4 100644 --- a/App/SplitViewController.swift +++ b/App/SplitViewController.swift @@ -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) } } diff --git a/Kiwix.xcodeproj/project.pbxproj b/Kiwix.xcodeproj/project.pbxproj index 2e2bc8ae..2aa1a10a 100644 --- a/Kiwix.xcodeproj/project.pbxproj +++ b/Kiwix.xcodeproj/project.pbxproj @@ -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 */; }; @@ -124,6 +128,11 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 8E4396442B02E443007F0BC4 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Main.strings; sourceTree = ""; }; + 8E4396452B02E443007F0BC4 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/LaunchScreen.strings; sourceTree = ""; }; + 8E4396482B02E455007F0BC4 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; + 8E43964A2B02E458007F0BC4 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Localizable.strings; sourceTree = ""; }; + 8E43964B2B02E4C6007F0BC4 /* String+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Extension.swift"; sourceTree = ""; }; 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 = ""; }; @@ -293,6 +302,7 @@ 9779A7E224567A5A00F6F6FF /* Log.swift */, 97B3BACD2736CE3500A23F49 /* URL.swift */, 9779A73A2456796B00F6F6FF /* WebKitHandler.swift */, + 8E43964B2B02E4C6007F0BC4 /* String+Extension.swift */, ); path = Utilities; sourceTree = ""; @@ -386,8 +396,8 @@ 974E7EE22930201500BDF59C /* ZimFileService */ = { isa = PBXGroup; children = ( - 974E7EE32930201500BDF59C /* ZimFileService.mm */, 974E7EE42930201500BDF59C /* ZimFileService.h */, + 974E7EE32930201500BDF59C /* ZimFileService.mm */, 974E7EE52930201500BDF59C /* ZimFileService.swift */, ); path = ZimFileService; @@ -549,6 +559,7 @@ 97E94B22271EF250005B0295 /* Kiwix.entitlements */, 9779A5D02456796A00F6F6FF /* Kiwix-Bridging-Header.h */, 970885D0271339A300C5795C /* wikipedia_dark.css */, + 8E4396492B02E455007F0BC4 /* Localizable.strings */, ); path = Support; sourceTree = ""; @@ -649,6 +660,7 @@ knownRegions = ( en, Base, + es, ); mainGroup = 97A2AB7F1C1B80FF00052E74; packageReferences = ( @@ -681,6 +693,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 */, ); @@ -691,6 +704,7 @@ buildActionMask = 2147483647; files = ( 97B448A1210FBC2E0004B056 /* LaunchScreen.storyboard in Resources */, + 8E4396472B02E455007F0BC4 /* Localizable.strings in Resources */, 97B4489E210FBC2E0004B056 /* Assets.xcassets in Resources */, 97B4489C210FBC2C0004B056 /* Main.storyboard in Resources */, ); @@ -753,6 +767,7 @@ buildActionMask = 2147483647; files = ( 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 */, @@ -836,6 +851,7 @@ buildActionMask = 2147483647; files = ( 97B44899210FBC2C0004B056 /* ViewController.swift in Sources */, + 8E43964D2B02E4C6007F0BC4 /* String+Extension.swift in Sources */, 97B44897210FBC2C0004B056 /* AppDelegate.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -851,10 +867,20 @@ /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ + 8E4396492B02E455007F0BC4 /* Localizable.strings */ = { + isa = PBXVariantGroup; + children = ( + 8E4396482B02E455007F0BC4 /* en */, + 8E43964A2B02E458007F0BC4 /* es */, + ); + name = Localizable.strings; + sourceTree = ""; + }; 97B4489A210FBC2C0004B056 /* Main.storyboard */ = { isa = PBXVariantGroup; children = ( 97B4489B210FBC2C0004B056 /* Base */, + 8E4396442B02E443007F0BC4 /* es */, ); name = Main.storyboard; sourceTree = ""; @@ -863,6 +889,7 @@ isa = PBXVariantGroup; children = ( 97B448A0210FBC2E0004B056 /* Base */, + 8E4396452B02E443007F0BC4 /* es */, ); name = LaunchScreen.storyboard; sourceTree = ""; @@ -943,7 +970,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 119; DEVELOPMENT_ASSET_PATHS = ""; - DEVELOPMENT_TEAM = L7HWM3SP3L; + DEVELOPMENT_TEAM = ""; "ENABLE_HARDENED_RUNTIME[sdk=macosx*]" = YES; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; @@ -989,7 +1016,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 119; DEVELOPMENT_ASSET_PATHS = ""; - DEVELOPMENT_TEAM = L7HWM3SP3L; + DEVELOPMENT_TEAM = ""; "ENABLE_HARDENED_RUNTIME[sdk=macosx*]" = YES; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; diff --git a/Model/DownloadService.swift b/Model/DownloadService.swift index 39761c77..a51b0e1d 100644 --- a/Model/DownloadService.swift +++ b/Model/DownloadService.swift @@ -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 diff --git a/Model/Entities/Errors.swift b/Model/Entities/Errors.swift index 45c9a26b..46ce9d55 100644 --- a/Model/Entities/Errors.swift +++ b/Model/Entities/Errors.swift @@ -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") } } } diff --git a/Model/Utilities/String+Extension.swift b/Model/Utilities/String+Extension.swift new file mode 100644 index 00000000..9f1ad136 --- /dev/null +++ b/Model/Utilities/String+Extension.swift @@ -0,0 +1,32 @@ +// +// 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) + } + } + +} + diff --git a/Support/en.lproj/Localizable.strings b/Support/en.lproj/Localizable.strings new file mode 100644 index 00000000..63237872 --- /dev/null +++ b/Support/en.lproj/Localizable.strings @@ -0,0 +1,202 @@ +/* + 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?"; +"An external link is tapped. However, your current setting does not allow it to be loaded." = "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"; +"Lorem ipsum dolor sit amet, consectetur adipiscing elit, \ +sed do eiusmod tempor incididunt ut labore et dolore magna aliqua." = "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 %@"; +"All bookmarked articles linked to this zim file will be deleted, \ +but the original file will remain in place." = "All bookmarked articles linked to this zim file will be deleted, \ +but the original file will remain in place."; +"Delete %@" = "Delete %@"; +"The zim file and all bookmarked articles linked to this zim file will be deleted." = "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"; +"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." = "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"; + diff --git a/Support/es.lproj/Localizable.strings b/Support/es.lproj/Localizable.strings new file mode 100644 index 00000000..e83d7f5c --- /dev/null +++ b/Support/es.lproj/Localizable.strings @@ -0,0 +1,8 @@ +/* + Localizable.strings + Kiwix + + Created by tvision251 on 11/13/23. + Copyright © 2023 Chris Li. All rights reserved. +*/ + diff --git a/ViewModel/BrowserViewModel.swift b/ViewModel/BrowserViewModel.swift index 2354f6b4..7eaccd1e 100644 --- a/ViewModel/BrowserViewModel.swift +++ b/ViewModel/BrowserViewModel.swift @@ -250,12 +250,12 @@ 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) } ) @@ -266,11 +266,11 @@ 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", image: UIImage(systemName: "star.slash.fill")) { _ in + return UIAction(title: "Remove Bookmark".localized, image: UIImage(systemName: "star.slash.fill")) { _ in self.deleteBookmark(url: url) } } else { - return UIAction(title: "Bookmark", image: UIImage(systemName: "star")) { _ in + return UIAction(title: "Bookmark".localized, image: UIImage(systemName: "star")) { _ in self.createBookmark(url: url) } } diff --git a/Views/Bookmarks.swift b/Views/Bookmarks.swift index f24ab25d..988023e8 100644 --- a/Views/Bookmarks.swift +++ b/Views/Bookmarks.swift @@ -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") } } } diff --git a/Views/BrowserTab.swift b/Views/BrowserTab.swift index 9a080449..aec014f5 100644 --- a/Views/BrowserTab.swift +++ b/Views/BrowserTab.swift @@ -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() diff --git a/Views/BuildingBlocks/ArticleCell.swift b/Views/BuildingBlocks/ArticleCell.swift index f5681f32..fa8069c8 100644 --- a/Views/BuildingBlocks/ArticleCell.swift +++ b/Views/BuildingBlocks/ArticleCell.swift @@ -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,12 +64,12 @@ struct ArticleCell: View { struct ArticleCell_Previews: PreviewProvider { static let result: SearchResult = { - let result = SearchResult(zimFileID: UUID(), path: "", title: "Article Title")! + let result = SearchResult(zimFileID: UUID(), path: "", title: "Article Title".localized)! result.snippet = NSAttributedString(string: """ Lorem ipsum dolor sit amet, consectetur adipiscing elit, \ sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. - """ + """.localized ) return result }() diff --git a/Views/BuildingBlocks/Attribute.swift b/Views/BuildingBlocks/Attribute.swift index 038f9202..98ff221b 100644 --- a/Views/BuildingBlocks/Attribute.swift +++ b/Views/BuildingBlocks/Attribute.swift @@ -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) diff --git a/Views/BuildingBlocks/DownloadTaskCell.swift b/Views/BuildingBlocks/DownloadTaskCell.swift index f1fb7f1a..530cd7ad 100644 --- a/Views/BuildingBlocks/DownloadTaskCell.swift +++ b/Views/BuildingBlocks/DownloadTaskCell.swift @@ -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") } diff --git a/Views/BuildingBlocks/FlavorTag.swift b/Views/BuildingBlocks/FlavorTag.swift index ffe22e3b..17e3c682 100644 --- a/Views/BuildingBlocks/FlavorTag.swift +++ b/Views/BuildingBlocks/FlavorTag.swift @@ -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 } } } diff --git a/Views/BuildingBlocks/GridSection.swift b/Views/BuildingBlocks/GridSection.swift index 6dd622d5..d820a915 100644 --- a/Views/BuildingBlocks/GridSection.swift +++ b/Views/BuildingBlocks/GridSection.swift @@ -29,8 +29,8 @@ struct GridSection: View { struct GridSection_Previews: PreviewProvider { static var previews: some View { - GridSection(title: "Header Text") { - Text("Content") + GridSection(title: "Header Text".localized) { + Text("Content".localized) } } } diff --git a/Views/BuildingBlocks/LibraryLastRefreshTime.swift b/Views/BuildingBlocks/LibraryLastRefreshTime.swift index 47156b96..9fbd4aa9 100644 --- a/Views/BuildingBlocks/LibraryLastRefreshTime.swift +++ b/Views/BuildingBlocks/LibraryLastRefreshTime.swift @@ -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) } } } diff --git a/Views/BuildingBlocks/Message.swift b/Views/BuildingBlocks/Message.swift index 4992e79d..2b0e5a88 100644 --- a/Views/BuildingBlocks/Message.swift +++ b/Views/BuildingBlocks/Message.swift @@ -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) } } diff --git a/Views/BuildingBlocks/SearchResultRow.swift b/Views/BuildingBlocks/SearchResultRow.swift index 55b33dc7..57edb5f0 100644 --- a/Views/BuildingBlocks/SearchResultRow.swift +++ b/Views/BuildingBlocks/SearchResultRow.swift @@ -38,12 +38,12 @@ struct SearchResultRow: View { struct SearchResultRow_Previews: PreviewProvider { static let result: SearchResult = { - let result = SearchResult(zimFileID: UUID(), path: "", title: "Article Title")! + let result = SearchResult(zimFileID: UUID(), path: "", title: "Article Title".localized)! result.snippet = NSAttributedString(string: """ Lorem ipsum dolor sit amet, consectetur adipiscing elit, \ sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. - """ + """.localized ) return result }() diff --git a/Views/BuildingBlocks/SheetContent.swift b/Views/BuildingBlocks/SheetContent.swift index f7f3fa06..022ea301 100644 --- a/Views/BuildingBlocks/SheetContent.swift +++ b/Views/BuildingBlocks/SheetContent.swift @@ -27,7 +27,7 @@ struct SheetContent: View { Button { dismiss() } label: { - Text("Done").fontWeight(.semibold) + Text("Done".localized).fontWeight(.semibold) } } } diff --git a/Views/BuildingBlocks/TabLabel.swift b/Views/BuildingBlocks/TabLabel.swift index ebd8fa59..e31847de 100644 --- a/Views/BuildingBlocks/TabLabel.swift +++ b/Views/BuildingBlocks/TabLabel.swift @@ -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") } } } diff --git a/Views/BuildingBlocks/ZimFileMissingIndicator.swift b/Views/BuildingBlocks/ZimFileMissingIndicator.swift index da7c3b3c..2e0a23a8 100644 --- a/Views/BuildingBlocks/ZimFileMissingIndicator.swift +++ b/Views/BuildingBlocks/ZimFileMissingIndicator.swift @@ -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) } } diff --git a/Views/BuildingBlocks/ZimFileRow.swift b/Views/BuildingBlocks/ZimFileRow.swift index 0fb4d387..73510e56 100644 --- a/Views/BuildingBlocks/ZimFileRow.swift +++ b/Views/BuildingBlocks/ZimFileRow.swift @@ -30,7 +30,7 @@ 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) } diff --git a/Views/Buttons/ArticleShortcutButtons.swift b/Views/Buttons/ArticleShortcutButtons.swift index 79ee831d..7e466047 100644 --- a/Views/Buttons/ArticleShortcutButtons.swift +++ b/Views/Buttons/ArticleShortcutButtons.swift @@ -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 } } diff --git a/Views/Buttons/BookmarkButton.swift b/Views/Buttons/BookmarkButton.swift index 38508442..96b40565 100644 --- a/Views/Buttons/BookmarkButton.swift +++ b/Views/Buttons/BookmarkButton.swift @@ -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) } } } diff --git a/Views/Buttons/NavigationButtons.swift b/Views/Buttons/NavigationButtons.swift index e30dec40..8f8f121d 100644 --- a/Views/Buttons/NavigationButtons.swift +++ b/Views/Buttons/NavigationButtons.swift @@ -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) } } diff --git a/Views/Buttons/OutlineButton.swift b/Views/Buttons/OutlineButton.swift index 671b74d9..3eb7ef87 100644 --- a/Views/Buttons/OutlineButton.swift +++ b/Views/Buttons/OutlineButton.swift @@ -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) } } } diff --git a/Views/Buttons/TabsManagerButton.swift b/Views/Buttons/TabsManagerButton.swift index f8bf203a..212ce302 100644 --- a/Views/Buttons/TabsManagerButton.swift +++ b/Views/Buttons/TabsManagerButton.swift @@ -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() } diff --git a/Views/Commands.swift b/Views/Commands.swift index 7550ec0e..61672c3d 100644 --- a/Views/Commands.swift +++ b/Views/Commands.swift @@ -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) } diff --git a/Views/Library/Library.swift b/Views/Library/Library.swift index 06d9770c..b2786227 100644 --- a/Views/Library/Library.swift +++ b/Views/Library/Library.swift @@ -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") } } } diff --git a/Views/Library/ZimFileDetail.swift b/Views/Library/ZimFileDetail.swift index 72b0d948..5ee91f1a 100644 --- a/Views/Library/ZimFileDetail.swift +++ b/Views/Library/ZimFileDetail.swift @@ -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,23 @@ 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)"), + title: Text("Unlink %@".localizedWithFormat(withArgs: 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")) { + primaryButton: .destructive(Text("Unlink".localized)) { LibraryOperations.unlink(zimFileID: zimFile.fileID) #if os(iOS) presentationMode.wrappedValue.dismiss() @@ -137,9 +137,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("The zim file and all bookmarked articles linked to this zim file will be deleted.".localized), + primaryButton: .destructive(Text("Delete".localized)) { LibraryOperations.delete(zimFileID: zimFile.fileID) #if os(iOS) presentationMode.wrappedValue.dismiss() @@ -151,7 +151,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 +159,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 +177,37 @@ 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 +237,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 +313,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 +329,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 diff --git a/Views/Library/ZimFilesCategories.swift b/Views/Library/ZimFilesCategories.swift index b5827709..f08c6298 100644 --- a/Views/Library/ZimFilesCategories.swift +++ b/Views/Library/ZimFilesCategories.swift @@ -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) diff --git a/Views/Library/ZimFilesDownloads.swift b/Views/Library/ZimFilesDownloads.swift index 54c3f678..ea83b8c0 100644 --- a/Views/Library/ZimFilesDownloads.swift +++ b/Views/Library/ZimFilesDownloads.swift @@ -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") } } } diff --git a/Views/Library/ZimFilesNew.swift b/Views/Library/ZimFilesNew.swift index 3ae5c3ae..772db276 100644 --- a/Views/Library/ZimFilesNew.swift +++ b/Views/Library/ZimFilesNew.swift @@ -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") } } } diff --git a/Views/Library/ZimFilesOpened.swift b/Views/Library/ZimFilesOpened.swift index 0b19b015..dfdf3f70 100644 --- a/Views/Library/ZimFilesOpened.swift +++ b/Views/Library/ZimFilesOpened.swift @@ -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) } } } diff --git a/Views/SearchResults.swift b/Views/SearchResults.swift index c1192cf1..a617a1fe 100644 --- a/Views/SearchResults.swift +++ b/Views/SearchResults.swift @@ -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) } } } diff --git a/Views/Settings/About.swift b/Views/Settings/About.swift index 09ea7bf0..c9a7407c 100644 --- a/Views/Settings/About.swift +++ b/Views/Settings/About.swift @@ -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() } @@ -83,36 +83,36 @@ struct About: View { 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. - """ + """.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") } } diff --git a/Views/Settings/LanguageSelector.swift b/Views/Settings/LanguageSelector.swift index 8dcaf387..d0375124 100644 --- a/Views/Settings/LanguageSelector.swift +++ b/Views/Settings/LanguageSelector.swift @@ -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 { diff --git a/Views/Settings/Settings.swift b/Views/Settings/Settings.swift index 71f652a3..bee1ddc5 100644 --- a/Views/Settings/Settings.swift +++ b/Views/Settings/Settings.swift @@ -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,22 @@ 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 +154,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".lowercased) { + 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 +215,7 @@ private struct SelectedLanaguageLabel: View { var body: some View { HStack { - Text("Languages") + Text("Languages".localized) Spacer() if languageCodes.count == 1, let languageCode = languageCodes.first, diff --git a/Views/ViewModifiers/AlertHandler.swift b/Views/ViewModifiers/AlertHandler.swift index 940a4ecc..b305b055 100644 --- a/Views/ViewModifiers/AlertHandler.swift +++ b/Views/ViewModifiers/AlertHandler.swift @@ -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)) } } } diff --git a/Views/ViewModifiers/BookmarkContextMenu.swift b/Views/ViewModifiers/BookmarkContextMenu.swift index 235e984d..5620f604 100644 --- a/Views/ViewModifiers/BookmarkContextMenu.swift +++ b/Views/ViewModifiers/BookmarkContextMenu.swift @@ -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") } } } diff --git a/Views/ViewModifiers/ExternalLinkHandler.swift b/Views/ViewModifiers/ExternalLinkHandler.swift index a3a1d0cb..aff6a661 100644 --- a/Views/ViewModifiers/ExternalLinkHandler.swift +++ b/Views/ViewModifiers/ExternalLinkHandler.swift @@ -41,19 +41,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("An external link is tapped. However, your current setting does not allow it to be loaded.".localized) } } #if os(iOS) diff --git a/Views/ViewModifiers/FileImport.swift b/Views/ViewModifiers/FileImport.swift index da40c6ea..020d7cc6 100644 --- a/Views/ViewModifiers/FileImport.swift +++ b/Views/ViewModifiers/FileImport.swift @@ -41,7 +41,7 @@ struct OpenFileButton: 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,11 @@ 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.") + Text("%@ cannot be opened.".localizedWithFormat(withArgs: ListFormatter.localizedString(byJoining: filenames))) } } } diff --git a/Views/Welcome.swift b/Views/Welcome.swift index 3c079fe4..377c58e6 100644 --- a/Views/Welcome.swift +++ b/Views/Welcome.swift @@ -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) diff --git a/WikiMed/es.lproj/LaunchScreen.strings b/WikiMed/es.lproj/LaunchScreen.strings new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/WikiMed/es.lproj/LaunchScreen.strings @@ -0,0 +1 @@ + diff --git a/WikiMed/es.lproj/Main.strings b/WikiMed/es.lproj/Main.strings new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/WikiMed/es.lproj/Main.strings @@ -0,0 +1 @@ + From 9fa13ea29e33fa519282144debd748a9e02860b9 Mon Sep 17 00:00:00 2001 From: tvision106 Date: Tue, 14 Nov 2023 17:12:29 -0500 Subject: [PATCH 07/19] fix codefactor issue --- Model/Utilities/String+Extension.swift | 9 ++++----- Support/en.lproj/Localizable.strings | 8 ++++++-- ViewModel/BrowserViewModel.swift | 3 ++- Views/BuildingBlocks/ZimFileRow.swift | 3 ++- Views/Library/ZimFileDetail.swift | 8 ++++++-- Views/Settings/About.swift | 3 ++- Views/Settings/Settings.swift | 3 ++- Views/ViewModifiers/ExternalLinkHandler.swift | 5 ++++- Views/ViewModifiers/FileImport.swift | 3 ++- 9 files changed, 30 insertions(+), 15 deletions(-) diff --git a/Model/Utilities/String+Extension.swift b/Model/Utilities/String+Extension.swift index 9f1ad136..e782c0d1 100644 --- a/Model/Utilities/String+Extension.swift +++ b/Model/Utilities/String+Extension.swift @@ -14,7 +14,7 @@ extension String { return NSLocalizedString(self, tableName: nil, bundle: Bundle.main, value: "", comment: "") } - func localized(withComment:String) -> String { + func localized(withComment: String) -> String { return NSLocalizedString(self, tableName: nil, bundle: Bundle.main, value: "", comment: withComment) } @@ -22,11 +22,10 @@ extension 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) + case 1: return String.localizedStringWithFormat(format, withArgs[0]) + case 2: return String.localizedStringWithFormat(format, withArgs[0], withArgs[1]) + default: return String.localizedStringWithFormat(format, withArgs) } } } - diff --git a/Support/en.lproj/Localizable.strings b/Support/en.lproj/Localizable.strings index 63237872..fd8353f4 100644 --- a/Support/en.lproj/Localizable.strings +++ b/Support/en.lproj/Localizable.strings @@ -45,7 +45,9 @@ "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?"; -"An external link is tapped. However, your current setting does not allow it to be loaded." = "An external link is tapped. However, your current setting does not allow it to be loaded."; +"An external link is tapped. \ +However, your current setting does not allow it to be loaded." = "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."; @@ -97,7 +99,9 @@ sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."; but the original file will remain in place." = "All bookmarked articles linked to this zim file will be deleted, \ but the original file will remain in place."; "Delete %@" = "Delete %@"; -"The zim file and all bookmarked articles linked to this zim file will be deleted." = "The zim file and all bookmarked articles linked to this zim file will be deleted."; +"The zim file and all bookmarked articles \ +linked to this zim file will be deleted." = "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."; diff --git a/ViewModel/BrowserViewModel.swift b/ViewModel/BrowserViewModel.swift index 7eaccd1e..9de80614 100644 --- a/ViewModel/BrowserViewModel.swift +++ b/ViewModel/BrowserViewModel.swift @@ -266,7 +266,8 @@ 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".localized, image: UIImage(systemName: "star.slash.fill")) { _ in + return UIAction(title: "Remove Bookmark".localized, + image: UIImage(systemName: "star.slash.fill")) { _ in self.deleteBookmark(url: url) } } else { diff --git a/Views/BuildingBlocks/ZimFileRow.swift b/Views/BuildingBlocks/ZimFileRow.swift index 73510e56..df40aa9d 100644 --- a/Views/BuildingBlocks/ZimFileRow.swift +++ b/Views/BuildingBlocks/ZimFileRow.swift @@ -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".localized + return "\(zimFile.articleCount.formatted(.number.notation(.compactName)))" + + "articles".localized } else { return Formatter.largeNumber(zimFile.articleCount) } diff --git a/Views/Library/ZimFileDetail.swift b/Views/Library/ZimFileDetail.swift index 5ee91f1a..4b58afaa 100644 --- a/Views/Library/ZimFileDetail.swift +++ b/Views/Library/ZimFileDetail.swift @@ -138,7 +138,10 @@ struct ZimFileDetail: View { }.alert(isPresented: $isPresentingDeleteAlert) { Alert( title: Text("Delete %@".localizedWithFormat(withArgs: zimFile.name)), - message: Text("The zim file and all bookmarked articles linked to this zim file will be deleted.".localized), + message: Text(""" + The zim file and all bookmarked articles \ + linked to this zim file will be deleted. + """.localized), primaryButton: .destructive(Text("Delete".localized)) { LibraryOperations.delete(zimFileID: zimFile.fileID) #if os(iOS) @@ -177,7 +180,8 @@ struct ZimFileDetail: View { @ViewBuilder var basicInfo: some View { - Attribute(title: "Language".localized, detail: Locale.current.localizedString(forLanguageCode: zimFile.languageCode)) + 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)) diff --git a/Views/Settings/About.swift b/Views/Settings/About.swift index c9a7407c..72d5bed7 100644 --- a/Views/Settings/About.swift +++ b/Views/Settings/About.swift @@ -92,7 +92,8 @@ struct About: View { } private var appVersion: some View { - Attribute(title: "Version".localized, detail: Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String) + Attribute(title: "Version".localized, + detail: Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String) } private var buildNumber: some View { diff --git a/Views/Settings/Settings.swift b/Views/Settings/Settings.swift index bee1ddc5..cf6bbd85 100644 --- a/Views/Settings/Settings.swift +++ b/Views/Settings/Settings.swift @@ -132,7 +132,8 @@ struct Settings: View { var readingSettings: some View { Section("Reading".localized) { Stepper(value: $webViewPageZoom, in: 0.5...2, step: 0.05) { - Text("Page zoom".localized + ": \(Formatter.percent.string(from: NSNumber(value: webViewPageZoom)) ?? "")") + Text("Page zoom".localized + + ": \(Formatter.percent.string(from: NSNumber(value: webViewPageZoom)) ?? "")") } Picker("External link".localized, selection: $externalLinkLoadingPolicy) { ForEach(ExternalLinkLoadingPolicy.allCases) { loadingPolicy in diff --git a/Views/ViewModifiers/ExternalLinkHandler.swift b/Views/ViewModifiers/ExternalLinkHandler.swift index aff6a661..f48bde1a 100644 --- a/Views/ViewModifiers/ExternalLinkHandler.swift +++ b/Views/ViewModifiers/ExternalLinkHandler.swift @@ -53,7 +53,10 @@ struct ExternalLinkHandler: ViewModifier { case .ask: 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.".localized) + Text(""" + An external link is tapped. \ + However, your current setting does not allow it to be loaded. + """.localized) } } #if os(iOS) diff --git a/Views/ViewModifiers/FileImport.swift b/Views/ViewModifiers/FileImport.swift index 020d7cc6..c3dd86c4 100644 --- a/Views/ViewModifiers/FileImport.swift +++ b/Views/ViewModifiers/FileImport.swift @@ -102,7 +102,8 @@ struct OpenFileHandler: ViewModifier { } message: { alert in switch alert { case .unableToOpen(let filenames): - Text("%@ cannot be opened.".localizedWithFormat(withArgs: ListFormatter.localizedString(byJoining: filenames))) + Text("%@ cannot be opened.".localizedWithFormat(withArgs: + ListFormatter.localizedString(byJoining: filenames))) } } } From 93de4e469e12ebfdf70418a0a367370a8cf84f64 Mon Sep 17 00:00:00 2001 From: tvision106 Date: Tue, 14 Nov 2023 17:15:21 -0500 Subject: [PATCH 08/19] fix codefactor issue --- Views/ViewModifiers/FileImport.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Views/ViewModifiers/FileImport.swift b/Views/ViewModifiers/FileImport.swift index c3dd86c4..edd5f426 100644 --- a/Views/ViewModifiers/FileImport.swift +++ b/Views/ViewModifiers/FileImport.swift @@ -102,8 +102,8 @@ struct OpenFileHandler: ViewModifier { } message: { alert in switch alert { case .unableToOpen(let filenames): - Text("%@ cannot be opened.".localizedWithFormat(withArgs: - ListFormatter.localizedString(byJoining: filenames))) + let name = ListFormatter.localizedString(byJoining: filenames) + Text("%@ cannot be opened.".localizedWithFormat(withArgs: name)) } } } From 9a41a6d949fabbdc85c51e460f19084ec450082c Mon Sep 17 00:00:00 2001 From: tvision106 Date: Wed, 15 Nov 2023 13:24:44 -0500 Subject: [PATCH 09/19] fix some issues for long strings --- Support/en.lproj/Localizable.strings | 21 ++++++++----------- Views/BuildingBlocks/ArticleCell.swift | 7 +------ Views/BuildingBlocks/SearchResultRow.swift | 7 +------ Views/Library/ZimFileDetail.swift | 10 ++------- Views/Settings/About.swift | 8 +------ Views/ViewModifiers/ExternalLinkHandler.swift | 5 +---- 6 files changed, 15 insertions(+), 43 deletions(-) diff --git a/Support/en.lproj/Localizable.strings b/Support/en.lproj/Localizable.strings index fd8353f4..fbcd8a24 100644 --- a/Support/en.lproj/Localizable.strings +++ b/Support/en.lproj/Localizable.strings @@ -45,16 +45,14 @@ "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?"; -"An external link is tapped. \ -However, your current setting does not allow it to be loaded." = "An external link is tapped. \ +"loc-extenral-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"; -"Lorem ipsum dolor sit amet, consectetur adipiscing elit, \ -sed do eiusmod tempor incididunt ut labore et dolore magna aliqua." = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, \ +"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"; @@ -95,12 +93,10 @@ sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."; "Download using cellular" = "Download using cellular"; "Unlink" = "Unlink"; "Unlink %@" = "Unlink %@"; -"All bookmarked articles linked to this zim file will be deleted, \ -but the original file will remain in place." = "All bookmarked articles linked to this zim file will be deleted, \ +"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 %@"; -"The zim file and all bookmarked articles \ -linked to this zim file will be deleted." = "The zim file and all bookmarked articles \ +"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"; @@ -152,9 +148,7 @@ linked to this zim file will be deleted."; "Dependencies" = "Dependencies"; "License" = "License"; "Version" = "Version"; -"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." = "Kiwix is an offline reader for online content like Wikipedia, Project Gutenberg, or TED Talks. \ +"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."; @@ -203,4 +197,7 @@ The software as well as the content is free to use for anyone."; "Vikidia" = "Vikidia"; "StackExchange" = "StackExchange"; "Other" = "Other"; - +"Disabled" = "Disabled"; +"First Paragraph" = "First Paragraph"; +"First Sentence" = "First Sentence"; +"Matches" = "Matches"; diff --git a/Views/BuildingBlocks/ArticleCell.swift b/Views/BuildingBlocks/ArticleCell.swift index fa8069c8..f947e7dd 100644 --- a/Views/BuildingBlocks/ArticleCell.swift +++ b/Views/BuildingBlocks/ArticleCell.swift @@ -65,12 +65,7 @@ struct ArticleCell: View { struct ArticleCell_Previews: PreviewProvider { static let result: SearchResult = { let result = SearchResult(zimFileID: UUID(), path: "", title: "Article Title".localized)! - result.snippet = NSAttributedString(string: - """ - Lorem ipsum dolor sit amet, consectetur adipiscing elit, \ - sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. - """.localized - ) + result.snippet = NSAttributedString(string:"loc-article-cell-template".localized) return result }() diff --git a/Views/BuildingBlocks/SearchResultRow.swift b/Views/BuildingBlocks/SearchResultRow.swift index 57edb5f0..249f72cd 100644 --- a/Views/BuildingBlocks/SearchResultRow.swift +++ b/Views/BuildingBlocks/SearchResultRow.swift @@ -39,12 +39,7 @@ struct SearchResultRow: View { struct SearchResultRow_Previews: PreviewProvider { static let result: SearchResult = { let result = SearchResult(zimFileID: UUID(), path: "", title: "Article Title".localized)! - result.snippet = NSAttributedString(string: - """ - Lorem ipsum dolor sit amet, consectetur adipiscing elit, \ - sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. - """.localized - ) + result.snippet = NSAttributedString(string:"loc-article-cell-template".localized) return result }() diff --git a/Views/Library/ZimFileDetail.swift b/Views/Library/ZimFileDetail.swift index 4b58afaa..7bfafb0a 100644 --- a/Views/Library/ZimFileDetail.swift +++ b/Views/Library/ZimFileDetail.swift @@ -117,10 +117,7 @@ struct ZimFileDetail: View { }.alert(isPresented: $isPresentingUnlinkAlert) { Alert( title: Text("Unlink %@".localizedWithFormat(withArgs: zimFile.name)), - message: Text(""" - All bookmarked articles linked to this zim file will be deleted, \ - but the original file will remain in place. - """), + message: Text("loc-ZimFileDetail-Alert-unlink".localized), primaryButton: .destructive(Text("Unlink".localized)) { LibraryOperations.unlink(zimFileID: zimFile.fileID) #if os(iOS) @@ -138,10 +135,7 @@ struct ZimFileDetail: View { }.alert(isPresented: $isPresentingDeleteAlert) { Alert( title: Text("Delete %@".localizedWithFormat(withArgs: zimFile.name)), - message: Text(""" - The zim file and all bookmarked articles \ - linked to this zim file will be deleted. - """.localized), + message: Text("loc-ZimFileDetail-Alert-Delete".localized), primaryButton: .destructive(Text("Delete".localized)) { LibraryOperations.delete(zimFileID: zimFile.fileID) #if os(iOS) diff --git a/Views/Settings/About.swift b/Views/Settings/About.swift index 72d5bed7..3db016d6 100644 --- a/Views/Settings/About.swift +++ b/Views/Settings/About.swift @@ -78,13 +78,7 @@ 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. - """.localized - ) + Text("loc-About-description".localized) } private var release: some View { diff --git a/Views/ViewModifiers/ExternalLinkHandler.swift b/Views/ViewModifiers/ExternalLinkHandler.swift index f48bde1a..4d3860d4 100644 --- a/Views/ViewModifiers/ExternalLinkHandler.swift +++ b/Views/ViewModifiers/ExternalLinkHandler.swift @@ -53,10 +53,7 @@ struct ExternalLinkHandler: ViewModifier { case .ask: 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. - """.localized) + Text("loc-extenral-alert".localized) } } #if os(iOS) From b3ee97f07d1510a2da81895089492d6605260c8c Mon Sep 17 00:00:00 2001 From: tvision106 Date: Wed, 15 Nov 2023 14:50:45 -0500 Subject: [PATCH 10/19] fix codefactor issue --- Views/BuildingBlocks/ArticleCell.swift | 2 +- Views/BuildingBlocks/SearchResultRow.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Views/BuildingBlocks/ArticleCell.swift b/Views/BuildingBlocks/ArticleCell.swift index f947e7dd..a3a1b3f7 100644 --- a/Views/BuildingBlocks/ArticleCell.swift +++ b/Views/BuildingBlocks/ArticleCell.swift @@ -65,7 +65,7 @@ struct ArticleCell: View { struct ArticleCell_Previews: PreviewProvider { static let result: SearchResult = { let result = SearchResult(zimFileID: UUID(), path: "", title: "Article Title".localized)! - result.snippet = NSAttributedString(string:"loc-article-cell-template".localized) + result.snippet = NSAttributedString(string: "loc-article-cell-template".localized) return result }() diff --git a/Views/BuildingBlocks/SearchResultRow.swift b/Views/BuildingBlocks/SearchResultRow.swift index 249f72cd..690a3732 100644 --- a/Views/BuildingBlocks/SearchResultRow.swift +++ b/Views/BuildingBlocks/SearchResultRow.swift @@ -39,7 +39,7 @@ struct SearchResultRow: View { struct SearchResultRow_Previews: PreviewProvider { static let result: SearchResult = { let result = SearchResult(zimFileID: UUID(), path: "", title: "Article Title".localized)! - result.snippet = NSAttributedString(string:"loc-article-cell-template".localized) + result.snippet = NSAttributedString(string: "loc-article-cell-template".localized) return result }() From 3857568cbdd2fe92bff218146195598a467ab99b Mon Sep 17 00:00:00 2001 From: tvision106 Date: Tue, 14 Nov 2023 16:20:36 -0500 Subject: [PATCH 11/19] Update the localization --- App/App_macOS.swift | 12 +- App/SidebarViewController.swift | 10 +- App/SplitViewController.swift | 2 +- Kiwix.xcodeproj/project.pbxproj | 33 ++- Model/DownloadService.swift | 4 +- Model/Entities/Errors.swift | 6 +- Model/Utilities/String+Extension.swift | 32 +++ Support/en.lproj/Localizable.strings | 202 ++++++++++++++++++ Support/es.lproj/Localizable.strings | 8 + ViewModel/BrowserViewModel.swift | 8 +- Views/Bookmarks.swift | 6 +- Views/BrowserTab.swift | 2 +- Views/BuildingBlocks/ArticleCell.swift | 6 +- Views/BuildingBlocks/Attribute.swift | 4 +- Views/BuildingBlocks/DownloadTaskCell.swift | 4 +- Views/BuildingBlocks/FlavorTag.swift | 6 +- Views/BuildingBlocks/GridSection.swift | 4 +- .../LibraryLastRefreshTime.swift | 4 +- Views/BuildingBlocks/Message.swift | 2 +- Views/BuildingBlocks/SearchResultRow.swift | 4 +- Views/BuildingBlocks/SheetContent.swift | 2 +- Views/BuildingBlocks/TabLabel.swift | 4 +- .../ZimFileMissingIndicator.swift | 2 +- Views/BuildingBlocks/ZimFileRow.swift | 2 +- Views/Buttons/ArticleShortcutButtons.swift | 16 +- Views/Buttons/BookmarkButton.swift | 14 +- Views/Buttons/NavigationButtons.swift | 4 +- Views/Buttons/OutlineButton.swift | 10 +- Views/Buttons/TabsManagerButton.swift | 26 +-- Views/Commands.swift | 10 +- Views/Library/Library.swift | 14 +- Views/Library/ZimFileDetail.swift | 78 +++---- Views/Library/ZimFilesCategories.swift | 10 +- Views/Library/ZimFilesDownloads.swift | 6 +- Views/Library/ZimFilesNew.swift | 8 +- Views/Library/ZimFilesOpened.swift | 10 +- Views/SearchResults.swift | 22 +- Views/Settings/About.swift | 34 +-- Views/Settings/LanguageSelector.swift | 16 +- Views/Settings/Settings.swift | 76 +++---- Views/ViewModifiers/AlertHandler.swift | 2 +- Views/ViewModifiers/BookmarkContextMenu.swift | 4 +- Views/ViewModifiers/ExternalLinkHandler.swift | 10 +- Views/ViewModifiers/FileImport.swift | 6 +- Views/Welcome.swift | 12 +- WikiMed/es.lproj/LaunchScreen.strings | 1 + WikiMed/es.lproj/Main.strings | 1 + 47 files changed, 515 insertions(+), 244 deletions(-) create mode 100644 Model/Utilities/String+Extension.swift create mode 100644 Support/en.lproj/Localizable.strings create mode 100644 Support/es.lproj/Localizable.strings create mode 100644 WikiMed/es.lproj/LaunchScreen.strings create mode 100644 WikiMed/es.lproj/Main.strings diff --git a/App/App_macOS.swift b/App/App_macOS.swift index ce6efe2c..2bbdcbf7 100644 --- a/App/App_macOS.swift +++ b/App/App_macOS.swift @@ -32,10 +32,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) @@ -91,11 +91,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) } } } @@ -106,7 +106,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: diff --git a/App/SidebarViewController.swift b/App/SidebarViewController.swift index 836850f3..a29afb15 100644 --- a/App/SidebarViewController.swift +++ b/App/SidebarViewController.swift @@ -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") diff --git a/App/SplitViewController.swift b/App/SplitViewController.swift index 1e830032..45a733c4 100644 --- a/App/SplitViewController.swift +++ b/App/SplitViewController.swift @@ -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) } } diff --git a/Kiwix.xcodeproj/project.pbxproj b/Kiwix.xcodeproj/project.pbxproj index 2e2bc8ae..2aa1a10a 100644 --- a/Kiwix.xcodeproj/project.pbxproj +++ b/Kiwix.xcodeproj/project.pbxproj @@ -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 */; }; @@ -124,6 +128,11 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 8E4396442B02E443007F0BC4 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Main.strings; sourceTree = ""; }; + 8E4396452B02E443007F0BC4 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/LaunchScreen.strings; sourceTree = ""; }; + 8E4396482B02E455007F0BC4 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; + 8E43964A2B02E458007F0BC4 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Localizable.strings; sourceTree = ""; }; + 8E43964B2B02E4C6007F0BC4 /* String+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Extension.swift"; sourceTree = ""; }; 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 = ""; }; @@ -293,6 +302,7 @@ 9779A7E224567A5A00F6F6FF /* Log.swift */, 97B3BACD2736CE3500A23F49 /* URL.swift */, 9779A73A2456796B00F6F6FF /* WebKitHandler.swift */, + 8E43964B2B02E4C6007F0BC4 /* String+Extension.swift */, ); path = Utilities; sourceTree = ""; @@ -386,8 +396,8 @@ 974E7EE22930201500BDF59C /* ZimFileService */ = { isa = PBXGroup; children = ( - 974E7EE32930201500BDF59C /* ZimFileService.mm */, 974E7EE42930201500BDF59C /* ZimFileService.h */, + 974E7EE32930201500BDF59C /* ZimFileService.mm */, 974E7EE52930201500BDF59C /* ZimFileService.swift */, ); path = ZimFileService; @@ -549,6 +559,7 @@ 97E94B22271EF250005B0295 /* Kiwix.entitlements */, 9779A5D02456796A00F6F6FF /* Kiwix-Bridging-Header.h */, 970885D0271339A300C5795C /* wikipedia_dark.css */, + 8E4396492B02E455007F0BC4 /* Localizable.strings */, ); path = Support; sourceTree = ""; @@ -649,6 +660,7 @@ knownRegions = ( en, Base, + es, ); mainGroup = 97A2AB7F1C1B80FF00052E74; packageReferences = ( @@ -681,6 +693,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 */, ); @@ -691,6 +704,7 @@ buildActionMask = 2147483647; files = ( 97B448A1210FBC2E0004B056 /* LaunchScreen.storyboard in Resources */, + 8E4396472B02E455007F0BC4 /* Localizable.strings in Resources */, 97B4489E210FBC2E0004B056 /* Assets.xcassets in Resources */, 97B4489C210FBC2C0004B056 /* Main.storyboard in Resources */, ); @@ -753,6 +767,7 @@ buildActionMask = 2147483647; files = ( 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 */, @@ -836,6 +851,7 @@ buildActionMask = 2147483647; files = ( 97B44899210FBC2C0004B056 /* ViewController.swift in Sources */, + 8E43964D2B02E4C6007F0BC4 /* String+Extension.swift in Sources */, 97B44897210FBC2C0004B056 /* AppDelegate.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -851,10 +867,20 @@ /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ + 8E4396492B02E455007F0BC4 /* Localizable.strings */ = { + isa = PBXVariantGroup; + children = ( + 8E4396482B02E455007F0BC4 /* en */, + 8E43964A2B02E458007F0BC4 /* es */, + ); + name = Localizable.strings; + sourceTree = ""; + }; 97B4489A210FBC2C0004B056 /* Main.storyboard */ = { isa = PBXVariantGroup; children = ( 97B4489B210FBC2C0004B056 /* Base */, + 8E4396442B02E443007F0BC4 /* es */, ); name = Main.storyboard; sourceTree = ""; @@ -863,6 +889,7 @@ isa = PBXVariantGroup; children = ( 97B448A0210FBC2E0004B056 /* Base */, + 8E4396452B02E443007F0BC4 /* es */, ); name = LaunchScreen.storyboard; sourceTree = ""; @@ -943,7 +970,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 119; DEVELOPMENT_ASSET_PATHS = ""; - DEVELOPMENT_TEAM = L7HWM3SP3L; + DEVELOPMENT_TEAM = ""; "ENABLE_HARDENED_RUNTIME[sdk=macosx*]" = YES; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; @@ -989,7 +1016,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 119; DEVELOPMENT_ASSET_PATHS = ""; - DEVELOPMENT_TEAM = L7HWM3SP3L; + DEVELOPMENT_TEAM = ""; "ENABLE_HARDENED_RUNTIME[sdk=macosx*]" = YES; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; diff --git a/Model/DownloadService.swift b/Model/DownloadService.swift index 39761c77..a51b0e1d 100644 --- a/Model/DownloadService.swift +++ b/Model/DownloadService.swift @@ -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 diff --git a/Model/Entities/Errors.swift b/Model/Entities/Errors.swift index 45c9a26b..46ce9d55 100644 --- a/Model/Entities/Errors.swift +++ b/Model/Entities/Errors.swift @@ -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") } } } diff --git a/Model/Utilities/String+Extension.swift b/Model/Utilities/String+Extension.swift new file mode 100644 index 00000000..9f1ad136 --- /dev/null +++ b/Model/Utilities/String+Extension.swift @@ -0,0 +1,32 @@ +// +// 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) + } + } + +} + diff --git a/Support/en.lproj/Localizable.strings b/Support/en.lproj/Localizable.strings new file mode 100644 index 00000000..63237872 --- /dev/null +++ b/Support/en.lproj/Localizable.strings @@ -0,0 +1,202 @@ +/* + 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?"; +"An external link is tapped. However, your current setting does not allow it to be loaded." = "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"; +"Lorem ipsum dolor sit amet, consectetur adipiscing elit, \ +sed do eiusmod tempor incididunt ut labore et dolore magna aliqua." = "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 %@"; +"All bookmarked articles linked to this zim file will be deleted, \ +but the original file will remain in place." = "All bookmarked articles linked to this zim file will be deleted, \ +but the original file will remain in place."; +"Delete %@" = "Delete %@"; +"The zim file and all bookmarked articles linked to this zim file will be deleted." = "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"; +"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." = "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"; + diff --git a/Support/es.lproj/Localizable.strings b/Support/es.lproj/Localizable.strings new file mode 100644 index 00000000..e83d7f5c --- /dev/null +++ b/Support/es.lproj/Localizable.strings @@ -0,0 +1,8 @@ +/* + Localizable.strings + Kiwix + + Created by tvision251 on 11/13/23. + Copyright © 2023 Chris Li. All rights reserved. +*/ + diff --git a/ViewModel/BrowserViewModel.swift b/ViewModel/BrowserViewModel.swift index 41fa872d..b77ce98e 100644 --- a/ViewModel/BrowserViewModel.swift +++ b/ViewModel/BrowserViewModel.swift @@ -251,12 +251,12 @@ 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) } ) @@ -267,11 +267,11 @@ 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", image: UIImage(systemName: "star.slash.fill")) { _ in + return UIAction(title: "Remove Bookmark".localized, image: UIImage(systemName: "star.slash.fill")) { _ in self.deleteBookmark(url: url) } } else { - return UIAction(title: "Bookmark", image: UIImage(systemName: "star")) { _ in + return UIAction(title: "Bookmark".localized, image: UIImage(systemName: "star")) { _ in self.createBookmark(url: url) } } diff --git a/Views/Bookmarks.swift b/Views/Bookmarks.swift index f24ab25d..988023e8 100644 --- a/Views/Bookmarks.swift +++ b/Views/Bookmarks.swift @@ -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") } } } diff --git a/Views/BrowserTab.swift b/Views/BrowserTab.swift index 9a080449..aec014f5 100644 --- a/Views/BrowserTab.swift +++ b/Views/BrowserTab.swift @@ -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() diff --git a/Views/BuildingBlocks/ArticleCell.swift b/Views/BuildingBlocks/ArticleCell.swift index f5681f32..fa8069c8 100644 --- a/Views/BuildingBlocks/ArticleCell.swift +++ b/Views/BuildingBlocks/ArticleCell.swift @@ -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,12 +64,12 @@ struct ArticleCell: View { struct ArticleCell_Previews: PreviewProvider { static let result: SearchResult = { - let result = SearchResult(zimFileID: UUID(), path: "", title: "Article Title")! + let result = SearchResult(zimFileID: UUID(), path: "", title: "Article Title".localized)! result.snippet = NSAttributedString(string: """ Lorem ipsum dolor sit amet, consectetur adipiscing elit, \ sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. - """ + """.localized ) return result }() diff --git a/Views/BuildingBlocks/Attribute.swift b/Views/BuildingBlocks/Attribute.swift index 038f9202..98ff221b 100644 --- a/Views/BuildingBlocks/Attribute.swift +++ b/Views/BuildingBlocks/Attribute.swift @@ -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) diff --git a/Views/BuildingBlocks/DownloadTaskCell.swift b/Views/BuildingBlocks/DownloadTaskCell.swift index f1fb7f1a..530cd7ad 100644 --- a/Views/BuildingBlocks/DownloadTaskCell.swift +++ b/Views/BuildingBlocks/DownloadTaskCell.swift @@ -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") } diff --git a/Views/BuildingBlocks/FlavorTag.swift b/Views/BuildingBlocks/FlavorTag.swift index ffe22e3b..17e3c682 100644 --- a/Views/BuildingBlocks/FlavorTag.swift +++ b/Views/BuildingBlocks/FlavorTag.swift @@ -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 } } } diff --git a/Views/BuildingBlocks/GridSection.swift b/Views/BuildingBlocks/GridSection.swift index 6dd622d5..d820a915 100644 --- a/Views/BuildingBlocks/GridSection.swift +++ b/Views/BuildingBlocks/GridSection.swift @@ -29,8 +29,8 @@ struct GridSection: View { struct GridSection_Previews: PreviewProvider { static var previews: some View { - GridSection(title: "Header Text") { - Text("Content") + GridSection(title: "Header Text".localized) { + Text("Content".localized) } } } diff --git a/Views/BuildingBlocks/LibraryLastRefreshTime.swift b/Views/BuildingBlocks/LibraryLastRefreshTime.swift index 47156b96..9fbd4aa9 100644 --- a/Views/BuildingBlocks/LibraryLastRefreshTime.swift +++ b/Views/BuildingBlocks/LibraryLastRefreshTime.swift @@ -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) } } } diff --git a/Views/BuildingBlocks/Message.swift b/Views/BuildingBlocks/Message.swift index 4992e79d..2b0e5a88 100644 --- a/Views/BuildingBlocks/Message.swift +++ b/Views/BuildingBlocks/Message.swift @@ -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) } } diff --git a/Views/BuildingBlocks/SearchResultRow.swift b/Views/BuildingBlocks/SearchResultRow.swift index 55b33dc7..57edb5f0 100644 --- a/Views/BuildingBlocks/SearchResultRow.swift +++ b/Views/BuildingBlocks/SearchResultRow.swift @@ -38,12 +38,12 @@ struct SearchResultRow: View { struct SearchResultRow_Previews: PreviewProvider { static let result: SearchResult = { - let result = SearchResult(zimFileID: UUID(), path: "", title: "Article Title")! + let result = SearchResult(zimFileID: UUID(), path: "", title: "Article Title".localized)! result.snippet = NSAttributedString(string: """ Lorem ipsum dolor sit amet, consectetur adipiscing elit, \ sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. - """ + """.localized ) return result }() diff --git a/Views/BuildingBlocks/SheetContent.swift b/Views/BuildingBlocks/SheetContent.swift index f7f3fa06..022ea301 100644 --- a/Views/BuildingBlocks/SheetContent.swift +++ b/Views/BuildingBlocks/SheetContent.swift @@ -27,7 +27,7 @@ struct SheetContent: View { Button { dismiss() } label: { - Text("Done").fontWeight(.semibold) + Text("Done".localized).fontWeight(.semibold) } } } diff --git a/Views/BuildingBlocks/TabLabel.swift b/Views/BuildingBlocks/TabLabel.swift index ebd8fa59..e31847de 100644 --- a/Views/BuildingBlocks/TabLabel.swift +++ b/Views/BuildingBlocks/TabLabel.swift @@ -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") } } } diff --git a/Views/BuildingBlocks/ZimFileMissingIndicator.swift b/Views/BuildingBlocks/ZimFileMissingIndicator.swift index da7c3b3c..2e0a23a8 100644 --- a/Views/BuildingBlocks/ZimFileMissingIndicator.swift +++ b/Views/BuildingBlocks/ZimFileMissingIndicator.swift @@ -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) } } diff --git a/Views/BuildingBlocks/ZimFileRow.swift b/Views/BuildingBlocks/ZimFileRow.swift index 0fb4d387..73510e56 100644 --- a/Views/BuildingBlocks/ZimFileRow.swift +++ b/Views/BuildingBlocks/ZimFileRow.swift @@ -30,7 +30,7 @@ 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) } diff --git a/Views/Buttons/ArticleShortcutButtons.swift b/Views/Buttons/ArticleShortcutButtons.swift index 79ee831d..7e466047 100644 --- a/Views/Buttons/ArticleShortcutButtons.swift +++ b/Views/Buttons/ArticleShortcutButtons.swift @@ -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 } } diff --git a/Views/Buttons/BookmarkButton.swift b/Views/Buttons/BookmarkButton.swift index 38508442..96b40565 100644 --- a/Views/Buttons/BookmarkButton.swift +++ b/Views/Buttons/BookmarkButton.swift @@ -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) } } } diff --git a/Views/Buttons/NavigationButtons.swift b/Views/Buttons/NavigationButtons.swift index e30dec40..8f8f121d 100644 --- a/Views/Buttons/NavigationButtons.swift +++ b/Views/Buttons/NavigationButtons.swift @@ -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) } } diff --git a/Views/Buttons/OutlineButton.swift b/Views/Buttons/OutlineButton.swift index 671b74d9..3eb7ef87 100644 --- a/Views/Buttons/OutlineButton.swift +++ b/Views/Buttons/OutlineButton.swift @@ -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) } } } diff --git a/Views/Buttons/TabsManagerButton.swift b/Views/Buttons/TabsManagerButton.swift index f8bf203a..212ce302 100644 --- a/Views/Buttons/TabsManagerButton.swift +++ b/Views/Buttons/TabsManagerButton.swift @@ -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() } diff --git a/Views/Commands.swift b/Views/Commands.swift index 7550ec0e..61672c3d 100644 --- a/Views/Commands.swift +++ b/Views/Commands.swift @@ -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) } diff --git a/Views/Library/Library.swift b/Views/Library/Library.swift index 06d9770c..b2786227 100644 --- a/Views/Library/Library.swift +++ b/Views/Library/Library.swift @@ -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") } } } diff --git a/Views/Library/ZimFileDetail.swift b/Views/Library/ZimFileDetail.swift index 72b0d948..5ee91f1a 100644 --- a/Views/Library/ZimFileDetail.swift +++ b/Views/Library/ZimFileDetail.swift @@ -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,23 @@ 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)"), + title: Text("Unlink %@".localizedWithFormat(withArgs: 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")) { + primaryButton: .destructive(Text("Unlink".localized)) { LibraryOperations.unlink(zimFileID: zimFile.fileID) #if os(iOS) presentationMode.wrappedValue.dismiss() @@ -137,9 +137,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("The zim file and all bookmarked articles linked to this zim file will be deleted.".localized), + primaryButton: .destructive(Text("Delete".localized)) { LibraryOperations.delete(zimFileID: zimFile.fileID) #if os(iOS) presentationMode.wrappedValue.dismiss() @@ -151,7 +151,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 +159,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 +177,37 @@ 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 +237,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 +313,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 +329,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 diff --git a/Views/Library/ZimFilesCategories.swift b/Views/Library/ZimFilesCategories.swift index b5827709..f08c6298 100644 --- a/Views/Library/ZimFilesCategories.swift +++ b/Views/Library/ZimFilesCategories.swift @@ -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) diff --git a/Views/Library/ZimFilesDownloads.swift b/Views/Library/ZimFilesDownloads.swift index 54c3f678..ea83b8c0 100644 --- a/Views/Library/ZimFilesDownloads.swift +++ b/Views/Library/ZimFilesDownloads.swift @@ -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") } } } diff --git a/Views/Library/ZimFilesNew.swift b/Views/Library/ZimFilesNew.swift index 3ae5c3ae..772db276 100644 --- a/Views/Library/ZimFilesNew.swift +++ b/Views/Library/ZimFilesNew.swift @@ -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") } } } diff --git a/Views/Library/ZimFilesOpened.swift b/Views/Library/ZimFilesOpened.swift index 0b19b015..dfdf3f70 100644 --- a/Views/Library/ZimFilesOpened.swift +++ b/Views/Library/ZimFilesOpened.swift @@ -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) } } } diff --git a/Views/SearchResults.swift b/Views/SearchResults.swift index c1192cf1..a617a1fe 100644 --- a/Views/SearchResults.swift +++ b/Views/SearchResults.swift @@ -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) } } } diff --git a/Views/Settings/About.swift b/Views/Settings/About.swift index 09ea7bf0..c9a7407c 100644 --- a/Views/Settings/About.swift +++ b/Views/Settings/About.swift @@ -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() } @@ -83,36 +83,36 @@ struct About: View { 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. - """ + """.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") } } diff --git a/Views/Settings/LanguageSelector.swift b/Views/Settings/LanguageSelector.swift index 8dcaf387..d0375124 100644 --- a/Views/Settings/LanguageSelector.swift +++ b/Views/Settings/LanguageSelector.swift @@ -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 { diff --git a/Views/Settings/Settings.swift b/Views/Settings/Settings.swift index 71f652a3..bee1ddc5 100644 --- a/Views/Settings/Settings.swift +++ b/Views/Settings/Settings.swift @@ -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,22 @@ 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 +154,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".lowercased) { + 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 +215,7 @@ private struct SelectedLanaguageLabel: View { var body: some View { HStack { - Text("Languages") + Text("Languages".localized) Spacer() if languageCodes.count == 1, let languageCode = languageCodes.first, diff --git a/Views/ViewModifiers/AlertHandler.swift b/Views/ViewModifiers/AlertHandler.swift index 940a4ecc..b305b055 100644 --- a/Views/ViewModifiers/AlertHandler.swift +++ b/Views/ViewModifiers/AlertHandler.swift @@ -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)) } } } diff --git a/Views/ViewModifiers/BookmarkContextMenu.swift b/Views/ViewModifiers/BookmarkContextMenu.swift index 235e984d..5620f604 100644 --- a/Views/ViewModifiers/BookmarkContextMenu.swift +++ b/Views/ViewModifiers/BookmarkContextMenu.swift @@ -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") } } } diff --git a/Views/ViewModifiers/ExternalLinkHandler.swift b/Views/ViewModifiers/ExternalLinkHandler.swift index 0fea5c71..3bb7c493 100644 --- a/Views/ViewModifiers/ExternalLinkHandler.swift +++ b/Views/ViewModifiers/ExternalLinkHandler.swift @@ -41,19 +41,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("An external link is tapped. However, your current setting does not allow it to be loaded.".localized) } } #if os(iOS) diff --git a/Views/ViewModifiers/FileImport.swift b/Views/ViewModifiers/FileImport.swift index da40c6ea..020d7cc6 100644 --- a/Views/ViewModifiers/FileImport.swift +++ b/Views/ViewModifiers/FileImport.swift @@ -41,7 +41,7 @@ struct OpenFileButton: 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,11 @@ 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.") + Text("%@ cannot be opened.".localizedWithFormat(withArgs: ListFormatter.localizedString(byJoining: filenames))) } } } diff --git a/Views/Welcome.swift b/Views/Welcome.swift index 3c079fe4..377c58e6 100644 --- a/Views/Welcome.swift +++ b/Views/Welcome.swift @@ -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) diff --git a/WikiMed/es.lproj/LaunchScreen.strings b/WikiMed/es.lproj/LaunchScreen.strings new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/WikiMed/es.lproj/LaunchScreen.strings @@ -0,0 +1 @@ + diff --git a/WikiMed/es.lproj/Main.strings b/WikiMed/es.lproj/Main.strings new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/WikiMed/es.lproj/Main.strings @@ -0,0 +1 @@ + From df10ab25e2b3fcbdea4db60d23c5363b63b8a66b Mon Sep 17 00:00:00 2001 From: tvision106 Date: Tue, 14 Nov 2023 17:12:29 -0500 Subject: [PATCH 12/19] fix codefactor issue --- Model/Utilities/String+Extension.swift | 9 ++++----- Support/en.lproj/Localizable.strings | 8 ++++++-- ViewModel/BrowserViewModel.swift | 3 ++- Views/BuildingBlocks/ZimFileRow.swift | 3 ++- Views/Library/ZimFileDetail.swift | 8 ++++++-- Views/Settings/About.swift | 3 ++- Views/Settings/Settings.swift | 3 ++- Views/ViewModifiers/ExternalLinkHandler.swift | 5 ++++- Views/ViewModifiers/FileImport.swift | 3 ++- 9 files changed, 30 insertions(+), 15 deletions(-) diff --git a/Model/Utilities/String+Extension.swift b/Model/Utilities/String+Extension.swift index 9f1ad136..e782c0d1 100644 --- a/Model/Utilities/String+Extension.swift +++ b/Model/Utilities/String+Extension.swift @@ -14,7 +14,7 @@ extension String { return NSLocalizedString(self, tableName: nil, bundle: Bundle.main, value: "", comment: "") } - func localized(withComment:String) -> String { + func localized(withComment: String) -> String { return NSLocalizedString(self, tableName: nil, bundle: Bundle.main, value: "", comment: withComment) } @@ -22,11 +22,10 @@ extension 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) + case 1: return String.localizedStringWithFormat(format, withArgs[0]) + case 2: return String.localizedStringWithFormat(format, withArgs[0], withArgs[1]) + default: return String.localizedStringWithFormat(format, withArgs) } } } - diff --git a/Support/en.lproj/Localizable.strings b/Support/en.lproj/Localizable.strings index 63237872..fd8353f4 100644 --- a/Support/en.lproj/Localizable.strings +++ b/Support/en.lproj/Localizable.strings @@ -45,7 +45,9 @@ "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?"; -"An external link is tapped. However, your current setting does not allow it to be loaded." = "An external link is tapped. However, your current setting does not allow it to be loaded."; +"An external link is tapped. \ +However, your current setting does not allow it to be loaded." = "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."; @@ -97,7 +99,9 @@ sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."; but the original file will remain in place." = "All bookmarked articles linked to this zim file will be deleted, \ but the original file will remain in place."; "Delete %@" = "Delete %@"; -"The zim file and all bookmarked articles linked to this zim file will be deleted." = "The zim file and all bookmarked articles linked to this zim file will be deleted."; +"The zim file and all bookmarked articles \ +linked to this zim file will be deleted." = "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."; diff --git a/ViewModel/BrowserViewModel.swift b/ViewModel/BrowserViewModel.swift index b77ce98e..e27399da 100644 --- a/ViewModel/BrowserViewModel.swift +++ b/ViewModel/BrowserViewModel.swift @@ -267,7 +267,8 @@ 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".localized, image: UIImage(systemName: "star.slash.fill")) { _ in + return UIAction(title: "Remove Bookmark".localized, + image: UIImage(systemName: "star.slash.fill")) { _ in self.deleteBookmark(url: url) } } else { diff --git a/Views/BuildingBlocks/ZimFileRow.swift b/Views/BuildingBlocks/ZimFileRow.swift index 73510e56..df40aa9d 100644 --- a/Views/BuildingBlocks/ZimFileRow.swift +++ b/Views/BuildingBlocks/ZimFileRow.swift @@ -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".localized + return "\(zimFile.articleCount.formatted(.number.notation(.compactName)))" + + "articles".localized } else { return Formatter.largeNumber(zimFile.articleCount) } diff --git a/Views/Library/ZimFileDetail.swift b/Views/Library/ZimFileDetail.swift index 5ee91f1a..4b58afaa 100644 --- a/Views/Library/ZimFileDetail.swift +++ b/Views/Library/ZimFileDetail.swift @@ -138,7 +138,10 @@ struct ZimFileDetail: View { }.alert(isPresented: $isPresentingDeleteAlert) { Alert( title: Text("Delete %@".localizedWithFormat(withArgs: zimFile.name)), - message: Text("The zim file and all bookmarked articles linked to this zim file will be deleted.".localized), + message: Text(""" + The zim file and all bookmarked articles \ + linked to this zim file will be deleted. + """.localized), primaryButton: .destructive(Text("Delete".localized)) { LibraryOperations.delete(zimFileID: zimFile.fileID) #if os(iOS) @@ -177,7 +180,8 @@ struct ZimFileDetail: View { @ViewBuilder var basicInfo: some View { - Attribute(title: "Language".localized, detail: Locale.current.localizedString(forLanguageCode: zimFile.languageCode)) + 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)) diff --git a/Views/Settings/About.swift b/Views/Settings/About.swift index c9a7407c..72d5bed7 100644 --- a/Views/Settings/About.swift +++ b/Views/Settings/About.swift @@ -92,7 +92,8 @@ struct About: View { } private var appVersion: some View { - Attribute(title: "Version".localized, detail: Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String) + Attribute(title: "Version".localized, + detail: Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String) } private var buildNumber: some View { diff --git a/Views/Settings/Settings.swift b/Views/Settings/Settings.swift index bee1ddc5..cf6bbd85 100644 --- a/Views/Settings/Settings.swift +++ b/Views/Settings/Settings.swift @@ -132,7 +132,8 @@ struct Settings: View { var readingSettings: some View { Section("Reading".localized) { Stepper(value: $webViewPageZoom, in: 0.5...2, step: 0.05) { - Text("Page zoom".localized + ": \(Formatter.percent.string(from: NSNumber(value: webViewPageZoom)) ?? "")") + Text("Page zoom".localized + + ": \(Formatter.percent.string(from: NSNumber(value: webViewPageZoom)) ?? "")") } Picker("External link".localized, selection: $externalLinkLoadingPolicy) { ForEach(ExternalLinkLoadingPolicy.allCases) { loadingPolicy in diff --git a/Views/ViewModifiers/ExternalLinkHandler.swift b/Views/ViewModifiers/ExternalLinkHandler.swift index 3bb7c493..22a35142 100644 --- a/Views/ViewModifiers/ExternalLinkHandler.swift +++ b/Views/ViewModifiers/ExternalLinkHandler.swift @@ -53,7 +53,10 @@ struct ExternalLinkHandler: ViewModifier { case .ask: 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.".localized) + Text(""" + An external link is tapped. \ + However, your current setting does not allow it to be loaded. + """.localized) } } #if os(iOS) diff --git a/Views/ViewModifiers/FileImport.swift b/Views/ViewModifiers/FileImport.swift index 020d7cc6..c3dd86c4 100644 --- a/Views/ViewModifiers/FileImport.swift +++ b/Views/ViewModifiers/FileImport.swift @@ -102,7 +102,8 @@ struct OpenFileHandler: ViewModifier { } message: { alert in switch alert { case .unableToOpen(let filenames): - Text("%@ cannot be opened.".localizedWithFormat(withArgs: ListFormatter.localizedString(byJoining: filenames))) + Text("%@ cannot be opened.".localizedWithFormat(withArgs: + ListFormatter.localizedString(byJoining: filenames))) } } } From 25c8ef322c185c7c7e4315eccaba3e7ae05dbaac Mon Sep 17 00:00:00 2001 From: tvision106 Date: Tue, 14 Nov 2023 17:15:21 -0500 Subject: [PATCH 13/19] fix codefactor issue --- Views/ViewModifiers/FileImport.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Views/ViewModifiers/FileImport.swift b/Views/ViewModifiers/FileImport.swift index c3dd86c4..edd5f426 100644 --- a/Views/ViewModifiers/FileImport.swift +++ b/Views/ViewModifiers/FileImport.swift @@ -102,8 +102,8 @@ struct OpenFileHandler: ViewModifier { } message: { alert in switch alert { case .unableToOpen(let filenames): - Text("%@ cannot be opened.".localizedWithFormat(withArgs: - ListFormatter.localizedString(byJoining: filenames))) + let name = ListFormatter.localizedString(byJoining: filenames) + Text("%@ cannot be opened.".localizedWithFormat(withArgs: name)) } } } From 22e555dd39fa34b4b77710398c9d28de206f7675 Mon Sep 17 00:00:00 2001 From: tvision106 Date: Wed, 15 Nov 2023 13:24:44 -0500 Subject: [PATCH 14/19] fix some issues for long strings --- Support/en.lproj/Localizable.strings | 21 ++++++++----------- Views/BuildingBlocks/ArticleCell.swift | 7 +------ Views/BuildingBlocks/SearchResultRow.swift | 7 +------ Views/Library/ZimFileDetail.swift | 10 ++------- Views/Settings/About.swift | 8 +------ Views/ViewModifiers/ExternalLinkHandler.swift | 5 +---- 6 files changed, 15 insertions(+), 43 deletions(-) diff --git a/Support/en.lproj/Localizable.strings b/Support/en.lproj/Localizable.strings index fd8353f4..fbcd8a24 100644 --- a/Support/en.lproj/Localizable.strings +++ b/Support/en.lproj/Localizable.strings @@ -45,16 +45,14 @@ "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?"; -"An external link is tapped. \ -However, your current setting does not allow it to be loaded." = "An external link is tapped. \ +"loc-extenral-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"; -"Lorem ipsum dolor sit amet, consectetur adipiscing elit, \ -sed do eiusmod tempor incididunt ut labore et dolore magna aliqua." = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, \ +"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"; @@ -95,12 +93,10 @@ sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."; "Download using cellular" = "Download using cellular"; "Unlink" = "Unlink"; "Unlink %@" = "Unlink %@"; -"All bookmarked articles linked to this zim file will be deleted, \ -but the original file will remain in place." = "All bookmarked articles linked to this zim file will be deleted, \ +"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 %@"; -"The zim file and all bookmarked articles \ -linked to this zim file will be deleted." = "The zim file and all bookmarked articles \ +"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"; @@ -152,9 +148,7 @@ linked to this zim file will be deleted."; "Dependencies" = "Dependencies"; "License" = "License"; "Version" = "Version"; -"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." = "Kiwix is an offline reader for online content like Wikipedia, Project Gutenberg, or TED Talks. \ +"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."; @@ -203,4 +197,7 @@ The software as well as the content is free to use for anyone."; "Vikidia" = "Vikidia"; "StackExchange" = "StackExchange"; "Other" = "Other"; - +"Disabled" = "Disabled"; +"First Paragraph" = "First Paragraph"; +"First Sentence" = "First Sentence"; +"Matches" = "Matches"; diff --git a/Views/BuildingBlocks/ArticleCell.swift b/Views/BuildingBlocks/ArticleCell.swift index fa8069c8..f947e7dd 100644 --- a/Views/BuildingBlocks/ArticleCell.swift +++ b/Views/BuildingBlocks/ArticleCell.swift @@ -65,12 +65,7 @@ struct ArticleCell: View { struct ArticleCell_Previews: PreviewProvider { static let result: SearchResult = { let result = SearchResult(zimFileID: UUID(), path: "", title: "Article Title".localized)! - result.snippet = NSAttributedString(string: - """ - Lorem ipsum dolor sit amet, consectetur adipiscing elit, \ - sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. - """.localized - ) + result.snippet = NSAttributedString(string:"loc-article-cell-template".localized) return result }() diff --git a/Views/BuildingBlocks/SearchResultRow.swift b/Views/BuildingBlocks/SearchResultRow.swift index 57edb5f0..249f72cd 100644 --- a/Views/BuildingBlocks/SearchResultRow.swift +++ b/Views/BuildingBlocks/SearchResultRow.swift @@ -39,12 +39,7 @@ struct SearchResultRow: View { struct SearchResultRow_Previews: PreviewProvider { static let result: SearchResult = { let result = SearchResult(zimFileID: UUID(), path: "", title: "Article Title".localized)! - result.snippet = NSAttributedString(string: - """ - Lorem ipsum dolor sit amet, consectetur adipiscing elit, \ - sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. - """.localized - ) + result.snippet = NSAttributedString(string:"loc-article-cell-template".localized) return result }() diff --git a/Views/Library/ZimFileDetail.swift b/Views/Library/ZimFileDetail.swift index 4b58afaa..7bfafb0a 100644 --- a/Views/Library/ZimFileDetail.swift +++ b/Views/Library/ZimFileDetail.swift @@ -117,10 +117,7 @@ struct ZimFileDetail: View { }.alert(isPresented: $isPresentingUnlinkAlert) { Alert( title: Text("Unlink %@".localizedWithFormat(withArgs: zimFile.name)), - message: Text(""" - All bookmarked articles linked to this zim file will be deleted, \ - but the original file will remain in place. - """), + message: Text("loc-ZimFileDetail-Alert-unlink".localized), primaryButton: .destructive(Text("Unlink".localized)) { LibraryOperations.unlink(zimFileID: zimFile.fileID) #if os(iOS) @@ -138,10 +135,7 @@ struct ZimFileDetail: View { }.alert(isPresented: $isPresentingDeleteAlert) { Alert( title: Text("Delete %@".localizedWithFormat(withArgs: zimFile.name)), - message: Text(""" - The zim file and all bookmarked articles \ - linked to this zim file will be deleted. - """.localized), + message: Text("loc-ZimFileDetail-Alert-Delete".localized), primaryButton: .destructive(Text("Delete".localized)) { LibraryOperations.delete(zimFileID: zimFile.fileID) #if os(iOS) diff --git a/Views/Settings/About.swift b/Views/Settings/About.swift index 72d5bed7..3db016d6 100644 --- a/Views/Settings/About.swift +++ b/Views/Settings/About.swift @@ -78,13 +78,7 @@ 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. - """.localized - ) + Text("loc-About-description".localized) } private var release: some View { diff --git a/Views/ViewModifiers/ExternalLinkHandler.swift b/Views/ViewModifiers/ExternalLinkHandler.swift index 22a35142..d7db201a 100644 --- a/Views/ViewModifiers/ExternalLinkHandler.swift +++ b/Views/ViewModifiers/ExternalLinkHandler.swift @@ -53,10 +53,7 @@ struct ExternalLinkHandler: ViewModifier { case .ask: 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. - """.localized) + Text("loc-extenral-alert".localized) } } #if os(iOS) From 095a6baac4cdccdd63e6f85ec1d1f3fd98cc8e88 Mon Sep 17 00:00:00 2001 From: tvision106 Date: Wed, 15 Nov 2023 14:50:45 -0500 Subject: [PATCH 15/19] fix codefactor issue --- Views/BuildingBlocks/ArticleCell.swift | 2 +- Views/BuildingBlocks/SearchResultRow.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Views/BuildingBlocks/ArticleCell.swift b/Views/BuildingBlocks/ArticleCell.swift index f947e7dd..a3a1b3f7 100644 --- a/Views/BuildingBlocks/ArticleCell.swift +++ b/Views/BuildingBlocks/ArticleCell.swift @@ -65,7 +65,7 @@ struct ArticleCell: View { struct ArticleCell_Previews: PreviewProvider { static let result: SearchResult = { let result = SearchResult(zimFileID: UUID(), path: "", title: "Article Title".localized)! - result.snippet = NSAttributedString(string:"loc-article-cell-template".localized) + result.snippet = NSAttributedString(string: "loc-article-cell-template".localized) return result }() diff --git a/Views/BuildingBlocks/SearchResultRow.swift b/Views/BuildingBlocks/SearchResultRow.swift index 249f72cd..690a3732 100644 --- a/Views/BuildingBlocks/SearchResultRow.swift +++ b/Views/BuildingBlocks/SearchResultRow.swift @@ -39,7 +39,7 @@ struct SearchResultRow: View { struct SearchResultRow_Previews: PreviewProvider { static let result: SearchResult = { let result = SearchResult(zimFileID: UUID(), path: "", title: "Article Title".localized)! - result.snippet = NSAttributedString(string:"loc-article-cell-template".localized) + result.snippet = NSAttributedString(string: "loc-article-cell-template".localized) return result }() From d0e310180043216ba7cd29c855ab845a3e544ebc Mon Sep 17 00:00:00 2001 From: tvision106 Date: Mon, 20 Nov 2023 10:13:15 -0500 Subject: [PATCH 16/19] fix typo issue --- Support/en.lproj/Localizable.strings | 2 +- Views/ViewModifiers/ExternalLinkHandler.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Support/en.lproj/Localizable.strings b/Support/en.lproj/Localizable.strings index fbcd8a24..b386eb7f 100644 --- a/Support/en.lproj/Localizable.strings +++ b/Support/en.lproj/Localizable.strings @@ -45,7 +45,7 @@ "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-extenral-alert" = "An external link is tapped. \ +"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"; diff --git a/Views/ViewModifiers/ExternalLinkHandler.swift b/Views/ViewModifiers/ExternalLinkHandler.swift index d7db201a..68d426ee 100644 --- a/Views/ViewModifiers/ExternalLinkHandler.swift +++ b/Views/ViewModifiers/ExternalLinkHandler.swift @@ -53,7 +53,7 @@ struct ExternalLinkHandler: ViewModifier { case .ask: Text("An external link is tapped, do you wish to load the link?".localized) case .notLoading: - Text("loc-extenral-alert".localized) + Text("loc-external-alert".localized) } } #if os(iOS) From 2c9f8d8cd56b10e74140ce51d91cc0c69eb8b2aa Mon Sep 17 00:00:00 2001 From: tvision106 Date: Tue, 21 Nov 2023 17:45:02 -0500 Subject: [PATCH 17/19] CI/CD - apple developer team --- Kiwix.xcodeproj/project.pbxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Kiwix.xcodeproj/project.pbxproj b/Kiwix.xcodeproj/project.pbxproj index 2aa1a10a..a84aa688 100644 --- a/Kiwix.xcodeproj/project.pbxproj +++ b/Kiwix.xcodeproj/project.pbxproj @@ -970,7 +970,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 119; DEVELOPMENT_ASSET_PATHS = ""; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = L7HWM3SP3L; "ENABLE_HARDENED_RUNTIME[sdk=macosx*]" = YES; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; @@ -1016,7 +1016,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 119; DEVELOPMENT_ASSET_PATHS = ""; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = L7HWM3SP3L; "ENABLE_HARDENED_RUNTIME[sdk=macosx*]" = YES; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; From 6bd468a64b7ed9dcb4b294bf623ee66365990ebd Mon Sep 17 00:00:00 2001 From: renaud gaudin Date: Fri, 17 Nov 2023 16:04:49 +0000 Subject: [PATCH 18/19] Introduce Continuous Deployment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Automatically building and deploying in Github Actions for Nightlies and Releases. Triggered by the following: - every day at 01:32am (nightly mode) - manualy (nightly mode) - on release publication (release mode) This workflow makes extensive use of secrets with no additional safe-guard, given: - `schedule` (nightly) runs only off `main` branch. - `workflow_dispatch` (manual) can run on any in-repo branch (but uses the workflow from `main`) - Release publication requires push access to repo. There are thus two *modes*: Release and Nightly (also used on manual dispatch). The mode sets the `VERSION` either to the YYYY-MM-DD date for nightly or the tag-name for the release. It has four *targets*: `macOS dmg`, `macOS app-store`, `iOS ipa` and `iOS app-store` - **macOS dmg**: universal notarized macOS App in a dmg uploaded to `Kiwix-$VERSION.dmg` - **macOS app-store**: universal notarized macOS App uploaded to the App Store. - **iOS ipa**: iOS App uploaded to `Kiwix-$VERSION.ipa` - **iOS app-store**: iOS App uploaded to the App Store Code Signing is *automatic* (xcode decides which one to use based on availability). We use Apple Distribution one for the app-store targets. IPA uses Apple Development and dmg uses Developer ID. ⚠️ This allows updates CI workflow to make use of the shared xcbuild action --- .github/actions/install-cert/action.yml | 31 ++++ .github/actions/xcbuild/action.yml | 133 +++++++++++++++ .github/dmg-bg.png | Bin 0 -> 2338 bytes .github/dmg-settings.py | 46 ++++++ .github/retry-if-retcode.py | 75 +++++++++ .github/upload_file.py | 110 +++++++++++++ .github/workflows/cd.yml | 209 ++++++++++++++++++++++++ .github/workflows/ci.yml | 105 +++++------- 8 files changed, 648 insertions(+), 61 deletions(-) create mode 100644 .github/actions/install-cert/action.yml create mode 100644 .github/actions/xcbuild/action.yml create mode 100644 .github/dmg-bg.png create mode 100644 .github/dmg-settings.py create mode 100644 .github/retry-if-retcode.py create mode 100644 .github/upload_file.py create mode 100644 .github/workflows/cd.yml diff --git a/.github/actions/install-cert/action.yml b/.github/actions/install-cert/action.yml new file mode 100644 index 00000000..fea88550 --- /dev/null +++ b/.github/actions/install-cert/action.yml @@ -0,0 +1,31 @@ +name: Install Certificate in Keychain +description: Install a single cert in existing keychain + +inputs: + KEYCHAIN: + required: true + KEYCHAIN_PASSWORD: + required: true + SIGNING_CERTIFICATE: + required: true + SIGNING_CERTIFICATE_P12_PASSWORD: + required: true + +runs: + using: composite + steps: + - name: Install certificate + shell: bash + env: + KEYCHAIN: ${{ inputs.KEYCHAIN }} + KEYCHAIN_PASSWORD: ${{ inputs.KEYCHAIN_PASSWORD }} + CERTIFICATE_PATH: /tmp/cert.p12 + SIGNING_CERTIFICATE: ${{ inputs.SIGNING_CERTIFICATE }} + SIGNING_CERTIFICATE_P12_PASSWORD: ${{ inputs.SIGNING_CERTIFICATE_P12_PASSWORD }} + run: | + security unlock-keychain -p $KEYCHAIN_PASSWORD $KEYCHAIN + echo "${SIGNING_CERTIFICATE}" | base64 --decode -o $CERTIFICATE_PATH + security import $CERTIFICATE_PATH -k $KEYCHAIN -P "${SIGNING_CERTIFICATE_P12_PASSWORD}" -A -T /usr/bin/codesign -T /usr/bin/security -T /usr/bin/productbuild + rm $CERTIFICATE_PATH + security find-identity -v $KEYCHAIN + security set-key-partition-list -S apple-tool:,apple: -s -k $KEYCHAIN_PASSWORD $KEYCHAIN diff --git a/.github/actions/xcbuild/action.yml b/.github/actions/xcbuild/action.yml new file mode 100644 index 00000000..cafc8d63 --- /dev/null +++ b/.github/actions/xcbuild/action.yml @@ -0,0 +1,133 @@ +name: Build with XCode +description: Run xcodebuild for Kiwix + +inputs: + action: + required: true + version: + required: true + xc-destination: + required: true + upload-to: + required: true + libkiwix-version: + required: true + APPLE_DEVELOPMENT_SIGNING_CERTIFICATE: + required: true + APPLE_DEVELOPMENT_SIGNING_P12_PASSWORD: + required: true + DEPLOYMENT_SIGNING_CERTIFICATE: + required: false + DEPLOYMENT_SIGNING_CERTIFICATE_P12_PASSWORD: + required: false + KEYCHAIN: + required: false + default: /Users/runner/build.keychain-db + KEYCHAIN_PASSWORD: + required: false + default: mysecretpassword + KEYCHAIN_PROFILE: + required: false + default: build-profile + XC_WORKSPACE: + required: false + default: Kiwix.xcodeproj/project.xcworkspace/ + XC_SCHEME: + required: false + default: Kiwix + XC_CONFIG: + required: false + default: Release + EXTRA_XCODEBUILD: + required: false + default: "" + +runs: + using: composite + steps: + + # not necessary on github runner but serves as documentation for local setup + - name: Update Apple Intermediate Certificate + shell: bash + run: | + curl -L -o ~/Downloads/AppleWWDRCAG3.cer https://www.apple.com/certificateauthority/AppleWWDRCAG3.cer + sudo security import ~/Downloads/AppleWWDRCAG3.cer \ + -k /Library/Keychains/System.keychain \ + -T /usr/bin/codesign \ + -T /usr/bin/security \ + -T /usr/bin/productbuild || true + + - name: Set Xcode version (15.0.1) + shell: bash + # https://github.com/actions/runner-images/blob/main/images/macos/macos-13-Readme.md#xcode + run: sudo xcode-select -s /Applications/Xcode_15.0.1.app + + - name: Create Keychain + shell: bash + env: + KEYCHAIN: ${{ inputs.KEYCHAIN }} + KEYCHAIN_PASSWORD: ${{ inputs.KEYCHAIN_PASSWORD }} + KEYCHAIN_PROFILE: ${{ inputs.KEYCHAIN_PROFILE }} + CERTIFICATE_PATH: /tmp/cert.p12 + APPLE_DEVELOPER_CERTIFICATE_PATH: /tmp/dev-cert.p12 + SIGNING_CERTIFICATE: ${{ inputs.SIGNING_CERTIFICATE }} + SIGNING_CERTIFICATE_P12_PASSWORD: ${{ inputs.SIGNING_CERTIFICATE_P12_PASSWORD }} + APPLE_DEVELOPER_ID_SIGNING_CERTIFICATE: ${{ inputs.APPLE_DEVELOPER_ID_SIGNING_CERTIFICATE }} + APPLE_DEVELOPER_ID_SIGNING_P12_PASSWORD: ${{ inputs.APPLE_DEVELOPER_ID_SIGNING_P12_PASSWORD }} + run: | + security create-keychain -p $KEYCHAIN_PASSWORD $KEYCHAIN + security default-keychain -s $KEYCHAIN + security set-keychain-settings $KEYCHAIN + security unlock-keychain -p $KEYCHAIN_PASSWORD $KEYCHAIN + + - name: Add Apple Development certificate to Keychain + uses: ./.github/actions/install-cert + with: + SIGNING_CERTIFICATE: ${{ inputs.APPLE_DEVELOPMENT_SIGNING_CERTIFICATE }} + SIGNING_CERTIFICATE_P12_PASSWORD: ${{ inputs.APPLE_DEVELOPMENT_SIGNING_P12_PASSWORD }} + KEYCHAIN: ${{ inputs.KEYCHAIN }} + KEYCHAIN_PASSWORD: ${{ inputs.KEYCHAIN_PASSWORD }} + + - name: Add Distribution certificate to Keychain + if: ${{ inputs.DEPLOYMENT_SIGNING_CERTIFICATE }} + uses: ./.github/actions/install-cert + with: + SIGNING_CERTIFICATE: ${{ inputs.DEPLOYMENT_SIGNING_CERTIFICATE }} + SIGNING_CERTIFICATE_P12_PASSWORD: ${{ inputs.DEPLOYMENT_SIGNING_CERTIFICATE_P12_PASSWORD }} + KEYCHAIN: ${{ inputs.KEYCHAIN }} + KEYCHAIN_PASSWORD: ${{ inputs.KEYCHAIN_PASSWORD }} + + - name: Download CoreKiwix.xcframework + env: + XCF_URL: https://download.kiwix.org/release/libkiwix/libkiwix_xcframework-${{ inputs.libkiwix-version }}.tar.gz + shell: bash + run: curl -L -o - $XCF_URL | tar -x --strip-components 2 + + - name: Prepare Xcode + shell: bash + run: xcrun xcodebuild -checkFirstLaunchStatus || xcrun xcodebuild -runFirstLaunch + + - name: Dump build settings + env: + XC_WORKSPACE: ${{ inputs.XC_WORKSPACE }} + XC_SCHEME: ${{ inputs.XC_SCHEME }} + shell: bash + run: xcrun xcodebuild -workspace $XC_WORKSPACE -scheme $XC_SCHEME -showBuildSettings + # build is launched up to twice as it's common the build fails, looking for CoreKiwix module + + - name: Install retry command + shell: bash + run: brew install kadwanev/brew/retry + + - name: Build with Xcode + env: + FRAMEWORK_SEARCH_PATHS: ${{ env.PWD }} + ACTION: ${{ inputs.action }} + VERSION: ${{ inputs.version }} + XC_WORKSPACE: ${{ inputs.XC_WORKSPACE }} + XC_SCHEME: ${{ inputs.XC_SCHEME }} + XC_CONFIG: ${{ inputs.XC_CONFIG }} + XC_DESTINATION: ${{ inputs.xc-destination }} + EXTRA_XCODEBUILD: ${{ inputs.EXTRA_XCODEBUILD }} + shell: bash + run: retry -t 2 -- xcrun xcodebuild ${EXTRA_XCODEBUILD} -workspace $XC_WORKSPACE -scheme $XC_SCHEME -destination "$XC_DESTINATION" -configuration $XC_CONFIG -onlyUsePackageVersionsFromResolvedFile -allowProvisioningUpdates -verbose -archivePath $PWD/Kiwix-$VERSION.xcarchive ${ACTION} diff --git a/.github/dmg-bg.png b/.github/dmg-bg.png new file mode 100644 index 0000000000000000000000000000000000000000..8523ba63fa054216ed74d4685f5698324d57e93c GIT binary patch literal 2338 zcmeAS@N?(olHy`uVBq!ia0y~yV4A|fz&L?}2`DoEO8NsJC0XJcQ4*Y=R#Ki=l*-_k zlAn~S;F+74o*I;zm{M7IGS!BGf#Zm$i(^Q|tv7f5GlI(*4t!L3A1Rt5+?8Ff*;Ie% z+SJVImyXYFB+TWp-O+jbXyb<0A8h=sDytp?hD8C+bZi%{-`QnUN;N#o(fB&u1zj&uXWrPYetBEfyZN4lT5|g(?%SIAZ|9JQG$GL>cq!Sv9Y4a=QiY|G8Db_=wk6 z;5~1??SI?3KTm1Dxf{KAx?PRV%y}kx4Hbt!9sT|E9qT^h-OFt6?6Q7yPtZT6d!5`? z8NLp4o&F^eGu@txe{9@#_5U4j`QAFKJWH91Q)>7n=HGpG;X!uJ)m_{6-cP%}<@y(0#JA_gw4O{F<>X`uXES zobIO`_B6JgF6C`mUq9jXt*G|*votNE*dFmNDgC+jEc^O>@@4N=oZtJU^Yz7Ee!Gyf=7pey#cyxzW2SC8lHzlsYJnktwzSDs$B4H)J(7c#R6ACdMrKiM}|db{pV z=`{V?RDG)s^9jF7qYp3Jw#4G=b#vxp0#U!;S2#21s?VI)v#vHW^TV6(@l({->FfQ; zzO^~~Gf+M%gJVJI(d6aY>%Dpo>;7)?&DSXij{esA^;~`PO!EzEgg>?~zIfu}3WE%d z181Z&7!N+Pb^JHiRhHdf?yhkj(B-$DovUY`y-mODd{oe!qVqDq;QY4oTg}A}n#mkM ze;Qql+p72CZwhDd+n=lYt;$bG0~KtEx$f}cR9}?bnNLgZ$n|qQ;x(yR6}x=C_~*ka zKqIq#wi@@ppX?sqe`_HyY)yWLSQ`g-aW#MZura0T(g9#F_}X6e>k0j(Zx@fc!MCk4H?oZv#zRyzJIaU+cs7_Lk1fQa`$~;?JViT!H)rfj9ZK z%k%8|CHCWR{P~mLh+8Fx4kZi-4t8va%-+5*pTG472iKcowg0` zIfKz?J!@W zdwXBqM){R`IsJ2V>~|!6VffgV8t`xLgx6>EU#u-YZ?d=c+95NAdmGk#{P(o|%(c7! z|IXW)K5N?X^FY@ek)EY+kAK~fa((;PiCI@$-_E`dj4NIdg?krV(%Sh{>g&YL&9Aqw zTeh+_Hz9M=Qsr7V#!EYHgsnZj@5=9#LvJ;;8)llD>`$pOx|p*-ZnSjAtkH&X=}vYv jOJT*-p!Rh;12e;c@SCz?`g`SpjUEP1S3j3^P6 int: + attempts = 0 + while True: + ps = subprocess.run(command, check=False) + attempts += 1 + + # either suceeded or returned an unexpected exit-code, returning. + if ps.returncode == 0 or ps.returncode != retcode: + return ps.returncode + + if attempts >= max_attempts: + print(f"Reached {max_attempts=}") + return ps.returncode + + print( + f"Received retcode={ps.returncode} on attempt #{attempts}. " + f"Retrying in {sleep_seconds}s." + ) + if sleep_seconds: + time.sleep(sleep_seconds) + + +def main(): + parser = argparse.ArgumentParser( + prog="retry-if-retcode", epilog=r"/!\ Append your command after those args!" + ) + + parser.add_argument( + "--retcode", + required=True, + help="Return code to retry when received", + type=int, + ) + + parser.add_argument( + "--attempts", + required=False, + help="Max number of attempts", + type=int, + default=10, + ) + + parser.add_argument( + "--sleep", + required=False, + help="Nb. of seconds to sleep in-between retries", + type=int, + default=1, + ) + + args, command = parser.parse_known_args() + if not command: + print("You must supply a command to run") + return 1 + + return run_command( + max_attempts=args.attempts, + retcode=args.retcode, + sleep_seconds=args.sleep, + command=command, + ) + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/.github/upload_file.py b/.github/upload_file.py new file mode 100644 index 00000000..f50f424d --- /dev/null +++ b/.github/upload_file.py @@ -0,0 +1,110 @@ +import argparse +import os +import pathlib +import subprocess +import sys +import urllib.parse + + +def main() -> int: + parser = argparse.ArgumentParser( + prog="scp-upload", + description="Upload files to Kiwix server", + ) + + parser.add_argument( + "--src", required=True, help="filepath to be uploaded", dest="src_path" + ) + + parser.add_argument( + "--dest", + required=True, + help="destination as user@host[:port]/folder/", + dest="dest", + ) + + parser.add_argument( + "--ssh-key", + required=False, + help="filepath to the private key to use for upload", + default=os.getenv("SSH_KEY", ""), + dest="ssh_key", + ) + + args = parser.parse_args() + + ssh_path = ( + pathlib.Path(args.ssh_key or os.getenv("SSH_KEY", "")).expanduser().resolve() + ) + src_path = pathlib.Path(args.src_path).expanduser().resolve() + dest = urllib.parse.urlparse(f"ssh://{args.dest}") + dest_path = pathlib.Path(dest.path) + + if not src_path.exists() or not ssh_path.is_file(): + print(f"Source file “{src_path}” missing") + return 1 + + if not ssh_path.exists() or not ssh_path.is_file(): + print(f"SSH Key “{ssh_path}” missing") + return 1 + + if not dest_path or dest_path == pathlib.Path("") or dest_path == pathlib.Path("/"): + print(f"Must upload in a subfoler, not “{dest_path}”") + return 1 + + return upload( + src_path=src_path, host=dest.netloc, dest_path=dest_path, ssh_path=ssh_path + ) + + +def upload( + src_path: pathlib.Path, host: str, dest_path: pathlib.Path, ssh_path: pathlib.Path +) -> int: + if ":" in host: + host, port = host.split(":", 1) + else: + port = "22" + + # sending SFTP mkdir command to the sftp interactive mode and not batch (-b) mode + # as the latter would exit on any mkdir error while it is most likely + # the first parts of the destination is already present and thus can't be created + sftp_commands = "\n".join( + [ + f"mkdir {part}" + for part in list(reversed(dest_path.parents)) + [str(dest_path)] + ] + ) + command = [ + "sftp", + "-i", + str(ssh_path), + "-P", + port, + "-o", + "StrictHostKeyChecking=no", + host, + ] + print(f"Creating dest path: {dest_path}") + subprocess.run(command, input=sftp_commands, text=True, check=True) + + command = [ + "scp", + "-c", + "aes128-ctr", + "-rp", + "-P", + port, + "-i", + str(ssh_path), + "-o", + "StrictHostKeyChecking=no", + str(src_path), + f"{host}:{dest_path}/", + ] + print(f"Sending archive with command {' '.join(command)}") + subprocess.run(command, check=True) + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml new file mode 100644 index 00000000..2b219efc --- /dev/null +++ b/.github/workflows/cd.yml @@ -0,0 +1,209 @@ +name: CD + +on: + schedule: + - cron: '32 1 * * *' + workflow_dispatch: + release: + types: [published] + +env: + LIBKIWIX_VERSION: "13.0.0" + KEYCHAIN: /Users/runner/build.keychain-db + KEYCHAIN_PASSWORD: mysecretpassword + KEYCHAIN_PROFILE: build-profile + SSH_KEY: /tmp/id_rsa + APPLE_STORE_AUTH_KEY_PATH: /tmp/authkey.p8 + +jobs: + build_and_deploy: + strategy: + fail-fast: false + matrix: + destination: + - platform: macOS + uploadto: dmg + - platform: macOS + uploadto: app-store + - platform: iOS + uploadto: ipa + xcode_extra: -sdk iphoneos + - platform: iOS + uploadto: app-store + xcode_extra: -sdk iphoneos + runs-on: macos-13 + steps: + - name: Checkout code + uses: actions/checkout@v3 + - name: Decide whether building nightly or release + env: + PLATFORM: ${{ matrix.destination.platform }} + UPLOAD_TO: ${{ matrix.destination.uploadto }} + EXTRA_XCODEBUILD: ${{ matrix.destination.xcode_extra }} + APPLE_STORE_AUTH_KEY_PATH: ${{ env.APPLE_STORE_AUTH_KEY_PATH }} + APPLE_STORE_AUTH_KEY_ID: ${{ secrets.APPLE_STORE_AUTH_KEY_ID }} + APPLE_STORE_AUTH_KEY_ISSUER_ID: ${{ secrets.APPLE_STORE_AUTH_KEY_ISSUER_ID }} + shell: python + run: | + import datetime + import os + if os.getenv("GITHUB_EVENT_NAME", "") == "release": + is_release = True + version = os.getenv("GITHUB_REF_NAME") + upload_folder = f"release/{version}" + else: + is_release = False + version = str(datetime.date.today()) + upload_folder = f"nightly/{version}" + + export_method = "developer-id" if os.getenv("UPLOAD_TO") == "dmg" else "app-store" + + extra_xcode = os.getenv("EXTRA_XCODEBUILD", "") + if os.getenv("PLATFORM") == "iOS": + extra_xcode += f" -authenticationKeyPath {os.getenv('APPLE_STORE_AUTH_KEY_PATH')}" + extra_xcode += f" -authenticationKeyID {os.getenv('APPLE_STORE_AUTH_KEY_ID')}" + extra_xcode += f" -authenticationKeyIssuerID {os.getenv('APPLE_STORE_AUTH_KEY_ISSUER_ID')}" + + with open(os.getenv("GITHUB_ENV"), "a") as fh: + fh.write(f"VERSION={version}\n") + fh.write(f"ISRELEASE={'yes' if is_release else ''}\n") + fh.write(f"EXPORT_METHOD={export_method}\n") + fh.write(f"UPLOAD_FOLDER={upload_folder}\n") + fh.write(f"EXTRA_XCODEBUILD={extra_xcode}\n") + + - name: Prepare use of Developper ID Certificate + if: ${{ matrix.destination.uploadto == 'dmg' }} + shell: bash + env: + APPLE_DEVELOPER_ID_SIGNING_CERTIFICATE: ${{ secrets.APPLE_DEVELOPER_ID_SIGNING_CERTIFICATE }} + APPLE_DEVELOPER_ID_SIGNING_P12_PASSWORD: ${{ secrets.APPLE_DEVELOPER_ID_SIGNING_P12_PASSWORD }} + APPLE_DEVELOPER_ID_SIGNING_IDENTITY: ${{ secrets.APPLE_DEVELOPER_ID_SIGNING_IDENTITY }} + run: | + echo "SIGNING_CERTIFICATE=${APPLE_DEVELOPER_ID_SIGNING_CERTIFICATE}" >> "$GITHUB_ENV" + echo "SIGNING_CERTIFICATE_P12_PASSWORD=${APPLE_DEVELOPER_ID_SIGNING_P12_PASSWORD}" >> "$GITHUB_ENV" + echo "SIGNING_IDENTITY=${APPLE_DEVELOPER_ID_SIGNING_IDENTITY}" >> "$GITHUB_ENV" + + - name: Prepare use of Apple Development Certificate + if: ${{ matrix.destination.uploadto == 'ipa' }} + shell: bash + env: + APPLE_DEVELOPMENT_SIGNING_CERTIFICATE: ${{ secrets.APPLE_DEVELOPMENT_SIGNING_CERTIFICATE }} + APPLE_DEVELOPMENT_SIGNING_P12_PASSWORD: ${{ secrets.APPLE_DEVELOPMENT_SIGNING_P12_PASSWORD }} + APPLE_DEVELOPMENT_SIGNING_IDENTITY: ${{ secrets.APPLE_DEVELOPMENT_SIGNING_IDENTITY }} + run: | + echo "SIGNING_CERTIFICATE=${APPLE_DEVELOPMENT_SIGNING_CERTIFICATE}" >> "$GITHUB_ENV" + echo "SIGNING_CERTIFICATE_P12_PASSWORD=${APPLE_DEVELOPMENT_SIGNING_P12_PASSWORD}" >> "$GITHUB_ENV" + echo "SIGNING_IDENTITY=${APPLE_DEVELOPMENT_SIGNING_IDENTITY}" >> "$GITHUB_ENV" + + - name: Prepare use of Apple Distribution Certificate + if: ${{ matrix.destination.uploadto == 'app-store' }} + shell: bash + env: + APPLE_DISTRIBUTION_SIGNING_CERTIFICATE: ${{ secrets.APPLE_DISTRIBUTION_SIGNING_CERTIFICATE }} + APPLE_DISTRIBUTION_SIGNING_P12_PASSWORD: ${{ secrets.APPLE_DISTRIBUTION_SIGNING_P12_PASSWORD }} + APPLE_DEVELOPMENT_SIGNING_IDENTITY: ${{ secrets.APPLE_DEVELOPMENT_SIGNING_IDENTITY }} + run: | + echo "SIGNING_CERTIFICATE=${APPLE_DISTRIBUTION_SIGNING_CERTIFICATE}" >> "$GITHUB_ENV" + echo "SIGNING_CERTIFICATE_P12_PASSWORD=${APPLE_DISTRIBUTION_SIGNING_P12_PASSWORD}" >> "$GITHUB_ENV" + echo "SIGNING_IDENTITY=${APPLE_DEVELOPMENT_SIGNING_IDENTITY}" >> "$GITHUB_ENV" + + - name: Add Apple Store Key + env: + APPLE_STORE_AUTH_KEY_PATH: ${{ env.APPLE_STORE_AUTH_KEY_PATH }} + APPLE_STORE_AUTH_KEY: ${{ secrets.APPLE_STORE_AUTH_KEY }} + shell: bash + run: echo "${APPLE_STORE_AUTH_KEY}" | base64 --decode -o $APPLE_STORE_AUTH_KEY_PATH + + - name: Build xcarchive + uses: ./.github/actions/xcbuild + with: + action: archive + xc-destination: generic/platform=${{ matrix.destination.platform }} + upload-to: ${{ matrix.destination.uploadto }} + libkiwix-version: ${{ env.LIBKIWIX_VERSION }} + version: ${{ env.VERSION }} + APPLE_DEVELOPMENT_SIGNING_CERTIFICATE: ${{ secrets.APPLE_DEVELOPMENT_SIGNING_CERTIFICATE }} + APPLE_DEVELOPMENT_SIGNING_P12_PASSWORD: ${{ secrets.APPLE_DEVELOPMENT_SIGNING_P12_PASSWORD }} + DEPLOYMENT_SIGNING_CERTIFICATE: ${{ env.SIGNING_CERTIFICATE }} + DEPLOYMENT_SIGNING_CERTIFICATE_P12_PASSWORD: ${{ env.SIGNING_CERTIFICATE_P12_PASSWORD }} + KEYCHAIN: ${{ env.KEYCHAIN }} + KEYCHAIN_PASSWORD: ${{ env.KEYCHAIN_PASSWORD }} + KEYCHAIN_PROFILE: ${{ env.KEYCHAIN_PROFILE }} + EXTRA_XCODEBUILD: ${{ env.EXTRA_XCODEBUILD }} + + - name: Add altool credentials to Keychain + shell: bash + env: + APPLE_SIGNING_ALTOOL_USERNAME: ${{ secrets.APPLE_SIGNING_ALTOOL_USERNAME }} + APPLE_SIGNING_ALTOOL_PASSWORD: ${{ secrets.APPLE_SIGNING_ALTOOL_PASSWORD }} + APPLE_SIGNING_TEAM: ${{ secrets.APPLE_SIGNING_TEAM }} + run: | + security find-identity -v $KEYCHAIN + security unlock-keychain -p $KEYCHAIN_PASSWORD $KEYCHAIN + xcrun notarytool store-credentials \ + --apple-id "${APPLE_SIGNING_ALTOOL_USERNAME}" \ + --password "${APPLE_SIGNING_ALTOOL_PASSWORD}" \ + --team-id "${APPLE_SIGNING_TEAM}" \ + --validate \ + --keychain $KEYCHAIN \ + $KEYCHAIN_PROFILE + + - name: Prepare export for ${{ env.EXPORT_METHOD }} + if: ${{ matrix.destination.uploadto != 'ipa' }} + run: | + plutil -create xml1 ./export.plist + plutil -insert destination -string upload ./export.plist + plutil -insert method -string $EXPORT_METHOD ./export.plist + + - name: Prepare export for IPA + if: ${{ matrix.destination.uploadto == 'ipa' }} + run: | + plutil -create xml1 ./export.plist + plutil -insert method -string ad-hoc ./export.plist + plutil -insert provisioningProfiles -dictionary ./export.plist + plutil -replace provisioningProfiles -json '{ "self.Kiwix" : "iOS Team Provisioning Profile" }' ./export.plist + + - name: Upload Archive to Apple (App Store or Notarization) + env: + APPLE_STORE_AUTH_KEY_PATH: ${{ env.APPLE_STORE_AUTH_KEY_PATH }} + APPLE_STORE_AUTH_KEY_ID: ${{ secrets.APPLE_STORE_AUTH_KEY_ID }} + APPLE_STORE_AUTH_KEY_ISSUER_ID: ${{ secrets.APPLE_STORE_AUTH_KEY_ISSUER_ID }} + run: python .github/retry-if-retcode.py --sleep 60 --attempts 5 --retcode 70 xcrun xcodebuild -exportArchive -archivePath $PWD/Kiwix-$VERSION.xcarchive -exportPath $PWD/export/ -exportOptionsPlist export.plist -authenticationKeyPath $APPLE_STORE_AUTH_KEY_PATH -allowProvisioningUpdates -authenticationKeyID $APPLE_STORE_AUTH_KEY_ID -authenticationKeyIssuerID $APPLE_STORE_AUTH_KEY_ISSUER_ID + + - name: Export notarized App from archive + if: ${{ matrix.destination.uploadto == 'dmg' }} + env: + APPLE_STORE_AUTH_KEY_PATH: ${{ env.APPLE_STORE_AUTH_KEY_PATH }} + APPLE_STORE_AUTH_KEY_ID: ${{ secrets.APPLE_STORE_AUTH_KEY_ID }} + APPLE_STORE_AUTH_KEY_ISSUER_ID: ${{ secrets.APPLE_STORE_AUTH_KEY_ISSUER_ID }} + run: python .github/retry-if-retcode.py --sleep 60 --attempts 20 --retcode 65 xcrun xcodebuild -exportNotarizedApp -archivePath $PWD/Kiwix-$VERSION.xcarchive -exportPath $PWD/export/ -authenticationKeyPath $APPLE_STORE_AUTH_KEY_PATH -allowProvisioningUpdates -authenticationKeyID $APPLE_STORE_AUTH_KEY_ID -authenticationKeyIssuerID $APPLE_STORE_AUTH_KEY_ISSUER_ID + + - name: Create DMG + if: ${{ matrix.destination.uploadto == 'dmg' }} + run: | + pip install dmgbuild + dmgbuild -s .github/dmg-settings.py -Dapp=$PWD/export/Kiwix.app -Dbg=.github/dmg-bg.png "Kiwix-$VERSION" $PWD/Kiwix-$VERSION.dmg + + - name: Notarize DMG + if: ${{ matrix.destination.uploadto == 'dmg' }} + run: | + xcrun notarytool submit --keychain $KEYCHAIN --keychain-profile $KEYCHAIN_PROFILE --wait $PWD/Kiwix-$VERSION.dmg + xcrun stapler staple $PWD/Kiwix-$VERSION.dmg + + - name: Add SSH_KEY to filesystem + shell: bash + env: + PRIVATE_KEY: ${{ secrets.SSH_KEY }} + run: | + echo "${PRIVATE_KEY}" > $SSH_KEY + chmod 600 $SSH_KEY + + - name: Upload DMG + if: ${{ matrix.destination.uploadto == 'dmg' }} + run: python .github/upload_file.py --src ${PWD}/Kiwix-${VERSION}.dmg --dest ci@master.download.kiwix.org:30022/data/download/${UPLOAD_FOLDER} --ssh-key ${SSH_KEY} + + - name: Upload IPA + if: ${{ matrix.destination.uploadto == 'ipa' }} + run: | + mv ${PWD}/export/Kiwix.ipa ${PWD}/export/Kiwix-${VERSION}.ipa + python .github/upload_file.py --src ${PWD}/export/Kiwix-${VERSION}.ipa --dest ci@master.download.kiwix.org:30022/data/download/${UPLOAD_FOLDER} --ssh-key ${SSH_KEY} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ae6b1fa3..c61a3bb4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,74 +8,57 @@ on: env: LIBKIWIX_VERSION: "13.0.0" + APPLE_STORE_AUTH_KEY_PATH: /tmp/authkey.p8 jobs: build: + runs-on: macos-13 strategy: fail-fast: false matrix: destination: - platform: macOS - name: Any Mac - platform: iOS - name: Any iOS Device - runs-on: macos-13 - env: - XC_WORKSPACE: Kiwix.xcodeproj/project.xcworkspace/ - XC_SCHEME: Kiwix - XC_CONFIG: Release - XC_DESTINATION: platform=${{ matrix.destination.platform }},name=${{ matrix.destination.name }} - CERTIFICATE: /tmp/apple-development.p12 - SIGNING_IDENTITY: ${{ secrets.APPLE_DEVELOPMENT_SIGNING_IDENTITY }} - KEYCHAIN: /Users/runner/build.keychain-db - KEYCHAIN_PASSWORD: mysecretpassword - KEYCHAIN_PROFILE: build-profile + xcode_extra: -sdk iphoneos steps: - - name: install Apple certificate - shell: bash - run: | - echo "${{ secrets.APPLE_DEVELOPMENT_SIGNING_CERTIFICATE }}" | base64 --decode -o $CERTIFICATE - security create-keychain -p $KEYCHAIN_PASSWORD $KEYCHAIN - security default-keychain -s $KEYCHAIN - security set-keychain-settings $KEYCHAIN - security unlock-keychain -p $KEYCHAIN_PASSWORD $KEYCHAIN - security import $CERTIFICATE -k $KEYCHAIN -P "${{ secrets.APPLE_DEVELOPMENT_SIGNING_P12_PASSWORD }}" -A -T /usr/bin/codesign -T /usr/bin/security -T /usr/bin/productbuild - rm $CERTIFICATE - security set-key-partition-list -S apple-tool:,apple: -s -k $KEYCHAIN_PASSWORD $KEYCHAIN - security find-identity -v $KEYCHAIN - xcrun notarytool store-credentials \ - --apple-id "${{ secrets.APPLE_SIGNING_ALTOOL_USERNAME }}" \ - --password "${{ secrets.APPLE_SIGNING_ALTOOL_PASSWORD }}" \ - --team-id "${{ secrets.APPLE_SIGNING_TEAM }}" \ - --validate \ - --keychain $KEYCHAIN \ - $KEYCHAIN_PROFILE - # not necessary on github runner but serves as documentation for local setup - - name: Update Apple Intermediate Certificate - run: | - curl -L -o ~/Downloads/AppleWWDRCAG3.cer https://www.apple.com/certificateauthority/AppleWWDRCAG3.cer - sudo security import ~/Downloads/AppleWWDRCAG3.cer \ - -k /Library/Keychains/System.keychain \ - -T /usr/bin/codesign \ - -T /usr/bin/security \ - -T /usr/bin/productbuild || true - - name: Set Xcode version (15.0.1) - # https://github.com/actions/runner-images/blob/main/images/macos/macos-13-Readme.md#xcode - run: sudo xcode-select -s /Applications/Xcode_15.0.1.app - - name: Checkout code - uses: actions/checkout@v3 - - name: Download CoreKiwix.xcframework - env: - XCF_URL: https://download.kiwix.org/release/libkiwix/libkiwix_xcframework-${{ env.LIBKIWIX_VERSION }}.tar.gz - run: curl -L -o - $XCF_URL | tar -x --strip-components 2 - - name: Prepare Xcode - run: xcrun xcodebuild -checkFirstLaunchStatus || xcrun xcodebuild -runFirstLaunch - - name: Dump build settings - run: xcrun xcodebuild -workspace $XC_WORKSPACE -scheme $XC_SCHEME -showBuildSettings - # build is launched up to twice as it's common the build fails, looking for CoreKiwix module - - name: Install retry command - run: brew install kadwanev/brew/retry - - name: Build for ${{ matrix.destination.platform }}/${{ matrix.destination.name }} - env: - FRAMEWORK_SEARCH_PATHS: /Users/runner/work/apple/apple/ - run: retry -t 2 -- xcrun xcodebuild -workspace $XC_WORKSPACE -scheme $XC_SCHEME -destination "$XC_DESTINATION" -configuration $XC_CONFIG -onlyUsePackageVersionsFromResolvedFile -derivedDataPath $PWD/build -allowProvisioningUpdates -verbose build + - name: Checkout code + uses: actions/checkout@v3 + + - name: Add Apple Store Key + if: ${{ matrix.destination.platform == 'iOS' }} + env: + APPLE_STORE_AUTH_KEY_PATH: ${{ env.APPLE_STORE_AUTH_KEY_PATH }} + APPLE_STORE_AUTH_KEY: ${{ secrets.APPLE_STORE_AUTH_KEY }} + shell: bash + run: echo "${APPLE_STORE_AUTH_KEY}" | base64 --decode -o $APPLE_STORE_AUTH_KEY_PATH + + - name: Extend EXTRA_XCODEBUILD + if: ${{ matrix.destination.platform == 'iOS' }} + env: + EXTRA_XCODEBUILD: ${{ matrix.destination.xcode_extra }} + APPLE_STORE_AUTH_KEY_PATH: ${{ env.APPLE_STORE_AUTH_KEY_PATH }} + APPLE_STORE_AUTH_KEY_ID: ${{ secrets.APPLE_STORE_AUTH_KEY_ID }} + APPLE_STORE_AUTH_KEY_ISSUER_ID: ${{ secrets.APPLE_STORE_AUTH_KEY_ISSUER_ID }} + shell: python + run: | + import os + extra_xcode = os.getenv("EXTRA_XCODEBUILD", "") + extra_xcode += f" -authenticationKeyPath {os.getenv('APPLE_STORE_AUTH_KEY_PATH')}" + extra_xcode += f" -authenticationKeyID {os.getenv('APPLE_STORE_AUTH_KEY_ID')}" + extra_xcode += f" -authenticationKeyIssuerID {os.getenv('APPLE_STORE_AUTH_KEY_ISSUER_ID')}" + + with open(os.getenv("GITHUB_ENV"), "a") as fh: + fh.write(f"EXTRA_XCODEBUILD={extra_xcode}\n") + + - name: Build + uses: ./.github/actions/xcbuild + with: + action: build + xc-destination: generic/platform=${{ matrix.destination.platform }} + upload-to: dev + libkiwix-version: ${{ env.LIBKIWIX_VERSION }} + version: CI + APPLE_DEVELOPMENT_SIGNING_CERTIFICATE: ${{ secrets.APPLE_DEVELOPMENT_SIGNING_CERTIFICATE }} + APPLE_DEVELOPMENT_SIGNING_P12_PASSWORD: ${{ secrets.APPLE_DEVELOPMENT_SIGNING_P12_PASSWORD }} + EXTRA_XCODEBUILD: ${{ env.EXTRA_XCODEBUILD }} + From e801098cdf2b98bb6e49181dff58353bf810bedb Mon Sep 17 00:00:00 2001 From: tvision251 Date: Wed, 22 Nov 2023 14:56:08 -0500 Subject: [PATCH 19/19] fix: localization spelling issue for iOS platform --- Views/Settings/Settings.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Views/Settings/Settings.swift b/Views/Settings/Settings.swift index cf6bbd85..2478da1d 100644 --- a/Views/Settings/Settings.swift +++ b/Views/Settings/Settings.swift @@ -200,7 +200,7 @@ struct Settings: View { } var miscellaneous: some View { - Section("Misc".lowercased) { + 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")!