Многоязычная поддержка сообщений по умолчанию, отображаемых во время проверки ввода

Дата создания страницы :

окружающая среда

ASP.NET Ядро
  • 5.0 MVC

Сначала

Многоязычная поддержка сообщений проверки ввода по умолчанию с помощью этих советов может быть не полной. Пожалуйста, разберитесь с недостающими.

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

Существует несколько способов изменения входного сообщения проверки по умолчанию, каждый из которых можно комбинировать. Это не один из них, поэтому если вы хотите изменить его надежно, вам нужно соответствовать всем шаблонам.

предпосылка

Эти советы написаны с пониманием следующих советов:

Кроме того, при создании нового проекта необходимо добавить следующие файлы и код на основе приведенных выше советов.

  • Создайте файл SharedResource.resx (+en, es). (Поскольку переводится только сообщение этого совета, содержимое может быть пустым.)
  • Создание файла SharedResource.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");}
}
UserViewМодель.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 with, чтобы можно было заменить полученные 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 класс Adapter: Он также находится в папке Адаптер.

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>

Есть и другие нелокализованные сообщения, но локализовать их можно в описанных выше методах. Добавляйте код, когда он вам нужен.