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:
- analyze the phrase selected by the user (by pressing a certain key combination);
- find the translation of the phrase in google translate (so as not to strain the user by inventing the name of the resource);
- add a phrase to the previously specified resources (and it is necessary that the changes be made via VS, since we need a generated proxy class);
- replace the user selected phrase with a binding to the resource.
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
- the macro call can be hung up on hotkeys, through the menu item tools / options in the Keyboard section;
- it is very easy to correct the code, in case it is necessary to modify it a bit (for example, you can not write a complicated mechanism for finding files with resources, but simply start constant paths in the code and correct them if necessary);
- pretty easy debugging code;
- good integration with VS.
from obvious flaws
- you cannot write C # code;
- poor performance, start long enough;
- frequent departures, especially during debugging.
The article provides a general approach to automating the formation of resource files, respectively, you can adjust it as you need.