Hosting Multi-Language IM Conversations in Lync 2010: Code Listing (Part 3 of 3)
Summary: This is the final article in a series of three articles that describe how to build a Microsoft Lync 2010 Conversation Window Extension application that translates IM text between two languages. The ConversationTranslator application described in this article uses the Microsoft Translation web service.
Applies to: Microsoft Lync 2010 SDK | Microsoft Lync 2010 | Microsoft Lync Server 2010
Published: June 2011 | Provided by: John Austin, Microsoft | About the Author
Contents
Introduction
XAML Controls
Initializing ConversationTranslator
Getting Languages
Translating Outgoing Message
Translating Incoming Message
Conclusion
Additional Resources
Introduction
Part 2 discusses the logic that is used by the Microsoft Lync 2010 ConversationTranslator application and describes how to obtain translation languages, send messages, and translate received messages. Part 3 lists the C# code that implements the logic that is discussed in Part 2.
Hosting Multi-Language IM Conversations in Lync 2010: Overview (Part 1 of 3)
Hosting Multi-Language IM Conversations in Lync 2010: Project Details (Part 2 of 3)
The examples in the following sections are included with the Microsoft Lync 2010 SDK ConversationTranslator sample project. The only sample code that is reproduced in this article is code that demonstrates the concepts that are described in this article. Some of the examples may refer to methods that are in the Lync SDK sample project but are not reproduced in this article. To compile the example code, install the Lync SDK and then open the installed sample in Microsoft Visual Studio 2010 development system.
XAML Controls
The two translation language controls (comboBoxMyLanguage, comboBoxTargetLanguage) are bound to the ObservableCollection<Language>Languages property of MainViewModel.
The following example declares two ComboBox controls and binds them to the MainViewModel.Languages property. The ItemSource attribute binds the collection of languages returned from the translation service. The combo boxes bind to the same collection of languages.
<!-- Me: FromLanguage Them: TargetLanguage -->
<Grid Grid.Row="0" Height="Auto" HorizontalAlignment="Stretch" Margin="0,0,0,0" Name="grid1" VerticalAlignment="Stretch" Width="Auto" Background="#FF8A9AB1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto"/>
<ColumnDefinition />
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<!-- Combos are bond to a single list of languages, and the properties SourceLanguage and TargetLanguage -->
<TextBlock Grid.Column="0" Text="Me:" HorizontalAlignment="Right" Margin="5,0,0,0" VerticalAlignment="Center" Width="Auto" />
<ComboBox Grid.Column="1" ItemsSource="{Binding Languages}" SelectedItem="{Binding SourceLanguage, Mode=TwoWay}" DisplayMemberPath="Name" Height="23" HorizontalAlignment="Left" Margin="5,0,0,0" Name="comboBoxMyLanguage" VerticalAlignment="Center" Width="138" />
<TextBlock Grid.Column="2" Text="Them:" HorizontalAlignment="Right" Margin="0,0,0,0" VerticalAlignment="Center" Width="Auto"/>
<ComboBox Grid.Column="3" ItemsSource="{Binding Languages}" SelectedItem="{Binding TargetLanguage, Mode=TwoWay}" DisplayMemberPath="Name" Height="23" HorizontalAlignment="Left" Margin="5,0,5,0" Name="comboBoxTargetLanguage" VerticalAlignment="Center" Width="138" />
</Grid>
Figure 1 shows the language combo boxes.
Figure 1. Bing Translator language boxes
The following example declares a data bound list, associated item container style, and the data template.
<ListBox Name="listBoxHistory" ScrollViewer.HorizontalScrollBarVisibility="Disabled" IsHitTestVisible="False" VerticalAlignment="Stretch"
ItemsSource="{Binding MessageHistory}" BorderThickness="0" Padding="0" FontSize="14" >
<!-- Gets the ListBox content to stretch horizontally -->
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem" >
<Setter Property="HorizontalContentAlignment" Value="Stretch"></Setter>
</Style>
</ListBox.ItemContainerStyle>
<!-- List template defines each line of the message history, which maps to one MessageLine object -->
<ListBox.ItemTemplate>
<DataTemplate>
<Grid x:Name="LayoutRoot" HorizontalAlignment="Stretch" Background="{Binding Background}" Margin="0,-7,-5,0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" /><RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBox Grid.Row="0" Grid.Column="0" Text="{Binding ParticipantName}" Background="{Binding Background}"
Visibility="{Binding FirstLineVisibility}" HorizontalAlignment="Left" Margin="3,2,0,0"
VerticalAlignment="Center" BorderThickness="0" Foreground="#666666" VerticalContentAlignment="Bottom" Padding="0"/>
<TextBox Grid.Row="0" Grid.Column="1" Text="{Binding MessageTime, StringFormat='hh:mm tt'}"
Background="{Binding Background}" Visibility="{Binding FirstLineVisibility}"
HorizontalAlignment="Right" Margin="0,2,2,0" VerticalAlignment="Center" BorderThickness="0"
Foreground="#666666" VerticalContentAlignment="Bottom" Padding="0"/>
<Grid Name="gridLine" Grid.Row="1" Grid.ColumnSpan="2" Margin="0,0,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBox Grid.Column="0" Text=" °" HorizontalAlignment="Left" Background="{Binding Background}" Height="20" Margin="0,0,0,3"
VerticalAlignment="Stretch" Width="20" TextWrapping="Wrap" BorderThickness="0" VerticalContentAlignment="Bottom" HorizontalContentAlignment="Right"
Foreground="#CACACA" FontWeight="SemiBold" Padding="-3"/>
<TextBox Grid.Column="1" Text="{Binding Message}" Background="{Binding Background}" FontSize="24" Height="20" Margin="0,0,0,3"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch" TextWrapping="Wrap" BorderThickness="0" VerticalContentAlignment="Bottom"
HorizontalContentAlignment="Left" Padding="-3" />
</Grid>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Figure 2 shows the rendered conversation history list with a Portuguese translation.
Figure 2. Conversation history list showing translated IM text
The following example declares a text box, send button, and cancel button that let a user enter text, translate, send, or cancel sending IM text.
<TextBox Grid.Column="0" Height="Auto" HorizontalAlignment="Stretch" Margin="0,0,0,0" Name="textBoxMessage" VerticalAlignment="Stretch" Width="Auto" KeyUp="textBoxMessage_KeyUp" TextWrapping="Wrap" />
<Grid Grid.Column="1" Height="Auto" HorizontalAlignment="Stretch" Margin="0,0,0,0" Name="grid3" VerticalAlignment="Stretch" Width="Auto">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition/>
</Grid.RowDefinitions>
<Button Content="Translate" Grid.Row="0" Height="70" HorizontalAlignment="Center" Margin="5,2,5,0" Name="buttonProofSend" VerticalAlignment="Top" Width="70" Click="buttonProofSend_Click" />
<Button Content="Cancel" Grid.Row="1" Height="23" HorizontalAlignment="Center" Margin="5,0,5,2" Name="buttonCancel" VerticalAlignment="Bottom" Width="70" Click="buttonCancel_Click" />
</Grid>
Figure 3 shows the text entry box and action buttons that are used to enter new IM text, translate, and send IM.
Figure 3. ConversationTranslator text entry and Translate button
Initializing ConversationTranslator
The following procedure shows how ConversationTranslator is initialized:
Get an instance of Microsoft.Lync.Model.Conversation.Conversation that represents the conversation in the conversation window.
Construct and start the TranslationService and ConversationService classes.
Register for events on the two service classes.
Get the Hosting Conversation
The following example from App.xaml.cs runs when a user starts ConversationTranslator.
private void Application_Startup(object sender, StartupEventArgs e)
{
// Creates the main view model.
viewModel = new MainViewModel();
MainPage mainPage = new MainPage();
mainPage.DataContext = viewModel;
this.RootVisual = mainPage;
}
The following example from the MainPage.UserControl_Loaded event handler gets the Microsoft.Lync.Model.Conversation.Conversation instance.
//gets the conversation this translator is associated with
conversation = (Conversation)LyncClient.GetHostingConversation();
//DEBUG: when running on the web browser, just get the first current
//active Lync conversation
if (conversation == null)
{
//obtains the first active Lync conversation
conversation = LyncClient.GetClient().ConversationManager.Conversations[0];
//cannot run without a conversation
if (this.conversation == null)
{
throw new NotSupportedException("You need one conversation to Debug the ConversationTranslator");
}
}
Start the Service Classes and Register for Events
The following example constructs ConversationService and TranslationService class instances, registers for events on the instances, and initializes them.
//creates the conversation service component and subscribes to events
conversationService = new ConversationService(conversation);
conversationService.MessageError += new MessageError(conversationService_MessageError);
conversationService.MessageReceived += new MessageReceived(conversationService_MessageReceived);
conversationService.MessageSent += new MessageSent(conversationService_MessageSent);
//starts listening to Lync events
conversationService.Start();
//obtains the translation service component and subscribes to events
translationService = new TranslationService();
translationService.LanguagesReceived += new LanguagesReceived(translationService_LanguagesReceived);
translationService.TranslationError += new TranslationError(translationService_TranslationError);
translationService.TranslationReceived += new TranslationReceived(translationService_TranslationReceived);
translationService.Start();
Getting Languages
Getting supported languages involves the following tasks:
Query BingTranslation for the set of languages that are supported for translation.
Update the UI with the languages that are specified in step 1.
Update a view model class instance with the collection of returned languages.
Query for Languages
The following example from MainPage gets the languages supported by BingTranslation.
/// <summary>
/// Starts the translator, which:
/// 1. Connects to the translation services.
/// 2. Updates the UI with the available languages.
/// </summary>
public void Start()
{
//connects to the translation service
languageService = new LanguageServiceClient();
//requests the available language codes
languageService.BeginGetLanguagesForTranslate(AppID, this.OnGetLanguagesResponse, null);
}
/// <summary>
/// Called back when the language list arrives.
/// </summary>
private void OnGetLanguagesResponse(IAsyncResult result)
{
//gets the codes
ObservableCollection<string> languageCodes = languageService.EndGetLanguagesForTranslate(result);
//requests the language names in English (and passes the codes as the asynchronous context)
languageService.BeginGetLanguageNames(AppID, "en", languageCodes, this.OnGetLanguageNamesResponse, languageCodes);
}
/// <summary>
/// Called back when the language name list arrives.
/// Assembles the array with the Language objects and notifies the UI.
/// </summary>
/// <param name="result"></param>
private void OnGetLanguageNamesResponse(IAsyncResult result)
{
//obtains the codes from the asyncronous context
ObservableCollection<string> languageCodes = (ObservableCollection<string>) result.AsyncState;
//obtains the names
ObservableCollection<string> languageNames = languageService.EndGetLanguageNames(result);
//assembles the language list
List<Language> languages = new List<Language>();
for (int i = 0; i < languageCodes.Count; i++)
{
languages.Add(new Language(languageCodes[i], languageNames[i]));
}
//notifies the UI the languages are ready
LanguagesReceived(languages);
}
Tip
Replace the language code passed in argument 2 of BeginGetLanguageNames with the language code of the user. This process causes BingTranslation to return a collection of language names in the user’s language instead of English.
Updating UI and MainViewModel
The following example from MainPage handles the LanguagesReceived event that is raised by the previous example. The viewModel.Languages collection is bound to the XAML control as shown in an earlier example.
/// <summary>
/// Languages received from the translation service.
/// </summary>
/// <param name="languages">Set of supported languages.</param>
private void translationService_LanguagesReceived(IList<Language> languages)
{
//need to swtich to the UI thread
Dispatcher.BeginInvoke(() =>
{
foreach (Language language in languages)
{
viewModel.Languages.Add(language);
}
//updates the combos, necessary given that the set of languages comes after initialization
comboBoxMyLanguage.SelectedItem = viewModel.SourceLanguage;
comboBoxTargetLanguage.SelectedItem = viewModel.TargetLanguage;
});
}
Tip
LanguageService (the kind of BingTranslation) exposes an observable collection of language codes and language names. To simplify ConversationTranslator code, the sample does not handle the SpecializedNotifyCollectionChangedEventHandler() event. Instead, the language list is loaded one time on startup. For information about how to handle ObservableCollection events, see System.Collections.Specialized.
Translating Outgoing Message
Translating outgoing IM text involves the following tasks:
Create an instance of MessageContext in the model layer to encapsulate message text and other context.
Get a translation of the message text from BingTranslation.
Update the ConversationTranslator IM entry text box by using the proposed translation.
Send the translated text as an IM.
Update the IM history list box and view model layer.
Creating MessageContext
The following example from MainPage creates a new MessageContext instance and sets the MessageContext.OriginalMessage property to the text typed by the user. The example calls Contact.GetContactInformation. This example code is called when a user wants to translate IM text to another user’s language before the IM is sent. The name string returned by this method call is used on the view to identify the sender of a message. Finally, the message is translated by TranslationService.
/// <summary>
/// Translates the message asynchronously.
/// </summary>
/// <param name="message"></param>
private void SendLocalMessageForTranslation(string message)
{
//assembles a MessageContext object
messageContext = new MessageContext();
messageContext.OriginalMessage = message;
//reads the self participant's name
try
{
messageContext.ParticipantName =
conversation.SelfParticipant.Contact.GetContactInformation(ContactInformationType.DisplayName) as string;
}
catch (LyncClientException e)
{
ShowError(e);
return;
}
catch (SystemException e)
{
if (LyncModelExceptionHelper.IsLyncException(e))
{
// Log the exception thrown by the Lync Model API.
ShowError(e);
return;
}
else
{
// Rethrow the SystemException which did not come from the Lync Model API.
throw;
}
}
messageContext.SourceLanguage = viewModel.SourceLanguage.Code;
messageContext.TargetLanguage = viewModel.TargetLanguage.Code;
messageContext.Direction = MessageDirection.Outgoing;
messageContext.MessageTime = DateTime.Now;
//sends the message for translation
translationService.Translate(messageContext);
}
Getting a Message Translation
The following example is taken from TranslationService.cs and is called by MainPage to translate an incoming or outgoing message. If the message direction is incoming, the example detects the language of the sender. Otherwise, the message is translated into the target language specified by the message context.
/// <summary>
/// Processes a translation.
/// </summary>
/// <param name="context">Context containing the message information.</param>
public void Translate(MessageContext context)
{
//first checks if need to detect the source language
if (context.Direction == MessageDirection.Incoming && context.IsLanguageDetectionNeeded)
{
//detects the incoming message language
languageService.BeginDetect(AppID, context.OriginalMessage, this.OnDetectResponse, context);
}
else
{
//translates directly
DoTranslate(context);
}
}
/// <summary>
/// Sends the message for translation.
/// </summary>
/// <param name="context"></param>
private void DoTranslate(MessageContext context)
{
//calls the translation service
languageService.BeginTranslate(AppID, context.OriginalMessage, context.SourceLanguage, context.TargetLanguage, this.OnTranslateResponse, context);
}
For an example that retrieves the translation obtained by calling by BeginTranslate, see Handling LanguageService Events.
The following example is called by LanguageService as a callback when the asynchronous translation operation is complete. The callback updates the MessageContext instance with the translated message and then raises an event, passing the updated MessageContext. The event is handled by MainPage.
/// <summary>
/// Called when the translation is received.
/// </summary>
/// <param name="result"></param>
private void OnTranslateResponse(IAsyncResult result)
{
//gets context from the asyncronous context
MessageContext context = (MessageContext)result.AsyncState;
try
{
//reads the response
context.TranslatedMessage = languageService.EndTranslate(result);
//notifies the UI
TranslationReceived(context);
}
catch (Exception ex)
{
TranslationError(ex, context);
}
}
Updating Text Box by Using Translated Message
The following example is taken from MainPage.xaml.cs and handles the event that is raised in the previous example. It updates the ConversationTranslator UI with translated message text. A user can review the translation before they send it.
/// <summary>
/// Shows the translation received from the translation service.
/// The visual representation depends on the message direction.
/// </summary>
/// <param name="context"></param>
private void translationService_TranslationReceived(MessageContext context)
{
//incoming message: just show in the history
if (context.Direction == MessageDirection.Incoming)
{
ShowIncomingTranslatedMessage(context);
}
else //outgoing message: move to the Send state
{
SetUIState(UIState.Send);
ShowProofMessage(context.TranslatedMessage);
}
}
/// <summary>
/// Shows a translated outgoing message for proofing
/// </summary>
private void ShowProofMessage(string message)
{
this.Dispatcher.BeginInvoke(() =>
{
textBoxMessage.Text = message;
});
}
Sending IM
The following example from MainPage.xaml.cs is called when a user clicks the Send button. If the message has not been translated, the message is translated by using code from an earlier example. Otherwise, the message is sent to other conversation participants.
Tip
The DoSendMessage example updates the MessageContext.TranslatedMessage property by using possible user input from the ConversationTranslator UI.
/// <summary>
/// Called when the ProofSend button is clicked or user presses Enter.
/// </summary>
/// <param name="message"></param>
private void ProofOrSend()
{
//gets the message and clears the text box
string message = textBoxMessage.Text;
textBoxMessage.Text = string.Empty;
//empty messages are not sent for translation
if (string.IsNullOrWhiteSpace(message))
{
return;
}
//depending on the UI state, the message will be sent for translation
//or into the conversation
if (state == UIState.Proof)
{
//sends the message for translation
SendLocalMessageForTranslation(message);
}
else // Send state
{
//sends the message into the conversation
DoSendMessage(message);
//switches back to proof state
SetUIState(UIState.Proof);
}
}
/// <summary>
/// Sends the message into the conversation.
/// </summary>
public void DoSendMessage(string translatedMessage)
{
try
{
//updates the translated message, in case the user corrected it
messageContext.TranslatedMessage = translatedMessage;
//sends the message into the conversation
conversationService.SendMessage(messageContext);
//notifies end of composing
SetComposing(false);
}
catch (Exception ex)
{
ShowError(ex);
}
}
The following example is taken from the ConversationService class. It sends a translated IM to all other participants.
/// <summary>
/// Sends a message into the conversation.
/// </summary>
public void SendMessage(MessageContext context)
{
//sends the message
myImModality.BeginSendMessage(context.TranslatedMessage, myImModality_OnMessageSent, context);
}
/// <summary>
/// Called when a message is sent.
/// </summary>
public void myImModality_OnMessageSent(IAsyncResult result)
{
//gets context from the asyncronous context
MessageContext context = (MessageContext)result.AsyncState;
//notifies the UI that the message was actually sent
MessageSent(context);
}
You do not have to call the InstantMessageModality.EndSendMessage method. However, the call is necessary if application logic must obtain the results of the operation. The send operation fails when the message recipient goes offline in the interval between BeginSendMessage and the routing of the IM text through the server and to the remote endpoint.
The following example can be added to the callback method from the previous example.
try
{
myImModality.EndSendMessage(result);
}
catch (LyncClientException ex)
{
System.Windows.MessageBox.Show(ex.Message);
}
Updating IM History and View Model
The following example from MainPage.xaml.cs handles the event that is raised by ConversationService when a message is sent. The example updates the ConversationTranslator UI with a history of the last sent message.
/// <summary>
/// Called when a message is effectively sent into the conversation.
/// </summary>
/// <param name="context"></param>
private void conversationService_MessageSent(MessageContext context)
{
//posts the action into the UI thread
this.Dispatcher.BeginInvoke(() =>
{
//writes the sent message into the in-memory history
WriteMessageToHistory(context.ParticipantName, context.MessageTime, context.TranslatedMessage);
});
}
/// <summary>
/// Writes a message to the in-memory conversation history.
/// </summary>
/// <param name="senderName">The name of the person who sent the message.</param>
/// <param name="time">The time information.</param>
/// <param name="message">The message itself.</param>
private void WriteMessageToHistory(string senderName, DateTime time, string message)
{
//adds a line for the received or sent message
MessageLine line = new MessageLine(senderName, time, message);
viewModel.MessageHistory.Add(line);
//manually scrolls down to the last added message
listBoxHistory.UpdateLayout();
scrollViewerMessageLog.ScrollToVerticalOffset(listBoxHistory.ActualHeight);
}
Translating Incoming Message
Translating incoming IM text involves the following tasks:
Handle an event that is raised when a message is received.
Create an instance of MessageContext in the model layer to encapsulate message context.
Get a translation of the message text.
Update the UI and view model layer.
Handling Message Received Event
The following example from ConversationService handles the IM received event that is raised by Lync 2010 when an IM is received from another conversation participant.
/// <summary>
/// Called by the Lync SDK when a new message is received.
/// </summary>
private void remoteImModality_InstantMessageReceived(object sender, MessageSentEventArgs args)
{
//casts the modality
InstantMessageModality modality = (InstantMessageModality)sender;
//gets the participant name
string name = (string)modality.Participant.Contact.GetContactInformation(ContactInformationType.DisplayName);
//reads the message in its plain text format (automatically converted)
string message = args.Text;
//notifies the UI about the new message
MessageReceived(message, name);
}
Creating MessageContext
The following example from MainPage handles the event that is raised by the previous example. The example event handler creates a new instance of MessageContext to encapsulate the received message and then requests a translation from the TranslationService class.
For an example that lists the Translate method, see Getting a Message Translation.
/// <summary>
/// Called when a message is received for the specified participant.
/// </summary>
private void conversationService_MessageReceived(string message, string participantName)
{
//posts the action into the UI thread
this.Dispatcher.BeginInvoke(() =>
{
//creates a message context object to pass the message data across the layers
MessageContext context = new MessageContext();
context.MessageTime = DateTime.Now;
context.ParticipantName = participantName;
context.OriginalMessage = message;
context.TargetLanguage = viewModel.SourceLanguage.Code;
context.Direction = MessageDirection.Incoming;
//optimization for two participants (avoids language detection)
if (conversation.Participants.Count == 2)
{
context.IsLanguageDetectionNeeded = false;
context.SourceLanguage = viewModel.TargetLanguage.Code;
}
//sends the message for translation
translationService.Translate(context);
});
}
Handling LanguageService Events
The following example from TranslatorService handles the events that are raised when the LanguageService detects the language of an IM and then returns a translation.
/// <summary>
/// Called back when the source language was detected.
/// </summary>
/// <param name="result"></param>
private void OnDetectResponse(IAsyncResult result)
{
//gets context from the asyncronous context
MessageContext context = (MessageContext) result.AsyncState;
try
{
//reads the response
context.SourceLanguage = languageService.EndDetect(result);
//now calls the translation itself
DoTranslate(context);
}
catch (Exception ex)
{
TranslationError(ex, context);
}
}
/// <summary>
/// Called when the translation is received.
/// </summary>
/// <param name="result"></param>
private void OnTranslateResponse(IAsyncResult result)
{
//gets context from the asyncronous context
MessageContext context = (MessageContext)result.AsyncState;
try
{
//reads the response
context.TranslatedMessage = languageService.EndTranslate(result);
//notifies the UI
TranslationReceived(context);
}
catch (Exception ex)
{
TranslationError(ex, context);
}
}
Updating UI and View Model
The following example is called by a MainPage event handler after the event in the previous example is raised. The code of the event handler appears in Updating Text Box by Using Translated Message.
The Lambda expression in the next example is invoked on the UI thread from the platform thread event handler with a call to BeginInvoke.
/// <summary>
/// Adds an incoming translated message to the message log.
/// </summary>
private void ShowIncomingTranslatedMessage(MessageContext context)
{
//writes a message log in the correct thread
this.Dispatcher.BeginInvoke(() =>
{
WriteMessageToHistory(context.ParticipantName, context.MessageTime, context.TranslatedMessage);
});
}
/// <summary>
/// Writes a message to the in-memory conversation history.
/// </summary>
/// <param name="senderName">The name of the person who sent the message.</param>
/// <param name="time">The time information.</param>
/// <param name="message">The message itself.</param>
private void WriteMessageToHistory(string senderName, DateTime time, string message)
{
//adds a line for the received or sent message
MessageLine line = new MessageLine(senderName, time, message);
viewModel.MessageHistory.Add(line);
//manually scrolls down to the last added message
listBoxHistory.UpdateLayout();
scrollViewerMessageLog.ScrollToVerticalOffset(listBoxHistory.ActualHeight);
}
Conclusion
ConversationTranslator is a Lync 2010 Conversation Window Extension application that bridges the language gap between you and a speaker of any one of 34 other languages. After adding several Lync 2010 API types to your code, you can add this translation capability to your application.
For more information about the ConversationTranslator application, see Hosting Multi-Language IM Conversations in Lync 2010: Overview (Part 1 of 3) and Hosting Multi-Language IM Conversations in Lync 2010: Project Details (Part 2 of 3).
Additional Resources
For more information, see the following resources:
About the Author
John Austin, Microsoft, is a programmer/writer in the Lync client SDK documentation team. He has been writing Microsoft technical documentation for four years. Prior to working for Microsoft, John spent two decades as a software developer.