AVIV Product & Tech Blog

Learn more about how tech, product and design people at AVIV build and operate the systems and products to unlock everyone’s perfect place.

iOS 18 — Apple's Translation API

activesludge
AVIV Product & Tech Blog
11 min readNov 19, 2024

--

Say goodbye to copy-pasting text between apps!

With iOS 18, Apple’s new Translation API brings translation seamlessly into the user experience.

Illustration by vectorjuice at freepik

Users do not have to copy and paste a text from an app to a translation apps anymore. They will be able to translate a text directly within the app elegantly.

Moreover, they'll be able to replace any text in the app. This includes any field or label. Sequentially or all at once.

I’ll walk you through some experiments with this powerful API and showcase a demo app that illustrates how some made up real-world scenarios can benefit from these features.

We can demonstrate the APIs capabilities in three main categories:

System UI

Using Apple native tools to translate text in Translation in-app popup and optionally replace the original text in the app.

  • Overlay Translation
  • Replace Translation

Custom UI

Translate a text or a batch of those and tailor your UI as you see fit.

  • Single String
  • Batch of Strings
  • Batch as a sequence

Availability

Checks the availability of translations based on user preferences, device settings, and installed language packs.

  • Language availability
  • Prepare for Translation

Plus, I'll share with you what you should expect and what I encountered unexpectedly.

Let's jump in.

Requirements

  • A real device (Translation API is not supported on simulated devices. Also not on Preview)
  • iOS 18.0 +
  • Xcode 16.0 +
  • Apple's Translate app has to be installed on the device. Since iOS 14, it's installed automatically.

Demo project

Here you can download the demo app and run it on Xcode.

Features are developed using both just a view and with a view model to provide flexibility on implementation.

https://github.com/Aviv-public/apples-translation-api-demo

Translation API Demo

System UI

Overlay Translation

  • Opens an overlay of Apple’s Translate app within our app.
  • Just like copying and pasting a text to the Translate app.
  • User can pick any source and target language they want in the overlay sheet.

In this example, we showcase a legal disclaimer page. Let's pretend that, to comply with local law, the particular disclaimer below must be displayed in the local language. However, to simplify the experience for our international users, we also offer a convenient button that provides a quick translation, allowing users to easily understand the content in their preferred language

A legal disclaimer page that must be presented in German, due to legal reasons.

struct LegalDisclaimerDemoView: View {
let legalTextTitle = "Nutzungsbedingungen"
let legalText = "Diese App dient ausschließlich ..."

var textToBeTranslated: String {
legalTextTitle + "\n" + legalText
}

@State var isTranslationOverlayPresented = false

var body: some View {
...

Text(verbatim: legalText)
// Presents the sheet binding it to a boolean
// just like presenting a sheet
// Add the source String as the text argument.
.translationPresentation(
isPresented: $isTranslationOverlayPresented,
text: textToBeTranslated
)

...
.overlay(alignment: .topTrailing) {
Button {
// Show translate overlay
isTranslationOverlayPresented = true
} label: {
Image(systemName: "translate")
}
}
...
}
}

Replace Translation

  • Opens an overlay of Apple’s Translate app within our app. Same as the prior feature.
  • User can pick any source and target language they want in the overlay sheet.
  • Now presents an option called “Replace with Translation” to replace the text within the app.
  • Replacing the translated String is up to the developer.

In this example, we present a real estate property detail page. The description was filled in French by the seller. We provide a button to see a translation of this description, and elegantly replace it inside of our app. Notice how the description turns into English.

A real estate property detail page with a description that has been filled in French.
struct PropertyDetailDemoView: View {
...

@State private var isTranslationOverlayPresented = false

var body: some View {
...
.translationPresentation(
isPresented: $isTranslationOverlayPresented,
text: viewModel.property.description
) { translatedText in
...
// Replace the translatedText here
viewModel.propertyDescription = translatedText
}
...
Button {
isTranslationOverlayPresented = true
} label: {
Image(systemName: "translate")
}
}
}

Custom UI

Doesn’t present and overlay view from the system. User doesn’t have to leave the app. We have the ultimate freedom to display translations.

  • It’s up to developer to display the translation.
  • It’s technically a Task. Which means it’s asynchronous. Has a delay, and can fail.

The API works with a couple of extra objects:

TranslationSession

  • A class that performs translations between a pair of languages.
  • It uses a configuration to define the source and target languages.
  • We obtain an instance of this class inside the closure by adding the view modifier .translationTask(_:action:) to a SwiftUI view.
  • It contains the methods we need to translate a source text.
  • All translations using the TranslationSession class are processed on the user’s device. Apple may collect API usage and performance metrics including the app bundle ID and the original and translated language, but this data does not include the original or translated content.

TranslationSession.Configuration

  • Specifies the source and target languages to use in a translation session. Default initialisation allows system to detect source and target language automatically.
  • When you pass this configuration into the .translationTask(_:action:) function, the framework uses the configuration you initialised for the translation.

Locale.Language

  • A type that represents a language, as used in a locale.

Single String

  • Pass the text and receive a response that contains translated text.

Here, we have a contact form to reach out to a seller. The user wrote their message in English. We give them a kind reminder saying that the seller of the property is from Italy. Sending the message in Italian may help them in their effort to get the best deal. They see the translation in a sheet, if they use it, the app replaces the message with the translated one.

A contact form to reach out to a property seller. The page offers the user to translate their message to Italian.
struct ContactSellerView: View {
...
@State private var message: String =
"""
Hello,
I'm interested in your property. I like what you did with the interior design. I love the neighborhood and the location. But I'm looking for an at least 10% discount on the deal. Is it acceptable to you? Please let me know.
Thanks!
"""
...

@State private var configuration: TranslationSession.Configuration?
@State private var translatedMessage: String = ""
@State private var isTranslationSheetPresent: Bool = false

var body: some View {
...

Button {
triggerTranslation()
} label: {
HStack {
Text("See how it looks in 🇮🇹")
}
}
// Once the configuration property detects a change,
// it triggers a new session.
.translationTask(configuration) { session in
do {
// Use the session the task provides to translate the text.
let response = try await session.translate(message)
// Update the view with the translated result.
translatedMessage = response.targetText
isTranslationSheetPresent = true
} catch {
// Handle any errors.
}
}
...
}

private func triggerTranslation() {
guard configuration == nil else {
// Call .invalidate() method to trigger the translation again
// with the same configuration instance
configuration?.invalidate()
return
}

// Create a new configuration for the translation session.
// This configuration will target Italian as the target language.
configuration = .init(target: .init(identifier: "it"))
}
}

Batch of Strings

  • Send an array of String in order, receive an array of translated String in the same order.

Here's a survey for a property detail page. The real estate agency is curious about how satisfied was the interested party about the details. The survey statements were provided by the agent, so they are in Dutch. We provide them a way to translate all those statements. Once the user taps on the translate button, all statements turn into the language of users location, in this case German.

Property detail survey in Dutch.
import SwiftUI
import Translation

struct SurveyPageView: View {
// Statement about the satisfaction on the property detail page in Dutch
@State var statements = [
"De details van het eigendom waren duidelijk.",
"De foto’s van het eigendom waren van goede kwaliteit.",
...
]

@State private var configuration: TranslationSession.Configuration?

var body: some View {
...

Button {
triggerTranslation()
} label: {
Image(systemName: "translate")
}

...

.translationTask(configuration) { session in
await translateAllAtOnce(using: session)
}

...
}
}

...

private func triggerTranslation() {
guard configuration == nil else {
configuration?.invalidate()
return
}

// Default initializer allows device to detect the source language
// and the target language.
// Target language is based on user's region by default.
configuration = .init()
}

private func translateAllAtOnce(using session: TranslationSession) async {
// Translate all statements at once
Task { @MainActor in
let requests: [TranslationSession.Request] = statements.map {
// Map each item into a request.
TranslationSession.Request(sourceText: $0)
}

do {
let responses = try await session.translations(from: requests)
withAnimation {
statements = responses.map {
// Update each item with the translated result.
$0.targetText
}

...
}
} catch {
// Handle any errors.
}
}
}
}

Batch as a sequence

  • An array of “translation requests” can be send sequentially. Then the result can be processed as they are being received.
  • Order of receiving is not guaranteed. But can be tracked with index using clientIdentifier property.
  • Can be useful for translating a set of very large texts.

Here we have an agents page and reviews given by customers. They're in French. When the blue button is tapped, we translate every review one by one to the primary language, which is German. When it's tapped again they're translated into English. which is the second preferred language of user's device.

An agents reviews in French. They’re being translated into German, then English.
struct RealEstateAgentProfileView: View {
...
// Customer reviews with dates and reviewer names
@State var reviews: [Review] = .reviews

...

@State private var configuration: TranslationSession.Configuration?

var body: some View {
...
Button {
triggerTranslation()
} label: {
Image(systemName: "translate")
}
...
.translationTask(configuration) { session in
await translateSequence(session)
}
...
}

private func triggerTranslation() {
guard configuration == nil else {
configuration?.invalidate()
return
}

// In default, source language will be detected as French
// since the review texts are in French.
// Target language is user's region, in this case German.
// When same configuration is used, then the source language is German,
// and the target language will the user's prefered language,
// in this case English.
configuration = .init()
}

private func translateSequence(_ session: TranslationSession) async {
Task { @MainActor in
// Create an array of requests. Use the index of the review array
// as the client identifier.
let requests: [TranslationSession.Request] = reviews.enumerated().map { (index, review) in
.init(sourceText: review.text, clientIdentifier: "\(index)")
}

do {
// Translate the batch of requests.
// For each response received, update the corresponding review in the array.
for try await response in session.translate(batch: requests) {
guard let index = Int(response.clientIdentifier ?? "") else { continue }
withAnimation {
// The client identifier is used to match the response to the original reviews
reviews[index].text = response.targetText
}
}
} catch {
// Handle error
}
}
}
}

Availability

Available languages

You can see the availability of languages the API offers. LanguageAvailability().status(_ from: _to:) checks to see if a specific language pairing is installed and ready for translation.

  • .installed supports, has been downloaded and made ready for use in a translation.
  • .supported supports but can’t yet use. Only after it downloads and installs.
  • .unsupported doesn’t support.
A page that displays the availability of a language pairing.
@Observable
class LanguageAvailabilityViewModel {
var isTranslationSupported: Bool?
...

func checkLanguageSupport(from source: Locale.Language, to target: Locale.Language) async {
let availability = LanguageAvailability()
let status = await availability.status(from: source, to: target)

switch status {
case .installed, .supported:
isTranslationSupported = true
case .unsupported:
isTranslationSupported = false
@unknown default:
print("Not supported")
}
}
}

Prepare languages

  • Displays a sheet asking the user’s permission to start downloading the language pairing. So that the above translation features can be ready from the get going.
  • Useful to prepare the device for translations.
  • If user is faced with any of above features before their device has the necessary language packs, the device will automatically ask to download languages.
A page where user can prepare the device for In-App translations.
struct PrepareTranslationView: View {
@State private var configuration = TranslationSession.Configuration(
source: Locale.Language(identifier: "pt_BR"),
target: Locale.Language(identifier: "ko_KR")
)

@State private var buttonTapped = false

var body: some View {
...
Button(
action: {
configuration.invalidate()
buttonTapped = true
}
) {
Text("Download language packs")
...
}
...
.translationTask(configuration) { session in
if buttonTapped {
do {
// This will present the sheet to
// download the required language packs.
try await session.prepareTranslation()
} catch {
// Handle any errors.
}
}
}
..
}
...
}

In default configuration, …

  • If the language of the source text is detected to be the user's region, then the target defaults to device’s preferred language.
  • If the source is not the same with the region, the target defaults to the region’s language.
  • If the source and target language pairing isn’t available on the device, user will be prompted with an overlay asking to download it.
  • If the translation intend is unclear from the APIs perspective, user will be prompted with an overlay asking to pick a source language from the list.

It was unexpected when I saw, that …

  • When the Translate app isn't installed on the device, features do not work as expected. For instance, when you try to use Custom UI features, the API throws error. But System UI features work just fine. So, Translate app is better be installed on user's device, or you may face with unexpected behaviours.
  • There is no way to check beforehand, whether the user has the Translation app on their device or not.
  • In default, if the target isn't specified in the configuration, target language is set to the device's region. I would expect it to be the devices preferred language, which wasn't the case.

User's Permission …

Is required by Apple’s Translate app (not the app utilising the Translation API) for the first time:

  • to read user input for translations, and
  • to download source and target language when translation is being used.

Advantages

  • Translates any text in the app.
  • Very easy to use and implement.
  • Translation are done on device. So, it's more secure and apart from downloading language pairings does not require constant internet connection.
  • You can expand and limit available languages in your app.
  • A large set of language availability.
  • Emojis do not distract translations.
  • Apple’s own API. Support in future is pretty much guaranteed.
  • Already downloaded languages can be present in the device and can be utilised by the API.
  • The API is smart enough to determine source and target languages, if a specific configuration isn’t passed as an argument.

Disadvantages

  • Can redirect user out of your apps.
  • Translations are provided by Apple. No any other translation API can be injected.
  • The algorithm of the translation is a black box. You will have no control over the translated text. They can get wrong.
  • Only available after iOS 18. No backward availability.
  • Internet connection is required for the first usage. Language pairings have to be downloaded.
  • Custom UI features do not work without the Translate app.

Conclusion

Easy to implement. Definitely an easy win for any text in your apps that get translated frequently by users. This can be a great addition to any app.

I hope that this article will be useful to you.

Have a great one, cheers! ✌️

--

--

AVIV Product & Tech Blog
AVIV Product & Tech Blog

Published in AVIV Product & Tech Blog

Learn more about how tech, product and design people at AVIV build and operate the systems and products to unlock everyone’s perfect place.

activesludge
activesludge

Written by activesludge

Software Engineer. Mostly iOS.

No responses yet