Realtime Translation - iOS
Realtime translation turns the speech in a room into text, translated into each participant's own language, while the room runs. This guide explains how to use the startTranslation(config:), stopTranslation() and changeTranslationLanguage(_:) methods of the Room class.
You set a participant's translation language in createRoom(), and can change it at any point in the room with changeTranslationLanguage(_:).
The translation language is always a language code, both in the translationLanguage parameter of createRoom() and in changeTranslationLanguage(_:). Only the codes listed in Supported Languages return translated text. If you pass an unsupported code, no translated text arrives.
Integrating Realtime Translation Feature
-
Start Translation: You start realtime translation with the
startTranslation(config:)method. TheonTranslationStateChanged(state:)event then reportsTRANSLATION_STARTING, followed byTRANSLATION_STARTEDonce translation is running. -
Translation Data: As translation progresses, the
onTranslationText(_:)event delivers aTranslationTextwith the translated text, the participant who spoke it, the timestamp and the language. -
Change Translation Language: A participant can switch to another language while translation is running with
changeTranslationLanguage(_:). TheonTranslationLanguageChanged(participantId:language:)event then reports the new language. -
Stop Translation: When you stop translation with
stopTranslation(), theonTranslationStateChanged(state:)event reportsTRANSLATION_STOPPING, followed byTRANSLATION_STOPPED.
Step 1: Configure the translation languages
- Set the participant's translation language and speaking language when you create the room with
createRoom().
do {
room = try VideoSDK.createRoom(
roomId: "abcd-efgh-xyzw",
token: "<Your-Token>",
participantName: "John Doe",
translationLanguage: "hi",
speakingLanguage: "en"
)
} catch {
print("Failed to create the room: \(error)")
}
-
translationLanguage: The language the participant receives translated text in.onTranslationText(_:)delivers only text in this language, so a participant without a translation language receives no translated text until they callchangeTranslationLanguage(_:). -
speakingLanguage: The language the participant speaks for the whole session. When it is not set, the participant'stranslationLanguageis also used as their spoken language. Refer to Speaking Language and Translation Language for how the two work together.
Step 2: Start realtime translation
- Start realtime translation for the room with the
startTranslation(config:)method, once the room is joined.configis optional. Leave it out to use the default settings.
@IBAction func startTranslationTapped(_ sender: Any) {
Task {
do {
try await room?.startTranslation()
} catch let error as VideoSDKError {
print("Failed to start translation: \(error.name) (\(error.code))")
} catch {
print("Failed to start translation: \(error)")
}
}
}
- One translation runs for the whole room. A participant who joins while it is already running may not receive
TRANSLATION_STARTED, so don't wait for that event before you show translated text.
Step 3: Change the translation language
- Switch to another language with the
changeTranslationLanguage(_:)method. It changes the language for this participant only.
@IBAction func frenchTapped(_ sender: Any) {
Task {
do {
try await room?.changeTranslationLanguage("fr")
} catch {
print("Failed to change the translation language: \(error)")
}
}
}
- The new language applies once
onTranslationLanguageChanged(participantId:language:)reports it for this participant. Until then,onTranslationText(_:)keeps delivering the old language.
Step 4: Stop realtime translation
- Stop realtime translation with the
stopTranslation()method. It stops translation for every participant in the room.
@IBAction func stopTranslationTapped(_ sender: Any) {
Task {
do {
try await room?.stopTranslation()
} catch {
print("Failed to stop translation: \(error)")
}
}
}
startTranslation(config:),stopTranslation()andchangeTranslationLanguage(_:)areasync throws. Call them withtry awaitinside aTask, and catch theVideoSDKErrorthey throw.- Before you join the room, or after you leave it, they throw
ERROR_ACTION_PERFORMED_BEFORE_ROOM_JOINED(3035). While the room is reconnecting, they throwERROR_ROOM_RECONNECTING(3065). - When the server refuses the request, they throw
START_TRANSLATION_FAILED(4042),STOP_TRANSLATION_FAILED(4043) orCHANGE_TRANSLATION_LANGUAGE_FAILED(4044).
Step 5: Listen for the translation events
- Add these callbacks to your
RoomEventListener.
import VideoSDKRTC
extension RoomViewController: RoomEventListener {
func onTranslationStateChanged(state: TranslationState) {
switch state {
case .TRANSLATION_STARTING:
print("Realtime Translation is starting")
case .TRANSLATION_STARTED:
print("Realtime Translation is started")
case .TRANSLATION_STOPPING:
print("Realtime Translation is stopping")
case .TRANSLATION_STOPPED:
print("Realtime Translation is stopped")
@unknown default:
break
}
}
func onTranslationText(_ data: TranslationText) {
print("\(data.participantName) (\(data.language)): \(data.text)")
}
func onTranslationLanguageChanged(participantId: String, language: String) {
print("\(participantId) now receives translations in \(language)")
}
}
-
onTranslationStateChanged(state:)receives aTranslationState:TRANSLATION_STARTING,TRANSLATION_STARTED,TRANSLATION_STOPPINGorTRANSLATION_STOPPED. ThetranslationStateproperty of theRoomclass holds the last state you received. -
onTranslationText(_:)receives aTranslationTextwithtext,participantId,participantName,participantLanguage(the language the speaker spoke),language(the language the text was translated into),timestampandtype. It fires only for text in this participant's translation language. -
typeis.PARTIALwhile the service is still revising a line, and.FULLonce the line is final. A new.PARTIALline replaces the previous one, so add a line to a transcript only when it is.FULL. -
onTranslationLanguageChanged(participantId:language:)fires when any participant changes their translation language, including you. CompareparticipantIdwithroom?.localParticipant.idto react only to your own change.
Example
- The following code starts and stops realtime translation, and switches the language, with a tap. Partial lines replace each other, and full lines are kept.
import UIKit
import VideoSDKRTC
class TranslationViewController: UIViewController {
var room: Room?
@IBOutlet weak var captionLabel: UILabel!
private var finishedLines: [String] = []
// create the room with a translationLanguage, add this view controller
// as a listener with room?.addEventListener(self) and join the room
// ...
@IBAction func startTapped(_ sender: Any) {
runTranslationAction { try await $0.startTranslation() }
}
@IBAction func stopTapped(_ sender: Any) {
runTranslationAction { try await $0.stopTranslation() }
}
@IBAction func frenchTapped(_ sender: Any) {
runTranslationAction { try await $0.changeTranslationLanguage("fr") }
}
private func runTranslationAction(_ action: @escaping (Room) async throws -> Void) {
guard let room else { return }
Task {
do {
try await action(room)
} catch let error as VideoSDKError {
print("Translation request failed: \(error.name) (\(error.code))")
} catch {
print("Translation request failed: \(error)")
}
}
}
}
extension TranslationViewController: RoomEventListener {
func onTranslationStateChanged(state: TranslationState) {
print("Translation state: \(state.rawValue)")
}
func onTranslationText(_ data: TranslationText) {
let line = "\(data.participantName): \(data.text)"
if data.type == .FULL {
finishedLines.append(line)
captionLabel.text = finishedLines.suffix(3).joined(separator: "\n")
} else {
// a partial line replaces the previous partial line
captionLabel.text = (finishedLines.suffix(2) + [line]).joined(separator: "\n")
}
}
}
Speaking Language and Translation Language
speakingLanguage sets the language a participant speaks, while translationLanguage sets the language they receive translated text in. The two examples below show how translation works with and without a speaking language.
Example 1: Without speakingLanguage
Both participants set only their translation language, so each participant's translation language is also their spoken language.
// On Participant 1's device (Spanish)
let room1 = try VideoSDK.createRoom(
roomId: roomId,
token: token,
participantName: "Participant 1",
translationLanguage: "es"
)
// On Participant 2's device (German)
let room2 = try VideoSDK.createRoom(
roomId: roomId,
token: token,
participantName: "Participant 2",
translationLanguage: "de"
)
| Speaker | Speaking Language (Input) | Receiver | Translated Output |
|---|---|---|---|
| Participant 1 | Spanish (es) | Participant 2 | German (de) |
| Participant 2 | German (de) | Participant 1 | Spanish (es) |
Here each participant must speak their own translation language, because no speaking language is set.
Example 2: With speakingLanguage on one participant
Here only Participant 1 sets a speakingLanguage. That fixes the language of their speech, while Participant 2's translation language is still also their spoken language.
// On Participant 1's device (speaks English, receives Spanish)
let room1 = try VideoSDK.createRoom(
roomId: roomId,
token: token,
participantName: "Participant 1",
translationLanguage: "es",
speakingLanguage: "en"
)
// On Participant 2's device (no speakingLanguage, receives German)
let room2 = try VideoSDK.createRoom(
roomId: roomId,
token: token,
participantName: "Participant 2",
translationLanguage: "de"
)
| Speaker | Speaking Language (Input) | Receiver | Translated Output |
|---|---|---|---|
| Participant 1 | English (en) | Participant 2 | German (de) |
| Participant 2 | German (de) | Participant 1 | Spanish (es) |
Key takeaways
- When
speakingLanguageis not set, the participant's translation language is also their speaking language. - When only one participant sets a
speakingLanguage, that participant's input language is fixed, while the others keep using their translation language as their input language. - Setting
speakingLanguagegives you more control, especially in rooms with many languages.
Supported Languages
These are the supported language codes:
- Multilingual (Spanish + English):
multi - Bulgarian:
bg - Catalan:
ca - Chinese (Mandarin, Simplified):
zh,zh-CN,zh-Hans - Chinese (Mandarin, Traditional):
zh-TW,zh-Hant - Chinese (Cantonese, Traditional):
zh-HK - Czech:
cs - Danish:
da,da-DK - Dutch:
nl - English:
en,en-US,en-AU,en-GB,en-NZ,en-IN - Estonian:
et - Finnish:
fi - Flemish:
nl-BE - French:
fr,fr-CA - German:
de - German (Switzerland):
de-CH - Greek:
el - Hindi:
hi - Hungarian:
hu - Indonesian:
id - Italian:
it - Japanese:
ja - Korean:
ko,ko-KR - Latvian:
lv - Lithuanian:
lt - Malay:
ms - Norwegian:
no - Polish:
pl - Portuguese:
pt,pt-BR,pt-PT - Romanian:
ro - Russian:
ru - Slovak:
sk - Spanish:
es,es-419 - Swedish:
sv,sv-SE - Thai:
th,th-TH - Turkish:
tr - Ukrainian:
uk - Vietnamese:
vi
API Reference
The API references for all the methods and events utilized in this guide are provided below.
Got a Question? Ask us on discord

