Locale is not correct when using #Config with language qualifiers. Robolectric 2.3 - robolectric

I want to test locale files using robolectric 2.3. I have two locales en(by default) and sv.
I've tried to use next variants:
//1
#Config(qualifiers = "sv")
public void testSVLocalization() {
Locale.setDefault(new Locale ("sv", "SV"));
configuration.locale = Locale.getDefault();
assertThat(configuration.locale).isEqualTo(new Locale ( "sv", "SV"));
String tabSettingsStr = new MainActivity().getResources().getString(R.string.tab_settings);
assertEquals(tabSettingsStr, "INSTÄLLNINGAR");
}
//2
...
Locale locale = new Locale("sv", "SV");
Robolectric.shadowOf( startAppActivity.getApplicationContext().getResources().getConfiguration() ).setLocale( locale );
Locale.setDefault( locale );
...
//3
#Config(qualifiers = "sv")
public void testSVLocalization() {
Locale locale = resources.getConfiguration().locale;
...}
also I've tried #Config(reportSdk = 20, emulateSdk = 20) etc. All this methods set locale and when I check it it's shows sv, but on this string assertEquals(tabSettingsStr, "INSTÄLLNINGAR"); I have an error org.junit.ComparisonFailure because this string took from english localization file.
Any ideas how to resolve this problem?

Ok, it seems like you are experiencing what is described in this issue:
https://github.com/robolectric/robolectric/issues/635
Basically, the #Config use will affect the way you load resources in the test directly, but not the way the app's Activities loads these resources.
To change the locale for your activity, do the following with the resources from that activity, before you test it. That should allow it to pickup the right strings:
Robolectric.shadowOf(resources.getConfiguration()).setLocale(locale);

Related

.Net Core 3.1 Razor Pages : autoredirect to culture

I try to accomplish a similar behaviour with MS Docs.
For example, if you visit https://learn.microsoft.com/, you will be redirected to your culture, in my case I'm being redirected automatically to https://learn.microsoft.com/en-gb/.
Same goes for inner pages if you access them without the culture in the URL.
For instance, by accessing:
https://learn.microsoft.com/aspnet/core/razor-pages/?view=aspnetcore-3.1&tabs=visual-studio
it will be automatically redirect you to:
https://learn.microsoft.com/en-gb/aspnet/core/razor-pages/?view=aspnetcore-3.1&tabs=visual-studio
I have a small demo app where I conduct my localisation experiment for .NET Core 3.1 and Razor Pages here.
I have set options.Conventions here, and I have created CustomCultureRouteRouteModelConvention class here, but I'm fairly novice with .NET Core and I'm kind of stuck on how to implement the above-described functionality.
Thank you all in advance!
You should use existing Rewriting Middleware to do redirects: https://learn.microsoft.com/en-us/aspnet/core/fundamentals/url-rewriting?view=aspnetcore-3.1
In the simplest form, you can tell rewrite middleware to redirect if it does not see a locale pattern at the beginning of the URL path, maybe
new RewriteOptions() .AddRedirect("^([a-z]{2}-[a-z]{2})", "en-US/$1")
(regex not tested) or do full redirect class with more detailed rules when and to what locale you want to redirect. Example in that aspnet document references RedirectImageRequest which you can use to get an understanding of how custom redirect rules works. Adapting to your case as a proof of concept, I reused most of the logic in your existing RedirectUnsupportedCulture:
public class RedirectUnsupportedCultures : IRule
{
private readonly string _extension;
private readonly PathString _newPath;
private IList<CultureInfo> _cultureItems;
private string _cultureRouteKey;
public RedirectUnsupportedCultures(IOptions<RequestLocalizationOptions> options)
{
RouteDataRequestCultureProvider provider = options.Value.RequestCultureProviders
.OfType<RouteDataRequestCultureProvider>()
.First();
_cultureItems = options.Value.SupportedUICultures;
_cultureRouteKey = provider.RouteDataStringKey;
}
public void ApplyRule(RewriteContext rewriteContext)
{
// do not redirect static assets and do not redirect from a controller that is meant to set the locale
// similar to how you would not restrict a guest user from login form on public site.
if (rewriteContext.HttpContext.Request.Path.Value.EndsWith(".ico") ||
rewriteContext.HttpContext.Request.Path.Value.Contains("change-culture"))
{
return;
}
IRequestCultureFeature cultureFeature = rewriteContext.HttpContext.Features.Get<IRequestCultureFeature>();
string actualCulture = cultureFeature?.RequestCulture.Culture.Name;
string requestedCulture = rewriteContext.HttpContext.GetRouteValue(_cultureRouteKey)?.ToString();
// Here you can add more rules to redirect based on maybe cookie setting, or even language options saved in database user profile
if(string.IsNullOrEmpty(requestedCulture) || _cultureItems.All(x => x.Name != requestedCulture)
&& !string.Equals(requestedCulture, actualCulture, StringComparison.OrdinalIgnoreCase))
{
string localizedPath = $"/{actualCulture}{rewriteContext.HttpContext.Request.Path.Value}";
HttpResponse response = rewriteContext.HttpContext.Response;
response.StatusCode = StatusCodes.Status301MovedPermanently;
rewriteContext.Result = RuleResult.EndResponse;
// preserve query part parameters of the URL (?parameters) if there were any
response.Headers[HeaderNames.Location] =
localizedPath + rewriteContext.HttpContext.Request.QueryString;
}
}
and registered it in Startup.cs with
// Attempt to make auto-redirect to culture if it is not exist in the url
RewriteOptions rewriter = new RewriteOptions();
rewriter.Add(new RedirectUnsupportedCultures(app.ApplicationServices.GetService<IOptions<RequestLocalizationOptions>>()));
app.UseRewriter(rewriter);
Improvement:
After using the above code I bumped on a bug that in case the culture is not supported by the application, the redirection will end up with infinite culture paths. For example, if I support the cultures en (default) and gr, if instead of either /en/foobar or /gr/foobar I would write /fr/foobar, I would end up getting /en/fr/foobar then /en/en/fr/foobar and etc.
I added private readonly LinkGenerator _linkGenerator; to the class, which I initialise it in the constructor. I removed that line string localizedPath = $"/{actualCulture}{rewriteContext.HttpContext.Request.Path.Value}"; and the code after that line looks like this:
rewriteContext.HttpContext.GetRouteData().Values[_cultureRouteKey] = actualCulture;
HttpResponse response = rewriteContext.HttpContext.Response;
response.StatusCode = StatusCodes.Status301MovedPermanently;
rewriteContext.Result = RuleResult.EndResponse;
// preserve query part parameters of the URL (?parameters) if there were any
response.Headers[HeaderNames.Location] =
_linkGenerator.GetPathByAction(
rewriteContext.HttpContext,
values: rewriteContext.HttpContext.GetRouteData().Values
)
+ rewriteContext.HttpContext.Request.QueryString;
As decribed in Microsoft docs localization middleware; each the localization request initializes a list of RequestCultureProvider and is enumerated by the below order :
QueryStringRequestCultureProvider : e.g. http://localhost:1234/Index?culture=en
CookieRequestCultureProvider : Looks for the culture cookie, and it will be null if you haven't set it manually.
AcceptLanguageHeaderRequestCultureProvider : This one depends on the browsers cultures adn this is what you need to look for.
To make sure how it works, delete the culture cookie and change the browser language preferences by moving the desired language to the top, you will see that the language is selected according to the browser preferences.

Redirect all ASP.NET Core logging into a single NLog logger

I have an ASP.NET project that sends its logs to NLog.
However in this project, I have my own NLog logger and I would like to know how to route all the logs through it.
I guess I shouldn't add NLog as a logger, but I should find a way to register a method that will get called each time ASP tries to log anything.
How can this be accomplished?
This is the code that creates the logger:
// create the module name
var ProcessName = Process.GetCurrentProcess().ProcessName;
_ModuleName = ProcessName + " (\"" + Oracle.GuessMyName() + "\")";
// create the logger configuration
var Configuration = new LoggingConfiguration();
// create the file target
var FileTarget = new FileTarget ("file")
{
FileName = #"x:\Logs\${processname}.log",
ArchiveFileName = #"x:\Logs\${processname}.{#}.log",
Layout = #"${longdate}|${logger}|${level}|${message}${onexception:|Exception occurred:${exception:format=tostring}${newline}",
ArchiveEvery = FileArchivePeriod.Day,
ArchiveNumbering = ArchiveNumberingMode.Rolling,
MaxArchiveFiles = 7,
ConcurrentWrites = true
};
Configuration.AddTarget(FileTarget);
// create the viewer target
var ViewerTarget = new NLogViewerTarget ("viewer")
{
Layout = #"${message}${onexception:${newline} --> Exception occurred\:${exception:format=tostring}",
IncludeSourceInfo = true,
IncludeCallSite = true,
Address = #"udp://127.0.0.1:9999"
};
Configuration.AddTarget(ViewerTarget);
// set the rules
Configuration.LoggingRules.Add(new LoggingRule("*", LogLevel.Info, FileTarget));
Configuration.LoggingRules.Add(new LoggingRule("*", LogLevel.Info, ViewerTarget));
// set the configuration
LogManager.Configuration = Configuration;
// create a new logger
_Logger = LogManager.GetLogger(_ModuleName);
and this is also how ASP.net gets attached to nlog:
LoggerFactory.AddNLog();
Application.AddNLogWeb();
Now the current log layout looks like this for two process (the animal names are automatically changing every time the process is restarted)
so both process: shinobi and mouserun here have their own log output, but anything ASP related goes to ASP's nlog instance called Microsoft, regardless of the process.
the goal is to have the ASP output of shinobi to go in the shinobi logger and the mouserun ASP output to go in the mouserun logger.
Look at the code of NLog.Extensions.Logging, where it injects its own custom log-provider.
You can do the same and just wrap your global-logger object:
https://github.com/NLog/NLog.Extensions.Logging/blob/e48d6cc54d9abd70d976066265c7992117cbac5a/src/NLog.Extensions.Logging/NLogLoggerProvider.cs
https://github.com/NLog/NLog.Extensions.Logging/blob/1474ffe5b26d2ac95534ed01ef259133133bfb67/src/NLog.Extensions.Logging/NLogLoggerFactory.cs
https://github.com/NLog/NLog.Extensions.Logging/blob/2c05a4fbdda0fe026e60814d535e164e18786aef/src/NLog.Extensions.Logging/ConfigureExtensions.cs
public static ILoggerFactory AddNLog(this ILoggerFactory factory, NLogProviderOptions options)
{
ConfigureHiddenAssemblies();
using (var provider = new NLogLoggerProvider(options))
{
factory.AddProvider(provider);
}
return factory;
}
You could also create a custom-target, and redirect all non-global-logger messages to this target using NLog rules:
https://github.com/nlog/NLog/wiki/Configuration-file#rules
The custom target can then just forward the log-event to the global-logger:
https://github.com/NLog/NLog/wiki/How-to-write-a-custom-target
You should be careful with cyclic logging. Maybe have a filter in the custom-target to ignore messages from the global-logger.
But I think this is an ugly solution, and I fail to understand the restriction of only one logger-object. Especially when the reason is because it should be named after the application. Why not not a global variable for the name instead of abusing the logger-name?
Alternative you can create a custom target wrapper, that fixes the Logger on LogEventInfo's, so when forwarded to the wrapped target (UDP- / File-target), then it looks like they are all come from the same logger.
Similar to what this guy is trying to do:
https://github.com/NLog/NLog/issues/2352
Again really ugly solution, and should only be used when not able to figure out, how to avoid using the logger-name in the configuration of the wanted Nlog-targets (Ex. configure file-target-filename using something else).

Add metadata to fields in proto3 for Java

Proto3 has been simplified such that the required and optional fields are no longer supported (See Why required and optional is removed in Protocol Buffers 3). Is there still a way to label a certain field as required?
I've looked into FieldOptions, and tried something like this:
message MyMeta {
bool isRequired = 1;
}
extend google.protobuf.FieldOptions {
MyMeta meta = 1234;
}
message Person {
string name = 1 [ (meta) = { isRequired: true }];
string address = 2 [ (meta) = { isRequired: true }];
string remarks = 3;
}
After compiling it into Java code, and as I was checking the compiled Java code I don't see any link between the fields and its metadata I specified in proto. Did I miss something here?
After a bit of tinkering and using #Eric Anderson's idea on using proto reflection, here is a way to retrieve MyMeta from the Person.name field:
Descriptor rootDesc = PersonProto.getDescriptor();
FieldDescriptor name = rootDesc.findFieldByName("name");
FieldDescriptor ext = rootDesc.getFile().getExtensions().get(0);
MyMeta meta = (MyMeta) name.getOptions().getField(ext);
boolean isReq = meta.getIsRequired();
No, that functionality was removed; use documentation instead. If you are trying to use FieldOptions for your own extensions, then you can make your own protoc plugin to generate supplemental code (like a verify utility) or use proto reflection at runtime (via FooMessage.getDescriptor() and Descriptors.FieldDescriptor.getOptions() in Java).

Identityserver3 get and change localization in runtime

I used IdentityServer3.Contrib.Localization to provide translation to the identityserver.
IdentityServer3.Contrib.Localization provides only localization for scopes, messages, events but still there are missing texts to translate in login page and etc.
I think you should provide a custom views for every language using IViewService but i don't know if this is the correct path.
For example in order to provide a localization for a specific language i register this in startup class configuration:
// Register the localization service
idServerServiceFactory.Register(
new Registration<ILocalizationService>(r => new GlobalizedLocalizationService(
new LocaleOptions { Locale = "de-DE" })));
but now i want to change the language based on the language that user input or based on the browser accept-language, how can i change the localization for (scopes, events, messages, views) in run time.
some one mention that i can use OwinEnvironementService and inject it to the localization service to get the language but is there any example?
Also i think that i can provide an owin middleware in order to provide the needed change in localization based on the language but any suggestions?
The IdentityServer3.Localization(nuget.org) package can now do this:
var opts = new LocaleOptions
{
LocaleProvider = env =>
{
var owinContext = new OwinContext(env);
var owinRequest = owinContext.Request;
var headers = owinRequest.Headers;
var accept_language_header = headers["accept-language"].ToString();
var languages = accept_language_header
.Split(',')
.Select(StringWithQualityHeaderValue.Parse)
.OrderByDescending(s => s.Quality.GetValueOrDefault(1));
var locale = languages.First().Value;
return locale;
}
};
var factory = new IdentityServerServiceFactory();
factory.Register(new Registration<LocaleOptions>(opts));
factory.LocalizationService = new Registration<ILocalizationService, GlobalizedLocalizationService>();
=> Link to sample here.

Localization works on localhost but not in production enviroment

I have this website with english and portuguese support.
In localhost everything works fine and the content is translated based on the querystring parameter named "lang".
This chunk of code makes the trick in every page:
protected override void InitializeCulture()
{
SetCulture();
}
private void SetCulture()
{
var logger = Util.GetLogger();
string lang = Request.QueryString["lang"];
if (string.IsNullOrEmpty(lang)) { lang = "pt-br"; }
string sessionLang = (string)Session["lang"];
if (sessionLang != lang)
{
Session["lang"] = lang;
}
logger.Log(string.Format("Culture {0} found",lang));
UICulture = lang;
Culture = lang;
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(lang);
Thread.CurrentThread.CurrentUICulture = new CultureInfo(lang);
logger.Log(string.Format("Cultura {0} set", lang));
base.InitializeCulture();
}
Thanks to the logger I can say for sure that this method is called in both cases (production and localhost).
I have two resources files in App_GlobalResources folder:
-WebSitemapGlobal.en-us.resx;
-WebSitemapGlobal.resx;
Any clue?
Thanks in advance.
I can see you are little confused about localization in .NET.
First, if WebSiteMapGlobal.resx is in Brazilian Portuguese, there is no need to specify pt-br as culture, since the culture is neutral
Probably, the problem is due to calling base.InitializeCulture() after setting language. You should also have posted a little log about your page execution.
Also, a software engineering tip: you should call base.InitializeCulture() from void InitializeCulture() after SetCulture(). The result is identical (I just asked you to remove that call anyway) but it's a very good practice to call a base method from its override.

Resources