Is there a way to completely disable the Bot Framework default localizer? The localizer seems to translate prompts inconsistently and in unexpected places. Also my bot sometimes cannot understand common user inputs (help, quit, back, yes, no) since it seems to be expecting them in a different language.
I didn't configure any localization settings so I'm guessing this behaviour is caused by the default Bot Framework localization. I'm looking for a way to completely avoid any attempts to translation and keep my bot using English only.
Have a look to the dedicated section of the documentation about localization: https://learn.microsoft.com/en-us/bot-framework/dotnet/bot-builder-dotnet-formflow-localize
The bot framework is automatically using the locale from the message to select the right Resources, but you can override this information by setting your thread's CurrentUICulture and CurrentCulture, and ideally also your Locale property in your MessageActivity
CultureInfo lang = ...;
Thread.CurrentThread.CurrentCulture = lang;
Thread.CurrentThread.CurrentUICulture = lang;
context.Activity.AsMessageActivity().Locale = lang.ToString();
Don't forget to set it for each Thread that will send messages as there is no global solution to switch the language.
If you want to go deeper, you can have a look to the bot framework sources:
LocalizedScope class
SetAmbientThreadCulture class in
PostToBot
Edit:
For the prompts part, if I remember well I had to create my own public abstract class MyPrompt<T, U> : IDialog<T> and in that one:
protected virtual IMessageActivity MakePrompt(IDialogContext context, string prompt, IReadOnlyList<U> options = null, IReadOnlyList<string> descriptions = null, string speak = null)
{
var msg = context.MakeMessage();
// force Culture
CultureInfo lang = ...;
if (lang != null)
{
Thread.CurrentThread.CurrentCulture = lang;
Thread.CurrentThread.CurrentUICulture = lang;
context.Activity.AsMessageActivity().Locale = lang.ToString();
}
if (options != null && options.Count > 0)
{
promptOptions.PromptStyler.Apply(ref msg, prompt, options, descriptions, speak);
}
else
{
promptOptions.PromptStyler.Apply(ref msg, prompt, speak);
}
return msg;
}
Related
I'm validating a new culture, the country part comes from a user-selection and needs to be checked, so i'm using this code:
string language = submittedCulture.TwoLetterISOLanguageName; // de
string countryCode = GetIso3166Country(model.Country); // submitted Italy and countryCode IT
string cultureString = string.IsNullOrEmpty(countryCode)
? language
: $"{language}-{countryCode}";
bool validCulture = cultureString.TryParseCultureInfo(out CultureInfo newCulture);
The TryParseCultureInfo is a simple extension method using CultureInfo.GetCultureInfo:
public static bool TryParseCultureInfo(this string stringValue, out CultureInfo value)
{
value = null;
if (stringValue == null)
{
return false;
}
try
{
value = CultureInfo.GetCultureInfo(stringValue);
return true;
}
catch (CultureNotFoundException)
{
return false;
}
}
If i test it locallly everything works as expected. I get a culture for "de-IT", so validation succeeds and i will resolve the redirect-url as next step. But on production(actually UAT) it fails, the culture is not supported.
Does every culture need to be installed? If i try "it-IT" it works also on production, so it seems that not italy is the problem but that "de-IT" does not exist. Why is the behavior so different, what causes it and what can i do to get the same behavior locally and on production?
Would it be an option to use CultureAndRegionInfoBuilder to create that culture on app-start or what is the best solution? Edit: I tried that but you need administrative rights which is not possible.
So what would be the best way to circumvent this issue even if that culture is not (yet) supported?
I've searched around and haven't been able to find an answer that worked for me.
My question is: how to set the culture of the current response within a controller method.
The scenario is as follows. We have a general area of the web application, which works with culture set via a cookie, which works fine.
One small part of the website, though, needs to be in Danish always, except if a query parameter is set to, for instance, en-GB.
In the controller method receiving the request I add the culture change to the cookie, but the returned view is still displayed in the previous language. A refresh of the page will then display the view in the correct language, since the cookie now has the correct values.
What I would like is to also change the culture of the current request besides the cookie, so that the returned view is displayed in the correct language.
My controller's code is as follows:
[Route("{organizationId}/{arrangementId}")]
public IActionResult LandingPage(int organizationId, int arrangementId, [FromQuery] string lang, [FromQuery] bool? allowup)
{
if (organizationId <= 0 || arrangementId <= 0)
{
return RedirectToAction("Index");
}
_utils.SetLoginRedirectUrl($"/Skolevaccination/Questionnaire/{organizationId}/{arrangementId}", Response);
SetLanguage(!string.IsNullOrWhiteSpace(lang) ? lang : "da-DK");
ViewBag.allowup = allowup ?? false;
return View("Index");
}
private void SetLanguage(string lang)
{
string cultureCookieValue;
switch (lang)
{
case "gb":
case "en-GB":
cultureCookieValue = CookieRequestCultureProvider.MakeCookieValue(new RequestCulture("en-GB"));
_cultureService.SetCulture(new CultureInfo("en-GB"));
break;
case "dk":
case "da-DK":
cultureCookieValue = CookieRequestCultureProvider.MakeCookieValue(new RequestCulture("da-DK"));
_cultureService.SetCulture(new CultureInfo("da-DK"));
break;
default:
_logger.LogInformation($"Trying to set a language {lang}, which isn't supported.");
return;
}
Response.Cookies.Append(CookieRequestCultureProvider.DefaultCookieName, cultureCookieValue);
}
The call to _cultureService.SetCulture(new CultureInfo("da-DK")) uses the following code, which are my attempts at solving the problem:
public void SetCulture(CultureInfo cultureInfo)
{
if (!GetSupportedCultures().Contains(cultureInfo))
{
throw new CultureNotFoundException("The given culture is not supported.");
}
Thread.CurrentThread.CurrentUICulture = cultureInfo;
Thread.CurrentThread.CurrentCulture = cultureInfo;
//CultureInfo.CurrentCulture = cultureInfo;
//CultureInfo.CurrentUICulture = cultureInfo;
}
As seen I tried setting the thread's current culture and using the static methods on CultureInfo as well (the commented out lines).
A lot of the posts I found online mentioned that setting the thread's culture was a thing used previous to ASP.Net 5 (ASP.Net core) and that all culture settings are now set up in the startup file. But if I change it in the startup file, I change it for the whole website, which is not what we want. And I need to set the cookie as well, since subsequent pages on the small part of the whole website needs to use the language set via the URL or the default one, but their urls will not have the language defined there.
If this can't be solved by setting the current culture directly, could it perhaps be solved by setting up a routing area for this specific part of the web app?
Thanks in advance!
EDIT 2018/10/05
I found a solution after looking closer at the view and its IViewLocalizer.
After looking closer at the documentation for the localizer classes I found that you can use their method WithCulture. This returns a new localizer for the same strings but for the given culture.
But after still having troubles with the view not being localized correctly, I found that the injected localizer in the view isn't the same as the one injected in the controller, so to fully localize the request, the localizer in both the controller and the view needs to be exchanged using the WithCulture method.
My solution ended up like the following:
In the controller:
private void SetLanguage(string lang, string defaultLang)
{
if (string.IsNullOrWhiteSpace(lang) || !_cultureService.IsSupportedCulture(lang)) {
_logger.LogInformation($"Tried to change culture to unsupported culture (to {lang}). Culture has been set to default culture {defaultLang}.");
lang = defaultLang;
}
string cultureCookieValue;
switch (lang)
{
case "gb":
case "en-GB":
cultureCookieValue = CookieRequestCultureProvider.MakeCookieValue(new RequestCulture("en-GB"));
_localizer = _localizer.WithCulture(new CultureInfo("en-GB"));
ViewBag.changeCulture = true;
ViewBag.lang = "en-GB";
break;
case "dk":
case "da-DK":
cultureCookieValue = CookieRequestCultureProvider.MakeCookieValue(new RequestCulture("da-DK"));
_localizer = _localizer.WithCulture(new CultureInfo("da-DK"));
ViewBag.changeCulture = true;
ViewBag.lang = "da-DK";
break;
default:
_logger.LogInformation($"Trying to set a language {lang}, which isn't supported.");
return;
}
Response.Cookies.Append(CookieRequestCultureProvider.DefaultCookieName, cultureCookieValue);
}
When calling this method in the controller, I set some data in the view bag, which is used in the view to also create a new localizer there:
#inject IViewLocalizer ViewLocalizer
#{
IHtmlLocalizer Localizer = ViewLocalizer;
if (ViewBag.changeCulture) {
var lang = ViewBag.lang;
Localizer = Localizer.WithCulture(new CultureInfo(lang));
}
var allowUserPasswordLogin = ViewBag.allowup;
}
When calling the WithCulture method the returned type is IHtmlLocalizer, which can't be cast back to a IViewLocalizer, but IViewLocalizer extends the IHtmlLocalizer interface, so it all works out with a few lines of code.
I have been using Thread.CurrentThread.CurrentUICulture with System.Threading and System.Globalization, for a while now to manually set the language used by my ASP.net pages, mainly WebForms and WebPages with Razor.
See MSDN: Thread.CurrentThread.CurrentUICulture
I recently read a tutorial that was using Page.UICulture instead (actually, UICulture which appears to be strongly typed). To my surprise I ended up with exactly the same result; they both changed my websites' ui language settings and read the correct resource file.
See MSDN: Page.UICulture
To me, the Thread.CurrentUICulture makes more sense (I say that intuitively, since it's literally "changing the culture of the current thread").
But calling Page.Culture is much easier and doesn't require to call yet another pair of ASP.net using, so I've settled for that solution for now.
Is there a fundamental difference between the two or are they perfectly interchangeable ?
The reason why I worry is that I have a bunch of old websites developed with the first method and I am afraid to run into interchangeability conflicts if I update them to the second one rashly.
Note: I usually focus on UICulture in my line of work and Culture is very accessory to what I do, but I am asking the question for the both of them.
Page.UICulture is a wrapper around Thread.CurrentThread property and is ment for internal .NET framework use:
This property is a shortcut for the CurrentThread property. The culture is a property of the executing thread
This API supports the .NET Framework infrastructure and is not intended to be used directly from your code.
Looking at the source code, you can clearly see that:
public string UICulture
{
set
{
CultureInfo newUICulture = null;
if(StringUtil.EqualsIgnoreCase(value, HttpApplication.AutoCulture))
{
CultureInfo browserCulture = CultureFromUserLanguages(false);
if(browserCulture != null)
{
newUICulture = browserCulture;
}
}
else if(StringUtil.StringStartsWithIgnoreCase(value, HttpApplication.AutoCulture))
{
CultureInfo browserCulture = CultureFromUserLanguages(false);
if(browserCulture != null)
{
newUICulture = browserCulture;
}
else
{
try
{
newUICulture = HttpServerUtility.CreateReadOnlyCultureInfo(value.Substring(5));
}
catch {}
}
}
else
{
newUICulture = HttpServerUtility.CreateReadOnlyCultureInfo(value);
}
if (newUICulture != null)
{
Thread.CurrentThread.CurrentUICulture = newUICulture;
_dynamicUICulture = newUICulture;
}
}
get { return Thread.CurrentThread.CurrentUICulture.DisplayName; }
}
They do exactly the same thing.
As you can see on the documentation page:
This property is a shortcut for the CurrentThread property. The culture is a property of the executing thread
I have a multilingual web-forms web application which I am using resource files and a BasePage class which sets the culture based on the QueryString that was included in the page which is inheriting from this class. This is all working well, if a tad clunky as I am having to do this sort of thing for every button which takes the user to a different page to maintain the culture:
if (Thread.CurrentThread.CurrentCulture.ToString() == "cy-GB")
{
return "~/Secure/Details.aspx?lang=cy-GB&PersonId=" + currentPersonId;
}
else
{
return "~/Secure/Details.aspx?PersonId=" + currentPersonId;
}
I knew there was probably a better way of doing this but being a novice as it worked I simply made do.
This was until I had to implement an asp:SiteMapPath control. I initially assumed that I could simply create a resource entry for the url property like I had done for the title:
<siteMapNode
title="$resources:SiteMapLocalizations,HomePageTitle"
description="Home"
url="~$resources:SiteMapLocalizations,HomePageUrl">
However this resulted in a server error:
A potentially dangerous Request.Path value was detected from the client (:).
I've done some reading and I believe I need to somehow store the current culture to a session variable which will follow the user around so when they click 'Home' on the breadcrumb it will be consistent with the culture and grab the appropriate text from the resource files, I'm also hoping this will allow me to remove all of the IF ELSE statements I've had to write to maintain the current language throughout the application.
My question is however, where do I start with this, I cannot find any guide step by step to follow in order to achieve this, can anyone provide some instructions?
Make sure you have a button of some sort which triggers the change of language. In my case I have two versions of the header, one with an English link which will append the Query String to English and one for Welsh, something like:
ASP
<a id="languagelink" runat="server" title="Cymraeg">Cymraeg</a>
C#
if (Thread.CurrentThread.CurrentCulture.ToString() == "en-GB")
{
Uri uri = new Uri(currentPage);
languagelink.HRef = String.Format(uri.GetLeftPart(UriPartial.Path)+"?lang=cy-GB");
}
Every page which requires the language switch needs to inherit from my custom BasePage like so:
Inheritance
public partial class Secure_CustomerSummary : BasePage
Base_Page
public partial class BasePage : System.Web.UI.Page
{
protected override void InitializeCulture()
{
if (Session["language"] == null)
{
Session["language"] = "en-GB";
}
else
{
if (Request.QueryString["lang"] == null)
{
SetSessionCulture();
}
if (Request.QueryString["lang"] != null)
{
string qs = Request.QueryString["lang"];
Session["language"] = qs;
}
SetSessionCulture();
}
SetSessionCulture();
}
private void SetSessionCulture()
{
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(Session["language"].ToString());
Thread.CurrentThread.CurrentUICulture = new CultureInfo(Session["language"].ToString());
base.InitializeCulture();
}
}
I have a breakpoint on the "return" line here:
[HttpGet]
[Route("api/Test/{id1}/{id2}")]
public NRBQEntity GetTestMessage(String id1, String id2)
{
return NRBQClient.GetTestMessage(id1, id2);
}
Although it does not crash the app, when I reach that point, I get,
"Exception:Thrown: "Culture is not supported." (System.Globalization.CultureNotFoundException)
A System.Globalization.CultureNotFoundException was thrown: "Culture is not supported.""
Which culture is trying to be supported, why is it not supported, and what, if anything, should I do to support the culture?
UPDATE
Answer to sphanley:
Besides standing for "New Riders of the BarbeQue," it is a "skeleton" (for now) Entity that looks like this:
public class NRBQEntity
{
public NRBQEntity()
{
}
public String Value { get; set; }
}
UPDATE 2
Answer to AnotherUser:
This is not my code, so I'm just in the process of trying to grok it; it has been provided as a starting point for me to copy over/refactor an existing standalone project, incorporating it into "the" solution. That having been said, to answer your question, here are all the instances of "GetTestMessage()" in the solution:
[HttpGet]
[Route("api/Test/{id1}/{id2}")]
public NRBQEntity GetTestMessage(String id1, String id2)
{
return NRBQClient.GetTestMessage(id1, id2);
}
[HttpGet]
[Route("api/Test/{id1}/{id2}")]
public NRBQEntity GetTestMessage(String id1, String id2)
{
return NRBQService.GetNRBQEntity(id1, id2);
}
public interface INRBQClient
{
NRBQEntity GetTestMessage(String id1, String id2);
}
public NRBQEntity GetTestMessage(String id1, String id2)
{
var res = RESTAPIClient.GET<NRBQEntity>(null
, new Uri(NRBQClientSettings.NRBQAPI)
, String.Format("api/Test/{0}/{1}"
, id1
, id2)
);
if (res.status != RequestResultStatus.Success)
{
throw new Exception(res.message);
}
return res.result;
}
...and this test:
[TestFixture, Category(DRBCOMMON.UnitTests.Categories.IntegrationTest)]
public class NRBQClientIntegrationTests
{
[Test]
public void TestNRBQInterface()
{
var NRBQClient = IOC.container.Resolve<INRBQClient>();
var s = NRBQClient.GetTestMessage("GET", "SORTY");
Assert.Greater(s.Value.Length, 0);
}
}
Which culture is trying to be supported
Place a try / catch around the offending line and catch the exception. Place a break point inside the catch block and debug your code. Examine the thrown CultureNotFoundException's InvalidCultureName property. This will tell you which Culture is trying be used but is not found on the system.
Why is it not supported
Windows has a built in set of Cultures (What cultures are supported by the CultureInfo class in .NET 3.5? and http://msdn.microsoft.com/en-us/goglobal/bb896001.aspx). If the Culture listed in InvalidCultureName is not on the list, it's not supported.
What, if anything, should I do to support the culture?
This depends on what InvalidCultureName is. If you are legitimately trying to use a non-supported culture (for example, you have a multi lingual / multi regional site and want to support English for every culture) you may have legitimate need to create a new culture. For example, I worked on a site, http://www.oneill.com, where we wanted to have a French version of the Netherlands site (http://www.oneill.com/nl-fr). We had to create a new culture and install it on the web server using these instructions: http://msdn.microsoft.com/en-us/library/ms172469(v=vs.90).aspx
If you aren't doing any fancy culture stuff, there are some known issues with asp.net involving the framework erroneously creating CultureInfo instances against based on directories that might not be present:
http://edd.stefancamilleri.com/2013/11/25/asp-net-mvc-always-throws-a-system-globalization-culturenotfoundexception/
.NET 4.0 - CultureNotFoundException
In that case, looks like the solution is to just turn off the Exception and ignore it.
You can check all existing codes in this list.
You can create a custom culture, like so:
// Create a new colture, with the name you desire
CultureAndRegionInfoBuilder cib = new CultureAndRegionInfoBuilder("en-IN", CultureAndRegionModifiers.None);
// Load all defaults from en-US
CultureInfo ci = new CultureInfo("en-US");
cib.LoadDataFromCultureInfo(ci);
// Populate the new CultureAndRegionInfoBuilder object with region information.
RegionInfo ri = new RegionInfo("US");
cib.LoadDataFromRegionInfo(ri);
// Now you can make changes, or finish.
// Changes can be currency, RegionName, etc.
// Finish
cib.Register();
this article explains how to do it.