We’ve recently translated one of our applications and thought we’d share the tools & techniques we used. In particular, every time we perform some ASP.NET translation, we hit a few gotchas. We kept facing the same problems every time we worked on translation, so we figured we might as well write everything down in a post for everyone’s benefit.

Technique

Because we’re translating an ASP.NET WebForms application, the main process is to open an *.aspx or *.ascx, switch to Design view, and perform Tools –> Generate Local Resource. This generates a *.resx file and adds the relevant markup in your source file. Tools are available to perform the actual translation and create the *.resx files in other languages.

The core issue here is that you need to perform this operation for each individual file. Potentially thousands of times and/or until you go crazy. (Personally, I find it frustrating that bulk resource generation is not a core VS.NET feature. )

 

Step 1 - Bulk Generate Local Resource

Instead of wasting our time opening each individual file, we found a macro on this forum. The macro does not run in VS.NET 2012, so we loaded up our old VS.NET 2010 and ran it from there.

The macro wasn’t flawless – it sometimes randomly crashed after processing files for half an hour. Deleting Visual Studio’s *.suo file and restarting it seemed to help.

 

Step 2 - Realize that VS.NET corrupted your files

I assume one of the reasons bulk resource generation is not a core VS.NET feature is because the feature is (in addition to being slow) partially broken.

Gotcha: Inline scripts/comments are sometimes deleted.

At a high level, any script blocks in your *.aspx/*.ascx files are vulnerable to deletion. Generate Local Resource will simply strip them out if they are contained in an <asp:UpdatePanel …/>.   We filed a bug on Microsoft Connect which was not deemed important enough to be fixed.

This is appalling because it will introduce pernicious bugs in your application that only show up at runtime, if you don’t pay close attention to each and every individual file.

For example:

<script>alert('<%="Some Constant" %>');</script>
<script>alert('<%= btnSomething.ClientID %>');</script>
<%-- <asp:Button runat="server" id="btn" Text="Some button that I may need to re-enable later"/> --%>
<% 
if (Request.QueryString["test"]=="bye") 
    Response.Write("Goodbye World"); 
else 
    Response.Write("Hello World"); 
%>

Would become the following, after Generate Local Resource, because everything

<script>alert('');</script>
<script>alert('');</script>

 

Admittedly, some of the inline code above is bad practice.  However, the silent deletion causes needle-in-a-haystack type bugs at runtime.

We decided to remove all of our inline code blocks from our code to avoid having issues during local resource generation.

Gotcha: culture=”auto” and uiculture=”auto” is added to all *.aspx files

These values, added in the *.aspx header, force the page to change culture based on the browser’s settings. In our application, this was not desirable as it by-passed the logic defined in our Global.asax file. (Our users can change their language via the web applications itself, not via their web browser settings.)

For more information, see this post by Rick Strahl.

Gotcha: Nested controls can be problematic

When trying to localize a LinkButton containing an Image and literal, the Image will be dropped.

<asp:LinkButton ID="lnkHello" runat="server" OnClick="lnkHello_Click">
    <asp:Image ID="imgEdit" runat="server" ImageUrl="~/images/icons/edit.gif"></asp:Image>
    HELLO!
</asp:LinkButton>


Becomes:

<asp:LinkButton ID="lnkHello" runat="server" OnClick="lnkHello_Click" meta:resourcekey="abcdef">    
    HELLO!
</asp:LinkButton>

 

To solve this issue, the nested controls must be separated.

Gotcha: Ajax:Accordion breaks during Generate Local Resource

If you are using <ajax:Accordion ../> from the ASP.NET Ajax control toolkit, be aware that it will be corrupted after generating *.resx files. The fix is simple: delete the erroneously added Accordion Extender.

 

Step 3 – Extract other hardcoded strings.

Your *.aspx/*.ascx files and your *.cs files may contain additional strings which must be extracted. Back in 2008, we had create a Macro to help with this process but in this iteration, we simply used JetBrains ReSharper. The VS.NET plugin made it easy to find strings which had not been extracted, and push them into *.resx files.  ReSharper is jam-packed with other useful features, but we’ve found that it does have a significant impact on performance in our solution.

 

Step 4 – Perform the actual translation

Back in 2008, we released a web application to help translate RESX files. We’re no longer using this application – there are better options out there. We picked Zeta Resource Editor and it worked nicely.

Conclusion

The tools available today are much better than they were five years ago, but one piece of the puzzle (Generate Local Resource) is still far from perfect. We’d love to see an improved version (in either VS.NET or ReSharper) which would:

  • Not delete inline code or comments inside UpdatePanels
  • Would be configurable (insert culture=”auto” everywhere? etc.)
  • Would produce reviewable reports of any changes which are not additions of meta:resourcekey to controls. (Performing a diff with regular tools is very time consuming given the thousands of changes.)
  • Could be executed in batch in a timely manner across a project

PS: Big thanks to @plgelinas for his research efforts for this project.