6. Internationalisering van weergaven
We gaan hier in op het probleem van de internationalisering van weergaven. Dit is een complex probleem dat goed wordt beschreven in het volgende artikel van Scott Hanselman:
[http://www.hanselman.com/blog/GlobalizationInternationalizationAndLocalizationInASPNETMVC3JavaScriptAndJQueryPart1.aspx]
Laten we eerst zijn definitie van de verschillende termen met betrekking tot de internationalisering van weergaven nog eens doornemen:
Internationalisering (i18n) | ervoor zorgen dat de applicatie verschillende talen en landinstellingen ondersteunt |
Lokalisatie (l10n) | ervoor zorgen dat de applicatie een specifieke combinatie van taal en landinstelling ondersteunt |
Globalisering | de combinatie van Internationalisation en Localisation |
Taal | gesproken taal – aangeduid met een code ISO (fr: Frans, es: Spaans, en: Engels, ...) |
Landinstelling | een variant van de taal – eveneens aangeduid met een code ISO (en_GB: Brits Engels, en_US: Amerikaans Engels, ...) |
Laten we het probleem aan de hand van een eerste voorbeeld bekijken.
6.1. Lokalisatie van reële getallen
Er valt een afwijking op in het vorige invoerformulier:
![]() |
Voor het reële getal hebben we [0,3] ingevoerd, maar dit werd niet geaccepteerd. We moeten [0.3] invoeren:
![]() |
Het verwachte formaat is dus het Angelsaksische formaat en niet het Franse formaat. Als we op internet zoeken, vinden we oplossingen. Hier is er een.
De acties [GET] en [POST] worden als volgt:
// Actie13-GET
[HttpGet]
public ViewResult Action13Get()
{
return View("Action13Get", new ViewModel11());
}
// Actie13-POST
[HttpPost]
public ViewResult Action13Post(ViewModel11 modèle)
{
return View("Action13Get", modèle);
}
De weergave [Action13Get.cshtml] is identiek aan de weergave [Action12Get.cshtml], op de JavaScript-scripts na:
<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>
Opmerking: regel 5, pas de versie van jQuery aan aan die van uw versie van Visual Studio.
- Op regel 9 hebben we een script toegevoegd: [myscripts.js] . Dit script ziet er als volgt uit:
![]() |
// 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) {
//Gebruik de Globalization-plugin om de waarde te parseren
var val = Globalize.parseFloat(value);
return this.optional(element) || (
val >= param[0] && val <= param[1]);
}
});
// bij het laden van het document
$(document).ready(function () {
var culture = 'fr-FR';
Globalize.culture(culture);
});
Ik heb in regel 1 aangegeven waar dit script is gevonden. Ik ga niet proberen het uit te leggen, want ik begrijp het zelf niet. JavaScript kan soms nogal ondoorgrondelijk zijn. In de regels 4, 9 en 15 wordt een object met de naam [Globalize] gebruikt. Dit object wordt geleverd door de bibliotheek JQuery Globalization, die je kunt verkrijgen via [NuGet]:
![]() |
![]() |
- in [1], beheer de pakketten [NuGet] van het project [Exemple-03];
- in [2], bekijk de pakketten online;
- in [3], typ de term [globalization];
- in [4], installeer het pakket [Globalize] van het project JQuery.
Zodra het pakket [Globalize] is geïnstalleerd, verschijnt er een nieuwe tak in de map [Scripts]:
![]() |
- in [1] is een map [globalize] aangemaakt met het hoofdscript [globalize.js];
- in [2] wordt het hoofdscript [globalize.js] aangevuld met taalspecifieke scripts en locale-specifieke scripts;
- in [3] zijn de scripts voor de Franse taal aangevuld met de Belgische (BE), Canadese (CA), Franse (FR), Zwitserse (CH), Luxemburgse (LU) en Monegaskisch (MC).
Het schrift [globalize.js] en het schrift van onze cultuur [globalize.culture.fr-FR.js] moeten deel uitmaken van de lijst met schriften op onze pagina [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>
- regel 5: het script [globalize];
- regel 6: het script [globalize.culture.fr-FR.js];
- regel 7: het script [myscripts.js];
Laten we nog eens terugkomen op dit laatste script:
// http://blog.instance-factory.com/?p=268
$.validator.methods.number = function (value, element) {
return this.optional(element) ||
!isNaN(Globalize.parseFloat(value));
}
...
// bij het laden van het document
$(document).ready(function () {
var culture = 'fr-FR';
Globalize.culture(culture);
});
De regels 10-13 stellen de client-side culture in op [fr-FR]:
- regel 10: de functie JQuery [ready] wordt uitgevoerd zodra het document waarin het script zich bevindt volledig door de browser is geladen;
- regels 11-12: de client-side cultuur wordt ingesteld op [fr-FR]. Hiervoor moet het bestand [globalize.culture.fr-FR.js] zijn opgenomen in de lijst met JavaScript-scripts die aan het document zijn gekoppeld.
Nu kunnen we de nieuwe applicatie testen:
![]() |
We kunnen nu [0,3] invoeren als reëel getal, wat voorheen niet mogelijk was. Er doet zich echter een andere afwijking voor:
Hierboven laat de validatie aan de clientzijde ons [11.2] invoeren in de Angelsaksische notatie. Deze waarde wordt aan de serverzijde niet geaccepteerd wanneer we het formulier valideren:
We moeten [11,2] invoeren en dan werkt het zowel aan de client- als aan de serverzijde. Aan de clientzijde zou de Angelsaksische notatie niet geaccepteerd moeten worden. Dat moet mogelijk zijn...
Laten we nu de internationalisering van de weergaven bespreken. We gaan verder met het voorbeeld van het vorige formulier en bieden het aan in twee talen: Frans en Engels.
6.2. Een cultuur beheren
De taal van de weergaven wordt bepaald door het object [Thread.CurrentThread.CurrentUICulture]. Om de pagina’s in de cultuur [fr-FR] weer te geven, schrijf je:
De lokalisatie (datums, getallen, valuta’s, tijden, ...) wordt bepaald door het object [Thread.CurrentThread.CurrentCulture]. Net zoals eerder beschreven, schrijft men:
Deze twee instructies zouden in de constructor van elke controller van de applicatie kunnen staan. Maar het kan ook wenselijk zijn om deze code, die alle controllers gemeen hebben, te factoriseren. We kiezen voor deze aanpak.
We maken twee nieuwe controllers aan:
![]() |
- [I18NController] wordt de bovenliggende klasse voor alle controllers die gebruikmaken van internationalisering;
- [SecondController] is een voorbeeldcontroller die is afgeleid van [I18NController].
De code van de controller [I18NController] is als volgt:
using System.Threading;
using System.Web;
using System.Web.Mvc;
namespace Exemples.Controllers
{
public abstract class I18NController : Controller
{
public I18NController()
{
// de context van de huidige aanvraag wordt opgehaald
HttpContext httpContext = HttpContext.Current;
// de aanvraag wordt doorzocht op de parameter [lang]
// de parameter wordt gezocht in de parameters van de URL
string langue = httpContext.Request.QueryString["lang"];
if (langue == null)
{
// de parameter wordt gezocht in de verzonden parameters
langue = httpContext.Request.Form["lang"];
}
if (langue == null)
{
// wordt gezocht in de sessie van de gebruiker
langue = httpContext.Session["lang"] as string;
}
if (langue == null)
{
// eerste parameter van de header HTTP AcceptLanguages
langue = httpContext.Request.UserLanguages[0];
}
if (langue == null)
{
// cultuur fr-FR
langue = "fr-FR";
}
// de taal wordt in de sessie opgeslagen
httpContext.Session["lang"] = langue;
// de culturen van de thread worden gewijzigd
Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo(langue);
Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture;
}
}
}
- regel 7: [I18NController] is afgeleid van de klasse [Controller];
- regel 7: de klasse wordt gedeclareerd als [abstract] om directe instantiatie te voorkomen: deze kan alleen worden afgeleid om te worden gebruikt;
- regel 9: de constructor van de klasse – wordt uitgevoerd bij elke instantiëring van een controller die is afgeleid van [I18NController];
- regel 12: de context van de verzoek HTTP die momenteel door de controller wordt verwerkt, wordt opgehaald;
- regel 15: er wordt aangenomen dat de taal wordt bepaald door een parameter [lang] die op verschillende plaatsen te vinden is. Er wordt in de volgende volgorde gezocht:
- regel 15: in de parameters van URL en [?lang=en-US],
- regel 19: in de verzonden parameters [lang=de],
- regel 24: in de sessie van de gebruiker,
- regel 29: in de taalvoorkeuren die door de client zijn verzonden HTTP,
- regel 26: als er niets is gevonden, wordt de cultuur ingesteld op [fr-FR];
- regel 37: de cultuur wordt opgeslagen in de sessie. Daar wordt deze bij volgende verzoeken teruggevonden. De gebruiker kan deze wijzigen door deze op te geven in de parameters van een commando GET of POST;
- regels 39-40: de cultuur van de weergave die na verwerking van de huidige aanvraag wordt getoond, wordt vastgelegd.
De controller [SecondController] ziet er als volgt uit:
using Exemple_03.Models;
using Exemples.Controllers;
using System.Web.Mvc;
namespace Exemple_03.Controllers
{
public class SecondController : I18NController
{
// Actie14-GET
[HttpGet]
public ViewResult Action14Get()
{
return View("Action14Get", new ViewModel14());
}
// Actie14-POST
[HttpPost]
public ViewResult Action14Post(ViewModel14 modèle)
{
return View("Action14Get", modèle);
}
}
}
- regel 7: [SecondController] is afgeleid van [I18NController]. Zo is men er zeker van dat de culture van de weer te geven weergave is geïnitialiseerd;
- regel 13: we gebruiken het weergavesjabloon [ViewModel14] dat we hierna zullen presenteren;
- regels 13 en 20: de weergave [Action14Get.cshtml] zorgt voor de weergave van het formulier.
6.3. Het weergavemodel [ViewModel14] internationaliseren
Het weergavemodel [ViewModel14] ziet er als volgt uit:
![]() |
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; }
// validatie
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
// lijst met fouten
List<ValidationResult> résultats = new List<ValidationResult>();
// dezelfde foutmelding voor iedereen
string errorMessage=MyResources.ResourceManager.GetObject("infoIncorrecte", new CultureInfo(System.Web.HttpContext.Current.Session["lang"] as string)).ToString();
// Datum 1
if (Date1.Date <= DateTime.Now.Date)
{
résultats.Add(new ValidationResult(errorMessage, new string[] { "Date1" }));
}
// E-mail1
try
{
new MailAddress(Email1);
}
catch
{
résultats.Add(new ValidationResult(errorMessage, new string[] { "Email1" }));
}
// Regexp1
try
{
DateTime.ParseExact(Regexp1, "dd/MM/yyyy", CultureInfo.CreateSpecificCulture("fr-FR"));
}
catch
{
résultats.Add(new ValidationResult(errorMessage, new string[] { "Regexp1" }));
}
// we geven de lijst met fouten weer
return résultats;
}
}
}
Dit model is de geïnternationaliseerde versie van het vorige model [ViewModel11]. We zullen het internationalisatiemechanisme beschrijven voor het eerste attribuut van de eerste eigenschap. Voor de overige attributen geldt hetzelfde mechanisme.
[Required(ErrorMessageResourceType = typeof(MyResources), ErrorMessageResourceName = "infoRequise")]
public string Chaine1 { get; set; }
In het vorige model [ViewModel11] waren deze regels als volgt:
[Required(ErrorMessage = "Information requise")]
public string Chaine1 { get; set; }
In de geïnternationaliseerde versie, regel 1, worden de weer te geven teksten in een bronbestand geplaatst. Dit bestand heet hier [MyResources.resx] (typeof) en is in de hoofdmap van het project geplaatst. Dit wordt een bronbestand genoemd.
![]() |
We hebben hier drie bronbestanden aangemaakt:
- [MyResources]: standaardresource wanneer er geen resource voor de huidige locale is;
- [MyResources.fr-FR]: bron voor de locale [fr-FR];
- [MyResources.en-US]: bronbestand voor de locale [en-US];
Om een bronbestand aan te maken, gaat u als volgt te werk: [1, 2, 3]:
![]() |
![]() |
Hiermee wordt het bronbestand [MyResources2.resx] aangemaakt. Als je erop dubbelklikt, verschijnt de volgende pagina:
![]() |
Een bronbestand is een woordenboek met sleutels en waarden die aan deze sleutels zijn gekoppeld. De sleutel wordt ingevoerd in [1], de waarde in [2] en het bereik van de bron in [3]. Om deze bronnen leesbaar te maken, moeten ze het bereik [Public] hebben. Laten we teruggaan naar de regel:
[Required(ErrorMessageResourceType = typeof(MyResources), ErrorMessageResourceName = "infoRequise")]
- [ErrorMessageResourceType]: verwijst naar het bronbestand. De parameter [typeof] is de bestandsnaam. Deze wordt tijdens het compilatieproces omgezet in een klasse en het bijbehorende binaire bestand wordt opgenomen in de assembly van het project. Uiteindelijk is [MyResources] dus de naam van de bronnenklasse;
- [ErrorMessageResourceName = "infoRequise"]: verwijst naar een sleutel in het bronnenbestand. Uiteindelijk betekent deze regel dat de weer te geven foutmelding de waarde is uit het bestand [MyResources] die gekoppeld is aan de sleutel [infoRequise].
Om de sleutel [infoRequise] en de bijbehorende waarde in het bestand [MyResources] aan te maken, gaat u als volgt te werk:
![]() |
Voer de sleutel in als [1], de waarde als [2] en het bereik van de bron als [3].
Er moet nog één punt worden verduidelijkt: de naamruimte van de klasse [MyResources]. Deze wordt gedefinieerd in de eigenschappen van het bestand [MyResources.resx]:
![]() |
In [1] definiëren we de naamruimte van de klasse [MyResources], die zal worden aangemaakt op basis van het bronbestand [MyResources.resx]. Laten we teruggaan naar de geïnternationaliseerde regel die we hebben bekeken:
[Required(ErrorMessageResourceType = typeof(MyResources), ErrorMessageResourceName = "infoRequise")]
De operator typeof verwacht een klasse, in dit geval de klasse [MyResources]. Om deze te kunnen vinden, moet de naamruimte ervan worden geïmporteerd in de klasse [ViewModel14]:
using Exemple_03.Resources;
Om de klasse [MyResources] zichtbaar te maken, moet het project ten minste één keer zijn gegenereerd sinds het aanmaken van het bronbestand [MyResources]. De code van deze klasse is te vinden in het bestand [MyResources.Designer.cs]:
Wanneer je op dit bestand dubbelklikt, krijg je toegang tot de code van de klasse [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);
}
}
}
}
- regel 1: de naamruimte van de klasse;
- regel 11: de sleutel [infoRequise] is een statische eigenschap van de klasse [MyResources] geworden. Deze is toegankelijk via de notatie [MyResources.infoRequise]. Overigens moet worden opgemerkt dat deze eigenschap het bereik [public] heeft. Zonder dit bereik zou deze niet toegankelijk zijn. Het is goed om dit in gedachten te houden, want helaas is het standaardbereik [internal] en dit leidt tot moeilijk te begrijpen fouten wanneer men vergeet dit bereik te wijzigen.
Waarom zijn er nu drie bronbestanden?
We hebben [MyResources.resx] aangemaakt. Dit is de hoofdbron. Vervolgens maken we evenveel bronbestanden [MyResources.locale.resx] aan als er locales (talen) zijn die moeten worden beheerd. Hier beheren we het Frans ([fr-FR]) en het Amerikaans Engels ([en-US]). Wanneer de huidige locale noch [fr-FR], noch [en-US] is, wordt de hoofdbron [MyResources.resx] gebruikt.
De uiteindelijke inhoud van [MyResources.resx] is als volgt:
![]() |
De berichten worden in het Frans weergegeven wanneer de locale niet wordt herkend. De uiteindelijke inhoud van [MyResources.fr-FR.resx] is identiek en wordt verkregen door het bestand eenvoudigweg te kopiëren.
De uiteindelijke inhoud van [MyResources.en-US.resx] wordt eveneens verkregen door het bestand te kopiëren en vervolgens als volgt te wijzigen:
![]() |
Laten we teruggaan naar het scherm [ViewModel14] en de bijbehorende methode [Validate]:
// validatie
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
// lijst met fouten
List<ValidationResult> résultats = new List<ValidationResult>();
// voor iedereen dezelfde foutmelding
string errorMessage=MyResources.ResourceManager.GetObject("infoIncorrecte", new CultureInfo(System.Web.HttpContext.Current.Session["lang"] as string)).ToString();
// Datum 1
if (Date1.Date <= DateTime.Now.Date)
{
résultats.Add(new ValidationResult(errorMessage, new string[] { "Date1" }));
}
...
// de lijst met fouten wordt weergegeven
return résultats;
}
Regel 7 laat zien hoe een bericht uit het bronnenbestand [MyResources] kan worden opgehaald. Hier willen we het bericht ophalen dat gekoppeld is aan de sleutel [infoIncorrecte], en wel in de huidige cultuur:
- MyResources.ResourceManager.GetObject("infoIncorrecte", new CultureInfo("en-US")) : haalt het object op dat is gekoppeld aan de sleutel [infoIncorrecte] in het bronbestand [MyResources.en-US.resx];
- we hebben gezien dat de controller [I18NController] de huidige cultuur instelt in de sessie die is gekoppeld aan de sleutel [lang]. De huidige cultuur kan dus worden opgehaald via System.Web.HttpContext.Current.Session["lang"] as string;
- de bron wordt opgehaald met het type [object]. Om de foutmelding te verkrijgen, passen we de methode [ToString] erop toe.
6.4. De weergave [Action14Get.cshtml] internationaliseren
We passen de weergave van het formulier als volgt aan:
![]() |
@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>
<!-- taalkeuze -->
@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>
}
Opmerking: regel 14, pas de versie van jQuery aan aan die van uw versie van Visual Studio.
Laten we beginnen met het eenvoudigste, de regels 36-38. Deze maken gebruik van de statische eigenschappen van de klasse [MyResources] die we zojuist hebben beschreven. Om toegang te krijgen tot de klasse [MyResources], moet u de naamruimte ervan importeren (regel 2).
Bij geïnternationaliseerde berichten moet ook rekening worden gehouden met de berichten die door het framework voor client-side validatie worden weergegeven. Hiervoor moeten de bibliotheken JQuery uit de regels 17-19 worden gebruikt. We gebruiken de bestanden JQuery voor de twee culturen die we beheren: [fr-FR] en [en-US]. Bovendien herinneren we ons misschien nog dat de weergave [Action13Get] het volgende JavaScript-script [myscripts.js] gebruikte:
// bij het laden van het document
$(document).ready(function () {
var culture = 'fr-FR';
Globalize.culture(culture);
});
Nu is de cultuur niet langer alleen [fr-FR], maar varieert deze. Daarom worden deze regels nu door de weergave [Action14Get] zelf gegenereerd op de regels 21-26. Deze zes regels worden opgenomen in de pagina HTML die naar de klant wordt verzonden.
- regel 23: de JavaScript-variabele [culture] wordt geïnitialiseerd met de huidige cultuur van de thread van het verzoek dat momenteel wordt verwerkt. Misschien herinner je je nog dat deze is geïnitialiseerd door de constructor van de klasse [I18NController]:
// de taal wordt in de sessie opgeslagen
httpContext.Session["lang"] = langue;
// de threadculturen worden aangepast
Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo(langue);
Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture;
Als de huidige cultuur [en-US] is, wordt het in de pagina HTML ingesloten JavaScript-script:
<script>
$(document).ready(function () {
var culture = 'en-US';
Globalize.culture(culture);
});
</script>
We hebben al vermeld dat de functie [$(document).ready] wordt uitgevoerd zodra de browser de pagina volledig heeft geladen. De uitvoering hiervan zorgt ervoor dat de taalinstelling van het client-side validatieframework wordt vastgelegd. Met de culture [en-US] zullen de foutmeldingen van het framework in het Engels zijn en afkomstig zijn uit het bronbestand [MyResources.en-US.resx]. We zullen zien hoe dat werkt.
Laten we nu eens kijken naar de regels 57-65:
<!-- taalkeuze -->
@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>
}
Dit is een tweede formulier; het eerste staat op de regels 31-53. Dit formulier toont onderaan de pagina de volgende links:
![]() |
- regel 2: het formulier wordt verzonden naar de actie [Lang] van de controller [Second]. Voorlopig zien we geen waarden die zouden kunnen worden verzonden;
- regels 6 en 7: een klik op de links zorgt ervoor dat de JavaScript-functie [postForm] wordt uitgevoerd. Waar bevindt deze functie zich? In het script [myscripts2.js] waarnaar in regel 20 van de weergave wordt verwezen:
![]() |
De inhoud ervan is als volgt:
function postForm(lang, url) {
// het tweede formulier uit het document wordt opgehaald
var form = document.forms[1];
// het verborgen attribuut 'lang' toevoegen
var hiddenField = document.createElement("input");
hiddenField.setAttribute("type", "hidden");
hiddenField.setAttribute("name", "lang");
hiddenField.setAttribute("value", lang);
// het verborgen veld wordt aan het formulier toegevoegd
form.appendChild(hiddenField);
// het verborgen attribuut 'url' toevoegen
var hiddenField = document.createElement("input");
hiddenField.setAttribute("type", "hidden");
hiddenField.setAttribute("name", "url");
hiddenField.setAttribute("value", url);
// het verborgen veld wordt aan het formulier toegevoegd
form.appendChild(hiddenField);
// verzenden
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) {
//Gebruik de Globalization-plugin om de waarde te parseren
var val = Globalize.parseFloat(value);
return this.optional(element) || (
val >= param[0] && val <= param[1]);
}
});
De regels 22-40 zijn dezelfde als die al aanwezig zijn in het script [myscripts.js] dat in het vorige voorbeeld werd gebruikt. Daar gaan we niet verder op in. De functie [postForm], die wordt uitgevoerd wanneer op de taallinks wordt geklikt, staat op de regels 1-20:
- regel 1: de functie accepteert twee parameters, [lang], de door de gebruiker gekozen cultuur, en [url], de URL waarnaar de browser van de klant moet worden omgeleid zodra de cultuurwijziging is doorgevoerd. Deze twee parameters worden bij de aanroep gespecificeerd:
<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>
- regel 3: er wordt een verwijzing opgehaald naar het tweede formulier van het document;
- regels 5-8: de tag
waarbij [xx-XX] de waarde is van de parameter [lang] van de functie;
- regel 10: nog steeds via programmering voegen we deze tag toe aan het tweede formulier. Uiteindelijk lijkt het alsof deze tag vanaf het begin al in het tweede formulier aanwezig was. De waarde ervan wordt dus verzonden. Dat was de bedoeling;
- regels 11-17: we herhalen hetzelfde mechanisme voor een tag
waarbij [url] de waarde is van de parameter [url] van de functie;
- regel 19: het tweede formulier wordt nu verzonden. Naar welke URL?
We moeten teruggaan naar de code van het tweede formulier op de pagina [Action14Get.cshtml]:
@using (Html.BeginForm("Lang", "Second"))
{
...
}
Het formulier wordt dus verzonden naar de URL [/Second/Lang]. We moeten dan een actie [Lang] definiëren in de controller [SecondController]. Deze ziet er als volgt uit:
public class SecondController : I18NController
{
// Actie14-GET
[HttpGet]
public ViewResult Action14Get()
{
return View("Action14Get", new ViewModel14());
}
// Action14-POST
[HttpPost]
public ViewResult Action14Post(ViewModel14 modèle)
{
return View("Action14Get", modèle);
}
// taal
[HttpPost]
public RedirectResult Lang(string url)
{
// de client wordt doorgestuurd naar de url
return new RedirectResult(url);
}
}
- regel 18: de actie reageert alleen op een [POST];
- regel 19: de actie haalt alleen de parameter met de naam [url] op;
- regel 22: de actie antwoordt de client dat hij moet worden omgeleid naar deze URL.
Maar wat is er gebeurd met de parameter met de naam [lang]? We moeten ons nu realiseren dat de controller [SecondController] is afgeleid van de klasse [I18NController] (regel 1 hieronder). Het is deze controller die de parameter [lang] beheert:
public abstract class I18NController : Controller
{
public I18NController()
{
// de context van het huidige verzoek wordt opgehaald
HttpContext httpContext = System.Web.HttpContext.Current;
// het verzoek wordt doorzocht op de parameter [lang]
// de parameter wordt gezocht in de parameters van de URL
string langue = httpContext.Request.QueryString["lang"];
if (langue == null)
{
// de parameter wordt gezocht in de verzonden parameters
langue = httpContext.Request.Form["lang"];
}
if (langue == null)
{
// wordt gezocht in de sessie van de gebruiker
langue = httpContext.Session["lang"] as string;
}
if (langue == null)
{
// eerste parameter van de header HTTP AcceptLanguages
langue = httpContext.Request.UserLanguages[0];
}
if (langue == null)
{
// cultuur fr-FR
langue = "fr-FR";
}
// de taal wordt in de sessie opgeslagen
httpContext.Session["lang"] = langue;
// de culturen van de thread worden gewijzigd
Thread.CurrentThread.CurrentCulture = new CultureInfo(langue);
Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture;
}
In het onderzochte voorbeeld wordt de parameter [lang] verzonden. Deze wordt dus gevonden op regel 13, in de sessie opgenomen op regel 31 en gebruikt om de context van de huidige thread bij te werken op de regels 33-34.
Wat gaat er vervolgens gebeuren? Laten we nog eens naar de koppelingen kijken:
<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>
De omleidingsactie URL is [/Second/Action14Get]. De actie [Action14Get] wordt dus uitgevoerd:
public class SecondController : I18NController
{
// Actie14-GET
[HttpGet]
public ViewResult Action14Get()
{
return View("Action14Get", new ViewModel14());
}
...
}
Eerder wordt de constructor van de klasse [I18NController] uitgevoerd:
public abstract class I18NController : Controller
{
public I18NController()
{
// de context van de huidige aanvraag wordt opgehaald
HttpContext httpContext = System.Web.HttpContext.Current;
// de aanvraag wordt doorzocht op de parameter [lang]
// we zoeken deze in de parameters van de URL
string langue = httpContext.Request.QueryString["lang"];
if (langue == null)
{
// de parameter wordt gezocht in de verzonden parameters
langue = httpContext.Request.Form["lang"];
}
if (langue == null)
{
// wordt gezocht in de sessie van de gebruiker
langue = httpContext.Session["lang"] as string;
}
if (langue == null)
{
// eerste parameter van de header HTTP AcceptLanguages
langue = httpContext.Request.UserLanguages[0];
}
if (langue == null)
{
// cultuur fr-FR
langue = "fr-FR";
}
// de taal wordt in de sessie opgeslagen
httpContext.Session["lang"] = langue;
// de culturen van de thread worden gewijzigd
Thread.CurrentThread.CurrentCulture = new CultureInfo(langue);
Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture;
}
Deze keer wordt de parameter [lang] in de sessie gevonden op regel 18. Stel dat de waarde ervan [en-US] is. Deze cultuur wordt dus de cultuur van de thread waarin de aanvraag wordt uitgevoerd (regels 33-34). Laten we teruggaan naar de actie [Action14Get]:
// Actie14-GET
[HttpGet]
public ViewResult Action14Get()
{
return View("Action14Get", new ViewModel14());
}
Op regel 5 wordt een instantie van het weergavemodel [ViewModel14] aangemaakt:
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; }
....
Omdat de cultuur van de huidige thread [en-US] is, wordt het bestand [MyResources.en-US.resx] gebruikt. De foutmeldingen zullen dus in het Engels zijn.
Zodra het model [ViewModel14] is geïnstantieerd, wordt de weergave [Action14Get.cshtml] weergegeven:
@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>
Omdat de cultuur van de huidige thread [en-US] is, is het in de pagina ingebedde script op de regels 15-20:
<script>
$(document).ready(function () {
var culture = 'en-US';
Globalize.culture(culture);
});
</script>
Dit zorgt ervoor dat het validatieframework werkt met Amerikaanse notaties (datum, valuta, getallen, ...). Om dezelfde reden worden de meldingen in de regels 30-32 uit het bronbestand [MyResources.en-US.resx] gehaald en zijn ze dus in het Engels.
6.5. Voorbeelden van uitvoering
Hier volgen enkele uitvoervoorbeelden:
![]() |
- in [1], het formulier in het Frans, in [2], het formulier in het Engels.
![]() |
- In [3] worden de foutmeldingen aan de clientzijde nu in het Engels weergegeven.
Als we de broncode van de pagina bekijken, zien we dat deze foutmeldingen in de pagina zijn ingebed en dus zijn gegenereerd door de weergave ASP.NET [Action14Get] en het bijbehorende sjabloon [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. Internationalisering van datums
Internationalisering is een complex probleem. Laten we daarom eens kijken naar de eigenschap [Date1] en de bijbehorende kalender:
![]() |
We zien dat de kalender een Franse kalender is, terwijl de cultuur van de pagina [en-US] is. In HTML5 bestaat een attribuut [lang] waarmee de taal van de pagina of van een onderdeel van de pagina kan worden ingesteld. We kunnen dan in de weergave [Action14Get.cshtml] de volgende code schrijven:
@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>
...
- regel 6: de cultuur wordt uit de sessie opgehaald;
- regel 11: het attribuut [lang] van de pagina wordt met deze waarde ingesteld.
Uit tests blijkt dat de kalender in het Frans blijft, zelfs wanneer de pagina verder in het Engels wordt weergegeven. Er is ook een probleem met de andere datum in het formulier:
![]() |
In [1] wordt de datum nog steeds gevraagd in het Franse formaat dd/mm/jjjj (20/11/2013), terwijl het Amerikaanse formaat mm/dd/jjjj is (10/21/2013). We gaan proberen deze twee problemen op te lossen met een nieuwe weergave en een nieuw weergavemodel.
JQuery UI is een project dat is afgeleid van het project JQuery en biedt componenten voor formulieren, waaronder een kalender. Deze kalender kan worden geïnternationaliseerd. Dat gaan we laten zien.
Laten we om te beginnen [JQuery UI] aan ons project toevoegen.
![]() |
![]() |
Zodra JQuery en UI zijn geïnstalleerd, verschijnen er nieuwe elementen in het project:
![]() |
- in [1], de bibliotheek [JQuery UI] in zowel de normale als de geminimaliseerde versie;
- in [2], het stylesheet van [JQuery UI];
De kalender JQuery UI is standaard in het Engels. Om deze te internationaliseren, moeten er scripts worden toegevoegd die te vinden zijn in URL [https://github.com/jquery/jquery-ui/tree/master/ui/i18n]:
![]() |
Om de kalender JQuery UI in het Frans te krijgen, kopieert u de inhoud van het bovenstaande bestand [jquery.ui.datepicker-fr.js] naar de map [Scripts] van het project.
![]() |
De code van de nieuwe weergave [Action15.cshtml] wordt verkregen door de vorige weergave [Action14.cshtml] te kopiëren en vervolgens aan te passen. We geven hier alleen de wijzigingen weer:
![]() |
@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>
}
<!-- taalkeuze -->
@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>
Opmerking: regel 16, pas de versie van jQuery-ui aan aan de versie die u hebt gedownload.
- regel 15: er wordt verwezen naar het stylesheet van JQuery UI;
- regel 16: er wordt verwezen naar de gedownloade versie van JQuery UI;
- regel 17: er wordt verwezen naar het script van de Franse kalender dat we zojuist hebben gedownload;
- regel 34: de methode [Html.TextBox] genereert hier een tag van het type [text], met id [Date1] en name [Date1];
- regel 19: zodra het laden van de pagina is voltooid, wordt de functie JQuery UI [datepicker] toegepast op het element met id [Date1], dus het element op regel 34. Deze functie zorgt ervoor dat wanneer de gebruiker de focus op het invoerveld van [Date1] plaatst, er een kalender verschijnt waarmee hij een datum kan invoeren. De functie [datepicker] accepteert een parameter die de taal van de kalender aangeeft. De variabele [@Model.Regionale] moet de waarde
- 'fr' voor een Franse kalender,
- '' voor een Engelse kalender;
Het sjabloon van de vorige weergave [Action15.cshtml] wordt het volgende sjabloon [ViewModel15]:
![]() |
De code ervan is die van het licht gewijzigde sjabloon [ViewModel14]. We geven alleen de wijzigingen weer:
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; }
// constructor
public ViewModel15()
{
// Huidige cultuur
Culture = HttpContext.Current.Session["lang"] as string;
cultureInfo=new CultureInfo(Culture);
// Regionale kalenderinstelling JQuery
Regionale = MyResources.ResourceManager.GetObject("regionale", cultureInfo).ToString();
// datumnotatie
FormatDate = MyResources.ResourceManager.GetObject("formatDate", cultureInfo).ToString();
}
// validatie
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
// Foutenlijst
List<ValidationResult> résultats = new List<ValidationResult>();
// dezelfde foutmelding voor alle
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" }));
}
// de foutenlijst wordt weergegeven
return résultats;
}
// velden buiten het actiemodel
public string Culture { get; set; }
public string Regionale { get; set; }
public string StrDate1 { get; set; }
public string FormatDate { get; set; }
// lokale gegevens
private CultureInfo cultureInfo;
}
}
Ten opzichte van het vorige model [ViewModel14] hebben we vier extra eigenschappen:
- regel 60: de taal van de weergave, 'fr-FR' of 'en-US'. Deze taal wordt geïnitialiseerd in de constructor op regel 26;
- regel 61: de regionale kalendercultuur van JQuery, 'fr' voor een Franse kalender, '' voor een Engelse kalender. Dit veld wordt geïnitialiseerd door regel 29 van de constructor;
- regel 63: het datumformaat van regel 15: 'dd/MM/yyyy' voor een Franse datum, 'MM/dd/yyyy' voor een Engelse datum. Dit veld wordt geïnitialiseerd in regel 31 van de constructor;
- regel 62: de tekenreeks die moet worden weergegeven in het invoerveld van [Date1]. Dit veld wordt geïnitialiseerd door de actie;
- regel 47: de datum [Regexp1] wordt nu gecontroleerd aan de hand van het formaat van de huidige cultuur.
De waarden van de eigenschappen [Regionale] en [FormatDate] zijn te vinden in de bronbestanden [MyResources]. De Franse bronbestanden [MyResources], [MyResources.fr-FR], [1] en het Engelse bronbestand [2] zijn als volgt gewijzigd:
![]() |
We zijn bijna klaar. We voegen een actie [Action15] toe aan de controller [SecondController]:
// Actie15
public ViewResult Action15(FormCollection formData)
{
// methode HTTP
string method = Request.HttpMethod.ToLower();
// sjabloon
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);
}
// weergave
return View("Action15", modèle);
}
- regel 2: de methode [Action15] verwerkt zowel de [GET] als de [POST]. In het laatste geval worden de verzonden waarden opgehaald uit de parameter [formData];
- regel 5: de methode HTTP wordt uit de query opgehaald;
- regel 7: het sjabloon van de weergave die zal worden getoond (het formulier) wordt aangemaakt;
- regels 8-11: in het geval van een opdracht [GET] wordt het invoerveld van [Date1] geïnitialiseerd met een lege tekenreeks;
- regels 12-16: in het geval van een bestelling [POST]:
- regel 14: het sjabloon wordt geïnitialiseerd met de geboekte waarden;
- regel 15: het invoerveld van [Date1] wordt geïnitialiseerd met een tekenreeks die de waarde van [Date1] weergeeft, opgemaakt volgens de huidige cultuurinstelling [dd/MM/yyyy] voor een Franse datum, [MM/dd/yyyy] voor een Engelse datum;
- regel 18: de weergave [Action15.cshtml] wordt weergegeven met het bijbehorende sjabloon.
Laten we wat testen:
![]() |
- in [1], een Franse kalender wanneer de pagina in het Frans is;
- in [2], een Engelse kalender wanneer de pagina in het Engels is;
- in [3], een datum in Frans formaat wanneer de pagina in het Frans is;
- in [4], dezelfde datum in het Engelse formaat wanneer de pagina in het Engels is;
6.7. Conclusion
Het is duidelijk dat de internationalisering van een applicatie een complex onderwerp is...
































