⬆️ ⬇️

Automation of localization in Silverlight using Visual Studio macros

big red button



Hello. I want to share a small experience in the field of automation of localization of applications developed using SilverLight technology. After reading this post (Localization in Silverlight), it became clear that all string constants would have to be put into resource files, which cannot be called a particularly intellectual work. Therefore, I decided to go a long way and try to automate this process using macros built into Visual Studio.



Due to the fact that I was also required to brush my code a little, it was decided to abandon full automation (complete scanning of all xaml files with searching for Russian letters and putting them into resources). Plus I could not think of normal automation of replacing string constants in .cs files.

')

After analysis, the following macro requirements were developed:





Visual Studio has a convenient macro editor. To call it, you must select in the menu Tools / Macros / Macros IDE. Before you start, you need to create a file with macros (the code is written on VB .Net). Any public procedure without parameters is visible in the list of macros and can be called by pressing a hot key or via Macro Explorer.



Task 1. Definition of the highlighted word in VS


Dim doc As Document = DTE.ActiveDocument Dim d As TextDocument = doc.Object Dim bp = d.Selection.AnchorPoint.CreateEditPoint, ep = d.Selection.BottomPoint.CreateEditPoint Dim caption As String = bp.GetText(ep) If (String.IsNullOrWhiteSpace(caption)) Then Exit Sub '       -   




We receive the active document, we bring it to the text document (more convenient class, for work with the text). We get two points: the beginning and end of the selection (it could have been easier, via doc.Selection.Text, but when opening a new file in the studio and returning back to the file being edited, the selection gets lost, which is very inconvenient, and these points will help us to restore the text selection ).



Task 2. Getting the name of the resource


The easiest way to get the resource name is to translate the selected phrase into English (note: the easiest way is not the most correct one). For the translation, it was decided to use the Google Translate service, as this service has a simple API.

As a result of the request, we get the following JSON object:

{"responseData": {"translatedText":"Organization Structure"}, "responseDetails": null, "responseStatus": 200}



 Private Function ConvertToResourceName(ByVal text As String) As String Dim result As String = (New WebClient()).DownloadString("http://ajax.googleapis.com/ajax/services/language/translate?v=1.0&q=" + text + "&langpair=ru|en") Dim stream As New MemoryStream(Encoding.Unicode.GetBytes(result)) Dim serializer As New System.Runtime.Serialization.Json.DataContractJsonSerializer(GetType(TranslateResult)) Return String.Join("", CType(serializer.ReadObject(stream), TranslateResult).responseData.translatedText _ .Split(" ", ".", ",", "!", "(", ")", vbCrLf, vbCr) _ .Where(Function(word) Not String.IsNullOrWhiteSpace(word)) _ .Select(Function(word) word(0).ToString().ToUpper() + word.Substring(1)) _ .ToArray()) End Function 


To receive a transfer, you must perform a special request to the Google service and pass it a string with text. The only thing that has complicated this code quite a bit is that as a result, we are returned not just a string, but a JSON object, which must be deserialized first. For deserialization, the DataContractJsonSerializer class was used. To use the DataContractJsonSerializer in macros, you need to add the System.Runtime.Serialization.dll assembly to the references and implement the class corresponding to the Google answer, or rather two classes.



Note: before using the name, you can ask the user to confirm the correctness of the choice of transfer (with the possibility of correction).



Note: for LINQ to Object to work, you must add references to System.Core.dll



 <DataContract()> _ Public Class TranslateResult <DataContract()> _ Public Class ResponseDataClass <DataMember()> _ Public translatedText As String <DataMember()> _ Public responseDetails As String <DataMember()> _ Public responseStatus As String End Class <DataMember()> _ Public responseData As ResponseDataClass End Class 


After receiving the translation, you need to slightly decorate the result, namely: remove all prohibited characters and lead to CamelCase style.



Task 3. Adding a new line to resources


Initially, you must activate the window with the resource editor, or open a new window in VS. And if a new window was opened, the focus will move to it. In order not to knock down the user, you need to return the focus back.

 Private Function FindWindow(ByVal fileName As String) As Window For i As Integer = 1 To DTE.Windows.Count If Not (DTE.Windows.Item(i).ProjectItem Is Nothing) Then If (DTE.Windows.Item(i).ProjectItem.FileNames(0) = fileName) Then Return DTE.Windows.Item(i) End If End If Next Dim oldactive As Document = DTE.ActiveDocument Dim newf = DTE.ItemOperations.OpenFile(fileName, Constants.vsViewKindTextView) oldactive.Activate() Return newf End Function 


Since resource files can be several for different languages, we memorize them in an array.

 Dim resourceFiles() As String = {" \ProjectResources.resx", _ " \ProjectResources.kk-KZ.resx"} 


You can optimize this piece and try to automatically find files with resources.

Then we go over each resource file, open it, check the availability of the resource with the same name, and if not, add it.

 Dim paramName As String = ConvertToResourceName(caption) For Each item As String In resourceFiles Dim textDocument As TextDocument = FindWindow(item).Document.Object Dim startPoint = textDocument.StartPoint.CreateEditPoint, endPoint = textDocument.EndPoint.CreateEditPoint Dim root = XElement.Parse(startPoint.GetText(endPoint)) Dim param As XElement = root.Elements("data").FirstOrDefault(Function(el) el.Attribute("name") = paramName) If (param Is Nothing) Then root.Add(New XElement("data", New XAttribute("name", paramName) _ , New XAttribute(XName.Get("space", "http://www.w3.org/XML/1998/namespace"), "preserve") _ , New XElement("value", caption))) startPoint.ReplaceText(endPoint, root.ToString(), vsEPReplaceTextOptions.vsEPReplaceTextAutoformat) textDocument.Parent.Save() End If Next 


The resource file is a simple xml-ku with data fields, respectively, the easiest way to directly modify the entire contents of the file (the content is obtained from the open document) and then completely update the entire document.

After modifying the document - you need to save the changes, which will lead to the generation of a proxy class.

Note: for XElement to work, you need to add references to System.Xml, System.Xml.Linq;



Task 4. Replace the word selected by the user with the binding template to the new resource


Since the access to resources in xaml / cs / vb files is different, it is necessary to check the extension of the open file and form a line accordingly.

 Dim replaceString As String Select Case Path.GetExtension(doc.FullName).ToLower() Case ".xaml" : replaceString = "{Binding Source={StaticResource ResourceProvider},Path=ProjectResources." + paramName + "}" Case ".cs" : replaceString = "ProjectResources." + paramName Case Else : replaceString = paramName End Select bp.ReplaceText(ep, replaceString, 0) '   d.Selection.MoveToPoint(bp) d.Selection.SwapAnchor() d.Selection.MoveToPoint(ep, True) '    ,   




All macro is ready to use.



Advantages / disadvantages of using macros




from obvious flaws



The article provides a general approach to automating the formation of resource files, respectively, you can adjust it as you need.

Source: https://habr.com/ru/post/116988/



All Articles