Skip to content

6. Internacjonalizacja widoków

W tym miejscu omówimy problem internacjonalizacji widoków. Jest to złożona kwestia, której dobry opis można znaleźć w poniższym artykule Scotta Hanselmana:

[http://www.hanselman.com/blog/GlobalizationInternationalizationAndLocalizationInASPNETMVC3JavaScriptAndJQueryPart1.aspx]

Na początek przytoczymy jego definicje różnych terminów związanych z internacjonalizacją widoków:

Internacjonalizacja (i18n)
sprawienie, by aplikacja obsługiwała różne języki i ustawienia regionalne
Lokalizacja (l10n)
zapewnienie, że aplikacja obsługuje konkretną parę językowo-lokalizacyjną
Globalizacja
połączenie Internationalisation i Localisation
Język
język używany – oznaczony kodem ISO (fr: francuski, es: hiszpański, en: angielski, ...)
Ustawienia regionalne
wariant języka – oznaczony również kodem ISO (en_GB: angielski brytyjski, en_US: angielski amerykański, ...)

Rozpocznijmy od pierwszego przykładu.

6.1. Lokalizacja liczb rzeczywistych

W poprzednim formularzu wprowadzania danych można zauważyć nieprawidłowość:

 

W przypadku liczby rzeczywistej wpisaliśmy [0,3], ale nie zostało to zaakceptowane. Należy wpisać [0.3]:

 

Oczekiwany format to zatem format anglosaskie, a nie francuski. Szukając w Internecie, można znaleźć rozwiązania. Oto jedno z nich.

Akcje [GET] i [POST] zmieniają się na następujące:


    // Akcja13-GET
    [HttpGet]
    public ViewResult Action13Get()
    {
      return View("Action13Get", new ViewModel11());
}

    // Action13-POST
    [HttpPost]
    public ViewResult Action13Post(ViewModel11 modèle)
    {
      return View("Action13Get", modèle);
}

Widok [Action13Get.cshtml] jest identyczny z widokiem [Action12Get.cshtml], z wyjątkiem skryptów JavaScript:


<head>
  <meta name="viewport" content="width=device-width" />
  <title>Action13Get</title>
  <link rel="stylesheet" href="~/Content/Site.css" />
  <script type="text/javascript" src="~/Scripts/jquery-1.8.2.min.js"></script>
  <script type="text/javascript" src="~/Scripts/jquery.validate.min.js"></script>
  <script type="text/javascript" src="~/Scripts/jquery.validate.unobtrusive.min.js"></script>
...
  <script type="text/javascript" src="~/Scripts/myscripts.js"></script>

</head>

Uwaga: w wierszu 5 należy dostosować wersję pliku jQuery do posiadanej wersji programu Visual Studio.

  • W wierszu 9 dodaliśmy skrypt [myscripts.js] . Oto jego treść:

// http://blog.instance-factory.com/?p=268
$.validator.methods.number = function (value, element) {
  return this.optional(element) ||
      !isNaN(Globalize.parseFloat(value));
}

$.validator.methods.date = function (value, element) {
  return this.optional(element) ||
      !isNaN(Globalize.parseDate(value));
}

jQuery.extend(jQuery.validator.methods, {
  range: function (value, element, param) {
    //Użyj wtyczki Globalization do analizy wartości        
    var val = Globalize.parseFloat(value);
    return this.optional(element) || (
        val >= param[0] && val <= param[1]);
  }
});

// podczas ładowania dokumentu
$(document).ready(function () {
  var culture = 'fr-FR';
  Globalize.culture(culture);
});

W wierszu 1 wskazałem, gdzie znaleziono ten skrypt. Nie będę próbował go wyjaśniać, ponieważ sam go nie rozumiem. Javascript bywa czasem niezrozumiały. W wierszach 4, 9 i 15 wykorzystywany jest obiekt [Globalize]. Jest on dostarczany przez bibliotekę JQuery Globalization, którą można uzyskać za pomocą [NuGet]:

  • w [1] należy zarządzać pakietami [NuGet] z projektu [Exemple-03];
  • w [2] przejrzyj pakiety online;
  • w [3] wpisz hasło [globalization];
  • w [4] zainstaluj pakiet [Globalize] z projektu JQuery.

Po zainstalowaniu pakietu [Globalize] w folderze [Scripts] pojawi się nowa gałąź:

  • w [1] utworzono folder [globalize] zawierający główny skrypt [globalize.js];
  • w [2] główny skrypt [globalize.js] został uzupełniony o skrypty specyficzne dla danego języka i ustawień regionalnych;
  • w [3] skrypty specyficzne dla języka francuskiego wraz z ustawieniami regionalnymi (wariantami) belgijskimi (BE), kanadyjskie (CA), francuskie (FR), szwajcarskie (CH), luksemburskie (LU), monakijskie (MC).

Skrypt [globalize.js] oraz skrypt dotyczący naszej kultury [globalize.culture.fr-FR.js] muszą znaleźć się na liście skryptów zawartych na naszej stronie [Action13Get.cshtml]:


<head>
  <meta name="viewport" content="width=device-width" />
  <title>Action13Get</title>
...
  <script type="text/javascript" src="~/Scripts/globalize/globalize.js"></script>
  <script type="text/javascript" src="~/Scripts/globalize/cultures/globalize.culture.fr-FR.js"></script>
  <script type="text/javascript" src="~/Scripts/myscripts.js"></script>
</head>
  • wiersz 5: skrypt [globalize];
  • wiersz 6: skrypt [globalize.culture.fr-FR.js];
  • wiersz 7: skrypt [myscripts.js];

Wróćmy do tego ostatniego skryptu:


// http://blog.instance-factory.com/?p=268
$.validator.methods.number = function (value, element) {
  return this.optional(element) ||
      !isNaN(Globalize.parseFloat(value));
}

...

// podczas ładowania dokumentu
$(document).ready(function () {
  var culture = 'fr-FR';
  Globalize.culture(culture);
});

Wiersze 10–13 ustawiają kulturę po stronie klienta na [fr-FR]:

  • wiersz 10: funkcja JQuery [ready] jest wykonywana, gdy dokument, w którym znajduje się skrypt, został w całości załadowany przez przeglądarkę;
  • wiersze 11–12: ustawia się kulturę po stronie klienta na [fr-FR]. W tym celu plik [globalize.culture.fr-FR.js] musi zostać dołączony do listy skryptów JavaScript powiązanych z dokumentem.

Teraz możemy przetestować nową aplikację:

 

Możemy teraz wpisać [0,3] jako liczbę rzeczywistą, czego wcześniej nie można było zrobić. Pojawia się jednak kolejna anomalia:

 

W powyższym przykładzie walidacja po stronie klienta pozwala na wpisanie [11.2] w notacji anglosaskiej. Wartość ta nie jest akceptowana po stronie serwera podczas walidacji formularza:

 

Należy wpisać [11,2], a wtedy działa to zarówno po stronie klienta, jak i serwera. Po stronie klienta notacja anglosaska nie powinna być akceptowana. To powinno być możliwe...

Przejdźmy teraz do kwestii internacjonalizacji widoków. Będziemy kontynuować na przykładzie poprzedniego formularza, udostępniając go w dwóch językach: francuskim i angielskim.

6.2. Zarządzanie kulturą

Język widoków jest kontrolowany przez obiekt [Thread.CurrentThread.CurrentUICulture]. Aby wyświetlić strony w kulturze [fr-FR], należy wpisać:

Thread.CurrentThread.CurrentUICulture=new CultureInfo("fr-FR");

Lokalizacja (daty, liczby, waluty, godziny itp.) jest kontrolowana przez obiekt [Thread.CurrentThread.CurrentCulture]. Podobnie jak w poprzednim przykładzie, należy wpisać:

Thread.CurrentThread.CurrentCulture=new CultureInfo("fr-FR");

Te dwie instrukcje mogłyby znajdować się w konstruktorze każdego kontrolera aplikacji. Można jednak również wyodrębnić ten wspólny dla wszystkich kontrolerów kod. Pójdziemy tą drogą.

Tworzymy dwa nowe kontrolery:

  
  • [I18NController] będzie klasą nadrzędną dla wszystkich kontrolerów wykorzystujących internacjonalizację;
  • [SecondController] to przykładowy kontroler wywodzący się z klasy [I18NController].

Kod kontrolera [I18NController] wygląda następująco:


using System.Threading;
using System.Web;
using System.Web.Mvc;

namespace Exemples.Controllers
{
  public abstract class I18NController : Controller
  {
    public I18NController()
    {
      // pobierany jest kontekst bieżącego żądania
      HttpContext httpContext = HttpContext.Current;
      // sprawdzamy żądanie pod kątem obecności parametru [lang]
      // szukamy go wśród parametrów URL
      string langue = httpContext.Request.QueryString["lang"];
      if (langue == null)
      {
        // szuka się go wśród przesłanych parametrów
        langue = httpContext.Request.Form["lang"];
      }
      if (langue == null)
      {
        // szuka się go w sesji użytkownika
        langue = httpContext.Session["lang"] as string;
      }
      if (langue == null)
      {
        // pierwszy parametr nagłówka HTTP AcceptLanguages
        langue = httpContext.Request.UserLanguages[0];
      }
      if (langue == null)
      {
        // kultura fr-FR
        langue = "fr-FR";
      }
      // ustawiamy język w sesji
      httpContext.Session["lang"] = langue;
      // zmiana ustawień regionalnych wątku            
      Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo(langue);
      Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture;
    }
  }
}
  • wiersz 7: [I18NController] wywodzi się z klasy [Controller];
  • wiersz 7: klasa jest zadeklarowana jako [abstract], aby uniemożliwić jej bezpośrednią instancjonację: może być używana wyłącznie jako klasa pochodna;
  • wiersz 9: konstruktor klasy – zostanie wykonany przy każdym instancjonowaniu kontrolera pochodnego od klasy [I18NController];
  • wiersz 12: pobierany jest kontekst żądania HTTP przetwarzanego obecnie przez kontroler;
  • wiersz 15: zakłada się, że język jest określony przez parametr [lang], który można znaleźć w różnych miejscach. Szuka się w następującej kolejności:
    • wiersz 15: w parametrach URL i [?lang=en-US],
    • wiersz 19: w przesłanych parametrach [lang=de],
    • wiersz 24: w sesji użytkownika,
    • wiersz 29: w preferencjach językowych przesłanych przez klienta HTTP,
    • wiersz 26: jeśli nic nie znaleziono, ustawia się kulturę na [fr-FR];
  • wiersz 37: zapisujemy ustawienie kulturowe w sesji. To właśnie tam zostanie ono odzyskane podczas kolejnych zapytań. Użytkownik będzie mógł je zmienić, wprowadzając je w parametrach polecenia GET lub POST;
  • wiersze 39–40: ustala się ustawienia regionalne widoku, który zostanie wyświetlony po przetworzeniu bieżącego zapytania.

Kontroler [SecondController] będzie wyglądał następująco:


using Exemple_03.Models;
using Exemples.Controllers;
using System.Web.Mvc;

namespace Exemple_03.Controllers
{
    public class SecondController : I18NController
    {
      // Akcja14-GET
      [HttpGet]
      public ViewResult Action14Get()
      {
        return View("Action14Get", new ViewModel14());
      }

      // Akcja14-POST
      [HttpPost]
      public ViewResult Action14Post(ViewModel14 modèle)
      {
        return View("Action14Get", modèle);
      }
    }
}
  • wiersz 7: [SecondController] wywodzi się z [I18NController]. W ten sposób mamy pewność, że kultura widoku, który ma zostać wyświetlony, została zainicjowana;
  • wiersz 13: wykorzystujemy szablon widoku [ViewModel14], który zamierzamy przedstawić;
  • wiersze 13 i 20: widok [Action14Get.cshtml] zapewnia wyświetlenie formularza.

6.3. Internacjonalizacja szablonu widoku [ViewModel14]

Szablon widoku [ViewModel14] wygląda następująco:


using Exemple_03.Resources;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.Net.Mail;

namespace Exemple_03.Models
{
  public class ViewModel14 : IValidatableObject
  {

    [Required(ErrorMessageResourceType = typeof(MyResources), ErrorMessageResourceName = "infoRequise")]
    [Display(ResourceType = typeof(MyResources), Name = "chaineaumoins4")]
    [RegularExpression(@"^.{4,}$", ErrorMessageResourceType = typeof(MyResources), ErrorMessageResourceName = "infoIncorrecte")]
    public string Chaine1 { get; set; }

    [Display(ResourceType = typeof(MyResources), Name = "chaineauplus4")]
    [Required(ErrorMessageResourceType = typeof(MyResources), ErrorMessageResourceName = "infoRequise")]
    [RegularExpression(@"^.{1,4}$", ErrorMessageResourceType = typeof(MyResources), ErrorMessageResourceName = "infoIncorrecte")]
    public string Chaine2 { get; set; }

    [Required(ErrorMessageResourceType = typeof(MyResources), ErrorMessageResourceName = "infoRequise")]
    [Display(ResourceType = typeof(MyResources), Name = "chaine4exactement")]
    [RegularExpression(@"^.{4,4}$", ErrorMessageResourceType = typeof(MyResources), ErrorMessageResourceName = "infoIncorrecte")]
    public string Chaine3 { get; set; }

    [Required(ErrorMessageResourceType = typeof(MyResources), ErrorMessageResourceName = "infoRequise")]
    [Display(ResourceType = typeof(MyResources), Name = "entier")]
    public int Entier1 { get; set; }

    [Display(ResourceType = typeof(MyResources), Name = "entierentrebornes")]
    [Required(ErrorMessageResourceType = typeof(MyResources), ErrorMessageResourceName = "infoRequise")]
    [Range(1, 100, ErrorMessageResourceType = typeof(MyResources), ErrorMessageResourceName = "infoIncorrecte")]
    public int Entier2 { get; set; }

    [Display(ResourceType = typeof(MyResources), Name = "reel")]
    [Required(ErrorMessageResourceType = typeof(MyResources), ErrorMessageResourceName = "infoRequise")]
    public double Reel1 { get; set; }

    [Display(ResourceType = typeof(MyResources), Name = "reelentrebornes")]
    [Required(ErrorMessageResourceType = typeof(MyResources), ErrorMessageResourceName = "infoRequise")]
    [Range(10.2, 11.3, ErrorMessageResourceType = typeof(MyResources), ErrorMessageResourceName = "infoIncorrecte")]
    public double Reel2 { get; set; }

    [Display(ResourceType = typeof(MyResources), Name = "email")]
    [Required(ErrorMessageResourceType = typeof(MyResources), ErrorMessageResourceName = "infoRequise")]
    [EmailAddress(ErrorMessageResourceType = typeof(MyResources), ErrorMessageResourceName = "infoIncorrecte", ErrorMessage="")]
    public string Email1 { get; set; }

    [Display(ResourceType = typeof(MyResources), Name = "date1")]
    [RegularExpression(@"\s*\d{2}/\d{2}/\d{4}\s*", ErrorMessageResourceType = typeof(MyResources), ErrorMessageResourceName = "infoIncorrecte")]
    [Required(ErrorMessageResourceType = typeof(MyResources), ErrorMessageResourceName = "infoRequise")]
    public string Regexp1 { get; set; }

    [Display(ResourceType = typeof(MyResources), Name = "date2")]
    [Required(ErrorMessageResourceType = typeof(MyResources), ErrorMessageResourceName = "infoRequise")]
    [DataType(DataType.Date)]
    public DateTime Date1 { get; set; }

    // walidacja
    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
      // lista błędów
      List<ValidationResult> résultats = new List<ValidationResult>();
      // ten sam komunikat o błędzie dla wszystkich
      string errorMessage=MyResources.ResourceManager.GetObject("infoIncorrecte", new CultureInfo(System.Web.HttpContext.Current.Session["lang"] as string)).ToString();

      // Data 1
      if (Date1.Date <= DateTime.Now.Date)
      {
        résultats.Add(new ValidationResult(errorMessage, new string[] { "Date1" }));
      }
      // E-mail 1
      try
      {
        new MailAddress(Email1);
      }
      catch
      {
        résultats.Add(new ValidationResult(errorMessage, new string[] { "Email1" }));
      }
      // Wyrażenie regularne 1
      try
      {
        DateTime.ParseExact(Regexp1, "dd/MM/yyyy", CultureInfo.CreateSpecificCulture("fr-FR"));
      }
      catch
      {
        résultats.Add(new ValidationResult(errorMessage, new string[] { "Regexp1" }));
      }

      // wyświetla się lista błędów
      return résultats;
    }
  }
}

Ten model jest zinternacjonalizowaną wersją poprzedniego modelu [ViewModel11]. Opiszemy mechanizm internacjonalizacji dla pierwszego atrybutu pierwszej właściwości. Pozostałe atrybuty działają według tego samego mechanizmu.


    [Required(ErrorMessageResourceType = typeof(MyResources), ErrorMessageResourceName = "infoRequise")]
public string Chaine1 { get; set; }

W poprzednim modelu [ViewModel11] linie te wyglądały następująco:


[Required(ErrorMessage = "Information requise")]
public string Chaine1 { get; set; }

W wersji zinternacjonalizowanej, w wierszu 1, teksty do wyświetlenia umieszczono w pliku zasobów. W tym przypadku plik ten nosi nazwę [MyResources.resx] (typeof) i został umieszczony w katalogu głównym projektu. Nazywa się go plikiem zasobów.

Utworzyliśmy tutaj trzy pliki zasobów:

  • [MyResources]: zasób domyślny, gdy nie ma zasobu dla bieżącej lokalizacji;
  • [MyResources.fr-FR]: zasób dla ustawień regionalnych [fr-FR];
  • [MyResources.en-US]: zasób dla ustawień regionalnych [en-US];

Aby utworzyć plik zasobów, należy postępować w następujący sposób: [1, 2, 3]:

Spowoduje to utworzenie pliku zasobów o nazwie [MyResources2.resx]. Po dwukrotnym kliknięciu na ten plik wyświetli się następująca strona:

Plik zasobów to słownik zawierający klucze i wartości powiązane z tymi kluczami. Klucz wpisujemy w pliku [1], wartość w pliku [2], a zakres zasobu w pliku [3]. Aby zasoby te były czytelne, muszą mieć zakres [Public]. Wróćmy do wiersza:


    [Required(ErrorMessageResourceType = typeof(MyResources), ErrorMessageResourceName = "infoRequise")]
  • [ErrorMessageResourceType]: oznacza plik zasobów. Parametr [typeof] to nazwa pliku. Jest on przekształcany w klasę podczas kompilacji, a jego plik binarny jest dołączany do zestawu plików projektu. Ostatecznie więc [MyResources] to nazwa klasy zasobów;
  • [ErrorMessageResourceName = "infoRequise"]: oznacza klucz w pliku zasobów. Ostatecznie wiersz ten oznacza, że komunikat o błędzie, który ma zostać wyświetlony, to wartość z pliku [MyResources] powiązana z kluczem [infoRequise].

Aby utworzyć klucz [infoRequise] i powiązaną z nim wartość w pliku [MyResources], należy postępować w następujący sposób:

Wprowadza się klucz w polu [1], wartość w polu [2], a zakres zasobu w polu [3].

Pozostaje jeszcze jedna kwestia do wyjaśnienia: przestrzeń nazw klasy [MyResources]. Jest ona zdefiniowana we właściwościach pliku [MyResources.resx]:

W pliku [1] definiujemy przestrzeń nazw klasy [MyResources], która zostanie utworzona na podstawie pliku zasobów [MyResources.resx]. Wróćmy do analizowanego wiersza z treścią zinternacjonalizowaną:


[Required(ErrorMessageResourceType = typeof(MyResources), ErrorMessageResourceName = "infoRequise")]

Operator typeof oczekuje klasy, w tym przypadku klasy [MyResources]. Aby ta klasa została znaleziona, należy zaimportować jej przestrzeń nazw do klasy [ViewModel14]:


using Exemple_03.Resources;

Aby klasa [MyResources] była widoczna, projekt musi zostać wygenerowany co najmniej raz od momentu utworzenia pliku zasobów [MyResources]. Kod tej klasy jest widoczny w pliku [MyResources.Designer.cs]:

  

Po dwukrotnym kliknięciu tego pliku uzyskuje się dostęp do kodu klasy [MyResources]:


namespace Exemple_03.Resources {
    using System;
    
    
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
    [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
    [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
    public class MyResources2 {
        
  ...
         public static string infoRequise {
            get {
                return ResourceManager.GetString("infoRequise", resourceCulture);
            }
        }
    }
}
  • wiersz 1: przestrzeń nazw klasy;
  • wiersz 11: klucz [infoRequise] stał się statyczną właściwością klasy [MyResources]. Dostęp do niego uzyskuje się za pomocą notacji [MyResources.infoRequise]. Ponadto należy zauważyć, że ta właściwość ma zakres [public]. Bez tego nie byłaby dostępna. Warto o tym pamiętać, ponieważ niestety domyślnym zakresem jest [internal], a zapomnienie o zmianie tego zakresu powoduje trudne do zrozumienia błędy.

Dlaczego teraz są trzy pliki zasobów?

  

Utworzyliśmy plik [MyResources.resx]. Jest to zasób główny. Następnie tworzymy tyle plików zasobów [MyResources.locale.resx], ile jest lokalizacji (języków) do obsługi. W tym przypadku obsługujemy język francuski [fr-FR] oraz amerykański angielski [en-US]. Gdy bieżąca lokalizacja nie jest ani [fr-FR], ani [en-US], wykorzystywany jest zasób główny [MyResources.resx].

Ostateczna zawartość pliku [MyResources.resx] jest następująca:

 

Komunikaty będą wyświetlane w języku francuskim, jeśli ustawienia regionalne nie zostaną rozpoznane. Ostateczna zawartość pliku [MyResources.fr-FR.resx] jest identyczna i uzyskano ją poprzez zwykłe skopiowanie pliku.

Ostateczna zawartość pliku [MyResources.en-US.resx] jest również uzyskiwana poprzez skopiowanie pliku, a następnie modyfikowana w następujący sposób:

 

Wróćmy do widoku [ViewModel14] i jego metody [Validate]:


    // weryfikacja
    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
      // lista błędów
      List<ValidationResult> résultats = new List<ValidationResult>();
      // ten sam komunikat o błędzie dla wszystkich
      string errorMessage=MyResources.ResourceManager.GetObject("infoIncorrecte", new CultureInfo(System.Web.HttpContext.Current.Session["lang"] as string)).ToString();

      // Data 1
      if (Date1.Date <= DateTime.Now.Date)
      {
        résultats.Add(new ValidationResult(errorMessage, new string[] { "Date1" }));
      }
...
      // wyświetla listę błędów
      return résultats;
}

Wiersz 7 pokazuje, jak pobrać komunikat z pliku zasobów [MyResources]. W tym przypadku chcemy pobrać komunikat powiązany z kluczem [infoIncorrecte] w aktualnej kulturze:

  • MyResources.ResourceManager.GetObject("infoIncorrecte", new CultureInfo("en-US")) : pobiera obiekt powiązany z kluczem [infoIncorrecte] z pliku zasobów [MyResources.en-US.resx];
  • widzieliśmy, że kontroler [I18NController] ustawia bieżącą kulturę w sesji powiązanej z kluczem [lang]. Bieżącą kulturę można zatem pobrać za pomocą System.Web.HttpContext.Current.Session["lang"] as string;
  • zasób jest pobierany z typem [object]. Aby uzyskać komunikat o błędzie, stosuje się do niego metodę [ToString].

6.4. Internacjonalizacja widoku [Action14Get.cshtml]

Modyfikujemy widok formularza w następujący sposób:

  

@model Exemple_03.Models.ViewModel14
@using Exemple_03.Resources
@{
  Layout = null;
}

<!DOCTYPE html>

<html>
<head>
  <meta name="viewport" content="width=device-width" />
  <title>Action14Get</title>
  <link rel="stylesheet" href="~/Content/Site.css" />
  <script type="text/javascript" src="~/Scripts/jquery-1.8.2.min.js"></script>
  <script type="text/javascript" src="~/Scripts/jquery.validate.min.js"></script>
  <script type="text/javascript" src="~/Scripts/jquery.validate.unobtrusive.min.js"></script>
  <script type="text/javascript" src="~/Scripts/globalize/globalize.js"></script>
  <script type="text/javascript" src="~/Scripts/globalize/cultures/globalize.culture.fr-FR.js"></script>
  <script type="text/javascript" src="~/Scripts/globalize/cultures/globalize.culture.en-US.js"></script>
  <script type="text/javascript" src="~/Scripts/myscripts2.js"></script>
  <script>
    $(document).ready(function () {
      var culture = '@System.Threading.Thread.CurrentThread.CurrentCulture';
        Globalize.culture(culture);
      });
  </script>

</head>
<body>
  <h3>Formulaire ASP.NET MVC - Internationalisation</h3>
  @using (Html.BeginForm("Action14Post", "Second"))
  {
    <table>
      <thead>
        <tr>
          <th>@MyResources.type</th>
          <th>@MyResources.value</th>
          <th>@MyResources.error</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <td>@Html.LabelFor(m => m.Chaine1)</td>
          <td>@Html.EditorFor(m => m.Chaine1)</td>
          <td>@Html.ValidationMessageFor(m => m.Chaine1)</td>
        </tr>
...
      </tbody>
    </table>
    <p>
      <input type="submit" value="Valider" />
    </p>
  }
</body>
</html>
<!-- wybór języka -->
@using (Html.BeginForm("Lang", "Second"))
{
  <table>
    <tr>
      <td><a href="javascript:postForm('fr-FR','/Second/Action14Get')">Français</a></td>
      <td><a href="javascript:postForm('en-US','/Second/Action14Get')">English</a></td>
    </tr>
  </table>
}

Uwaga: w wierszu 14 należy dostosować wersję jQuery do posiadanej wersji programu Visual Studio.

Zacznijmy od najprostszych linii, czyli 36–38. Wykorzystują one statyczne właściwości klasy [MyResources], którą właśnie opisaliśmy. Aby uzyskać dostęp do klasy [MyResources], należy zaimportować jej przestrzeń nazw (linia 2).

W przypadku komunikatów zinternacjonalizowanych należy uwzględnić również te, które są wyświetlane przez framework walidacji po stronie klienta. W tym celu należy skorzystać z bibliotek JQuery w wierszach 17–19. Pliki JQuery wykorzystujemy dla obu obsługiwanych przez nas kultur: [fr-FR] i [en-US]. Ponadto być może pamiętamy, że widok [Action13Get] wykorzystywał następujący skrypt JavaScript [myscripts.js]:


// podczas ładowania dokumentu
$(document).ready(function () {
  var culture = 'fr-FR';
  Globalize.culture(culture);
});

Obecnie wartość nie jest już stała i wynosi [fr-FR], lecz ulega zmianom. Dlatego te wiersze są teraz generowane przez sam widok [Action14Get] w wierszach 21–26. Te sześć wierszy zostanie dołączonych do strony HTML wysyłanej do klienta.

  • wiersz 23: zmienna JavaScript [culture] jest inicjowana z bieżącą kulturą wątku aktualnie przetwarzanego żądania. Być może pamiętamy, że została ona zainicjowana przez konstruktor klasy [I18NController]:

      // ustawianie języka w sesji
      httpContext.Session["lang"] = langue;
      // zmiana ustawień kulturowych wątku            
      Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo(langue);
Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture;

Jeśli bieżącą kulturą jest [en-US], skrypt JavaScript osadzony na stronie HTML przyjmuje następującą postać:


  <script>
    $(document).ready(function () {
      var culture = 'en-US';
        Globalize.culture(culture);
      });
</script>

Jak już wspomniano, funkcja [$(document).ready] jest wykonywana po zakończeniu ładowania strony przez przeglądarkę. Jej wykonanie spowoduje ustalenie ustawień regionalnych frameworka walidacji po stronie klienta. Przy ustawieniu kultury [en-US] komunikaty o błędach frameworka będą wyświetlane w języku angielskim i będą pochodzić z pliku zasobów [MyResources.en-US.resx]. Zobaczymy, w jaki sposób.

Przyjrzyjmy się teraz wierszom 57–65:


<!-- wybór języka -->
@using (Html.BeginForm("Lang", "Second"))
{
  <table>
    <tr>
      <td><a href="javascript:postForm('fr-FR','/Second/Action14Get')">Français</a></td>
      <td><a href="javascript:postForm('en-US','/Second/Action14Get')">English</a></td>
    </tr>
  </table>
}

Mamy tu drugi formularz, pierwszy znajduje się w wierszach 31–53. Formularz ten wyświetla na dole strony następujące linki:

  • wiersz 2: formularz jest wysyłany do akcji [Lang] kontrolera [Second]. Na razie nie widać żadnej wartości, która mogłaby zostać przesłana;
  • wiersze 6 i 7: kliknięcie na linki powoduje wykonanie funkcji JavaScript [postForm]. Gdzie znajduje się ta funkcja? W skrypcie [myscripts2.js], do którego odwołuje się wiersz 20 widoku:

Jej treść jest następująca:


function postForm(lang, url) {
  // pobieramy drugi formularz z dokumentu
  var form = document.forms[1];
  // dodajemy do niego ukryty atrybut „lang”
  var hiddenField = document.createElement("input");
  hiddenField.setAttribute("type", "hidden");
  hiddenField.setAttribute("name", "lang");
  hiddenField.setAttribute("value", lang);
  // dodawanie ukrytego pola do formularza
  form.appendChild(hiddenField);
  // dodajemy do niego ukryty atrybut „url”
  var hiddenField = document.createElement("input");
  hiddenField.setAttribute("type", "hidden");
  hiddenField.setAttribute("name", "url");
  hiddenField.setAttribute("value", url);
  // dodajemy ukryte pole do formularza
  form.appendChild(hiddenField);
  // przesłanie
  form.submit();
}

// http://blog.instance-factory.com/?p=268
$.validator.methods.number = function (value, element) {
  return this.optional(element) ||
      !isNaN(Globalize.parseFloat(value));
}

$.validator.methods.date = function (value, element) {
  return this.optional(element) ||
      !isNaN(Globalize.parseDate(value));
}

jQuery.extend(jQuery.validator.methods, {
  range: function (value, element, param) {
    //Użyj wtyczki Globalization do analizy wartości        
    var val = Globalize.parseFloat(value);
    return this.optional(element) || (
        val >= param[0] && val <= param[1]);
  }
});

Wiersze 22–40 to te, które już występowały w skrypcie [myscripts.js] wykorzystanym w poprzednim przykładzie. Nie będziemy do nich wracać. Funkcja [postForm], uruchamiana po kliknięciu linków języków, znajduje się w wierszach 1–20:

  • wiersz 1: funkcja przyjmuje dwa parametry: [lang], czyli kulturę wybraną przez użytkownika, oraz [url], czyli adres URL, pod który ma zostać przekierowana przeglądarka klienta po dokonaniu zmiany kultury. Oba te parametry są określane podczas wywołania:

<td><a href="javascript:postForm('fr-FR','/Second/Action14Get')">Français</a></td>
<td><a href="javascript:postForm('en-US','/Second/Action14Get')">English</a></td>
  • wiersz 3: pobieramy odwołanie do drugiego formularza w dokumencie;
  • wiersze 5–8: programowo tworzy się tag
<input type="hidden" value="xx-XX"/>

gdzie [xx-XX] to wartość parametru [lang] tej funkcji;

  • wiersz 10: nadal programowo dodajemy ten tag do drugiego formularza. Ostatecznie wszystko przebiega tak, jakby ten tag był obecny w drugim formularzu od samego początku. Jego wartość zostanie zatem przesłana. Tego właśnie chcieliśmy;
  • wiersze 11–17: powtarzamy ten sam mechanizm dla tagu
<input type="hidden" value="url"/>

gdzie [url] jest wartością parametru [url] funkcji;

  • wiersz 19: drugi formularz został właśnie wysłany. Do jakiego URL?

Należy powrócić do kodu drugiego formularza na stronie [Action14Get.cshtml]:


@using (Html.BeginForm("Lang", "Second"))
{
...
}

Formularz jest zatem wysyłany do URL [/Second/Lang]. Musimy zatem zdefiniować akcję [Lang] w kontrolerze [SecondController]. Będzie ona wyglądać następująco:


public class SecondController : I18NController
    {
      // Action14-GET
      [HttpGet]
      public ViewResult Action14Get()
      {
        return View("Action14Get", new ViewModel14());
      }

      // Action14-POST
      [HttpPost]
      public ViewResult Action14Post(ViewModel14 modèle)
      {
        return View("Action14Get", modèle);
      }

      // język
      [HttpPost]
      public RedirectResult Lang(string url)
      {
        // klient jest przekierowywany do adresu URL
        return new RedirectResult(url);
      }

    }
  • wiersz 18: akcja odpowiada wyłącznie na [POST];
  • wiersz 19: pobiera wyłącznie parametr o nazwie [url];
  • wiersz 22: odpowiada klientowi, aby przekierował się do tego URL.

Ale co stało się z parametrem o nazwie [lang]? Należy teraz pamiętać, że kontroler [SecondController] wywodzi się z klasy [I18NController] (wiersz 1 poniżej). To właśnie ten kontroler zarządza parametrem [lang]:


  public abstract class I18NController : Controller
  {
    public I18NController()
    {
      // pobieramy kontekst bieżącego żądania
      HttpContext httpContext = System.Web.HttpContext.Current;
      // sprawdzamy żądanie pod kątem obecności parametru [lang]
      // szuka się go wśród parametrów URL
      string langue = httpContext.Request.QueryString["lang"];
      if (langue == null)
      {
        // szuka się go wśród przesłanych parametrów
        langue = httpContext.Request.Form["lang"];
      }
      if (langue == null)
      {
        // szuka się go w sesji użytkownika
        langue = httpContext.Session["lang"] as string;
      }
      if (langue == null)
      {
        // pierwszy parametr nagłówka HTTP AcceptLanguages
        langue = httpContext.Request.UserLanguages[0];
      }
      if (langue == null)
      {
        // kultura fr-FR
        langue = "fr-FR";
      }
      // ustawia się język w sesji
      httpContext.Session["lang"] = langue;
      // zmiana ustawień regionalnych wątku            
      Thread.CurrentThread.CurrentCulture = new CultureInfo(langue);
      Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture;
}

W analizowanym przykładzie wysyłany jest parametr [lang]. Zostanie on zatem znaleziony w wierszu 13, wprowadzony do sesji w wierszu 31 i wykorzystany do aktualizacji kultury bieżącego wątku w wierszach 33–34.

Co stanie się dalej? Wróćmy do linków:


<td><a href="javascript:postForm('fr-FR','/Second/Action14Get')">Français</a></td>
<td><a href="javascript:postForm('en-US','/Second/Action14Get')">English</a></td>

Przekierowanie URL to [/Second/Action14Get]. Wykonana zostaje zatem akcja [Action14Get]:


public class SecondController : I18NController
    {
      // Akcja14-GET
      [HttpGet]
      public ViewResult Action14Get()
      {
        return View("Action14Get", new ViewModel14());
      }
...
}

Wcześniej uruchamiany jest konstruktor klasy [I18NController]:


  public abstract class I18NController : Controller
  {
    public I18NController()
    {
      // pobieramy kontekst bieżącego żądania
      HttpContext httpContext = System.Web.HttpContext.Current;
      // sprawdzamy żądanie pod kątem obecności parametru [lang]
      // szukamy go wśród parametrów URL
      string langue = httpContext.Request.QueryString["lang"];
      if (langue == null)
      {
        // szuka się go wśród przesłanych parametrów
        langue = httpContext.Request.Form["lang"];
      }
      if (langue == null)
      {
        // szuka się go w sesji użytkownika
        langue = httpContext.Session["lang"] as string;
      }
      if (langue == null)
      {
        // pierwszy parametr nagłówka HTTP AcceptLanguages
        langue = httpContext.Request.UserLanguages[0];
      }
      if (langue == null)
      {
        // kultura fr-FR
        langue = "fr-FR";
      }
      // ustawia się język w sesji
      httpContext.Session["lang"] = langue;
      // zmiana ustawień regionalnych wątku            
      Thread.CurrentThread.CurrentCulture = new CultureInfo(langue);
      Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture;
}

Tym razem parametr [lang] zostanie znaleziony w sesji w wierszu 18. Załóżmy, że jego wartością jest [en-US]. Ta kultura staje się zatem kulturą wątku wykonującego zapytanie (wiersze 33–34). Wróćmy do akcji [Action14Get]:


      // Akcja14-GET
      [HttpGet]
      public ViewResult Action14Get()
      {
        return View("Action14Get", new ViewModel14());
}

W wierszu 5 zostanie utworzona instancja modelu widoku [ViewModel14]:


  public class ViewModel14 : IValidatableObject
  {

    [Required(ErrorMessageResourceType = typeof(MyResources), ErrorMessageResourceName = "infoRequise")]
    [Display(ResourceType = typeof(MyResources), Name = "chaineaumoins4")]
    [RegularExpression(@"^.{4,}$", ErrorMessageResourceType = typeof(MyResources), ErrorMessageResourceName = "infoIncorrecte")]
    public string Chaine1 { get; set; }
....

Ponieważ kulturą bieżącego wątku jest [en-US], wykorzystany zostanie plik [MyResources.en-US.resx]. Komunikaty o błędach będą zatem w języku angielskim.

Po utworzeniu instancji szablonu [ViewModel14] wyświetlany jest widok [Action14Get.cshtml]:


@model Exemple_03.Models.ViewModel14
@using Exemple_03.Resources
@using System.Threading
@{
  Layout = null;
}

<!DOCTYPE html>

<html>
<head>
  <meta name="viewport" content="width=device-width" />
  <title>Action14Get</title>
  ...
  <script>
    $(document).ready(function () {
      var culture = '@Thread.CurrentThread.CurrentCulture';
        Globalize.culture(culture);
      });
  </script>

</head>
<body>
  <h3>Formulaire ASP.NET MVC - Internationalisation</h3>
  @using (Html.BeginForm("Action14Post", "Second"))
  {
    <table>
      <thead>
        <tr>
          <th>@MyResources.type</th>
          <th>@MyResources.value</th>
          <th>@MyResources.error</th>
        </tr>
      </thead>
      <tbody>
        <tr>
...
        </tr>
<tr>

Ponieważ kulturą bieżącego wątku jest [en-US], skrypt osadzony na stronie w wierszach 15–20 ma następującą postać:


  <script>
    $(document).ready(function () {
      var culture = 'en-US';
        Globalize.culture(culture);
      });
  </script>

Zapewnia to, że framework walidacji będzie działał z formatami amerykańskimi (data, waluta, liczby itp.). Z tego samego powodu komunikaty w wierszach 30–32 będą pobierane z pliku zasobów [MyResources.en-US.resx] i będą zatem w języku angielskim.

6.5. Przykłady działania

Oto kilka przykładów działania:

  • w pliku [1] znajduje się formularz w języku francuskim, a w pliku [2] – formularz w języku angielskim.
  • W [3], po stronie klienta, komunikaty o błędach są teraz wyświetlane w języku angielskim.

Jeśli przyjrzymy się kodowi źródłowemu strony, widać, że te komunikaty o błędach zostały wbudowane w stronę, a więc wygenerowane przez widok ASP.NET [Action14Get] i jego szablon [ViewModel14]:


        <tr>
          <td><label for="Reel1">Real number</label></td>
          <td><input class="text-box single-line" data-val="true" data-val-number="The field Real number must be a number." data-val-required="Required data" id="Reel1" name="Reel1" type="text" value="0" /></td>
          <td><span class="field-validation-valid" data-valmsg-for="Reel1" data-valmsg-replace="true"></span></td>
        </tr>
        <tr>
          <td><label for="Reel2">Real number in range [10.2-11.3]</label></td>
          <td><input class="text-box single-line" data-val="true" data-val-number="The field Real number in range [10.2-11.3] must be a number." data-val-range="Invalid data" data-val-range-max="11.3" data-val-range-min="10.2" data-val-required="Required data" id="Reel2" name="Reel2" type="text" value="0" /></td>
          <td><span class="field-validation-valid" data-valmsg-for="Reel2" data-valmsg-replace="true"></span></td>
</tr>

6.6. Internacjonalizacja dat

Internacjonalizacja to złożony problem. Przyjrzyjmy się zatem właściwości [Date1] i jej kalendarzowi:

  

Można zauważyć, że kalendarz jest kalendarzem francuskim, podczas gdy kultura strony to [en-US]. W HTML5 istnieje atrybut [lang], który pozwala ustawić język strony lub jej elementu. W widoku [Action14Get.cshtml] można zatem wpisać następujący kod:


@model Exemple_03.Models.ViewModel14
@using Exemple_03.Resources
@using System.Threading
@{
  Layout = null;
  var lang = Session["lang"] as string;  
}

<!DOCTYPE html>

<html lang="@lang">
<head>
...
  • wiersz 6: pobieramy ustawienia regionalne z sesji;
  • wiersz 11: ustawia się atrybut [lang] strony na tę wartość.

Testy wykazują, że kalendarz pozostaje w języku francuskim, nawet gdy strona jest wyświetlana w języku angielskim. Występuje również problem z drugą datą w formularzu:

W [1] data jest nadal wymagana w formacie francuskim dd/mm/rrrr (20/11/2013), podczas gdy format amerykański to mm/dd/rrrr (10/21/2013). Spróbujemy rozwiązać te dwa problemy za pomocą nowego widoku i nowego szablonu widoku.

JQuery UI to projekt wywodzący się z projektu JQuery, oferujący komponenty do formularzy, w tym kalendarz. Kalendarz ten można dostosować do różnych krajów. Właśnie to zamierzamy pokazać.

Na początek dodajmy [JQuery UI] do naszego projektu.

Po zainstalowaniu JQuery i UI w projekcie pojawiają się nowe elementy:

  • w [1] – biblioteka [JQuery UI] w wersji normalnej i zminimalizowanej;
  • w [2] – arkusz stylów [JQuery UI];

Kalendarz JQuery UI jest domyślnie w języku angielskim. Aby go zinternacjonalizować, należy dodać skrypty, które można znaleźć w pliku URL [https://github.com/jquery/jquery-ui/tree/master/ui/i18n]:

Aby uzyskać kalendarz JQuery UI w języku francuskim, należy skopiować zawartość powyższego pliku [jquery.ui.datepicker-fr.js] do folderu [Scripts] projektu.

Kod nowego widoku [Action15.cshtml] uzyskano poprzez skopiowanie poprzedniego widoku [Action14.cshtml], a następnie jego modyfikację. Przedstawiamy jedynie zmiany:


@model Exemple_03.Models.ViewModel15
@using Exemple_03.Resources
@using System.Threading
@{
  Layout = null;
}

<!DOCTYPE html>

<html lang="@Model.Culture">
<head>
  <meta name="viewport" content="width=device-width" />
  <title>Action15</title>
...
  <link rel="stylesheet" href="~/Content/themes/base/jquery-ui.css" />
  <script type="text/javascript" src="~/Scripts/jquery-ui-1.10.3.js"></script>
  <script type="text/javascript" src="~/Scripts/jquery.ui.datepicker-fr.js"></script>
  <script>
    $(document).ready(function () {
      var culture = '@Thread.CurrentThread.CurrentCulture';
      Globalize.culture(culture);
      $("#Date1").datepicker($.datepicker.regional['@Model.Regionale']);
    });
  </script>
</head>
<body>
  <h3>@MyResources.titre</h3>
  @using (Html.BeginForm("Action15", "Second"))
  {
    <table>
...
        <tr>
          <td>@Html.LabelFor(m => m.Date1)</td>
          <td>@Html.TextBox("Date1", Model.StrDate1)</td>
          <td>@Html.ValidationMessageFor(m => m.Date1)</td>
        </tr>
      </tbody>
    </table>
    <p>
      <input type="submit" value="Valider" />
    </p>
  }
  <!-- wybór języka -->
  @using (Html.BeginForm("Lang", "Second"))
  {
    <table>
      <tr>
        <td><a href="javascript:postForm('fr-FR','/Second/Action15')">Français</a></td>
        <td><a href="javascript:postForm('en-US','/Second/Action15')">English</a></td>
      </tr>
    </table>
  }
</body>
</html>

Uwaga: w wierszu 16 należy dostosować wersję jQuery-ui do tej, którą pobrałeś.

  • wiersz 15: odwołujemy się do arkusza stylów z pliku JQuery UI;
  • wiersz 16: odwołujemy się do pobranej wersji JQuery UI;
  • wiersz 17: odwołujemy się do skryptu francuskiego kalendarza, który właśnie pobraliśmy;
  • wiersz 34: metoda [Html.TextBox] wygeneruje tutaj tag o nazwie [input] typu [text], o identyfikatorze [Date1] i nazwie [Date1];
  • wiersz 19: po zakończeniu ładowania strony funkcja JQuery UI [datepicker] zostanie zastosowana do elementu o identyfikatorze [Date1], czyli element z wiersza 34. Funkcja ta sprawia, że gdy użytkownik ustawi fokus na polu wprowadzania danych o identyfikatorze [Date1], pojawi się kalendarz umożliwiający wprowadzenie daty. Funkcja [datepicker] przyjmuje parametr określający język kalendarza. Zmienna [@Model.Regionale] musi mieć wartość:
  • „fr” dla kalendarza francuskiego,
  • '' dla kalendarza angielskiego;

Szablon poprzedniego widoku [Action15.cshtml] będzie następującym szablonem [ViewModel15]:

Jego kod jest kodem nieznacznie zmodyfikowanego szablonu [ViewModel14]. Przedstawiamy jedynie zmiany:


using Exemple_03.Resources;
...
using System.Web;

namespace Exemple_03.Models
{
  [Bind(Exclude = "Culture,Regionale,StrDate1,FormatDate")]
  public class ViewModel15 : IValidatableObject
  {

...
    [Display(ResourceType = typeof(MyResources), Name = "date1")]
    [RegularExpression(@"\s*\d{2}/\d{2}/\d{4}\s*", ErrorMessageResourceType = typeof(MyResources), ErrorMessageResourceName = "infoIncorrecte")]
    [Required(ErrorMessageResourceType = typeof(MyResources), ErrorMessageResourceName = "infoRequise")]
    public string Regexp1 { get; set; }

    [Display(ResourceType = typeof(MyResources), Name = "date2")]
    [Required(ErrorMessageResourceType = typeof(MyResources), ErrorMessageResourceName = "infoRequise")]
    [DataType(DataType.Date)]
    public DateTime Date1 { get; set; }

    // konstruktor
    public ViewModel15()
    {
      // Aktualna kultura
      Culture = HttpContext.Current.Session["lang"] as string;
      cultureInfo=new CultureInfo(Culture);
      // Region kalendarza JQuery
      Regionale = MyResources.ResourceManager.GetObject("regionale", cultureInfo).ToString();
      // format daty
      FormatDate = MyResources.ResourceManager.GetObject("formatDate", cultureInfo).ToString();
    }



    // walidacja
    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
      // Lista błędów
      List<ValidationResult> résultats = new List<ValidationResult>();
      // ten sam komunikat o błędzie dla wszystkich
      string errorMessage = MyResources.ResourceManager.GetObject("infoIncorrecte", cultureInfo).ToString();
...
      // Regexp1
      try
      {
        DateTime.ParseExact(Regexp1, FormatDate, cultureInfo);
      }
      catch
      {
        résultats.Add(new ValidationResult(errorMessage, new string[] { "Regexp1" }));
      }

      // wyświetlanie listy błędów
      return résultats;

    }

    // pola poza szablonem akcji
    public string Culture { get; set; }
    public string Regionale { get; set; }
    public string StrDate1 { get; set; }
    public string FormatDate { get; set; }

    // dane lokalne
    private CultureInfo cultureInfo;
  }
}

W porównaniu z poprzednim modelem [ViewModel14] mamy cztery dodatkowe właściwości:

  • wiersz 60: ustawienia regionalne widoku, „fr-FR” lub „en-US”. Ustawienia te są inicjowane w konstruktorze w wierszu 26;
  • wiersz 61: ustawienia regionalne kalendarza JQuery, „fr” dla kalendarza francuskiego, „” dla kalendarza angielskiego. Pole to jest inicjowane w wierszu 29 konstruktora;
  • wiersz 63: format daty z wiersza 15: „dd/MM/yyyy” dla daty francuskiej, „MM/dd/yyyy” dla daty angielskiej. Pole to jest inicjowane w wierszu 31 konstruktora;
  • wiersz 62: ciąg znaków do wyświetlenia w polu wprowadzania danych [Date1]. Pole to zostanie zainicjowane przez akcję;
  • wiersz 47: data [Regexp1] jest teraz sprawdzana pod kątem zgodności z formatem obowiązującym w bieżącej kulturze.

Wartości właściwości [Regionale] i [FormatDate] znajdują się w plikach zasobów [MyResources]. Francuskie pliki zasobów [MyResources], [MyResources.fr-FR], [1] oraz angielski plik zasobów [2] ulegają następującym zmianom:

Jesteśmy już prawie gotowi. Dodajemy akcję [Action15] do kontrolera [SecondController]:


      // Akcja15
      public ViewResult Action15(FormCollection formData)
      {
        // metoda HTTP
        string method = Request.HttpMethod.ToLower();
        // szablon
        ViewModel15 modèle = new ViewModel15();
        if (method == "get")
        {
          modèle.StrDate1 = "";
        }
        else
        {
          TryUpdateModel(modèle, formData);
          modèle.StrDate1 = modèle.Date1.ToString(modèle.FormatDate);
        }
        // widok
        return View("Action15", modèle);
}
  • wiersz 2: metoda [Action15] obsługuje zarówno [GET], jak i [POST]. W tym drugim przypadku wartości przesłane są pobierane z parametru [formData];
  • wiersz 5: pobieramy metodę HTTP z zapytania;
  • wiersz 7: tworzy się szablon widoku, który ma zostać wyświetlony (formularz);
  • wiersze 8–11: w przypadku polecenia [GET] pole wprowadzania danych [Date1] jest inicjowane pustym ciągiem znaków;
  • wiersze 12–16: w przypadku zamówienia [POST]:
    • wiersz 14: szablon jest inicjowany wartościami zaksięgowanymi,
    • wiersz 15: pole wprowadzania danych dla [Date1] jest inicjowane ciągiem znaków, który stanowi wartość [Date1] sformatowaną zgodnie z bieżącą kulturą – [dd/MM/yyyy] dla daty francuskiej, [MM/dd/yyyy] dla daty angielskiej;
  • wiersz 18: widok [Action15.cshtml] jest wyświetlany wraz z szablonem.

Przeprowadźmy testy:

  • w formacie [1] – kalendarz francuski, gdy strona jest w języku francuskim;
  • w formacie [2] – kalendarz angielski, gdy strona jest w języku angielskim;
  • w formacie [3] – data w formacie francuskim, gdy strona jest w języku francuskim;
  • w formacie [4] – tę samą datę w formacie angielskim, gdy strona jest w języku angielskim;

6.7. Conclusion

Jak widać, kwestia internacjonalizacji aplikacji jest złożona...