Localizing Microsoft ReportViewer
Microsoft ReportViewer Control can be localized by implementing the three IReportViewerMessages interfaces. However, the documentation of these interfaces lacks the original string values, which in turn makes it difficult to provide a proper translation. In this blog, a complete listing of these values and a Dutch implementation of IReportViewerMessages are presented.
Microsoft ReportViewer
Microsoft ReportViewer is a .NET control that can be used in both Windows and Web applications to render Microsoft Reports. Unlike the name suggests, the ReportViewer does a whole lot more than just view reports. The control is capable of loading (remote) RDL files, executing its contents and rendering the end result in various formats (including HTML, Excel, Word, PDF). Once rendered, the user can interact with the control in order to pass report parameters, navigate through multipage reports and more.
ReportViewer localized to the Dutch culture and language.
In my current project, we embedded ReportViewer inside an ASP MVC web application. As the audience of the application is Dutch, all content, including the ReportViewer, must be localized to the Dutch culture. Unfortunately, the language packs available for ReportViewer do not include Dutch, so I had to come up with an alternative solution.
The Good
Fortunately, Microsoft included a mechanism in the ReportViewer control which allows developers to override the default messages. This is achieved by implementing various interfaces in the parent application that utilizes ReportViewer and then passing these custom implementations to the ReportViewer control.
More specifically, these interfaces are IReportViewerMessages
, IReportViewerMessages2
and IReportViewerMessages3
, which reside in the Microsoft.Reporting.WebForms
namespace (or WinForms if you’re using that). These interfaces are essentially just collections of read-only string properties with static hardcoded values.
The Bad
While Microsoft was graceful enough to document these interfaces, their documentation lacks the original values of these string properties. As a result, anyone who wants to create a meaningful translation of these properties must deduce the meaning from the property’s name and description, rather than the original value. This can be quite challenging when faced with cryptic names such as ParameterAreaButtonToolTip
, NullValueText
or ClientPrintControlLoadFailed
.
The Ugly
While the lack of original values is a nuisance, it is in no means a showstopper. Poor translations can result in the user being faced with messages that seem out of place, but the application itself will remain functional. Or so I thought:
First attempt at localizing ReportViewer results in a FormatException
Most .NET developers will be familiar with this exception as it is the result of String.Format being invoked with a format string which does not match the expected objects to format. In other words, one or more of the strings which I am translating use the composite formatting feature. Without the original English values of these strings, one is left to guess which of these strings must contain formatting placeholders.
The Solution
To overcome this problem, I decompiled the ReportViewer assembly and extracted the original string values of these properties. Below is a version of my Dutch implementation of the IReportViewerMessages
interfaces. Each property includes the original text in English, so you could use it as a template for your own translations.
DutchReportViewerMessages.cs
1using System;2using System.Globalization;3using Microsoft.Reporting.WebForms;45namespace InfoSupport.SomeApplication6{7 public class DutchReportViewerMessages : IReportViewerMessages, IReportViewerMessages2, IReportViewerMessages38 {9 #region IReportViewerMessages Members1011 // English value: Back to Parent Report12 public string BackButtonToolTip13 {14 get { return "Terug naar het vorige rapport"; }15 }1617 // English value: Change Credentials18 public string ChangeCredentialsText19 {20 get { return "Wijzig Rechten"; }21 }2223 // English value: Change Credentials24 public string ChangeCredentialsToolTip25 {26 get { return "Wijzig Rechten"; }27 }2829 // English value: Current Page30 public string CurrentPageTextBoxToolTip31 {32 get { return "Huidige Pagina"; }33 }3435 // English value: Document Map36 public string DocumentMap37 {38 get { return "Document Map"; }39 }4041 // English value: Show / Hide Document Map42 public string DocumentMapButtonToolTip43 {44 get { return "Toon / Verberg Document Map"; }45 }4647 // English value: Export48 public string ExportButtonText49 {50 get { return "Exporteer"; }51 }5253 // English value: Export54 public string ExportButtonToolTip55 {56 get { return "Exporteer"; }57 }5859 // English value: Export Formats60 public string ExportFormatsToolTip61 {62 get { return "Exporteer Formaten"; }63 }6465 // English value: False66 public string FalseValueText67 {68 get { return "Onwaar"; }69 }7071 // English value: Find72 public string FindButtonText73 {74 get { return "Zoek"; }75 }7677 // English value: Find78 public string FindButtonToolTip79 {80 get { return "Zoek"; }81 }8283 // English value: Next84 public string FindNextButtonText85 {86 get { return "Volgende"; }87 }8889 // English value: Find Next90 public string FindNextButtonToolTip91 {92 get { return "Volgend Resultaat"; }93 }9495 // English value: First Page96 public string FirstPageButtonToolTip97 {98 get { return "Eerste Pagina"; }99 }100101 // English value: Enter a valid page number102 public string InvalidPageNumber103 {104 get { return "Voer een geldig paginanummer in"; }105 }106107 // English value: Last Page108 public string LastPageButtonToolTip109 {110 get { return "Laatste Pagina"; }111 }112113 // English value: Next Page114 public string NextPageButtonToolTip115 {116 get { return "Volgende Pagina"; }117 }118119 // English value: The entire report has been searched.120 public string NoMoreMatches121 {122 get { return "Het volledige rapport is doorzocht."; }123 }124125 // English value: NULL126 public string NullCheckBoxText127 {128 get { return "Geen waarde"; }129 }130131 // English value: Null132 public string NullValueText133 {134 get { return "Geen waarde"; }135 }136137 // English value: of138 public string PageOf139 {140 get { return "van"; }141 }142143 // English value: Show / Hide Parameters144 public string ParameterAreaButtonToolTip145 {146 get { return "Toon / Verberg Parameters"; }147 }148149 // English value: Password:150 public string PasswordPrompt151 {152 get { return "Wachtwoord:"; }153 }154155 // English value: Previous Page156 public string PreviousPageButtonToolTip157 {158 get { return "Vorige Pagina"; }159 }160161 // English value: Print162 public string PrintButtonToolTip163 {164 get { return "Afdrukken"; }165 }166167 // English value: Loading...168 public string ProgressText169 {170 get { return "Verwerken..."; }171 }172173 // English value: Refresh174 public string RefreshButtonToolTip175 {176 get { return "Vernieuwen"; }177 }178179 // English value: Find Text in Report180 public string SearchTextBoxToolTip181 {182 get { return "Zoek naar tekst binnen het rapport"; }183 }184185 // English value: <Select a Value>186 public string SelectAValue187 {188 get { return "<Selecteer een waarde>"; }189 }190191 // English value: (Select All)192 public string SelectAll193 {194 get { return "(Selecteer alles)"; }195 }196197 // English value: Select a format198 public string SelectFormat199 {200 get { return "Selecteer een formaat"; }201 }202203 // English value: The search text was not found.204 public string TextNotFound205 {206 get { return "De zoektekst is niet gevonden."; }207 }208209 // English value: Today is {0}210 public string TodayIs211 {212 get { return "Vandaag is {0}"; }213 }214215 // English value: True216 public string TrueValueText217 {218 get { return "Waar"; }219 }220221 // English value: Log In Name:222 public string UserNamePrompt223 {224 get { return "Gebruikersnaam:"; }225 }226227 // English value: View Report228 public string ViewReportButtonText229 {230 get { return "Toon Rapport"; }231 }232233 // English value: Zoom234 public string ZoomControlToolTip235 {236 get { return "Zoom"; }237 }238239 // English value: Page Width240 public string ZoomToPageWidth241 {242 get { return "Paginabreedte"; }243 }244245 // English value: Whole Page246 public string ZoomToWholePage247 {248 get { return "Volledige pagina"; }249 }250251 #endregion252253 #region IReportViewerMessages2 Members254255 // English value: Your browser does not support scripts or has been configured not to allow scripts.256 public string ClientNoScript257 {258 get { return "Uw browser ondersteunt geen JavaScript of deze ondersteuning is uitgeschakeld."; }259 }260261 // English value: Unable to load client print control.262 public string ClientPrintControlLoadFailed263 {264 get { return "Het laden van het client print control is niet gelukt."; }265 }266267 // English value: One or more data sources is missing a user name.268 public string CredentialMissingUserNameError(string dataSourcePrompt)269 {270 return "Een of meerdere databronnen missen een gebruikersnaam.";271 }272273 // English value is different for each Rendering Extension. See comment behind each type.274 public string GetLocalizedNameForRenderingExtension(string format)275 {276 switch (format)277 {278 case "XML" : return "XML databestand (.xml)"; // XML file with report data279 case "CSV" : return "CSV databestand (.csv)"; // CSV (comma delimited)280 case "PDF" : return "PDF document (.pdf)"; // PDF281 case "MHTML" : return "Webarchief (.mhtml)"; // MHTML (web archive)282 case "EXCEL" : return "Excel rekenblad (.xls)"; // Excel283 case "IMAGE" : return "Afbeelding (.tif)"; // TIFF file284 case "WORD" : return "Word document (.doc)"; // Word285 default : return null;286 }287 }288289 // English value: Select a value290 public string ParameterDropDownToolTip291 {292 get { return "Selecteer een waarde"; }293 }294295 // English value: Please select a value for the parameter '{0}'.296 public string ParameterMissingSelectionError(string parameterPrompt)297 {298 return String.Format(CultureInfo.CurrentCulture, "Selecteer een waarde voor de parameter '{0}'", parameterPrompt);299 }300301 // English value: Please enter a value for the parameter '{0}'. The parameter cannot be blank.302 public string ParameterMissingValueError(string parameterPrompt)303 {304 return String.Format(CultureInfo.CurrentCulture, "Selecteer een waarde voor de parameter '{0}'. De parameter mag niet leeg zijn.", parameterPrompt);305 }306307 #endregion308309 #region IReportViewerMessages3 Members310311 // English value: Loading...312 public string CalendarLoading313 {314 get { return "Verwerken..."; }315 }316317 // English value: Cancel318 public string CancelLinkText319 {320 get { return "Annuleer"; }321 }322323 // English value: pageCount if PageCountMode.Actual, else pageCount suffixed with a ?324 public string TotalPages(int pageCount, PageCountMode pageCountMode)325 {326 return string.Format(CultureInfo.CurrentCulture, "{0}{1}", pageCount, pageCountMode == PageCountMode.Estimate ? "~" : String.Empty);327 }328329 #endregion330 }331}
Naturally, it would be irresponsible of me to paste code without some matching unit tests:
DutchReportViewerMessagesTests.cs
1using System;2using Microsoft.VisualStudio.TestTools.UnitTesting;3using InfoSupport.SomeApplication;45namespace InfoSupport.SomeApplication.Tests6{7 [TestClass]8 public class DutchReportViewerMessagesTests9 {10 [TestMethod]11 public void TranslatedStringsTests()12 {13 var m = new DutchReportViewerMessages();14 Assert.IsTrue(!String.IsNullOrEmpty(m.BackButtonToolTip));15 Assert.IsTrue(!String.IsNullOrEmpty(m.CalendarLoading));16 Assert.IsTrue(!String.IsNullOrEmpty(m.CancelLinkText));17 Assert.IsTrue(!String.IsNullOrEmpty(m.ChangeCredentialsText));18 Assert.IsTrue(!String.IsNullOrEmpty(m.ChangeCredentialsToolTip));19 Assert.IsTrue(!String.IsNullOrEmpty(m.ClientNoScript));20 Assert.IsTrue(!String.IsNullOrEmpty(m.ClientPrintControlLoadFailed));21 Assert.IsTrue(!String.IsNullOrEmpty(m.CredentialMissingUserNameError(null)));22 Assert.IsTrue(!String.IsNullOrEmpty(m.CurrentPageTextBoxToolTip));23 Assert.IsTrue(!String.IsNullOrEmpty(m.DocumentMap));24 Assert.IsTrue(!String.IsNullOrEmpty(m.DocumentMapButtonToolTip));25 Assert.IsTrue(!String.IsNullOrEmpty(m.ExportButtonText));26 Assert.IsTrue(!String.IsNullOrEmpty(m.ExportButtonToolTip));27 Assert.IsTrue(!String.IsNullOrEmpty(m.ExportFormatsToolTip));28 Assert.IsTrue(!String.IsNullOrEmpty(m.FalseValueText));29 Assert.IsTrue(!String.IsNullOrEmpty(m.FindButtonText));30 Assert.IsTrue(!String.IsNullOrEmpty(m.FindButtonToolTip));31 Assert.IsTrue(!String.IsNullOrEmpty(m.FindNextButtonText));32 Assert.IsTrue(!String.IsNullOrEmpty(m.FindNextButtonToolTip));33 Assert.IsTrue(!String.IsNullOrEmpty(m.FirstPageButtonToolTip));34 Assert.IsTrue(!String.IsNullOrEmpty(m.InvalidPageNumber));35 Assert.IsTrue(!String.IsNullOrEmpty(m.LastPageButtonToolTip));36 Assert.IsTrue(!String.IsNullOrEmpty(m.NextPageButtonToolTip));37 Assert.IsTrue(!String.IsNullOrEmpty(m.NoMoreMatches));38 Assert.IsTrue(!String.IsNullOrEmpty(m.NullCheckBoxText));39 Assert.IsTrue(!String.IsNullOrEmpty(m.NullValueText));40 Assert.IsTrue(!String.IsNullOrEmpty(m.PageOf));41 Assert.IsTrue(!String.IsNullOrEmpty(m.ParameterAreaButtonToolTip));42 Assert.IsTrue(!String.IsNullOrEmpty(m.ParameterDropDownToolTip));43 Assert.IsTrue(!String.IsNullOrEmpty(m.ParameterMissingSelectionError(String.Empty)));44 Assert.IsTrue(!String.IsNullOrEmpty(m.ParameterMissingValueError(String.Empty)));45 Assert.IsTrue(!String.IsNullOrEmpty(m.PasswordPrompt));46 Assert.IsTrue(!String.IsNullOrEmpty(m.PreviousPageButtonToolTip));47 Assert.IsTrue(!String.IsNullOrEmpty(m.PrintButtonToolTip));48 Assert.IsTrue(!String.IsNullOrEmpty(m.ProgressText));49 Assert.IsTrue(!String.IsNullOrEmpty(m.RefreshButtonToolTip));50 Assert.IsTrue(!String.IsNullOrEmpty(m.SearchTextBoxToolTip));51 Assert.IsTrue(!String.IsNullOrEmpty(m.SelectAll));52 Assert.IsTrue(!String.IsNullOrEmpty(m.SelectAValue));53 Assert.IsTrue(!String.IsNullOrEmpty(m.SelectFormat));54 Assert.IsTrue(!String.IsNullOrEmpty(m.TextNotFound));55 Assert.IsTrue(!String.IsNullOrEmpty(m.TodayIs));56 Assert.IsTrue(!String.IsNullOrEmpty(m.TrueValueText));57 Assert.IsTrue(!String.IsNullOrEmpty(m.UserNamePrompt));58 Assert.IsTrue(!String.IsNullOrEmpty(m.ViewReportButtonText));59 Assert.IsTrue(!String.IsNullOrEmpty(m.ZoomControlToolTip));60 Assert.IsTrue(!String.IsNullOrEmpty(m.ZoomToPageWidth));61 Assert.IsTrue(!String.IsNullOrEmpty(m.ZoomToWholePage));62 }6364 [TestMethod]65 public void FormattableStringsTest()66 {67 var m = new DutchReportViewerMessages();68 var parameter = "abc123";69 Assert.IsTrue(m.ParameterMissingSelectionError(parameter).Contains(parameter));70 Assert.IsTrue(m.ParameterMissingValueError(parameter).Contains(parameter));71 Assert.IsTrue(String.Format(m.TodayIs, parameter).Contains(parameter));72 }7374 [TestMethod]75 public void GetLocalizedNameForRenderingExtensionTest()76 {77 var m = new DutchReportViewerMessages();78 string[] knownExtensions = { "XML", "CSV", "PDF", "MHTML", "EXCEL", "IMAGE", "WORD"};79 foreach (var ext in knownExtensions)80 Assert.IsTrue(!String.IsNullOrEmpty(m.GetLocalizedNameForRenderingExtension(ext)));81 }8283 [TestMethod]84 public void GetLocalizedNameForRenderingExtensionTest_UnknownExtensionReturnsNull()85 {86 var m = new DutchReportViewerMessages();87 string[] unknownExtensions = { "DOCX", "PNG", "JPG", "JPEG" };88 foreach (var ext in unknownExtensions)89 Assert.IsNull(m.GetLocalizedNameForRenderingExtension(ext));90 }9192 [TestMethod]93 public void TotalPagesTest()94 {95 var m = new DutchReportViewerMessages();96 Assert.AreEqual("10", m.TotalPages(10, Microsoft.Reporting.WebForms.PageCountMode.Actual));97 Assert.AreEqual("10~", m.TotalPages(10, Microsoft.Reporting.WebForms.PageCountMode.Estimate));98 }99 }100}
Additionally, below is a table of all properties with their respective English and Dutch translation. Note that this table does not include the methods that are part of the interfaces.
IREPORTVIEWERMESSAGES
PROPERTY NAME | ORIGINAL ENGLISH VALUE | DUTCH TRANSLATION |
---|---|---|
BackButtonToolTip | Back to Parent Report | Terug naar het vorige rapport |
ChangeCredentialsText | Change Credentials | Wijzig Rechten |
ChangeCredentialsToolTip | Change Credentials | Wijzig Rechten |
CurrentPageTextBoxToolTip | Current Page | Huidige Pagina |
DocumentMap | Document Map | Document Map |
DocumentMapButtonToolTip | Show / Hide Document Map | Toon / Verberg Document Map |
ExportButtonText | Export | Exporteer |
ExportButtonToolTip | Export | Exporteer |
ExportFormatsToolTip | Export Formats | Exporteer Formaten |
FalseValueText | False | Onwaar |
FindButtonText | Find | Zoek |
FindButtonToolTip | Find | Zoek |
FindNextButtonText | Next | Volgende |
FindNextButtonToolTip | Find Next | Volgend Resultaat |
FirstPageButtonToolTip | First Page | Eerste Pagina |
InvalidPageNumber | Enter a valid page number | Voer een geldig paginanummer in |
LastPageButtonToolTip | Last Page | Laatste Pagina |
NextPageButtonToolTip | Next Page | Volgende Pagina |
NoMoreMatches | The entire report has been searched. | Het volledige rapport is doorzocht. |
NullCheckBoxText | NULL | Geen waarde |
NullValueText | Null | Geen waarde |
PageOf | of | van |
ParameterAreaButtonToolTip | Show / Hide Parameters | Toon / Verberg Parameters |
PasswordPrompt | Password: | Wachtwoord: |
PreviousPageButtonToolTip | Previous Page | Vorige Pagina |
PrintButtonToolTip | Afdrukken | |
ProgressText | Loading... | Verwerken... |
RefreshButtonToolTip | Refresh | Vernieuwen |
SearchTextBoxToolTip | Find Text in Report | Zoek naar tekst binnen het rapport |
SelectAValue | <Select a Value> | <Selecteer een waarde> |
SelectAll | (Select All) | (Selecteer alles) |
SelectFormat | Select a format | Selecteer een formaat |
TextNotFound | The search text was not found. | De zoektekst is niet gevonden. |
TodayIs | Today is 0 | Vandaag is 0 |
TrueValueText | True | Waar |
UserNamePrompt | Log In Name: | Gebruikersnaam: |
View Report | ViewReportButtonText | Toon Rapport |
ZoomControlToolTip | Zoom | Zoom |
ZoomToPageWidth | Page Width | Paginabreedte |
ZoomToWholePage | Whole Page | Volledige pagina |
IREPORTVIEWERMESSAGES2
PROPERTY NAME | ORIGINAL ENGLISH VALUE | DUTCH TRANSLATION |
---|---|---|
ClientNoScript | Your browser does not support scripts or has been configured not to allow scripts. | Uw browser ondersteunt geen JavaScript of deze ondersteuning is uitgeschakeld. |
ClientPrintControlLoadFailed | Unable to load client print control. | Het laden van het client print control is niet gelukt. |
ParameterDropDownToolTip | Select a value | Selecteer een waarde |
IREPORTVIEWERMESSAGES3
PROPERTY NAME | ORIGINAL ENGLISH VALUE | DUTCH TRANSLATION |
---|---|---|
CalendarLoading | Loading... | Verwerken... |
CancelLinkText | Cancel | Annuleer |
Additional notes
- Returning null will make ReportViewer default back to the original value. Use this for string properties or methods you do not wish to translate.
- Getting ReportViewer to pick up your custom implementation requires a change in your application’s web.config or app.config. Add
<add key="ReportViewerMessages" value="MyClass, MyAssembly" />
to your appSettings and substitute MyClass and MyAssembly with the name of your implementing class and the assembly it resides in.
Summary
Microsoft ReportViewer Control can be localized by implementing the three IReportViewerMessages interfaces. However, the documentation of these interfaces lack the original string values, which in turn makes it difficult to provide a proper translation. In this blog, a complete listing of these values and a Dutch implementation of IReportViewerMessages are presented.