From d1116661f388326bd9315c32fdba3cbea10e450c Mon Sep 17 00:00:00 2001 From: Jakub Olejnik Date: Mon, 28 Sep 2026 16:54:55 +0200 Subject: [PATCH 1/2] =?UTF-8?q?=E2=9C=A8=20Add=20embeddable=20ACKLocalizat?= =?UTF-8?q?ionCommand?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Command-line handling now uses Swift Argument Parser. The command (`localization`) lives in the new ACKLocalizationCommands library so other tools can embed it, the executable only marks it as @main. ACKLocalization.run() now rethrows instead of calling exit(1), so an embedding tool is not terminated on failure. --- .claude/CLAUDE.md | 7 +++--- CHANGELOG.md | 2 ++ Package.resolved | 11 +++++++++- Package.swift | 19 +++++++++++++++- .../ACKLocalizationCommand+Main.swift | 4 ++++ Sources/ACKLocalization/main.swift | 5 ----- .../ACKLocalizationCommand.swift | 22 +++++++++++++++++++ .../ACKLocalizationCore/ACKLocalization.swift | 8 ++++--- 8 files changed, 65 insertions(+), 13 deletions(-) create mode 100644 Sources/ACKLocalization/ACKLocalizationCommand+Main.swift delete mode 100644 Sources/ACKLocalization/main.swift create mode 100644 Sources/ACKLocalizationCommands/ACKLocalizationCommand.swift diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index be072d8..7e4c8f9 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -17,10 +17,11 @@ Tests use **Swift Testing** (`@Test`/`#expect`), not XCTest. ## Architecture -Two targets with a thin executable wrapper: +Two library targets with a thin executable wrapper: -- `Sources/ACKLocalization/main.swift` — executable; just instantiates `ACKLocalization` and calls `run()`. -- `Sources/ACKLocalizationCore` — all logic, exposed as a library so it is testable. +- `Sources/ACKLocalization` — executable; only marks `ACKLocalizationCommand` as `@main`. +- `Sources/ACKLocalizationCommands` — library with the public `ACKLocalizationCommand` (swift-argument-parser `AsyncParsableCommand`, command name `localization`) that instantiates `ACKLocalization` and runs it; exposed so other tools can embed it (e.g. as a subcommand). +- `Sources/ACKLocalizationCore` — all logic, exposed as a library so it is testable; does not depend on ArgumentParser. The pipeline lives in `ACKLocalizationCore/ACKLocalization.swift` and runs: load `localization.json` (`Model/Configuration.swift`, with fallback decoding of the legacy `ConfigurationV1` format) → resolve credentials (config values take priority over the `ACKLOCALIZATION_SERVICE_ACCOUNT_PATH` / `ACKLOCALIZATION_API_KEY` env vars, service account over API key, ADC as last resort) → fetch spreadsheet via `SheetsAPIService` → `transformValues` maps sheet columns to languages via `languageMapping` → `saveMappedValues` groups rows per output file (keys prefixed `plist..` go to `.strings`, plural keys `key##{rule}` go to `.stringsdict`) and writes via `FileSystem`. diff --git a/CHANGELOG.md b/CHANGELOG.md index 21c3b01..be413c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ ## main ### Changed +- Use [Swift Argument Parser](https://github.com/apple/swift-argument-parser) for command-line handling, the command is available as `ACKLocalizationCommand` in the new `ACKLocalizationCommands` library so it can be embedded in other tools (kudos to @olejnjak) +- `ACKLocalization.run()` no longer calls `exit(1)` on failure, it displays the error and rethrows it instead (kudos to @olejnjak) - Replace internal Combine usage with async/await ([#47](https://github.com/AckeeCZ/ACKLocalization/pull/47), kudos to @olejnjak) - Migrate tests to Swift Testing and extend test coverage ([#47](https://github.com/AckeeCZ/ACKLocalization/pull/47), kudos to @olejnjak) diff --git a/Package.resolved b/Package.resolved index d340402..a40a4d6 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "f6844888ec22893d862a6d50b01a09ebc625f4e3501a03169b6156668ab7e9fa", + "originHash" : "284993b03ec469775bc02b18c34a7995014bd5748ca9f0f36c6e5f85a5a2e875", "pins" : [ { "identity" : "google-auth-swift", @@ -19,6 +19,15 @@ "version" : "5.1.0" } }, + { + "identity" : "swift-argument-parser", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-argument-parser", + "state" : { + "revision" : "6a52f3251125d74daf04fcbd5e6f08a75d074382", + "version" : "1.8.2" + } + }, { "identity" : "swift-asn1", "kind" : "remoteSourceControl", diff --git a/Package.swift b/Package.swift index c57503c..8a9e3b0 100644 --- a/Package.swift +++ b/Package.swift @@ -11,6 +11,9 @@ let package = Package( .library( name: "ACKLocalizationCore", targets: ["ACKLocalizationCore"]), + .library( + name: "ACKLocalizationCommands", + targets: ["ACKLocalizationCommands"]), .executable( name: "ACKLocalization", targets: ["ACKLocalization"]), @@ -20,6 +23,10 @@ let package = Package( url: "https://github.com/olejnjak/google-auth-swift", from: "0.1.1" ), + .package( + url: "https://github.com/apple/swift-argument-parser", + from: "1.8.2" + ), ], targets: [ .target( @@ -31,9 +38,19 @@ let package = Package( ), ] ), + .target( + name: "ACKLocalizationCommands", + dependencies: [ + "ACKLocalizationCore", + .product( + name: "ArgumentParser", + package: "swift-argument-parser" + ), + ] + ), .executableTarget( name: "ACKLocalization", - dependencies: ["ACKLocalizationCore"]), + dependencies: ["ACKLocalizationCommands"]), .testTarget( name: "ACKLocalizationCoreTests", dependencies: ["ACKLocalizationCore"]), diff --git a/Sources/ACKLocalization/ACKLocalizationCommand+Main.swift b/Sources/ACKLocalization/ACKLocalizationCommand+Main.swift new file mode 100644 index 0000000..0d03bbc --- /dev/null +++ b/Sources/ACKLocalization/ACKLocalizationCommand+Main.swift @@ -0,0 +1,4 @@ +import ACKLocalizationCommands + +@main +extension ACKLocalizationCommand { } diff --git a/Sources/ACKLocalization/main.swift b/Sources/ACKLocalization/main.swift deleted file mode 100644 index 8b72f69..0000000 --- a/Sources/ACKLocalization/main.swift +++ /dev/null @@ -1,5 +0,0 @@ -import ACKLocalizationCore - -let localization = ACKLocalization() - -await localization.run() diff --git a/Sources/ACKLocalizationCommands/ACKLocalizationCommand.swift b/Sources/ACKLocalizationCommands/ACKLocalizationCommand.swift new file mode 100644 index 0000000..3e5bb91 --- /dev/null +++ b/Sources/ACKLocalizationCommands/ACKLocalizationCommand.swift @@ -0,0 +1,22 @@ +import ACKLocalizationCore +import ArgumentParser + +public struct ACKLocalizationCommand: AsyncParsableCommand { + public static let configuration = CommandConfiguration( + commandName: "localization", + abstract: "Generates localization files from a Google Spreadsheet using `localization.json` in the current directory." + ) + + public init() { } + + public func run() async throws { + let localization = ACKLocalization() + + do { + try await localization.run() + } catch { + // error has already been displayed by `ACKLocalization` + throw ExitCode.failure + } + } +} diff --git a/Sources/ACKLocalizationCore/ACKLocalization.swift b/Sources/ACKLocalizationCore/ACKLocalization.swift index d1488ba..7ff4b0a 100644 --- a/Sources/ACKLocalizationCore/ACKLocalization.swift +++ b/Sources/ACKLocalizationCore/ACKLocalization.swift @@ -27,16 +27,18 @@ public final class ACKLocalization { // MARK: - Public interface /// Main that loads configuration from _localization.json_, fetches access token and loads content of spreadsheet - public func run() async { + /// + /// Errors are displayed before being rethrown + public func run() async throws { do { let config = try loadConfiguration() try await run(configuration: config) } catch let error as LocalizationError { displayError(error) - exit(1) + throw error } catch { print(error) - exit(1) + throw error } } From 4c9cac4159f610f1849a06fdb68811a589cddd39 Mon Sep 17 00:00:00 2001 From: Jakub Olejnik Date: Tue, 29 Sep 2026 01:48:54 +0200 Subject: [PATCH 2/2] =?UTF-8?q?=F0=9F=93=9D=20Add=20PR=20number=20to=20cha?= =?UTF-8?q?ngelog?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index be413c7..eac4cf3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,8 +9,8 @@ ## main ### Changed -- Use [Swift Argument Parser](https://github.com/apple/swift-argument-parser) for command-line handling, the command is available as `ACKLocalizationCommand` in the new `ACKLocalizationCommands` library so it can be embedded in other tools (kudos to @olejnjak) -- `ACKLocalization.run()` no longer calls `exit(1)` on failure, it displays the error and rethrows it instead (kudos to @olejnjak) +- Use [Swift Argument Parser](https://github.com/apple/swift-argument-parser) for command-line handling, the command is available as `ACKLocalizationCommand` in the new `ACKLocalizationCommands` library so it can be embedded in other tools ([#48](https://github.com/AckeeCZ/ACKLocalization/pull/48), kudos to @olejnjak) +- `ACKLocalization.run()` no longer calls `exit(1)` on failure, it displays the error and rethrows it instead ([#48](https://github.com/AckeeCZ/ACKLocalization/pull/48), kudos to @olejnjak) - Replace internal Combine usage with async/await ([#47](https://github.com/AckeeCZ/ACKLocalization/pull/47), kudos to @olejnjak) - Migrate tests to Swift Testing and extend test coverage ([#47](https://github.com/AckeeCZ/ACKLocalization/pull/47), kudos to @olejnjak)