Translation framework use in Swift 6

I’m trying to integrate Apple’s Translation framework in a Swift 6 project with Approachable Concurrency enabled.

I’m following the code here: https://developer.apple.com/documentation/translation/translating-text-within-your-app#Offer-a-custom-translation

And, specifically, inside the following code

.translationTask(configuration) { session in
    do {
        // Use the session the task provides to translate the text.
        let response = try await session.translate(sourceText)


        // Update the view with the translated result.
        targetText = response.targetText
    } catch {
        // Handle any errors.
    }
}

On the try await session.translate(…) line, the compiler complains that “Sending ‘session’ risks causing data races”.

Extended error message:

Sending main actor-isolated 'session' to @concurrent instance method 'translate' risks causing data races between @concurrent and main actor-isolated uses

I’ve downloaded Apple’s sample code (at the top of linked webpage), it compiles fine as-is on Xcode 26.4, but fails with the same error as soon as I switch the Swift Language Mode to Swift 6 in the project.

How can I fix this?

Did you try to wrap the code with a main actor task, as shown below:

.translationTask(configuration) { session in
    Task { @MainActor in
        do {
            // Use the session the task provides to translate the text.
            let response = try await session.translate(sourceText)
            // Update the view with the translated result.
            targetText = response.targetText
        } catch {
            // Handle any errors.
        }
    }
}

This gives the "main actor-isolated 'session'" a main actor context, and so should calm down the error, while translate will still run in the background because it's a "@concurrent instance method".

Best,
——
Ziqiao Chen
 Worldwide Developer Relations.

I have found two possible solutions :

  • either (1) use @preconcurrency import Translation instead of just import Translation, and it works magically ;

  • or (2) mark the translationTask closure as @Sendable, and detach to MainActor when needed :

.translationTask(configuration) { @Sendable session in
    do {
        let responseText = try await session.translate(sourceText).targetText
        await MainActor.run {
            // ... propagate back to UI
        }
    } catch {}
}

I'm not sure which is the best solution: #1 is shorter and cleaner but I don't know how it handles concurrency behind the scenes, #2 is more tedious but the isolation path is more clear.

I just wanted to stop by and drop some links:

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

The error you're encountering is due to Swift's concurrency model introduced in Swift 5.5, which aims to prevent data races by enforcing actor isolation. In Swift 6, these checks have become more stringent, especially when dealing with concurrent and actor-isolated contexts.

The Translation framework's methods, like translate, are likely designed to work concurrently, which means they expect to be called from an actor context. When you pass the session object, which is likely actor-isolated to the main actor (due to how URLSession typically works), to a concurrent method, Swift flags this as a potential data race.

How to Fix the Issue

  1. Make the Session Actor

If possible, wrap the URLSession instance in an actor. This way, all interactions with it are serialized and safe from data races.

actor TranslationSession {
private let session: URLSession

init() {
    self.session = URLSession(configuration: .default)
}

func translate(_ text: String) async throws -> TranslatedText {
    let configuration = TranslationConfiguration(sourceLanguage: .english, targetLanguage: .french)
    return try await session.translate(text, configuration: configuration)
}

}

Then, use this actor in your concurrent context:

await withTaskGroup(of: Void.self) { group in
group.addTask {
    let translationSession = TranslationSession()
    let translatedText = try await translationSession.translate(sourceText)
    targetText = translatedText.targetText
}

}

  1. Explicitly Isolate to Main Actor

If making the session an actor isn't feasible, you can explicitly ensure that the translate call happens on the main actor:

await` withTaskGroup(of: Void.self) { group in
group.addTask {
    let configuration = TranslationConfiguration(sourceLanguage: .english, targetLanguage: .french)
    let response = try await withCheckedContinuation { continuation in
        DispatchQueue.main.async {
            continuation.resume(returning: session.translate(sourceText, configuration: configuration))
        }
    }
    targetText = response.targetText
}
  1. Check for Actor Conformance in Frameworks

Ensure that the Translation framework's methods are correctly annotated for concurrency. If they are not, you might be hitting a limitation where the framework itself doesn't fully conform to Swift's concurrency model yet. In such cases, consider reaching out to Apple or checking for updates to the framework.

(:

By aligning your use of URLSession with Swift's concurrency model, either by making it an actor or ensuring operations occur on the main actor, you can resolve the data race warning and successfully integrate the Translation framework in your Swift 6 project with concurrency enabled.

Translation framework use in Swift 6
 
 
Q