I am trying to develop a MVC3 (razor) application with Select language functionality.
Using the following view as a Partial view on _Layout.cshtml
_SelectCulture
<text>
#Html.ActionLink("English", "SetCulture", new { controller = "Culture", culture = "en-GB" })
|
#Html.ActionLink("Welsh", "SetCulture", new { controller = "Culture", culture = "cy-GB" })
</text>
<div>
#System.Threading.Thread.CurrentThread.CurrentUICulture.ToString()
</div>
CultureController
public ActionResult SetCulture(string culture)
{
System.Globalization.CultureInfo ci = new System.Globalization.CultureInfo(culture);
System.Threading.Thread.CurrentThread.CurrentCulture = ci;
System.Threading.Thread.CurrentThread.CurrentUICulture = ci;
return RedirectToAction("Index", "Home");
}
But its Still not changing the Language.
Any help please.
Thanks
Well, you are changing the language of the current thread. The current thread ends with the current request which is a little bit later after your controller action executes. Then you are redirecting to some other controller action. Then ASP.NET spawns a new thread to serve this request which obviously doesn't have the culture set.
So you will have to persist this change somewhere. Basically there are 3 different approaches:
route variable
cookies
session
I am putting them in the order of preference. The first approach consists into integrating a {culture} token in all your routes. IMHO this is the best approach in terms of SEO as well. So you will redirect for example to /fr/home/index if you want to get your site in French. You could then use a custom action filter attribute which will run before each action, inspect the culture route parameter and set the current thread culture (this time for the current action).
Cookies and sessions also involve persisting the current language between the requests. In the first example this is done on the client whereas in the second it is done in the server. Once again a custom action filter could be used to read the value of the language before each action and reflect the current thread culture.
You may take a look at the following guide which uses Session to persist the current language.
Related
I have an ASP.NET MVC app. This app allows the user to set the culture to view the contents of the app in. To do this, the user visits ~/user/language. They choose the language and click a "Save" button. In my controller, I have the following:
CultureInfo ci = new CultureInfo(model.Culture);
Thread.CurrentThread.CurrentCulture = ci;
Thread.CurrentThread.CurrentUICulture = ci;
return View(model);
When the view is re-loaded, the strings have been translated like I would expect. However, if I visit another page in the app, the strings are NOT translated like I would expect. It's like the culture information isn't being preserved or the thread is being killed.
What am I doing wrong?
#heymega is correct. The current culture is non-persistent. You have to load at the beginning of each request. You need to set the culture to a persistent location (such as a cookie) and then set the culture to the thread in the Application_BeginRequest event.
A better way than using a cookie is to build the culture into the URL.
http://www.somesite.com/es-MX/somewhere
http://www.somesite.com/en-US/somewhere
This is what search engines expect, it means the user can switch culture easily by switching URLs, and you don't have to keep track of culture on a per user basis. However, you do still need to set the culture at the beginning of each request based on the URL.
I'm working on a system that needs to know a user's choice before they enter a site. Up till now the choice has been stored in a cookie and checked by JavaScript on the page load - if the cookie doesn't exist then a dialog is shown and the user makes the choice.
Although we'd normally expect the user to arrive at the homepage of the application, they can legally follow a URL to any page within the application, so the JavaScript to check the choice exists on every page.
This has caused problems (almost always fixed by clearing cookies) so we're switching to store the choice in the database. What we need is a neat way of making sure that all pages (MVC and Web Forms) check that the choice has been made and, if it hasn't, either display a dialog or redirect to a page where the choice can be made.
The main thing bothering me is that to cause a redirect using MVC, I need to return a RedirectResult, and this can only be done from an Action. I don't want every action to have code regarding this check - it seems like the kind of thing that should be possible from a base controller (in the same way a base page could cause a Response.Redirect.
Can anyone suggest a good way for all pages to perform a check on the database and then either cause a redirect or show a dialog?
The main thing bothering me is that to cause a redirect using MVC, I
need to return a RedirectResult, and this can only be done from an
Action.
Oh not at all. You could also redirect from a custom action filters.
For example you could write a custom IAuthorizationFilter that will check whether the user made the necessary choice and if not redirect to some given page. The check could be done against a cookie, database or wherever you decide to persist this information:
public class EnsureChoiceHasBeenMadeAttribute : FilterAttribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationContext filterContext)
{
// get the current user
var user = filterContext.HttpContext.User;
if (user.Identity.IsAuthenticated && !UserMadeAChoice(user.Identity.Name))
{
// if the current user is authenticated and he didn't made a choice
// redirect him to some page without even attempting to execute
// the controller action that he requested
var values = new RouteValueDictionary(new
{
controller = "home",
action = "index"
});
filterContext.Result = new RedirectToRouteResult(values);
}
}
private bool UserMadeAChoice(string username)
{
throw new NotImplementedException();
}
}
Now you have different possibilities:
You decorate the controllers/actions that you want to perform this check with the [EnsureChoiceHasBeenMade] attribute
You register the action filter as a global action filter so that it applies to absolutely all actions
You write a custom filter provider in order to dynamically apply the action filter to some actions based on some dynamic values (you have access to the HttpContext).
I am trying to localize my web page, and have done the following :
Added resource files with some example strings
Added a call to a method to set culture and ui culture on the click event of flag icons :
public void SetCulture(string culture)
{
Thread.CurrentThread.CurrentCulture = new CultureInfo(culture);
Thread.CurrentThread.CurrentUICulture = new CultureInfo(culture);
}
And I have made reference to my resource file strings in my web page :
#Resources.General.String1
I have stepped through my code and the culture is successfully changed in my SetCulture method, but the string does not change on the web page. Can anybody advise why?
I don't understand what you mean with the click event in a ASP.NET MVC application. I would use routing to set the new language. A sample you can find at Localization with ASP.NET MVC using Routing.
Instead of using a Javascript event, you have to reload the whole page.
There were questions about multilingual apps in MVC here on SO but they were mostly answered by giving details about implementing Resource files and then referencing those Resource strings in Views or Controller. This works fine for me in conjunction with the detection of user's locale.
What I want to do now is support localized routes. For instance, we have some core pages for each website like the Contact Us page.
What I want to achieve is this:
1) routes like this
/en/Contact-us (English route)
/sl/Kontakt (Slovenian route)
2) these two routes both have to go to the same controller and action and these will not be localized (they will be in English because they are hidden away from the user since they are part of the site's core implementation):
My thought is to make the Controller "Feedback" and Action "FeedbackForm"
3) FeedbackForm would be a View or View User control (and it would use references to strings in RESX files, as I said before, I already have set this up and it works)
4) I have a SetCulture attribute attached to BaseController which is the parent of all of my controllers and this attribute actually inherits FilterAttribute and implements IActionFilter - but what does it do? Well, it detects browser culture and sets that culture in a Session and in a cookie (forever) - this functionality is working fine too. It already effects the Views and View User Controls but at this time it does not effect routes.
5) at the top of the page I will give the user a chance to choose his language (sl|en). This decision must override 4). If a user arrives at our website and is detected as Slovenian and they decide to switch to English, this decision must become permanent. From this time on SetCulture attribute must somehow loose its effect.
6) After the switch, routes should immediately update - if the user was located at /sl/Kontakt
he should immediately be redirected to /en/Contact-us.
These are the constraints of the design I would like. Simply put, I do not want English routes while displaying localized content or vice-versa.
Suggestions are welcome.
EDIT:
There's some information and guidance here - Multi-lingual websites with ASP.NET MVC, but I would still like to hear more thoughts or practices on this problem.
Translating routes (ASP.NET MVC and Webforms)
How about this?
Create custom translate route class.
Localization with ASP.NET MVC using Routing
Preview:
For my site the URL schema should look
like this in general:
/{culture}/{site}
Imagine there is a page called FAQ,
which is available in different
languages. Here are some sample URLs
for these pages:
/en-US/FAQ /de-DE/FAQ /de-CH/FAQ
Why not create the action names desired and simply RedirectToAction for the single, real implementation?
public ActionResult Kontakt() {
return RedirectToAction("Contact");
}
public ActionResult Contact() {
return View();
}
I just used a simple solution with "Globalization Resources", like this:
routes.MapRoute(
"nameroute", // Route name
App_GlobalResources.Geral.Route_nameroute+"/{Obj}", // URL with parameters
new { controller = "Index", action = "Details", Obj = UrlParameter.Optional } // Parameter defaults
);
But, you could customize as needed.
In the conventional website a url displayed as:
http://www.mySite.com/Topics
would typically mean a page sits in a subfolder below root named 'Topics' and have a page named default.htm (or similar).
I'm trying to get my head in gear with the MVC way of doing things and understand just enough of routing to know i should be thinking of URLs differently.
So if i have a db-driven page that i'd typically script in a physical page located at /Topics/index.aspx - how does this look in an MVC app?
mny thx
--steve...
It sounds like you are used to breaking down your website in terms of resources(topics, users etc) to structure your site. This is good, because now you can more or less think in terms of controllers rather than folders.
Let's say you have a structure like this in WebForms ASP.NET.
-Topics
-index.aspx
-newtopic.aspx
-topicdetails.aspx
-Users
-index.aspx
-newuser.aspx
-userdetails.aspx
The structure in an MVC app will be pretty much the same from a users point of view, but instead of mapping a url to a folder, you map a url to a controller. Instead of the folder(resource) having files inside it, it has actions.
-TopicController
-index
-new
-details
-UserController
-index
-new
-details
Each one of these Actions will then decide what view (be this html, or json/xml) needs to be returned to the browser.
Actions can act differently depending on what HTTP verb they're repsonding to. For example;
public class UserController : Controller
{
public ActionResult Create()
{
return View(new User());
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(User user)
{
// code to validate /save user
if (notValid)
return new View(user);
else
return new View("UserCreatedConfirmation");
}
}
This is sort of a boiled down version of RESTful URLs, which I recommend you take a look at. They can help simplify the design of your application.
It looks just like you want it to be.
Routing enables URL to be quite virtual. In asp.net mvc it will end at specified controller action method which will decide what to do further (i.e. - it can return specified view wherever it's physical location is, it can return plain text, it can return something serialized in JSON/XML).
Here are some external links:
URL routing introduction by ScottGu
ASP.NET MVC tutorials by Stephan Walther
You would have an default view that is associated with an action on the Topics controller.
For example, a list page (list.aspx) with the other views that is tied to the list action of the Topics controller.
That is assuming the default routing engine rules, which you can change.
Read more here:
http://weblogs.asp.net/scottgu/archive/2007/12/03/asp-net-mvc-framework-part-2-url-routing.aspx
IMHO this is what you need for your routes.
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Topics", action = "Index", id = "" } // Parameter defaults
);
You would need a TopicsController that you build the View (topics) on.