Многоезична поддръжка за съобщения по подразбиране, показвани по време на проверка на въвеждане

Дата на създаване на страница :

околна среда

ASP.NET Ядро
  • 5.0 MVC

Отначало

Многоезичната поддръжка на съобщенията за проверка на въвеждане по подразбиране с тези съвети може да не е пълна. Моля те, оправи се с изчезналите.

Също така промяната на езика в зависимост от състоянието на сесията или състоянието на кеша все още може да се появи в текст от предишния език.

Има няколко начина за промяна на съобщението за проверка на въвеждане по подразбиране, всеки от които може да бъде комбиниран. Не е един от тях, така че ако искате да го промените надеждно, трябва да съответствате с всички модели.

Предпоставката

Този Съвети е написан като разбиране на следните съвети:

Също така, ако създавате нов проект, трябва да сте добавили следните файлове и код въз основа на съвети по-горе.

  • Създаване на файл на SharedResource.resx (+en, es). (Тъй като се превежда само съобщението от настоящия Съвет, съдържанието може да е празно.)
  • Създаване на файл на Споделен ресурс.cs
  • Добавяне на код за локализация към Startup.ConfigureServices
  • Добавяне на код на локализация към Startup.Configure
  • Добавен UserViewModel (този път не ключови изрично съобщенията по подразбиране на няколко езика)
  • Добавени са създадени от потребителя действия и изгледи на екрана (Create.cshtml)

Начален код

Въз основа на горните предположения всеки код е следният:

Стартиране.cs
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Localization;
using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System.Globalization;

namespace LocalizationDefaultValidation
{
  public class Startup
  {
    // 省略

    public void ConfigureServices(IServiceCollection services)
    {
      services.AddControllersWithViews();

      services.AddMvc()
        // ローカライズに必要。Resx ファイルのフォルダパスを指定
        .AddViewLocalization(LanguageViewLocationExpanderFormat.Suffix, opts => { opts.ResourcesPath = "Resources"; })
        // DataAnnotations のローカライズに必要
        .AddDataAnnotationsLocalization(options =>
        {
          // DataAnnotation を使ったときのメッセージは SharedResource に集約する
          options.DataAnnotationLocalizerProvider = (type, factory) => factory.Create(typeof(SharedResource));
        });
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
      if (env.IsDevelopment())
      {
        app.UseDeveloperExceptionPage();
      }
      else
      {
        app.UseExceptionHandler("/Home/Error");
        // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
        app.UseHsts();
      }

      // 標準の機能で切り替えたい言語を定義します。
      var supportedCultures = new[]
      {
        new CultureInfo("ja"),
        new CultureInfo("en"),
        new CultureInfo("es"),
      };

      // 標準の言語切り替え機能を有効にします。対応しているのは「クエリ文字列」「Cookie」「Accept-Language HTTP ヘッダー」です。
      app.UseRequestLocalization(new RequestLocalizationOptions
      {
        DefaultRequestCulture = new RequestCulture("ja"),
        SupportedCultures = supportedCultures,
        SupportedUICultures = supportedCultures
      });

      app.UseHttpsRedirection();
      app.UseStaticFiles();

      // 省略
    }
  }
}
НачалоКонтролер.cs
using LocalizationDefaultValidation.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;

namespace LocalizationDefaultValidation.Controllers
{
  public class HomeController : Controller
  {
    // 省略

    public IActionResult Create()
    {
      SetDayOfWeeksViewData();
      return View();
    }

    [HttpPost]
    public IActionResult Create(UserViewModel model)
    {
      SetDayOfWeeksViewData();

      // エラーなら差し戻し
      if (ModelState.IsValid == false) return View(model);

      // 正常に登録できた場合は Index に戻る
      return RedirectToAction(nameof(Index));
    }

    /// <summary>曜日の一覧を ViewData に設定します。</summary>
    private void SetDayOfWeeksViewData()
      => ViewData["DayOfWeeks"] = ((DayOfWeek[])Enum.GetValues(typeof(DayOfWeek))).Select(x => new SelectListItem(x.ToString(), ((int)x).ToString()));
  }
}
Индекс.cshtml
@{
  ViewData["Title"] = "Home Page";
}

<div class="text-center">
  <h1 class="display-4">Welcome</h1>
  <p>Learn about <a href="https://docs.microsoft.com/aspnet/core">building Web apps with ASP.NET Core</a>.</p>
</div>

<p>ユーザー作成画面に遷移します。リンクごとに選択した言語で表示できます。</p>
<ul>
  <li><a asp-action="Create">Create</a></li>
  <li><a asp-action="Create" asp-route-culture="ja">Create (ja)</a></li>
  <li><a asp-action="Create" asp-route-culture="en">Create (en)</a></li>
  <li><a asp-action="Create" asp-route-culture="es">Create (es)</a></li>
</ul>
Създаване.cshtml
@model LocalizationDefaultValidation.Models.UserViewModel
@using System.Globalization;
@{
  ViewData["Title"] = "Create";
}

<h1>Create</h1>

<h4>UserViewModel</h4>
<hr />
<div class="row">
  <div class="col-md-4">
    <form asp-action="Create" enctype="multipart/form-data" >
      <div asp-validation-summary="ModelOnly" class="text-danger"></div>
      <div class="form-group">
        <label asp-for="ID" class="control-label"></label>
        <input asp-for="ID" class="form-control" />
        <span asp-validation-for="ID" class="text-danger"></span>
      </div>
      <div class="form-group">
        <label asp-for="Name" class="control-label"></label>
        <input asp-for="Name" class="form-control" />
        <span asp-validation-for="Name" class="text-danger"></span>
      </div>
      <div class="form-group">
        <label asp-for="Password" class="control-label"></label>
        <input asp-for="Password" class="form-control" />
        <span asp-validation-for="Password" class="text-danger"></span>
      </div>
      <div class="form-group">
        <label asp-for="ConfirmPassword" class="control-label"></label>
        <input asp-for="ConfirmPassword" class="form-control" />
        <span asp-validation-for="ConfirmPassword" class="text-danger"></span>
      </div>
      <div class="form-group">
        <label asp-for="Email" class="control-label"></label>
        <input asp-for="Email" class="form-control" />
        <span asp-validation-for="Email" class="text-danger"></span>
      </div>
      <div class="form-group">
        <label asp-for="Age" class="control-label"></label>
        <input asp-for="Age" class="form-control" />
        <span asp-validation-for="Age" class="text-danger"></span>
      </div>
      <div class="form-group">
        <label asp-for="Gender"></label>
        <div>
          <label><input type="radio" asp-for="Gender" value="@(GenderType.None)" />@(GenderType.None)</label>
          <label><input type="radio" asp-for="Gender" value="@(GenderType.Man)" />@(GenderType.Man)</label>
          <label><input type="radio" asp-for="Gender" value="@(GenderType.Woman)" />@(GenderType.Woman)</label>
        </div>
        <span asp-validation-for="Gender" class="text-danger"></span>
      </div>
      <div class="form-group">
        <label asp-for="Birthday" class="control-label"></label>
        <input asp-for="Birthday" class="form-control" />
        <span asp-validation-for="Birthday" class="text-danger"></span>
      </div>
      <div class="form-group">
        <label asp-for="Phone" class="control-label"></label>
        <input asp-for="Phone" class="form-control" />
        <span asp-validation-for="Phone" class="text-danger"></span>
      </div>
      <div class="form-group">
        <label asp-for="PostalCode" class="control-label"></label>
        <input asp-for="PostalCode" class="form-control" />
        <span asp-validation-for="PostalCode" class="text-danger"></span>
      </div>
      <div class="form-group">
        <label asp-for="CreditCard" class="control-label"></label>
        <input asp-for="CreditCard" class="form-control" />
        <span asp-validation-for="CreditCard" class="text-danger"></span>
      </div>
      <div class="form-group">
        <label asp-for="Money" class="control-label"></label>
        <input asp-for="Money" class="form-control" />
        <span asp-validation-for="Money" class="text-danger"></span>
      </div>
      <div class="form-group">
        <label asp-for="StartDateTime" class="control-label"></label>
        <input asp-for="StartDateTime" class="form-control" />
        <span asp-validation-for="StartDateTime" class="text-danger"></span>
      </div>
      <div class="form-group">
        <label asp-for="WakeUpTime" class="control-label"></label>
        <input asp-for="WakeUpTime" class="form-control" />
        <span asp-validation-for="WakeUpTime" class="text-danger"></span>
      </div>
      <div class="form-group">
        <label asp-for="Homepage" class="control-label"></label>
        <input asp-for="Homepage" class="form-control" />
        <span asp-validation-for="Homepage" class="text-danger"></span>
      </div>
      <div class="form-group">
        <label asp-for="MyImage" class="control-label"></label>
        <input asp-for="MyImage" class="form-control" />
        <span asp-validation-for="MyImage" class="text-danger"></span>
      </div>
      <div class="form-group">
        <label asp-for="MyColor" class="control-label"></label>
        <input asp-for="MyColor" class="form-control" type="color" />
        <span asp-validation-for="MyColor" class="text-danger"></span>
      </div>
      <div class="form-group">
        <label asp-for="WorkingDays" class="control-label"></label>
        <select asp-for="WorkingDays" class="form-control" asp-items="@((IEnumerable<SelectListItem>)ViewData["DayOfWeeks"])" multiple></select>
        <span asp-validation-for="WorkingDays" class="text-danger"></span>
      </div>
      <div class="form-group">
        <label asp-for="VacationDay" class="control-label"></label>
        <select asp-for="VacationDay" class="form-control" asp-items="@((IEnumerable<SelectListItem>)ViewData["DayOfWeeks"])" multiple></select>
        <span asp-validation-for="VacationDay" class="text-danger"></span>
      </div>
      <div class="form-group">
        <label asp-for="Comment" class="control-label"></label>
        <textarea asp-for="Comment" class="form-control"></textarea>
        <span asp-validation-for="Comment" class="text-danger"></span>
      </div>
      <div class="form-group">
        <label asp-for="FileName" class="control-label"></label>
        <input asp-for="FileName" class="form-control" />
        <span asp-validation-for="FileName" class="text-danger"></span>
      </div>
      <div class="form-group">
        <label asp-for="UploadFile" class="control-label"></label>
        <input asp-for="UploadFile" type="file" multiple />
        <span asp-validation-for="UploadFile" class="text-danger"></span>
      </div>
      <div class="form-group">
        <label asp-for="Month" class="control-label"></label>
        <input asp-for="Month" class="form-control" type="month" />
        <span asp-validation-for="Month" class="text-danger"></span>
      </div>
      <div class="form-group">
        <label asp-for="Search" class="control-label"></label>
        <input asp-for="Search" class="form-control" type="search" />
        <span asp-validation-for="Search" class="text-danger"></span>
      </div>
      <div class="form-group">
        <label asp-for="Range" class="control-label"></label>
        <input asp-for="Range" class="form-control" type="range" min="10" max="100" />
        <span asp-validation-for="Range" class="text-danger"></span>
      </div>
      <div class="form-group">
        <label asp-for="Week" class="control-label"></label>
        <input asp-for="Week" class="form-control" type="week" />
        <span asp-validation-for="Week" class="text-danger"></span>
      </div>
      <div class="form-group form-check">
        <label class="form-check-label">
          <input class="form-check-input" asp-for="IsAccepted" /> @Html.DisplayNameFor(model => model.IsAccepted)
        </label>
      </div>
      <div class="form-group">
        <input type="submit" value="Create" class="btn btn-primary" asp-route-culture="@CultureInfo.CurrentCulture.Name" />
      </div>
    </form>
  </div>
</div>

<div>
  <a asp-action="Index">Back to List</a>
</div>

@section Scripts {
  @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
Потребителски прегледМодел.cs
using Microsoft.AspNetCore.Http;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

namespace LocalizationDefaultValidation.Models
{
  public class UserViewModel
  {
    [Required]
    [Display(Name = "ID (半角英数字)")]
    [StringLength(20)]
    [RegularExpression(@"^[0-9a-zA-Z]*$")]
    public string ID { get; set; }

    [StringLength(50)]
    public string Name { get; set; }

    [StringLength(50, MinimumLength = 8)]
    [DataType(DataType.Password)]
    [RegularExpression(@"^[0-9a-zA-Z]*$")]
    public string Password { get; set; }

    [StringLength(50, MinimumLength = 8)]
    [DataType(DataType.Password)]
    [Compare(nameof(Password))]
    public string ConfirmPassword { get; set; }

    [EmailAddress]
    [DataType(DataType.EmailAddress)] // スキャフォールディングするときはコメントアウトする
    public string Email { get; set; }

    //[Int]
    [Range(0, 150)]
    public int Age { get; set; }

    [Required]
    [EnumDataType(typeof(GenderType))]
    public GenderType Gender { get; set; }

    [DataType(DataType.Date)]
    public DateTime Birthday { get; set; }

    [Phone()]
    [DataType(DataType.PhoneNumber)] // スキャフォールディングするときはコメントアウトする
    public string Phone { get; set; }

    [DataType(DataType.PostalCode)]
    public string PostalCode { get; set; }

    [CreditCard()]
    [DataType(DataType.CreditCard)] // スキャフォールディングするときはコメントアウトする
    public string CreditCard { get; set; }

    [DataType(DataType.Currency)]
    public decimal Money { get; set; }

    [DataType(DataType.DateTime)]
    public DateTime StartDateTime { get; set; }

    [DataType(DataType.Time)]
    public TimeSpan WakeUpTime { get; set; }

    [Url]
    [DataType(DataType.Url)] // スキャフォールディングするときはコメントアウトする
    public string Homepage { get; set; }

    [Url]
    [DataType(DataType.ImageUrl)] // スキャフォールディングするときはコメントアウトする
    public string MyImage { get; set; }

    public string MyColor { get; set; }

    [MaxLength(5)]
    public DayOfWeek[] WorkingDays { get; set; }

    [MinLength(3)]
    public DayOfWeek[] VacationDay { get; set; }

    [StringLength(200)]
    [DataType(DataType.MultilineText)]
    public string Comment { get; set; }

    [Display(Name = "ファイル名 (.png)")]
    [FileExtensions(Extensions = "png")]
    public string FileName { get; set; }

    [DataType(DataType.Upload)]
    public List<IFormFile> UploadFile { get; set; }

    public DateTime Month { get; set; }

    public string Search { get; set; }

    [Range(10, 100)]
    public int Range { get; set; }

    public string Week { get; set; }

    [Required]
    public bool IsAccepted { get; set; }
  }

  public enum GenderType
  {
    None,
    Man,
    Woman,
    Other,
  }
}
Споделен ресурс.cs
namespace LocalizationDefaultValidation
{
  // クラス名は作成した .resx のファイル名と同じにする必要がある
  public class SharedResource { }
}

Съобщения за подвързване на модели

Ако зададете типа свойство на модела int на или го обвържете с DateTime изгледа, и се опитате да го регистрирате неотпечатано, ще видите съобщение като "Стойността '' е невалидна.". Тези съобщения се появяват, когато празен низ int не може да бъде обвързан например с модела. Тъй като е време, което не може да бъде обвързано, то възниква само преди друга проверка на стойността и обработка от страна на сървъра.

Тези съобщения се DefaultModelBindingMessageProvider определят като .

Startup.cs Добавете към аргументите на метода, които са дефинирани от началото services.AddControllersWithViews в , след което добавете Action . Можете да бъдете многоезични, като зададете преминалите в options ModelBindingMessageProvider аргумента.

Има 11 вида съобщения, които можете да зададете, така че първо трябва да дефинирате съобщението в както следва: SharedResource.resx Името на ключа не е задължително. Английското съобщение по подразбиране в колоната за коментари е (коментарите не трябва да бъдат включени). Няма да се налага да превеждате всеки език, така че трябва да направите свой собствен превод (примерният код включва и преведения SharedResource.resx).

Коментар за стойност на име
ModelBinding_AttemptedValueIsInvalid "{0}" е невалидна стойност в {1}. Стойността '{0}' не е валидна за {1}.
ModelBinding_MissingBindRequiredValue Стойността за {0} не е зададена. Не е предоставена стойност за параметъра или свойството "{0}".
ModelBinding_MissingKeyOrValue Изисква. Необходима е стойност.
ModelBinding_MissingRequestBodyRequiredValue Молбата трябва да има тяло. Изисква се не празен орган за заявки.
ModelBinding_NonPropertyAttemptedValueIsInvalid '{0}' не е валиден. Стойността '{0}' не е валидна.
ModelBinding_NonPropertyUnknownValueIsInvalid Стойността не е валидна. Доставената стойност е невалидна.
ModelBinding_NonPropertyValueMustBeANumber Трябва да бъде зададен номер. Полето трябва да е число.
ModelBinding_UnknownValueIsInvalid Стойността на {0} не е валидна. Доставената стойност е невалидна за {0}.
ModelBinding_ValueIsInvalid '{0}' не е валиден. Стойността '{0}' е невалидна.
ModelBinding_ValueMustBeANumber {0} трябва да е число. Полето {0} трябва да е число.
ModelBinding_ValueMustNotBeNull Необходим вход. Стойността '{0}' е невалидна.

Коригирайте стартирането.cs както следва:

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Localization;
using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Localization;
using System;
using System.Globalization;
using System.Reflection;

namespace LocalizationDefaultValidation
{
  public class Startup
  {
    // 省略 (初期コード)

    /// <summary>
    /// 検証メッセージローカライズで使用。
    /// </summary>
    private IServiceProvider ServiceProvider { get; set; }

    private IStringLocalizer _localizer = null;
    /// <summary>
    /// 検証メッセージローカライズで使用。
    /// </summary>
    private IStringLocalizer Localizer
      => _localizer ?? (_localizer = ServiceProvider.GetService<IStringLocalizerFactory>()
               .Create(nameof(SharedResource), new AssemblyName(typeof(SharedResource).Assembly.FullName).Name));

    // このメソッドはランタイムによって呼び出されます。 このメソッドを使用して、コンテナーにサービスを追加します。
    public void ConfigureServices(IServiceCollection services)
    {
      services.AddControllersWithViews(options =>
      {
        // 検証メッセージのローカライズで使用
        // モデルバインディング失敗時のエラーメッセージをカスタマイズ
        // サーバ側でモデルに値を格納する際に発生する可能性がある
        
        // メッセージの {0} や {1} を置換するための関数を定義
        static string f1(string f, string a1) => string.Format(f, a1);
        static string f2(string f, string a1, string a2) => string.Format(f, a1, a2);

        // 各メソッドを読んでメッセージを置き換えます
        var mp = options.ModelBindingMessageProvider;
        mp.SetAttemptedValueIsInvalidAccessor((x, y) => f2(Localizer["ModelBinding_AttemptedValueIsInvalid"], x, y));
        mp.SetMissingBindRequiredValueAccessor((x) => f1(Localizer["ModelBinding_MissingBindRequiredValue"], x));
        mp.SetMissingKeyOrValueAccessor(() => Localizer["ModelBinding_MissingKeyOrValue"]);
        mp.SetMissingRequestBodyRequiredValueAccessor(() => Localizer["ModelBinding_MissingRequestBodyRequiredValue"]);
        mp.SetNonPropertyAttemptedValueIsInvalidAccessor((x) => f1(Localizer["ModelBinding_NonPropertyAttemptedValueIsInvalid"], x));
        mp.SetNonPropertyUnknownValueIsInvalidAccessor(() => Localizer["ModelBinding_NonPropertyUnknownValueIsInvalid"]);
        mp.SetNonPropertyValueMustBeANumberAccessor(() => Localizer["ModelBinding_NonPropertyValueMustBeANumber"]);
        mp.SetUnknownValueIsInvalidAccessor((x) => f1(Localizer["ModelBinding_UnknownValueIsInvalid"], x));
        mp.SetValueIsInvalidAccessor((x) => f1(Localizer["ModelBinding_ValueIsInvalid"], x));
        mp.SetValueMustBeANumberAccessor((x) => f1(Localizer["ModelBinding_ValueMustBeANumber"], x));
        mp.SetValueMustNotBeNullAccessor((x) => f1(Localizer["ModelBinding_ValueMustNotBeNull"], x));
      });

      services.AddMvc()
        // ローカライズに必要。Resx ファイルのフォルダパスを指定
        .AddViewLocalization(LanguageViewLocationExpanderFormat.Suffix, opts => { opts.ResourcesPath = "Resources"; })
        // DataAnnotations のローカライズに必要
        .AddDataAnnotationsLocalization(options =>
        {
          // DataAnnotation を使ったときのメッセージは SharedResource に集約する
          options.DataAnnotationLocalizerProvider = (type, factory) => factory.Create(typeof(SharedResource));
        });
    }

    // このメソッドはランタイムによって呼び出されます。 このメソッドを使用して、HTTP要求パイプラインを構成します。
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
      // ローカライズで使用するため IServiceProvider をプロパティに保持しておきます。
      ServiceProvider = app.ApplicationServices;

      // 標準の機能で切り替えたい言語を定義します。
      var supportedCultures = new[]
      {
        new CultureInfo("ja"),
        new CultureInfo("en"),
        new CultureInfo("es"),
      };

      // 標準の言語切り替え機能を有効にします。対応しているのは「クエリ文字列」「Cookie」「Accept-Language HTTP ヘッダー」です。
      app.UseRequestLocalization(new RequestLocalizationOptions
      {
        DefaultRequestCulture = new RequestCulture("ja"),
        SupportedCultures = supportedCultures,
        SupportedUICultures = supportedCultures
      });

      // 省略 (初期コード)
    }
  }
}

Ключовата точка е services.AddControllersWithViews да добавите a към метода, който получава option Action , options.ModelBindingMessageProvider Задавам локализиран текст за всеки метод Set в .

Преведеният текст се IStringLocalizer извлича от . Няма начин да получите директен IStringLocalizer код, който е малко кръгъл код. SharedResource.resx Ако генерирате код от, ще получите локализирани стойности директно от него.

IStringLocalizer Ключът, който задавате в SharedResource.resx задава ключа, към който сте добавили . Задайте го да съответства на съдържанието на всеки метод Set.

Също така, защото всяко съобщение има аргумент формат низ, като например , аз {0}{1} използвам Simple с, така че можете да замените string.Format Func получените стойности.

Локализиране на съобщенията за проверка по подразбиране

Съобщението за грешка по подразбиране, когато атрибутите на свойствата на даден модел са зададени на или така се определя от рамката и е основно на Required StringLength английски език.

Те могат да бъдат IValidationAttributeAdapterProvider локализирани чрез дефиниране на клас, получен от интерфейса.

Преди SharedResource.resx всичко регистрирайте текста, който е многоезичен. Името на ключа е произволно, но за да опростите програмата, я регистрирайте под формата на "Validator_< име на атрибут за проверка>".

Коментар за стойност на име
Validator_CompareAttribute {0} и {1} не съвпадат. '{0}' и '{1}' не съвпадат.
Validator_CreditCardAttribute {0} не е валиден номер на карта. Полето {0} не е валиден номер на кредитна карта.
Validator_DataTypeAttribute_Date Въведете валидна дата. Въведете валидна дата.
Validator_EmailAddressAttribute {0} не е валиден имейл адрес. Полето {0} не е валиден имейл адрес.
Validator_FileExtensionsAttribute {0} приема файлове само със следните разширения: : {1} Полето {0} приема само файлове със следните разширения: {1}
Validator_MaxLengthAttribute {0} трябва да бъде низ или тип масив с максимална дължина "{1}". Полето {0} трябва да бъде низ или вид масив с максимална дължина "{1}".
Validator_MinLengthAttribute {0} трябва да бъде низ или тип масив с минимална дължина от "{1}". Полето {0} трябва да бъде низ или вид масив с минимална дължина "{1}".
Validator_PhoneAttribute {0} не е валиден телефонен номер. Полето {0} не е валиден телефонен номер.
Validator_RangeAttribute {0} трябва да варира от {1} до {2}. Полето {0} трябва да е между {1} и {2}.
Validator_RegularExpressionAttribute {0} трябва да съответства на редовен израз "{1}". Полето {0} трябва да съответства на редовния израз "{1}".
Validator_RequiredAttribute изисква се {0}. Полето за {0} е задължително.
Validator_StringLengthAttribute {0} трябва да са в рамките на {1} цифри. Полето {0} трябва да бъде низ с максимална дължина на {1}.
Validator_UrlAttribute {0} не е валиден URL адрес. Полето {0} не е валиден напълно квалифициран HTTP, https или FTP URL адрес.
Validator_StringLengthAttributeWithMin {0} трябва да са поне {2} {1} цифри. Полето {0} трябва да бъде низ с максимална дължина на {1} и минимална дължина на {2}.

След това създайте следния клас adapterProvider:

Името на класа е произволно, но този път CustomValidationAttributeAdapterProvider ще напиша клас, наречен и ще напиша кода, както следва: Местоположението на файла с кода е произволно, но пробата се поставя в папка, наречена Адаптер.

using Microsoft.AspNetCore.Mvc.DataAnnotations;
using Microsoft.Extensions.Localization;
using System;
using System.ComponentModel.DataAnnotations;

namespace LocalizationDefaultValidation
{
  public class CustomValidationAttributeAdapterProvider : IValidationAttributeAdapterProvider
  {
    /// <summary>コード簡略化のためのリソースキー命名規則プリフィックス</summary>
    private const string RESOURCE_KEY_PREFIX = "Validator_";

    private readonly IValidationAttributeAdapterProvider _fallback = new ValidationAttributeAdapterProvider();

    /// <summary>
    /// 指定された ValidationAttribute の IAttributeAdapter を返します。
    /// </summary>
    /// <param name="attribute">IAttributeAdapter を作成するための ValidationAttribute。</param>
    /// <param name="stringLocalizer">メッセージの作成に使用される IStringLocalizer。</param>
    /// <returns>指定された属性の IAttributeAdapter。</returns>
    IAttributeAdapter IValidationAttributeAdapterProvider.GetAttributeAdapter(ValidationAttribute attribute, IStringLocalizer stringLocalizer)
    {
      // すでにエラーメッセージが設定されている場合はそれを使用するのでここでは何も設定しない
      if (attribute.ErrorMessageResourceName != null) return _fallback.GetAttributeAdapter(attribute, stringLocalizer);

      // attribute には「Required」や「StringLength」などが設定されています
      Type attrType = attribute.GetType();
      
      // プリフィックスと属性のクラス名からローカライズ用のキーを生成します
      var key = RESOURCE_KEY_PREFIX + attrType.Name;

      // IStringLocalizer から指定したキーでローカライズされたテキストを取得します
      // ない場合は getString にそのまま key の値が入ります
      var getString = stringLocalizer[key];

      // 正しくローカライズされたテキストが取得できた場合は ErrorMessage に値をセットします。
      if (key != getString && attribute.ErrorMessage != getString)
      {
        attribute.ErrorMessage = getString;
      }

      // 設定した attribute を渡します
      return _fallback.GetAttributeAdapter(attribute, stringLocalizer);
    }
  }
}

Ако зададете свойствата на модела на Required или , методът се извиква за всяка StringLength IValidationAttributeAdapterProvider.GetAttributeAdapter проверка.

ErrorMessageResourceName Ако свойството съдържа стойност, кодът от страната на модела вече има съобщение или ключ за локализация, така че се връща такъв, какъвто е.

Ако е празен, съпоставете имената на класовете на атрибута префикс и проверка, за да го направите локализационния ключ и след това извлечете локализирания текст от ключа и го задайте ErrorMessage на . Това позволява повечето от съобщенията по подразбиране да бъдат локализирани.

След това регистрирайте този клас в .cs за стартиране. По принцип можете да го добавите, както следва.

// 省略

using Microsoft.AspNetCore.Mvc.DataAnnotations;

namespace LocalizationDefaultValidation
{
  public class Startup
  {
    // 省略

    // このメソッドはランタイムによって呼び出されます。 このメソッドを使用して、コンテナーにサービスを追加します。
    public void ConfigureServices(IServiceCollection services)
    {
      // 省略

      // 作成した CustomValidationAttributeAdapterProvider をシングルトンとして登録します
      services.AddSingleton<IValidationAttributeAdapterProvider, CustomValidationAttributeAdapterProvider>();
    }
    
    // 省略
  }
}

Пусни го, за да се увериш, че работи както трябва.

Промяна на съществуващо съобщение по параметри

Например съобщението за StringLength атрибут е локализирано, като например "{0} трябва да е в рамките на {1} цифри". MinimumLength Ако свойството е зададено, може да искате да промените {0} като "Посочете {2} цифри или повече {1} в рамките на редица цифри".

В този CustomValidationAttributeAdapterProvider случай разтегни класа, както следва:

// 省略

namespace LocalizationDefaultValidation
{
  public class CustomValidationAttributeAdapterProvider : IValidationAttributeAdapterProvider
  {
    // 省略

    IAttributeAdapter IValidationAttributeAdapterProvider.GetAttributeAdapter(ValidationAttribute attribute, IStringLocalizer stringLocalizer)
    {
      if (attribute is StringLengthAttribute slAttribute)
      {
        // attribute が StringLengthAttribute で MinimumLength が設定されている場合はメッセージを変える
        if (slAttribute.MinimumLength >= 1)
        {
          attribute.ErrorMessage = stringLocalizer["Validator_StringLengthAttributeWithMin"];
          return _fallback.GetAttributeAdapter(slAttribute, stringLocalizer);
        }
      }

      // 省略
    }
  }
}

Тъй като методът се извиква за всяка GetAttributeAdapter проверка, ако преминатата променлива attribute е била StringLengthAttribute MinimumLength атрибут, проверете свойството, Ако е зададено, получете съобщение, което взема предвид и минималния брой цифри и ErrorMessage замества .

Ако го стартирате, проверете и съобщението се промени, то е ОК.

Други родови съобщения

Някои съобщения не са локализирани, след като го направите досега. Например съобщения, които се определят само в Javascript.

Целта е jquery.validate.js съобщението, изброено във файла.

Тъй като този файл не трябва да се редактира директно, за да замените тези съобщения с локализиран текст, input трябва да добавите атрибут като към маркера и да зададете data-val-XXXX локализиран текст там. XXXX частта съдържа ключа на фигурата jquery.validate.js по-горе. (напр. data-val-number т.н.)

data-val-XXXX За да зададете локализиран текст на атрибути в

Нека локализираме съобщението, когато нечислите знаци са включени в полето за въвеждане на възраст като пример. UserViewModel.Age има Range атрибут, а изходният HTML прилага data-val-range атрибута за въвеждане.

<div class="form-group">
  <label class="control-label" for="Age">Age</label>
  <input class="form-control" type="number" data-val="true" data-val-range="Ageは0から150の範囲で指定してください。" data-val-range-max="150" data-val-range-min="0" data-val-required="Ageは必須です。" id="Age" name="Age" value="" />
  <span class="text-danger field-validation-valid" data-valmsg-for="Age" data-valmsg-replace="true"></span>
</div>

Обаче data-val-number числова проверка съобщение не е локализиран, защото няма атрибут. Ще добавите друг атрибут от страната на модела, за да отпечатате и локализирания атрибут, добавен към data-val-number съобщението.

Първата стъпка е да създадете клас атрибут, който проверява дали е IntAttribute число: Тя е ограничена до тук, но вие сте int свободни да го промените в зависимост от ситуацията. Файлът с код може да бъде навсякъде, но този път е в папката "Адаптер".

using System.ComponentModel.DataAnnotations;

namespace LocalizationDefaultValidation
{
  public class IntAttribute : ValidationAttribute
  {
    public override bool IsValid(object value)
    {
      // 渡された値が int であれば有効な値とする
      return value is int;
    }
  }
}

Ако зададената стойност е int тип, това е атрибут, който е само нормална присъда, но на практика е 100% нормално, ако е зададено на свойство от тип int, така че самата обработка на този клас няма никакъв смисъл. Целта на това време е да се показват локализирани съобщения за грешки в клиента.

След това създайте следния клас IntAttributeAdapter адаптер: Това също е в папката Адаптер.

using Microsoft.AspNetCore.Mvc.DataAnnotations;
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
using Microsoft.Extensions.Localization;

namespace LocalizationDefaultValidation
{
  /// <summary>Int 属性のアダプター。</summary>
  public class IntAttributeAdapter : AttributeAdapterBase<IntAttribute>
  {
    /// <summary>受け取った IStringLocalizer を保持しておく</summary>
    IStringLocalizer _stringLocalizer;

    public IntAttributeAdapter(IntAttribute attribute, IStringLocalizer stringLocalizer)
        : base(attribute, stringLocalizer)
    {
      _stringLocalizer = stringLocalizer;
    }

    /// <summary>バリデーションの追加処理として呼ばれる。</summary>
    public override void AddValidation(ClientModelValidationContext context)
    {
      // 新たに data-val-number 属性をマージして追加します。値はローカライズしたテキストをセットします。
      MergeAttribute(context.Attributes, "data-val-number", _stringLocalizer["ModelBinding_NonPropertyValueMustBeANumber"]);
    }

    /// <summary>サーバーのエラーメッセージはそのまま返します。</summary>
    public override string GetErrorMessage(ModelValidationContextBase validationContext) => Attribute.ErrorMessage;
  }
}

Точката тук е AddValidation методът, описан в MergeAttribute метода.

Обединяване на атрибути като атрибути на входния маркер, за да се върнете data-val-number към клиента. Стойността, която трябва да зададете в атрибута, задава локализирано съобщение за грешка.

Върнете този адаптер в класа, който създадохте CustomValidationAttributeAdapterProvider по-рано.

// 省略

namespace LocalizationDefaultValidation
{
  public class CustomValidationAttributeAdapterProvider : IValidationAttributeAdapterProvider
  {
    // 省略
    
    IAttributeAdapter IValidationAttributeAdapterProvider.GetAttributeAdapter(ValidationAttribute attribute, IStringLocalizer stringLocalizer)
    {
      // IntAttribute の場合は IntAttributeAdapter 経由で返す
      if (attribute is IntAttribute intAttribute)
      {
        return new IntAttributeAdapter(intAttribute, stringLocalizer);
      }

      // 省略 (前に追加したコード)
    }
  }
}

Ако стойността за проверка IntAttribute е , върнете тази, която сте създали IntAttributeAdapter по-рано. Свойството с атрибута Int сега се добавя, когато се показва от страната на data-val-number клиента.

Накрая добавете UserViewModel.Age IntAttribute към .

След като оправите кода си, изпробвайте го, за да видите дали искате да го проверите.

<div class="form-group">
  <label class="control-label" for="Age">Age</label>
  <input class="form-control" type="number" data-val="true" data-val-number="数字を指定してください。" data-val-range="Ageは0から150の範囲で指定してください。" data-val-range-max="150" data-val-range-min="0" data-val-required="Ageは必須です。" id="Age" name="Age" value="" />
  <span class="text-danger field-validation-valid" data-valmsg-for="Age" data-valmsg-replace="true"></span>
</div>

Има и други не-локализирани съобщения, но можете да ги локализирате в описаните по-горе методи. Добавете код, когато имате нужда от него.