<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>UIKit » MszPro・株式会社Smartソフト</title>
	<atom:link href="https://mszpro.com/category/uikit/feed" rel="self" type="application/rss+xml" />
	<link>https://mszpro.com</link>
	<description>iOS VisionOS SwiftUI Programming Blog. Dream it, Chase it, Code it.</description>
	<lastBuildDate>Mon, 16 Dec 2024 12:33:28 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.8.1</generator>

<image>
	<url>https://static-assets.mszpro.com/2024/12/cropped-Unknown-32x32.webp</url>
	<title>UIKit » MszPro・株式会社Smartソフト</title>
	<link>https://mszpro.com</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>Free translation API — using the iOS 18&#8217;s system Translation framework in your app (SwiftUI or UIKit)</title>
		<link>https://mszpro.com/ios-system-translate</link>
		
		<dc:creator><![CDATA[msz]]></dc:creator>
		<pubDate>Mon, 16 Dec 2024 12:26:05 +0000</pubDate>
				<category><![CDATA[iOS 18]]></category>
		<category><![CDATA[SwiftUI]]></category>
		<category><![CDATA[UIKit]]></category>
		<guid isPermaLink="false">https://mszpro.com/?p=486</guid>

					<description><![CDATA[<p>If you offer contents within your app and are targeting users around the world, you probably want translation feature. User submitted texts can be in all kinds of languages, and providing a translation feature will enrich your user experience. For example, in my Mastodon, Misskey, Bluesky, Nostr all in one client SoraSNS, I used the [&#8230;]</p>
<p>The post <a href="https://mszpro.com/ios-system-translate">Free translation API — using the iOS 18’s system Translation framework in your app (SwiftUI or UIKit)</a> first appeared on <a href="https://mszpro.com">MszPro・株式会社Smartソフト</a>.</p>]]></description>
										<content:encoded><![CDATA[<figure class="wp-block-image"><img fetchpriority="high" decoding="async" width="1400" height="457" src="https://static-assets.mszpro.com/2024/12/1uomB_nQRMRCAAHznVcfAEA@2x.png" alt="" class="wp-image-495" srcset="https://static-assets.mszpro.com/2024/12/1uomB_nQRMRCAAHznVcfAEA@2x-300x98.png 300w, https://static-assets.mszpro.com/2024/12/1uomB_nQRMRCAAHznVcfAEA@2x-1024x334.png 1024w, https://static-assets.mszpro.com/2024/12/1uomB_nQRMRCAAHznVcfAEA@2x-768x251.png 768w, https://static-assets.mszpro.com/2024/12/1uomB_nQRMRCAAHznVcfAEA@2x.png 1400w" sizes="(max-width: 1400px) 100vw, 1400px" /><figcaption class="wp-element-caption">Photo from Apple documentation</figcaption></figure>



<p id="788b">If you offer contents within your app and are targeting users around the world, you probably want translation feature. User submitted texts can be in all kinds of languages, and providing a translation feature will enrich your user experience.</p>



<p id="284e">For example, in my Mastodon, Misskey, Bluesky, Nostr all in one client SoraSNS, I used the similar technique described in this article for free translation API.</p>



<p id="6f54">Using online API provided by companies like Google or DeepL can incur a fee, which indie developers like myself find it difficult.</p>



<h1 class="wp-block-heading" id="b45f">The answer: The Translation Framework</h1>



<p id="8c33">Ever since iOS 14 in 2020, Apple has introduced the system Translation app. It is a very simple yet elegant app with just a single text box. What’s more, Apple’s translation app allows you to download for local translation as well.</p>



<figure class="wp-block-image"><img decoding="async" width="1400" height="829" src="https://static-assets.mszpro.com/2024/12/1bYNmmm5hc5RIVzl7NtkNyA.png" alt="" class="wp-image-490" srcset="https://static-assets.mszpro.com/2024/12/1bYNmmm5hc5RIVzl7NtkNyA-300x178.png 300w, https://static-assets.mszpro.com/2024/12/1bYNmmm5hc5RIVzl7NtkNyA-1024x606.png 1024w, https://static-assets.mszpro.com/2024/12/1bYNmmm5hc5RIVzl7NtkNyA-768x455.png 768w, https://static-assets.mszpro.com/2024/12/1bYNmmm5hc5RIVzl7NtkNyA.png 1400w" sizes="(max-width: 1400px) 100vw, 1400px" /></figure>



<p id="0e39">In iOS 18, Apple has opened the translation API, so your app can use it to translate content.</p>



<p id="4112">Enough story telling. Let’s get started!</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p id="e2fc"><strong>Notice: You have to test these APIs on real devices. The Translation feature does not work in simulator!<em>&nbsp;(wired-ly sometimes the pre-build UI does work in simulator LOL)</em></strong></p>
</blockquote>



<h1 class="wp-block-heading" id="3e18">Present pre-built translation UI in SwiftUI</h1>



<p id="0c87">Your SwiftUI app can present a pre-built Apple translate app popup, within your own app.</p>



<p id="bf35">First, import the&nbsp;<code>Translation</code>&nbsp;framework. You can also use the&nbsp;<code>#if canImport(Translation)</code>statement to check if the system supports it. For example, if the user is running iOS 18 and up, provide the translation feature; if the user is running iOS 17 and below, fallback to an online translation service or hide the translation button.</p>



<pre class="wp-block-code"><code>#if canImport(Translation)<br>import Translation<br>#endif</code></pre>



<p id="dd19">Then, create a&nbsp;<code>@State</code>&nbsp;variable that controls when the translation dialog is shown. Initially, it is&nbsp;<code>false</code>&nbsp;and you set it to&nbsp;<code>true</code>&nbsp;once you want to present the translated result:</p>



<pre class="wp-block-code"><code>@State private var isTranslationShown: Bool = false</code></pre>



<p id="6e30">Then, attach the&nbsp;<code>translationPresentation</code>&nbsp;view modifier to your view component, with the&nbsp;<code>isPresented</code>&nbsp;parameter set to the binding of the variable that controls whether it is shown or not, and the&nbsp;<code>text</code>&nbsp;parameter set to the text you want to translate.</p>



<pre class="wp-block-code"><code>Form {<br>    // ... //<br>}<br>#if canImport(Translation)<br>.translationPresentation(isPresented: $isTranslationShown,<br>                         text: self.sourceText)<br>#endif</code></pre>



<p id="a752">Here is the full example code:</p>



<pre class="wp-block-code"><code>import SwiftUI<br><br>#if canImport(Translation)<br>import Translation<br>#endif<br><br>struct PopupTranslation: View {<br>    <br>    @State private var sourceText = "Hello, World! This is a test."<br>    <br>    @State private var isTranslationShown: Bool = false<br>    <br>    var body: some View {<br>        <br>        NavigationStack {<br>            Form {<br>                <br>                Section {<br>                    Label("Source text", systemImage: "globe")<br>                    <br>                    TextField("What do you want to translate?",<br>                              text: $sourceText,<br>                              axis: .vertical)<br>                }<br>                <br>            }<br>            .toolbar {<br>                ToolbarItem(placement: .topBarTrailing) {<br>                    Button("Translate") {<br>                        self.isTranslationShown = true<br>                    }<br>                }<br>            }<br>#if canImport(Translation)<br>            .translationPresentation(isPresented: $isTranslationShown,<br>                                     text: self.sourceText)<br>#endif<br>        }<br>        <br>    }<br>    <br>}<br><br>#Preview {<br>    PopupTranslation()<br>}</code></pre>



<p id="53ca">Now, if you run it, you will get a simple translation popup when the user presses the translate button:</p>



<figure class="wp-block-image"><img decoding="async" width="295" height="640" src="https://static-assets.mszpro.com/2024/12/1qErPBfjANhIwW41IJD8_dQ.gif" alt="" class="wp-image-494"/></figure>



<p id="cf9e">You can also add a button within the present popup so user can quickly provide the translated text to your app:</p>



<pre class="wp-block-code"><code>//<br>//  PopupTranslation.swift<br>//  iOSTranslationVideo<br>//<br>//  Created by msz on 2024/12/01.<br>//<br><br>import SwiftUI<br><br>#if canImport(Translation)<br>import Translation<br>#endif<br><br>struct PopupTranslation: View {<br>    <br>    @State private var sourceText = "Hello, World!"<br>+    @State private var targetText = ""<br>    <br>    @State private var isTranslationShown: Bool = false<br>    <br>    var body: some View {<br>        <br>        NavigationStack {<br>            Form {<br>                <br>                Section {<br>                    Label("Source text", systemImage: "globe")<br>                    <br>                    TextField("What do you want to translate?",<br>                              text: $sourceText,<br>                              axis: .vertical)<br>                }<br>                <br>+                Section {<br>+                    Label("Translated text", systemImage: "globe")                    <br>+                    Text(targetText)<br>+                }<br>                <br>            }<br>            .toolbar {<br>                ToolbarItem(placement: .topBarTrailing) {<br>                    Button("Translate") {<br>                        self.isTranslationShown = true<br>                    }<br>                }<br>            }<br>#if canImport(Translation)<br>            .translationPresentation(isPresented: $isTranslationShown,<br>                                     text: self.sourceText)<br>+            { newString in<br>+                self.targetText = newString<br>+            }<br>#endif<br>        }<br>        <br>    }<br>    <br>}<br><br>#Preview {<br>    PopupTranslation()<br>}</code></pre>



<p id="04c7">In the above code, we added a code block for the&nbsp;<code>translationPresentation</code>view modifier. We then set the result text to the&nbsp;<code>targetText</code>&nbsp;variable of the app.</p>



<p id="5309">This will not automatically provide the translated result to the app. Instead, user will see a button called&nbsp;<code>Replace with translation</code></p>



<figure class="wp-block-image"><img loading="lazy" decoding="async" width="295" height="640" src="https://static-assets.mszpro.com/2024/12/1c8eulmzf4daXMw3Rriq0zA.gif" alt="" class="wp-image-491"/></figure>



<p id="0cb6">The benefit of the above is that it uses system pre-designed UI, and you do not need to provide the source and target language, all are auto configured and detected by the system.</p>



<p id="79f3">But if you want to get the translated result programmatically, read on!</p>



<h1 class="wp-block-heading" id="fd26">Check if language is supported by iOS system</h1>



<p id="bffd">iOS system offers translation service for some popular languages. To check which language pair is available, you can use</p>



<pre class="wp-block-code"><code>func checkSpecificLanguagePairs() async {<br>    let availability = LanguageAvailability()<br>    <br>    // English to Japanese<br>    let english = Locale.Language(identifier: "en")<br>    let japanese = Locale.Language(identifier: "ja")<br>    let statusEnJa = await availability.status(from: english, to: japanese)<br>    print("English to Japanese: \(statusDescription(statusEnJa))")<br>    <br>    // English to Simplified Chinese<br>    let chinese = Locale.Language(identifier: "zh-Hans")<br>    let statusEnCh = await availability.status(from: english, to: chinese)<br>    print("English to Simplified Chinese: \(statusDescription(statusEnCh))")<br>    <br>    <br>    // English to German<br>    let german = Locale.Language(identifier: "de")<br>    let statusEnDe = await availability.status(from: english, to: german)<br>    print("English to German: \(statusDescription(statusEnDe))")<br>}<br><br>// Helper function to describe the status<br>func statusDescription(_ status: LanguageAvailability.Status) -&gt; String {<br>    switch status {<br>        case .installed:<br>            return "Translation installed and ready to use."<br>        case .supported:<br>            return "Translation supported but requires download of translation model."<br>        case .unsupported:<br>            return "Translation unsupported between the given language pair."<br>        @unknown default:<br>            return "Unknown status"<br>    }<br>}</code></pre>



<p id="60a5">Now, if the returned status is&nbsp;<code>installed</code>, it means you can translate normally. If it is&nbsp;<code>unsupported</code>&nbsp;, it means iOS does not support translation of that language peer. If it is&nbsp;<code>supported</code>&nbsp;but not&nbsp;<code>installed</code>&nbsp;it means the iOS system has not yet downloaded the files necessary for this translation.</p>



<p id="176e">These translation model files only need to be downloaded once every device.</p>



<h2 class="wp-block-heading" id="e736">Bonus topic: checking the language of a text</h2>



<p id="3994">Your app can actually detect the language of a given text by using&nbsp;<code>NaturalLanguage</code>&nbsp;framework</p>



<pre class="wp-block-code"><code>import NaturalLanguage<br><br>static func detectLanguage(for string: String) -&gt; String? {<br>    let recognizer = NLLanguageRecognizer()<br>    recognizer.processString(string)<br>    guard let languageCode = recognizer.dominantLanguage?.rawValue else {<br>        return nil<br>    }<br>    return languageCode<br>}</code></pre>



<h1 class="wp-block-heading" id="86af">Download translation models</h1>



<p id="d05b">You can ask iOS to present the download dialog for the translation files.</p>



<pre class="wp-block-code"><code>struct TranslationModelDownloader: View {<br>    <br>    var configuration: TranslationSession.Configuration {<br>        TranslationSession.Configuration(<br>            source: Locale.Language(identifier: "en"),<br>            target: Locale.Language(identifier: "ja")<br>        )<br>    }<br>    <br>    var body: some View {<br>        NavigationView {<br>            Text("Download translation files between \(configuration.source?.minimalIdentifier ?? "?") and \(configuration.target?.minimalIdentifier ?? "?")")<br>            .translationTask(configuration) { session in<br>                do {<br>                    try await session.prepareTranslation()<br>                } catch {<br>                    // Handle any errors.<br>                    print("Error downloading translation: \(error)")<br>                }<br>            }<br>        }<br>    }<br>}</code></pre>



<p id="f2a5">In the above code, you will attach the&nbsp;<code>translationTask</code>&nbsp;view modifier to your SwiftUI view. You will define the language configuration (source language and target language) within the&nbsp;<code>configuration</code>&nbsp;parameter. Then, you will call&nbsp;<code>session.prepareTranslation()</code>&nbsp;within the translation task view modifier.</p>



<p id="cd84">When the view shows up, it will present a system dialog for downloading translation files.</p>



<figure class="wp-block-image"><img loading="lazy" decoding="async" width="600" height="1303" src="https://static-assets.mszpro.com/2024/12/1ySRkfOVcJTaSARhg5cwrvw.gif" alt="" class="wp-image-492"/></figure>



<p id="c5c0">Here is a full SwiftUI demo code for checking available languages and downloading the models:</p>



<pre class="wp-block-code"><code>//<br>//  LanguageAvailabilityChecker.swift<br>//  iOSTranslationVideo<br>//<br>//  Created by msz on 2024/12/01.<br>//<br><br>import SwiftUI<br>import Translation<br><br>fileprivate class ViewModel: ObservableObject {<br>    @Published var sourceLanguage: Locale.Language = Locale.current.language<br>    @Published var targetLanguage: Locale.Language = Locale.current.language<br>    <br>    @Published var languageStatus: LanguageAvailability.Status = .unsupported<br>    <br>    @Published var sourceFilter: String = "English"<br>    @Published var targetFilter: String = "German"<br>    <br>    let languages: &#91;Locale.Language]<br>    <br>    init() {<br>        // Initialize the list of available languages<br>        let languageCodes = Locale.LanguageCode.isoLanguageCodes<br>        self.languages = languageCodes.compactMap { Locale.Language(languageCode: $0) }<br>    }<br>    <br>    func displayName(for language: Locale.Language) -&gt; String {<br>        guard let languageCode = language.languageCode?.identifier else {<br>            return language.maximalIdentifier<br>        }<br>        return Locale.current.localizedString(forLanguageCode: languageCode) ?? languageCode<br>    }<br>    <br>    var filteredSourceLanguages: &#91;Locale.Language] {<br>        if sourceFilter.isEmpty {<br>            return languages<br>        } else {<br>            return languages.filter {<br>                displayName(for: $0).localizedCaseInsensitiveContains(sourceFilter)<br>            }<br>        }<br>    }<br>    <br>    var filteredTargetLanguages: &#91;Locale.Language] {<br>        if targetFilter.isEmpty {<br>            return languages<br>        } else {<br>            return languages.filter {<br>                displayName(for: $0).localizedCaseInsensitiveContains(targetFilter)<br>            }<br>        }<br>    }<br>    <br>    func checkLanguageSupport() async {<br>        let availability = LanguageAvailability()<br>        let status = await availability.status(from: sourceLanguage, to: targetLanguage)<br>        <br>        DispatchQueue.main.async {<br>            self.languageStatus = status<br>        }<br>    }<br>}<br><br><br>struct LanguageAvailabilityChecker: View {<br>    @StateObject fileprivate var viewModel = ViewModel()<br>    <br>    var body: some View {<br>        Form {<br>            // Source Language Section<br>            Section("Source Language") {<br>                TextField("Filter languages", text: $viewModel.sourceFilter)<br>                    .padding(.vertical, 4)<br>                <br>                Picker("Select Source Language", selection: $viewModel.sourceLanguage) {<br>                    ForEach(viewModel.filteredSourceLanguages, id: \.maximalIdentifier) { language in<br>                        Button {} label: {<br>                            Text(viewModel.displayName(for: language))<br>                            Text(language.minimalIdentifier)<br>                        }<br>                        .tag(language)<br>                    }<br>                }<br>                .disabled(viewModel.filteredSourceLanguages.isEmpty)<br>                .onChange(of: viewModel.sourceLanguage) { _, _ in<br>                    Task {<br>                        await viewModel.checkLanguageSupport()<br>                    }<br>                }<br>            }<br>            <br>            // Target Language Section<br>            Section("Target Language") {<br>                TextField("Filter languages", text: $viewModel.targetFilter)<br>                <br>                Picker("Select Target Language", selection: $viewModel.targetLanguage) {<br>                    ForEach(viewModel.filteredTargetLanguages, id: \.maximalIdentifier) { language in<br>                        Button {} label: {<br>                            Text(viewModel.displayName(for: language))<br>                            Text(language.minimalIdentifier)<br>                        }<br>                        .tag(language)<br>                    }<br>                }<br>                .disabled(viewModel.filteredTargetLanguages.isEmpty)<br>                .onChange(of: viewModel.targetLanguage) { _, _ in<br>                    Task {<br>                        await viewModel.checkLanguageSupport()<br>                    }<br>                }<br>            }<br>            <br>            // Status Section<br>            Section {<br>                if viewModel.languageStatus == .installed {<br>                    Text("✅ Translation Installed")<br>                        .foregroundColor(.green)<br>                } else if viewModel.languageStatus == .supported {<br>                    Text("⬇️ Translation Available to Download")<br>                        .foregroundColor(.orange)<br>                } else {<br>                    Text("❌ Translation Not Supported")<br>                        .foregroundColor(.red)<br>                }<br>            }<br>            <br>            // Download Button Section<br>            if viewModel.languageStatus == .supported {<br>                NavigationLink("Download") {<br>                    TranslationModelDownloader(sourceLanguage: viewModel.sourceLanguage,<br>                                               targetLanguage: viewModel.targetLanguage)<br>                }<br>            }<br>        }<br>        .navigationTitle("Language Selector")<br>        .onAppear {<br>            Task {<br>                await viewModel.checkLanguageSupport()<br>            }<br>        }<br>    }<br>}<br><br>#Preview {<br>    LanguageAvailabilityChecker()<br>}<br><br>struct TranslationModelDownloader: View {<br>    <br>    var configuration: TranslationSession.Configuration<br>    <br>    init(sourceLanguage: Locale.Language, targetLanguage: Locale.Language) {<br>        self.configuration = TranslationSession.Configuration(source: sourceLanguage, target: targetLanguage)<br>    }<br>    <br>    var body: some View {<br>        NavigationView {<br>            Text("Download translation files between \(configuration.source?.minimalIdentifier ?? "?") and \(configuration.target?.minimalIdentifier ?? "?")")<br>            .translationTask(configuration) { session in<br>                do {<br>                    try await session.prepareTranslation()<br>                } catch {<br>                    // Handle any errors.<br>                    print("Error downloading translation: \(error)")<br>                }<br>            }<br>        }<br>    }<br>}</code></pre>



<h1 class="wp-block-heading" id="5eab">Get translated result programmatically</h1>



<p id="70e9">If you want to show the translated result within your app’s view. You can use a translation session.</p>



<p id="18e5">To get the translation result programmatically, you still need a SwiftUI shown on screen.</p>



<p id="f0d6">First, set up the variables to store the text to translate, an optional translation configuration, and one to store the result:</p>



<pre class="wp-block-code"><code>@State private var textToTranslate: String?<br>@State private var translationConfiguration: TranslationSession.Configuration?<br>@State private var translationResult: String?</code></pre>



<p id="91b2">Then, add a&nbsp;<code>translationTask</code>&nbsp;view modifier:</p>



<pre class="wp-block-code"><code>.translationTask(translationConfiguration) { session in<br>    do {<br>        guard let textToTranslate else { return }<br>        let response = try await session.translate(textToTranslate)<br>        self.translationResult = response.targetText<br>    } catch {<br>        print("Error: \(error)")<br>    }<br>}</code></pre>



<p id="0ee4">Now, when you are ready to translate (for example, you have fetched the text for translation, you can set a value to the variables). For example, in the below code, we fetch the content of a web page and translate it to Japanese.</p>



<pre class="wp-block-code"><code>let (data, _) = try await URLSession.shared.data(from: URL(string: "https://raw.githubusercontent.com/swiftlang/swift/refs/heads/main/.github/ISSUE_TEMPLATE/task.yml")!)<br>guard let webPageContent = String(data: data, encoding: .utf8) else { return }<br>// start a translation to Japanese<br>self.textToTranslate = webPageContent<br>self.translationConfiguration = .init(target: .init(identifier: "ja"))</code></pre>



<p id="9078">Notice that the&nbsp;<code>self.translationConfiguration</code>&nbsp;can take no parameters, just the source language code, just the target language code, or both. For the parameters that you do not provide, the system will infer automatically based on user configuration.</p>



<p id="b5de">It is recommended that you define the source and target language by yourself.</p>



<p id="8088">Here is the complete code:</p>



<pre class="wp-block-code"><code>import SwiftUI<br>import Translation<br><br>struct CustomTranslation: View {<br>    <br>    @State private var textToTranslate: String?<br>    @State private var translationConfiguration: TranslationSession.Configuration?<br>    @State private var translationResult: String?<br>    <br>    var body: some View {<br>        Form {<br>            <br>            Section("Original text") {<br>                if let textToTranslate {<br>                    Text(textToTranslate)<br>                }<br>            }<br>            <br>            Section("Translated text") {<br>                if let translationResult {<br>                    Text(translationResult)<br>                }<br>            }<br>            <br>        }<br>        .translationTask(translationConfiguration) { session in<br>            do {<br>                guard let textToTranslate else { return }<br>                let response = try await session.translate(textToTranslate)<br>                self.translationResult = response.targetText<br>            } catch {<br>                print("Error: \(error)")<br>            }<br>        }<br>        .task {<br>            // fetch the text<br>            do {<br>                let (data, response) = try await URLSession.shared.data(from: URL(string: "https://raw.githubusercontent.com/swiftlang/swift/refs/heads/main/.github/ISSUE_TEMPLATE/task.yml")!)<br>                guard let webPageContent = String(data: data, encoding: .utf8) else { return }<br>                // start a translation to Japanese<br>                self.textToTranslate = webPageContent<br>                self.translationConfiguration = .init(target: .init(identifier: "ja"))<br>            } catch {<br>                print("Error: \(error)")<br>            }<br>        }<br>    }<br>    <br>}<br><br>#Preview {<br>    CustomTranslation()<br>}</code></pre>



<figure class="wp-block-image"><img loading="lazy" decoding="async" width="400" height="869" src="https://static-assets.mszpro.com/2024/12/1Nu0wxFHoDvN82cva_LWb6w.gif" alt="" class="wp-image-489"/></figure>



<h1 class="wp-block-heading" id="05fb">Compatibility for older iOS versions</h1>



<p id="dfd9">The&nbsp;<code>translationTask</code>&nbsp;view modifier is only available for iOS 18 and higher. If you also want your app to support iOS 17, you can create a custom SwiftUI view modifier (that runs the translation task if it is iOS 18 or higher, and do nothing when it is not supported):</p>



<pre class="wp-block-code"><code>@ViewBuilder<br>public func translationTaskCompatible(<br>    shouldRun: Bool,<br>    textToTranslate: String,<br>    targetLanguage: Locale.Language = Locale.current.language,<br>    action: @escaping (_ detectedSourceLanguage: String, _ translationResult: String) -&gt; Void<br>) -&gt; some View {<br>    if shouldRun, #available(iOS 18.0, *) {<br>        self<br>            .translationTask(.init(target: targetLanguage), action: { session in<br>                do {<br>                    let response = try await session.translate(textToTranslate)<br>                    action(response.sourceLanguage.minimalIdentifier, response.targetText)<br>                } catch {<br>                    print("Translation failed: \(error.localizedDescription)")<br>                }<br>            })<br>    } else {<br>        self // No-op for unsupported iOS versions<br>    }<br>}</code></pre>



<p id="9282">Then, in your iOS code, you can use it like this:</p>



<pre class="wp-block-code"><code>.translationTaskCompatible(shouldRun: self.runAppleTranslation,<br>                               textToTranslate: self.displayedPostContent,<br>                               targetLanguage: Locale.current.language, action: { detectedSourceLanguageCode, translationResult in<br>        self.displayedPostContent = translationResult<br>        })</code></pre>



<h1 class="wp-block-heading" id="819d">Submit multiple translation requests</h1>



<p id="8a54">You can submit multiple translation requests, and have results coming with unique IDs whenever it becomes available:</p>



<figure class="wp-block-image"><img loading="lazy" decoding="async" width="600" height="1303" src="https://static-assets.mszpro.com/2024/12/1ysU4lvZj0034HAvz0fk18A.gif" alt="" class="wp-image-493"/></figure>



<p id="ed95">Within a Task block and a SwiftUI view, you can create an array of translation requests, and submit them all in once. Within each request, you can specify a request ID, so you know which original text it is referring to when you get the result back.</p>



<pre class="wp-block-code"><code>//<br>//  MultipleTranslate.swift<br>//  iOSTranslationVideo<br>//<br>//  Created by msz on 2024/12/05.<br>//<br><br>import SwiftUI<br>import Translation<br><br>struct MultipleTranslate: View {<br>    <br>    // translation struct with the original text and optional translated text String<br>    struct TranslationEntry: Identifiable {<br>        let id: String<br>        let originalText: String<br>        var translatedText: String?<br>        <br>        init(id: String = UUID().uuidString, originalText: String, translatedText: String? = nil) {<br>            self.id = id<br>            self.originalText = originalText<br>            self.translatedText = translatedText<br>        }<br>    }<br>    <br>    @State private var textsToTranslate: &#91;TranslationEntry] = &#91;<br>        .init(originalText: "Hello world! This is just a test."),<br>        .init(originalText: "The quick brown fox jumps over the lazy dog."),<br>        .init(originalText: "How are you doing today?"),<br>        .init(originalText: "It is darkest just before the dawn."),<br>        .init(originalText: "The early bird catches the worm."),<br>    ]<br>    @State private var userEnteredNewText: String = ""<br>    <br>    @State private var configuration: TranslationSession.Configuration?<br>    <br>    var body: some View {<br>        <br>        Form {<br>            <br>            // list all text<br>            Section("Texts to translate") {<br>                List {<br>                    ForEach(textsToTranslate) { text in<br>                        VStack(alignment: .leading) {<br>                            // original text<br>                            Text(text.originalText)<br>                                .font(.headline)<br>                            // translated text, if available<br>                            if let translatedText = text.translatedText {<br>                                Text(translatedText)<br>                                    .font(.subheadline)<br>                            }<br>                        }<br>                    }<br>                }<br>            }<br>            <br>            // allow user to add a new text, using a TextField and a Button<br>            Section("Add new text") {<br>                HStack {<br>                    TextField("Enter text to translate",<br>                              text: $userEnteredNewText)<br>                    Button("Add") {<br>                        textsToTranslate.append(.init(originalText: userEnteredNewText))<br>                        userEnteredNewText = ""<br>                    }<br>                }<br>            }<br>            <br>            Button("Translate all to Japanese") {<br>                self.configuration = .init(target: .init(identifier: "ja"))<br>            }<br>            <br>        }<br>        .translationTask(configuration) { session in<br>            let allRequests = textsToTranslate.map {<br>                return TranslationSession.Request(<br>                    sourceText: $0.originalText,<br>                    clientIdentifier: $0.id)<br>            }<br>            do {<br>                for try await response in session.translate(batch: allRequests) {<br>                    print(response.targetText, response.clientIdentifier ?? "")<br>                    if let i = self.textsToTranslate.firstIndex(where: { $0.id == response.clientIdentifier }) {<br>                        var entry = self.textsToTranslate&#91;i]<br>                        entry.translatedText = response.targetText<br>                        self.textsToTranslate.remove(at: i)<br>                        self.textsToTranslate.insert(entry, at: i)<br>                    }<br>                }<br>            } catch {<br>                print(error.localizedDescription)<br>            }<br>        }<br>        <br>    }<br>    <br>}<br><br>#Preview {<br>    MultipleTranslate()<br>}</code></pre>



<h1 class="wp-block-heading" id="1c02">Using in UIKit</h1>



<p id="b5ab">You might notice that all the above need a view modifier attached to a SwiftUI View.</p>



<p id="0808">Here is a quote from Apple Engineer:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p id="a3ec"><em>“While the Translation APIs do need to be triggered from SwiftUI, there’s still a straightforward workaround to get this working from a mostly UIKit (or AppKit) app. You can add&nbsp;</em><code><em>UIHostingController</em></code><em>&nbsp;(or&nbsp;</em><code><em>NSHostingController</em></code><em>) to the place in your app you want any translation UI to present from. You can add the&nbsp;</em><code><em>.translationPresentation</em></code><em>&nbsp;or&nbsp;</em><code><em>.translationTask</em></code><em>&nbsp;modifier to a simple SwiftUI view, even though most of your app doesn&#8217;t use SwiftUI.”&nbsp;</em><a href="https://developer.apple.com/forums/thread/756837?answerId=791116022#791116022" rel="noreferrer noopener" target="_blank">https://developer.apple.com/forums/thread/756837?answerId=791116022#791116022</a></p>
</blockquote>



<p id="29b1">So the translation API needs to be triggered from a SwiftUI view.</p>



<p id="8045">If you are using UIKit, you can use a&nbsp;<code><em>UIHostingController</em></code></p>



<pre class="wp-block-code"><code>//<br>//  TranslationUIKit.swift<br>//  iOSTranslationVideo<br>//<br>//  Created by msz on 2024/12/05.<br>//<br><br>import Foundation<br>import UIKit<br>import SwiftUI<br><br>#if canImport(Translation)<br>import Translation<br>#endif<br><br>struct EmbeddedTranslationView: View {<br>    var sourceText: String<br>    @State private var isTranslationShown: Bool = false<br>    <br>    var body: some View {<br>        VStack {<br>#if canImport(Translation)<br>            Button("Translate") {<br>                self.isTranslationShown = true<br>            }<br>            .translationPresentation(isPresented: $isTranslationShown,<br>                                     text: self.sourceText)<br>#else<br>            Text("Translation feature not available.")<br>#endif<br>        }<br>    }<br>}<br><br>// UIKit ViewController<br>class ViewController: UIViewController {<br>    override func viewDidLoad() {<br>        super.viewDidLoad()<br>        view.backgroundColor = .systemBackground<br>        <br>        // Create the SwiftUI view<br>        let embeddedSwiftUIView = EmbeddedTranslationView(sourceText: "Hello world! This is a test.")<br>        <br>        // Embed the SwiftUI view in a UIHostingController<br>        let hostingController = UIHostingController(rootView: embeddedSwiftUIView)<br>        <br>        // Add the UIHostingController as a child view controller<br>        addChild(hostingController)<br>        hostingController.view.translatesAutoresizingMaskIntoConstraints = false<br>        view.addSubview(hostingController.view)<br>        hostingController.didMove(toParent: self)<br>        <br>        // Layout the SwiftUI view<br>        NSLayoutConstraint.activate(&#91;<br>            hostingController.view.centerXAnchor.constraint(equalTo: view.centerXAnchor),<br>            hostingController.view.centerYAnchor.constraint(equalTo: view.centerYAnchor),<br>            hostingController.view.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.8),<br>            hostingController.view.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.5)<br>        ])<br>    }<br>}<br><br>// Wrap UIKit ViewController for SwiftUI<br>struct UIKitViewWrapper: UIViewControllerRepresentable {<br>    func makeUIViewController(context: Context) -&gt; UIViewController {<br>        return ViewController()<br>    }<br>    <br>    func updateUIViewController(_ uiViewController: UIViewController, context: Context) {<br>        // No updates required for now<br>    }<br>}<br><br>// Add a SwiftUI Preview for Testing<br>struct UIKitViewWrapper_Previews: PreviewProvider {<br>    static var previews: some View {<br>        UIKitViewWrapper()<br>            .edgesIgnoringSafeArea(.all)<br>    }<br>}</code></pre>



<p id="66ef">In the above view, you will embed the SwiftUI view (which shows a single translate button). When the user taps that button, you can present the translation view.</p>



<p id="c7cc">Similarly, if you want to call the translation API, you can pass the translated value back from SwiftUI view to your UIKit view.</p>



<h1 class="wp-block-heading" id="9c09">Thank you for reading!</h1>



<p id="a8c1">The code in this article is available at:&nbsp;<a href="https://github.com/mszpro/iOS-System-Translation-Demo" rel="noreferrer noopener" target="_blank">https://github.com/mszpro/iOS-System-Translation-Demo</a></p>



<p id="2fc4">Japanese version:&nbsp;<a href="https://qiita.com/mashunzhe/items/d90ae92e7daba800abaf" rel="noreferrer noopener" target="_blank">https://qiita.com/mashunzhe/items/d90ae92e7daba800abaf</a></p>



<figure class="wp-block-image"><img loading="lazy" decoding="async" width="586" height="488" src="https://static-assets.mszpro.com/2024/12/1PGkriQSbIQaMy_eBMW58tg.png" alt="" class="wp-image-488" srcset="https://static-assets.mszpro.com/2024/12/1PGkriQSbIQaMy_eBMW58tg-300x250.png 300w, https://static-assets.mszpro.com/2024/12/1PGkriQSbIQaMy_eBMW58tg.png 586w" sizes="auto, (max-width: 586px) 100vw, 586px" /></figure>



<p id="844b">Follow me on Twitter:&nbsp;<a href="https://twitter.com/mszpro" rel="noreferrer noopener" target="_blank">https://twitter.com/mszpro</a></p>



<p id="645e">Subscribe on Youtube:&nbsp;<a href="https://www.youtube.com/@MszPro6" rel="noreferrer noopener" target="_blank">https://www.youtube.com/@MszPro6</a></p>



<p id="8b88">Mastodon, Misskey: @me@mszpro.com</p>



<p id="98bd">Bluesky: @mszpro.com</p>



<p id="d083">Website:&nbsp;<a href="https://mszpro.com/" rel="noreferrer noopener" target="_blank">https://mszpro.com</a></p>



<p id="1a18">SoraSNS for Mastodon, Misskey, Bluesky, Nostr all in one:&nbsp;<a href="https://mszpro.com/sorasns" rel="noreferrer noopener" target="_blank">https://mszpro.com/sorasns</a></p>



<figure class="wp-block-image"><img loading="lazy" decoding="async" width="1080" height="567" src="https://static-assets.mszpro.com/2024/12/1UKKVmi2cx0vo6bLuz3RWQA.png" alt="" class="wp-image-487" srcset="https://static-assets.mszpro.com/2024/12/1UKKVmi2cx0vo6bLuz3RWQA-300x158.png 300w, https://static-assets.mszpro.com/2024/12/1UKKVmi2cx0vo6bLuz3RWQA-1024x538.png 1024w, https://static-assets.mszpro.com/2024/12/1UKKVmi2cx0vo6bLuz3RWQA-768x403.png 768w, https://static-assets.mszpro.com/2024/12/1UKKVmi2cx0vo6bLuz3RWQA.png 1080w" sizes="auto, (max-width: 1080px) 100vw, 1080px" /></figure><p>The post <a href="https://mszpro.com/ios-system-translate">Free translation API — using the iOS 18’s system Translation framework in your app (SwiftUI or UIKit)</a> first appeared on <a href="https://mszpro.com">MszPro・株式会社Smartソフト</a>.</p>]]></content:encoded>
					
		
		
			</item>
	</channel>
</rss>

<!--
Performance optimized by W3 Total Cache. Learn more: https://www.boldgrid.com/w3-total-cache/

Page Caching using Disk: Enhanced 
Lazy Loading (feed)
Database Caching 1/73 queries in 0.023 seconds using Disk

Served from: mszpro.com @ 2025-07-08 14:32:07 by W3 Total Cache
-->