From 8132e31b893055ceb7f2a68fc71d487fc8cbc1c8 Mon Sep 17 00:00:00 2001 From: renaud gaudin Date: Sat, 11 Nov 2023 19:20:29 +0000 Subject: [PATCH 01/25] Swicth to LibraryPtr See https://github.com/kiwix/libkiwix/commit/1dc97055974a95245e17d14cf27e6376cb8ca416 --- Model/OPDSParser/OPDSParser.mm | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/Model/OPDSParser/OPDSParser.mm b/Model/OPDSParser/OPDSParser.mm index 5d309824..6dd3fd73 100644 --- a/Model/OPDSParser/OPDSParser.mm +++ b/Model/OPDSParser/OPDSParser.mm @@ -18,7 +18,7 @@ @interface OPDSParser () -@property kiwix::Library *library; +@property kiwix::LibraryPtr library; @end @@ -27,15 +27,11 @@ - (instancetype _Nonnull)init { self = [super init]; if (self) { - self.library = new kiwix::Library(); + self.library = kiwix::Library::create(); } return self; } -- (void)dealloc { - delete self.library; -} - - (BOOL)parseData:(nonnull NSData *)data { try { NSString *content = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; From e0aa5876c1958b5885c5868ee7f6983e31d63c12 Mon Sep 17 00:00:00 2001 From: Emmanuel Engelhart Date: Sat, 4 Nov 2023 10:22:40 +0100 Subject: [PATCH 02/25] Multiple clarifications around system support --- README.md | 87 ++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 64 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 1a765465..49980289 100644 --- a/README.md +++ b/README.md @@ -1,36 +1,74 @@ -# Kiwix for iOS & macOS +# Kiwix for Apple iOS & macOS -This is the home for Kiwix apps on iOS and macOS. +This is the home for Kiwix apps for Apple iOS and macOS. [![CodeFactor](https://www.codefactor.io/repository/github/kiwix/apple/badge)](https://www.codefactor.io/repository/github/kiwix/apple) [![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0) Drawing= -### Mobile app for iPads & iPhones ### +## Download + +Kiwix apps are made available primarily via the [Mac App +Store](https://macos.kiwix.org). + +Most recent versions of Kiwix support the three latest versions of the +OSes (either iOS or macOS). Older versions of Kiwix being still +downloadable for older versions of macOS and iOS on the Mac App Store. + +### iPads & iPhones ### - Download the iOS mobile app on the [App Store](https://ios.kiwix.org) -### Kiwix Desktop for macOS ### +### macOS ### - Download Kiwix Desktop on the [Mac App Store](https://macos.kiwix.org) - Download Kiwix Desktop [DMG file](https://download.kiwix.org/release/kiwix-desktop-macos/kiwix-desktop-macos.dmg) -## Developers +## Develop + +Kiwix developers use to work with cutting-edge versions of both macOS +and Xcode. [Continuous +integration](https://en.wikipedia.org/wiki/Continuous_integration) +secures that the whole project still compiles on the next to last +version of macOS with latest version of Xcode distributed on it. + +### CPU Architectures + +Kiwix compiles on both macOS with x86_64 or arm64 (M1, M2, ... family). + +Kiwix for iOS and macOS can run, in both cases, on x86_64 or arm64. ### Dependencies +To compile Kiwix you rely on the following compilation tools: * An [Apple Developer account](https://developer.apple.com) (doesn't require membership) * Latest Apple Developers Tools ([Xcode](https://developer.apple.com/xcode/)) * Its command-line utilities (`xcode-select --install`) -* `CoreKiwix.xcframework` ([libkiwix](https://github.com/kiwix/libkiwix)) +* `CoreKiwix.xcframework` ([libkiwix](https://github.com/kiwix/libkiwix) and [libzim](https://github.com/openzim/libzim)) -### Creating `CoreKiwix.xcframework` +### Steps -Instructions to build libkiwix at [on the kiwix-build repo](https://github.com/kiwix/kiwix-build). +To compile Kiwix, follow these steps: +* Open project with Xcode `open Kiwix.xcodeproj/project.xcworkspace/` +* Change the Bundle Identifier (in *Signing & Capabilities*) +* Select appropriate Signing Certificate/Profile. -The xcframework is a bundle of a library for multiple architectures and/or platforms. The `CoreKiwix.xcframework` will contain libkiwix library for macOS archs and for iOS. You don't have to follow steps for other platform/arch if you don't need them. +## Compile `CoreKiwix.xcframework` yourself -Following steps are done from kiwix-build root and assume your apple repository is at `../apple`. +`CoreKiwix.xcframework` is [made +available](https://dev.kiwix.org/apple/CoreKiwix.xcframework.zip) for +all supported OSes and CPU architectures. But you might want to +compile this piece (C++ code) by yourself. Here follow the +instructions to build libkiwix at [on the kiwix-build +repo](https://github.com/kiwix/kiwix-build). -#### Build libkiwix +The xcframework is a bundle of a library for multiple architectures +and/or platforms. The `CoreKiwix.xcframework` will contain libkiwix +library for macOS archs and for iOS. You don't have to follow steps +for other platform/arch if you don't need them. + +Following steps are done from kiwix-build root and assume your apple +repository is at `../apple`. + +### Build libkiwix Make sure to preinstall kiwix-build prerequisites (ninja and meson). @@ -40,7 +78,8 @@ If you use homebrew, run the following brew install ninja meson ``` -Make sure xcode command tools are installed. Make sure to download an iOS SDK if you want to build for iOS. +Make sure Xcode command tools are installed. Make sure to download an +iOS SDK if you want to build for iOS. ```sh xcode-select --install @@ -59,10 +98,11 @@ kiwix-build --target-platform macOS_x86_64 libkiwix kiwix-build --target-platform macOS_arm64_static libkiwix ``` -#### Create fat archive with all dependencies +### Create fat archive with all dependencies -This creates a single `.a` archive named `merged.a` (for each platform) which contains libkiwix and all it's dependencies. -Skip those you don't want to support. +This creates a single `.a` archive named `merged.a` (for each +platform) which contains libkiwix and all it's dependencies. Skip +those you don't want to support. ```sh libtool -static -o BUILD_macOS_x86_64/INSTALL/lib/merged.a BUILD_macOS_x86_64/INSTALL/lib/*.a @@ -71,7 +111,9 @@ libtool -static -o BUILD_iOS_x86_64/INSTALL/lib/merged.a BUILD_iOS_x86_64/INSTAL libtool -static -o BUILD_iOS_arm64/INSTALL/lib/merged.a BUILD_iOS_arm64/INSTALL/lib/*.a ``` -If you built macOS support for both archs (that's what you want unless you know what you're doing), you need to merge both files into a single one +If you built macOS support for both archs (that's what you want unless +you know what you're doing), you need to merge both files into a +single one ```sh mkdir -p macOS_fat @@ -80,7 +122,7 @@ lipo -create -output macOS_fat/merged.a \ -arch arm64 BUILD_macOS_arm64_static/INSTALL/lib/merged.a ``` -#### Add fat archive to xcframework +### Add fat archive to xcframework ```sh xcodebuild -create-xcframework \ @@ -90,11 +132,10 @@ xcodebuild -create-xcframework \ -output ../apple/CoreKiwix.xcframework ``` -You can now launch the build from Xcode and use the iOS simulator or your macOS target. At this point the xcframework is not signed. +You can now launch the build from Xcode and use the iOS simulator or +your macOS target. At this point the xcframework is not signed. +## License -### Building Kiwix iOS or Kiwix macOS - -* Open project with Xcode `open Kiwix.xcodeproj/project.xcworkspace/` -* Change the Bundle Identifier (in *Signing & Capabilities*) -* Select appropriate Signing Certificate/Profile. +[GPLv3](https://www.gnu.org/licenses/gpl-3.0) or later, see +[LICENSE](LICENSE) for more details. \ No newline at end of file From 67591eac0dcf6a1448fbd9028c80b0cadeb7e36f Mon Sep 17 00:00:00 2001 From: Emmanuel Engelhart Date: Sun, 5 Nov 2023 17:33:00 +0100 Subject: [PATCH 03/25] Add CoreKiwix/xcframework setup instruction --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 49980289..fada8e57 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,7 @@ To compile Kiwix you rely on the following compilation tools: ### Steps To compile Kiwix, follow these steps: +* Put CoreKiwix/xcframework at the root of the root of code folder * Open project with Xcode `open Kiwix.xcodeproj/project.xcworkspace/` * Change the Bundle Identifier (in *Signing & Capabilities*) * Select appropriate Signing Certificate/Profile. From bd23f3f4d2d7407829a7bb747ba9bb41d656339b Mon Sep 17 00:00:00 2001 From: Kelson Date: Sun, 5 Nov 2023 17:34:48 +0100 Subject: [PATCH 04/25] Update README.md Co-authored-by: rgaudin --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fada8e57..44f508aa 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ To compile Kiwix, follow these steps: available](https://dev.kiwix.org/apple/CoreKiwix.xcframework.zip) for all supported OSes and CPU architectures. But you might want to compile this piece (C++ code) by yourself. Here follow the -instructions to build libkiwix at [on the kiwix-build +instructions to build libkiwix+libzim at [on the kiwix-build repo](https://github.com/kiwix/kiwix-build). The xcframework is a bundle of a library for multiple architectures From 3362c7e26caae78ddb339e3c6b8b4266e555f380 Mon Sep 17 00:00:00 2001 From: Kelson Date: Sun, 5 Nov 2023 17:35:06 +0100 Subject: [PATCH 05/25] Update README.md Co-authored-by: rgaudin --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 44f508aa..6c7f496f 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ compile this piece (C++ code) by yourself. Here follow the instructions to build libkiwix+libzim at [on the kiwix-build repo](https://github.com/kiwix/kiwix-build). -The xcframework is a bundle of a library for multiple architectures +The xcframework is a bundle of all libkiwix dependencies for multiple architectures and/or platforms. The `CoreKiwix.xcframework` will contain libkiwix library for macOS archs and for iOS. You don't have to follow steps for other platform/arch if you don't need them. From 32f497c85cdb5eee4670be39b13f6e392376e477 Mon Sep 17 00:00:00 2001 From: Kelson Date: Sun, 5 Nov 2023 17:35:52 +0100 Subject: [PATCH 06/25] Update README.md Co-authored-by: rgaudin --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6c7f496f..1b1686a3 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ version of macOS with latest version of Xcode distributed on it. ### CPU Architectures -Kiwix compiles on both macOS with x86_64 or arm64 (M1, M2, ... family). +Kiwix compiles on both macOS architectures x86_64 and arm64 (Apple silicon). Kiwix for iOS and macOS can run, in both cases, on x86_64 or arm64. From 7ed437081dcf05ad0b2cde2cd11722133e365a68 Mon Sep 17 00:00:00 2001 From: renaud gaudin Date: Thu, 16 Nov 2023 08:58:36 +0000 Subject: [PATCH 07/25] updated README to match CoreKiwix from kiwix-build --- README.md | 98 ++++++++++++++----------------------------------------- 1 file changed, 25 insertions(+), 73 deletions(-) diff --git a/README.md b/README.md index 1b1686a3..0cfdb29a 100644 --- a/README.md +++ b/README.md @@ -8,27 +8,15 @@ This is the home for Kiwix apps for Apple iOS and macOS. ## Download -Kiwix apps are made available primarily via the [Mac App -Store](https://macos.kiwix.org). +Kiwix apps are made available primarily via the [App Store](https://ios.kiwix.org) and [Mac App Store](https://macos.kiwix.org). macOS version can also be [downloaded directly](https://download.kiwix.org/release/kiwix-desktop-macos/kiwix-desktop-macos.dmg). -Most recent versions of Kiwix support the three latest versions of the +Most recent versions of Kiwix support the three latest major versions of the OSes (either iOS or macOS). Older versions of Kiwix being still downloadable for older versions of macOS and iOS on the Mac App Store. -### iPads & iPhones ### -- Download the iOS mobile app on the [App Store](https://ios.kiwix.org) - -### macOS ### -- Download Kiwix Desktop on the [Mac App Store](https://macos.kiwix.org) -- Download Kiwix Desktop [DMG file](https://download.kiwix.org/release/kiwix-desktop-macos/kiwix-desktop-macos.dmg) - ## Develop -Kiwix developers use to work with cutting-edge versions of both macOS -and Xcode. [Continuous -integration](https://en.wikipedia.org/wiki/Continuous_integration) -secures that the whole project still compiles on the next to last -version of macOS with latest version of Xcode distributed on it. +Kiwix developers usually work with latest macOS and Xcode. Check our [Continuous Integration Workflow](https://github.com/kiwix/apple/blob/main/.github/workflows/ci.yml) to find out which XCode version we use on Github Actions. ### CPU Architectures @@ -39,6 +27,7 @@ Kiwix for iOS and macOS can run, in both cases, on x86_64 or arm64. ### Dependencies To compile Kiwix you rely on the following compilation tools: + * An [Apple Developer account](https://developer.apple.com) (doesn't require membership) * Latest Apple Developers Tools ([Xcode](https://developer.apple.com/xcode/)) * Its command-line utilities (`xcode-select --install`) @@ -47,33 +36,29 @@ To compile Kiwix you rely on the following compilation tools: ### Steps To compile Kiwix, follow these steps: -* Put CoreKiwix/xcframework at the root of the root of code folder + +* Put `CoreKiwix/xcframework` at the root of this folder * Open project with Xcode `open Kiwix.xcodeproj/project.xcworkspace/` * Change the Bundle Identifier (in *Signing & Capabilities*) * Select appropriate Signing Certificate/Profile. -## Compile `CoreKiwix.xcframework` yourself +### Getting `CoreKiwix.xcframework` -`CoreKiwix.xcframework` is [made -available](https://dev.kiwix.org/apple/CoreKiwix.xcframework.zip) for -all supported OSes and CPU architectures. But you might want to -compile this piece (C++ code) by yourself. Here follow the -instructions to build libkiwix+libzim at [on the kiwix-build -repo](https://github.com/kiwix/kiwix-build). +`CoreKiwix.xcframework` is published with all supported platforms and CPU architectures: + +- [latest release](https://download.kiwix.org/release/libkiwix/libkiwix_xcframework.tar.gz) +- [latest nightly](https://download.kiwix.org/nightly/libkiwix_xcframework.tar.gz): using `main` branch of both `libkiwix` and `libzim`. + +#### Compiling `CoreKiwix.xcframework` + +You may want to compile it yourself, to use different branches of said projects for instance. The xcframework is a bundle of all libkiwix dependencies for multiple architectures -and/or platforms. The `CoreKiwix.xcframework` will contain libkiwix -library for macOS archs and for iOS. You don't have to follow steps -for other platform/arch if you don't need them. +and platforms. The `CoreKiwix.xcframework` will contain libkiwix +library for macOS archs and for iOS. It is built off [kiwix-build +repo](https://github.com/kiwix/kiwix-build). -Following steps are done from kiwix-build root and assume your apple -repository is at `../apple`. - -### Build libkiwix - -Make sure to preinstall kiwix-build prerequisites (ninja and meson). - -If you use homebrew, run the following +Make sure to preinstall kiwix-build prerequisites (ninja and meson). If you use homebrew, run the following ```sh brew install ninja meson @@ -91,46 +76,13 @@ Then you can build `libkiwix` ```sh git clone https://github.com/kiwix/kiwix-build.git cd kiwix-build -# [iOS] build libkiwix -kiwix-build --target-platform iOS_arm64 libkiwix -kiwix-build --target-platform iOS_x86_64 libkiwix # iOS simulator in Xcode -# [macOS] build libkiwix -kiwix-build --target-platform macOS_x86_64 libkiwix -kiwix-build --target-platform macOS_arm64_static libkiwix -``` +python3 -m venv .venv +source .venv/bin/activate +pip install -e . -### Create fat archive with all dependencies - -This creates a single `.a` archive named `merged.a` (for each -platform) which contains libkiwix and all it's dependencies. Skip -those you don't want to support. - -```sh -libtool -static -o BUILD_macOS_x86_64/INSTALL/lib/merged.a BUILD_macOS_x86_64/INSTALL/lib/*.a -libtool -static -o BUILD_macOS_arm64_static/INSTALL/lib/merged.a BUILD_macOS_arm64_static/INSTALL/lib/*.a -libtool -static -o BUILD_iOS_x86_64/INSTALL/lib/merged.a BUILD_iOS_x86_64/INSTALL/lib/*.a -libtool -static -o BUILD_iOS_arm64/INSTALL/lib/merged.a BUILD_iOS_arm64/INSTALL/lib/*.a -``` - -If you built macOS support for both archs (that's what you want unless -you know what you're doing), you need to merge both files into a -single one - -```sh -mkdir -p macOS_fat -lipo -create -output macOS_fat/merged.a \ - -arch x86_64 BUILD_macOS_x86_64/INSTALL/lib/merged.a \ - -arch arm64 BUILD_macOS_arm64_static/INSTALL/lib/merged.a -``` - -### Add fat archive to xcframework - -```sh -xcodebuild -create-xcframework \ - -library macOS_fat/merged.a -headers BUILD_macOS_x86_64/INSTALL/include \ - -library BUILD_iOS_x86_64/INSTALL/lib/merged.a -headers BUILD_iOS_x86_64/INSTALL/include \ - -library BUILD_iOS_arm64/INSTALL/lib/merged.a -headers BUILD_iOS_arm64/INSTALL/include \ - -output ../apple/CoreKiwix.xcframework +kiwix-build --target-platform apple_all_static libkiwix +# assuming your kiwix-build and apple folder at at same level +cp -r BUILD_apple_all_static/INSTALL/lib/CoreKiwix.xcframework ../apple/ ``` You can now launch the build from Xcode and use the iOS simulator or From c64044d2431a9b630ce89779f619f517e14cafd5 Mon Sep 17 00:00:00 2001 From: renaud gaudin Date: Mon, 13 Nov 2023 13:58:57 +0000 Subject: [PATCH 08/25] added dummy CI workflow --- .github/ci.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 .github/ci.yml diff --git a/.github/ci.yml b/.github/ci.yml new file mode 100644 index 00000000..16360993 --- /dev/null +++ b/.github/ci.yml @@ -0,0 +1,15 @@ +name: CI + +on: + pull_request: + push: + branches: + - main + - ci + +jobs: + test: + runs-on: ubuntu-22-04 + steps: + - name: Hello + run: echo "hello world" From aa58f2b57bd106ea451dc57870f97be7256d0637 Mon Sep 17 00:00:00 2001 From: renaud gaudin Date: Mon, 13 Nov 2023 14:00:33 +0000 Subject: [PATCH 09/25] dummy action in correct location --- .github/{ => workflows}/ci.yml | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/{ => workflows}/ci.yml (100%) diff --git a/.github/ci.yml b/.github/workflows/ci.yml similarity index 100% rename from .github/ci.yml rename to .github/workflows/ci.yml From 001136d59e62c43e202fd1a7fb5adddbc1796598 Mon Sep 17 00:00:00 2001 From: renaud gaudin Date: Mon, 13 Nov 2023 14:03:19 +0000 Subject: [PATCH 10/25] fixed image name --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 16360993..c12cba13 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,7 +9,7 @@ on: jobs: test: - runs-on: ubuntu-22-04 + runs-on: ubuntu-22.04 steps: - name: Hello run: echo "hello world" From 9298b978a541553328018b86c3ab2ea6f1627adf Mon Sep 17 00:00:00 2001 From: renaud gaudin Date: Mon, 13 Nov 2023 13:52:25 +0000 Subject: [PATCH 11/25] Introducing Continuous Integration Building both macOS App and iOS App on every commit on `main` and on PRs. Build is not used/uploaded but still requires to be signed. Signing is done by automatically requesting appropriate Dev certificate using Store API Key. --- .github/workflows/ci.yml | 60 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 56 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c12cba13..d53f113b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,8 +8,60 @@ on: - ci jobs: - test: - runs-on: ubuntu-22.04 + build: + strategy: + fail-fast: false + matrix: + destination: + - platform: macOS + name: Any Mac + - platform: iOS + name: Any iOS Device + runs-on: macos-13 + env: + XCF_URL: https://tmp.kiwix.org/ci/dev_preview/xcframework/libkiwix_xcframework-2023-11-11.tar.gz + # XCF_URL: https://download.kiwix.org/nightly/libkiwix_xcframework-2023-11-11.tar.gz + XC_PROJECT: Kiwix.xcodeproj + XC_SCHEME: Kiwix + XC_CONFIG: Release + XC_DESTINATION: platform=${{ matrix.destination.platform }},name=${{ matrix.destination.name }} + STORE_AUTH_KEY: /tmp/authkey.p8 steps: - - name: Hello - run: echo "hello world" + - 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 + - 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 + 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 -project Kiwix.xcodeproj -scheme Kiwix -showBuildSettings + - name: Copy Key file to disk + run: echo "%{{ secrets.APPLE_STORE_AUTH_KEY }}" > ${STORE_AUTH_KEY} && chmod 600 ${STORE_AUTH_KEY} + - name: Build for ${{ matrix.destination.platform }}/${{ matrix.destination.name }} + env: + 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: xcrun xcodebuild \ + -project ${XC_PROJECT} \ + -scheme ${XC_SCHEME} \ + -destination "${XC_DESTINATION}" \ + -configuration ${XC_CONFIG} \ + -onlyUsePackageVersionsFromResolvedFile \ + -derivedDataPath $PWD/build \ + -allowProvisioningUpdates \ + -authenticationKeyPath ${STORE_AUTH_KEY} \ + -authenticationKeyID ${APPLE_STORE_AUTH_KEY_ID} \ + -authenticationKeyIssuerID ${APPLE_STORE_AUTH_KEY_ISSUER_ID} \ + -verbose \ + build From 0a3c36417fd78560be7ff920e2d92d2b650467ab Mon Sep 17 00:00:00 2001 From: renaud gaudin Date: Mon, 13 Nov 2023 14:08:52 +0000 Subject: [PATCH 12/25] GH runner already has intermediate certificate --- .github/workflows/ci.yml | 21 +++++---------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d53f113b..9633f3a2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,6 +22,7 @@ jobs: XCF_URL: https://tmp.kiwix.org/ci/dev_preview/xcframework/libkiwix_xcframework-2023-11-11.tar.gz # XCF_URL: https://download.kiwix.org/nightly/libkiwix_xcframework-2023-11-11.tar.gz XC_PROJECT: Kiwix.xcodeproj + XC_WORKSPACE: Kiwix.xcodeproj/project.xcworkspace/ XC_SCHEME: Kiwix XC_CONFIG: Release XC_DESTINATION: platform=${{ matrix.destination.platform }},name=${{ matrix.destination.name }} @@ -34,7 +35,7 @@ jobs: -k /Library/Keychains/System.keychain \ -T /usr/bin/codesign \ -T /usr/bin/security \ - -T /usr/bin/productbuild + -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 @@ -45,23 +46,11 @@ jobs: - name: Prepare Xcode run: xcrun xcodebuild -checkFirstLaunchStatus || xcrun xcodebuild -runFirstLaunch - name: Dump build settings - run: xcrun xcodebuild -project Kiwix.xcodeproj -scheme Kiwix -showBuildSettings + run: xcrun xcodebuild -workspace $XC_WORKSPACE -scheme $XC_SCHEME -showBuildSettings - name: Copy Key file to disk - run: echo "%{{ secrets.APPLE_STORE_AUTH_KEY }}" > ${STORE_AUTH_KEY} && chmod 600 ${STORE_AUTH_KEY} + run: echo "${{ secrets.APPLE_STORE_AUTH_KEY }}" > ${STORE_AUTH_KEY} && chmod 600 ${STORE_AUTH_KEY} - name: Build for ${{ matrix.destination.platform }}/${{ matrix.destination.name }} env: 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: xcrun xcodebuild \ - -project ${XC_PROJECT} \ - -scheme ${XC_SCHEME} \ - -destination "${XC_DESTINATION}" \ - -configuration ${XC_CONFIG} \ - -onlyUsePackageVersionsFromResolvedFile \ - -derivedDataPath $PWD/build \ - -allowProvisioningUpdates \ - -authenticationKeyPath ${STORE_AUTH_KEY} \ - -authenticationKeyID ${APPLE_STORE_AUTH_KEY_ID} \ - -authenticationKeyIssuerID ${APPLE_STORE_AUTH_KEY_ISSUER_ID} \ - -verbose \ - build + run: xcrun xcodebuild -workspace $XC_WORKSPACE -scheme $XC_SCHEME -destination "$XC_DESTINATION" -configuration $XC_CONFIG -onlyUsePackageVersionsFromResolvedFile -derivedDataPath $PWD/build -allowProvisioningUpdates -authenticationKeyPath $STORE_AUTH_KEY -authenticationKeyID $APPLE_STORE_AUTH_KEY_ID -authenticationKeyIssuerID $APPLE_STORE_AUTH_KEY_ISSUER_ID -verbose build From da67eefdc574b84918320510b03a3117c3218ba9 Mon Sep 17 00:00:00 2001 From: renaud gaudin Date: Mon, 13 Nov 2023 16:34:38 +0000 Subject: [PATCH 13/25] import apple-development certificate --- .github/workflows/ci.yml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9633f3a2..24b3e7f2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,7 +27,31 @@ jobs: XC_CONFIG: Release XC_DESTINATION: platform=${{ matrix.destination.platform }},name=${{ matrix.destination.name }} STORE_AUTH_KEY: /tmp/authkey.p8 + 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 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 - name: Update Apple Intermediate Certificate run: | curl -L -o ~/Downloads/AppleWWDRCAG3.cer https://www.apple.com/certificateauthority/AppleWWDRCAG3.cer From f8c863d29a8687e97a79e3d50ef3c4762334170c Mon Sep 17 00:00:00 2001 From: renaud gaudin Date: Tue, 14 Nov 2023 11:29:35 +0000 Subject: [PATCH 14/25] mark CoreKiwix.xcframework relative to root --- Kiwix.xcodeproj/project.pbxproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Kiwix.xcodeproj/project.pbxproj b/Kiwix.xcodeproj/project.pbxproj index 922d260d..2e2bc8ae 100644 --- a/Kiwix.xcodeproj/project.pbxproj +++ b/Kiwix.xcodeproj/project.pbxproj @@ -223,7 +223,7 @@ 97DA90D72975B0C100738365 /* LibraryRefreshViewModelTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryRefreshViewModelTest.swift; sourceTree = ""; }; 97DE2BA1283A8E5C00C63D9B /* LibraryViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryViewModel.swift; sourceTree = ""; }; 97DE2BA4283A944100C63D9B /* GridCommon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GridCommon.swift; sourceTree = ""; }; - 97E88F4C2AE407320037F0E5 /* CoreKiwix.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = CoreKiwix.xcframework; sourceTree = ""; }; + 97E88F4C2AE407320037F0E5 /* CoreKiwix.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = CoreKiwix.xcframework; sourceTree = SOURCE_ROOT; }; 97E94B1D271EF250005B0295 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97E94B22271EF250005B0295 /* Kiwix.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Kiwix.entitlements; sourceTree = ""; }; 97F3332E28AFC1A2007FF53C /* SearchResults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchResults.swift; sourceTree = ""; }; From 67a31f0a91fa35d65329c8e156066b40313b3c2a Mon Sep 17 00:00:00 2001 From: renaud gaudin Date: Wed, 15 Nov 2023 15:33:18 +0000 Subject: [PATCH 15/25] Running build up to twice MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Only “reliable” way I found to succeed build in CI --- .github/workflows/ci.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 24b3e7f2..79e5ca64 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -73,8 +73,11 @@ jobs: run: xcrun xcodebuild -workspace $XC_WORKSPACE -scheme $XC_SCHEME -showBuildSettings - name: Copy Key file to disk run: echo "${{ secrets.APPLE_STORE_AUTH_KEY }}" > ${STORE_AUTH_KEY} && chmod 600 ${STORE_AUTH_KEY} + - name: Install retry command + run: brew install kadwanev/brew/retry - name: Build for ${{ matrix.destination.platform }}/${{ matrix.destination.name }} env: 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: xcrun xcodebuild -workspace $XC_WORKSPACE -scheme $XC_SCHEME -destination "$XC_DESTINATION" -configuration $XC_CONFIG -onlyUsePackageVersionsFromResolvedFile -derivedDataPath $PWD/build -allowProvisioningUpdates -authenticationKeyPath $STORE_AUTH_KEY -authenticationKeyID $APPLE_STORE_AUTH_KEY_ID -authenticationKeyIssuerID $APPLE_STORE_AUTH_KEY_ISSUER_ID -verbose build + 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 -authenticationKeyPath $STORE_AUTH_KEY -authenticationKeyID $APPLE_STORE_AUTH_KEY_ID -authenticationKeyIssuerID $APPLE_STORE_AUTH_KEY_ISSUER_ID -verbose build From b3a3dbadc192cf526c6246fcb51b636bb1171b41 Mon Sep 17 00:00:00 2001 From: renaud gaudin Date: Wed, 15 Nov 2023 16:07:38 +0000 Subject: [PATCH 16/25] Without auth key --- .github/workflows/ci.yml | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 79e5ca64..964ab131 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,7 +26,6 @@ jobs: XC_SCHEME: Kiwix XC_CONFIG: Release XC_DESTINATION: platform=${{ matrix.destination.platform }},name=${{ matrix.destination.name }} - STORE_AUTH_KEY: /tmp/authkey.p8 CERTIFICATE: /tmp/apple-development.p12 SIGNING_IDENTITY: ${{ secrets.APPLE_DEVELOPMENT_SIGNING_IDENTITY }} KEYCHAIN: /Users/runner/build.keychain-db @@ -71,13 +70,9 @@ jobs: run: xcrun xcodebuild -checkFirstLaunchStatus || xcrun xcodebuild -runFirstLaunch - name: Dump build settings run: xcrun xcodebuild -workspace $XC_WORKSPACE -scheme $XC_SCHEME -showBuildSettings - - name: Copy Key file to disk - run: echo "${{ secrets.APPLE_STORE_AUTH_KEY }}" > ${STORE_AUTH_KEY} && chmod 600 ${STORE_AUTH_KEY} - name: Install retry command run: brew install kadwanev/brew/retry - name: Build for ${{ matrix.destination.platform }}/${{ matrix.destination.name }} env: - APPLE_STORE_AUTH_KEY_ID: ${{ secrets.APPLE_STORE_AUTH_KEY_ID }} - APPLE_STORE_AUTH_KEY_ISSUER_ID: ${{ secrets.APPLE_STORE_AUTH_KEY_ISSUER_ID }} 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 -authenticationKeyPath $STORE_AUTH_KEY -authenticationKeyID $APPLE_STORE_AUTH_KEY_ID -authenticationKeyIssuerID $APPLE_STORE_AUTH_KEY_ISSUER_ID -verbose build + 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 From 55db5807df63ceb734eaa9ec09df66b586bde4d3 Mon Sep 17 00:00:00 2001 From: renaud gaudin Date: Wed, 15 Nov 2023 16:19:57 +0000 Subject: [PATCH 17/25] additional comments --- .github/workflows/ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 964ab131..0b83cf00 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -51,6 +51,7 @@ jobs: --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 @@ -70,6 +71,7 @@ jobs: 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 }} From b195baa5b9e3d77697b3a0c7b135c389a6effb73 Mon Sep 17 00:00:00 2001 From: renaud gaudin Date: Thu, 16 Nov 2023 09:53:35 +0000 Subject: [PATCH 18/25] using latest nightly redirect --- .github/workflows/ci.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0b83cf00..a70a2926 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,7 +5,6 @@ on: push: branches: - main - - ci jobs: build: @@ -19,8 +18,7 @@ jobs: name: Any iOS Device runs-on: macos-13 env: - XCF_URL: https://tmp.kiwix.org/ci/dev_preview/xcframework/libkiwix_xcframework-2023-11-11.tar.gz - # XCF_URL: https://download.kiwix.org/nightly/libkiwix_xcframework-2023-11-11.tar.gz + XCF_URL: https://download.kiwix.org/nightly/libkiwix_xcframework.tar.gz XC_PROJECT: Kiwix.xcodeproj XC_WORKSPACE: Kiwix.xcodeproj/project.xcworkspace/ XC_SCHEME: Kiwix From 84fc4ac780a438f0c52979a17aff913b21d2827d Mon Sep 17 00:00:00 2001 From: renaud gaudin Date: Sat, 11 Nov 2023 19:20:29 +0000 Subject: [PATCH 19/25] Swicth to LibraryPtr See https://github.com/kiwix/libkiwix/commit/1dc97055974a95245e17d14cf27e6376cb8ca416 --- Model/OPDSParser/OPDSParser.mm | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/Model/OPDSParser/OPDSParser.mm b/Model/OPDSParser/OPDSParser.mm index 5d309824..6dd3fd73 100644 --- a/Model/OPDSParser/OPDSParser.mm +++ b/Model/OPDSParser/OPDSParser.mm @@ -18,7 +18,7 @@ @interface OPDSParser () -@property kiwix::Library *library; +@property kiwix::LibraryPtr library; @end @@ -27,15 +27,11 @@ - (instancetype _Nonnull)init { self = [super init]; if (self) { - self.library = new kiwix::Library(); + self.library = kiwix::Library::create(); } return self; } -- (void)dealloc { - delete self.library; -} - - (BOOL)parseData:(nonnull NSData *)data { try { NSString *content = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; From 66ee6f775928af5bae8cba6c585f2f18a5f08e3c Mon Sep 17 00:00:00 2001 From: renaud gaudin Date: Fri, 17 Nov 2023 16:10:20 +0000 Subject: [PATCH 20/25] using libkiwix release, set to 13.0.0 --- .github/workflows/ci.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 36f3ca91..ae6b1fa3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,6 +5,10 @@ on: push: branches: - main + +env: + LIBKIWIX_VERSION: "13.0.0" + jobs: build: strategy: @@ -17,8 +21,6 @@ jobs: name: Any iOS Device runs-on: macos-13 env: - XCF_URL: https://download.kiwix.org/nightly/libkiwix_xcframework.tar.gz - XC_PROJECT: Kiwix.xcodeproj XC_WORKSPACE: Kiwix.xcodeproj/project.xcworkspace/ XC_SCHEME: Kiwix XC_CONFIG: Release @@ -63,6 +65,8 @@ jobs: - 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 From 22c72ae2a2513561cdf73de9e3e0e1db8671c911 Mon Sep 17 00:00:00 2001 From: tvision106 Date: Tue, 14 Nov 2023 16:20:36 -0500 Subject: [PATCH 21/25] 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 22/25] 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 23/25] 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 24/25] 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 25/25] 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 }()