Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions .claude/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.<FileName>.` go to `<FileName>.strings`, plural keys `key##{rule}` go to `.stringsdict`) and writes via `FileSystem`.

Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 ([#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)

Expand Down
11 changes: 10 additions & 1 deletion Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 18 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ let package = Package(
.library(
name: "ACKLocalizationCore",
targets: ["ACKLocalizationCore"]),
.library(
name: "ACKLocalizationCommands",
targets: ["ACKLocalizationCommands"]),
.executable(
name: "ACKLocalization",
targets: ["ACKLocalization"]),
Expand All @@ -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(
Expand All @@ -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"]),
Expand Down
4 changes: 4 additions & 0 deletions Sources/ACKLocalization/ACKLocalizationCommand+Main.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import ACKLocalizationCommands

@main
extension ACKLocalizationCommand { }
5 changes: 0 additions & 5 deletions Sources/ACKLocalization/main.swift

This file was deleted.

22 changes: 22 additions & 0 deletions Sources/ACKLocalizationCommands/ACKLocalizationCommand.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import ACKLocalizationCore
import ArgumentParser
Comment thread
olejnjak marked this conversation as resolved.

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
}
}
}
8 changes: 5 additions & 3 deletions Sources/ACKLocalizationCore/ACKLocalization.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Comment thread
olejnjak marked this conversation as resolved.
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
}
}

Expand Down
Loading