Skip to content

chatoutsidis/XCSKit

XCSKit

XCSKit is a Swift Package for working with Apple's .xcstrings string catalog files through a layered set of modules.

The repository includes a working parser implementation, a representative sample catalog, an automated test suite, and a public software requirements specification that tracks implemented behavior and near-term roadmap items.

Installation

Add XCSKit to a Swift Package Manager project with the repository URL:

dependencies: [
	.package(url: "https://github.com/chatoutsidis/XCSKit.git", from: "0.3.1")
]

Then add whichever product you need to your target dependencies:

.target(
	name: "YourTarget",
	dependencies: [
		.product(name: "XCSParser", package: "XCSKit"),
		.product(name: "XCSValidator", package: "XCSKit"),
		.product(name: "XCSWriter", package: "XCSKit")
	]
)

In Xcode, use File > Add Package Dependencies..., enter the repository URL, and select the products your app or tool imports. Use XCSKit when you want the umbrella import, or choose individual products for narrower module boundaries.

Requirements

Requirement Current Support
Swift tools 6.3
Package manager Swift Package Manager
Runtime dependencies None
Verified CI environment macOS 15 runner with Swift 6.3

The package is Foundation-based and intended for local tooling, server-side utilities, and Apple-platform build workflows. Broader platform minimums should be treated as uncommitted until they are added to the package manifest and CI matrix.

Goals

  • Parse .xcstrings catalogs from Data or file URLs.
  • Model catalog entries, localizations, string units, and variant trees.
  • Model entry metadata including extractionState using a forward-compatible typed representation.
  • Support lookup by source key, locale, and variant selector.
  • Surface malformed catalog structure through deterministic typed errors.
  • Provide read-only analytics helpers for keys, locales, coverage, and translation progress.

Current Scope

The package currently supports:

  • Root catalog fields such as sourceLanguage, strings, and version.
  • Entry metadata including comment, isCommentAutoGenerated, shouldTranslate, and extractionState.
  • Direct localized stringUnit values with translation state and value.
  • Variant trees for dimensions such as plural and device.
  • Exact preservation of localized string values and placeholder tokens such as %@, %lld, and %1$@.

The package still does not include:

  • Multi-file catalog merge workflows.

The repository now also includes separate modules for validation, translation orchestration, and writing so these concerns remain isolated from the parser itself.

Module Layout

The package is organized as one repository with multiple library products:

  • XCSCore: shared domain models such as Catalog, Entry, Localization, and StringUnit.
  • XCSParser: parsing plus fatal structural validation only.
  • XCSValidator: non-mutating semantic validation and finding generation.
  • XCSTranslator: translation orchestration through a provider protocol.
  • XCSWriter: serialization and patch-oriented persistence helpers.
  • XCSAnalytics: read-only catalog reporting and statistics helpers.

This keeps parsing, validation, translation, and writing separate while still sharing the same .xcstrings model and fixtures.

Example Input

The repository includes a sample catalog at docs/example.xcstrings. It demonstrates:

  • Empty entries
  • Simple localized strings
  • Metadata-bearing entries
  • Plural variants
  • Device-specific variants
  • Placeholder-bearing values

Basic Usage

import Foundation
import XCSParser

let data = try Data(contentsOf: catalogURL)
let catalog = try XCSParser.parse(data: data)

let entry = catalog.entry(for: "Add")
let greekValue = catalog
	.localization(forKey: "Add", locale: "el")?
	.stringUnit?
	.value

let extractionState = catalog.entry(for: "spectrum")?.extractionState

Layered Workflow

The parser remains intentionally read-only. A higher-level workflow can compose the new modules without coupling them together:

import Foundation
import XCSParser
import XCSValidator
import XCSTranslator
import XCSWriter

let data = try Data(contentsOf: catalogURL)
let catalog = try XCSParser.parse(data: data)

let findings = XCSValidator.validate(catalog)

if findings.isEmpty {
	let result = try await XCSTranslator.translate(
		catalog,
		targetLocales: ["fr"],
		provider: MyTranslationProvider()
	)

	let updatedData = try XCSWriter.apply(patches: result.patches, to: data)
	try XCSWriter.write(updatedData, to: outputURL)
}

Notes:

  • XCSValidator reports findings and never mutates the catalog.
  • XCSTranslator produces translation patches and does not write files directly.
  • XCSTranslator supports whole-catalog, single-key, batch-key, exact-leaf, and variation-branch translation entry points.
  • XCSWriter serializes full catalogs or applies targeted string patches to existing JSON trees.

Translation Modes

The translator defaults to translating every eligible direct leaf and variation leaf in the selected entries. You can also narrow translation to one exact leaf path or to an entire variation branch.

Full Catalog Translation

Translate every eligible entry and leaf in the catalog:

let fullCatalogResult = try await XCSTranslator.translate(
	catalog,
	targetLocales: ["fr", "de"],
	provider: MyTranslationProvider()
)

Single-Key Translation

Translate every eligible leaf for one entry key:

let singleKeyResult = try await XCSTranslator.translate(
	catalog,
	entryKey: "Add",
	targetLocales: ["fr"],
	provider: MyTranslationProvider()
)

Batch-Key Translation

Translate every eligible leaf for a selected set of keys:

let batchResult = try await XCSTranslator.translate(
	catalog,
	entryKeys: ["Add", "Save"],
	targetLocales: ["fr", "de"],
	provider: MyTranslationProvider()
)

Exact Leaf Translation

Translate only one requested variation leaf, such as the plural=one leaf or the device=mac leaf:

let pluralOneOnlyResult = try await XCSTranslator.translate(
	catalog,
	entryKey: "%lld items",
	targetLocales: ["fr"],
	leafSelection: .exact([
		VariationSelection(dimension: "plural", selector: "one")
	]),
	provider: MyTranslationProvider()
)

let deviceMacOnlyResult = try await XCSTranslator.translate(
	catalog,
	entryKeys: ["Open on", "Tap to see"],
	targetLocales: ["fr"],
	leafSelection: .exact([
		VariationSelection(dimension: "device", selector: "mac")
	]),
	provider: MyTranslationProvider()
)

Plural Branch Translation

Translate every plural leaf for the selected entries while preserving the original plural selector path in each emitted patch:

let pluralBranchResult = try await XCSTranslator.translate(
	catalog,
	entryKey: "%lld items",
	targetLocales: ["fr"],
	leafSelection: .branch(dimension: "plural"),
	provider: MyTranslationProvider()
)

Device Branch Translation

Translate every device leaf for the selected entries, or narrow device translation to a nested prefix such as plural=one:

let deviceBranchResult = try await XCSTranslator.translate(
	catalog,
	entryKey: "Tap to see",
	targetLocales: ["fr"],
	leafSelection: .branch(dimension: "device"),
	provider: MyTranslationProvider()
)

let nestedDeviceBranchResult = try await XCSTranslator.translate(
	catalog,
	entryKey: "Shared Items",
	targetLocales: ["fr"],
	leafSelection: .branch(
		dimension: "device",
		prefix: [VariationSelection(dimension: "plural", selector: "one")]
	),
	provider: MyTranslationProvider()
)

Use .exact([]) if you want to translate only an entry's direct stringUnit leaf and skip all variation leaves.

Analytics

For read-only reporting, use the analytics module on top of the shared catalog model:

import XCSAnalytics

let keys = XCSAnalytics.keys(in: catalog)
let sourceLanguage = XCSAnalytics.sourceLanguage(in: catalog)
let targetLanguages = XCSAnalytics.targetLanguages(in: catalog)
let untranslatedFrenchKeys = XCSAnalytics.untranslatedKeys(in: catalog, locale: "fr")
let helloCoverage = XCSAnalytics.keyCoverage(forKey: "Hello", locale: "ja", in: catalog)
let stats = XCSAnalytics.statistics(in: catalog)

The analytics APIs are read-only and reuse the same catalog model as the parser, validator, translator, and writer layers.

Development

Requirements:

  • Swift 6.3
  • Swift Package Manager

Common commands:

swift build
swift test

Continuous integration:

  • GitHub Actions runs the Swift CI workflow on pull requests, pushes to main, and manual dispatches.
  • The workflow currently uses a macOS runner, installs Swift 6.3, and runs swift build plus swift test.
  • If CI fails, reproduce the same checks locally from the repository root before opening or updating a pull request.

Documentation

License

XCSKit is available under the MIT License. See LICENSE for details.

Status

Current repository status:

  • Package manifest is present in Package.swift and now exposes parser, core, validator, translator, and writer modules.
  • Parser implementation supports core .xcstrings parsing, typed lookup helpers, and forward-compatible extraction-state metadata.
  • Validation, translation orchestration, writing, and analytics are implemented as separate layers over the shared catalog model.
  • The checked-in tests cover parsing, validation, placeholder preservation, representative variant behavior, translation workflows, writer behavior, analytics, and non-functional requirements.
  • The public API is still pre-1.0. Minor releases may refine API names or module boundaries before a stable 1.0 baseline.

If you are contributing implementation work, start with docs/SRS.md. It defines the committed v1 requirements, validation behavior, non-functional constraints, and planned verification coverage.

About

Swift package for parsing, validating, translating, writing, and analyzing Apple .xcstrings string catalog files.

Topics

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages