Exploring Translation: Getting Source Language in iOS 18 Using NaturalLanguage

Over the past two months, I have been exploring Apple's Translate framework for my app, LyricsLink.

LyricsLink: Translate Music for Everyone!
LyricsLink is an app for music lovers who want to understand and enjoy song lyrics in any language. With integration with Apple Music, LyricsLink allows you to search for songs, view their lyrics, and instantly translate them into the language of your choice!Key Features:Apple Music Integration: Easily search for songs using the vast Apple Music library.Instant Lyrics: Get the lyrics of your favourite songs displayed beautifully.Real-Time Translations: Instantly translate lyrics into your desired language.Note: An active Apple Music subscription is required to access the song library and lyrics. Requires macOS 14.4+, iOS 17.4 or iPadOS 17.4+Contains:Zip file to the app.TestFlight link for automatic updates.Pricing:You can pay what you want, or put $0, or support my budding indie dev journey! 😊

I have run into some interesting cases that I want to share with you that might be save you some time later in the future.

Working with TranslationSession Configuration

When you are working with translations, Apple suggests using nil for the source language. This lets the framework figure out what language you are starting with:

let configuration = TranslationSession.Configuration(source: nil, target: selectedLanguage)

This works great most of the time. The framework is good enough to figure out the language you are dealing with.

Sometimes songs mix languages. I ran into cases where songs were mostly in Korean, but had enough English that the Translation framework got confused. It could not automatically identify the main language.

NLLanguageRecognizer

In those cases, using NLLanguageRecognizer to find the dominant language helped to continue with the translation process. Here is how to use it:

private func identifyLanguage(of lyrics: String) -> Locale.Language? {
  let recogniser = NLLanguageRecognizer()
  recogniser.processString(lyrics)
  
  guard let language = recogniser.dominantLanguage else {
    return nil
  }
  
  return Locale.Language(identifier: language.rawValue)
}

In my initial exploration, I used the approach demonstrated in the WWDC session, which involves creating an NLLanguageRecognizer instance, processing the string, and then getting the dominant language. However, there is an even simpler method you can use.

There is a class method on NLLanguageRecognizer called dominantLanguage(for:) that you can directly use without creating an NLLanguageRecognizer instance:

private func identifyLanguage(of lyrics: String) -> Locale.Language? {
  guard let language = NLLanguageRecognizer.dominantLanguage(for: lyrics) else {
    return nil
  }
  
  return Locale.Language(identifier: language.rawValue)
}

Yay, you managed to shave off two whole lines of code! 

Moving Forward

I have certainly learned a lot about the Translate framework while working on LyricsLink.

The error that got me exploring for a workaround was theunableToIdentifyLanguage error. This one shows up when the Translation framework cannot figure out what language your text is in. You might see this error try to get the language availability status:

let status = try await LanguageAvailability().status(for: lyrics, to: targetLanguage)

Here is how you can handle the unableToIdentifyLanguage error:

do {
  let status = try await LanguageAvailability().status(for: lyrics, to: targetLanguage)
  // Process the status here
} catch let error as TranslationError {
  if error == .unableToIdentifyLanguage {

    // The framework could not identify the language automatically

    // Use NLLanguageRecognizer here
    if let detectedLanguage = identifyLanguage(of: lyrics) {

      // Now you have the detected language, you can proceed with translation
      print("Detected language: \(detectedLanguage)")
    } else {
      print("Still unable to identify language")
    }
  } else {
    // Handle other TranslationError cases
    print("Translation error: \(error)")
  }
} catch {
  // Handle any other errors
  print("Unexpected error: \(error)")
}

This code tries to get the translation status. If it cannot identify the language, it uses the identifyLanguage function that we wrote earlier using NLLanguageRecognizer to detect the language.

Remember, TranslationSession and LanguageAvailability are new in iOS 18.0, iPadOS 18.0, and macOS 15.0.

Happy translating!