ارتقاء به ASP.NET Core 1.0 - قسمت 19 - بومی سازی
اندازه‌ی قلم متن
تخمین مدت زمان مطالعه‌ی مطلب: دوازده دقیقه

هدف از زیر ساخت بومی سازی در ASP.NET Core، حذف عبارات و رشته‌های درج شده‌ی در کلاس‌ها و ویووهای مختلف برنامه و انتقال آن‌ها به فایل‌های منبع resx است و سپس استفاده‌ی از آن‌ها توسط تزریق وابستگی‌ها. به این ترتیب می‌توان بر اساس نوع فرهنگ درخواستی کاربر جاری، رشته‌های درج شده را به صورت پویا، در زمان اجرای برنامه، بر اساس ترجمه‌های آن‌ها به کاربر نمایش داد.


نحوه‌ی تعیین فرهنگ ترد جاری در ASP.NET Core

در نگارش‌های پیشین ASP.NET، برای تعیین فرهنگ ترد جاری، از یکی از دو روش ذیل استفاده می‌شود:
الف) افزودن مدخل بومی سازی به فایل web.config
<system.web>
    <globalization uiCulture="fa-IR" culture="fa-IR" />
</system.web>
ب) و یا تعیین فرهنگ ترد با کدنویسی مستقیم در فایل global.asax
protected void Application_BeginRequest()
{
   Thread.CurrentThread.CurrentCulture = new CultureInfo("fa-IR");
   Thread.CurrentThread.CurrentUICulture = new CultureInfo("fa-IR");
}
در ASP.NET Core با حذف شدن System.Web و همچنین فایل global.asax، برای تعیین فرهنگ پیش فرض ترد جاری، به همراه فرهنگ‌هایی که برنامه از آن‌ها پشتیبانی می‌کند، به صورت ذیل عمل می‌شود:
public void Configure(IApplicationBuilder app)
{
    app.UseRequestLocalization(new RequestLocalizationOptions
    {
        DefaultRequestCulture = new RequestCulture(new CultureInfo("fa-IR")),
        SupportedCultures = new[]
        {
            new CultureInfo("en-US"),
            new CultureInfo("fa-IR")
        },
        SupportedUICultures = new[]
        {
            new CultureInfo("en-US"),
            new CultureInfo("fa-IR")
        }
    });
در اینجا با مراجعه به کلاس آغازین برنامه و افزودن تنظیمات میان افزار RequestLocalization، می‌توان فرهنگ پیش فرض درخواست جاری و یا فرهنگ‌های پشتیبانی شده را مشخص کرد.
- تنظیمات SupportedCultures بر روی نمایش تاریخ، ساعت و واحد پولی تاثیر دارند. همچنین می‌توانند بر روی نحوه‌ی مقایسه‌ی حروف و مرتب سازی آن‌ها تاثیر داشته باشند.
- تنظیمات SupportedUICultures مشخص می‌کنند که کدامیک از فایل‌های resx برنامه که مداخل ترجمه‌های آن‌را به زبان‌های مختلف مشخص می‌کنند، باید بارگذاری شوند.
- تنظیم DefaultRequestCulture در صورت مشخص نشدن فرهنگ ترد جاری مورد استفاده قرار می‌گیرد.

یک مثال: هر ترد در دات نت دارای اشیاء CurrentCulture و CurrentUICulture است. اگر فرهنگ ترد جاری به en-US تنظیم شده باشد، متد DateTime.Now.ToLongDateString، خروجی نمونه Thursday, February 18, 2016 را نمایش می‌دهد.


زمانیکه میان افزار RequestLocalization فعال می‌شود، سه تامین کننده‌ی پیش فرض (مقدار‌های پیش فرض خاصیت RequestCultureProviders شیء RequestLocalizationOptions فوق)، جهت مشخص ساختن فرهنگ ترد جاری بکار گرفته خواهند شد:
الف) از طریق کوئری استرینگ با فعال سازی QueryStringRequestCultureProvider
http://localhost:5000/?culture=es-MX&ui-culture=es-MX
http://localhost:5000/?culture=es-MX
برای مثال در اینجا QueryStringRequestCultureProvider به دنبال کوئری استرینگ‌های culture و یا ui-culture گشته و با رسیدن به es-MX، فرهنگ جاری را به اسپانیایی مکزیکی تنظیم می‌کند. در این حالت اگر فقط culture ذکر شود، ui-culture نیز به همان مقدار تنظیم خواهد شد.
ب) از طریق نام کوکی با فعال سازی CookieRequestCultureProvider
CookieRequestCultureProvider کوکی ویژه‌ای را با نام پیش فرض AspNetCore.Culture. ایجاد می‌کند. این کوکی برای ردیابی اطلاعات بومی سازی انتخابی کاربر بکار می‌رود. برای مثال اگر به مقدار ذیل تنظیم شود:
 c='en-UK'|uic='en-US'
c آن به معنای culture و uic آن به معنای ui-culture خواهد بود.
ج) از طریق هدر مخصوص Accept-Language با فعال سازی AcceptLanguageHeaderRequestCultureProvider که می‌تواند به همراه درخواست HTTP ارسال شود.

اگر تمام این حالت‌ها تنظیم نشده بودند، آنگاه از مقدارDefaultRequestCulture  استفاده می‌شود. برای مثال اگر مرورگر به صورت پیش فرض هدر Accept-Language را en-US ارسال می‌کند :


دیگر کار به پردازش مقدارDefaultRequestCulture  نخواهد رسید.

اکنون اگر علاقمند بودید تا به کاربر امکان انتخاب زبانی را بدهید، یک چنین اکشن متدی را طراحی کنید:
public IActionResult SetFaLanguage()
{
    Response.Cookies.Append(
        CookieRequestCultureProvider.DefaultCookieName,
        CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(new CultureInfo("fa-IR"))),
        new CookieOptions { Expires = DateTimeOffset.UtcNow.AddYears(1) }
    );
 
    return RedirectToAction("GetTitle");
}
این اکشن متد بر اساس تامین کننده‌ی کوکی ردیابی زبان انتخاب شده‌ی توسط کاربر و یا CookieRequestCultureProvider کار می‌کند و توسط آن، فرهنگ جاری برنامه به زبان فارسی تنظیم می‌شود. هرگاه که این اکشن متد فراخوانی شود، کوکی AspNetCore.Culture. به مقدار c=fa-IR|uic=fa-IR تنظیم می‌شود:


از اینجا به بعد است که اگر نام کنترلر شما TestLocalController باشد، فایل منبع متناظر با آن یعنی Controllers.TestLocalController.fa.resx، به صورت خودکار بارگذاری و پردازش خواهد شد. در غیر اینصورت فایل نمونه‌ی ختم شده‌ی به en.resx پردازش می‌شود؛ چون این زبان به صورت پیش فرض در هدر Accept-Language قید شده‌است.


آماده سازی برنامه برای کار با فایل‌های منبع زبان‌های مختلف

ابتدا پوشه‌ی جدیدی را به نام Resources به ریشه‌ی پروژه اضافه کنید. سپس به کلاس آغازین برنامه مراجعه کرده و محل یافت شدن این پوشه را معرفی کنید:
public void ConfigureServices(IServiceCollection services)
{
    services.AddLocalization(options => options.ResourcesPath = "Resources");
    services.AddMvc()
        .AddViewLocalization(LanguageViewLocationExpanderFormat.Suffix)
        .AddDataAnnotationsLocalization();
در اینجا سرویس جدید Localization، به لیست سرویس‌های ثبت شده‌ی در IoC Container اضافه می‌شود. همچنین توسط خاصیت  ResourcesPath  آن مشخص شده‌است که فایل‌های resx را باید از کجا دریافت کند.
به علاوه به سرویس ASP.NET MVC، تنظیمات بومی سازی Viewها و DataAnnotations نیز اضافه شده‌اند. تنظیم suffix به معنای  view file suffix و یا مثلا fr در نام فایل Index.fr.cshtml است.


نحوه‌ی تعریف و پوشه بندی فایل‌های منبع زبان‌های مختلف

تا اینجا پوشه‌ی جدید Resources را به پروژه اضافه، معرفی و سرویس‌های مرتبط را نیز فعال کردیم. پس از آن نوبت به افزودن فایل‌های resx است. برای این منظور بر روی پوشه‌ی منابع کلیک راست کرده و گزینه‌ی add->new item را انتخاب کنید.


در اینجا با جستجوی resource، می‌توان فایل resx جدیدی را به پروژه اضافه کرد؛ اما ... انتخاب نام آن باید بر اساس نکات ذیل باشد:
الف) برای کنترلرها یکی از دو مسیر / دار و یا نقطه دار جستجو می‌شوند:
Resources/Controllers.HomeController.fr.resx
Resources/Controllers/HomeController.fr.resx

در اینجا fr ذکر شده، همان LanguageViewLocationExpanderFormat.Suffix است که پیشتر بحث شد. قسمت ابتدایی Controllers همیشه ثابت است (یا به صورت نام یک پوشه و یا به عنوان قسمت اول نام فایل). سپس نام کلاس کنترلر به همراه نام فرهنگ مدنظر باید ذکر شوند. قسمت نام پوشه‌ی Resources را نیز به services.AddLocalization معرفی کرده‌ایم.

ب) برای Viewها نیز همان حالت‌های / دار و یا نقطه دار بررسی می‌شوند:
Resources/Views.Home.About.fr.resx
Resources/Views/Home/About.fr.resx


برای تمام فایل‌ها و کلاس‌ها می‌توان فایل منبع ایجاد کرد

در این نگارش از ASP.NET، در حالت کلی، نام یک فایل منبع، همان نام کامل کلاس آن است؛ منهای فضای نام آن (اگر این فایل منبع در همان اسمبلی قرار گیرد). برای مثال اگر می‌خواهید برای کلاس Startup برنامه، فایل منبعی را درست کنید و نام کامل آن با درنظر گرفتن فضای نام، معادل LocalizationWebsite.Web.Startup است، ابتدای فضای نام آن‌را حذف کنید و سپس آن‌را ختم به fa.resx کنید؛ مثلا Startup.fa.resx
اگر محل واقع شدن فایل‌های resx در همان اسمبلی اصلی پروژه باشند، نیازی به ذکر فضای نام پیش فرض پروژه نیست. برای مثال اگر فضای نام پیش فرض پروژه‌ی وب جاری MyLocalizationWebsite.Web است، بجای نام فایل MyLocalizationWebsite.Web.Controllers.HomeController.fr.resx می‌توانید به صورت خلاصه بنویسید Controllers.HomeController.fr.resx. در غیراینصورت (استفاده از اسمبلی‌های دیگر)، ذکر کامل فضای نام مرتبط هم الزامی است.


چند نکته:
- اگر ResourcesPath را در services.AddLocalization معرفی نکنید، مسیر پیش فرض یافتن فایل‌های resx مربوط به کنترلرها، پوشه‌ی ریشه‌ی پروژه است و برای Viewها، همان پوشه‌ی محل واقع شدن View متناظر خواهد بود.
- اینکه کدام فایل منبع در برنامه بارگذاری می‌شود، دقیقا مرتبط است با فرهنگ ترد جاری و این فرهنگ به صورت پیش فرض en-US است (چون همواره در هدر Accept-Language ارسالی توسط مرورگر وجود دارد). برای تغییر آن، از نکته‌ی اکشن متد public IActionResult SetFaLanguage ابتدای بحث استفاده کنید (در غیراینصورت در آزمایشات خود شاهد بارگذاری فایل‌های منبع دیگری بجز en.resx‌ها نخواهید بود).
- فایل‌های منبع را به صورت کامپایل شده در پوشه‌ی bin برنامه خواهید یافت:



خواندن اطلاعات منابع در کنترلرهای برنامه

فرض کنید کنترلری را به نام TestLocalController ایجاد کرده‌ایم. بنابراین فایل منبع فارسی متناظر با آن Controllers.TestLocalController.fa.resx خواهد بود؛ با این محتوای نمونه:


محتوای این کنترلر نیز به صورت ذیل است:
using System;
using System.Globalization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Localization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Localization;
using Microsoft.Extensions.Localization;
 
namespace Core1RtmEmptyTest.Controllers
{
    public class TestLocalController : Controller
    {
        private readonly IStringLocalizer<TestLocalController> _stringLocalizer;
        private readonly IHtmlLocalizer<TestLocalController> _htmlLocalizer;
 
        public TestLocalController(
            IStringLocalizer<TestLocalController> stringLocalizer,
            IHtmlLocalizer<TestLocalController> htmlLocalizer)
        {
            _stringLocalizer = stringLocalizer;
            _htmlLocalizer = htmlLocalizer;
        }
 
        public IActionResult Index()
        {
            var name = "DNT";
            var message = _htmlLocalizer["<b>Hello</b><i> {0}</i>", name];
            ViewData["Message"] = message;
            return View();
        }
 
        [HttpGet]
        public string GetTitle()
        {
            var about = _stringLocalizer["About Title"];
            return about;
        }
 
        public IActionResult SetFaLanguage()
        {
            Response.Cookies.Append(
                CookieRequestCultureProvider.DefaultCookieName,
                CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(new CultureInfo("fa-IR"))),
                new CookieOptions { Expires = DateTimeOffset.UtcNow.AddYears(1) }
            );
 
            return RedirectToAction("GetTitle");
        }
    }
}
در اینجا نحوه‌ی دسترسی به فایل‌های منبع را در کنترلرها مشاهده می‌کنید. سرویس IStringLocalizer برای خواندن key/valueهای معمولی طراحی شده‌است و سرویس IHtmlLocalizer برای خواندن key/valueهای تگ دار، بکار می‌رود. علت تنظیم شدن پارامتر جنریک آن‌ها به نام کنترلر جاری این است که این سرویس‌ها بدانند دقیقا چه نوعی را قرار است بارگذاری کنند و دقیقا باید به دنبال کدام فایل بگردند. این سرویس‌ها یک کلید را می‌گیرند و یک خروجی و مقدار را باز می‌گردانند.
اگر برنامه را در حالت معمولی اجرا کنید و سپس آدرس http://localhost:7742/testlocal/gettitle را درخواست کنید، عبارت About Title را مشاهده می‌کنید؛ به دو علت:
الف) هنوز فرهنگ پیش فرض ترد جاری همان en-US است که توسط مرورگر ارسال شده‌است.
ب) چون فایل resx متناظر با فرهنگ پیش فرض ترد جاری یافت نشده‌است، مقدار همان کلید درخواستی بازگشت داده می‌شود؛ یعنی همان About Title.

برای رفع این مشکل آدرس http://localhost:7742/testlocal/SetFaLanguage را درخواست کنید. به این صورت با تنظیم کوکی ردیابی فرهنگ ترد جاری به زبان فارسی، خروجی GetTile این‌بار «درباره» خواهد بود.


خواندن اطلاعات منابع در Viewهای برنامه

فرض کنید فایل Views.TestLocal.Index.fa.resx (فایل منبع کنترلر TestLocal و ویوو Index آن به زبان فارسی) دقیقا همان محتوای فایل Controllers.TestLocalController.fa.resx فوق را دارد (اگر نام پوشه‌ی Views را تغییر داده‌اید، قسمت ابتدایی نام فایل Views را هم باید تغییر دهید). برای دسترسی به اطلاعات آن در یک ویوو، می‌توان از سرویس IViewLocalizer  به نحو ذیل استفاده کرد:
@using Microsoft.AspNetCore.Mvc.Localization
@inject IViewLocalizer Localizer
 
@{
}
Message @ViewData["Message"]
<br/>
@Localizer["<b>Hello</b><i> {0}</i>", "DNT"]
<br/>
@Localizer["About Title"]
در اینجا ViewData، از همان اطلاعات اکشن متد Index استفاده می‌کند.
Localizer از طریق تزریق سرویس IViewLocalizer  به View برنامه تامین می‌شود. این سرویس در پشت صحنه از همان IHtmlLocalizer استفاده می‌کند و در حین استفاده‌ی از آن، اطلاعات تگ‌ها انکد (encoded) نخواهند شد (به همین جهت برای کار با کلیدها و مقادیر تگ‌دار توصیه می‌شود).


استفاده از اطلاعات منابع در DataAnnotations

قسمت اول فعال سازی بومی سازی DataAnnotations با ذکر AddDataAnnotationsLocalization در متد ConfigureServices، در ابتدای بحث انجام شد و همانطور که پیشتر نیز عنوان گردید، در این نگارش از ASP.NET، برای تمام کلاس‌های برنامه می‌توان فایل منبع ایجاد کرد. برای مثال اگر کلاس RegisterViewModel در فضای نام ViewModels.Account قرار گرفته‌است، نام فایل منبع آن یکی از دو حالت / دار و یا نقطه دار ذیل می‌تواند باشد:
Resources/ViewModels.Account.RegisterViewModel.fr.resx
Resources/ViewModels/Account/RegisterViewModel.fr.resx

محتوای این کلاس را در ذیل مشاهده می‌کنید:
using System.ComponentModel.DataAnnotations;
 
namespace Core1RtmEmptyTest.ViewModels.Account
{
    public class RegisterViewModel
    {
        [Required(ErrorMessage = "EmailReq")]
        [EmailAddress(ErrorMessage = "EmailType")]
        [Display(Name = "Email")]
        public string Email { get; set; }
    }
}
در این حالت مقداری که برای ErrorMessage ذکر می‌شود، کلیدی است که باید در فایل منبع جستجو شود:


یک نکته: هیچ الزامی ندارد که کلیدها را به این شکل وارد کنید. از این جهت که اگر این کلید در فایل منبع یافت نشد و یا فرهنگ ترد جاری با فایل‌های منبع مهیا تطابقی نداشت، عبارتی را که کاربر مشاهده می‌کند، دقیقا معادل «EmailReq» خواهد بود. بنابراین در اینجا می‌توانید کلید را به صورت کامل، مثلا مساوی «The Email field is required» وارد کنید و همین عبارت را به عنوان کلید در فایل منبع ذکر کرده و مقدار آن‌را مساوی ترجمه‌ی آن قرار دهید. این نکته در تمام حالات کار با کنترلرها و ویووها نیز صادق است.


استفاده از یک منبع اشتراکی

اگر می‌خواهید تعدادی از منابع را در همه‌جا در اختیار داشته باشید، روش کار به این صورت است:
الف) یک کلاس خالی را به نام SharedResource دقیقا با فرمت ذیل در پوشه‌ی Resources ایجاد کنید:
// Dummy class to group shared resources
namespace Core1RtmEmptyTest
{
   public class SharedResource
   {
   }
}
ب) اکنون فایل‌های منبع خود را در پوشه‌ی Resources، دقیقا با این نام‌های خاص ایجاد کنید:
SharedResource.fa.resx
SharedResource.en-US.resx
و امثال آن

ج) برای استفاده‌ی از این منبع اشتراکی در کلاس‌های مختلف برنامه تنها کافی است در حین تزریق وابستگی‌ها، نوع آرگومان جنریک IStringLocalizer را به SharedResource تنظیم کنید:
 IStringLocalizer<SharedResource> sharedLocalizer
و یا حتی در ویووهای برنامه نیز می‌توان از آن استفاده کرد:
 @inject IHtmlLocalizer<SharedResource> SharedLocalizer
  • #
    ‫۸ سال و ۲ ماه قبل، دوشنبه ۴ مرداد ۱۳۹۵، ساعت ۱۷:۱۳
    مسئله ای که وجود داره اینه چطور میشه به ClassLibrary جداگانه ای انتقال داده و مسیر را در Startup تنظیم نمود


    اگر نام classlibrary را مساوی با Sample.Resources قرار بدهیم (تنظیمات localization اعمال نمیشود) :
    services.AddLocalization(options => options.ResourcesPath = "Sample.Resources");
    و فقط در صورت تنظیم بصورت زیر (تنظیمات localization اعمال میشود)   :
    services.AddLocalization(options => options.ResourcesPath = "Resources");
    و اگر از PresentaionLayer برای نگهداری ViewModels استفاده کنیم، تنظیمات  استفاده از اطلاعات منابع در DataAnnotations (نام فایل منبع ) پیدا نمی‌شود :
    Resources/ViewModels.Account.RegisterViewModel.fr.resx
    Resources/ViewModels/Account/RegisterViewModel.fr.resx

    همچنین استفاده از یک منبع اشتراکی در classlibrary جداگانه که تعبیه شده اعمال نمی‌شود

    اگر امکان تنظیم منبع Resources از طریق خواندن Assembly وجود داشته باشه انعطاف بیشتری خواهد داشت اما هنوز موفق به انجام اینکار نشدم.
    • #
      ‫۸ سال و ۲ ماه قبل، دوشنبه ۴ مرداد ۱۳۹۵، ساعت ۱۸:۱۶
      قسمت «... در غیراینصورت (استفاده از اسمبلی‌های دیگر)، ذکر کامل فضای نام مرتبط هم الزامی است ...» مهم است. از این جهت که اگر به اسمبلی نهایی دقت کنید (اسمبلی واقع شده‌ی در پوشه‌ی src\Core1RtmEmptyTest\bin\Debug\netcoreapp1.0\fa):

      - در اینجا ابتدا فضای نام اصلی پروژه‌ی جاری ذکر شده‌است. به همین جهت است که عنوان کرده‌اند اگر منابع در اسمبلی جاری هستند، نباید مجددا این فضای نام پیش فرض ذکر شود.
      - سپس نام Resources را مشاهده می‌کنید. بنابراین چیزی که بارگذاری می‌شود، یک منبع مدفون شده‌ی در یک فایل dll است و نه اینکه در زمان اجرا به پوشه‌ی Resources مراجعه می‌شود. این پوشه‌ی Resources در اینجا در حد یک جزء از نام کامل بیشتر مطرح نیست.
      - در آخر هم نام کامل نوع مدنظر ذکر شده‌است. مثلا نام کامل ViewModel مورد استفاده.
      - این نکات در مورد فایل‌های Shared هم مطرح هستند.
      - من چون نام پوشه‌ی Views را به Features تغییر داده‌ام، دومین فایل لیست شده‌ی در اینجا، چنین نامی را دارد (بجای Views استاندارد).

      بنابراین اگر اطلاعات را به اسمبلی‌های دیگر منتقل می‌کنید:
      - به ذکر کامل فضاهای نام دقت داشته باشید.
      - بررسی کنید آیا اسمبلی منبع با اسمی شبیه به Core1RtmEmptyTest.resources.dll در پوشه‌ی bin\Debug\netcoreapp1.0\fa موجود هست یا خیر؟ و اگر موجود است، بررسی کنید چه محتوایی در آن ثبت شده‌است.
      • #
        ‫۸ سال و ۲ ماه قبل، سه‌شنبه ۵ مرداد ۱۳۹۵، ساعت ۰۰:۲۳
        ممنون از پاسخگویی
        نتیجه بررسی :


        اما سوالی که برای من بوجود آمده :
        در یک ClassLibrary با نام (SampleFive.Localization) چطور ذکر کامل فضاهای نام را باید تعریف شود.
        تعریف نام برای منبع اطلاعات login.cshtml :
        SampleFive.Web.Features.Account.Login.fa.resx 
        اما در انتها و پس از Build = اسمبلی نهایی و بررسی نام آن نتیجه با اضافه شدن پیشوندی به ابتدای نام منبع تکمیل میشود که بنظر من مشکل دقیقا همینجاست :
        SampleFive.Localization.SampleFive.Web.Features.Account.Login.fa.resx
        اما دقیقا به شکل صحیح تعریف منبع اطلاعات پی نبردم!
        • #
          ‫۸ سال و ۲ ماه قبل، سه‌شنبه ۵ مرداد ۱۳۹۵، ساعت ۰۰:۴۶
          روش انتقال منابع مرتبط با کلاس‌ها و کنترلرها به یک اسمبلی دیگر
          - فرض کنید یک class library مخصوص NET Core. را به نام Core1RtmTestResources.ExternalResources جهت درج منابع تهیه کرده‌اید و پوشه‌ی Resources را از پروژه‌ی اصلی به آن انتقال داده‌اید (بدون هیچ تغییر نامی).
          - نیازی نیست تا قسمت options.ResourcesPath کلاس آغازین برنامه را تغییر دهید و همان مقدار Resources در این حالت هم کار می‌کند.
          - فقط نکته‌ی مهم اینجا است:
          public TestLocalController(
                      IStringLocalizer<TestLocalController> stringLocalizer,
                      IHtmlLocalizer<TestLocalController> htmlLocalizer)
          زمانیکه این آرگومان‌های جنریک مشخص می‌شوند، دو کاربرد «تعیین نام نوع» و «تعیین محل اسمبلی» واقع شدن آن‌را به یکباره به همراه دارند.
          الان چون این اسمبلی دیگر اسمبلی جاری نیست، باید به نحو زیر عمل کرد:
          private readonly IStringLocalizer _stringLocalizer;
          private readonly IHtmlLocalizer _htmlLocalizer;
           
          public TestLocalController(
              IStringLocalizerFactory stringLocalizerFactory,
              IHtmlLocalizerFactory htmlLocalizerFactory)
          {
              _stringLocalizer = stringLocalizerFactory.Create(
                  baseName: "Controllers.TestLocalController" /*مشخصات کنترلر جاری*/,
                  location: "Core1RtmTestResources.ExternalResources" /*نام اسمبلی ثالث*/);
              _htmlLocalizer = htmlLocalizerFactory.Create(
                  baseName: "Controllers.TestLocalController" /*مشخصات کنترلر جاری*/,
                  location: "Core1RtmTestResources.ExternalResources" /*نام اسمبلی ثالث*/);
          }
          یعنی باید کاری را که در پشت صحنه به صورت خودکار انجام می‌شود، اینبار دستی انجام داد و با کلاس‌های Factory شروع کرد. در اینجا پارامتر location دقیقا به نام اسمبلی ثالث دربرگیرنده‌ی منابع اشاره می‌کند. به این صورت است که اینبار اسمبلی مرتبط، به درستی یافت شده و از آن استفاده می‌شود.
          • #
            ‫۸ سال و ۲ ماه قبل، سه‌شنبه ۵ مرداد ۱۳۹۵، ساعت ۰۷:۰۳
            با انتقال پوشه Resources (بدون تغییر نام پوشه و namespace ها):
            پس از بررسی دسترسی به منابع اطلاعات :

            کار میکند »  Controller
            کار نمیکنید » Views ، SharedResource.fa.resx  ،ViewModels  


            ضمنا اگر ViewModels را به class library (مخصوص NET Core. مجزایی به نام Core1RtmTestResources.PresentaionLayer منتقل کنید، حتی در وضعیتی که Resource در ریشه پروژه اصلی Core1RtmTestResources.Web هم قرار داشته باشد منبع اطلاعات را پیدا نمی‌کند (به درستی عمل نمیکند)
      • #
        ‫۴ سال و ۱ ماه قبل، پنجشنبه ۲ مرداد ۱۳۹۹، ساعت ۰۸:۲۷
        سلام و درود
        بنده نتونستم حالت shared رو بدست بیارم.
        xProject.Domain.SharedResource.fa برای annotation‌ها جواب نمیده.
        لایه دامین، پوشه بندی شده و نیاز به 1 منبع اشتراکی برای مدیریت پیام‌های خطا و ... در سطح همون پروژه دامین هست.
        به چه صورت باید پیاده سازی بشه؟
        • #
          ‫۴ سال و ۱ ماه قبل، پنجشنبه ۲ مرداد ۱۳۹۹، ساعت ۱۱:۴۱
          تمام نظرات را یکبار مطالعه کنید: «روش انتقال منابع مرتبط با data annotations و ViewModelها به یک اسمبلی دیگر »، «...  روشی برای بومی سازی DisplayAttribute »، «مثال توضیحات تکمیلی در مورد انتقال منابع به یک اسمبلی دیگر»، «در تکمیل قسمت « استفاده از اطلاعات منابع در DataAnnotations»»  و ...
          • #
            ‫۴ سال و ۱ ماه قبل، پنجشنبه ۲ مرداد ۱۳۹۹، ساعت ۱۵:۵۰
            متشکرم، قبل و بعد از سوال کردن، چندین بار مطالعه کردم.
            مساله ای که دارم رو احتمالا نتونستم درست توضیح بدم، تصویر ساختار پروژه رو پایین قرار دادم، تنظیمات استارت آپ دقیقا همین مواردی هست که در این پست توضیح دادید. در اینجا قصدم صرفا استفاده منبع برای annotation‌های مدل هست.

            منبعی که با کادر قرمز مشخص کردم، به درستی کار میکنن و مشکلی نداره و از کلاس‌های لایه domain میتونن ازش استفاده کنن. اما خب منطقی نیست برای هر کلاس یک منبع بسازم.

            مساله ام، نحوه نامگذاری منبع اشتراکی هست. SharedResource رو هر گونه نامگذاری کردم، domain نتونست اونو بشناسه. صرفا نمیدونم که منبع اشتراکی رو چه نامی بهش بدم. نه نامگذاری تصویر بالا و نه تصویر پایین، هیچکدوم کار نکرد.

            متشکرم.

            • #
              ‫۴ سال و ۱ ماه قبل، پنجشنبه ۲ مرداد ۱۳۹۹، ساعت ۱۵:۵۹
              باید کلاس SharedResource را به پروایدر بومی سازی Data Annotations معرفی کنید تا شناسایی شود:
              services.AddMvc().AddDataAnnotationsLocalization(o =>
              {
                  o.DataAnnotationLocalizerProvider = (type, factory) =>
                  {
                      return factory.Create(typeof(SharedResource));
                  };
              });
              services.AddLocalization(o =>
              {
                  o.ResourcesPath = "Resources";
              });
              • #
                ‫۴ سال و ۱ ماه قبل، پنجشنبه ۲ مرداد ۱۳۹۹، ساعت ۱۷:۵۵
                با این مواردی که فرمودید از کار می‌افته. تنظیماتی که بالاتر گفتم کار میکرد و فقط SharedResource کار نمی‌کرد، بدین صورت بود:
                .AddViewLocalization(LanguageViewLocationExpanderFormat.Suffix, option =>
                                {
                                    option.ResourcesPath = "Resources";
                                })
                                .AddDataAnnotationsLocalization(option =>
                                {
                                    option.DataAnnotationLocalizerProvider = (type, factory) => factory.Create(
                
                                        baseName: type.FullName,
                                        location: "x.Resources");
                                });
                • #
                  ‫۴ سال و ۱ ماه قبل، پنجشنبه ۲ مرداد ۱۳۹۹، ساعت ۱۹:۱۶
                  - factory.Create به معنای ندیدگرفتن تنظیمات قبلی و ارائه یک وهله ساز جدید هست (یعنی قبلی از کار می‌افتد و فقط پروایدر جدید کار می‌کند).
                  - این مورد را الان آزمایش کردم و کار می‌کند:
                  return factory.Create(
                       baseName: "SharedResource",
                       location: "Core3xRtmTestResources.ExternalResources"  //نام اسمبلی ثالث
                  );
                  همان مثال نگارش 1x پیوست در نظرات هست که به نگارش 3x ارتقاء داده شده: Core3xRtmTestResources.zip
                  روی دکمه صفحه‌ی اول آن کلیک کنید (بعد از کلیک بر روی لینک فارسی سازی)، پیام خطا را از فایل shared می‌خواند.
  • #
    ‫۸ سال و ۲ ماه قبل، سه‌شنبه ۵ مرداد ۱۳۹۵، ساعت ۱۴:۴۸
    روش انتقال منابع مرتبط با data annotations و ViewModelها به یک اسمبلی دیگر
    - فرض کنید یک class library مخصوص NET Core. را به نام Core1RtmTestResources.ExternalResources جهت درج منابع تهیه کرده‌اید و پوشه‌ی Resources را از پروژه‌ی اصلی به آن انتقال داده‌اید (بدون هیچ تغییر نامی).
    - نیازی نیست تا قسمت options.ResourcesPath کلاس آغازین برنامه را تغییر دهید و همان مقدار Resources در این حالت هم کار می‌کند.
    - در اینجا دو مورد باید تغییر کنند:
    الف) باید مشخص کنید که این اطلاعات قرار است از کدام اسمبلی خوانده شود:
    public void ConfigureServices(IServiceCollection services)
    {
      services.AddLocalization(options =>
      {
       options.ResourcesPath = "Resources";
      });
     
      services.AddMvc()
        .AddViewLocalization(LanguageViewLocationExpanderFormat.Suffix)
        .AddDataAnnotationsLocalization(options=>
        {
          options.DataAnnotationLocalizerProvider = (type, factory) =>
          {
           return factory.Create(
            baseName: type.FullName /* بر این اساس نام فایل منبع متناظر باید به همراه ذکر فضای نام پایه آن هم باشد */,
            location: "Core1RtmTestResources.ExternalResources" /*نام اسمبلی ثالث*/);
          };
        });
    در اینجا location به نام اسمبلی اشاره می‌کند که حاوی فایل resx مرتبط است. حالت پیش فرض آن (بدون این تنظیمات)، به اسمبلی اشاره می‌کند که کلاس ViewModel در آن قرار گرفته‌است.
    ب) چون در این تنظیم baseName به FullName تنظیم شده‌است، نام فایل منبع باید کامل باشد؛ یعنی باید به همراه فضای نام اصلی اسمبلی هم باشد. مثلا اگر قبلا چنین نامی را داشته
    ViewModels.Account.RegisterViewModel.fa.resx 
    الان باید فضای نام مرتبط را هم داشته باشد (نام کامل نوع آن کلاس):
    Core1RtmTestResources.ViewModels.Account.RegisterViewModel.fa.resx 
    • #
      ‫۸ سال و ۲ ماه قبل، سه‌شنبه ۵ مرداد ۱۳۹۵، ساعت ۱۸:۰۸
      پس از بررسی ، ویژگی زیر درست عمل نمی‌کند:
      [Display(Name = "Email")]  

  • #
    ‫۸ سال و ۲ ماه قبل، سه‌شنبه ۵ مرداد ۱۳۹۵، ساعت ۱۵:۵۲
    استفاده‌ی از منابع منتقل شده‌ی به یک اسمبلی دیگر در Viewها
    در این حالت نیاز است location این اسمبلی ثالث حاوی فایل‌های resx را در ویوو مدنظر صریحا مشخص کرد:
    @using Microsoft.AspNetCore.Mvc.Localization
    @model Core1RtmTestResources.ViewModels.Account.RegisterViewModel
    
    @inject IHtmlLocalizerFactory HtmlLocalizerFactory
    
    @{
        var localizer = HtmlLocalizerFactory.Create(
                    baseName: "Controllers.TestLocalController" /*مشخصات کنترلر جاری*/,
                    location: "Core1RtmTestResources.ExternalResources" /*نام اسمبلی ثالث*/);
    
        var sharedLocalizer = HtmlLocalizerFactory.Create(
                    baseName: "SharedResource" /*مشخصات*/,
                    location: "Core1RtmTestResources.ExternalResources" /*نام اسمبلی ثالث*/);
    }
    
    Activate Persian Localization:
    <a asp-controller="TestLocal" asp-action="SetFaLanguage">SetFaLanguage</a>
    <br />
    Message @ViewData["Message"]
    <br />
    @localizer["<b>Hello</b><i> {0}</i>", "DNT"]
    <br />
    @localizer["About Title"]
    <br />
    shared data: @sharedLocalizer["About Title"]
    <form asp-controller="TestLocal" asp-action="Index" method="post" class="form-horizontal" role="form">
        <input asp-for="Email" />
        <span asp-validation-for="Email" class="text-danger"></span>
        <input type="submit" />
    </form>
  • #
    ‫۸ سال و ۲ ماه قبل، چهارشنبه ۶ مرداد ۱۳۹۵، ساعت ۱۷:۴۳
    با سلام؛ با تنظیم Culture و CultureUI با مقدار fa-IR ، به شکل خودکار خصوصیت Calendar اونها هم به PersianCalendar تنظیم بشه. راه حلش چی هست؟ البته تا قبل از این با استفاده از یه کلاس کمکی ^ و تنظیمات کالچر با آن کلاس در کلاس سراسری امکان پذیر بود ولی تو .Net Core ظاهرا قضیه فرق می‌کنه. راه حلی پیدا کردم که بشه با همون کلاس کمکی به هدف رسید ولی تو هر View باید تکرار بشه:
    @{
    CultureInfo.CurrentCulture =
                    CultureInfo.CurrentUICulture = PersianDateExtensionMethods.GetPersianCulture();
    }
    چون بیرون از View عملا کار نمی‌کنه و ظاهرا کالچر به GregorianCalendar ریست میشه. راه حل استاندارد چی هست؟ ممنون
    • #
      ‫۸ سال و ۲ ماه قبل، چهارشنبه ۶ مرداد ۱۳۹۵، ساعت ۱۸:۲۵
      - کار رندر اطلاعات در ASP.NET MVC از اکشن متد یک کنترلر شروع می‌شود و به View ختم خواهد شد. سفارشی سازی آن توسط اکشن‌فیلترها میسر است:
          public class FaLanguageActionFilterAttribute : ActionFilterAttribute
          {
              public override void OnActionExecuting(ActionExecutingContext context)
              {
                  CultureInfo.CurrentCulture = new CultureInfo("fa-IR");
                  CultureInfo.CurrentUICulture = new CultureInfo("fa-IR");
                  base.OnActionExecuting(context);
              }
          }
      برای توضیحات بیشتر و نحو‌ه‌ی ثبت سراسری آن‌ها مراجعه کنید به مطلب «ارتقاء به ASP.NET Core 1.0 - قسمت 20 - بررسی تغییرات فیلترها»
      + برای تنظیم اطلاعات تکراری Viewها می‌توان کلاس پایه آن‌ها را نیز سفارشی سازی کرد:
          public abstract class MyCustomBaseView<TModel> : RazorPage<TModel>
          {
              protected MyCustomBaseView()
              {
                  CultureInfo.CurrentCulture = new CultureInfo("fa-IR");
                  CultureInfo.CurrentUICulture = new CultureInfo("fa-IR");
              }
      یک مثال تکمیلی
    • #
      ‫۸ سال و ۲ ماه قبل، چهارشنبه ۶ مرداد ۱۳۹۵، ساعت ۱۸:۲۹
      در حالت کلی همانطور که عنوان شد، برای تغییر فرهنگ ترد جاری از یکی از سه روش کوکی، کوئری استرینگ و هدر زبان استفاده می‌شود. این سه تامین کننده، تامین کننده‌های پیش فرض هستند.
      اگر می‌خواهید برای مثال از روش کوکی ذکر شده (در متد public IActionResult SetFaLanguage ابتدای بحث) استفاده نکنید، می‌توان تامین کنند‌ه‌ی چهارمی را هم تدارک دید:
          public class FaRequestCultureProvider : RequestCultureProvider
          {
              public override Task<ProviderCultureResult> DetermineProviderCultureResult(HttpContext httpContext)
              {
                  return Task.FromResult(new ProviderCultureResult("fa-IR"));
              }
          }
      و بعد آن‌را به ابتدای لیست تامین کننده‌ها (insert 0 ذیل) اضافه کرد:
              public void Configure(IApplicationBuilder app)
              {
                  var requestLocalizationOptions = new RequestLocalizationOptions
                  {
                      DefaultRequestCulture = new RequestCulture(new CultureInfo("fa-IR")),
                      SupportedCultures = new[]
                      {
                          new CultureInfo("en-US"),
                          new CultureInfo("fa-IR")
                      },
                      SupportedUICultures = new[]
                      {
                          new CultureInfo("en-US"),
                          new CultureInfo("fa-IR")
                      }
                  };
                  requestLocalizationOptions.RequestCultureProviders.Insert(0, new FaRequestCultureProvider());
                  app.UseRequestLocalization(requestLocalizationOptions);
      به این صورت فرهنگ پیش فرض ترد جاری به fa-IR استاندارد تنظیم می‌شود. برای حالت غیر استاندارد از همان اکشن فیلتر استفاده کنید.
      • #
        ‫۸ سال و ۲ ماه قبل، چهارشنبه ۶ مرداد ۱۳۹۵، ساعت ۱۹:۳۲
        از طریق همان کوکی بر اساس مقاله فرهنگ به fa-IR تنظیم میشه اما تاریخ همان میلادی میمونه:

        • #
          ‫۸ سال و ۲ ماه قبل، چهارشنبه ۶ مرداد ۱۳۹۵، ساعت ۱۹:۴۸
          - اگر از ویندوز 10 که تاریخ شمسی آن فعال شده استفاده کنید، این مورد بعد از تنظیم فرهنگ ترد جاری، شمسی نمایش داده می‌شود (امتحان کردم).
          - ولی درکل بهتر است این تنظیمات استاندارد را تغییر ندهید و از متدهای الحاقی استفاده کنید (هرجایی که نیاز هست و نه به صورت سراسری با دستکاری جزئیات فرهنگ جاری؛ چون اثرات جانبی نامطلوبی را می‌توانند به همراه داشته باشند).
  • #
    ‫۸ سال و ۱ ماه قبل، سه‌شنبه ۲۶ مرداد ۱۳۹۵، ساعت ۲۰:۵۰
    مشکلی در عدم پشتیبانی ، استفاده از Class Library جداگانه جهت بومی سازی، (تا اینجا که بنده تست کردم)
    در نسخه شبانه 1.1.0-alpha1-21873 مشاهده شده که به مخزن aspnet/Localization در بخش  Issues گزارش دادم.
    • #
      ‫۷ سال و ۶ ماه قبل، یکشنبه ۶ فروردین ۱۳۹۶، ساعت ۲۱:۲۶
      من مطابق موارد گفته شده و با توجه به امکانات  DataAnnotations  فرهنگ جاری رو تغییر میدم اما با توجه به ذکر دستور زیر
         opts.DefaultRequestCulture = new RequestCulture(culture: "fa-IR", uiCulture: "fa-IR");
      متاسفانه در زمان اجرای پروژه و موجود نبودن کوکی فرهنگ en-US برگردانده میشود و پس از فراخوانی کنترلر تغییر زبان کوکی به فارسی برگردانده میشود و مشکل حل میشود. ولی متاسفانه در صورت نبود کوکی Default Culture انگیسی است البته با توجه به اینکه از Distibuted sql cache و Identity استفاده میکنم چطور میتونم این مشکل رو در پروژه‌ها حل کنیم؟  
      • #
        ‫۷ سال و ۶ ماه قبل، یکشنبه ۶ فروردین ۱۳۹۶، ساعت ۲۲:۵۵
        در متن توضیح دادم: «... زمانیکه میان افزار RequestLocalization فعال می‌شود، سه تامین کننده‌ی پیش فرض، جهت مشخص ساختن فرهنگ ترد جاری بکار گرفته خواهند شد ...». یکی از این سه مورد را استفاده کنید. مثلا: «... اکنون اگر علاقمند بودید تا به کاربر امکان انتخاب زبانی را بدهید، یک چنین اکشن متدی را طراحی کنید ...». برای نمونه یک منو را طراحی کنید تا کاربر زبان مدنظر را از بین چند زبان انتخاب کند. سپس اکشن متد مرتبط با آن، کار تنظیم کوکی آن‌را انجام دهد.
        البته در اینجا «... تامین کنند‌ه‌ی چهارمی را هم می‌توان تدارک دید ...». کمی بالاتر در قسمت نظرات عنوان شده‌است (FaRequestCultureProvider). کار آن تنظیم فرهنگ پیش فرض ترد جاری به fa-IR استاندارد است.
  • #
    ‫۷ سال و ۶ ماه قبل، جمعه ۲۰ اسفند ۱۳۹۵، ساعت ۲۳:۱۹
    در تکمیل قسمت « استفاده از اطلاعات منابع در DataAnnotations »
    عموما برای بومی سازی DataAnnotations، نیاز به قید resource name و resource type است. در اینجا resource name همان نام کلید منبع تعریف شده‌است و resource type به کلاس Designer.cs آن منبع اشاره می‌کند. برای تولید خودکار آن، نیاز است نام فایل منبع را «بدون قید نام زبان آن» درنظر گرفت؛ برای مثال Controllers.HomeController.resx. در این حالت به صورت خودکار فایل Controllers.HomeController.Designer.cs تولید خواهد شد که از نام کلاس آن (Controllers_HomeController) می‌توان به عنوان مقدار resource type استفاده کرد. پس از آن فایل Controllers.HomeController.fa-IR.resx را ایجاد کنید تا به عنوان منبع زبان فارسی متناظر آن استفاده شود.  
  • #
    ‫۷ سال و ۶ ماه قبل، شنبه ۲۱ اسفند ۱۳۹۵، ساعت ۲۱:۳۲
    پوشه بندی برای Area‌های جدید به چطوری خواهد شد ؟ تداخل HomeController شاید به وجود بیاد .
    • #
      ‫۷ سال و ۶ ماه قبل، شنبه ۲۱ اسفند ۱۳۹۵، ساعت ۲۲:۲۴
      مثل بقیه: Resources/Areas.AreaName.Controllers.HomeController.fa-IR.resx 
    • #
      ‫۶ سال و ۳ ماه قبل، جمعه ۱۸ خرداد ۱۳۹۷، ساعت ۲۱:۴۱
      من روی سورس پروژه DNTIdentity  کار می‌کنم، برای فایل‌های منابع از پروژه Class Library مجزا و روش پوشه بندی استفاده می‌کنم به صورت زیر:

      و طبق کامنت‌های فوق، داخل Contractor رو هم اینطور تعریف می‌کنم:

                  _stringLocalizer = stringLocalizerFactory.Create(
                       baseName: "Controllers.LoginController",
                       location: "Zagros.Resources");
                  _htmlLocalizer = htmlLocalizerFactory.Create(
                      baseName: "Controllers.LoginController",
                      location: "Zagros.Resources");


       ولی خب مقدار مورد نظر داخل فایل منبع برگردانده نمی‌شود؟ 

      • #
        ‫۶ سال و ۳ ماه قبل، جمعه ۱۸ خرداد ۱۳۹۷، ساعت ۲۳:۴۱
        بعد از بررسی بیشتر متوجه شدم که فایل‌های منبع من به دلیلی که مشخص نیست اصلا به درستی ساخته نمی‌شوند.
        یک پروژه Class Library از نوع (2.1) .Net Core به وجود می‌آورم (در VS 2017). فایل‌های Resource که فاقد کالچر هستند بدون ایراد به پروژه اضافه می‌شود ( مثلا Resource1.resx) ولی زمانی که می‌خواهم یک فایل منبع همراه کالچر اضافه کنم (مثلا Resource1.fa.resx ) با خطای زیر مواجه می‌شوم:
        Custom tool ResXFileCodeGenerator failed to produce an output for input file 'Resource.fa.resx' but did not log a specific error.
        مقدار Custom Tool رو هم حذف می‌کنم، باز این خطا وجود دارد.
        بعد از آن هم طبق کامنت بالا، درست کار نمی‌کند.
      • #
        ‫۶ سال و ۳ ماه قبل، جمعه ۱۸ خرداد ۱۳۹۷، ساعت ۲۳:۵۸
        لازم به ذکر هست در پروژه نمونه ای هم که اینجا  ارائه شد، همین مشکل هست
        فکر می‌کنم مشکل از VS 2017 باشه 
      • #
        ‫۶ سال و ۳ ماه قبل، شنبه ۱۹ خرداد ۱۳۹۷، ساعت ۲۳:۵۷
        بعد از تلاش بسیار متوجه شدم، با اینکه پیام خطا ساخت generator در ویژوال استودیو نمایش داده می‌شود، ولی عملا به درستی ساخته می‌شود.

        مشکل از معرفی baseName  بود که باید به جای

                    _stringLocalizer = stringLocalizerFactory.Create(
                         baseName: "Controllers.LoginController",
                         location: "Zagros.ExternalResources");
                    _htmlLocalizer = htmlLocalizerFactory.Create(
                        baseName: "Controllers.LoginController",
                        location: "Zagros.ExternalResources");

        به دلیل اینکه در مسیر اصلی نیستیم، باید به این صورت معرفی شود (همراه مسیر فایل):

                    _stringLocalizer = stringLocalizerFactory.Create(
                         baseName: "Areas.Identity.Controllers.LoginController",
                         location: "Zagros.ExternalResources");
                    _htmlLocalizer = htmlLocalizerFactory.Create(
                        baseName: "Areas.Identity.Controllers.LoginController",
                        location: "Zagros.ExternalResources");


  • #
    ‫۶ سال و ۳ ماه قبل، جمعه ۱۸ خرداد ۱۳۹۷، ساعت ۲۰:۴۱
    ضمن تشکر از مطالب خوبتون
    آیا راهی هست که ساخت فایل ها Resource به صورت خودکار ساخته بشود؟
    مثلا زمانی که یک Controller ساخته می‌شود، همزمان فایل نظیر Resource آن هم ایجاد شود؟
    یا عملکری شبیه T4 ؟
  • #
    ‫۶ سال و ۳ ماه قبل، یکشنبه ۲۰ خرداد ۱۳۹۷، ساعت ۰۰:۲۵
    مشکلی که در استفاده از منابع در یک Library دیگر وجود دارد این است که، تغییرات در حالت Start without debugging تغییرات اعمال نمی‌شود و باید ناچار پروژه متوقف و دوباره اجرا شود.
  • #
    ‫۶ سال و ۲ ماه قبل، یکشنبه ۲۴ تیر ۱۳۹۷، ساعت ۱۶:۳۳
    با سلام؛ فایل‌ها رو در یک پروژه جدید اینطور تغییر دادم:
                _stringLocalizer = stringLocalizerFactory.Create(
                    baseName: "Project3.Core.MetadataCore.TagsService.en.resx" ,
                    location: "Project.Apps.Office");
    این خط رو به پروژه اضافه کردم : 
    string TNTNT= _stringLocalizer["Title"].Value;
    و آدرسی که _stringLocalizer  بهش اشاره می‌کنه اینه: 
    "Project.Apps.Office.Resources.Project.Core.MetadataCore.TagsService.en.resx"
    ولی مقداری که برای Title در نظر گرفتم رو بر نمی‌گردونه. 
    • #
      ‫۶ سال و ۲ ماه قبل، یکشنبه ۲۴ تیر ۱۳۹۷، ساعت ۱۷:۰۲
      - در نگارش‌های جدید ASP.NET Core، بسته‌ی بومی سازی آن، خطاهای یافت نشدن کلیدها را توسط ILogger آن لاگ می‌کند (و هیچ استثنایی را مشاهده نخواهید کرد). به همین جهت لاگ کردن را در برنامه‌ی خود فعال کنید و خروجی آن‌را جهت یافتن عباراتی مانند search for بررسی کنید (دقیقا عنوان می‌کند که کجاها را برای یافتن معادل‌ها بررسی کرده‌است و چیزی یافت نشده).
      - برای مثال اگر از قالب پیش‌فرض ASP.NET Core 2.1 استفاده می‌کنید، لاگ کردن در کنسول و همچنین دیباگ آن فعال است (بدون نیاز به تنظیم اضافه‌تری). در اینجا برنامه را در حالت dotnet watch run اجرا کنید (این دستور را در ریشه‌ی پروژه اجرا کنید) و سپس در کنسول جاری، عبارات لاگ شده را بررسی کنید. یا پنجره‌ی debug در ویژوال استودیو نیز همینکار را انجام می‌دهد.
      بررسی لاگ‌های برنامه جزو الزامات کار با برنامه‌های NET Core. است. در اینجا کمتر استثناءها را مشاهده می‌کنید و چون یک فریم ورک توکار Logging را به همراه دارد، همه‌جا از آن برای گزارش مشکلات، در پشت صحنه استفاده می‌شود.
      • #
        ‫۶ سال و ۲ ماه قبل، یکشنبه ۲۴ تیر ۱۳۹۷، ساعت ۱۹:۵۴
        سلام.
        این ارور در لاگ‌ها وجود داره :

          'dotnet.exe' (CoreCLR: clrhost): Loaded 'C:\Users\kazemi\source\repos\Project\bin\Debug\netcoreapp2.1\en\Project.Models.Project.resources.dll'. Module was built without symbols.
        • #
          ‫۶ سال و ۲ ماه قبل، یکشنبه ۲۴ تیر ۱۳۹۷، ساعت ۲۰:۴۳
          مشکل برطرف شد. اشکال از این بود که به جای: 
          baseName: "Project3.Core.MetadataCore.TagsService.en.resx" ,
          می‌بایست به شکل زیر تعریف کرد:
          baseName: "Project3.Core.MetadataCore.TagsService",
          بنده به اشتباه فرمت فایل را که .en.resx می‌باشد، در تعریف آدرس قرار می‌دادم.
  • #
    ‫۶ سال و ۱ ماه قبل، شنبه ۱۳ مرداد ۱۳۹۷، ساعت ۱۵:۵۶
    با سلام؛ حالا قصد دارم بومی سازی را در بخش فایل‌های js انجام دهم . یعنی پیغام‌های موجود در فایل .js خودم رو هم بومی سازی کنم چون تمامی پیغام‌ها عملا یکسان هستند. آیا راهی هست تا بشود از این شیوه در فایل‌ها js استفاده کرد؟؟
    • #
      ‫۶ سال و ۱ ماه قبل، شنبه ۱۳ مرداد ۱۳۹۷، ساعت ۱۶:۱۰
      راه حل توکاری ندارد؛ چون این فناوری سمت سرور است. حتی Razor هم یک فناوری سمت سرور است. بنابراین یا باید وقت بگذارید این روش‌های قدیمی را به جدید ترجمه کنید:
      و یا یک تامین کننده‌ی منابع عمومی اسکریپت‌ها را تعریف کنید:
      <script type="text/javascript">
      if (!window.resourceProvider) {
          window.resourceProvider = {
              message1: '',
              message2: ''
          };
      }
      </script>
      سپس در View باید این کلیدها را بر اساس سرویس سمت سرور بومی سازی، مقدار دهی کنید:
      @using Microsoft.AspNetCore.Mvc.Localization
      @inject IViewLocalizer Localizer
      
      @section Scripts
      {
        <script type="text/javascript">
          resourceProvider.message1 = '@Localizer["About Title"]';
        </script> 
      }
      و در آخر به صورت زیر در هر قسمتی قابل استفاده خواهند بود:
      <script type="text/javascript">
         alert(resourceProvider.message1); 
      </script>
  • #
    ‫۶ سال و ۱ ماه قبل، شنبه ۱۳ مرداد ۱۳۹۷، ساعت ۱۷:۴۴
    با سلام؛ بنده یک فایل بومی سازی مشترک دارم که در همه ویو‌ها ازش استفاده می‌کنم و پیغام‌های سیستم رو درش ذخیره کردم. حالا می‌خوام این رو در ViewImports قراربدم تا لازم نباشه اول هر view دوباره تعریفش کنم. آیا این کار امکان پذیر هست؟ اینو بنده در ViewImports گذاشتم ولی به خطا بر می‌خوره:
    @inject IHtmlLocalizerFactory HtmlLocalizerFactory
    
    @{ var _sharedLocalizer = HtmlLocalizerFactory.Create(
        baseName: "Shared.SharedMessages" /*مشخصات کنترلر جاری*/,
        location: "Project.Resources" /*نام اسمبلی ثالث*/);
    }
  • #
    ‫۵ سال و ۴ ماه قبل، یکشنبه ۱ اردیبهشت ۱۳۹۸، ساعت ۲۱:۳۰
    توی کلاس Start چنین تنظیماتی رو دارم:
            public class LanguageRouteConstraint : IRouteConstraint
        {
            public bool Match(HttpContext httpContext, IRouter route, string routeKey, RouteValueDictionary values, RouteDirection routeDirection)
            {
                if (!values.ContainsKey("lang"))
                {
                    return false;
                }
                var lang = values["lang"].ToString();
                var result = lang == "fa" || lang == "en";
                return result;
            }
        }  
    public void ConfigureServices(IServiceCollection services)
            {
                services.AddLocalization(o => o.ResourcesPath = "Resources");
                services.Configure<RouteOptions>(options =>
                {
                    options.ConstraintMap.Add("lang", typeof(LanguageRouteConstraint));
                });
    
                services.AddMvc()
                    .SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
                    .AddViewLocalization(LanguageViewLocationExpanderFormat.Suffix)
                    .AddDataAnnotationsLocalization();

            public void Configure(IApplicationBuilder app, IHostingEnvironment env)
            {
                
                app.UseRequestLocalization(new RequestLocalizationOptions
                {
                    DefaultRequestCulture = new RequestCulture(new CultureInfo("fa-IR")),
                    SupportedCultures = new[]
                    {
                        new CultureInfo("en-US"),
                        new CultureInfo("fa-IR"),
                    },
                    SupportedUICultures = new[]
                    {
                        new CultureInfo("en-US"),
                        new CultureInfo("fa-IR"),
                    },
                    RequestCultureProviders = new List<IRequestCultureProvider>()
                    {
                        new RouteDataRequestCultureProvider()
                        {
                            UIRouteDataStringKey = "lang",
                            RouteDataStringKey = "lang"
                        }
                    }
                });
    
                app.UseMvc(routes =>
                {
                    routes.MapRoute(
                        name: "LocalizedAreas",
                        template: "{lang:lang}/{area:exists}/{controller=Home}/{action=Index}/{id?}");
    
                    routes.MapRoute(
                        name: "LocalizedDefault",
                        template: "{lang:lang}/{controller=Home}/{action=Index}/{id?}"
                    );
                    routes.MapRoute(
                        name: "default",
                        template: "{*catchall}",
                        defaults: new { controller = "Home", action = "RedirectToDefaultLanguage" });
                });
    با این تنظیمات، زبان برنامه فقط با تغییر DefaultRequestCulture  تغییر میکنه، توی تنظیمات بالا، زبان سایت فقط فارسی هست، حتی اگه lang به en تغییر کنه. آیا تنظیمات دیگری باید صورت بگیره؟
  • #
    ‫۵ سال و ۱ ماه قبل، یکشنبه ۳۰ تیر ۱۳۹۸، ساعت ۱۵:۴۷
    سلام
    من از تنظیم کوکی برای تغییر Culture جاری کاربر استفاده کردم. در مرورگر pc کار میکنه ولی وقتی با مرورگر موبایل تغییر زبان رو انجام میدم کار نمیکنه
  • #
    ‫۵ سال و ۱ ماه قبل، دوشنبه ۳۱ تیر ۱۳۹۸، ساعت ۲۰:۳۹
    در خصوص مطلبی که عنوان کردید :
    زمانیکه میان افزار RequestLocalization فعال می‌شود، سه تامین کننده‌ی پیش فرض جهت مشخص ساختن فرهنگ ترد جاری بکار گرفته خواهند شد:
    الف) از طریق کوئری استرینگ
    ب) از طریق نام کوکی
    ج) از طریق هدر مخصوص Accept-Language 

    تا جایی که مطالعه کردم در سئوی سایت تاثیر مطلوبی ندارد چون با همان آدرس محتوی به صورت چندین زبان برای گوگل ایندکس می‌شود و رتبه سئو را پایین می‌آورد.
    حال اگر بخواهیم آدرس دهی مطابق با سئو را در اینجا داشته باشیم که قوانین سئو را هم رعایت کنیم و مثلا آدرسهایی به شکل
    https://mySite.com/fa/home/index
    داشته باشیم باید به چه نحو عمل کرد که بهترین بازدهی رو داشته باشیم؟
    • #
      ‫۵ سال و ۱ ماه قبل، دوشنبه ۳۱ تیر ۱۳۹۸، ساعت ۲۲:۴۹
      - یک یا همان آدرس نیست. مثالی در مطلب مانند http://localhost:5000/?culture=es-MX&ui-culture=es-MX وجود دارد و با تغییر این کوئری استرینگ‌ها، آدرس‌ها یکی نخواهند بود + گوگل توانایی پردازش جاوا اسکریپت و خیلی از مسایل دیگر را هم دارد و یک صفحه و یک درخواست را صرفا بر اساس آدرس آن شناسایی نمی‌کند.
      - در مطلب از تنظیمات RequestCultureProviders & UseRequestLocalization استفاده شده. با کمک این تنظیمات، چنین مسیریابی {culture} داری، قابل تعریف است:
      [Route("{culture}/[controller]")]
      public class ValuesController : Controller
      {
          [Route("ShowMeTheCulture")]
          public string GetCulture()
          {
              return $"CurrentCulture:{CultureInfo.CurrentCulture.Name}, CurrentUICulture:{CultureInfo.CurrentUICulture.Name}";
          }
      }
      که به آدرس‌هایی مانند fa/controller/action به صورت خودکار تنظیم می‌شود.
      • #
        ‫۵ سال و ۱ ماه قبل، چهارشنبه ۹ مرداد ۱۳۹۸، ساعت ۱۴:۲۶
        در این حالت که نام culture در ابتدای URL تنظیم می‌شود آدرس دهی‌ها در خصوصیت Href لینک‌ها در مدل MVC خالی تنظیم می‌شود که از طریق تکه کد زیر باید آدرس دهی‌ها را هم تنظیم کرد تا به مشکل نخورد ^

        services.AddMvc(options => options.EnableEndpointRouting = false)
        .SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

        یه نمونه مثال هم با توضیحات خوب شما و دوستان آماده کردم که آدرس دهی‌ها به صورت https://mySite.com/fa/home/index می‌باشد و نام culture در ابتدای آدرس‌دهی‌ها در آن رعایت شده است .
        از طریق نوار ابزار قرمز رنگ پایین می‌توان نام culture و زبان صفحات را تنظیم کرد به طوری که راست به چپ بودن یا چپ به راست بودن را هم می‌توان در آن دید. همینطور استفاده از Resources به صورت دوحالت عمومی (Common.resx) یا شخصی سازی شده (Views.Home.About.fa.resx) بر حسب نام View‌ها در آن رعایت شده است که بر حسب نیاز می‌توان هرکدام از این دو مورد را استفاده یا توسعه داد.

        امیدوارم برای دوستان هم مفید باشه
        WebApplicationLocalization.rar

        • #
          ‫۵ سال و ۱ ماه قبل، دوشنبه ۱۴ مرداد ۱۳۹۸، ساعت ۱۵:۵۶
          در این حالت آدرس دهی در مثال وقتی می‌خواهیم از سیستم اهراز هویت در متد Configure  استفاده کنیم
          app.UseAuthentication();
          بعد از اینکه کاربر لاگین کرد و اهراز هویت انجام شد گویا مشکلی ^ برای پر کردن Claim‌ها و null بود User.Identity.Name پیش میاد
          البته راه حلی که که در ^ گفته شده در متد   ConfigureOptions قابل فعال سازی نبود به این دلیل که در آرایه RequestCultureProviders یک شی از کلاس RouteDataRequestCultureProvider برای آدرس دهی‌ها اضافه شده و با شی از کلاس CustomRequestCultureProvider که در راه حل گفته شده همخوانی نداره

           public static void ConfigureOptions(RequestLocalizationOptions options)
                  {
                      var supportedCultures = new List<CultureInfo>
                                          {  new CultureInfo("fa"),
                                              new CultureInfo("en"),
                                              new CultureInfo("ar")
                                          };
          
                      options.DefaultRequestCulture = new RequestCulture(culture: "fa", uiCulture: "fa");
                      options.SupportedCultures = supportedCultures;
                      options.SupportedUICultures = supportedCultures;
                      options.RequestCultureProviders = new[] {
                          new RouteDataRequestCultureProvider()
                          {
                              Options = options,
                              RouteDataStringKey = "lang",
                              UIRouteDataStringKey = "lang"
                          }
                      }; 
                  }
          راه حلی برای رفع این مشکل وجود داره؟
          • #
            ‫۵ سال و ۱ ماه قبل، دوشنبه ۱۴ مرداد ۱۳۹۸، ساعت ۱۶:۰۵
            قرار دادن سطر ()app.UseAuthentication را پیش از تنظیمات Localization، بررسی کنید (پیش و پس این‌ها مهم است).
            • #
              ‫۵ سال و ۱ ماه قبل، دوشنبه ۱۴ مرداد ۱۳۹۸، ساعت ۱۶:۱۸
              هم پیش و هم پس از سه خط زیر در متد Configure تست گرفتم
              var options = app.ApplicationServices.GetService<IOptions<RequestLocalizationOptions>>();
              LocalizationPipeline.ConfigureOptions(options.Value);
              app.UseRequestLocalization(options.Value);
              متاسفانه بعد از اهراز هویت که توسط متد PasswordSignInAsync انجام می‌شود و با اینکه resultIdentity هم مقدار آن Successed می‌باشد.
              var resultIdentity = await _signInManager.PasswordSignInAsync(username, password, rememberMe, lockoutOnFailure: true);
              ولی باز هم با مشکل مواجه است اطلاعات User بعد از لاگین به صورت شکل زیر است در صورتی که اگر از متد UseRequestLocalization در کانفیگ استفاده نشود و موارد مربوط به Localization فعال نباشد کاربر به درستی لاگین می‌کند و اطلاعات Claim‌ها و بقیه موارد از بانک اطلاعاتی پر می‌شود.
              • #
                ‫۵ سال قبل، دوشنبه ۲۸ مرداد ۱۳۹۸، ساعت ۱۹:۱۵
                مشکل برطرف شد. تکه کد زیر را در انتها و بعد از تنظیمات Localization اضافه کردم: 
                app.UseAuthentication();
                همینطور در متد ConfigureServices تنظیمات AddIdentityOptions را داخل همین متد و قبل از متد AddMvc نوشتم
  • #
    ‫۵ سال قبل، دوشنبه ۲۸ مرداد ۱۳۹۸، ساعت ۱۹:۲۹
    در این حالت بومی سازی وقتی از کتابخانه DNTBreadCrumb.Core برای نمایش bread crumb و مشخص کردن عمق صفحه‌ی جاری می‌خواهیم استفاده کنیم چطوری می‌توانیم نام کنترلرها که در عمق قرار گرفتند رو از منابع (Resources) مربوط به همان زبان نمایش بدیم؟

    البته نام اصلی سایت و نام Action از طریق تکه کد زیر از منابع لود میشه و درست نمایش داده میشه 
    <breadcrumb asp-homepage-title="@localizer["MyApp"]"
                asp-homepage-url="@Url.Action("Index", "Home", values: new { area = "" })"
                asp-bootstrap-version="V3"
                asp-homepage-glyphicon="glyphicon glyphicon-home"></breadcrumb>
    @localizer["About"]
    ولی نامی که بالای کنترلر‌ها قرار میگیره رو نمیشه از Resources لود کرد (منظورم وقتی هست که مثلا تکه کد زیر بالای نام کنترلر قرار بگیر)
    [BreadCrumb(Title = "Home", UseDefaultRouteUrl = true, Order = 0)]
    public class HomeController : Controller
    {
    }  
  • #
    ‫۵ سال قبل، یکشنبه ۱۷ شهریور ۱۳۹۸، ساعت ۰۳:۴۰
    آیا راهی وجود داره برای RequestLocalizationOptions  قسمت SupportedCultures و SupportedUICultures به صورت runtime و براساس جدول زبان اضافه یا کم شود
     var supportedCultures = new List<CultureInfo>
    {
    new CultureInfo("fa"),
    new CultureInfo("en"),
    new CultureInfo("ar")
    };
    services.Configure<RequestLocalizationOptions>(
    options =>
    {
      options.DefaultRequestCulture = new RequestCulture("fa");
      options.SupportedCultures = supportedCultures;
      options.SupportedUICultures = supportedCultures;
      options.RequestCultureProviders = new List<IRequestCultureProvider> { new CookieRequestCultureProvider() };
    });

  • #
    ‫۴ سال و ۱ ماه قبل، پنجشنبه ۲ مرداد ۱۳۹۹، ساعت ۱۹:۱۹
    جهت اطلاع: مثال این قسمت به همراه نکات مطرح شده‌ی در نظرات آن، مخصوص نگارش 3x:
  • #
    ‫۳ سال و ۱ ماه قبل، پنجشنبه ۲۴ تیر ۱۴۰۰، ساعت ۰۳:۳۶
    نکته تکمیلی
    از نسخه 3.1 به بعد اگر فایل‌های منبع SharedResource.xx.resx و کلاس SharedResouce کنار هم باشند فضا‌های نام دچار Error میشوند. برای حل این مشکل کلاس SharedResource را در ریشه پروژه ساخته و فایل‌های منبع SharedResouce.xx.resx را داخل پوشه Resources.
    ماخذ : ^  ^