I am new at working with resource files and I haven't quite got how it works yet. Now I need to have my application's text available in English and in Chinese. I will receive a get parameter (e.g. lang) and from there I will need to decide whether to use Language.zh.resx or my default Language.resx - That's what I understood from articles that I have been reading. Now I have my View Title for example:
#{
ViewBag.Title = MyApplication.App_GlobalResources.Language.MyPage_Title;
}
I can't figure out where to check the parameter lang and apply it. I saw articles where people say I should create an action filter and they add stuff to cookies and they were confusing. In my case it might not be necessary as it just has one request, there is no requirement for preserving the state as once the page is loaded that's it.
If someone could also give some brief explanation of how resources work that would be nice, thanks!
There are a couple of articles that discuss Globalization and MVC using both session and URL variables. I am linking both because the session one covers Views a little more in deoth, but the logic for views should stay the same regardless if you are routing (/en-us/Controller) or using a session.
Session based Globalization
Routing Based Globalization
lets say you have a string lang in your controller, in the controller you should change the current culture, so the only thing you need to do is is something like this
var culture = new CultureInfo(lang);
System.Threading.Thread.CurrentThread.CurrentCulture = culture;
System.Threading.Thread.CurrentThread.CurrentUICulture = culture;
i suggests you to do this steps via some attribute, and then apply it on controllers.
Related
I have an application which I must port to asp.net 6. I try to implement the authentication logic with scaffolded default identity UI pages.
The application uses URLs which start with a path component which holds the user’s language, then followed by the concrete path components. Something like:
/{language}/product/{product}
Now I try to establish this url schema also with the asp.net identity default UI pages. For example, the login page url should be look something like this:
/en/login
/fr/login
/it/login
However, up to now I had only little success in doing so. In changing the #page directive in the scaffolded pages I was able to introduce the {language} path component. However, how do I now tell the cookie middleware to integrate the current {language} placeholder into the redirect? Something like this:
builder.Services.ConfigureApplicationCookie(options =>
{
options.LoginPath = "/{language}/Login";
});
Is this feasible in some way or another or is there even a more solid way to accomplish the goal?
Update
Up to now I came up with a solution and posted it as an answer, since it works. However if anybody knows a more sophisticated approach, please post it, I feel that the way I did this is really ugly and I cannot believe that there is no cleaner way to accomplish this, since also Microsoft uses the Url schema I try to implement in their websites.
One possibility I've found is to use the CookieAuthenticationOptions.Events-instance. This seems a feasible way, however it seems to me extremely brutish and one has to register to every event which is concerned and every scaffolded page has to be changed (redirects etcetera).
However, as long as no other solution is provided, this may help someone:
builder.Services.ConfigureApplicationCookie(options =>
{
options.LoginPath = "/language/Login";
options.Events.OnRedirectLogin=>(context){
// here would stand some Ajax-checks ...
var myLanguage= ... // detection of the language from the query string
context.Response.Redirect(context.RedirectUri.Replace("/language/", myLanguage));
}
});
The above code is best refactored in a new class which derives from CookieAutenticationEvents, where then proper handling can be done for each event. So the only assigment in the Program.cs file is the assignment of the custom authentication events class. The original class seems a bit quirky and depending on the purpose of the derived class, it is either better to initially assign custom events while constructing the instance or to override the methods which raise the assigned event handlers.
Within the pages, the language can be declared via the page directive:
#page "/{language}/login"
#page "/{language}/loginWith2fa"
etcetera
The code behind then will be changed, for example the signature of the get and set method will be extended with the language-parameter.
public async Task<IActionResult> OnGetAsync(string language,bool rememberMe, string returnUrl = null) {
and additionally, any url references in the code behinds must be changed. Since this way is so ugly, I tried to completely abstain from the default identity ui. In this post I ask for a way to do so.
I am trying to localize a hosted service in response to a runtime condition which is fed in a variable lang, which represents a 2-letter ISO code (such as 'en', 'es', ...).
I set the localization service in my Startup.cs like this:
services.AddLocalization(options => { options.ResourcesPath = "xresx"; });
In my controller I have the following code:
Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo(lang);
I know this works, because when I pass in lang='es' the following:
var check = Thread.CurrentThread.CurrentCulture.TwoLetterISOLanguageName;
returns the correct value check = 'es'.
But then the next statement:
var msg = Resources.TestMsg
picks up my the value from my English resource file Resource.resx instead of Resource.es.resx.
What am I doing wrong, and how can I make it work?
Thanks!
OK, so what ultimately worked for me was following exactly the steps in this guide: https://joonasw.net/view/aspnet-core-localization-deep-dive
This link is the only source I've found that worked for me, and it was better than Microsoft's own documentation (which omits potential pitfalls like not naming your Resource files a very certain way).
Let me summarize some points:
One needs to inject IStringLocalizer into their controller, eg:
IStringLocalizer<MyController> _localizer;
Then inside the controller you can localize your strings, eg:
_localizer["STRINGKEY"]
where STRINGKEY is a key from your resource file.
Make sure to name your resource files properly. This is very important and is not documented by Microsoft as far as I know! Cost me a lot of time until I stumbled over the web link I've referenced above.
I was naming my files like this:
Resource.resx, Resource.es.resx etc
and the localized wasn't finding the values, instead just returning the key itself.
Eg, _localizer["STRINGKEY]" would return "STRINGKEY" rather than the corresponding value in the resource file.
So you must name your files instead using your Controller's name, like this:
Controllers.MyController.resx, Controllers.MyController.es.resx
These are the main points to remember. Sadly, Microsoft documentation glosses over a lot of this stuff.
I am developing an ASP.NET Core 3.1 MVC website that uses domain names to determine the culture for localization.
For example, all requests going to the domain example.com will have the en-US culture, whereas all requests going to the domain example.org will have the de-DE culture.
I modified my previously unlocalized website to use localization in views, etc.. I have utilized an own RequestCultureProvider implementation to set the culture based on the domain name. The result was the following:
https://example.com/Products/My-Application/Help is using en-US as the culture and have English texts.
https://example.org/Products/My-Application/Help is using de-DE as the culture and have German texts.
Note: Products is the controller, My-Application the name of the application (URL slug from the database), and Help is the action within the Products controller. The URL structure is the actual structure I am using for my website.
However, I also want to translate the URL while relying on English only names in the actual source code (controller classes and action functions).
For example, here is a dummy controller with a single action (breaking with the example URL above due to simplification).
[Controller]
[Route("/Products")]
public class ProductController : Controller
{
[ActionName("EnglishName")]
public IActionResult EnglishName()
{
return View();
}
}
For this simplified example:
URL examples:
English URL: https://example.com/Products/EnglishName
German URL: https://example.org/Produkte/GermanName
In source code (for clarity reasons), the value of Route of the controller shall not be changed.
In source code (for clarity reasons), the value of ActionName of the EnglishName action function shall not be changed.
In source code (for clarity reasons), the function name of the action EnglishName shall not be changed.
In routing context, the value of Route shall be "/Produkte" in the de-DE culture.
In routing context, the value of ActionName of the EnglishName action shall be "GermanName" in the de-DE culture.
All localizations shall be using the "usual way" (.resx files, like the stock localization for views etc.).
How can I achieve this without manually building a dynamic routing table?
Currently, I have no starting point. Search results for e.g. ASP.NET Core route localization just bring up answers for having the culture in the URL (except domain name), but this is not my use case. I tried to use variable names for the attributes, but of course this does not work.
Filip Woj has pointed out a solution in his blog post.
He shows the usage of MapDynamicControllerRoute in combination with DynamicRouteValueTransformer class to translate the routes.
All my controllers are based off of a BaseController, to share properties between them and override OnActionExecuting to set some values based on the route.
I'm creating a BaseViewData class to do the same for all my view data.
At the moment I'm populating the view data like so (C#):
var viewData = new BaseViewData
{
Name = "someName",
Language = "aLanguage",
Category = "aCategoryName"
};
I do this in every action that requires the view data. Some of the properties are common, need to be set throughout every action. Is there a way to set some of the properties on a more global scale?
If I instantiate the BaseViewData class in the OnActionExecuting method on the BaseController, how do I access the BaseViewData properties from the action in the regular controllers (derived from the BaseController)?
Update in response to Dennis Palmer:
I'm essentially doing this because of a nagging issue I'm having with ViewData["lang"] not being populated randomly on some requests. ViewData["lang"] contains "en" if the language is English, and "ja" if it is Japanese (well, it's supposed to anyway). I populate ViewData["lang"] inside OnActionExecuting on the BaseController.
In my view, I make a call to some partial views based on the language:
<% Html.RenderPartial(ViewData["lang"] + "/SiteMenu"); %>
But I'm randomly getting errors thrown that state "Cannot find /SiteMenu", which points to the fact that ViewData["lang"] has no value. I just cannot find any reason why ViewData["lang"] would not get populated. So, I'm rewriting the site to use ONLY strongly typed view data (and setting some hard defaults). But if another method is better, I'll go that way.
Thank you!
I'm not sure I follow exactly what you're trying to do, but if your view is using values in the route to display certain information, it seems like adding your own extension methods for HtmlHelper would be a better way to go.
Are Name, Language and Category contained in your routes? If so, then HtmlHelper will have access to the route info and can determine what to display via the extension methods. What is the correlation between your routes and what your views need to know?
Update: Is lang part of your route? If so, then I would still contend that you could write an HtmlHelper extension method that looks at the route data directly and determines which partial view to render. That way your controller wouldn't even need to worry about setting the ViewData["lang"]. The view would always know how to render based on the route.
Update 2: I think dismissing use of an HtmlHelper extension method because it re-evaluates the route data might be a case of premature optimization. Your controller inheritance scheme sounds overly complex and you asked the question because the way you were setting ViewData was unreliable. I doubt that pulling the value from route data would be much, if any, less efficient than setting and reading from ViewData.
From your comment:
In the controller I use the lang value
to determine which view to show as
well.
That only makes me think that there are more pieces of your system that I'd need to see in order to give better advice. If you have separate views for each language then why does the view need to be told which language to use?
Another alternative to consider would be using nested master pages. You could have a single master page for your site layout and then a nested master page for each language that just contains a hard coded lang value.
Perhaps instead of this inheritance scheme you have, you can just use action filters to add the data you need.
I'm having a hack around with the MVC framework, to try some proof of concept ideas. This are not production code..
Anyhow - I have an anonymous controller. I would like to execute an Action on that controller, however, I only have this controller's action name available as a string.
How can I render the controller's action via a string name?
Thank you all!
Franko
You have multiple options:
Asp.net MVC 2 Beta 2 (used to be part of MVC Futures) that has RenderAction() built in for these purposes
similar thing is implemented in MVC Contrib with Sub controllers
or you can have PartialRequest() as explained here
But you'll have to be careful since there are issues in all of them.
Even though. Your anonymous controller is probably going to be the main obstacle. But that depends on the way you have it and how you access it. It would be easier if you'd show us some code so we could provide some more insight into your problem.
Not sure about an anonymous controller?? But ...
If you have a look at the MVC futures project on codeplex they have a Html.RenderAction
Kindness,
Dan
Although I believe that your idea may not be optimal, you can use this code:
var controller = new SomeController(null);
var controllerContext = new ControllerContext(new HttpContextWrapper(System.Web.HttpContext.Current),new RouteData(),controller);
var actionInvoker = new ControllerActionInvoker();
actionInvoker.InvokeAction(controllerContext, "Test");
I you need more details about how this code works, look at System.Web.Mvc in Reflector.