I want to get the name of a translated string by its value from RESX file in .NET Core 3.1.
I Know the below code is answer but how do I access to RESX file correctly?
System.Resources.ResourceManager rm =
new System.Resources.ResourceManager("Resources.Views.Shared._Layout.de.resx", this.GetType().Assembly);
return await Task.Run(() => rm.GetResourceSet(System.Threading.Thread.CurrentThread.CurrentCulture, true, true)
.OfType<DictionaryEntry>()
.FirstOrDefault(e => e.Value.ToString() == value).Key.ToString());
My resource files are scaffolded as below.
And the error message is as below.
What is the right addressing to the RESX files in this case?
If you will look at the documentation, you will see that ASP.NET Core has introduced IStringLocalizer and IStringLocalizer.
Under the cover, IStringLocalizer use ResourceManager and ResourceReader. Basic usage from the documentation:
public class StringLocalizerController : Controller
{
private readonly IStringLocalizer localizer;
public StringLocalizerController(IStringLocalizerFactory factory)
{
var assemblyName = new AssemblyName(this.GetType().Assembly.FullName);
localizer = factory.Create("SharedResource", assemblyName.Name);
}
public IActionResult SharedResource()
{
return Content(localizer["AAA"]);
}
}
Start up:
services.AddLocalization(options => options.ResourcesPath = "Resources/View/Shared");
Result:
Related
In a WEB API in .NET 6.0, I'd like access resources based on a language. I do this :
In Startup.cs :
services.AddLocalization(options => options.ResourcesPath = "Resources");
services.AddControllersWithViews()
.AddViewLocalization
(LanguageViewLocationExpanderFormat.SubFolder)
.AddDataAnnotationsLocalization();
services.Configure<RequestLocalizationOptions>(options => {
var supportedCultures = new[] { "fr-BE", "nl-BE" };
options.SetDefaultCulture(supportedCultures[0])
.AddSupportedCultures(supportedCultures)
.AddSupportedUICultures(supportedCultures);
});
The resource files are in Resources\Controllers\ with 2 files MyController.fr-BE.resx and MyController.nl-BE.resx
In the controller :
private readonly IStringLocalizer<MyController> _localizer;
public MyController(IStringLocalizer<MyController> localizer)
{
_localizer = localizer;
}
In one of the entry point I do this :
public ActionResult Get()
{
var article = _localizer["Article"];
return Ok();
}
The article variable has these values :
Name = "Article"
ResourceNotFound = true
article.SearchedLocation = API.Resources.Controllers.MyController
Value = "Article"
In the resource file, I have for "Article" in MyController.fr-BE : "Article FR" and in MyController.nl-BE : "Article NL"
The request call from postman has in the header :
Accept-Language = fr-BE
Am I missed something ?
Thanks,
In your startup you misconfigured LanguageViewLocaionExpanderFormat to SubFolder. It should be Suffix, see the docs.
LanguageViewLocaionExpanderFormat has two options:
SubFolder: Locale is a subfolder under which the view exists.
Suffix: Locale is part of the view name as a suffix.
The sub folder would have a file structure: Resources/controllers/fr-BE/filename.resx
Whilst The suffix would have the following structure: Resources/controllers/filename.fr-BE.resx. Where filename should be the name of the controller.
services.AddControllersWithViews()
.AddViewLocalization
(LanguageViewLocationExpanderFormat.SubFolder)
.AddDataAnnotationsLocalization();
Your resources should look something like this:
I'm trying to move my localization resource files to a separate project, since in my solution I have my logic split in different projects, and I'd like to share the same resource files on all of them without adding references between each other. I've got familiar with the new .NET Core way of localization and I tested it out in my MVC project and everything worked, however when I created a new .NET Core class library for the resources I couldn't manage to use them. I've read pretty much everything and couldn't manage to find a solution.
Here's my Resources project hierarchy:
I've tried moving the "resx" files to Resources folder, moving them next to Resources.cs without any folders, moving Resources.cs to the Resources folder, nothing works.
Here's my code:
Resources.cs:
using Microsoft.Extensions.Localization;
namespace Resources
{
public interface IResources
{
}
public class Resources : IResources
{
private readonly IStringLocalizer _localizer;
public Resources(IStringLocalizer<Resources> localizer)
{
_localizer = localizer;
}
public string this[string index]
{
get
{
return _localizer[index];
}
}
}
}
Startup.cs:
Configureservices method:
services.AddLocalization(options => { options.ResourcesPath = "Resources"; });
services
.AddMvc()
.AddDataAnnotationsLocalization(o =>
{
o.DataAnnotationLocalizerProvider = (type, factory) =>
{
return factory.Create(typeof(Resources.SharedResources));
};
})
.AddViewLocalization(LanguageViewLocationExpanderFormat.Suffix);
services.Configure<RequestLocalizationOptions>(options =>
{
CultureInfo[] supportedCultures = new[]
{
new CultureInfo("en"),
new CultureInfo("bg")
};
options.DefaultRequestCulture = new RequestCulture("en");
options.SupportedCultures = supportedCultures;
options.SupportedUICultures = supportedCultures;
});
Configure method:
app.UseRequestLocalization();
Index.cshtml where I'm trying to load my localization resources:
#using Microsoft.AspNetCore.Mvc.Localization;
#inject IHtmlLocalizer<Resources.Resources> Localizer
#{
ViewData["Title"] = "Home Page";
}
<div class="text-center">
<h1 class="display-4">#Localizer["Welcome"]</h1>
<p>#Localizer["LearnAbout"] #Localizer["Build"].</p>
</div>
Instead of getting the resource values I just get the name of the resource as written inside the #Localizer.
Is there any way to have the resources separated in a different project in .NET Core 3.1? I've seen a lot of answers about older versions, but they don't work in 3.1.
Any help will be appreciated!
Edit: After changing the names of the resource files to SharedResources I can use them in the views, but I can't understand how to use them in ViewModels.
Whenever I call [Display(ResourceType = typeof(SharedResources), Name = "lblFromDate")] on my attribute I get an error:
InvalidOperationException: Cannot retrieve property 'Name' because
localization failed. Type 'Resources.SharedResources' is not public or
does not contain a public static string property with the name
'lblFromDate'.
I've made the resource files public, but for some reason they don't generate .cs files. Something that I've noticed is that I actually can't create resource files in .Net 3.1 Class Libraries, even searching for a resource file doesn't show anything up.
I am new to asp.net core localization, and trying to use resource files. There are multiple ways of doing it, so I started with IStringLocalizer and IHtmlLocalizer.
We can specify the type while injecting the Localizer into the view, and most of the tutorial recommend to create an Empty SharedResource class file with root namespace.
I tried to find the reason behind it but didn't find, Could anyone please help me out about the reason of having the empty SharedResource class?
#inject IHtmlLocalizer<SharedResources> Localizer
namespace Root.Namespace
{
public class SharedResources
{
}
}
empty SharedResource class is use to group your resource file in Visual studio like this
Also it need for IStringLocalizeFactory
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
.AddDataAnnotationsLocalization(options =>
{
options.DataAnnotationLocalizerProvider = (type, factory) =>
{
var assemblyName = new AssemblyName(typeof(SharedResource).GetTypeInfo().Assembly.FullName);
return factory.Create("SharedResource", assemblyName.Name);
};
});
You can read my blog and source code here to understand it better
I've a modular web application and I need to load some libraries on runtime, those libraries will contain Controllers.
Each controller must have a single Area and it's common to all controllers inside that library.
I've used ASP.NET Core's application parts to load this assembly during runtime.
services.AddMvcCore(setup =>
{
setup.Filters.Add(new AuthorizeFilter());
// Get all global filters
foreach (var filter in GetInterfacesFromAssembly<IGlobalAsyncFilter>())
setup.Filters.Add(new AsyncActionFilterProxy(filter, container));
})
.SetCompatibilityVersion(CompatibilityVersion.Latest)
.AddFormatterMappings()
.AddJsonFormatters()
.AddCors()
.AddAuthorization(o =>
{
o.DefaultPolicy = new AuthorizationPolicyBuilder(JwtBearerDefaults.AuthenticationScheme)
.RequireAuthenticatedUser()
.Build();
})
.AddApplicationPart(GetModuleAssemblies());
GetModuleAssemblies gets all the possible libraries called in runtime that may contain Controllers
/// <summary>
/// Get any DLL that contains "ModularPortal.Modules.*.dll"
/// </summary>
private static IEnumerable<Assembly> GetModuleAssemblies()
{
string location = Assembly.GetEntryAssembly().Location;
string path = Path.GetDirectoryName(location);
DirectoryInfo directory = new DirectoryInfo(path);
FileInfo[] files = directory.GetFiles("ModularPortal.Module*.dll");
foreach (FileInfo file in files)
yield return Assembly.Load(file.Name.Replace(".dll", ""));
}
Using this, the following controller is located in /api/example1/test:
[AllowAnonymous]
public class Example1Controller : ControllerBase
{
readonly IMembership membership;
public Example1Controller(
IMembership membership
)
{
this.membership = membership;
}
[HttpGet]
public IActionResult Test()
{
return Ok("t1");
}
}
The problem is that by adding ApplicationPart I'll need to use the AreaAttribute to identity that the controller belongs to /Area.
I want a generic "Module Configurator" that any controller inside that library WILL have a common area.
I started by creating an interface to identity the module:
public interface IModule
{
string DisplayName { get; }
string RouteName { get; }
void Configure(ModuleConfiguration config);
}
Then inside the library, I used it:
public class Module : IModule
{
public string DisplayName => "Example Module";
public string RouteName => "Ex";
public void Configure(ModuleConfiguration config)
{
throw new NotImplementedException();
}
}
I want to use this code to configure everything about that module, including Area and controller registration.
In the example above, after registering an Area the path should be /api/ex1/example1/test.
This will only be possible if I, somehow, changed the AddApplicationPart to register it myself.
Any idea?
Seem like you are trying build modular project like I was. Here is the reference link for you.
Hope it help, if you have any query please let me know. I would love to support.
Kinda confused here, super simple hello-world example of localization in ASP.Net Core 2.0. My About page is set up to render two localized strings:
From the view (using IViewLocalizer)
From code (using IStringLocalizer<HomeController> via the controller)
The code in the controller refuses to get the loc string appropriately. This is not complicated, what obvious things am I missing?
About.cshtml
#using Microsoft.AspNetCore.Mvc.Localization
#inject IViewLocalizer Localizer
#{
ViewData["Title"] = "About";
}
<h2>#ViewData["Title"]</h2>
<h3>#ViewData["Message"]</h3>
<p>#Localizer["Use this area to provide additional information."]</p>
^ Note the two strings: "Message" will be localized from code using IStringLocalizer (see HomeController below), and the #Localizer will use the IViewLocalizer class.
HomeController.cs
public class HomeController : Controller
{
private readonly IStringLocalizer _localizer;
public HomeController(IStringLocalizer<HomeController> localizer)
{
_localizer = localizer;
}
public IActionResult Index()
{
return View();
}
public IActionResult About()
{
ViewData["Message"] = _localizer["Your application description page."];
return View();
}
}
Startup.cs (relevant parts)
public void ConfigureServices(IServiceCollection services)
{
services.AddLocalization(options => options.ResourcesPath = "Resources");
services.AddMvc()
.AddViewLocalization(LanguageViewLocationExpanderFormat.Suffix)
.AddDataAnnotationsLocalization();
services.Configure<RequestLocalizationOptions>(options =>
{
var supportedCultures = new[]
{
new CultureInfo("en-US"),
new CultureInfo("fr-CH"),
};
options.DefaultRequestCulture = new RequestCulture(culture: "en-US", uiCulture: "en-US");
options.SupportedCultures = supportedCultures;
options.SupportedUICultures = supportedCultures;
});
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
var locOptions = app.ApplicationServices.GetService<IOptions<RequestLocalizationOptions>>();
app.UseRequestLocalization(locOptions.Value);
if (env.IsDevelopment())
{
app.UseBrowserLink();
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
Resources:
Views.Home.About.fr-CH.resx
^ with two values in it:
"Use this area to provide additional information." = "Use this
area... success for fr-CH!"
"Your application description page." =
"Your app descript... success for fr-CH!"
My Results:
localhost:56073/Home/About
^ This renders the strings as expected in en-US (default finds nothing, uses the strings actually hard coded)
localhost:56073/Home/About?culture=fr-CH
^ This renders ONLY the 2nd string: "Use this area... success for fr-CH!", which clearly means all the code wired up is working and finding the fr-CH.resx as expected.
BUT, the first string (set in code as ViewData["Message"]) does NOT get the fr-CH version! It's like the IStringLocalizer<HomeController> failed to realize there was a lang specified, or failed to find the fr-CH.resx that is clearly available.
Why???
Also BTW, I tried using the ShareResource example too (see link below), and passed in the factory to the HomeController ctor as IStringLocalizerFactory factory, also with no love, still not getting the fr-CH resource. Sigh.
Other notes:
Using this as my primary reference:
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/localization
Using VS 2017, latest updates, with ASP.Net Core 2.0
The issue is that ASP .NET Core creates wrong RESX namespace for localization using IStringLocalizer. If you have in the code
services.AddLocalization(options => options.ResourcesPath = "Resources");
then the instance of injected service IStringLocalizer has in the namespace twice "Resources", namespace looking "Resources.Resources" . This is the root cause why the RESX cannot be found.
You are using IStringLocalizer<HomeController> as localizer in the controller to find the localized string. The localizer will look in Resources folder to find YouControllerNameSpace.HomeController resource file and since it doesn't find it, it will return the original key which you passed to the localizer.
To solve the problem, you can use either of following options:
Inject IStringLocalizer<T>
Inject IStringLocalizerFactory
For more information about resource file names, take a look at Resource file naming section in documentations.
Inject IStringLocalizer<T>
Using this option, you should have a resource file with the same name as full name of T, in your case, the controller code should be the same as it is:
IStringLocalizer _localizer;
public HomeController(IStringLocalizer<HomeController> localizer)
{
_localizer = localizer;
}
For the resource file:
Make sure you have YouControllerNameSpace.HomeController resource file. (YouControllerNameSpace is just a placeholder, use your controller namespace.)
Make sure you have the specified string in resource file.
Make sure you have resource files for different cultures.
Inject IStringLocalizerFactory
Using this option you can use any file as resource file. For example if you want to read resources from Views.Home.About resource file, you should change the controller code to this:
IStringLocalizer _localizer;
public HomeController(IStringLocalizerFactory factory)
{
_localizer = factory.Create("Views.Home.About",
System.Reflection.Assembly.GetExecutingAssembly().GetName().Name);
}
For the resource file:
Make sure you have Views.Home.About resource file.
Make sure you have the specified string in resource file.
Make sure you have resource files for different cultures.
Try the technique described by tmg in this answer.
Specifically, try adding the lines
options.RequestCultureProviders = new List<IRequestCultureProvider>
{
new QueryStringRequestCultureProvider(),
new CookieRequestCultureProvider()
};
to your ConfigureServices() function