Merge pull request #537 from tvision251/feature/24-localization

Added the localization
This commit is contained in:
Kelson 2023-11-22 19:00:19 +01:00 committed by GitHub
commit 5285e176d0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
47 changed files with 519 additions and 262 deletions

View File

@ -33,10 +33,10 @@ struct Kiwix: App {
}.commands {
SidebarCommands()
CommandGroup(replacing: .importExport) {
OpenFileButton(context: .command) { Text("Open...") }
OpenFileButton(context: .command) { Text("Open...".localized) }
}
CommandGroup(replacing: .newItem) {
Button("New Tab") {
Button("New Tab".localized) {
guard let currentWindow = NSApp.keyWindow,
let controller = currentWindow.windowController else { return }
controller.newWindowForTab(nil)
@ -93,11 +93,11 @@ struct RootView: View {
NavigationView {
List(selection: $navigation.currentItem) {
ForEach(primaryItems, id: \.self) { navigationItem in
Label(navigationItem.name, systemImage: navigationItem.icon)
Label(navigationItem.name.localized, systemImage: navigationItem.icon)
}
Section("Library") {
Section("Library".localized) {
ForEach(libraryItems, id: \.self) { navigationItem in
Label(navigationItem.name, systemImage: navigationItem.icon)
Label(navigationItem.name.localized, systemImage: navigationItem.icon)
}
}
}
@ -108,7 +108,7 @@ struct RootView: View {
responder.tryToPerform(#selector(NSSplitViewController.toggleSidebar(_:)), with: nil)
} label: {
Image(systemName: "sidebar.leading")
}.help("Show sidebar")
}.help("Show sidebar".localized)
}
switch navigation.currentItem {
case .reading:

View File

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

View File

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

View File

@ -7,6 +7,10 @@
objects = {
/* Begin PBXBuildFile section */
8E4396462B02E455007F0BC4 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 8E4396492B02E455007F0BC4 /* Localizable.strings */; };
8E4396472B02E455007F0BC4 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 8E4396492B02E455007F0BC4 /* Localizable.strings */; };
8E43964C2B02E4C6007F0BC4 /* String+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E43964B2B02E4C6007F0BC4 /* String+Extension.swift */; };
8E43964D2B02E4C6007F0BC4 /* String+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E43964B2B02E4C6007F0BC4 /* String+Extension.swift */; };
97008ABD2974A5BF0076E60C /* OPDSParserTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97008ABC2974A5BF0076E60C /* OPDSParserTests.swift */; };
9709C0982A8E4C5700E4564C /* Commands.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9709C0972A8E4C5700E4564C /* Commands.swift */; };
97121EBE28849F0000371AEB /* ZimFileMissingIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97121EBC28849F0000371AEB /* ZimFileMissingIndicator.swift */; };
@ -125,6 +129,11 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
8E4396442B02E443007F0BC4 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Main.strings; sourceTree = "<group>"; };
8E4396452B02E443007F0BC4 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/LaunchScreen.strings; sourceTree = "<group>"; };
8E4396482B02E455007F0BC4 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; };
8E43964A2B02E458007F0BC4 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Localizable.strings; sourceTree = "<group>"; };
8E43964B2B02E4C6007F0BC4 /* String+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Extension.swift"; sourceTree = "<group>"; };
97008AB42974A5A70076E60C /* WikiMed.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = WikiMed.app; sourceTree = BUILT_PRODUCTS_DIR; };
97008ABA2974A5BF0076E60C /* Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
97008ABC2974A5BF0076E60C /* OPDSParserTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OPDSParserTests.swift; sourceTree = "<group>"; };
@ -294,6 +303,7 @@
9779A7E224567A5A00F6F6FF /* Log.swift */,
97B3BACD2736CE3500A23F49 /* URL.swift */,
9779A73A2456796B00F6F6FF /* WebKitHandler.swift */,
8E43964B2B02E4C6007F0BC4 /* String+Extension.swift */,
);
path = Utilities;
sourceTree = "<group>";
@ -387,8 +397,8 @@
974E7EE22930201500BDF59C /* ZimFileService */ = {
isa = PBXGroup;
children = (
974E7EE32930201500BDF59C /* ZimFileService.mm */,
974E7EE42930201500BDF59C /* ZimFileService.h */,
974E7EE32930201500BDF59C /* ZimFileService.mm */,
974E7EE52930201500BDF59C /* ZimFileService.swift */,
);
path = ZimFileService;
@ -550,6 +560,7 @@
97E94B22271EF250005B0295 /* Kiwix.entitlements */,
9779A5D02456796A00F6F6FF /* Kiwix-Bridging-Header.h */,
970885D0271339A300C5795C /* wikipedia_dark.css */,
8E4396492B02E455007F0BC4 /* Localizable.strings */,
);
path = Support;
sourceTree = "<group>";
@ -650,6 +661,7 @@
knownRegions = (
en,
Base,
es,
);
mainGroup = 97A2AB7F1C1B80FF00052E74;
packageReferences = (
@ -682,6 +694,7 @@
buildActionMask = 2147483647;
files = (
973A0DF72830929C00B41E71 /* Assets.xcassets in Resources */,
8E4396462B02E455007F0BC4 /* Localizable.strings in Resources */,
979D3A7C284159BF00E396B8 /* injection.js in Resources */,
97DE2BAD283B133700C63D9B /* wikipedia_dark.css in Resources */,
);
@ -692,6 +705,7 @@
buildActionMask = 2147483647;
files = (
97B448A1210FBC2E0004B056 /* LaunchScreen.storyboard in Resources */,
8E4396472B02E455007F0BC4 /* Localizable.strings in Resources */,
97B4489E210FBC2E0004B056 /* Assets.xcassets in Resources */,
97B4489C210FBC2C0004B056 /* Main.storyboard in Resources */,
);
@ -755,6 +769,7 @@
files = (
983ED7192B08AFE700409078 /* Kiwix-Bridging-Header.h in Sources */,
972DE4BB2814A5A4004FD9B9 /* Errors.swift in Sources */,
8E43964C2B02E4C6007F0BC4 /* String+Extension.swift in Sources */,
9790CA5A28A05EBB00D39FC6 /* ZimFilesCategories.swift in Sources */,
97486D08284A42B90096E4DD /* SearchResultRow.swift in Sources */,
9753D949285B55F100A626CC /* DefaultKeys.swift in Sources */,
@ -838,6 +853,7 @@
buildActionMask = 2147483647;
files = (
97B44899210FBC2C0004B056 /* ViewController.swift in Sources */,
8E43964D2B02E4C6007F0BC4 /* String+Extension.swift in Sources */,
97B44897210FBC2C0004B056 /* AppDelegate.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
@ -853,10 +869,20 @@
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */
8E4396492B02E455007F0BC4 /* Localizable.strings */ = {
isa = PBXVariantGroup;
children = (
8E4396482B02E455007F0BC4 /* en */,
8E43964A2B02E458007F0BC4 /* es */,
);
name = Localizable.strings;
sourceTree = "<group>";
};
97B4489A210FBC2C0004B056 /* Main.storyboard */ = {
isa = PBXVariantGroup;
children = (
97B4489B210FBC2C0004B056 /* Base */,
8E4396442B02E443007F0BC4 /* es */,
);
name = Main.storyboard;
sourceTree = "<group>";
@ -865,6 +891,7 @@
isa = PBXVariantGroup;
children = (
97B448A0210FBC2E0004B056 /* Base */,
8E4396452B02E443007F0BC4 /* es */,
);
name = LaunchScreen.storyboard;
sourceTree = "<group>";

View File

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

View File

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

View File

@ -0,0 +1,31 @@
//
// String+Extension.swift
// Kiwix
//
// Created by tvision251 on 11/13/23.
// Copyright © 2023 Chris Li. All rights reserved.
//
import Foundation
extension String {
var localized: String {
return NSLocalizedString(self, tableName: nil, bundle: Bundle.main, value: "", comment: "")
}
func localized(withComment: String) -> String {
return NSLocalizedString(self, tableName: nil, bundle: Bundle.main, value: "", comment: withComment)
}
func localizedWithFormat(withArgs: CVarArg...) -> String {
let format = NSLocalizedString(self, tableName: nil, bundle: Bundle.main, value: "", comment: "")
switch withArgs.count {
case 1: return String.localizedStringWithFormat(format, withArgs[0])
case 2: return String.localizedStringWithFormat(format, withArgs[0], withArgs[1])
default: return String.localizedStringWithFormat(format, withArgs)
}
}
}

View File

@ -0,0 +1,203 @@
/*
Localizable.strings
Kiwix
Created by tvision251 on 11/13/23.
Copyright © 2023 Chris Li. All rights reserved.
*/
/* Library Refresh Error */
"Error retrieving library data." = "Error retrieving library data.";
/* Library Refresh Error */
"Error parsing library data." = "Error parsing library data.";
/* Library Refresh Error */
"Error processing library data." = "Error processing library data.";
"Download Completed" = "Download Completed";
"%@ has been downloaded successfully." = "%@ has been downloaded successfully.";
"Done" = "Done";
"Go Back" = "Go Back";
"Go Forward" = "Go Forward";
"Outline" = "Outline";
"Show article outline" = "Show article outline";
"No outline available" = "No outline available";
"Remove Bookmark" = "Remove Bookmark";
"Add Bookmark" = "Add Bookmark";
"Show Bookmarks" = "Show Bookmarks";
"Show bookmarks. Long press to bookmark or unbookmark the current article." = "Show bookmarks. Long press to bookmark or unbookmark the current article.";
"Main Article" = "Main Article";
"Show main article" = "Show main article";
"Random Article" = "Random Article";
"Show random article" = "Show random article";
"Random Page" = "Random Page";
"Tabs" = "Tabs";
"New Tab" = "New Tab";
"Close This Tab" = "Close This Tab";
"Close All Tabs" = "Close All Tabs";
"Close Tab" = "Close Tab";
"Library" = "Library";
"Settings" = "Settings";
"Tabs Manager" = "Tabs Manager";
"Unable to load the article requested." = "Unable to load the article requested.";
"View" = "View";
"Remove" = "Remove";
"External Link" = "External Link";
"Load the link" = "Load the link";
"Cancel" = "Cancel";
"An external link is tapped, do you wish to load the link?" = "An external link is tapped, do you wish to load the link?";
"loc-external-alert" = "An external link is tapped. \
However, your current setting does not allow it to be loaded.";
"Open a zim file" = "Open a zim file";
"Unable to open file" = "Unable to open file";
"%@ cannot be opened." = "%@ cannot be opened.";
"No snippet" = "No snippet";
"Article Title" = "Article Title";
"loc-article-cell-template" = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, \
sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.";
"Unknown" = "Unknown";
"Yes" = "Yes";
"No" = "No";
"Failed" = "Failed";
"Downloading..." = "Downloading...";
"Paused" = "Paused";
"everything except large media files like video/audio" = "everything except large media files like video/audio";
"most pictures have been removed" = "most pictures have been removed";
"only a subset of the text is available, probably the first section" = "only a subset of the text is available, probably the first section";
"Header Text" = "Header Text";
"Content" = "Content";
"Just Now" = "Just Now";
"Never" = "Never";
"There is nothing to see" = "There is nothing to see";
"Article Title" = "Article Title";
"Zim file is missing." = "Zim file is missing.";
"articles" = "articles";
"Select a zim file to see detail" = "Select a zim file to see detail";
"Main Page" = "Main Page";
"Copy URL" = "Copy URL";
"Copy ID" = "Copy ID";
"No opened zim file" = "No opened zim file";
"Show Sidebar" = "Show Sidebar";
"Open..." = "Open...";
"Category" = "Category";
"No zim file under this category." = "No zim file under this category.";
"No download tasks" = "No download tasks";
"No new zim file" = "No new zim file";
"Refresh" = "Refresh";
"Name" = "Name";
"Description" = "Description";
"Actions" = "Actions";
"Info" = "Info";
"Locate" = "Locate";
"Open Main Page" = "Open Main Page";
"Reveal in Finder" = "Reveal in Finder";
"Download using cellular" = "Download using cellular";
"Unlink" = "Unlink";
"Unlink %@" = "Unlink %@";
"loc-ZimFileDetail-Alert-unlink" = "All bookmarked articles linked to this zim file will be deleted, \
but the original file will remain in place.";
"Delete %@" = "Delete %@";
"loc-ZimFileDetail-Alert-Delete" = "The zim file and all bookmarked articles \
linked to this zim file will be deleted.";
"Download" = "Download";
"Space Warning" = "Space Warning";
"There might not be enough space on your device for this zim file." = "There might not be enough space on your device for this zim file.";
"There would be less than 1GB space left after the zim file is downloaded." = "There would be less than 1GB space left after the zim file is downloaded.";
"Download Anyway" = "Download Anyway";
"Language" = "Language";
"Size" = "Size";
"Created" = "Created";
"Pictures" = "Pictures";
"Videos" = "Videos";
"Details" = "Details";
"Requires Service Workers" = "Requires Service Workers";
"Article Count" = "Article Count";
"Media Count" = "Media Count";
"ID" = "ID";
"Try to Recover" = "Try to Recover";
"Pause" = "Pause";
"Resume" = "Resume";
"Zim files requiring service workers are not supported." = "Zim files requiring service workers are not supported.";
"A very long description" = "A very long description";
"Page zoom" = "Page zoom";
"Reset" = "Reset";
"External link" = "External link";
"Search snippet" = "Search snippet";
"Reading" = "Reading";
"Catalog" = "Catalog";
"Refresh Now" = "Refresh Now";
"Last refresh" = "Last refresh";
"Auto refresh" = "Auto refresh";
"When enabled, the library catalog will be refreshed automatically when outdated." = "When enabled, the library catalog will be refreshed automatically when outdated.";
"Languages" = "Languages";
"Library" = "Library";
"Change will only apply to new download tasks." = "Change will only apply to new download tasks.";
"Refreshing..." = "Refreshing...";
"Include zim files in backup" = "Include zim files in backup";
"Backup" = "Backup";
"Does not apply to files opened in place." = "Does not apply to files opened in place.";
"Misc" = "Misc";
"Feedback" = "Feedback";
"Rate the App" = "Rate the App";
"About" = "About";
"Count" = "Count";
"No language" = "No language";
"Showing" = "Showing";
"Hiding" = "Hiding";
"Sorting" = "Sorting";
"Release" = "Release";
"Dependencies" = "Dependencies";
"License" = "License";
"Version" = "Version";
"loc-About-description" = "Kiwix is an offline reader for online content like Wikipedia, Project Gutenberg, or TED Talks. \
It makes knowledge available to people with no or limited internet access. \
The software as well as the content is free to use for anyone.";
"This app is released under the terms of the GNU General Public License version 3." = "This app is released under the terms of the GNU General Public License version 3.";
"Build" = "Build";
"Our Website" = "Our Website";
"Source" = "Source";
"GNU General Public License v3" = "GNU General Public License v3";
"Bookmarks" = "Bookmarks";
"No bookmarks" = "No bookmarks";
"No result" = "No result";
"Recent Search" = "Recent Search";
"Clear" = "Clear";
"Clear Recent Searches" = "Clear Recent Searches";
"Clear All" = "Clear All";
"All recent search history will be removed." = "All recent search history will be removed.";
"Included in Search" = "Included in Search";
"None" = "None";
"All" = "All";
"Open File" = "Open File";
"Fetching..." = "Fetching...";
"Fetch Catalog" = "Fetch Catalog";
"Actual Size" = "Actual Size";
"Zoom In" = "Zoom In";
"Zoom Out" = "Zoom Out";
"Not yet implemented" = "Not yet implemented";
"Close" = "Close";
"Map" = "Map";
"Opened" = "Opened";
"Categories" = "Categories";
"New" = "New";
"Downloads" = "Downloads";
"A-Z" = "A-Z";
"By Count" = "By Count";
"Always Ask" = "Always Ask";
"Always Load" = "Always Load";
"Never Load" = "Never Load";
"Wikipedia" = "Wikipedia";
"Wikibooks" = "Wikibooks";
"Wikinews" = "Wikinews";
"Wikiquote" = "Wikiquote";
"Wikisource" = "Wikisource";
"Wikiversity" = "Wikiversity";
"Wikivoyage" = "Wikivoyage";
"Wiktionary" = "Wiktionary";
"TED" = "TED";
"Vikidia" = "Vikidia";
"StackExchange" = "StackExchange";
"Other" = "Other";
"Disabled" = "Disabled";
"First Paragraph" = "First Paragraph";
"First Sentence" = "First Sentence";
"Matches" = "Matches";

View File

@ -0,0 +1,8 @@
/*
Localizable.strings
Kiwix
Created by tvision251 on 11/13/23.
Copyright © 2023 Chris Li. All rights reserved.
*/

View File

@ -317,12 +317,12 @@ final class BrowserViewModel: NSObject, ObservableObject,
// open url
actions.append(
UIAction(title: "Open", image: UIImage(systemName: "doc.text")) { _ in
UIAction(title: "Open".localized, image: UIImage(systemName: "doc.text")) { _ in
webView.load(URLRequest(url: url))
}
)
actions.append(
UIAction(title: "Open in New Tab", image: UIImage(systemName: "doc.badge.plus")) { _ in
UIAction(title: "Open in New Tab".localized, image: UIImage(systemName: "doc.badge.plus")) { _ in
NotificationCenter.openURL(url, inNewTab: true)
}
)
@ -333,12 +333,12 @@ final class BrowserViewModel: NSObject, ObservableObject,
let predicate = NSPredicate(format: "articleURL == %@", url as CVarArg)
let request = Bookmark.fetchRequest(predicate: predicate)
if let bookmarks = try? context.fetch(request), !bookmarks.isEmpty {
return UIAction(title: "Remove Bookmark",
return UIAction(title: "Remove Bookmark".localized,
image: UIImage(systemName: "star.slash.fill")) { [weak self] _ in
self?.deleteBookmark(url: url)
}
} else {
return UIAction(title: "Bookmark", image: UIImage(systemName: "star")) { [weak self] _ in
return UIAction(title: "Bookmark".localized, image: UIImage(systemName: "star")) { [weak self] _ in
self?.createBookmark(url: url)
}
}

View File

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

View File

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

View File

@ -44,7 +44,7 @@ struct ArticleCell: View {
if let snippet = snippet {
Text(AttributedString(snippet)).lineLimit(4)
} else if alwaysShowSnippet {
Text("No snippet").foregroundColor(.secondary)
Text("No snippet".localized).foregroundColor(.secondary)
}
}.font(.caption).multilineTextAlignment(.leading)
Spacer(minLength: 0)
@ -64,13 +64,8 @@ struct ArticleCell: View {
struct ArticleCell_Previews: PreviewProvider {
static let result: SearchResult = {
let result = SearchResult(zimFileID: UUID(), path: "", title: "Article Title")!
result.snippet = NSAttributedString(string:
"""
Lorem ipsum dolor sit amet, consectetur adipiscing elit, \
sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
"""
)
let result = SearchResult(zimFileID: UUID(), path: "", title: "Article Title".localized)!
result.snippet = NSAttributedString(string: "loc-article-cell-template".localized)
return result
}()

View File

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

View File

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

View File

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

View File

@ -29,8 +29,8 @@ struct GridSection<Content: View>: View {
struct GridSection_Previews: PreviewProvider {
static var previews: some View {
GridSection(title: "Header Text") {
Text("Content")
GridSection(title: "Header Text".localized) {
Text("Content".localized)
}
}
}

View File

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

View File

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

View File

@ -38,13 +38,8 @@ struct SearchResultRow: View {
struct SearchResultRow_Previews: PreviewProvider {
static let result: SearchResult = {
let result = SearchResult(zimFileID: UUID(), path: "", title: "Article Title")!
result.snippet = NSAttributedString(string:
"""
Lorem ipsum dolor sit amet, consectetur adipiscing elit, \
sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
"""
)
let result = SearchResult(zimFileID: UUID(), path: "", title: "Article Title".localized)!
result.snippet = NSAttributedString(string: "loc-article-cell-template".localized)
return result
}()

View File

@ -27,7 +27,7 @@ struct SheetContent<Content: View>: View {
Button {
dismiss()
} label: {
Text("Done").fontWeight(.semibold)
Text("Done".localized).fontWeight(.semibold)
}
}
}

View File

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

View File

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

View File

@ -30,7 +30,8 @@ struct ZimFileRow: View {
Formatter.size.string(fromByteCount: zimFile.size),
{
if #available(iOS 15.0, *) {
return "\(zimFile.articleCount.formatted(.number.notation(.compactName))) articles"
return "\(zimFile.articleCount.formatted(.number.notation(.compactName)))" +
"articles".localized
} else {
return Formatter.largeNumber(zimFile.articleCount)
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -25,10 +25,10 @@ struct ZimFileDetail: View {
var body: some View {
#if os(macOS)
List {
Section("Name") { Text(zimFile.name).lineLimit(nil) }.collapsible(false)
Section("Description") { Text(zimFile.fileDescription).lineLimit(nil) }.collapsible(false)
Section("Actions") { actions }.collapsible(false)
Section("Info") {
Section("Name".localized) { Text(zimFile.name).lineLimit(nil) }.collapsible(false)
Section("Description".localized) { Text(zimFile.fileDescription).lineLimit(nil) }.collapsible(false)
Section("Actions".localized) { actions }.collapsible(false)
Section("Info".localized) {
basicInfo
boolInfo
counts
@ -81,15 +81,15 @@ struct ZimFileDetail: View {
if let downloadTask = zimFile.downloadTask { // zim file is being downloaded
DownloadTaskDetail(downloadTask: downloadTask)
} else if zimFile.isMissing { // zim file was opened, but is now missing
Action(title: "Locate") { isPresentingFileLocator = true }
Action(title: "Locate".localized) { isPresentingFileLocator = true }
unlinkAction
} else if zimFile.fileURLBookmark != nil { // zim file is opened
Action(title: "Open Main Page") {
Action(title: "Open Main Page".localized) {
guard let url = ZimFileService.shared.getMainPageURL(zimFileID: zimFile.fileID) else { return }
NotificationCenter.openURL(url, inNewTab: true)
}
#if os(macOS)
Action(title: "Reveal in Finder") {
Action(title: "Reveal in Finder".localized) {
guard let url = ZimFileService.shared.getFileURL(zimFileID: zimFile.id) else { return }
NSWorkspace.shared.activateFileViewerSelecting([url])
}
@ -105,23 +105,20 @@ struct ZimFileDetail: View {
#endif
} else if zimFile.downloadURL != nil { // zim file can be downloaded
#if os(iOS)
Toggle("Download using cellular", isOn: $downloadUsingCellular)
Toggle("Download using cellular".localized, isOn: $downloadUsingCellular)
#endif
downloadAction
}
}
var unlinkAction: some View {
Action(title: "Unlink", isDestructive: true) {
Action(title: "Unlink".localized, isDestructive: true) {
isPresentingUnlinkAlert = true
}.alert(isPresented: $isPresentingUnlinkAlert) {
Alert(
title: Text("Unlink \(zimFile.name)"),
message: Text("""
All bookmarked articles linked to this zim file will be deleted, \
but the original file will remain in place.
"""),
primaryButton: .destructive(Text("Unlink")) {
title: Text("Unlink %@".localizedWithFormat(withArgs: zimFile.name)),
message: Text("loc-ZimFileDetail-Alert-unlink".localized),
primaryButton: .destructive(Text("Unlink".localized)) {
LibraryOperations.unlink(zimFileID: zimFile.fileID)
#if os(iOS)
presentationMode.wrappedValue.dismiss()
@ -137,9 +134,9 @@ struct ZimFileDetail: View {
isPresentingDeleteAlert = true
}.alert(isPresented: $isPresentingDeleteAlert) {
Alert(
title: Text("Delete \(zimFile.name)"),
message: Text("The zim file and all bookmarked articles linked to this zim file will be deleted."),
primaryButton: .destructive(Text("Delete")) {
title: Text("Delete %@".localizedWithFormat(withArgs: zimFile.name)),
message: Text("loc-ZimFileDetail-Alert-Delete".localized),
primaryButton: .destructive(Text("Delete".localized)) {
LibraryOperations.delete(zimFileID: zimFile.fileID)
#if os(iOS)
presentationMode.wrappedValue.dismiss()
@ -151,7 +148,7 @@ struct ZimFileDetail: View {
}
var downloadAction: some View {
Action(title: "Download") {
Action(title: "Download".localized) {
if let freeSpace = freeSpace, zimFile.size >= freeSpace - 10^9 {
isPresentingDownloadAlert = true
} else {
@ -159,15 +156,15 @@ struct ZimFileDetail: View {
}
}.alert(isPresented: $isPresentingDownloadAlert) {
Alert(
title: Text("Space Warning"),
title: Text("Space Warning".localized),
message: Text({
if let freeSpace = freeSpace, zimFile.size > freeSpace {
return "There might not be enough space on your device for this zim file."
return "There might not be enough space on your device for this zim file.".localized
} else {
return "There would be less than 1GB space left after the zim file is downloaded."
return "There would be less than 1GB space left after the zim file is downloaded.".localized
}
}()),
primaryButton: .default(Text("Download Anyway")) {
primaryButton: .default(Text("Download Anyway".localized)) {
DownloadService.shared.start(zimFileID: zimFile.id, allowsCellularAccess: false)
},
secondaryButton: .cancel()
@ -177,37 +174,38 @@ struct ZimFileDetail: View {
@ViewBuilder
var basicInfo: some View {
Attribute(title: "Language", detail: Locale.current.localizedString(forLanguageCode: zimFile.languageCode))
Attribute(title: "Category", detail: Category(rawValue: zimFile.category)?.description)
Attribute(title: "Size", detail: Formatter.size.string(fromByteCount: zimFile.size))
Attribute(title: "Created", detail: Formatter.dateMedium.string(from: zimFile.created))
Attribute(title: "Language".localized,
detail: Locale.current.localizedString(forLanguageCode: zimFile.languageCode))
Attribute(title: "Category".localized, detail: Category(rawValue: zimFile.category)?.description)
Attribute(title: "Size".localized, detail: Formatter.size.string(fromByteCount: zimFile.size))
Attribute(title: "Created".localized, detail: Formatter.dateMedium.string(from: zimFile.created))
}
@ViewBuilder
var boolInfo: some View {
AttributeBool(title: "Pictures", detail: zimFile.hasPictures)
AttributeBool(title: "Videos", detail: zimFile.hasVideos)
AttributeBool(title: "Details", detail: zimFile.hasDetails)
AttributeBool(title: "Pictures".localized, detail: zimFile.hasPictures)
AttributeBool(title: "Videos".localized, detail: zimFile.hasVideos)
AttributeBool(title: "Details".localized, detail: zimFile.hasDetails)
if zimFile.requiresServiceWorkers {
AttributeBool(title: "Requires Service Workers", detail: zimFile.requiresServiceWorkers)
AttributeBool(title: "Requires Service Workers".localized, detail: zimFile.requiresServiceWorkers)
}
}
@ViewBuilder
var counts: some View {
Attribute(
title: "Article Count",
title: "Article Count".localized,
detail: Formatter.number.string(from: NSNumber(value: zimFile.articleCount))
)
Attribute(
title: "Media Count",
title: "Media Count".localized,
detail: Formatter.number.string(from: NSNumber(value: zimFile.mediaCount))
)
}
@ViewBuilder
var id: some View {
Attribute(title: "ID", detail: String(zimFile.fileID.uuidString.prefix(8)))
Attribute(title: "ID".localized, detail: String(zimFile.fileID.uuidString.prefix(8)))
}
private var freeSpace: Int64? {
@ -237,27 +235,27 @@ private struct DownloadTaskDetail: View {
@ObservedObject var downloadTask: DownloadTask
var body: some View {
Action(title: "Cancel", isDestructive: true) {
Action(title: "Cancel".localized, isDestructive: true) {
DownloadService.shared.cancel(zimFileID: downloadTask.fileID)
}
if let error = downloadTask.error {
if downloadTask.resumeData != nil {
Action(title: "Try to Recover") {
Action(title: "Try to Recover".localized) {
DownloadService.shared.resume(zimFileID: downloadTask.fileID)
}
}
Attribute(title: "Failed", detail: detail)
Attribute(title: "Failed".localized, detail: detail)
Text(error)
} else if downloadTask.resumeData == nil {
Action(title: "Pause") {
Action(title: "Pause".localized) {
DownloadService.shared.pause(zimFileID: downloadTask.fileID)
}
Attribute(title: "Downloading...", detail: detail)
Attribute(title: "Downloading...".localized, detail: detail)
} else {
Action(title: "Resume") {
Action(title: "Resume".localized) {
DownloadService.shared.resume(zimFileID: downloadTask.fileID)
}
Attribute(title: "Paused", detail: detail)
Attribute(title: "Paused".localized, detail: detail)
}
}
@ -313,7 +311,7 @@ private struct Action: View {
private struct ServiceWorkerWarning: View {
var body: some View {
Label {
Text("Zim files requiring service workers are not supported.")
Text("Zim files requiring service workers are not supported.".localized)
} icon: {
Image(systemName: "exclamationmark.triangle.fill").renderingMode(.original)
}
@ -329,7 +327,7 @@ struct ZimFileDetail_Previews: PreviewProvider {
zimFile.created = Date()
zimFile.downloadURL = URL(string: "https://www.example.com")
zimFile.fileID = UUID()
zimFile.fileDescription = "A very long description"
zimFile.fileDescription = "A very long description".localized
zimFile.flavor = "max"
zimFile.hasDetails = true
zimFile.hasPictures = false

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -17,27 +17,27 @@ struct About: View {
var body: some View {
#if os(macOS)
VStack(spacing: 16) {
SettingSection(name: "About") {
SettingSection(name: "About".localized) {
about
ourWebsite
}
SettingSection(name: "Release") {
SettingSection(name: "Release".localized) {
release
HStack {
source
license
}
}
SettingSection(name: "Dependencies", alignment: .top) {
SettingSection(name: "Dependencies".localized, alignment: .top) {
Table(dependencies) {
TableColumn("Name", value: \.name)
TableColumn("License") { dependency in Text(dependency.license ?? "") }
TableColumn("Version", value: \.version)
TableColumn("Name".localized, value: \.name)
TableColumn("License".localized) { dependency in Text(dependency.license ?? "") }
TableColumn("Version".localized, value: \.version)
}.tableStyle(.bordered(alternatesRowBackgrounds: true))
}
}
.padding()
.tabItem { Label("About", systemImage: "info.circle") }
.tabItem { Label("About".localized, systemImage: "info.circle") }
.task { await getDependencies() }
.onChange(of: externalLinkURL) { url in
guard let url = url else { return }
@ -49,14 +49,14 @@ struct About: View {
about
ourWebsite
}
Section("Release") {
Section("Release".localized) {
release
appVersion
buildNumber
source
license
}
Section("Dependencies") {
Section("Dependencies".localized) {
ForEach(dependencies) { dependency in
HStack {
Text(dependency.name)
@ -70,7 +70,7 @@ struct About: View {
}
}
}
.navigationTitle("About")
.navigationTitle("About".localized)
.navigationBarTitleDisplayMode(.inline)
.sheet(item: $externalLinkURL) { SafariView(url: $0) }
.task { await getDependencies() }
@ -78,41 +78,36 @@ struct About: View {
}
private var about: some View {
Text(
"""
Kiwix is an offline reader for online content like Wikipedia, Project Gutenberg, or TED Talks. \
It makes knowledge available to people with no or limited internet access. \
The software as well as the content is free to use for anyone.
"""
)
Text("loc-About-description".localized)
}
private var release: some View {
Text("This app is released under the terms of the GNU General Public License version 3.")
Text("This app is released under the terms of the GNU General Public License version 3.".localized)
}
private var appVersion: some View {
Attribute(title: "Version", detail: Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String)
Attribute(title: "Version".localized,
detail: Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String)
}
private var buildNumber: some View {
Attribute(title: "Build", detail: Bundle.main.infoDictionary?["CFBundleVersion"] as? String)
Attribute(title: "Build".localized, detail: Bundle.main.infoDictionary?["CFBundleVersion"] as? String)
}
private var ourWebsite: some View {
Button("Our Website") {
Button("Our Website".localized) {
externalLinkURL = URL(string: "https://www.kiwix.org")
}
}
private var source: some View {
Button("Source") {
Button("Source".localized) {
externalLinkURL = URL(string: "https://github.com/kiwix/apple")
}
}
private var license: some View {
Button("GNU General Public License v3") {
Button("GNU General Public License v3".localized) {
externalLinkURL = URL(string: "https://www.gnu.org/licenses/gpl-3.0.en.html")
}
}

View File

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

View File

@ -18,30 +18,30 @@ struct ReadingSettings: View {
var body: some View {
VStack(spacing: 16) {
SettingSection(name: "Page zoom") {
SettingSection(name: "Page zoom".localized) {
HStack {
Stepper(webViewPageZoom.formatted(.percent), value: $webViewPageZoom, in: 0.5...2, step: 0.05)
Spacer()
Button("Reset") { webViewPageZoom = 1 }.disabled(webViewPageZoom == 1)
Button("Reset".localized) { webViewPageZoom = 1 }.disabled(webViewPageZoom == 1)
}
}
SettingSection(name: "External link") {
SettingSection(name: "External link".localized) {
Picker(selection: $externalLinkLoadingPolicy) {
ForEach(ExternalLinkLoadingPolicy.allCases) { loadingPolicy in
Text(loadingPolicy.name).tag(loadingPolicy)
Text(loadingPolicy.name.localized).tag(loadingPolicy)
}
} label: { }
}
SettingSection(name: "Search snippet") {
SettingSection(name: "Search snippet".localized) {
Picker(selection: $searchResultSnippetMode) {
ForEach(SearchResultSnippetMode.allCases) { snippetMode in
Text(snippetMode.name).tag(snippetMode)
Text(snippetMode.name.localized).tag(snippetMode)
}
} label: { }
}
}
.padding()
.tabItem { Label("Reading", systemImage: "book") }
.tabItem { Label("Reading".localized, systemImage: "book") }
}
}
@ -51,30 +51,30 @@ struct LibrarySettings: View {
var body: some View {
VStack(spacing: 16) {
SettingSection(name: "Catalog") {
SettingSection(name: "Catalog".localized) {
HStack(spacing: 6) {
Button("Refresh Now") {
Button("Refresh Now".localized) {
library.start(isUserInitiated: true)
}.disabled(library.isInProgress)
if library.isInProgress {
ProgressView().progressViewStyle(.circular).scaleEffect(0.5).frame(height: 1)
}
Spacer()
Text("Last refresh:").foregroundColor(.secondary)
Text("Last refresh".localized + ":").foregroundColor(.secondary)
LibraryLastRefreshTime().foregroundColor(.secondary)
}
VStack(alignment: .leading) {
Toggle("Auto refresh", isOn: $libraryAutoRefresh)
Text("When enabled, the library catalog will be refreshed automatically when outdated.")
Toggle("Auto refresh".localized, isOn: $libraryAutoRefresh)
Text("When enabled, the library catalog will be refreshed automatically when outdated.".localized)
.foregroundColor(.secondary)
}
}
SettingSection(name: "Languages", alignment: .top) {
SettingSection(name: "Languages".localized, alignment: .top) {
LanguageSelector()
}
}
.padding()
.tabItem { Label("Library", systemImage: "folder.badge.gearshape") }
.tabItem { Label("Library".localized, systemImage: "folder.badge.gearshape") }
}
}
@ -126,22 +126,23 @@ struct Settings: View {
miscellaneous
}
.modifier(ToolbarRoleBrowser())
.navigationTitle("Settings")
.navigationTitle("Settings".localized)
}
var readingSettings: some View {
Section("Reading") {
Section("Reading".localized) {
Stepper(value: $webViewPageZoom, in: 0.5...2, step: 0.05) {
Text("Page zoom: \(Formatter.percent.string(from: NSNumber(value: webViewPageZoom)) ?? "")")
Text("Page zoom".localized +
": \(Formatter.percent.string(from: NSNumber(value: webViewPageZoom)) ?? "")")
}
Picker("External link", selection: $externalLinkLoadingPolicy) {
Picker("External link".localized, selection: $externalLinkLoadingPolicy) {
ForEach(ExternalLinkLoadingPolicy.allCases) { loadingPolicy in
Text(loadingPolicy.name).tag(loadingPolicy)
Text(loadingPolicy.name.localized).tag(loadingPolicy)
}
}
Picker("Search snippet", selection: $searchResultSnippetMode) {
Picker("Search snippet".localized, selection: $searchResultSnippetMode) {
ForEach(SearchResultSnippetMode.allCases) { snippetMode in
Text(snippetMode.name).tag(snippetMode)
Text(snippetMode.name.localized).tag(snippetMode)
}
}
}
@ -154,58 +155,58 @@ struct Settings: View {
} label: {
SelectedLanaguageLabel()
}
Toggle("Download using cellular", isOn: $downloadUsingCellular)
Toggle("Download using cellular".localized, isOn: $downloadUsingCellular)
} header: {
Text("Library")
Text("Library".localized)
} footer: {
Text("Change will only apply to new download tasks.")
Text("Change will only apply to new download tasks.".localized)
}
}
var catalogSettings: some View {
Section {
HStack {
Text("Last refresh")
Text("Last refresh".localized)
Spacer()
LibraryLastRefreshTime().foregroundColor(.secondary)
}
if library.isInProgress {
HStack {
Text("Refreshing...").foregroundColor(.secondary)
Text("Refreshing...".localized).foregroundColor(.secondary)
Spacer()
ProgressView().progressViewStyle(.circular)
}
} else {
Button("Refresh Now") {
Button("Refresh Now".localized) {
library.start(isUserInitiated: true)
}
}
Toggle("Auto refresh", isOn: $libraryAutoRefresh)
Toggle("Auto refresh".localized, isOn: $libraryAutoRefresh)
} header: {
Text("Catalog")
Text("Catalog".localized)
} footer: {
Text("When enabled, the library catalog will be refreshed automatically when outdated.")
Text("When enabled, the library catalog will be refreshed automatically when outdated.".localized)
}.onChange(of: libraryAutoRefresh) { LibraryOperations.applyLibraryAutoRefreshSetting(isEnabled: $0) }
}
var backupSettings: some View {
Section {
Toggle("Include zim files in backup", isOn: $backupDocumentDirectory)
Toggle("Include zim files in backup".localized, isOn: $backupDocumentDirectory)
} header: {
Text("Backup")
Text("Backup".localized)
} footer: {
Text("Does not apply to files opened in place.")
Text("Does not apply to files opened in place.".localized)
}.onChange(of: backupDocumentDirectory) { LibraryOperations.applyFileBackupSetting(isEnabled: $0) }
}
var miscellaneous: some View {
Section("Misc") {
Button("Feedback") { UIApplication.shared.open(URL(string: "mailto:feedback@kiwix.org")!) }
Button("Rate the App") {
Section("Misc".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 +216,7 @@ private struct SelectedLanaguageLabel: View {
var body: some View {
HStack {
Text("Languages")
Text("Languages".localized)
Spacer()
if languageCodes.count == 1,
let languageCode = languageCodes.first,

View File

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

View File

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

View File

@ -40,19 +40,19 @@ struct ExternalLinkHandler: ViewModifier {
activeAlert = .notLoading
}
}
.alert("External Link", isPresented: $isAlertPresented, presenting: activeAlert) { alert in
.alert("External Link".localized, isPresented: $isAlertPresented, presenting: activeAlert) { alert in
if case .ask(let url) = alert {
Button("Load the link") {
Button("Load the link".localized) {
load(url: url)
}
Button("Cancel", role: .cancel) { }
Button("Cancel".localized, role: .cancel) { }
}
} message: { alert in
switch alert {
case .ask:
Text("An external link is tapped, do you wish to load the link?")
Text("An external link is tapped, do you wish to load the link?".localized)
case .notLoading:
Text("An external link is tapped. However, your current setting does not allow it to be loaded.")
Text("loc-external-alert".localized)
}
}
#if os(iOS)

View File

@ -41,7 +41,7 @@ struct OpenFileButton<Label: View>: View {
guard case let .success(urls) = result else { return }
NotificationCenter.openFiles(urls, context: context)
}
.help("Open a zim file")
.help("Open a zim file".localized)
.keyboardShortcut("o")
}
}
@ -98,11 +98,12 @@ struct OpenFileHandler: ViewModifier {
isAlertPresented = true
activeAlert = .unableToOpen(filenames: invalidURLs.map({ $0.lastPathComponent }))
}
}.alert("Unable to open file", isPresented: $isAlertPresented, presenting: activeAlert) { _ in
}.alert("Unable to open file".localized, isPresented: $isAlertPresented, presenting: activeAlert) { _ in
} message: { alert in
switch alert {
case .unableToOpen(let filenames):
Text("\(ListFormatter.localizedString(byJoining: filenames)) cannot be opened.")
let name = ListFormatter.localizedString(byJoining: filenames)
Text("%@ cannot be opened.".localizedWithFormat(withArgs: name))
}
}
}

View File

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

View File

@ -0,0 +1 @@

View File

@ -0,0 +1 @@