.NET Tutorial

Handling the Culture-Sensitive Methods

In the results of the scan from the previous lesson, we see that Globalyzer detected three categories of internationalization issues within the project's C# source. As before, it detected embedded strings. But this time it also uncovered some locale-sensitive methods and general patterns. It is up to the developer to ensure that those methods and constructors are refactored as needed to work in the desired culture. We will start our fixing process by examining these constructors and methods.

  1. Select Locale-Sensitive Methods in the Results dropdown in the Scan Results pane. This will display the list of culture-sensitive constructors discovered during the scan. Order the results alphabetically by clicking on the column header "Issue".

  2. Double click anywhere in the top row, which should be a CreateSpecificCulture declaration. You will be taken to a block of code in the source viewer that handles the instantiation of a CreateSpecificCulture object within the application. This block of code contains several of the Locale-Sensitive Method and General Pattern detections within your Scan Results.

    CultureInfo locale = CultureInfo.CreateSpecificCulture(Context.Request.UserLanguages[0]);
    Thread.CurrentThread.CurrentCulture = locale;
    Thread.CurrentThread.CurrentUICulture = locale;

    The purpose of scanning for CreateSpecificCulture is to ensure that it is dynamically instantiated according to the culture of the current user. In addition, the correct instantiation of CreateSpecificCulture needs to be set in Thread.CurrentThread so that it is accessible to all of the culture-sensitive methods throughout your code.

    It would be a mistake to instantiate CreateSpecificCulture in various places throughout the code passing in different culture arguments. This would result in inconsistent formatting of DateTime values, strings, currencies, etc. in your User Interface. CurrentCulture and CurrentUICulture are the properties of Thread.CurrentThread that must be set, as mentioned earlier, to ensure that the culture-sensitive methods in your application perform according to the correct CreateSpecificCulture. To better understand the purpose of these classes, let's look at Globalyzer's just-in-time help system.

  3. Look in the All Predicted Scan Summary view, located at the bottom left of the Workbench. Scroll down so you can see Locale-Sensitive Methods and General Patterns summaries.

  4. Click on the top link for CreateSpecificCulture. You will see a page that provides helpful information about this class and including its purpose within C# software internationalization and how to use it properly in your code.

  5. After reading about CreateSpecificCulture you should see that it is used properly within our sample code. It is set according to the locale of the end user through the Context object in this case. Next, the sample code sets the newly instantiated CreateSpecificCulture as the CurrentCulture and CurrentUICulture in the Thread.CurrentThread object. This will provide access to the correct set of culture rules for all other culture-sensitive functions in our application.

  6. Now look at the General Patterns section of the All Predicted Scan Summary. If you click on the link for CurrentCulture and then for CurrentUICulture in the All Predicted Scan Summary, you will see that CurrentCulture is responsible for the proper functioning of culture-specific methods throughout the code and CurrentUICulture is responsible for the proper retrieval of translated-strings at runtime. Both are crucial to the proper functioning of an internationalized C# application.

  7. Similarly, the Write method also relies on the culture being set properly, and so is not an issue here.

  8. Return to the Results Table, switch to General Patterns, and double click on the first row where the Reason field contains the text \bDateTime\.Now\b.

    Note: The "\b" is a regular expression code for word boundary, while the "\." causes the period to be used as such, instead of as a wildcard. This expression is therefore looking for "DateTime.Now".

    If you look at the information Globalyzer provides under the Predicted Active Scan Summary for \bDateTime\.Now\b, you'll see that developers are cautioned to make sure that the CultureInfo value is set properly in the current thread. We already know that the culture has been set properly. If you look specifically in the help provided via the DateTime.Now link, you'll see a reference to the very block of code being used in this example.

    As pointed out in the Globalyzer help links for DateTime.Now, the code in our project demonstrates the wrong way to use the DateTime.Now property. This code example pieces together a display date in a fashion that is specific to U.S. English. It will be incorrect for even other English-speaking locales, such as the United Kingdom.

                  string theMonth = 
                  	DateTime.Now.Month.ToString();
                  string theDay = DateTime.Now.Day.ToString();
                  string theYear = DateTime.Now.Year.ToString();
                  string theTime = DateTime.Now.Hour.ToString() + ":" +
                  		DateTime.Now.Minute.ToString() + ":" +
                  		DateTime.Now.Second.ToString();
                  string displayTime = "Timestamp: " +
                  		theTime + " " + theMonth + "/" + 
                  		theDay + "/" + theYear;
    	      return displayTime;
    			  

    The correct way to format a long date string such as the one attempted above, would be to call the ToString method on DateTime.Now, passing in the U format string, which specifies a long date value. This way, if the correct culture has been set in the current thread, the date formatting rules of the user's culture will be used to generate the correct long-date string. Even better, the above block of code is rewritten in two lines instead of eight. Replace the existing makeTimeStamp method with the following code and press Ctrl-S to save your changes:

                  public string makeTimeStamp()
                  {
                    string timestamp = "Timestamp: " + 
                      DateTime.Now.ToString("U");
                    return timestamp;
                  }
                  
  9. Globalyzer will automatically rescan the modified source file. You should now see only the Locale-Sensitive Methods and General Patterns that you have already reviewed along with a single mention to the new line containing "DateTime.Now".

  10. Let's tell Globalyzer to ignore these calls in subsequent scans so we don't waste time checking them again later. There are two ways to do this: inserting Ignore This Line Comment text into the source files, or setting the Status of the rows to something other than Active or ToDo. The first approach is more permanent: View the Locale-Sensitive Method results and Double-click on the Issue CreateSpecificCulture from the source file "AControl.cs". This will open the source file in the Source Files view.

  11. To preserve the integrity of the following block of code, move your cursor up to the line "CultureInfo locale = ". Select Fix Code => Start Ignore Comment from the Menu Bar. A comment is placed in a new line above the row with the CultureInfo reference. Looking at the source code, you can see there are several of the General pattern issues present in the next few subsequent lines. Click at the start of the code line following the CurrentUICulture reference and select Fix Code => End Ignore Comment.

    Globalyzer will automatically rescan the file, ignoring any issues found between the Start Ignore and End Ignore comments. The following times Globalyzer scans this file it will continue to ignore these issues.

  12. The second, though less permanent way, to skip lines in subsequent scans is to set the Status of the rows to something other than Active or ToDo. As long as you don't change the Rule Set, delete your scan results or significantly alter the source file, Globalyzer will maintain the Status setting. The rows will therefore not appear in any Active Scan Results. Select the rest of the rows in the Scan Results for either General Patterns or Locale Sensitive Methods (i.e. click on first row, then press ctrl-A.). Right-click them and select Ignore. Repeat so that the step is performed for both General Patterns and Locale Sensitive Methods. The Active Scan Results should now be empty.

In the next lesson we will address the embedded C# strings.