Багатомовна підтримка повідомлень за промовчанням, що відображаються під час перевірки вводу

Дата створення сторінки :

середовище

ASP.NET ядро
  • 5.0 MVC

Спочатку

Багатомовна підтримка повідомлень перевірки вводу за промовчанням за допомогою цих порад може бути не завершена. Будь ласка, розберіться з зниклими безвісти.

Крім того, зміна мови залежно від стану сеансу або стану кеша все ще може відображатися в тексті з попередньої мови.

Існує кілька способів змінити повідомлення перевірки вводу за промовчанням, кожен з яких можна об'єднати. Він не один з них, тому якщо ви хочете його надійно змінити, потрібно відповідати всім візерункам.

Передумови

Ця порада написана як розуміння наступних порад:

Крім того, якщо ви створюєте новий проект, ви повинні додати наступні файли та код на основі наведених вище порад.

  • Створіть файл SharedResource.resx (+en, es). (Оскільки перекладено лише повідомлення цієї поради, вміст може бути пустим.)
  • Створення файлу спільного джерела.cs
  • Додати код локалізації до запуску.ConfigureServices
  • Додати код локалізації до запуску.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()));
  }
}
Index.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>
Create.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");}
}
UserViewModel.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} не є припустимою повністю кваліфікованою URL-адресою http, https або ftp.
Validator_StringLengthAttributeWithMin {0} повинні бути не менше {2} {1} цифр. Поле {0} має бути рядком з максимальною довжиною {1} і мінімальною довжиною {2}.

Далі створіть такий клас AdapterProvider:

Ім'я класу довільне, але на цей раз CustomValidationAttributeAdapterProvider я напишу клас, який називається, і напишу код наступним чином: Розташування файлу коду довільне, але зразок поміщається в папку під назвою Adapter.

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 Щоб встановити локалізований текст атрибути в

Давайте локалізуємо повідомлення, коли нечислові символи включені в поле введення Age як приклад. 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 можете змінити його в залежності від ситуації. Кодовий файл може бути в будь-якому місці, але на цей раз він є в папці Adapter.

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>

Є й інші нелокалізовані повідомлення, але їх можна локалізувати описаними вище способами. Додайте код, коли він вам потрібен.